로그 출력 등을 위해 항상 문자열을 작성해야합니다. JDK 버전을 통해 우리는 언제 StringBuffer
(많은 추가, 스레드 안전) 및 StringBuilder
(많은 추가, 비 스레드 안전) 을 사용 했는지 배웠습니다 .
사용에 대한 조언은 무엇입니까 String.format()
? 효율적이거나 성능이 중요한 단일 라이너에 대한 연결을 고수해야합니까?
예를 들어 못생긴 구식,
String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";
깔끔한 새 스타일 (String.format, 느릴 수 있음)
String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);
참고 : 내 구체적인 사용 사례는 코드 전체에서 수백 개의 ‘한 줄짜리’로그 문자열입니다. 그들은 루프를 포함하지 않으므로 StringBuilder
너무 무겁습니다. String.format()
특별히 관심이 있습니다.
답변
나는 두 가지의 더 나은 성능을 가진 작은 클래스를 작성했으며 +는 형식보다 앞서 나갔다. 5-6의 요소로
import java.io.*;
import java.util.Date;
public class StringTest{
public static void main( String[] args ){
int i = 0;
long prev_time = System.currentTimeMillis();
long time;
for( i = 0; i< 100000; i++){
String s = "Blah" + i + "Blah";
}
time = System.currentTimeMillis() - prev_time;
System.out.println("Time after for loop " + time);
prev_time = System.currentTimeMillis();
for( i = 0; i<100000; i++){
String s = String.format("Blah %d Blah", i);
}
time = System.currentTimeMillis() - prev_time;
System.out.println("Time after for loop " + time);
}
}
다른 N에 대해 위를 실행하면 둘 다 선형으로 작동하지만 String.format
5-30 배 느립니다.
그 이유는 현재 구현에서 String.format
먼저 정규식으로 입력을 구문 분석 한 후 매개 변수를 채우기 때문입니다. 반면에 plus와의 연결은 JIT가 아닌 javac에 의해 최적화되어 StringBuilder.append
직접 사용 됩니다.
답변
hhafez 코드를 가져 와서 메모리 테스트를 추가했습니다 .
private static void test() {
Runtime runtime = Runtime.getRuntime();
long memory;
...
memory = runtime.freeMemory();
// for loop code
memory = memory-runtime.freeMemory();
각 접근 방식, ‘+’연산자, String.format 및 StringBuilder (toString () 호출)에 대해 별도로 실행하므로 사용 된 메모리는 다른 접근 방식의 영향을받지 않습니다. 더 많은 연결을 추가하여 문자열을 “Blah”+ i + “Blah”+ i + “Blah”+ i + “Blah”로 만듭니다.
결과는 다음과 같습니다 (평균 5 회 실행) :
접근 시간 (ms) 메모리 할당 (긴)
‘+’연산자 747 320,504
문자열 형식 16484 373,312
StringBuilder 769 57,344
우리는 String ‘+’와 StringBuilder가 실제로 시간적으로 동일하다는 것을 알 수 있지만 StringBuilder는 메모리 사용에서 훨씬 더 효율적입니다. 가비지 콜렉터가 ‘+’연산자로 인해 많은 문자열 인스턴스를 정리하지 못할 정도로 짧은 시간 간격으로 많은 로그 호출 (또는 문자열과 관련된 기타 명령문)이있는 경우 매우 중요합니다.
BTW라는 메모 는 메시지를 작성하기 전에 로깅 수준 을 확인하는 것을 잊지 마십시오 .
결론 :
- 계속해서 StringBuilder를 사용하겠습니다.
- 나는 시간이 너무 많거나 인생이 적다.
답변
여기에 제시된 모든 벤치 마크에는 몇 가지 결함 이 있으므로 결과가 신뢰할 수 없습니다.
아무도 벤치마킹에 JMH 를 사용 하지 않았다는 사실에 놀랐습니다 .
결과 :
Benchmark Mode Cnt Score Error Units
MyBenchmark.testOld thrpt 20 9645.834 ± 238.165 ops/s // using +
MyBenchmark.testNew thrpt 20 429.898 ± 10.551 ops/s // using String.format
단위는 초당 작업 일수록 더 좋습니다. 벤치 마크 소스 코드 . OpenJDK IcedTea 2.5.4 Java 가상 머신이 사용되었습니다.
따라서 이전 스타일 (+ 사용)이 훨씬 빠릅니다.
답변
이전의 추악한 스타일은 JAVAC 1.6에서 다음과 같이 자동으로 컴파일됩니다.
StringBuilder sb = new StringBuilder("What do you get if you multiply ");
sb.append(varSix);
sb.append(" by ");
sb.append(varNine);
sb.append("?");
String s = sb.toString();
따라서 이것과 StringBuilder 사용 사이에는 아무런 차이가 없습니다.
String.format은 새로운 Formatter를 만들고 입력 형식 문자열을 구문 분석하고 StringBuilder를 만들고 모든 것을 추가하고 toString ()을 호출하기 때문에 훨씬 더 무겁습니다.
답변
Java의 String.format은 다음과 같이 작동합니다.
- 형식 문자열을 구문 분석하여 형식 청크 목록으로 분해합니다.
- 형식 청크를 반복하여 StringBuilder로 렌더링합니다. StringBuilder는 기본적으로 새 배열로 복사하여 필요에 따라 크기를 조정하는 배열입니다. 우리는 아직 최종 문자열을 얼마나 큰지 알지 못하기 때문에 필요합니다.
- StringBuilder.toString ()은 내부 버퍼를 새 문자열로 복사합니다.
이 데이터의 최종 대상이 스트림 인 경우 (예 : 웹 페이지 렌더링 또는 파일 쓰기) 스트림에 형식 청크를 직접 어셈블 할 수 있습니다.
new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");
옵티마이 저가 형식 문자열 처리를 최적화하지 않을 것이라고 추측합니다. 그렇다면 String.format을 StringBuilder로 수동으로 언 롤링 하는 것과 동등한 상각 성능을 유지합니다.
답변
위의 첫 번째 답변을 확장 / 수정하기 위해 String.format이 실제로 도움이되는 번역은 아닙니다.
String.format이 도움이되는 것은 현지화 (l10n) 차이가있는 날짜 / 시간 (또는 숫자 형식 등)을 인쇄 할 때입니다 (일부 국가는 04Feb2009를 인쇄하고 다른 국가는 Feb042009를 인쇄합니다).
번역에서는 ResourceBundle 및 MessageFormat을 사용하여 올바른 언어에 적합한 번들을 사용할 수 있도록 외부화 가능한 문자열 (예 : 오류 메시지 및 기타)을 속성 번들로 이동하는 것에 대해서만 이야기하고 있습니다.
위의 모든 것을 살펴보면 성능 측면에서 String.format 대 일반 연결이 선호하는 것입니다. 연결보다 .format에 대한 호출을보고 싶다면 반드시 그렇게하십시오.
결국, 코드는 작성된 것보다 훨씬 많이 읽습니다.
답변
귀하의 예에서 성능 probalby는 그다지 다르지 않지만 고려해야 할 다른 문제가 있습니다 : 즉 메모리 조각화. 연결 작업조차도 임시 문자열 인 경우에도 새 문자열을 생성합니다 (GC에 시간이 걸리고 더 많은 작업이 필요합니다). String.format ()은 더 읽기 쉽고 조각화가 적습니다.
또한 특정 형식을 많이 사용하는 경우 Formatter () 클래스를 직접 사용할 수 있습니다 (모든 String.format ()이 사용하는 것은 Formatter 인스턴스를 인스턴스화하는 것입니다).
또한, 당신이 알아야 할 다른 것 : substring () 사용에주의하십시오. 예를 들면 다음과 같습니다.
String getSmallString() {
String largeString = // load from file; say 2M in size
return largeString.substring(100, 300);
}
Java 하위 문자열이 작동하는 방식이기 때문에 큰 문자열은 여전히 메모리에 있습니다. 더 나은 버전은 다음과 같습니다.
return new String(largeString.substring(100, 300));
또는
return String.format("%s", largeString.substring(100, 300));
두 번째 양식은 다른 작업을 동시에 수행하는 경우 더 유용합니다.