λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Java

[Java DeepDive] - String (2) λ‚΄μž₯ ν•¨μˆ˜

by 주발2 2022. 10. 23.
λ°˜μ‘ν˜•

πŸ“Ž String (2) - λ‚΄μž₯ ν•¨μˆ˜

μ•ˆλ…•ν•˜μ„Έμš”, μ§€λ‚œ μ‹œκ°„μ—λŠ” String - λ¬Έμžμ—΄ 생성 κ΄€λ ¨ν•΄μ„œ μ‚΄νŽ΄λ³΄μ•˜λŠ”λ°μš”, μ΄λ²ˆμ—λŠ” String ν΄λž˜μŠ€μ— μ‘΄μž¬ν•˜λŠ” λ©”μ„œλ“œλ“€ 쀑 일뢀 λ©”μ„œλ“œλ“€μ— λŒ€ν•΄ λ‚΄λΆ€ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

(예제 μ½”λ“œλŠ” κΉƒν—ˆλΈŒμ—μ„œ ν™•μΈν•˜μ‹€ 수 있으며, 버전은 Java 11을 μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€.)

 

 

String 클래슀 λ‚΄μ˜ λ©”μ„œλ“œλ“€μ€ μœ„ μ‚¬μ§„μ²˜λŸΌ ꡉμž₯히 많이 μ‘΄μž¬ν•˜λŠ”λ°μš”, 개인적으둜 자주 μ‚¬μš©ν•˜λŠ” λ©”μ„œλ“œλ“€μ— λŒ€ν•΄ 정리해 λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

· String.length()

    @Stable
    private final byte[] value;
    
    ...
    
    static final boolean COMPACT_STRINGS;

    static {
        COMPACT_STRINGS = true;
    }
    
    ...

    public int length() {
        return value.length >> coder();
    }

    byte coder() {
        return COMPACT_STRINGS ? coder : UTF16;
    }

String 클래슀의 length() λ©”μ„œλ“œλŠ” coder() λ©”μ„œλ“œμ˜ 결괏값에 λŒ€ν•΄ μ‹œν”„νŠΈ 연산을 μ μš©ν•©λ‹ˆλ‹€.

 

coder() λ©”μ„œλ“œλŠ” COMPACT_STRINGS 값이 trueμ΄λ―€λ‘œ(졜초 static 블둝에 true둜 μ„€μ •) coderλ₯Ό λ¦¬ν„΄ν•˜κ³ , 값은 0μž…λ‹ˆλ‹€.

 

κ²°κ΅­μ—” value.length의 값을 λ¦¬ν„΄ν•˜λŠ”λ°μš”, valueλŠ” byte λ°°μ—΄ νƒ€μž…μœΌλ‘œ λ¬Έμžμ—΄μ˜ 각 문자λ₯Ό μ•„μŠ€ν‚€μ½”λ“œμ˜ ν‘œμ— 따라 10μ§„μˆ˜μ˜ κ°’μœΌλ‘œ λ””μ½”λ”©ν•˜μ—¬ 배열에 μ €μž₯을 ν•©λ‹ˆλ‹€.

 

 

· String.isEmpty()

    public boolean isEmpty() {
        return value.length == 0;
    }

isEmpty() λ©”μ„œλ“œλŠ” λ‹¨μˆœνžˆ byte 배열인 value의 lengthκ°€ 0인지 μ•„λ‹Œμ§€λ₯Ό λΉ„κ΅ν•©λ‹ˆλ‹€.

 

 

· String.charAt(int index)

    public char charAt(int index) {
        if (isLatin1()) {
            return StringLatin1.charAt(value, index);
        } else {
            return StringUTF16.charAt(value, index);
        }
    }
    
    
final class StringLatin1 {

