๐ŸŽฏ1์ฃผ์ฐจ Unit 6.2 โ€” StringBuilder vs StringBuffer

Psjยท์•ฝ 20์‹œ๊ฐ„ ์ „

F-lab

๋ชฉ๋ก ๋ณด๊ธฐ
42/42

๐ŸŽฏ Unit 6.2 โ€” StringBuilder vs StringBuffer

F-lab Java 1์ฃผ์ฐจ / Phase 6 / Unit 6.2 ๋ณธ๊ฒฉ ํ•™์Šต ์ž๋ฃŒ
9-์„น์…˜ ๋งˆ์Šคํ„ฐ ํ”„๋กฌํ”„ํŠธ ํ˜•์‹์œผ๋กœ ๊นŠ์ด ํŒŒํ—ค์นœ๋‹ค.

์„ ์ˆ˜ ์ง€์‹: Unit 6.1 (String ๊ณผ Constant Pool)
๋‹ค์Œ Unit: 6.3 โ€” ArrayList vs LinkedList

์ด Unit์˜ ์˜๋ฏธ: String ์˜ ๋ถˆ๋ณ€์„ฑ์„ ๋ณด์™„ํ•˜๋Š” ๊ฐ€๋ณ€ ๋ฒ„ํผ.
๋ฉด์ ‘ ๋‹จ๊ณจ (String + vs StringBuilder) + ์‹ค๋ฌด ํ•„์ˆ˜ (๋กœ๊ทธ, SQL, ๋ณด๊ณ ์„œ).
๋™์‹œ์„ฑ ์ธก๋ฉด์—์„œ ๋‘˜์˜ ์ •ํ™•ํ•œ ์ฐจ์ด.


๐ŸŒ 1. ์„ธ์ƒ ์† ๋น„์œ 

String = ์ธ์‡„๋œ ์ข…์ด / StringBuilder = ํ™”์ดํŠธ๋ณด๋“œ

์‹œ๋‚˜๋ฆฌ์˜ค 1 โ€” ์ธ์‡„๋œ ์ข…์ด (String)

  • ํ•œ๋ฒˆ ์ธ์‡„ํ•˜๋ฉด ๊ณ ์น  ์ˆ˜ ์—†์Œ
  • ์ˆ˜์ •ํ•˜๋ ค๋ฉด โ†’ ์ƒˆ ์ข…์ด ์ธ์‡„
  • 1000๋ฒˆ ์ˆ˜์ • โ†’ 1000์žฅ์˜ ์ข…์ด (๋Œ€๋ถ€๋ถ„ ๋ฒ„๋ ค์ง)
  • โ†’ ์ž์› ๋‚ญ๋น„ โš ๏ธ
String result = "";
for (int i = 0; i < 1000; i++) {
    result += "item" + i;  // ๋งค๋ฒˆ ์ƒˆ ์ข…์ด!
}
// โ†’ 1000์žฅ์˜ String ๊ฐ์ฒด โ†’ GC ๋ถ€๋‹ด

์‹œ๋‚˜๋ฆฌ์˜ค 2 โ€” ํ™”์ดํŠธ๋ณด๋“œ (StringBuilder)

  • ์ง€์šฐ๊ณ  ๋‹ค์‹œ ์“ธ ์ˆ˜ ์žˆ์Œ
  • ๊ฐ™์€ ๋ณด๋“œ์—์„œ ๊ณ„์† ์ˆ˜์ •
  • 1000๋ฒˆ ์ˆ˜์ • โ†’ 1๊ฐœ์˜ ๋ณด๋“œ
  • ๋งˆ์ง€๋ง‰์— ๊ฒฐ๊ณผ๋ฅผ ์ข…์ด๋กœ ์ธ์‡„ (toString())
  • โ†’ ํšจ์œจ์  โœ“
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("item").append(i);  // ๊ฐ™์€ ๋ณด๋“œ์— ์ถ”๊ฐ€
}
String result = sb.toString();  // ๋งˆ์ง€๋ง‰์— ์ข…์ด๋กœ

StringBuffer = ์ž ๊ธด ํ™”์ดํŠธ๋ณด๋“œ (๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ์•ˆ์ „)

์‹œ๋‚˜๋ฆฌ์˜ค 3 โ€” ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ๊ฐ™์ด ์“ฐ๋Š” ํ™”์ดํŠธ๋ณด๋“œ

  • ํ•œ ์‚ฌ๋žŒ์ด ์“ฐ๋Š” ๋™์•ˆ ๋‹ค๋ฅธ ์‚ฌ๋žŒ ๋Œ€๊ธฐ (lock)
  • ์ฐจ๋ก€๋กœ ํ•œ ๋ช…์”ฉ โ†’ ์ˆœ์„œ ๋ณด์žฅ
  • ๊ทธ๋Ÿฌ๋‚˜ ๋А๋ฆผ (๋Œ€๊ธฐ ์‹œ๊ฐ„)
  • โ†’ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ ์ ํ•ฉ
StringBuffer sb = new StringBuffer();  // ๋™๊ธฐํ™”๋จ
// ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

ํ•ต์‹ฌ ํ•œ ๋ฌธ์žฅ

"String ์€ ๋ถˆ๋ณ€, StringBuilder/Buffer ๋Š” ๊ฐ€๋ณ€. ๋‹จ์ผ ์Šค๋ ˆ๋“œ = StringBuilder, ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ = StringBuffer (๊ทธ๋Ÿฌ๋‚˜ ๊ฑฐ์˜ ์•ˆ ์”€)."

๋น„์œ  ์ •๋ฆฌ:

๋น„์œ ์ž๋ฐ” ํด๋ž˜์ŠคํŠน์„ฑ
์ธ์‡„๋œ ์ข…์ดString๋ถˆ๋ณ€, ์•ˆ์ „
๊ฐœ์ธ ํ™”์ดํŠธ๋ณด๋“œStringBuilder๊ฐ€๋ณ€, ๋น ๋ฆ„
๊ณต์šฉ ํ™”์ดํŠธ๋ณด๋“œStringBuffer๊ฐ€๋ณ€, ๋™๊ธฐํ™”, ๋А๋ฆผ

๐Ÿ”ฅ 2. ํƒ„์ƒ ๋ฐฐ๊ฒฝ

"String ์œผ๋กœ ๋‹ค ๋˜๋Š”๋ฐ ์™œ ๋˜ ๋งŒ๋“ค์—ˆ๋‚˜?"

์ž๋ฐ” ์ดˆ๊ธฐ (1.0):

  • String ๋งŒ ์žˆ์Œ
  • ๋ฌธ์ž์—ด ์กฐ์ž‘์€ ๋ชจ๋‘ String ์œผ๋กœ

๋ฌธ์ œ ๋ฐœ๊ฒฌ:

// ์ž๋ฐ” 1.0 ์‹œ๋Œ€ ์ฝ”๋“œ
String log = "";
for (int i = 0; i < 100000; i++) {
    log = log + "[" + i + "] event\n";  // ๋งค๋ฒˆ ์ƒˆ String!
}

์‹ค์ œ ๋™์ž‘:
1. log + "[" + i + "] event\n" โ†’ ์ƒˆ String ์ƒ์„ฑ
2. ๊ธฐ์กด log ๋Š” Garbage
3. 100,000 ๋ฒˆ ๋ฐ˜๋ณต โ†’ 100,000 ๊ฐœ String ์ƒ์„ฑ โ†’ 99,999 ๊ฐœ Garbage

์‹œ๊ฐ„ ๋ณต์žก๋„:

  • ๋งค๋ฒˆ String ์ „์ฒด ๋ณต์‚ฌ (๊ธธ์ด n)
  • n ๋ฒˆ ๋ฐ˜๋ณต = O(nยฒ)
  • 100,000 ์ž โ†’ 100์–ต ํšŒ ์—ฐ์‚ฐ โ†’ ์ˆ˜ ๋ถ„ ์†Œ์š”

์ž๋ฐ” 1.0 ์˜ ๋‹ต โ€” StringBuffer

์ž๋ฐ” 1.0 ๋ถ€ํ„ฐ StringBuffer ๋“ฑ์žฅ:

  • ๊ฐ€๋ณ€ char[] ๋ฒ„ํผ
  • ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ ๊ฐ™์€ ๋ฒ„ํผ์— ์ถ”๊ฐ€
  • O(n) ์‹œ๊ฐ„ ๋ณต์žก๋„

๊ทธ๋Ÿฌ๋‚˜ ํ•œ๊ณ„:

  • ๋ชจ๋“  ๋ฉ”์„œ๋“œ๊ฐ€ synchronized
  • ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ๋„ lock ๋น„์šฉ
  • 90% ์˜ ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค (๋‹จ์ผ ์Šค๋ ˆ๋“œ) ์—์„œ ๋ถˆํ•„์š”ํ•œ ๋น„์šฉ

Java 5 ์˜ ๋‹ต โ€” StringBuilder

Java 5 (2004):

  • StringBuffer ์˜ ๋™๊ธฐํ™” ์—†๋Š” ๋ฒ„์ „
  • ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ ๋” ๋น ๋ฆ„
  • ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋™์ผ
StringBuilder sb = new StringBuilder();  // ๋‹จ์ผ ์Šค๋ ˆ๋“œ์šฉ
sb.append("hello");

ํ˜„์žฌ ํ‘œ์ค€:

  • StringBuilder ๊ฐ€ ๊ธฐ๋ณธ ์„ ํƒ
  • StringBuffer ๋Š” ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ์—์„œ ๊ณต์œ ํ•  ๋•Œ๋งŒ

์ปดํŒŒ์ผ๋Ÿฌ์˜ ์ž๋™ ๋ณ€ํ™˜

Java 5+ ์ปดํŒŒ์ผ๋Ÿฌ:

String result = "Hello" + name + ", welcome";

โ†’ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ณ€ํ™˜:

String result = new StringBuilder()
    .append("Hello")
    .append(name)
    .append(", welcome")
    .toString();

