💻 프로그래밍

String

문자열

문자(character)의 시퀀스로 구성된 텍스트 데이터 타입. 대부분의 언어에서 불변(Immutable)하며, 다양한 조작 메서드를 제공합니다.

📖 상세 설명

String(문자열)은 문자(character)들의 순차적 시퀀스로 구성된 기본 데이터 타입입니다. 프로그래밍에서 텍스트 데이터를 표현하고 처리하는 핵심 자료구조로, 사용자 입력, 파일 내용, API 응답, 데이터베이스 레코드 등 거의 모든 곳에서 사용됩니다. 내부적으로는 문자 배열(char array)이나 바이트 시퀀스로 구현되며, 언어마다 표현 방식이 다릅니다.

불변성(Immutability)은 String의 핵심 특성입니다. Java, Python, JavaScript, Go 등 대부분의 현대 언어에서 String은 한 번 생성되면 변경할 수 없습니다. "hello"" world"를 더하면 기존 문자열이 수정되는 것이 아니라 새로운 문자열 "hello world"가 메모리에 생성됩니다. 이 특성은 스레드 안전성, 해시 키 사용, 문자열 인터닝(String Pool) 등의 이점을 제공합니다.

문자 인코딩은 문자를 바이트로 변환하는 방식입니다. ASCII는 영문자를 1바이트(0-127)로 표현하고, UTF-8은 가변 길이(1-4바이트)로 전 세계 문자를 표현합니다. UTF-16은 대부분의 문자를 2바이트로, 일부를 4바이트로 표현합니다. JavaScript와 Java는 내부적으로 UTF-16을 사용하고, Python 3와 Go는 UTF-8을 기본으로 합니다. 인코딩 불일치는 "깨진 문자" 버그의 주요 원인입니다.

실무 고려사항으로 문자열 연결 성능이 중요합니다. 반복문에서 += 연산은 매번 새 객체를 생성하여 O(n²) 시간복잡도가 됩니다. 대신 Java의 StringBuilder, Python의 "".join(list), JavaScript의 배열 join()을 사용하면 O(n)으로 개선됩니다. 대용량 텍스트 처리 시 메모리 효율을 위해 스트리밍 방식이나 Buffer를 활용합니다.

💻 코드 예제

// 1. 기본 문자열 조작
const str = "Hello, World!";

console.log(str.length);           // 13
console.log(str.toUpperCase());    // "HELLO, WORLD!"
console.log(str.slice(0, 5));      // "Hello"
console.log(str.split(", "));      // ["Hello", "World!"]
console.log(str.includes("World")); // true
console.log(str.indexOf("o"));     // 4

// 2. 템플릿 리터럴 (ES6+)
const name = "김개발";
const age = 28;
const greeting = `안녕하세요, ${name}님!
나이: ${age}세
내년 나이: ${age + 1}세`;
console.log(greeting);

// 3. 정규식 활용
const email = "user@example.com";
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
console.log(emailRegex.test(email)); // true

const text = "가격: 10,000원, 수량: 5개";
const numbers = text.match(/\d+/g);  // ["10", "000", "5"]
const cleaned = text.replace(/,/g, ""); // 쉼표 제거

// 4. 효율적인 문자열 연결
const items = ["사과", "바나나", "오렌지"];

// 비효율적 (매번 새 문자열 생성)
let bad = "";
for (const item of items) {
    bad += item + ", ";  // O(n²)
}

// 효율적 (배열 join 사용)
const good = items.join(", ");  // O(n)
console.log(good); // "사과, 바나나, 오렌지"

// 5. 유니코드 처리
const emoji = "👨‍👩‍👧‍👦";
console.log(emoji.length);        // 11 (서로게이트 페어 + ZWJ)
console.log([...emoji].length);   // 1 (스프레드로 제대로 분리)

// 한글 자모 분리
const korean = "한글";
console.log(korean.normalize("NFD").length); // 6 (자모 분리)
console.log(korean.normalize("NFC").length); // 2 (완성형)
# 1. 기본 문자열 조작
text = "Hello, World!"

print(len(text))              # 13
print(text.upper())           # "HELLO, WORLD!"
print(text[0:5])              # "Hello" (슬라이싱)
print(text.split(", "))       # ["Hello", "World!"]
print("World" in text)        # True
print(text.find("o"))         # 4

# 2. f-string (Python 3.6+)
name = "김개발"
age = 28
greeting = f"""안녕하세요, {name}님!
나이: {age}세
내년 나이: {age + 1}세"""
print(greeting)

# 디버깅용 (Python 3.8+)
print(f"{name=}, {age=}")  # name='김개발', age=28

# 3. 정규식 활용
import re

email = "user@example.com"
pattern = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
print(bool(re.match(pattern, email)))  # True

text = "가격: 10,000원, 수량: 5개"
numbers = re.findall(r'\d+', text)  # ['10', '000', '5']
cleaned = re.sub(r',', '', text)    # 쉼표 제거

# 4. 효율적인 문자열 연결
items = ["사과", "바나나", "오렌지"]

# 비효율적 (매번 새 문자열 생성)
bad = ""
for item in items:
    bad += item + ", "  # O(n²)

