StringBuilder 사용에 관한 성능 관련 질문이 있습니다. 매우 긴 루프에서 a를 조작하고 다음 StringBuilder
과 같은 다른 메서드에 전달합니다.
for (loop condition) {
StringBuilder sb = new StringBuilder();
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
StringBuilder
모든 루프주기에서 인스턴스화 하는 것이 좋은 솔루션입니까? 그리고 다음과 같이 대신 삭제를 호출하는 것이 더 낫습니까?
StringBuilder sb = new StringBuilder();
for (loop condition) {
sb.delete(0, sb.length);
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
답변
두 번째는 내 미니 벤치 마크에서 약 25 % 더 빠릅니다.
public class ScratchPad {
static String a;
public static void main( String[] args ) throws Exception {
long time = System.currentTimeMillis();
for( int i = 0; i < 10000000; i++ ) {
StringBuilder sb = new StringBuilder();
sb.append( "someString" );
sb.append( "someString2"+i );
sb.append( "someStrin4g"+i );
sb.append( "someStr5ing"+i );
sb.append( "someSt7ring"+i );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
time = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for( int i = 0; i < 10000000; i++ ) {
sb.delete( 0, sb.length() );
sb.append( "someString" );
sb.append( "someString2"+i );
sb.append( "someStrin4g"+i );
sb.append( "someStr5ing"+i );
sb.append( "someSt7ring"+i );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
}
}
결과 :
25265
17969
이것은 JRE 1.6.0_07을 사용합니다.
편집에서 Jon Skeet의 아이디어를 바탕으로 여기에 버전 2가 있습니다.하지만 동일한 결과입니다.
public class ScratchPad {
static String a;
public static void main( String[] args ) throws Exception {
long time = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for( int i = 0; i < 10000000; i++ ) {
sb.delete( 0, sb.length() );
sb.append( "someString" );
sb.append( "someString2" );
sb.append( "someStrin4g" );
sb.append( "someStr5ing" );
sb.append( "someSt7ring" );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
time = System.currentTimeMillis();
for( int i = 0; i < 10000000; i++ ) {
StringBuilder sb2 = new StringBuilder();
sb2.append( "someString" );
sb2.append( "someString2" );
sb2.append( "someStrin4g" );
sb2.append( "someStr5ing" );
sb2.append( "someSt7ring" );
a = sb2.toString();
}
System.out.println( System.currentTimeMillis()-time );
}
}
결과 :
5016
7516
답변
견고한 코드를 작성하는 철학에서는 StringBuilder를 루프에 넣는 것이 항상 좋습니다. 이렇게하면 의도 한 코드를 벗어나지 않습니다.
둘째, StringBuilder의 가장 큰 개선은 루프가 실행되는 동안 더 커지지 않도록 초기 크기를 제공하는 것입니다.
for (loop condition) {
StringBuilder sb = new StringBuilder(4096);
}
답변
더 빠른 속도 :
public class ScratchPad {
private static String a;
public static void main( String[] args ) throws Exception {
long time = System.currentTimeMillis();
StringBuilder sb = new StringBuilder( 128 );
for( int i = 0; i < 10000000; i++ ) {
// Resetting the string is faster than creating a new object.
// Since this is a critical loop, every instruction counts.
//
sb.setLength( 0 );
sb.append( "someString" );
sb.append( "someString2" );
sb.append( "someStrin4g" );
sb.append( "someStr5ing" );
sb.append( "someSt7ring" );
setA( sb.toString() );
}
System.out.println( System.currentTimeMillis()-time );
}
private static void setA( String aString ) {
a = aString;
}
}
견고한 코드를 작성하는 철학에서 메서드의 내부 동작은 메서드를 사용하는 객체로부터 숨겨져 야합니다. 따라서 루프 내에서 또는 루프 외부에서 StringBuilder를 재 선언하든 시스템의 관점에서 차이가 없습니다. 루프 외부에서 선언하는 것이 더 빠르며 코드를 읽기가 더 복잡하게 만들지 않으므로 다시 인스턴스화하기보다는 객체를 재사용하십시오.
코드가 더 복잡하고 객체 인스턴스화가 병목이라는 것을 확실히 알았더라도 주석을 달아주세요.
이 답변으로 세 번 실행 :
$ java ScratchPad
1567
$ java ScratchPad
1569
$ java ScratchPad
1570
다른 답변으로 세 번 실행 :
$ java ScratchPad2
1663
2231
$ java ScratchPad2
1656
2233
$ java ScratchPad2
1658
2242
중요하지는 않지만 StringBuilder
의 초기 버퍼 크기를 설정하면 약간의 이득을 얻을 수 있습니다.
답변
좋아요, 이제 무슨 일이 일어나고 있는지 이해하고 말이 되네요.
나는 사본을 가져 가지 않은 String 생성자에 toString
기본 char[]
을 전달 했다는 인상을 받았습니다 . 그러면 다음 “쓰기”작업 (예 :)에서 복사본이 만들어집니다 . 나는이 믿고 있었다 의 경우 일부 이전 버전입니다. (지금은 아닙니다.) 그러나 아니요- 배열 (및 인덱스 및 길이)을 복사본 을받는 공용 생성자에 전달합니다.delete
StringBuffer
toString
String
따라서 “재사용 StringBuilder
“의 경우에는 버퍼에있는 동일한 문자 배열을 사용하여 문자열 당 하나의 데이터 복사본을 생성합니다. 분명히 StringBuilder
매번 새로운 것을 생성하면 새로운 기본 버퍼가 생성되고 그 버퍼는 새로운 문자열을 생성 할 때 복사됩니다 (우리의 특정 경우에는 다소 의미가 없지만 안전상의 이유로 수행됨).
이 모든 것이 두 번째 버전이 확실히 더 효율적으로 이어지지 만 동시에 나는 여전히 더 나쁜 코드라고 말할 것입니다.
답변
String 연결을 볼 때 자동으로 StringBuilders (StringBuffers pre-J2SE 5.0)를 생성하는 Sun Java 컴파일러에 내장 된 최적화로 인해 아직 지적되지 않았다고 생각하므로 질문의 첫 번째 예는 다음과 같습니다.
for (loop condition) {
String s = "some string";
. . .
s += anotherString;
. . .
passToMethod(s);
}
더 읽기 쉬운 IMO, 더 나은 접근 방식입니다. 최적화하려는 시도는 일부 플랫폼에서 이익을 얻을 수 있지만 잠재적으로 다른 플랫폼에서 손실을 입을 수 있습니다.
그러나 실제로 성능 문제가 발생하면 최적화하십시오. Jon Skeet에 따라 StringBuilder의 버퍼 크기를 명시 적으로 지정하는 것으로 시작합니다.
답변
현대 JVM은 이와 같은 것에 대해 정말 똑똑합니다. 나는 두 번째로 그것을 추측하지 않고 유지 보수 및 가독성이 떨어지는 해킹을하지 않을 것입니다 … 사소하지 않은 성능 향상을 검증하는 프로덕션 데이터로 적절한 벤치 마크를 수행하지 않는 한 (그리고 문서화합니다.)
답변
Windows에서 소프트웨어를 개발 한 경험을 바탕으로 루프 중에 StringBuilder를 지우는 것이 각 반복마다 StringBuilder를 인스턴스화하는 것보다 성능이 더 좋다고 말합니다. 이를 지우면 추가 할당없이 즉시 덮어 쓸 수 있도록 해당 메모리가 해제됩니다. Java 가비지 수집기에 익숙하지 않지만 (다음 문자열이 StringBuilder를 늘리지 않는 한) 해제하고 재할 당하지 않는 것이 인스턴스화보다 더 유익하다고 생각합니다.
(제 의견은 다른 사람들이 제안하는 것과 상반됩니다. 흠. 벤치마킹 할 시간입니다.)