๊ทธ๋Ÿฌ๋‚˜ ํ•œ๊ณ„ โ€” ๋ฐ˜๋ณต๋ฌธ์—์„œ๋Š” ์ž๋™ ๋ณ€ํ™˜ X:

String result = "";
for (String s : list) {
    result = result + s;  // ๋งค ๋ฐ˜๋ณต๋งˆ๋‹ค ์ƒˆ StringBuilder!
}
// โ†’ ์‚ฌ์‹ค์ƒ String ๋งŒ ์“ด ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋น„ํšจ์œจ

โ†’ ๋ฐ˜๋ณต๋ฌธ์—์„œ๋Š” ์ง์ ‘ StringBuilder ์‚ฌ์šฉ ํ•„์ˆ˜.


Java 9+ ์˜ invokedynamic

Java 9 ๋ถ€ํ„ฐ String + ์˜ ์ปดํŒŒ์ผ ๊ฒฐ๊ณผ๊ฐ€ ๋” ํšจ์œจ์ :

  • invokedynamic ์œผ๋กœ ๋Ÿฐํƒ€์ž„ ์ตœ์ ํ™”
  • StringConcatFactory ์‚ฌ์šฉ
  • ๋‹จ์ˆœ ํ•ฉ์น˜๊ธฐ๋Š” ๊ฑฐ์˜ StringBuilder ์ˆ˜์ค€

๊ทธ๋Ÿฌ๋‚˜ โ€” ๋ฐ˜๋ณต๋ฌธ์€ ์—ฌ์ „ํžˆ ์ง์ ‘ StringBuilder ๊ถŒ์žฅ.


ํ•ต์‹ฌ ํ†ต์ฐฐ

"๊ฐ€๋ณ€ ๋ฒ„ํผ๋Š” String ์˜ ๋ถˆ๋ณ€์„ฑ์„ ๋ณด์™„ํ•˜๋Š” ๋„๊ตฌ๋‹ค."

String ์˜ ๋ถˆ๋ณ€์„ฑ์€ ์•ˆ์ „, StringBuilder/Buffer ๋Š” ํšจ์œจ. ๋‘˜์ด ๊ณต์กดํ•˜๋Š” ์ด์œ .
์ž๋ฐ”๋Š” ์ฝ๊ธฐ/๊ณต์œ ๋Š” String, ๋งŒ๋“ค๊ธฐ๋Š” StringBuilder ์˜ ํŒจํ„ด ๊ถŒ์žฅ.
๋‹จ์ผ ์Šค๋ ˆ๋“œ = StringBuilder, ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ (๊ฑฐ์˜ ์—†๋Š” ์ผ€์ด์Šค) = StringBuffer.


๐Ÿ’ฃ 3. ์—†์œผ๋ฉด ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ

์‹œ๋‚˜๋ฆฌ์˜ค 1: ILIC ์˜ ๋ณด๊ณ ์„œ ์ƒ์„ฑ โ€” ์šด์˜ ์‚ฌ๊ณ 

// โŒ ๋น„ํšจ์œจ ์ฝ”๋“œ โ€” ์šด์˜์—์„œ ์ž์ฃผ ๋ณด์ž„
public String generateReport(List<Cargo> cargos) {
    String report = "=== ์šด์†ก ๋ณด๊ณ ์„œ ===\n";
    for (Cargo c : cargos) {
        report += c.getId() + " | " 
               + c.getOrigin() + " โ†’ " 
               + c.getDestination() + " | "
               + c.getStatus() + "\n";
    }
    report += "=== ๋ ===";
    return report;
}

ํ˜„์‹ค ์˜ํ–ฅ:

  • 1๋งŒ ๊ฑด ํ™”๋ฌผ ๋ณด๊ณ ์„œ โ†’ 1๋งŒ ๋ฒˆ String ์ƒˆ๋กœ ์ƒ์„ฑ
  • 1MB ์˜ ์ตœ์ข… ๋ฌธ์ž์—ด์„ 1๋งŒ ๋ฒˆ ๋ณต์‚ฌ โ†’ ์ˆ˜ GB ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ
  • GC ํญ์ฆ โ†’ API ์‘๋‹ต ์ง€์—ฐ
  • โ†’ ๋งค์›” ๋ณด๊ณ ์„œ ์ƒ์„ฑ ์‹œ ์„œ๋ฒ„ ๋ฉˆ์ถค

ํ•ด๊ฒฐ:

public String generateReport(List<Cargo> cargos) {
    StringBuilder sb = new StringBuilder();
    sb.append("=== ์šด์†ก ๋ณด๊ณ ์„œ ===\n");
    for (Cargo c : cargos) {
        sb.append(c.getId()).append(" | ")
          .append(c.getOrigin()).append(" โ†’ ")
          .append(c.getDestination()).append(" | ")
          .append(c.getStatus()).append("\n");
    }
    sb.append("=== ๋ ===");
    return sb.toString();
}

โ†’ ๋ฉ”๋ชจ๋ฆฌ โ†“, ์†๋„ โ†‘.


์‹œ๋‚˜๋ฆฌ์˜ค 2: ๋™์  SQL ์ƒ์„ฑ

// โŒ ๋น„ํšจ์œจ
public String buildQuery(SearchCriteria c) {
    String sql = "SELECT * FROM cargos WHERE 1=1";
    if (c.getOrigin() != null) {
        sql += " AND origin = '" + c.getOrigin() + "'";
    }
    if (c.getDestination() != null) {
        sql += " AND destination = '" + c.getDestination() + "'";
    }
    if (c.getStatusList() != null) {
        for (String status : c.getStatusList()) {
            sql += " AND status = '" + status + "'";  // ๋ฐ˜๋ณต!
        }
    }
    return sql;
}

๋ฌธ์ œ:

  • ๋‹จ์ˆœํ•œ ์กฐํšŒ์—์„œ๋Š” ์ฐจ์ด ๋ฏธ๋ฏธ
  • ๊ทธ๋Ÿฌ๋‚˜ ์šด์˜ ํ™˜๊ฒฝ์˜ ๋ฐฑ๋งŒ ๋ฒˆ ํ˜ธ์ถœ ์‹œ ๋ˆ„์  ๋น„์šฉ

์‹œ๋‚˜๋ฆฌ์˜ค 3: ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ๋™์‹œ ์ ‘๊ทผ

// โŒ ์œ„ํ—˜: ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ์—์„œ StringBuilder
public class LogCollector {
    private static StringBuilder log = new StringBuilder();
    
    public static void append(String msg) {
        log.append(msg);  // โŒ ๋™๊ธฐํ™” X โ†’ ๋ฐ์ดํ„ฐ ์†์‹ค
    }
}

๋ฌธ์ œ:

  • ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ append โ†’ race condition
  • ์ผ๋ถ€ ๋ฉ”์‹œ์ง€ ์†์‹ค ๋˜๋Š” ๊นจ์ง„ ๋ฐ์ดํ„ฐ
  • IndexOutOfBoundsException ๊ฐ€๋Šฅ

ํ•ด๊ฒฐ์ฑ… 1: StringBuffer

private static StringBuffer log = new StringBuffer();  // โœ“ ๋™๊ธฐํ™”

ํ•ด๊ฒฐ์ฑ… 2: ๋” ์ข‹์€ ๋ฐฉ๋ฒ•

// ์Šค๋ ˆ๋“œ๋ณ„ ๋…๋ฆฝ StringBuilder
private static ThreadLocal<StringBuilder> log = ThreadLocal.withInitial(StringBuilder::new);

// ๋˜๋Š” ๋™์‹œ์„ฑ ์ปฌ๋ ‰์…˜ ์‚ฌ์šฉ
private static Queue<String> messages = new ConcurrentLinkedQueue<>();

์‹œ๋‚˜๋ฆฌ์˜ค 4: ๋ฉด์ ‘ ๋‹จ๊ณจ ์งˆ๋ฌธ

"String, StringBuilder, StringBuffer ์˜ ์ฐจ์ด๋Š”?"

๋ชจ๋ฅด๋ฉด:

  • "์Œ... StringBuilder ๋Š” ๋น ๋ฅด๊ณ  StringBuffer ๋Š” ๋А๋ ค์š”?"
  • โ†’ ์‹œ๋‹ˆ์–ด ์ž๊ฒฉ ์˜์‹ฌ

์•Œ๋ฉด:

  • ๋ถˆ๋ณ€ vs ๊ฐ€๋ณ€
  • ๋™๊ธฐํ™” ์—ฌ๋ถ€ (StringBuffer = synchronized)
  • ์‹œ๊ฐ„ ๋ณต์žก๋„ (String + = O(nยฒ) vs StringBuilder = O(n))
  • ์‚ฌ์šฉ ์‹œ์  ๊ฒฐ์ • ๊ฐ€๋Šฅ

์‹œ๋‚˜๋ฆฌ์˜ค 5: ์ž‘์€ ์ฐจ์ด์˜ ๋ˆ„์  ์˜ํ–ฅ

// ์ผ๊ฒฌ ๋ฌธ์ œ ์—†์–ด ๋ณด์ด๋Š” ์ฝ”๋“œ
public String formatPhoneNumber(String raw) {
    String result = "";
    for (char c : raw.toCharArray()) {
        if (Character.isDigit(c)) {
            result += c;  // ์งง์€ ๋ฌธ์ž์—ด์ด์ง€๋งŒ...
        }
    }
    return formatHyphens(result);
}

๋ˆ„์  ์˜ํ–ฅ:

  • ๋ฉ”์„œ๋“œ ์ž์ฒด๋Š” ๋น ๋ฆ„ (~์ˆ˜ ฮผs)
  • ๊ทธ๋Ÿฌ๋‚˜ ๋งค API ํ˜ธ์ถœ๋งˆ๋‹ค ํ˜ธ์ถœ โ†’ ์ผ 100๋งŒ ๋ฒˆ
  • โ†’ ๋ˆ„์  GC ๋น„์šฉ + ์„ฑ๋Šฅ ์ €ํ•˜

ํ•ด๊ฒฐ:

public String formatPhoneNumber(String raw) {
    StringBuilder sb = new StringBuilder(raw.length());
    for (char c : raw.toCharArray()) {
        if (Character.isDigit(c)) {
            sb.append(c);
        }
    }
    return formatHyphens(sb.toString());
}

