문자열에“”를 추가하면 왜 메모리가 절약됩니까?


193

많은 데이터가있는 변수를 사용했습니다 String data. 이 문자열의 작은 부분을 다음과 같은 방식으로 사용하고 싶었습니다.

this.smallpart = data.substring(12,18);

몇 시간의 디버깅 (메모리 시각화 도구 사용) 후에는 객체 필드 에 하위 문자열 만 포함되어 있지만 객체 필드의 smallpart모든 데이터를 기억 한다는 것을 알았습니다 data.

코드를 다음과 같이 변경했을 때 :

this.smallpart = data.substring(12,18)+""; 

.. 문제가 해결되었습니다! 이제 내 응용 프로그램은 메모리를 거의 사용하지 않습니다!

어떻게 가능합니까? 누구든지 이것을 설명 할 수 있습니까? 작은 부분은 계속 데이터를 참조한다고 생각하지만 왜 그럴까요?

업데이트 : 큰 문자열을 지우려면 어떻게해야합니까? data = new String (data.substring (0,100))이 작동합니까?


아래에서 귀하의 궁극적 의도에 대해 더 읽어보십시오 : 큰 줄은 처음부터 어디에서 왔습니까? 파일이나 데이터베이스 CLOB 또는 다른 것에서 읽는 경우 구문 분석하는 동안 필요한 것을 읽는 것이 가장 좋습니다.
PSpeed

4
놀라운 ... 나는 4 ~ 5 년 이상 자바에서 일하고 있지만 여전히 나에게 새롭다. :). 정보 형제 주셔서 감사합니다.
Parth

1
사용하는 데 미묘한 점이 있습니다 new String(String). stackoverflow.com/a/390854/8946을 참조하십시오 .
Lawrence Dol

답변:


159

다음을 수행하십시오.

data.substring(x, y) + ""

새로운 (더 작은) String 객체를 만들고 substring ()으로 만든 String에 대한 참조를 버려서 가비지 수집을 가능하게합니다.

알아야 할 중요한 점 substring()기존 문자열 또는 원래 문자열의 기본이되는 문자 배열에 창 을 제공 한다는 것입니다 . 따라서 원래 문자열과 동일한 메모리를 사용합니다. 이것은 어떤 상황에서는 유리할 수 있지만, 부분 문자열을 가져 와서 원래 문자열을 처리하려는 경우 문제가됩니다 (발견 한대로).

자세한 정보는 JDK 문자열 소스 의 substring () 메소드 를보십시오.

편집 : 당신의 메모리 사용량을 줄입니다 문자열에서 새로운 문자열을 구성, 당신의 보충 질문에 대한 답을 제공하는 원래의 문자열에 당신에게 참조를 빈.

참고 (2013 년 1 월). 위의 동작은 Java 7u6에서 변경 되었습니다 . 플라이급 패턴은 더 이상 사용되지 않으며 substring()예상대로 작동합니다.


89
String(String)생성자 (즉, String을 입력으로 사용하는 String 생성자)가 유용한 몇 가지 경우 중 하나입니다. new String(data.substring(x, y))를 추가하는 것과 사실상 같은 일을 ""하지만 의도를 다소 명확하게 만듭니다.
Joachim Sauer

3
정확하게, 부분 문자열은 value원래 문자열 의 속성을 사용 합니다. 그것이 참조가 유지되는 이유라고 생각합니다.
Valentin Rocher

@Bishiboosh-그렇습니다. 구현의 특수성을 드러내고 싶지 않았지만 정확히 무슨 일이 일어나고 있는지입니다.
Brian Agnew

5
기술적으로는 구현 세부 사항입니다. 그럼에도 불구하고 그것은 실망스럽고 많은 사람들을 사로 잡습니다.
Brian Agnew

1
약한 참조 등을 사용하여 JDK에서 이것을 최적화 할 수 있는지 궁금합니다. 이 문자 []가 필요한 마지막 사람이고 약간만 필요한 경우 내부적으로 사용할 새 배열을 만드십시오.
WW.

28

의 출처를 보면 다음을 substring(int, int)반환 함을 알 수 있습니다.

new String(offset + beginIndex, endIndex - beginIndex, value);

value원본은 어디에 있습니까 char[]? 따라서 새로운 String을 얻지 만 기본 이 동일char[] 합니다.

당신이 할 때 data.substring() + "", 당신은 새로운 기초를 가진 새로운 문자열을 얻는다 char[].

실제로 유스 케이스는 String(String)생성자 를 사용해야하는 유일한 상황입니다 .

String tiny = new String(huge.substring(12,18));

1
사용하는 데 미묘한 점이 있습니다 new String(String). stackoverflow.com/a/390854/8946을 참조하십시오 .
로렌스 돌

17

을 사용할 때 substring실제로 새 문자열을 만들지는 않습니다. 여전히 오프셋과 크기 제한이있는 원래 문자열을 참조합니다.

따라서 원래 문자열을 수집하려면 new String, 또는 사용하여 새 문자열을 만들어야합니다 .


5

작은 부분은 계속 데이터를 참조한다고 생각하지만 왜 그럴까요?