    public static char charAt(byte[] value, int index) {
        if (index < 0 || index >= value.length) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return (char)(value[index] & 0xff);
    }

charAt() λ©”μ„œλ“œλŠ” StringLatin1 ν˜Ήμ€ StringUTF16 클래슀의 charAt(byte[] value, int index) λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€.

(μ•„λž˜μ—μ„œ μ‚΄νŽ΄λ³Ό λŒ€λΆ€λΆ„μ˜ λ©”μ„œλ“œλ“€λ„ Latin1 ν˜Ήμ€ UTF16에 따라 μ½”λ“œλ“€μ΄ λ‹€λ₯Έλ° μ•„λž˜λŠ” μ „λΆ€ Latin1 κΈ°μ€€ μ½”λ“œμž…λ‹ˆλ‹€.)

 

λ‚΄λΆ€ μ μœΌλ‘œλŠ” StringLatin1 ν΄λž˜μŠ€κ°€ 호좜이 되고, index의 μœ νš¨μ„± 검증을 ν•œ ν›„ 16μ§„μˆ˜μΈ 0xff와 and μ—°μ‚°(&)의 결괏값을 λ¦¬ν„΄ν•©λ‹ˆλ‹€.
(0xff와 & 연산은 λΆ€ν˜Έ λ•Œλ¬Έμ— μ‚¬μš©μ„ ν•˜λŠ”λ°, μžμ„Έν•œ 건 ν¬μŠ€νŒ…μ„ μ°Έκ³ ν•΄ μ£Όμ„Έμš” :)

    private static void printByte() {
        System.out.println((byte) 127); // 127
        System.out.println((byte) 128); // -128
        System.out.println((byte) 255); // -1
        System.out.println((byte) 257); // 1

        System.out.println((byte) 128 & 0xff); // 128
        System.out.println((byte) 255 & 0xff); // 255
    }

 

 

· String.compareTo(String anotherString)

    public int compareTo(String anotherString) {
        byte v1[] = value;
        byte v2[] = anotherString.value;
        if (coder() == anotherString.coder()) {
            return isLatin1() ? StringLatin1.compareTo(v1, v2)
                              : StringUTF16.compareTo(v1, v2);
        }
        return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
                          : StringUTF16.compareToLatin1(v1, v2);
     }
     
    // StringLatin1.java
    @HotSpotIntrinsicCandidate
    public static int compareTo(byte[] value, byte[] other) {
        int len1 = value.length;
        int len2 = other.length;
        return compareTo(value, other, len1, len2);
    }

    public static int compareTo(byte[] value, byte[] other, int len1, int len2) {
        int lim = Math.min(len1, len2);
        for (int k = 0; k < lim; k++) {
            if (value[k] != other[k]) {
                return getChar(value, k) - getChar(other, k);
            }
        }
        return len1 - len2;
    }

compareTo() λ©”μ„œλ“œμ˜ 경우 λ¬Έμžμ—΄ μƒνƒœμ— 따라 결괏값이 λ‹¬λΌμ§€λŠ”λ°μš”, 비ꡐ할 두 λ¬Έμžμ—΄μ˜ 길이와 문자의 λ°”μ΄νŠΈ κ°’ 등을 λΉ„κ΅ν•©λ‹ˆλ‹€.

 

두 λ¬Έμžμ—΄μ˜ λ°”μ΄νŠΈ 값을 λΉ„κ΅ν•˜λ©΄μ„œ λ°”μ΄νŠΈκ°€ λ‹€λ₯΄λ‹€λ©΄, 두 λ°”μ΄νŠΈμ˜ 차이 값을 λ¦¬ν„΄ν•©λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄ μ•„λž˜μ˜ μ½”λ“œμ—μ„œλŠ” 두 번째 문자인 r, 1 이 λ‹€λ₯΄λ―€λ‘œ 두 λ°”μ΄νŠΈ κ°’μ˜ 차이인 65λ₯Ό λ¦¬ν„΄ν•©λ‹ˆλ‹€.

(1의 μ•„μŠ€ν‚€μ½”λ“œλŠ” 49, r의 μ•„μŠ€ν‚€μ½”λ“œλŠ” 114 μž…λ‹ˆλ‹€.)

String str1 = "original";
String str2 = "o1r2iginal";
return str1.compareTo(str2);

 

μ•„λž˜μ˜ μΌ€μ΄μŠ€λŠ” 두 λ¬Έμžμ—΄μ΄ λͺ¨λ‘ λ™μΌν•˜κΈ° λ•Œλ¬Έμ— 두 λ¬Έμžμ—΄μ˜ 길이 차인 0을 λ¦¬ν„΄ν•©λ‹ˆλ‹€.

String str1 = "original";
String str2 = "original";
return str1.compareTo(str2);

 

μ•„λž˜λŠ” originalκΉŒμ§€λŠ” λͺ¨λ“  λ¬Έμžκ°€ λ™μΌν•˜κΈ° λ•Œλ¬Έμ—, 두 λ¬Έμžμ—΄μ˜ 길이 차인 -4λ₯Ό λ¦¬ν„΄ν•©λ‹ˆλ‹€.

(str1이 기쀀이기에 길이 차이가 μŒμˆ˜μž…λ‹ˆλ‹€.)

String str1 = "original";
String str2 = "original1234";
return str1.compareTo(str2);

 

 

· String.startsWith(String prefix, int toffset)

toffset λΆ€ν„° μ‹œμž‘ν•΄μ„œ prefix와 λ¬Έμžμ—΄μ΄ λ™μΌν•œμ§€λ₯Ό λΉ„κ΅ν•©λ‹ˆλ‹€.

μœ„μ—μ„œ toλŠ” μ‹œμž‘ 지점을 λ‚˜νƒ€λ‚΄λŠ” 인덱슀이고, 핡심은 while λ‚΄λΆ€μΈλ°μš” κΈ°λ³Έ λ¬Έμžμ—΄(ta)와 비ꡐ λ¬Έμžμ—΄(pa)κ°€ μΌμΉ˜ν•˜λŠ”μ§€λ₯Ό λΉ„κ΅ν•˜μ—¬ 결괏값을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

 

 

· String.endsWith(String prefix)