์‹œ๋‚˜๋ฆฌ์˜ค 6: API ์‘๋‹ต ๋นŒ๋”ฉ โ€” JSON ์ˆ˜๋™ ์ƒ์„ฑ

// ์‹ฌ๊ฐํ•˜๊ฒŒ ๋น„ํšจ์œจ
public String buildJson(List<Customer> customers) {
    String json = "[";
    for (int i = 0; i < customers.size(); i++) {
        Customer c = customers.get(i);
        json += "{\"id\":" + c.getId() 
             + ",\"name\":\"" + c.getName() + "\""
             + ",\"grade\":\"" + c.getGrade() + "\"}";
        if (i < customers.size() - 1) {
            json += ",";
        }
    }
    json += "]";
    return json;
}

๋” ์ข‹์€ ๋ฐฉ๋ฒ•:

  • JSON ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (Jackson, Gson) ์‚ฌ์šฉ
  • ๋งŒ์•ฝ ์ง์ ‘ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๋ฉด StringBuilder
public String buildJson(List<Customer> customers) {
    StringBuilder sb = new StringBuilder("[");
    for (int i = 0; i < customers.size(); i++) {
        Customer c = customers.get(i);
        sb.append("{\"id\":").append(c.getId())
          .append(",\"name\":\"").append(c.getName()).append("\"")
          .append(",\"grade\":\"").append(c.getGrade()).append("\"}");
        if (i < customers.size() - 1) {
            sb.append(',');
        }
    }
    sb.append("]");
    return sb.toString();
}

์˜ํ–ฅ ์ •๋ฆฌ

์‹œ๋‚˜๋ฆฌ์˜คStringBuilder ๋ชจ๋ฅด๋ฉด์•Œ๋ฉด
๋ณด๊ณ ์„œ ์ƒ์„ฑ๋ฉ”๋ชจ๋ฆฌ ํญ๋ฐœํšจ์œจ์ 
SQL ๋นŒ๋”ฉ๋ˆ„์  GC ๋ถ€๋‹ด์•ˆ์ •
๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œrace conditionStringBuffer ๋˜๋Š” ThreadLocal
๋ฉด์ ‘ํƒˆ๋ฝ์‹œ๋‹ˆ์–ด ๋‹ต๋ณ€
์ž‘์€ ๋ฉ”์„œ๋“œ ๋ˆ„์ ์ž ์žฌ ์„ฑ๋Šฅ ๋ฌธ์ œ์ฒ˜์Œ๋ถ€ํ„ฐ ์ตœ์ 

โ†’ ์ž๋ฐ” ์‹œ๋‹ˆ์–ด์˜ ๊ธฐ๋ณธ ๋„๊ตฌ.


โœ… 4. ํ•ด๊ฒฐ์ฑ… โ€” ๋‘ ํด๋ž˜์Šค์˜ ์ •ํ™•ํ•œ ์ดํ•ด

StringBuilder โญ (๋‹จ์ผ ์Šค๋ ˆ๋“œ ํ‘œ์ค€)

ํ•ต์‹ฌ ํŠน์ง•

public final class StringBuilder 
    extends AbstractStringBuilder
    implements Serializable, Comparable<StringBuilder>, CharSequence {
    
    // ๋ถ€๋ชจ ํด๋ž˜์Šค AbstractStringBuilder ์˜ ํ•„๋“œ
    // byte[] value;       (Java 9+) ๋˜๋Š” char[] (Java 8 ์ดํ•˜)
    // int count;          ํ˜„์žฌ ๊ธธ์ด
    // capacity = value.length
}

ํ•ต์‹ฌ:

  • ๊ฐ€๋ณ€ (mutable)
  • ๋™๊ธฐํ™” X (๋‹จ์ผ ์Šค๋ ˆ๋“œ)
  • ๋น ๋ฆ„
  • Java 5+ ๋„์ž…

์ฃผ์š” ๋ฉ”์„œ๋“œ

StringBuilder sb = new StringBuilder();

// === ์ถ”๊ฐ€ ===
sb.append("hello")           // ๋์— ์ถ”๊ฐ€
  .append(' ')
  .append(42)                // ์ž๋™ ๋ณ€ํ™˜ (int โ†’ "42")
  .append(true)              // ์ž๋™ ๋ณ€ํ™˜ (boolean โ†’ "true")
  .append(123.45);           // ์ž๋™ ๋ณ€ํ™˜ (double โ†’ "123.45")

// === ์‚ฝ์ž… ===
sb.insert(0, "Start: ");     // 0๋ฒˆ ์œ„์น˜์— ์‚ฝ์ž…

// === ์‚ญ์ œ ===
sb.delete(0, 7);             // 0~7 ์ธ๋ฑ์Šค ์‚ญ์ œ
sb.deleteCharAt(0);          // 0๋ฒˆ ๋ฌธ์ž ์‚ญ์ œ

// === ์น˜ํ™˜ ===
sb.replace(0, 5, "World");   // 0~5 ๋ฅผ "World" ๋กœ

// === ๋’ค์ง‘๊ธฐ ===
sb.reverse();

// === ์ •๋ณด ===
sb.length();                 // ํ˜„์žฌ ๋ฌธ์ž ์ˆ˜
sb.capacity();               // ํ˜„์žฌ ๋ฒ„ํผ ํฌ๊ธฐ
sb.charAt(0);                // ํŠน์ • ์œ„์น˜ ๋ฌธ์ž

// === ๋ณ€ํ™˜ ===
String result = sb.toString();  // String ์œผ๋กœ

Method Chaining (๋ฉ”์„œ๋“œ ์ฒด์ด๋‹)

String result = new StringBuilder()
    .append("Hello")
    .append(", ")
    .append(name)
    .append("!")
    .toString();

์ด๊ฒŒ ๊ฐ€๋Šฅํ•œ ์ด์œ :

  • ๋ชจ๋“  modify ๋ฉ”์„œ๋“œ๊ฐ€ this ๋ฐ˜ํ™˜
  • โ†’ ๊น”๋”ํ•œ ์ฝ”๋“œ + ๊ฐ์ฒด 1๊ฐœ๋กœ ์ž‘์—…
public StringBuilder append(String str) {
    super.append(str);
    return this;  // โ† ์ž๊ธฐ ์ž์‹  ๋ฐ˜ํ™˜
}

StringBuffer (๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ โ€” ๊ฑฐ์˜ ์•ˆ ์”€)

ํ•ต์‹ฌ ํŠน์ง•

public final class StringBuffer extends AbstractStringBuilder ... {
    
    @Override
    public synchronized StringBuffer append(String str) {  // โ† synchronized!
        toStringCache = null;
        super.append(str);
        return this;
    }
    
    // ๊ฑฐ์˜ ๋ชจ๋“  public ๋ฉ”์„œ๋“œ๊ฐ€ synchronized
}

ํ•ต์‹ฌ:

  • StringBuilder ์™€ ์ธํ„ฐํŽ˜์ด์Šค ๋™์ผ
  • ๋ชจ๋“  ๋ฉ”์„œ๋“œ synchronized โ†’ Thread-Safe
  • ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ๋„ lock ๋น„์šฉ โ†’ ๋А๋ฆผ
  • Java 1.0+

์„ฑ๋Šฅ ๋น„๊ต (1๋งŒ ๋ฒˆ append)

ํด๋ž˜์Šค์‹œ๊ฐ„์‚ฌ์šฉ ์‹œ์ 
String +~5์ดˆ์ ˆ๋Œ€ X
StringBuilder~5ms๋‹จ์ผ ์Šค๋ ˆ๋“œ (99%)
StringBuffer~10ms๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ (๋“œ๋ฌพ)

์…‹ ๋น„๊ต ํ‘œ โญโญ (๋ฉด์ ‘ ๋‹ต๋ณ€ ํ•ต์‹ฌ)

StringStringBuilderStringBuffer
๋ถˆ๋ณ€/๊ฐ€๋ณ€๋ถˆ๋ณ€๊ฐ€๋ณ€๊ฐ€๋ณ€
๋™๊ธฐํ™”(ํ•ด๋‹น ์—†์Œ)Xโœ“
์†๋„๋งค์šฐ ๋А๋ฆผ (๋ณ€๊ฒฝ ์‹œ)๋น ๋ฆ„๋ณดํ†ต
Thread-Safeโœ“ (๋ถˆ๋ณ€)โœ—โœ“
์‹œ๊ฐ„ ๋ณต์žก๋„O(nยฒ)O(n)O(n)
์–ธ์ œ์ฝ๊ธฐ/๊ณต์œ ๋‹จ์ผ ์Šค๋ ˆ๋“œ ๋นŒ๋”ฉ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ๊ณต์œ 
๋“ฑ์žฅJava 1.0Java 5Java 1.0

์‚ฌ์šฉ ๊ฒฐ์ • ๊ฐ€์ด๋“œ

๋ฌธ์ž์—ด์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ?
โ”œโ”€โ”€ ์ฝ๊ธฐ/๊ณต์œ ๋งŒ โ†’ String
โ”œโ”€โ”€ ๋งŒ๋“ค๊ธฐ (๋ฐ˜๋ณต ๋“ฑ)
โ”‚   โ”œโ”€โ”€ ๋‹จ์ผ ์Šค๋ ˆ๋“œ โ†’ StringBuilder โญ
โ”‚   โ””โ”€โ”€ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ๊ณต์œ  (๋“œ๋ฌพ)
โ”‚       โ”œโ”€โ”€ StringBuffer
โ”‚       โ””โ”€โ”€ ๋˜๋Š” ThreadLocal<StringBuilder>
โ””โ”€โ”€ ๋น„๊ต/์กฐํšŒ โ†’ String + equals

ํ˜„์‹ค โ€” 99% ์˜ ๊ฒฝ์šฐ:

  • ์ฝ๊ธฐ: String
  • ๋นŒ๋”ฉ: StringBuilder
  • StringBuffer ๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉ ์•ˆ ํ•จ

