Dieser Artikel basiert auf der Frage: Best Practices für die Java-String-Verbindung?
Es gibt viele Möglichkeiten, Strings in Java zu verbinden, z. B. Operatoren und die StringBuilder.append-Methode. Welche Vor- und Nachteile haben diese? Jede dieser Methoden (kann entsprechend erläutert werden) Implementierungsdetails verschiedener Methoden)?
Was sind nach dem Prinzip der Effizienz die Best Practices für die String-Verkettung in Java?
Welche anderen Best Practices? Gibt es für die String-Verarbeitung?
Ohne weitere Umschweife: Die Umgebung ist wie folgt:
CPU: i7 4790
Speicher: 16G
Spleißen direkt verwenden
Sehen Sie sich den folgenden Code an:
Im obigen Code verwenden wir das Pluszeichen Verbinden Sie vier Strings. Die Vorteile der Spleißmethode liegen auf der Hand: Der Code ist einfach und intuitiv, aber im Vergleich zu StringBuilder und StringBuffer ist er in den meisten Fällen niedriger als letzterer Javap-Tool zum Dekompilieren des vom obigen Code generierten Bytecodes. Sehen Sie, was der Compiler mit diesem Code macht.@Test public void test() { String str1 = "abc"; String str2 = "def"; logger.debug(str1 + str2); }
public void test(); Code: 0: ldc #5 // String abc 2: astore_1 3: ldc #6 // String def 5: astore_2 6: aload_0 7: getfield #4 // Field logger:Lorg/slf4j/Logger; 10: new #7 // class java/lang/StringBuilder 13: dup 14: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V 17: aload_1 18: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: aload_2 22: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 25: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 28: invokeinterface #11, 2 // InterfaceMethod org/slf4j/Logger.debug:(Ljava/lang/String;)V 33: return
StringBuilder-Quellcode-Analyse
Die Die Antwort lautet natürlich: Nein, der Grund liegt darin, was die StringBuilder-Klasse intern tut.
Werfen wir einen Blick auf den Konstruktor der StringBuilder-Klasse
StringBuilder bietet neben dem parameterlosen Konstruktor auch drei weitere überladene Versionen Die super(int-Kapazität)-Konstruktionsmethode der übergeordneten Klasse wird intern aufgerufen. Ihre übergeordnete Klasse ist AbstractStringBuilder. Die Konstruktionsmethode lautet wie folgt:public StringBuilder() { super(16); } public StringBuilder(int capacity) { super(capacity); } public StringBuilder(String str) { super(str.length() + 16); append(str); } public StringBuilder(CharSequence seq) { this(seq.length() + 16); append(seq); }
AbstractStringBuilder(int capacity) { value = new char[capacity]; }
Das heißt, wenn die Gesamtlänge der zu spleißenden Zeichenfolgen nicht weniger als 16 Zeichen beträgt, gibt es keinen großen Unterschied zwischen direktem Spleißen und unserem manuellen StringBuilder, aber wir können die Größe angeben des Arrays, indem wir die StringBuilder-Klasse selbst erstellen, um zu vermeiden, dass zu viel Speicher zugewiesen wird.
Jetzt werfen wir einen Blick darauf, was in der StringBuilder.append-Methode geschieht:
Die Append-Methode der übergeordneten Klasse, die direkt aufgerufen wird:@Override public StringBuilder append(String str) { super.append(str); return this; }
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } value = Arrays.copyOf(value, newCapacity); }
Vergleich zwischen der Verwendung von Spleißen und der Verwendung von StringBuilder
Der obige Code ist so optimiert, dass er äquivalent ist zu:@Test public void test() { String str = ""; for (int i = 0; i < 10000; i++) { str += "asjdkla"; } }
@Test public void test() { String str = null; for (int i = 0; i < 10000; i++) { str = new StringBuilder().append(str).append("asjdkla").toString(); } }
public void test() { StringBuilder sb = new StringBuilder("asjdkla".length() * 10000); for (int i = 0; i < 10000; i++) { sb.append("asjdkla"); } String str = sb.toString(); }
Derselbe Code oben dauert beim Anpassen der Anzahl der Schleifen auf 1000000 auf meinem Computer etwa 20 ms, wenn die Kapazität angegeben ist, und etwa 29 ms, wenn die Kapazität nicht angegeben ist. Dieser Unterschied unterscheidet sich jedoch von dem von direkte Verwendung Der Operator wurde erheblich verbessert (und die Anzahl der Schleifen wurde um das Hundertfache erhöht), löst jedoch weiterhin mehrere Erweiterungen und Replikationen aus.
Ändern Sie den obigen Code, um StringBuffer zu verwenden. Dies liegt daran, dass StringBuffer den meisten Methoden ein gewisses Maß an Reduzierung verleiht.
Verwenden Sie String.concat zum Spleißen
Sehen Sie sich nun diesen Code an:
@Test public void test() { String str = ""; for (int i = 0; i < 10000; i++) { str.concat("asjdkla"); } }
这段代码使用了String.concat方法,在我的机器上,执行时间大约为130ms,虽然直接相加要好的多,但是比起使用StringBuilder还要太多了,似乎没什么用。其实并不是,在很多时候,我们只需要连接两个字符串,而不是多个字符串的拼接,这个时候使用String.concat方法比StringBuilder要简洁且效率要高。
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
上面这段是String.concat的源码,在这个方法中,调用了一次Arrays.copyOf,并且指定了len + otherLen,相当于分配了一次内存空间,并分别从str1和str2各复制一次数据。而如果使用StringBuilder并指定capacity,相当于分配一次内存空间,并分别从str1和str2各复制一次数据,最后因为调用了toString方法,又复制了一次数据。
结论
现在根据上面的分析和测试可以知道:
Java中字符串拼接不要直接使用+拼接。
使用StringBuilder或者StringBuffer时,尽可能准确地估算capacity,并在构造时指定,避免内存浪费和频繁的扩容及复制。
在没有线程安全问题时使用StringBuilder, 否则使用StringBuffer。
两个字符串拼接直接调用String.concat性能最好。
关于String的其他最佳实践
用equals时总是把能确定不为空的变量写在左边,如使用"".equals(str)判断空串,避免空指针异常。
第二点是用来排挤第一点的.. 使用str != null && str.length() != 0来判断空串,效率比第一点高。
在需要把其他对象转换为字符串对象时,使用String.valueOf(obj)而不是直接调用obj.toString()方法,因为前者已经对空值进行检测了,不会抛出空指针异常。
使用String.format()方法对字符串进行格式化输出。
在JDK 7及以上版本,可以在switch结构中使用字符串了,所以对于较多的比较,使用switch代替if-else。