    public boolean endsWith(String suffix) {
        return startsWith(suffix, length() - suffix.length());
    }

 

endsWith() λ©”μ„œλ“œλŠ” μœ„μ—μ„œ μ‚΄νŽ΄λ³Έ startsWith() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ”λ°, 두 번째 μΈμžμ— 비ꡐλ₯Ό μ‹œμž‘ν•  index 값을 κ΅¬ν•˜μ—¬ ν˜ΈμΆœν•©λ‹ˆλ‹€.

 

 

· String.indexOf(String str)

indexOf() λ©”μ„œλ“œλŠ” μ—¬λŸ¬ 개 μ‘΄μž¬ν•©λ‹ˆλ‹€.

주둜 μ‚¬μš©ν•˜λŠ” λ©”μ„œλ“œλŠ” String νƒ€μž…μ„ ν•˜λ‚˜λ§Œ λ°›λŠ” λ©”μ„œλ“œμ΄λ―€λ‘œ, ν•΄λ‹Ή μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

    private static void indexOf() {
        String str = "original";

        System.out.println(str.indexOf("o")); // 0
        System.out.println(str.indexOf("r")); // 1
        System.out.println(str.indexOf("k")); // -1
    }

str λ¬Έμžμ—΄κ³Ό λ§€κ°œλ³€μˆ˜μ˜ λ¬Έμžκ°€ μΌμΉ˜ν• κ²½μš° 인덱슀λ₯Ό λ¦¬ν„΄ν•˜κ³ , μ—†μ„κ²½μš° -1을 λ¦¬ν„΄ν•©λ‹ˆλ‹€.

 

 

· String.contains(CharSequence s)

contains() λ©”μ„œλ“œλŠ” μœ„μ—μ„œ μ‚΄νŽ΄λ³΄μ•˜λ˜ indexOf() λ©”μ„œλ“œλ₯Ό ν™œμš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

 

· String.replace(char oldChar, char newChar)

replace() λ©”μ„œλ“œλŠ” oldChar 문자λ₯Ό newChar 문자둜 λ³€κ²½ν•˜λŠ”λ°μš”, μ•„λž˜ μ‚¬μ§„μ˜ 주석에 λ‚˜μ™€μžˆλ“―μ΄ λͺ¨λ“  λ¬Έμžμ— λŒ€ν•΄ 변경을 ν•©λ‹ˆλ‹€.

String.java
StringLatin1.java

replace() λ©”μ„œλ“œμ˜ 경우 bufλΌλŠ” μƒˆλ‘œμš΄ byte 배열을 μƒμ„±ν•œ ν›„ κΈ°μ‘΄ 값을 λ³΅μ‚¬ν•˜κ³ , while 문을 λ°˜λ³΅ν•˜λ©΄μ„œ λ³€κ²½ν•  문자의 경우 μƒˆλ‘œμš΄ 문자둜 값을 μ €μž₯ν•œ ν›„ String 객체λ₯Ό μƒμ„±ν•˜μ—¬ λ¦¬ν„΄ν•©λ‹ˆλ‹€.

 

LATIN1의 값은 0으둜 String μƒμ„±μžμ—μ„œ coder 값에 μ„€μ •λ©λ‹ˆλ‹€.

 

 

· String.repeat(int count)

repeat() λ©”μ„œλ“œμ˜ 경우 넀이밍 κ·ΈλŒ€λ‘œ 주어진 λ¬Έμžμ—΄μ„ μ΄μ–΄λΆ™μ΄λŠ” κΈ°λŠ₯을 ν•˜λŠ” λ©”μ„œλ“œμΈλ°μš”, ν•΄λ‹Ή λ©”μ„œλ“œλŠ” Java 11 버전뢀터 μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

빨간색 λ„€λͺ¨ λ°•μŠ€λ₯Ό κΈ°μ€€μœΌλ‘œ μœ„ 뢀뢄은 νŒŒλΌλ―Έν„°μ˜ μœ νš¨μ„± 검증, λ¬Έμžμ—΄μ˜ 길이 등에 λŒ€ν•œ 검증을 ν•˜λŠ” λΆ€λΆ„μž…λ‹ˆλ‹€.

OOM이 λ°œμƒν•  수 μžˆλŠ” 검증 뢀뢄은 count, len 길이λ₯Ό λΉ„κ΅ν•˜μ—¬ String의 μ΅œλŒ€ μ‚¬μ΄μ¦ˆμ™€ 비ꡐλ₯Ό ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

μ‹€μ œλ‘œ λ¬Έμžμ—΄μ„ μ΄μ–΄λΆ™μ΄λŠ” λ‘œμ§μ€ byte array인 multiple 배열을 μƒμ„±ν•˜κ³ , 첫 len만큼 λ³΅μ‚¬ν•œ λ’€ for문을 λŒλ©΄μ„œ 이후 뢀뢄을 System.arrayCopy() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ—¬ 볡사λ₯Ό ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