Java ์˜ ์ปดํŒŒ์ผ๋Ÿฌ ์ตœ์ ํ™”

String greeting = "Hello, " + name + "!";  // ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ณ€ํ™˜

Java 8 ๊นŒ์ง€:

String greeting = new StringBuilder()
    .append("Hello, ")
    .append(name)
    .append("!")
    .toString();

Java 9+ (invokedynamic):

// StringConcatFactory.makeConcatWithConstants() ์‚ฌ์šฉ
// ๋” ํšจ์œจ์ , ๋Ÿฐํƒ€์ž„ ์ตœ์ ํ™”

๊ทธ๋Ÿฌ๋‚˜ ํ•œ๊ณ„ โ€” ๋ฐ˜๋ณต๋ฌธ์€ ์ž๋™ ๋ณ€ํ™˜ X:

String result = "";
for (String s : list) {
    result += s;  // ๋งค ๋ฐ˜๋ณต๋งˆ๋‹ค ์ƒˆ StringBuilder
}
// โ†’ ์‚ฌ์‹ค์ƒ O(nยฒ)

โ†’ ๋ฐ˜๋ณต๋ฌธ์—์„  ์ง์ ‘ StringBuilder ์‚ฌ์šฉ.


๐Ÿ—๏ธ 5. ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ

๋‚ด๋ถ€ ๊ตฌ์กฐ (AbstractStringBuilder)

abstract class AbstractStringBuilder {
    byte[] value;     // ์‹ค์ œ ๋ฌธ์ž ๋ฐ์ดํ„ฐ (Java 9+, ์ด์ „์—” char[])
    byte coder;       // LATIN1 or UTF16 (Java 9+)
    int count;        // ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ๊ธธ์ด
    
    // capacity = value.length / (coder == UTF16 ? 2 : 1)
}

ํ•ต์‹ฌ ๊ฐœ๋…:

  • count โ€” ํ˜„์žฌ ๋ฌธ์ž์—ด ๊ธธ์ด (length() ๊ฐ€ ๋ฐ˜ํ™˜)
  • capacity โ€” ๋ฒ„ํผ ์ „์ฒด ํฌ๊ธฐ (count ๋ณด๋‹ค ํผ)

์ดˆ๊ธฐ capacity

new StringBuilder()              // capacity = 16 (๊ธฐ๋ณธ)
new StringBuilder(100)           // capacity = 100
new StringBuilder("hello")       // capacity = 5 + 16 = 21
new StringBuilder(initialString) // capacity = initialString.length() + 16

์™œ 16?:

  • ์ž๋ฐ” ํ‘œ์ค€ ๊ธฐ๋ณธ๊ฐ’
  • ์ž‘์€ String ์˜ ๊ฒฝ์šฐ ํ™•์žฅ ์—†์ด ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
  • ๋„ˆ๋ฌด ํฌ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„, ๋„ˆ๋ฌด ์ž‘์œผ๋ฉด ํ™•์žฅ ๋นˆ๋ฒˆ

์ž๋™ ํ™•์žฅ ๋ฉ”์ปค๋‹ˆ์ฆ˜

public void ensureCapacity(int minimumCapacity) {
    if (minimumCapacity > value.length) {
        int newCapacity = (value.length << 1) + 2;  // ํ˜„์žฌ * 2 + 2
        if (newCapacity < minimumCapacity) {
            newCapacity = minimumCapacity;
        }
        value = Arrays.copyOf(value, newCapacity);
    }
}

ํ™•์žฅ ๊ทœ์น™:
1. ์ƒˆ capacity = ํ˜„์žฌ * 2 + 2 (๋ณดํ†ต)
2. ๊ทธ๋ž˜๋„ ๋ถ€์กฑํ•˜๋ฉด = ํ•„์š”ํ•œ ํฌ๊ธฐ

์˜ˆ์‹œ:

StringBuilder sb = new StringBuilder();  // capacity = 16
sb.append("a".repeat(20));  // 16 โ†’ 34
sb.append("a".repeat(50));  // 34 โ†’ 84

ํ™•์žฅ ๋น„์šฉ

๋น„์šฉ:

  • ์ƒˆ ๋ฐฐ์—ด ํ• ๋‹น
  • ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ (Arrays.copyOf)
  • ์˜›๋‚  ๋ฐฐ์—ด GC ๋Œ€์ƒ

์‹œ๊ฐ„ ๋ณต์žก๋„:

  • ๋‹จ์ผ append = O(1) ํ‰๊ท  (๋ถ„ํ•  ์ƒํ™˜)
  • ํ™•์žฅ ๋ฐœ์ƒ ์‹œ = O(n)
  • n ๋ฒˆ append ์ „์ฒด = O(n) (๋ถ„ํ•  ์ƒํ™˜ ๋ถ„์„)

์ดˆ๊ธฐ capacity ์„ค์ •์˜ ํšจ๊ณผ

// โŒ ํ™•์žฅ ์—ฌ๋Ÿฌ ๋ฒˆ
StringBuilder sb = new StringBuilder();  // 16
for (int i = 0; i < 1000; i++) {
    sb.append("x");  // 16 โ†’ 34 โ†’ 70 โ†’ ... ์—ฌ๋Ÿฌ ๋ฒˆ ํ™•์žฅ
}

// โœ“ ํ•œ ๋ฒˆ์— ์ถฉ๋ถ„ํžˆ
StringBuilder sb = new StringBuilder(1024);  // 1024
for (int i = 0; i < 1000; i++) {
    sb.append("x");  // ํ™•์žฅ ์—†์Œ
}

โ†’ ์˜ˆ์ƒ ํฌ๊ธฐ๋ฅผ ์•Œ๋ฉด ์ดˆ๊ธฐ capacity ์ง€์ •.


StringBuffer ์˜ ๋™๊ธฐํ™”

// StringBuffer ์˜ ๋ฉ”์„œ๋“œ (๊ฐ„๋žตํ™”)
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

public synchronized String toString() {
    if (toStringCache == null) {
        return toStringCache = new String(value, 0, count);
    }
    return new String(toStringCache);
}

lock ๋ฉ”์ปค๋‹ˆ์ฆ˜:

  • ๊ฐ์ฒด ์ž์ฒด์— ๋ชจ๋‹ˆํ„ฐ (intrinsic lock)
  • ํ•œ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ โ†’ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ ๋Œ€๊ธฐ
  • ๋ฉ”์„œ๋“œ ์ข…๋ฃŒ ์‹œ โ†’ ๋‹ค์Œ ์Šค๋ ˆ๋“œ ์ง„์ž…

์„ฑ๋Šฅ ์˜ํ–ฅ:

  • ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ๋„ lock ํš๋“/ํ•ด์ œ ๋น„์šฉ
  • JIT ์ตœ์ ํ™”๋กœ ์ผ๋ถ€ ์™„ํ™” (Lock Elision)

toString() ์˜ ๋™์ž‘

public String toString() {
    return new String(value, 0, count);
}

ํ•ต์‹ฌ:

  • ๋‚ด๋ถ€ ๋ฐฐ์—ด์„ ๋ณต์‚ฌ ํ•ด์„œ ์ƒˆ String ์ƒ์„ฑ
  • StringBuilder ์˜ ๋ฒ„ํผ์™€ String ์€ ๋ณ„๊ฐœ
  • StringBuilder ์ˆ˜์ • ์‹œ String ์˜ํ–ฅ X
StringBuilder sb = new StringBuilder("hello");
String s = sb.toString();
sb.append(" world");
System.out.println(s);   // "hello" (๋ณ€ํ•˜์ง€ ์•Š์Œ)
System.out.println(sb);  // "hello world"

Java 9+ Compact Strings

abstract class AbstractStringBuilder {
    byte[] value;
    byte coder;  // LATIN1 (1 byte/char) or UTF16 (2 bytes/char)
}

ํšจ๊ณผ:

  • ASCII ๋งŒ โ†’ LATIN1 โ†’ ๋ฉ”๋ชจ๋ฆฌ 50% ์ ˆ์•ฝ
  • ํ•œ๊ธ€ ๋“ฑ โ†’ UTF16 โ†’ ๊ธฐ์กด๊ณผ ๋™์ผ

ILIC ๊ฐ™์€ ํ•œ๊ธ€/์˜๋ฌธ ํ˜ผ์šฉ ์‹œ์Šคํ…œ:

  • ํšจ๊ณผ ์ž‘์„ ์ˆ˜ ์žˆ์Œ (๋Œ€๋ถ€๋ถ„ UTF16)
  • ๊ทธ๋Ÿฌ๋‚˜ ์˜๋ฌธ ์ฝ”๋“œ/์ˆซ์ž๊ฐ€ ๋งŽ์€ ๋ถ€๋ถ„์—์„œ๋Š” ํšจ๊ณผ

+ vs append ์˜ ์ปดํŒŒ์ผ ๊ฒฐ๊ณผ (Java 9+)

// ์†Œ์Šค ์ฝ”๋“œ
String a = "Hello, " + name;
// Java 8 ์ปดํŒŒ์ผ ๊ฒฐ๊ณผ (StringBuilder ์‚ฌ์šฉ)
String a = new StringBuilder()
    .append("Hello, ")
    .append(name)
    .toString();
// Java 9+ ์ปดํŒŒ์ผ ๊ฒฐ๊ณผ (invokedynamic)
String a = StringConcatFactory.makeConcatWithConstants(
    MethodHandles.lookup(),
    "concat",
    MethodType.methodType(String.class, String.class),
    "Hello, \u0001"  // ํŒจํ„ด
).invoke(name);

โ†’ Java 9+ ๋Š” ๋” ํšจ์œจ์ ์ด์ง€๋งŒ, ๋ฐ˜๋ณต๋ฌธ์—์„  ์—ฌ์ „ํžˆ ์ง์ ‘ StringBuilder ๊ถŒ์žฅ.


๐Ÿ’ป 6. ์‹ค์ „ ์ฝ”๋“œ ์˜ˆ์‹œ

์˜ˆ์‹œ 1: ILIC ์˜ ํšจ์œจ์ ์ธ ๋ณด๊ณ ์„œ ์ƒ์„ฑ

