program story

Java 문자열 풀이 란 무엇이며 "s"는 새로운 문자열과 어떻게 다른가요?

inputbox 2020. 7. 28. 08:29
반응형

Java 문자열 풀이 란 무엇이며 "s"는 새로운 문자열과 어떻게 다른가요? [복제]


이 질문에는 이미 답변이 있습니다.

String Pool 이란 무엇입니까 ? 그리고 다음 선언의 차이점은 무엇입니까?

String s = "hello";
String s = new String("hello");

JVM이이 두 문자열을 저장하는 것 사이에 차이점이 있습니까?


문자열 풀은 문자열 interning 개념의 JVM의 특정 구현입니다 .

컴퓨터 과학에서 문자열 인터 닝은 각각의 고유 한 문자열 값의 사본 하나만 저장하는 방법이며, 변경 불가능합니다. 문자열을 문자열로 만들면 문자열을 만들거나 묶을 때 더 많은 시간이 걸리면서 일부 문자열 처리 작업이 시간이나 공간 효율성이 향상됩니다. 고유 값은 문자열 인턴 풀에 저장됩니다.

기본적으로 문자열 인턴 풀을 사용하면 런타임에서 풀에 불변 문자열을 유지하여 메모리 영역을 절약 할 수 있으므로 응용 프로그램 영역에서 공통 문자열의 인스턴스를 여러 인스턴스로 만드는 대신 재사용 할 수 있습니다.

흥미로운 부수적으로, 문자열 인터 닝은 플라이급 디자인 패턴 의 예입니다 .

Flyweight는 소프트웨어 디자인 패턴입니다. 플라이급은 다른 유사한 객체와 가능한 많은 데이터를 공유하여 메모리 사용을 최소화하는 객체입니다. 단순한 반복 표현이 허용 할 수없는 양의 메모리를 사용할 때 오브젝트를 많은 수로 사용하는 방법입니다.


문자열 풀은 문자열 상수를 재사용 할 수있게하며, 이는 Java의 문자열이 변경 불가능하기 때문에 가능합니다. Java 코드의 모든 곳에서 동일한 문자열 상수를 반복하면 실제로 시스템에 해당 문자열의 사본을 하나만 가질 수 있으며 이는이 메커니즘의 장점 중 하나입니다.

사용 String s = "string constant";하면 문자열 풀에있는 사본을 얻습니다. 그러나 String s = new String("string constant");복사 할 때 사본을 강제로 할당합니다.


JLS

Andrew가 언급 한대로이 개념을 JLS에서 "인터 닝"이라고합니다.

JLS 7 3.10.5의 관련 구절 :

또한 문자열 리터럴은 항상 String 클래스의 동일한 인스턴스를 나타냅니다. String.intern 메소드를 사용하여 문자열 리터럴 또는보다 일반적으로 상수 표현식 (§15.28)의 값인 문자열이 "인터 닝"되어 고유 한 인스턴스를 공유하기 때문입니다.

예 3.10.5-1. 문자열 리터럴

편집 단위 (§7.3)로 구성된 프로그램 :

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

그리고 컴파일 단위 :

package other;
public class Other { public static String hello = "Hello"; }

출력을 생성합니다.

true true true true false true

JVMS

JVMS 7 5.1 말한다 :

문자열 리터럴은 String 클래스의 인스턴스에 대한 참조이며 클래스 또는 인터페이스의 이진 표현에서 CONSTANT_String_info 구조 (§4.4.3)에서 파생됩니다. CONSTANT_String_info 구조는 문자열 리터럴을 구성하는 일련의 유니 코드 코드 포인트를 제공합니다.

Java 프로그래밍 언어를 사용하려면 동일한 문자열 리터럴 (즉, 동일한 코드 포인트 시퀀스를 포함하는 리터럴)이 동일한 String 클래스 인스턴스 (JLS §3.10.5)를 참조해야합니다. 또한 String.intern 메소드가 문자열에서 호출되면 결과는 해당 문자열이 리터럴로 표시 될 때 리턴되는 동일한 클래스 인스턴스에 대한 참조입니다. 따라서 다음 표현식은 true 값을 가져야합니다.

("a" + "b" + "c").intern() == "abc"

문자열 리터럴을 도출하기 위해 Java Virtual Machine은 CONSTANT_String_info 구조에서 제공하는 코드 포인트 시퀀스를 검사합니다.

  • CONSTANT_String_info 구조에서 제공 한 것과 동일한 유니 코드 코드 포인트 시퀀스를 포함하는 String 클래스의 인스턴스에서 String.intern 메소드를 이전에 호출 한 경우 문자열 리터럴 파생 결과는 동일한 클래스의 클래스 인스턴스에 대한 참조입니다.

  • 그렇지 않으면 CONSTANT_String_info 구조에 의해 제공된 일련의 유니 코드 코드 포인트를 포함하는 String 클래스의 새 인스턴스가 작성됩니다. 해당 클래스 인스턴스에 대한 참조는 문자열 리터럴 파생의 결과입니다. 마지막으로, 새로운 String 인스턴스의 인턴 메소드가 호출됩니다.

바이트 코드

OpenJDK 7의 바이트 코드 구현을 살펴 보는 것도 유익합니다.