Java 문자열은 char 배열, 시작 오프셋 및 길이 (및 캐시 된 hashCode)로 구성됩니다. 일부 String 작업 substring()은 원본의 char 배열을 공유하고 단순히 오프셋 및 / 또는 길이 필드가 다른 새 String 객체를 만드는 것과 같습니다 . String의 char 배열은 일단 생성되면 수정되지 않기 때문에 작동합니다.

여러 하위 문자열이 겹치는 부분을 복제하지 않고 동일한 기본 문자열을 참조 할 때 메모리를 절약 할 수 있습니다. 알다시피, 어떤 상황에서는 더 이상 필요하지 않은 데이터를 가비지 수집하지 못하게 할 수 있습니다.

이것을 고치는 "올바른"방법은 new String(String)생성자입니다.

this.smallpart = new String(data.substring(12,18));

BTW의 전반적인 최상의 솔루션은 처음에는 매우 큰 문자열을 사용하지 않고 한 번에 몇 KB 씩 작은 청크로 입력을 처리하는 것을 피하는 것입니다.


사용하는 데 미묘한 점이 있습니다 new String(String). stackoverflow.com/a/390854/8946을 참조하십시오 .
Lawrence Dol

5

Java 문자열에서 문자열은 변경 불가능한 개체이며 일단 문자열이 생성되면 가비지 콜렉터가 정리할 때까지 메모리에 남아 있습니다 (이 정리는 당연한 것으로 간주되지 않습니다).

부분 문자열 메소드를 호출하면 Java는 완전히 새로운 문자열을 만들지 않고 원래 문자열 안에 다양한 문자를 저장합니다.

따라서이 코드로 새 문자열을 만들 때 :

this.smallpart = data.substring(12, 18) + ""; 

빈 문자열로 결과를 연결할 때 실제로 새 문자열을 만들었습니다. 그 이유입니다.


3

1997 년jwz기록한 대로 :

거대한 문자열이있는 경우 하위 문자열 ()을 꺼내고 하위 문자열을 잡고 더 긴 문자열이 가비지가되도록 허용하십시오 (즉, 하위 문자열의 수명이 길어짐). 거대한 문자열의 기본 바이트는 절대 가지 않습니다. 떨어져.


2

요약하면 소수의 큰 문자열에서 많은 하위 문자열을 만들면 다음을 사용하십시오.

   String subtring = string.substring(5,23)

큰 문자열을 저장하기 위해 공간 만 사용하기 때문에 큰 문자열의 손실에서 소수의 작은 문자열을 추출하는 경우

   String substring = new String(string.substring(5,23));

더 이상 필요하지 않을 때 큰 문자열을 회수 할 수 있으므로 메모리 사용을 줄입니다.

전화 new String한다는 것은 원래 문자열에 대한 참조가 아니라 실제로 새로운 문자열을 받고 있음을 알려주는 유용한 정보입니다.


사용하는 데 미묘한 점이 있습니다 new String(String). stackoverflow.com/a/390854/8946을 참조하십시오 .
Lawrence Dol

2

먼저 호출 java.lang.String.substring하면 String기본 배열의 상당 부분을 복사하는 대신 오프셋과 길이를 사용 하여 원본에 새 창을 만듭니다 .

substring메소드를 자세히 살펴보면 문자열 생성자를 호출 String(int, int, char[])하고 문자열char[] 을 나타내는 전체 를 전달하는 것을 알 수 있습니다. 이는 하위 문자열 이 원래 문자열 만큼 많은 메모리를 차지함을 의미 합니다 .

좋아, 그러나 왜 + ""그것이없는 것보다 적은 메모리를 요구 하는가?

수행 +에하는 strings을 통해 구현되는 StringBuilder.append메소드 호출. AbstractStringBuilder클래스 에서이 메소드의 구현을 arraycopy살펴보면 실제로 필요한 부분 ()과 관련이 substring있음을 알 수 있습니다.

다른 해결 방법 ??

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();

0

문자열에 ""를 추가하면 때때로 메모리 가 절약됩니다.

책 전체를 포함하는 거대한 문자열 (백만 문자)이 있다고 가정 해 봅시다.

그런 다음 책의 챕터를 포함하는 20 개의 문자열을 하위 문자열로 만듭니다.

그런 다음 모든 단락을 포함하는 1000 개의 문자열을 만듭니다.

그런 다음 모든 문장을 포함하는 10,000 개의 문자열을 만듭니다.

그런 다음 모든 단어를 포함하는 100,000 개의 문자열을 만듭니다.

여전히 1,000,000자를 사용합니다. 각 장, 단락, 문장 및 단어에 ""를 추가하면 5,000,000자를 사용합니다.

물론 전체 책에서 하나의 단어 만 추출하면 책 전체가 가비지 수집 될 수 있지만 단어 하나에 대한 참조가 포함되어있는 것은 아닙니다.

백만 개의 문자열이 있고 양쪽 끝에서 탭과 공백을 제거하면 하위 문자열을 만들기 위해 10 번의 호출을하는 경우에도 다시 다릅니다. Java가 작동하거나 작동하는 방식은 매번 백만 문자를 복사하지 않습니다. 타협이 있으며 타협이 무엇인지 아는 것이 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.