@Service
public class ReportService {
    
    public String generateMonthlyReport(List<Cargo> cargos) {
        // ์˜ˆ์ƒ ํฌ๊ธฐ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐ (์„ ํƒ์ , ๋” ํšจ์œจ์ )
        int expectedSize = cargos.size() * 100;  // ํ‰๊ท  100 ์ž/์ค„
        StringBuilder sb = new StringBuilder(expectedSize);
        
        // ํ—ค๋”
        sb.append("=== ์›”๊ฐ„ ์šด์†ก ๋ณด๊ณ ์„œ ===\n")
          .append("์ด ํ™”๋ฌผ ์ˆ˜: ").append(cargos.size()).append("\n")
          .append("์ƒ์„ฑ ์‹œ๊ฐ: ").append(LocalDateTime.now()).append("\n\n");
        
        // ๋ณธ๋ฌธ
        for (Cargo c : cargos) {
            sb.append(String.format("%5d", c.getId()))
              .append(" | ").append(c.getOrigin())
              .append(" โ†’ ").append(c.getDestination())
              .append(" | ").append(c.getStatus())
              .append("\n");
        }
        
        // ํ‘ธํ„ฐ
        sb.append("\n=== ๋ ===");
        
        return sb.toString();
    }
}

์˜ˆ์‹œ 2: ๋™์  SQL ๋นŒ๋”

public class CargoQueryBuilder {
    
    public String build(SearchCriteria c) {
        StringBuilder sql = new StringBuilder(
            "SELECT * FROM cargos WHERE 1=1"
        );
        List<Object> params = new ArrayList<>();
        
        if (c.getOrigin() != null) {
            sql.append(" AND origin = ?");
            params.add(c.getOrigin());
        }
        if (c.getDestination() != null) {
            sql.append(" AND destination = ?");
            params.add(c.getDestination());
        }
        if (c.getStatusList() != null && !c.getStatusList().isEmpty()) {
            sql.append(" AND status IN (");
            for (int i = 0; i < c.getStatusList().size(); i++) {
                if (i > 0) sql.append(",");
                sql.append("?");
                params.add(c.getStatusList().get(i));
            }
            sql.append(")");
        }
        
        return sql.toString();
    }
}

๋ณด๋„ˆ์Šค โ€” ๋” ์•ˆ์ „: PreparedStatement + JOIN ๋“ฑ ORM ํ™œ์šฉ ๊ถŒ์žฅ. ์œ„๋Š” ํ•™์Šต์šฉ ์˜ˆ์‹œ.


์˜ˆ์‹œ 3: CSV ํŒŒ์‹ฑ / ์ƒ์„ฑ

public class CsvService {
    
    // โœ“ StringBuilder ๋กœ CSV ๋นŒ๋”ฉ
    public String toCsv(List<Customer> customers) {
        StringBuilder sb = new StringBuilder();
        
        // ํ—ค๋”
        sb.append("ID,Name,Email,Grade\n");
        
        // ๋ฐ์ดํ„ฐ
        for (Customer c : customers) {
            sb.append(c.getId()).append(',')
              .append(escape(c.getName())).append(',')
              .append(escape(c.getEmail())).append(',')
              .append(c.getGrade())
              .append('\n');
        }
        
        return sb.toString();
    }
    
    // ๊ฐ„๋‹จํ•œ CSV ์ด์Šค์ผ€์ดํ”„
    private String escape(String value) {
        if (value == null) return "";
        if (value.contains(",") || value.contains("\"")) {
            return "\"" + value.replace("\"", "\"\"") + "\"";
        }
        return value;
    }
}

์˜ˆ์‹œ 4: ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ โ€” StringBuffer ์‹œ์—ฐ

// โŒ StringBuilder ์‚ฌ์šฉ ์‹œ ์œ„ํ—˜
public class UnsafeLogger {
    private static final StringBuilder log = new StringBuilder();
    
    public static void append(String msg) {
        log.append(msg).append('\n');  // race condition!
    }
}

// โœ“ StringBuffer ์‚ฌ์šฉ
public class SafeLogger {
    private static final StringBuffer log = new StringBuffer();
    
    public static void append(String msg) {
        log.append(msg).append('\n');  // synchronized
    }
}

// โœ“ ๋” ์ข‹์€ ๋ฐฉ๋ฒ•: ThreadLocal
public class BetterLogger {
    private static final ThreadLocal<StringBuilder> log = 
        ThreadLocal.withInitial(StringBuilder::new);
    
    public static void append(String msg) {
        log.get().append(msg).append('\n');  // ์Šค๋ ˆ๋“œ๋ณ„ ๋…๋ฆฝ
    }
    
    public static String getLog() {
        return log.get().toString();
    }
}

์˜ˆ์‹œ 5: ์„ฑ๋Šฅ ๋น„๊ต ๋ฒค์น˜๋งˆํฌ

@Benchmark
public class ConcatBenchmark {
    
    private static final int N = 100_000;
    
    @Benchmark
    public String stringConcat() {
        String result = "";
        for (int i = 0; i < N; i++) {
            result += "x";
        }
        return result;
        // ๊ฒฐ๊ณผ: ~์ˆ˜ ๋ถ„ (O(nยฒ))
    }
    
    @Benchmark
    public String stringBuilder() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < N; i++) {
            sb.append("x");
        }
        return sb.toString();
        // ๊ฒฐ๊ณผ: ~5ms (O(n))
    }
    
    @Benchmark
    public String stringBuilderWithCapacity() {
        StringBuilder sb = new StringBuilder(N);  // ์ดˆ๊ธฐ capacity
        for (int i = 0; i < N; i++) {
            sb.append("x");
        }
        return sb.toString();
        // ๊ฒฐ๊ณผ: ~3ms (ํ™•์žฅ X)
    }
    
    @Benchmark
    public String stringBuffer() {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < N; i++) {
            sb.append("x");
        }
        return sb.toString();
        // ๊ฒฐ๊ณผ: ~10ms (synchronized)
    }
    
    @Benchmark
    public String streamCollect() {
        return IntStream.range(0, N)
            .mapToObj(i -> "x")
            .collect(Collectors.joining());
        // ๊ฒฐ๊ณผ: ~10ms (๋‚ด๋ถ€์ ์œผ๋กœ StringBuilder ์‚ฌ์šฉ)
    }
}

์˜ˆ์‹œ 6: ILIC โ€” ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ๋นŒ๋”

@Service
public class NotificationService {
    
    public String buildShipmentNotification(Shipment shipment) {
        StringBuilder sb = new StringBuilder();
        
        sb.append("[ILIC ์šด์†ก ์•Œ๋ฆผ]\n\n");
        sb.append("ํ™”๋ฌผ ๋ฒˆํ˜ธ: ").append(shipment.getId()).append("\n");
        sb.append("ํ˜„์žฌ ์ƒํƒœ: ").append(shipment.getStatus()).append("\n");
        
        if (shipment.getCurrentLocation() != null) {
            sb.append("ํ˜„์žฌ ์œ„์น˜: ").append(shipment.getCurrentLocation()).append("\n");
        }
        
        sb.append("์ถœ๋ฐœ์ง€: ").append(shipment.getOrigin()).append("\n");
        sb.append("๋„์ฐฉ์ง€: ").append(shipment.getDestination()).append("\n");
        
        if (shipment.getEstimatedArrival() != null) {
            sb.append("์˜ˆ์ƒ ๋„์ฐฉ: ")
              .append(shipment.getEstimatedArrival().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
              .append("\n");
        }
        
        sb.append("\n์ž์„ธํ•œ ์ •๋ณด๋Š” ILIC ์•ฑ์—์„œ ํ™•์ธํ•˜์„ธ์š”.");
        
        return sb.toString();
    }
}

์˜ˆ์‹œ 7: String.format vs StringBuilder

// ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋น„๊ต
public class FormattingDemo {
    
    // ๋ฐฉ๋ฒ• 1: String.format (๊ฐ„๊ฒฐ, ์•ฝ๊ฐ„ ๋А๋ฆผ)
    public String formatV1(Customer c) {
        return String.format("[%d] %s (%s)", 
            c.getId(), c.getName(), c.getGrade());
    }
    
    // ๋ฐฉ๋ฒ• 2: StringBuilder (์žฅํ™ฉ, ๋น ๋ฆ„)
    public String formatV2(Customer c) {
        return new StringBuilder()
            .append('[').append(c.getId()).append("] ")
            .append(c.getName()).append(" (")
            .append(c.getGrade()).append(')')
            .toString();
    }
    
    // ๋ฐฉ๋ฒ• 3: + ์—ฐ์‚ฐ (Java 9+ ์—์„  ๋น„์Šท)
    public String formatV3(Customer c) {
        return "[" + c.getId() + "] " + c.getName() + " (" + c.getGrade() + ")";
    }
}

์„ ํƒ ๊ธฐ์ค€:

  • ๋‹จ์ˆœํ•œ ํฌ๋งท: + ๋˜๋Š” String.format
  • ๋ฐ˜๋ณต๋ฌธ์—์„œ ๋นŒ๋”ฉ: StringBuilder ํ•„์ˆ˜
  • ๋กœ๊น…: ์ตœ์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ž์ฒด ์ตœ์ ํ™”

์˜ˆ์‹œ 8: Stream API ์™€ ํ•จ๊ป˜

// โœ“ Stream + Collectors.joining
public String getCustomerNames(List<Customer> customers) {
    return customers.stream()
        .map(Customer::getName)
        .collect(Collectors.joining(", "));
}

// ๊ฒฐ๊ณผ: "Alice, Bob, Charlie"

// ๋‚ด๋ถ€์ ์œผ๋กœ StringBuilder ์‚ฌ์šฉ โ€” ํšจ์œจ์ 

// ๋” ์ •๊ตํ•œ ํ˜•ํƒœ
public String formatCustomerList(List<Customer> customers) {
    return customers.stream()
        .map(c -> String.format("[%d] %s", c.getId(), c.getName()))
        .collect(Collectors.joining(
            "\n",         // ๊ตฌ๋ถ„์ž
            "=== ๊ณ ๊ฐ ๋ชฉ๋ก ===\n",  // ์‹œ์ž‘
            "\n=== ๋ ==="           // ๋
        ));
}

โš ๏ธ 7. ์ฃผ์˜์‚ฌํ•ญ & ํ”ํ•œ ์‹ค์ˆ˜

์‹ค์ˆ˜ 1: ๋ฐ˜๋ณต๋ฌธ์—์„œ String +

// โŒ O(nยฒ)
String result = "";
for (String s : list) {
    result += s;
}

// โœ“ O(n)
StringBuilder sb = new StringBuilder();
for (String s : list) {
    sb.append(s);
}
String result = sb.toString();

์‹ค์ˆ˜ 2: ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ StringBuffer

// โŒ ๋ถˆํ•„์š”ํ•œ ๋™๊ธฐํ™” ๋น„์šฉ
StringBuffer sb = new StringBuffer();
// ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ๋งŒ ์‚ฌ์šฉ

// โœ“
StringBuilder sb = new StringBuilder();

์‹ค์ˆ˜ 3: ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ์—์„œ StringBuilder

// โŒ race condition!
public class Logger {
    private static StringBuilder log = new StringBuilder();
    
    public static void log(String msg) {
        log.append(msg);  // ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœ ์‹œ ์œ„ํ—˜!
    }
}

// โœ“ StringBuffer ๋˜๋Š” ThreadLocal
public class Logger {
    private static StringBuffer log = new StringBuffer();
    // ๋˜๋Š”
    private static ThreadLocal<StringBuilder> log = 
        ThreadLocal.withInitial(StringBuilder::new);
}

์‹ค์ˆ˜ 4: capacity ๋ฌด์‹œ (์ž ์žฌ์  ๋น„ํšจ์œจ)

// โŒ ํ™•์žฅ ์—ฌ๋Ÿฌ ๋ฒˆ
StringBuilder sb = new StringBuilder();  // ๊ธฐ๋ณธ 16
for (int i = 0; i < 10000; i++) {
    sb.append(largeData);  // ์—ฌ๋Ÿฌ ๋ฒˆ ํ™•์žฅ
}

// โœ“ ์˜ˆ์ƒ ํฌ๊ธฐ ์ง€์ •
StringBuilder sb = new StringBuilder(10000 * 100);  // 100์ž/์ค„ ๊ฐ€์ •
for (int i = 0; i < 10000; i++) {
    sb.append(largeData);
}

์‹ค์ˆ˜ 5: toString() ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœ

StringBuilder sb = new StringBuilder();
// ... ๋นŒ๋”ฉ ...

String s1 = sb.toString();  // ์ƒˆ String ์ƒ์„ฑ
String s2 = sb.toString();  // ๋˜ ์ƒˆ String ์ƒ์„ฑ!

// โœ“ ํ•œ ๋ฒˆ๋งŒ
String result = sb.toString();
String s1 = result;
String s2 = result;

์‹ค์ˆ˜ 6: ์งง์€ ๋ฌธ์ž์—ด์—์„œ๋„ StringBuilder

// โŒ ๊ณผํ•œ ์‚ฌ์šฉ
public String greet(String name) {
    StringBuilder sb = new StringBuilder();
    sb.append("Hello, ");
    sb.append(name);
    sb.append("!");
    return sb.toString();
}

// โœ“ ๋‹จ์ˆœํ•œ ๊ฒฐํ•ฉ์€ + ๋กœ ์ถฉ๋ถ„ (Java 9+ ํšจ์œจ์ )
public String greet(String name) {
    return "Hello, " + name + "!";
}

์›์น™: ๋ฐ˜๋ณต๋ฌธ์ด ์•„๋‹ˆ๋ฉด + ๋„ OK.


์‹ค์ˆ˜ 7: equals ๋กœ StringBuilder ๋น„๊ต

StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = new StringBuilder("hello");

sb1.equals(sb2);  // โŒ false! (Object.equals โ€” ์ฐธ์กฐ ๋น„๊ต)

์ด์œ :

  • StringBuilder ๋Š” equals() ์˜ค๋ฒ„๋ผ์ด๋“œ X
  • ๋น„๊ตํ•˜๋ ค๋ฉด toString().equals() ๋˜๋Š” compareTo()
// โœ“
sb1.toString().equals(sb2.toString());  // true

์‹ค์ˆ˜ 8: ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ์—์„œ toString ๋„ ์œ„ํ—˜

// โŒ StringBuffer ๋ผ๋„ ๋‹ค์Œ์€ ์•ˆ์ „ X
StringBuffer log = new StringBuffer();

// Thread 1
log.append("event1");
String snapshot = log.toString();  // ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ˆ˜์ • ์ค‘์ผ ์ˆ˜ ์žˆ์Œ

// ์ฆ‰, ๊ฐœ๋ณ„ ๋ฉ”์„œ๋“œ๋Š” ๋™๊ธฐํ™”๋˜์ง€๋งŒ ์—ฌ๋Ÿฌ ๋ฉ”์„œ๋“œ ๊ฐ„์€ X

ํ•ด๊ฒฐ: ์™ธ๋ถ€ ๋™๊ธฐํ™” ๋˜๋Š” ๋‹ค๋ฅธ ํŒจํ„ด.


๐Ÿ”— 8. ์—ฐ๊ด€ ๊ฐœ๋… ๋งต

Phase 6 (๋ฐ์ดํ„ฐ ๋‹ค๋ฃจ๊ธฐ) ์—์„œ์˜ ์œ„์น˜

[Unit 6.1: String + Constant Pool] โœ“
        โ†“
[Unit 6.2: StringBuilder vs StringBuffer] โ† ์ง€๊ธˆ ์—ฌ๊ธฐ โ˜…
        โ†“
[Unit 6.3: ArrayList vs LinkedList] (๋‹ค์Œ)
        โ†“
[Unit 6.4: HashMap ๋‚ด๋ถ€ ๊ตฌ์กฐ] โ˜…โ˜…โ˜…
        โ†“
[Unit 6.5: TreeMap, LinkedHashMap]

Phase 4-5 ์™€์˜ ์—ฐ๊ฒฐ

ํ•™์Šต์—ฐ๊ฒฐ
Unit 4.1 (Heap)StringBuilder ์˜ char[] ๋„ Heap
Unit 5.1 (GC)String + ์˜ Garbage ํญ๋ฐœ = GC ๋ถ€๋‹ด
Unit 6.1 (String)๊ฐ€๋ณ€ vs ๋ถˆ๋ณ€์˜ ๋Œ€์กฐ

Stream API ์™€์˜ ๊ด€๊ณ„

// Stream + Collectors.joining ์€ ๋‚ด๋ถ€์ ์œผ๋กœ StringBuilder ์‚ฌ์šฉ
list.stream().collect(Collectors.joining(","))

// JDK ์˜ StringJoiner ๋„ ๋‚ด๋ถ€์— StringBuilder
StringJoiner sj = new StringJoiner(", ", "[", "]");
sj.add("a").add("b").add("c");
sj.toString();  // "[a, b, c]"

๋™์‹œ์„ฑ ํŒจํ„ด ๋น„๊ต

๋ฐฉ์‹์„ค๋ช…์ ํ•ฉ
StringBuffersynchronized ๋ฉ”์„œ๋“œ์˜› ์ฝ”๋“œ
ThreadLocal์Šค๋ ˆ๋“œ๋ณ„ ๋…๋ฆฝ์ผ๋ฐ˜์ 
ConcurrentLinkedQueue + ๋‚˜์ค‘ ํ•ฉ์น˜๊ธฐ๋น„๋™๊ธฐ ์ˆ˜์ง‘ ํ›„ ์ผ๊ด„๊ณ ์„ฑ๋Šฅ
Reactive (Flux ๋“ฑ)์ŠคํŠธ๋ฆผ ๊ธฐ๋ฐ˜์ตœ์‹ 

๋‹ค๋ฅธ ์–ธ์–ด์™€ ๋น„๊ต

์–ธ์–ด๊ฐ€๋ณ€ String
JavaStringBuilder, StringBuffer
C#StringBuilder (Java ์™€ ๋น„์Šท)
Pythonlist + ''.join() (๊ด€์šฉ ํŒจํ„ด)
JavaScriptArray + .join() ๋˜๋Š” += (V8 ์ด ์ตœ์ ํ™”)
C++std::string (๊ฐ€๋ณ€, +=)
RustString (๊ฐ€๋ณ€), &str (๋ถˆ๋ณ€)

๋ฉด์ ‘ ๋‹จ๊ณจ ์งˆ๋ฌธ ๋งคํ•‘

์งˆ๋ฌธ์ด Unit ์—์„œ์˜ ๋‹ต
String, StringBuilder, StringBuffer ์ฐจ์ด?๋ถˆ๋ณ€ / ๊ฐ€๋ณ€ / ๋™๊ธฐํ™”
์‹œ๊ฐ„ ๋ณต์žก๋„?String + = O(nยฒ), StringBuilder = O(n)
์–ธ์ œ ์–ด๋–ค ๊ฑฐ ์“ฐ๋‚˜?๋‹จ์ผ=Builder, ๋ฉ€ํ‹ฐ=Buffer (๋“œ๋ฌพ)
capacity ์™€ length ์ฐจ์ด?๋ฒ„ํผ ํฌ๊ธฐ vs ์‹ค์ œ ๊ธธ์ด
์ž๋™ ํ™•์žฅ์€?* 2 + 2

๐Ÿ“ 9. ํ•ต์‹ฌ ์š”์•ฝ โ€” 3์ค„ ์ •๋ฆฌ

1๏ธโƒฃ String ์€ ๋ถˆ๋ณ€, StringBuilder/Buffer ๋Š” ๊ฐ€๋ณ€. ์…‹์˜ ์ •ํ™•ํ•œ ์ฐจ์ด๋ฅผ ์•Œ์•„์•ผ.

String = ๋ถˆ๋ณ€, ๋น„๊ต/๊ณต์œ ์— ์ ํ•ฉ, ๋งค ๋ณ€๊ฒฝ ์‹œ ์ƒˆ ๊ฐ์ฒด (O(nยฒ)). StringBuilder = ๊ฐ€๋ณ€, ๋‹จ์ผ ์Šค๋ ˆ๋“œ, ๋น ๋ฆ„, ๋™๊ธฐํ™” X (Java 5+, ํ‘œ์ค€ ์„ ํƒ). StringBuffer = ๊ฐ€๋ณ€, ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ, synchronized, ๋А๋ฆผ (Java 1.0+, ๊ฑฐ์˜ ์•ˆ ์”€). ๋ชจ๋‘ ๊ฐ™์€ ์ธํ„ฐํŽ˜์ด์Šค (append, insert, delete, replace).

2๏ธโƒฃ ๋ฐ˜๋ณต๋ฌธ์—์„  ๋ฌด์กฐ๊ฑด StringBuilder. ๋‹จ์ˆœ ๊ฒฐํ•ฉ์€ + ๋„ OK.

๋ฐ˜๋ณต๋ฌธ์—์„œ String + ๋Š” ๋งค๋ฒˆ ์ƒˆ ๊ฐ์ฒด โ†’ O(nยฒ). StringBuilder ๋Š” ๊ฐ™์€ ๋ฒ„ํผ์— ์ถ”๊ฐ€ โ†’ O(n). ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋‹จ์ˆœ ๊ฒฐํ•ฉ (a + b + c) ์€ ์ž๋™ ๋ณ€ํ™˜ (Java 9+ invokedynamic ์œผ๋กœ ๋” ํšจ์œจ์ ). ๊ทธ๋Ÿฌ๋‚˜ ๋ฐ˜๋ณต๋ฌธ์€ ์ž๋™ ๋ณ€ํ™˜ X. ์˜ˆ์ƒ ํฌ๊ธฐ ์•Œ๋ฉด ์ดˆ๊ธฐ capacity ์ง€์ • (ํ™•์žฅ ๋น„์šฉ ์ ˆ์•ฝ).

3๏ธโƒฃ ๋‹จ์ผ ์Šค๋ ˆ๋“œ = StringBuilder, ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ = ThreadLocal ๊ถŒ์žฅ.

StringBuffer ์˜ synchronized ๋Š” ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ๋„ ๋น„์šฉ. ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ์—์„œ๋„ StringBuffer ๋ณด๋‹ค ThreadLocal\<StringBuilder> ๋˜๋Š” ConcurrentLinkedQueue + ์ผ๊ด„ ์ฒ˜๋ฆฌ ๊ฐ€ ๋ณดํ†ต ๋” ํšจ์œจ์ . toString() ์€ ๋‚ด๋ถ€ ๋ฐฐ์—ด ๋ณต์‚ฌ โ†’ ์ƒˆ String. ILIC ์˜ ๋ณด๊ณ ์„œ/SQL/JSON ๋นŒ๋”ฉ์—์„œ StringBuilder ๊ฐ€ ์ •๋‹ต. Stream + Collectors.joining ๋„ ๋‚ด๋ถ€์— StringBuilder.


๐ŸŽ“ ํ•™์Šต ์ž๊ธฐ ์ ๊ฒ€

๊ธฐ๋ณธ ์ดํ•ด