디 컴파일하면 :

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

우리는 상수 수영장에 있습니다.

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

그리고 main:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

참고 사항 :

  • 03: 동일한 ldc #2상수로드 (리터럴)
  • 12: 새로운 문자열 인스턴스가 생성됩니다 ( #2인수로)
  • 35: ac일반 객체로 비교if_acmpne

상수 문자열의 표현은 바이트 코드에서 매우 마술입니다.

  • 그것은 전용 갖는다 CONSTANT_String_info의 일반 오브젝트와 달리 구조 (예를 new String)
  • 구조체 는 데이터를 포함 하는 CONSTANT_Utf8_info 구조체를 가리 킵니다 . 이것이 문자열을 나타내는 데 필요한 유일한 데이터입니다.

위의 JVMS 인용문은 Utf8이 가리키는 경우마다 동일한 인스턴스가에 의해로드된다고 말합니다 ldc.

필드에 대해 비슷한 테스트를 수행했으며

  • static final String s = "abc"ConstantValue 속성을 통해 상수 테이블을 가리킴
  • 최종이 아닌 필드에는 해당 속성이 없지만 다음으로 초기화 할 수 있습니다. ldc

결론 : 문자열 풀에 대한 직접적인 바이트 코드 지원이 있으며 메모리 표현이 효율적입니다.

보너스 : 직접 바이트 코드를 지원하지 않는 Integer pool비교하십시오 (예 : CONSTANT_String_info아날로그 없음 ).


문자열 객체는 기본적으로 문자열 리터럴을 감싸는 래퍼입니다. 고유 한 문자열 개체는 불필요한 개체 생성을 방지하기 위해 풀링되며 JVM은 문자열 리터럴을 내부적으로 풀링하기로 결정할 수 있습니다. 컴파일러에서이를 지원하는 경우 여러 번 참조되는 문자열 상수에 대한 직접적인 바이트 코드 지원이 있습니다.

리터럴을 사용 String str = "abc";하면 풀의 객체가 사용됩니다. 을 사용 String str = new String("abc");하면 새 객체가 만들어 지지만 기존 문자열 리터럴은 JVM 레벨 또는 바이트 코드 레벨 (컴파일시)에서 재사용 될 수 있습니다.

for 루프에서 많은 문자열을 만들고 ==연산자를 사용하여 객체가 같은지 확인 하여 직접 확인할 수 있습니다 . 다음 예제 string.value에서은 private String이고 사용 된 문자열 리터럴을 보유합니다. 비공개이기 때문에 리플렉션을 통해 액세스해야합니다.

public class InternTest {
    public static void main(String[] args) {
        String rehi = "rehi";
        String rehi2 = "rehi";
        String rehi2a = "not rehi";
        String rehi3 = new String("rehi");
        String rehi3a = new String("not rehi");
        String rehi4 = new String(rehi);
        String rehi5 = new String(rehi2);
        String rehi6 = new String(rehi2a);

        String[] arr  = new String[] { rehi, rehi2, rehi2a, rehi3, rehi3a, rehi4, rehi5, rehi6 };
        String[] arr2 = new String[] { "rehi", "rehi (2)", "not rehi", "new String(\"rehi\")", "new String(\"not rehi\")", "new String(rehi)", "new String(rehi (2))", "new String(not rehi)" };

        Field f;
        try {
            f = String.class.getDeclaredField("value");
            f.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }

        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                System.out.println("i: " +arr2[i]+", j: " +arr2[j]);
                System.out.println("i==j: " + (arr[i] == arr[j]));
                System.out.println("i equals j: " + (arr[i].equals(arr[j])));
                try {
                    System.out.println("i.value==j.value: " + (f.get(arr[i]) == f.get(arr[j])));
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
                System.out.println("========");
            }
        }
    }
}

산출:

i: rehi, j: rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi, j: rehi (2)
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi, j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi, j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi, j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: rehi (2)
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: not rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: not rehi, j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String("not rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: not rehi, j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String(not rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("rehi"), j: new String("rehi")
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("rehi"), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: not rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("not rehi"), j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String("not rehi")
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String("not rehi"), j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String(not rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi), j: new String(rehi)
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String(rehi (2))
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: not rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(not rehi), j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String("not rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(not rehi), j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String(not rehi)
i==j: true
i equals j: true
i.value==j.value: true
========

아무도 질문에 직접 대답하지는 않았지만 대부분의 답변에는 많은 찬사가 있습니다.

간단히 말해서, 첫 번째는 문자열 풀에 항목을 작성하며 재사용 할 수 있습니다 (불변성에 대한 위의 링크로 인해 기본적으로 인턴으로 인해 더 효율적 임). 두 번째는 새 문자열 객체를 생성합니다 (비용이 많이 듭니다).

두 개체는 모두 힙에 있습니다. 둘 다에 대한 참조는 스레드 스택에 있습니다.

http://www.journaldev.com/797/what-is-java-string-pool 은 이것이 어떻게 달성되는지에 대한 명확한 통찰력을 제공합니다.

참고 URL : https://stackoverflow.com/questions/2486191/what-is-the-java-string-pool-and-how-is-s-different-from-new-strings

반응형