    private static void repeat() {
        String str = "ab";

        str = str.repeat(3);

        System.out.println(str); // ababab
    }

 

 

마무리

μ΄μƒμœΌλ‘œ String ν΄λž˜μŠ€μ— μ‘΄μž¬ν•˜λŠ” 일뢀 λ©”μ„œλ“œλ“€μ— λŒ€ν•΄ μ‚΄νŽ΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

 

λ‚΄λΆ€ μ½”λ“œλŠ” λŒ€λΆ€λΆ„ λ°”μ΄νŠΈλ₯Ό κΈ°μ€€μœΌλ‘œ 연산이 μ§„ν–‰λ˜κ³ , λ³΅μ‚¬μ˜ 경우 System 클래슀의 arrayCopy() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

Java 11을 κΈ°μ€€μœΌλ‘œ String 클래슀의 μ½”λ“œλŠ” λŒ€λž΅ 3300라인 μΈλ°μš”, μ—¬κΈ°μ—” ꡉμž₯히 많고 μΉœμ ˆν•˜κ²Œ 주석이 μž‘μ„±λ˜μ–΄ μžˆμ–΄μ„œ ν•¨μˆ˜μ˜ κΈ°λŠ₯을 νŒŒμ•…ν•˜λŠ”λ° λ§Žμ€ 도움이 λ˜λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€.

(μžμ„Έν•˜μ§„ μ•Šμ§€λ§Œ, μ½”λ“œλ³΄λ‹€ 주석이 더 λ§Žμ€ λŠλ‚Œμž…λ‹ˆλ‹€.. πŸ˜‚)

 

 

μ°Έμ‘°

 

λ°˜μ‘ν˜•

λŒ“κΈ€