在Java中,最快的连接两个字符串的方法是什么?
即:
String ccyPair = ccy1 + ccy2;
我正在使用cyPair
作为HashMap
中的键,并在一个非常紧密的循环中调用它以检索值。
当我进行分析时,这是瓶颈所在。
java.lang.StringBuilder.append(StringBuilder.java:119)
java.lang.StringBuilder.(StringBuilder.java:93)
在Java中,最快的连接两个字符串的方法是什么?
即:
String ccyPair = ccy1 + ccy2;
我正在使用cyPair
作为HashMap
中的键,并在一个非常紧密的循环中调用它以检索值。
当我进行分析时,这是瓶颈所在。
java.lang.StringBuilder.append(StringBuilder.java:119)
java.lang.StringBuilder.(StringBuilder.java:93)
有很多理论 - 是时候实践一下了!
private final String s1 = new String("1234567890");
private final String s2 = new String("1234567890");
在经过热身的64位Hotspot,1.6.0_22上,在Intel Mac OS上使用1000万次普通for循环。
例如:
@Test public void testConcatenation() {
for (int i = 0; i < COUNT; i++) {
String s3 = s1 + s2;
}
}
在循环中使用以下语句String s3 = s1 + s2;
1.33秒
String s3 = new StringBuilder(s1).append(s2).toString();
1.28秒
String s3 = new StringBuffer(s1).append(s2).toString();
1.92秒
String s3 = s1.concat(s2);
0.70秒
String s3 = "1234567890" + "1234567890";
concat函数是最优选择,除非你有静态字符串,在这种情况下,编译器已经为你处理了。
我相信答案可能已经确定,但我发布分享代码。
简短的回答,如果你只是想要纯粹的连接字符串,那就是:String.concat(...)
输出:
ITERATION_LIMIT1: 1
ITERATION_LIMIT2: 10000000
s1: STRING1-1111111111111111111111
s2: STRING2-2222222222222222222222
iteration: 1
null: 1.7 nanos
s1.concat(s2): 106.1 nanos
s1 + s2: 251.7 nanos
new StringBuilder(s1).append(s2).toString(): 246.6 nanos
new StringBuffer(s1).append(s2).toString(): 404.7 nanos
String.format("%s%s", s1, s2): 3276.0 nanos
Tests complete
样例代码:
package net.fosdal.scratch;
public class StringConcatenationPerformance {
private static final int ITERATION_LIMIT1 = 1;
private static final int ITERATION_LIMIT2 = 10000000;
public static void main(String[] args) {
String s1 = "STRING1-1111111111111111111111";
String s2 = "STRING2-2222222222222222222222";
String methodName;
long startNanos, durationNanos;
int iteration2;
System.out.println("ITERATION_LIMIT1: " + ITERATION_LIMIT1);
System.out.println("ITERATION_LIMIT2: " + ITERATION_LIMIT2);
System.out.println("s1: " + s1);
System.out.println("s2: " + s2);
int iteration1 = 0;
while (iteration1++ < ITERATION_LIMIT1) {
System.out.println();
System.out.println("iteration: " + iteration1);
// method #0
methodName = "null";
iteration2 = 0;
startNanos = System.nanoTime();
while (iteration2++ < ITERATION_LIMIT2) {
method0(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #1
methodName = "s1.concat(s2)";
iteration2 = 0;
startNanos = System.nanoTime();
while (iteration2++ < ITERATION_LIMIT2) {
method1(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #2
iteration2 = 0;
startNanos = System.nanoTime();
methodName = "s1 + s2";
while (iteration2++ < ITERATION_LIMIT2) {
method2(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #3
iteration2 = 0;
startNanos = System.nanoTime();
methodName = "new StringBuilder(s1).append(s2).toString()";
while (iteration2++ < ITERATION_LIMIT2) {
method3(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #4
iteration2 = 0;
startNanos = System.nanoTime();
methodName = "new StringBuffer(s1).append(s2).toString()";
while (iteration2++ < ITERATION_LIMIT2) {
method4(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #5
iteration2 = 0;
startNanos = System.nanoTime();
methodName = "String.format(\"%s%s\", s1, s2)";
while (iteration2++ < ITERATION_LIMIT2) {
method5(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
}
System.out.println();
System.out.println("Tests complete");
}
public static String method0(String s1, String s2) {
return "";
}
public static String method1(String s1, String s2) {
return s1.concat(s2);
}
public static String method2(String s1, String s2) {
return s1 + s2;
}
public static String method3(String s1, String s2) {
return new StringBuilder(s1).append(s2).toString();
}
public static String method4(String s1, String s2) {
return new StringBuffer(s1).append(s2).toString();
}
public static String method5(String s1, String s2) {
return String.format("%s%s", s1, s2);
}
}
plus: 118 ns
concat: 52 ns
builder1: 102 ns
builder2: 66 ns
buffer1: 119 ns
buffer2: 87 ns
使用这个实现:
private static long COUNT = 10000000;
public static void main(String[] args) throws Exception {
String s1 = UUID.randomUUID().toString();
String s2 = UUID.randomUUID().toString();
for(String methodName : new String[] {
"none", "plus", "concat", "builder1", "builder2", "buffer1", "buffer2"
}) {
Method method = ConcatPerformanceTest.class.getMethod(methodName, String.class, String.class);
long time = System.nanoTime();
for(int i = 0; i < COUNT; i++) {
method.invoke((Object) null, s1, s2);
}
System.out.println(methodName + ": " + (System.nanoTime() - time)/COUNT + " ns");
}
}
public static String none(String s1, String s2) {
return null;
}
public static String plus(String s1, String s2) {
return s1 + s2;
}
public static String concat(String s1, String s2) {
return s1.concat(s2);
}
public static String builder1(String s1, String s2) {
return new StringBuilder(s1).append(s2).toString();
}
public static String builder2(String s1, String s2) {
return new StringBuilder(s1.length() + s2.length()).append(s1).append(s2).toString();
}
public static String buffer1(String s1, String s2) {
return new StringBuffer(s1).append(s2).toString();
}
public static String buffer2(String s1, String s2) {
return new StringBuffer(s1.length() + s2.length()).append(s1).append(s2).toString();
}
String.concat
通常是连接两个String
的最快方法(但请注意null
)。没有[过大]中间缓冲区或其他对象参与。奇怪的是,+
编译成相对低效的代码,涉及StringBuilder
。List
和通用元组类是一种懒散的hack。在我的Windows和远程Linux机器上进行基准测试后,我发现以下concat3方法是最快的方式: 尽管我相信concat1的性能取决于JVM的实现和优化,并且在未来版本中可能表现更好。
public class StringConcat {
public static void main(String[] args) {
int run = 100 * 100 * 1000;
long startTime, total = 0;
final String a = "a";
final String b = "assdfsaf";
final String c = "aasfasfsaf";
final String d = "afafafdaa";
final String e = "afdassadf";
startTime = System.currentTimeMillis();
concat1(run, a, b, c, d, e);
total = System.currentTimeMillis() - startTime;
System.out.println(total);
startTime = System.currentTimeMillis();
concat2(run, a, b, c, d, e);
total = System.currentTimeMillis() - startTime;
System.out.println(total);
startTime = System.currentTimeMillis();
concat3(run, a, b, c, d, e);
total = System.currentTimeMillis() - startTime;
System.out.println(total);
}
private static void concat3(int run, String a, String b, String c, String d, String e) {
for (int i = 0; i < run; i++) {
String str = new StringBuilder(a.length() + b.length() + c.length() + d.length() + e.length()).append(a)
.append(b).append(c).append(d).append(e).toString();
}
}
private static void concat2(int run, String a, String b, String c, String d, String e) {
for (int i = 0; i < run; i++) {
String str = new StringBuilder(a).append(b).append(c).append(d).append(e).toString();
}
}
private static void concat1(int run, String a, String b, String c, String d, String e) {
for (int i = 0; i < run; i++) {
String str = a + b + c + d + e;
}
}
}
public class Pair<T1, T2> {
private T1 first;
private T2 second;
public static <U1,U2> Pair<U1,U2> create(U1 first, U2 second) {
return new Pair<U1,U2>(U1,U2);
}
public Pair( ) {}
public Pair( T1 first, T2 second ) {
this.first = first;
this.second = second;
}
public T1 getFirst( ) {
return first;
}
public void setFirst( T1 first ) {
this.first = first;
}
public T2 getSecond( ) {
return second;
}
public void setSecond( T2 second ) {
this.second = second;
}
@Override
public String toString( ) {
return "Pair [first=" + first + ", second=" + second + "]";
}
@Override
public int hashCode( ) {
final int prime = 31;
int result = 1;
result = prime * result + ((first == null)?0:first.hashCode());
result = prime * result + ((second == null)?0:second.hashCode());
return result;
}
@Override
public boolean equals( Object obj ) {
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
Pair<?, ?> other = (Pair<?, ?>) obj;
if ( first == null ) {
if ( other.first != null )
return false;
}
else if ( !first.equals(other.first) )
return false;
if ( second == null ) {
if ( other.second != null )
return false;
}
else if ( !second.equals(other.second) )
return false;
return true;
}
}
将此作为您的HashMap键
使用HashMap<Pair<String,String>,Whatever>
而不是HashMap<String,Whatever>
在紧密循环中,您将使用map.get(Pair.create(str1,str2))
而不是map.get(str1 + str2)
。
public static void main方法
,以便于进一步参考。 - Deepak我建议尝试Thorbjørn Ravn Andersen的建议。
如果您需要根据两个部分的长度连接字符串,则创建具有所需大小的StringBuilder实例可能会稍微更好,以避免重新分配。默认的StringBuilder构造函数在当前实现中保留16个字符 - 至少在我的机器上是这样。因此,如果连接的字符串比初始缓冲区大小长,StringBuilder必须重新分配。
试试这个,并告诉我们您的分析器对此的看法:
StringBuilder ccyPair = new StringBuilder(ccy1.length()+ccy2.length());
ccyPair.append(ccy1);
ccyPair.append(ccy2);
为了提高重复字符串连接的性能,Java编译器可以使用StringBuffer类或类似技术来减少通过表达式求值创建的中间String对象的数量。
因此,基本上对于变量,使用+运算符
或StringBuilder.append
是基本相同的。
我使用了稍微修改过的@Duncan McGregor示例。我有5种方法使用concat连接2到6个字符串,并且有5种方法使用StringBuilder连接2到6个字符串:
// Initialization
private final String s1 = new String("1234567890");
private final String s2 = new String("1234567890");
private final String s3 = new String("1234567890");
private final String s4 = new String("1234567890");
private final String s5 = new String("1234567890");
private final String s6 = new String("1234567890");
// testing the concat
public void testConcatenation2stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2);
}
}
public void testConcatenation3stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2).concat(s3);
}
}
public void testConcatenation4stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2).concat(s3).concat(s4);
}
}
public void testConcatenation5stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2).concat(s3).concat(s4).concat(s5);
}
}
public void testConcatenation6stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2).concat(s3).concat(s4).concat(s5).concat(s6);
}
}
//testing the StringBuilder
public void testConcatenation2stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).toString();
}
}
public void testConcatenation3stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).append(s3).toString();
}
}
public void testConcatenation4stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).append(s3).append(s4).toString();
}
}
public void testConcatenation5stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).append(s3).append(s4).append(s5).toString();
}
}
public void testConcatenation6stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).append(s3).append(s4).append(s5).append(s6).toString();
}
}
有趣的是,StringJoiner
在这里没有被提到...
通常需要在字符串之间插入分隔符,例如", "
。
使用StringJoiner
比使用StringBuilder
更易于阅读,速度也相同。
StringJoiner joiner = new StringJoiner( ", " );
joiner.add( ccy1 ).add( ccy2 );
StringBuilder
明显比 StringJoiner("")
更快。一旦你想在字符串之间加入分隔符,速度就不会有明显的差异了。在后一种情况下,我更喜欢使用 StringJoiner
来提高可读性。当然,进行基准测试肯定是很有趣的。 - Kaplan