# 효율적 (join 사용)
good = ", ".join(items)  # O(n)
print(good)  # "사과, 바나나, 오렌지"

# 대용량 처리: StringIO 사용
from io import StringIO
buffer = StringIO()
for item in items:
    buffer.write(item)
    buffer.write(", ")
result = buffer.getvalue()

# 5. 인코딩/디코딩
korean = "한글"
encoded = korean.encode('utf-8')   # b'\xed\x95\x9c\xea\xb8\x80'
decoded = encoded.decode('utf-8')  # "한글"

# 인코딩 오류 처리
text_with_error = b'\xff\xfe'
safe = text_with_error.decode('utf-8', errors='replace')  # 깨진 문자 대체
// 1. 기본 문자열 조작
String str = "Hello, World!";

System.out.println(str.length());           // 13
System.out.println(str.toUpperCase());      // "HELLO, WORLD!"
System.out.println(str.substring(0, 5));    // "Hello"
System.out.println(str.split(", ")[0]);     // "Hello"
System.out.println(str.contains("World"));  // true
System.out.println(str.indexOf("o"));       // 4

// 2. 문자열 비교 (== vs equals)
String a = "hello";
String b = "hello";
String c = new String("hello");

System.out.println(a == b);        // true (String Pool)
System.out.println(a == c);        // false (다른 객체)
System.out.println(a.equals(c));   // true (내용 비교)

// 3. 정규식 활용
import java.util.regex.*;

String email = "user@example.com";
boolean isValid = email.matches("^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$");

String text = "가격: 10,000원";
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
    System.out.println(matcher.group());  // 10, 000
}

// 4. 효율적인 문자열 연결 (StringBuilder)
String[] items = {"사과", "바나나", "오렌지"};

// 비효율적 (매번 새 String 생성)
String bad = "";
for (String item : items) {
    bad += item + ", ";  // O(n²) - 내부적으로 StringBuilder 생성
}

// 효율적 (StringBuilder 재사용)
StringBuilder sb = new StringBuilder();
for (String item : items) {
    if (sb.length() > 0) sb.append(", ");
    sb.append(item);
}
String good = sb.toString();  // O(n)

// Java 8+ String.join
String joined = String.join(", ", items);

// 5. Text Block (Java 15+)
String json = """
    {
        "name": "김개발",
        "age": 28,
        "skills": ["Java", "Spring"]
    }
    """;

// 6. 인코딩 처리
import java.nio.charset.StandardCharsets;

String korean = "한글";
byte[] utf8Bytes = korean.getBytes(StandardCharsets.UTF_8);
String decoded = new String(utf8Bytes, StandardCharsets.UTF_8);

🗣️ 실무에서 이렇게 말하세요

💬 코드 리뷰에서 (문자열 연결 성능)
"이 for 루프에서 문자열 += 연산이 1만 번 반복되고 있네요. String이 불변이라 매번 새 객체가 생성되어서 O(n²)이 됩니다. StringBuilder로 바꾸거나 String.join() 쓰면 O(n)으로 개선되고, 제 테스트에서는 실행 시간이 2초에서 50ms로 줄었어요."
💬 면접에서 (불변성 설명)
"String의 불변성은 세 가지 이점이 있습니다. 첫째, 스레드 안전성 - 여러 스레드가 동시에 읽어도 안전합니다. 둘째, 해시맵 키로 사용 가능 - hashCode가 변하지 않으니까요. 셋째, String Pool을 통한 메모리 절약 - 같은 리터럴은 하나의 객체만 생성됩니다. 단점은 수정할 때마다 새 객체가 생성되어 GC 부담이 늘어나는 것인데, 이건 StringBuilder로 해결합니다."
💬 버그 리포트에서 (인코딩 이슈)
"한글이 깨지는 이유를 찾았습니다. 파일은 EUC-KR로 저장되어 있는데 서버에서 UTF-8로 읽고 있었어요. 파일을 UTF-8로 재저장하거나, InputStreamReader에 'EUC-KR' charset을 명시하면 해결됩니다. 앞으로는 모든 파일을 UTF-8 BOM 없이 저장하는 걸로 가이드 업데이트하죠."

⚠️ 흔한 실수 & 주의사항

반복문에서 문자열 += 연결

불변 String을 += 로 연결하면 매번 새 객체 생성 (O(n²)). StringBuilder, String.join(), 또는 언어별 권장 방식 사용. 1만 번 루프에서 100배 이상 성능 차이.

인코딩 미지정

파일 읽기/쓰기, HTTP 응답에서 인코딩 생략 시 시스템 기본값 사용 → 환경마다 다른 결과. 항상 UTF-8 명시적 지정. Content-Type 헤더에 charset=utf-8 포함.

SQL Injection 취약점

사용자 입력을 직접 SQL에 연결 ("SELECT * FROM users WHERE name='" + input + "'")하면 공격에 노출. 반드시 Prepared Statement나 ORM의 파라미터 바인딩 사용.

올바른 방법

StringBuilder/join으로 효율적 연결, 명시적 인코딩 지정, Prepared Statement로 SQL 주입 방지, == 대신 equals()로 내용 비교 (Java), 유니코드 길이는 [...str].length 사용.

🔗 관련 용어

📚 더 배우기