  • String, StringBuilder, StringBuffer ์…‹์˜ ์ฐจ์ด๋ฅผ ํ‘œ๋กœ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋‹ค
  • StringBuilder ๊ฐ€ ๋น ๋ฅธ ์ด์œ ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ ๊ด€์ ์—์„œ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • StringBuffer ์˜ synchronized ์˜ ๋น„์šฉ์„ ์•ˆ๋‹ค
  • capacity ์™€ length ์˜ ์ฐจ์ด๋ฅผ ์•ˆ๋‹ค

์‹ค์ „ ์ ์šฉ

  • ๋ฐ˜๋ณต๋ฌธ์—์„œ StringBuilder ์ž๋™ ์‚ฌ์šฉ
  • ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ์ ์ ˆํ•œ ์„ ํƒ ๊ฐ€๋Šฅ
  • ์˜ˆ์ƒ ํฌ๊ธฐ์— ๋งž๋Š” ์ดˆ๊ธฐ capacity ์ง€์ •
  • Stream + Collectors.joining ํ™œ์šฉ

๋ฉด์ ‘ ๋Œ€๋น„ (5๋ถ„ ๋‹ต๋ณ€)

  • "String, StringBuilder, StringBuffer ์ฐจ์ด?" ๋‹ต๋ณ€ ๊ฐ€๋Šฅ
  • "์™œ String + ๊ฐ€ ๋น„ํšจ์œจ์ธ๊ฐ€?" ๋ฉ”๋ชจ๋ฆฌ ๊ทธ๋ฆผ ๋‹ต๋ณ€
  • "์–ธ์ œ ์–ด๋–ค ๊ฑฐ ์“ฐ๋‚˜?" ์ •ํ™•ํ•œ ๋‹ต๋ณ€
  • capacity ์ž๋™ ํ™•์žฅ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์„ค๋ช…

์ž๊ธฐ ์ ๊ฒ€ ์งˆ๋ฌธ ๋‹ต๋ณ€

Q1: String result = result + "x"; ๋ฅผ 1๋งŒ ๋ฒˆ ๋ฐ˜๋ณตํ•˜๋ฉด ์™œ ๋น„ํšจ์œจ์ ์ธ์ง€ ๋ฉ”๋ชจ๋ฆฌ ๊ด€์ ์—์„œ ์„ค๋ช…ํ•˜๋ผ.

ํ•œ ์ค„ ๋‹ต: ๋งค๋ฒˆ ์ƒˆ String ๊ฐ์ฒด ์ƒ์„ฑ + ๊ธฐ์กด ๊ฐ์ฒด ๋ณต์‚ฌ โ†’ O(nยฒ) + GC ๋ถ€๋‹ด.

์ƒ์„ธ ์„ค๋ช…:

Step 1: ์ฒซ ๋ฐ˜๋ณต (i=0)

String result = "";  // ๋นˆ String ๊ฐ์ฒด #1
result = result + "x";  // ์–ด๋–ป๊ฒŒ ๋™์ž‘?

์‹ค์ œ ๋™์ž‘:
1. result + "x" ์‹คํ–‰
2. ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ โ†’ new StringBuilder(result).append("x").toString() ์œผ๋กœ ๋ณ€ํ™˜
3. โ†’ ์ƒˆ String ๊ฐ์ฒด #2 ("x") ์ƒ์„ฑ
4. result ๊ฐ€ #2 ๋ฅผ ๊ฐ€๋ฆฌํ‚ด
5. ๊ฐ์ฒด #1 ("") ์€ Garbage

๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ:

[Heap]
โ”œโ”€โ”€ String #1 ""  (Garbage)
โ””โ”€โ”€ String #2 "x" โ† result

Step 2: 100๋ฒˆ์งธ ๋ฐ˜๋ณต (i=99)

result = result + "x";  // result ๋Š” 99์ž String

๋™์ž‘:
1. result (99์ž) + "x" โ†’ ์ƒˆ String 100์ž
2. 99์ž String ์€ Garbage
3. ๋งค๋ฒˆ n ๊ธ€์ž ๋ณต์‚ฌ ๋ฐœ์ƒ

์‹œ๊ฐ„:

  • 99์ž ๋ณต์‚ฌ + 1์ž ์ถ”๊ฐ€ = 100 ๊ฐœ ๊ธ€์ž ์ฒ˜๋ฆฌ

Step 3: 10000๋ฒˆ์งธ ๋ฐ˜๋ณต

result = result + "x";  // result ๋Š” 9999์ž

๋™์ž‘:

  • 9999์ž ๋ณต์‚ฌ + 1์ž ์ถ”๊ฐ€ = 10000 ๊ฐœ ๊ธ€์ž ์ฒ˜๋ฆฌ
  • ๊ทธ ์ „ String ์€ Garbage

๋ˆ„์  ๋ถ„์„

์ด ์ฒ˜๋ฆฌ ๊ธ€์ž ์ˆ˜:

1 + 2 + 3 + ... + 10000
= 10000 * 10001 / 2
= 50,005,000 (์•ฝ 5์ฒœ๋งŒ)

์ด ์ƒ์„ฑ ๊ฐ์ฒด ์ˆ˜:

10000 ๊ฐœ String ๊ฐ์ฒด
9999 ๊ฐœ๊ฐ€ Garbage

๋ฉ”๋ชจ๋ฆฌ ์†Œ๋น„:

  • ํ‰๊ท  String ํฌ๊ธฐ = 5000 ์ž
  • 1๋งŒ ๊ฐœ ๊ฐ์ฒด = 50,000,000 ์ž
  • 1์ž = 2 byte (UTF16) = 100 MB ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ
  • ๊ทธ ์ค‘ 99% ๊ฐ€ Garbage โ†’ GC ํญ์ฆ

์‹œ๊ฐ„ ๋ณต์žก๋„

T(n) = 1 + 2 + 3 + ... + n
     = n(n+1)/2
     = O(nยฒ)

์‹ค์ธก (1๋งŒ ํšŒ):

  • String + : ~5์ดˆ
  • StringBuilder : ~5ms
  • โ†’ 1000๋ฐฐ ๋А๋ฆผ

์‹œ๊ฐํ™”

String + ๋ฐ˜๋ณต:

๋ฐ˜๋ณต 1:  [""] โ†’ [..."x"]                     (์ƒˆ ๊ฐ์ฒด)
๋ฐ˜๋ณต 2:  ["x"] โ†’ [..."xx"]                   (์ƒˆ ๊ฐ์ฒด, 1์ž ๋ณต์‚ฌ)
๋ฐ˜๋ณต 3:  ["xx"] โ†’ [..."xxx"]                 (์ƒˆ ๊ฐ์ฒด, 2์ž ๋ณต์‚ฌ)
...
๋ฐ˜๋ณต N:  ["xxx...x"] โ†’ [..."xxx...xx"]       (์ƒˆ ๊ฐ์ฒด, N-1 ์ž ๋ณต์‚ฌ)

โ†’ N ๊ฐœ ๊ฐ์ฒด + ๋ˆ„์  Nยฒ/2 ๊ธ€์ž ๋ณต์‚ฌ

StringBuilder:

์ดˆ๊ธฐ:    [๋ฒ„ํผ 16์ž, ์‚ฌ์šฉ 0]
๋ฐ˜๋ณต 1:  [๋ฒ„ํผ 16์ž, ์‚ฌ์šฉ 1]                 (์ž๋ฆฌ ์ถ”๊ฐ€)
๋ฐ˜๋ณต 2:  [๋ฒ„ํผ 16์ž, ์‚ฌ์šฉ 2]
...
๋ฐ˜๋ณต 16: [๋ฒ„ํผ 16์ž, ์‚ฌ์šฉ 16]                (๊ฝ‰ ์ฐธ)
๋ฐ˜๋ณต 17: [๋ฒ„ํผ 34์ž, ์‚ฌ์šฉ 17]                (ํ™•์žฅ!)
...
๋ฐ˜๋ณต N:  [์ถฉ๋ถ„ํ•œ ๋ฒ„ํผ, ์‚ฌ์šฉ N]

โ†’ 1 ๊ฐœ ๊ฐ์ฒด + ๊ฐ€๋” ํ™•์žฅ โ†’ ๋ˆ„์  ~2N ๊ธ€์ž (๋ถ„ํ•  ์ƒํ™˜ O(n))

๊ฒฐ๋ก 

String ์˜ ๋ถˆ๋ณ€์„ฑ ์€ ์•ˆ์ „์ด๋ผ๋Š” ์ด์ ์„ ์ฃผ์ง€๋งŒ,
๋ฐ˜๋ณต์  ๋ณ€๊ฒฝ ์—๋Š” ์น˜๋ช…์  ๋น„ํšจ์œจ.
StringBuilder ๊ฐ€ ๊ฐ™์€ ์ž‘์—…์„ 1000๋ฐฐ ๋น ๋ฅด๊ฒŒ.
โ†’ ์ž๋ฐ”์—์„œ StringBuilder ๊ฐ€ ๋‹จ์ˆœํ•œ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ธฐ๋ณธ ๋„๊ตฌ.


Q2: StringBuilder ์˜ capacity ์™€ ์ž๋™ ํ™•์žฅ์˜ ๋น„์šฉ์€?

ํ•œ ์ค„ ๋‹ต: capacity ๋ถ€์กฑ ์‹œ ํ˜„์žฌ * 2 + 2 ๋กœ ํ™•์žฅ + ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ. ๋ถ„ํ•  ์ƒํ™˜ O(n).

์ƒ์„ธ ์„ค๋ช…:

capacity vs length

StringBuilder sb = new StringBuilder();
System.out.println(sb.capacity());  // 16
System.out.println(sb.length());    // 0

sb.append("hello");
System.out.println(sb.capacity());  // 16 (ํ™•์žฅ ์—†์Œ)
System.out.println(sb.length());    // 5

๊ฐœ๋…:

  • capacity = ๋‚ด๋ถ€ ๋ฒ„ํผ ํฌ๊ธฐ (value.length)
  • length = ์‹ค์ œ ์‚ฌ์šฉ ์ค‘์ธ ๋ฌธ์ž ์ˆ˜ (count)
  • length <= capacity ํ•ญ์ƒ ์„ฑ๋ฆฝ

๊ทธ๋ฆผ:

[StringBuilder ๋‚ด๋ถ€]
โ”œโ”€โ”€ byte[] value:  [h][e][l][l][o][_][_][_][_][_][_][_][_][_][_][_]
โ”‚                   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
โ”‚                   โ†‘                โ†‘
โ”‚                  length=5      capacity=16

์ž๋™ ํ™•์žฅ ๋ฉ”์ปค๋‹ˆ์ฆ˜

public void ensureCapacityInternal(int minimumCapacity) {
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value, newCapacity(minimumCapacity));
    }
}

private int newCapacity(int minCapacity) {
    int newCapacity = (value.length << 1) + 2;  // ํ˜„์žฌ * 2 + 2
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    return newCapacity;
}

ํ™•์žฅ ๊ทœ์น™:
1. ์ƒˆ capacity = ํ˜„์žฌ * 2 + 2
2. ๊ทธ๋ž˜๋„ ๋ถ€์กฑ โ†’ ํ•„์š”ํ•œ ํฌ๊ธฐ๋กœ

์˜ˆ์‹œ:

์ดˆ๊ธฐ: capacity = 16
์ฒซ ํ™•์žฅ: 16 โ†’ 34 (16 * 2 + 2)
๋‘ ๋ฒˆ์งธ: 34 โ†’ 70
์„ธ ๋ฒˆ์งธ: 70 โ†’ 142
๋„ค ๋ฒˆ์งธ: 142 โ†’ 286
...

ํ™•์žฅ ๋น„์šฉ

๋น„์šฉ:
1. ์ƒˆ ๋ฐฐ์—ด ํ• ๋‹น โ€” O(์ƒˆ ํฌ๊ธฐ)
2. ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ โ€” Arrays.copyOf โ€” O(๊ธฐ์กด ํฌ๊ธฐ)
3. ์˜› ๋ฐฐ์—ด์€ GC ๋Œ€์ƒ

์‹œ๊ฐ„:

  • ๋‹จ์ผ ํ™•์žฅ = O(ํ˜„์žฌ ํฌ๊ธฐ)
  • ๊ทธ๋Ÿฌ๋‚˜ ๋นˆ๋„๊ฐ€ ์ ์Œ โ†’ ๋ถ„ํ•  ์ƒํ™˜ O(1) per append

๋ถ„ํ•  ์ƒํ™˜ ๋ถ„์„ (Amortized Analysis)

N ๊ฐœ ๋ฌธ์ž ์ถ”๊ฐ€:
- ํ™•์žฅ ํšŸ์ˆ˜: logโ‚‚(N) (๋Œ€๋žต)
- ๊ฐ ํ™•์žฅ์˜ ๋น„์šฉ: O(ํ˜„์žฌ ํฌ๊ธฐ)
- ๋ˆ„์  ๋ณต์‚ฌ: 16 + 34 + 70 + 142 + ... โ‰ˆ 2N

์ด ๋น„์šฉ: N (์ถ”๊ฐ€) + 2N (๋ณต์‚ฌ) = O(n)

โ†’ append ํ•œ ๋ฒˆ = O(1) ํ‰๊ท  (๋ถ„ํ•  ์ƒํ™˜).


์ดˆ๊ธฐ capacity ์˜ ํšจ๊ณผ

๋‚˜์œ ์˜ˆ โ€” ํ™•์žฅ ๋นˆ๋ฒˆ:

StringBuilder sb = new StringBuilder();  // 16
for (int i = 0; i < 1_000_000; i++) {
    sb.append("x");
}
// ํ™•์žฅ: 16 โ†’ 34 โ†’ 70 โ†’ 142 โ†’ ... โ†’ 1,048,594
// ์•ฝ 17 ๋ฒˆ ํ™•์žฅ
// ๋ˆ„์  ๋ณต์‚ฌ: ~ 2 ๋ฐฑ๋งŒ ๊ธ€์ž

์ข‹์€ ์˜ˆ โ€” ํ•œ ๋ฒˆ์— ์ถฉ๋ถ„ํžˆ:

StringBuilder sb = new StringBuilder(1_000_000);  // 100๋งŒ
for (int i = 0; i < 1_000_000; i++) {
    sb.append("x");
}
// ํ™•์žฅ: 0 ๋ฒˆ
// ๋ˆ„์  ๋ณต์‚ฌ: 0
// โ†’ ์•ฝ 30% ๋น ๋ฆ„

capacity ์ง€์ •์˜ ๊ฐ€์ด๋“œ

์ƒํ™ฉ๊ถŒ์žฅ
์งง์€ ๊ฒฐํ•ฉ (~100 ์ž)๊ธฐ๋ณธ (16) OK
์ค‘๊ฐ„ (~1KB)๋ช…์‹œ (1024)
ํฐ (~MB)๋ช…์‹œ (์˜ˆ์ƒ ํฌ๊ธฐ)
์ •ํ™•ํ•œ ์˜ˆ์ธก ์–ด๋ ค์›€๊ธฐ๋ณธ ์‚ฌ์šฉ (์ž๋™ ํ™•์žฅ OK)

๊ฒฐ๋ก 

capacity ์™€ length ๋Š” ๋‹ค๋ฅด๋‹ค โ€” capacity ๋Š” ๋ฒ„ํผ, length ๋Š” ์‹ค์ œ.
์ž๋™ ํ™•์žฅ์€ ํšจ์œจ์  (๋ถ„ํ•  ์ƒํ™˜ O(1)) ์ด์ง€๋งŒ, ์˜ˆ์ƒ ํฌ๊ธฐ๋ฅผ ์•Œ๋ฉด ์ดˆ๊ธฐ capacity ์ง€์ • ์œผ๋กœ ๋” ๋‚˜์Œ.
1๋งŒ ์ž String ๋นŒ๋”ฉ ์‹œ โ€” capacity ์ง€์ •์œผ๋กœ ์•ฝ 30% ์„ฑ๋Šฅ ๊ฐœ์„ .


๋‹ค์Œ Unit์œผ๋กœ

  • ArrayList vs LinkedList ํ•™์Šต ์ค€๋น„ ์™„๋ฃŒ
  • List ์ž๋ฃŒ๊ตฌ์กฐ์˜ ์„ ํƒ ๊ธฐ์ค€์ด ๊ถ๊ธˆํ•˜๋‹ค
  • ๋ฉ”๋ชจ๋ฆฌ ๋ชจ๋ธ (Phase 4) + GC (Phase 5) ์˜ ์ ์šฉ ์‚ฌ๋ก€ ๋งŒ๋‚  ์ค€๋น„
profile
Software Developer

0๊ฐœ์˜ ๋Œ“๊ธ€