Quel est le moyen le plus rapide de concaténer deux chaînes en Java?
c'est à dire
String ccyPair = ccy1 + ccy2;
J'utilise cyPair
comme clé dans une HashMap
et elle est appelée dans une boucle très étroite pour récupérer des valeurs.
Quand je profile alors c'est le goulot d'étranglement
Java.lang.StringBuilder.append(StringBuilder.Java:119)
Java.lang.StringBuilder.(StringBuilder.Java:93)
La raison pour laquelle ces routines apparaissent dans le test de performance est que le compilateur implémente votre "+" sous les couvertures.
Si vous avez vraiment besoin de la chaîne concaténée, vous devriez laisser le compilateur faire sa magie avec le "+". Si vous avez tous besoin d'une clé pour la recherche de carte, une classe de clés contenant les deux chaînes avec les implémentations appropriées de equals
et hashMap
pourrait être une bonne idée car elle évite l'étape de copie.
Beaucoup de théorie - temps pour la pratique!
private final String s1 = new String("1234567890");
private final String s2 = new String("1234567890");
Utilisation de lignes simples pour des boucles de 10 000 000, sur un Hotspot 64 bits réchauffé, 1.6.0_22 sur Intel Mac OS.
par exemple
@Test public void testConcatenation() {
for (int i = 0; i < COUNT; i++) {
String s3 = s1 + s2;
}
}
Avec les déclarations suivantes dans les boucles
String s3 = s1 + s2;
1.33s
String s3 = new StringBuilder(s1).append(s2).toString();
1,28s
String s3 = new StringBuffer(s1).append(s2).toString();
1,92s
String s3 = s1.concat(s2);
0.70s
String s3 = "1234567890" + "1234567890";
0.0s
Donc concat est le gagnant clair, sauf si vous avez des chaînes statiques, auquel cas le compilateur aura déjà pris soin de vous.
Je pense que la réponse a peut-être déjà été déterminée, mais je poste pour partager le code.
Une réponse courte, si la concaténation pure est tout ce que vous recherchez, est: String.concat (...)
Sortie:
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
Exemple de code:
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);
}
}
Vous devez tester avec une chaîne générée au moment de l'exécution (comme UUID.randomUUID (). ToString ()) pas au moment de la compilation (comme "ma chaîne"). Mes résultats sont
plus: 118 ns
concat: 52 ns
builder1: 102 ns
builder2: 66 ns
buffer1: 119 ns
buffer2: 87 ns
avec cette implémentation:
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();
}
Pour la question dans le titre: String.concat
sera généralement le moyen le plus rapide de concaténer deux String
s (mais notez null
s). Aucun tampon intermédiaire [surdimensionné] ou autre objet n'est impliqué. Étrangement, +
est compilé en un code relativement inefficace impliquant StringBuilder
.
Cependant, votre question soulève d’autres problèmes. La concaténation de chaînes pour générer des clés pour une carte est un "anti-idiome" courant. C'est un hack et sujet aux erreurs. Etes-vous sûr que la clé générée est unique? Restera-t-il unique après la maintenance de votre code pour certaines exigences encore inconnues? La meilleure approche consiste à créer une classe de valeur immuable pour la clé. Utiliser une classe List
et générique Tuple est un bidouillage bâclé.
Pour moi, la méthode concat3 ci-dessous est le moyen le plus rapide après avoir effectué des tests de performance sur mes machines Windows et Linux distantes: - Bien que je pense que les performances de concat1 dépendent de l’implémentation et de l’optimisation de la machine virtuelle Java et qu’elles soient meilleures dans les versions futures.
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;
}
}
}
Peut-être devriez-vous créer une classe Pair au lieu de la concaténation?
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;
}
}
Et utilisez ceci comme clé dans votre HashMap
Au lieu de HashMap<String,Whatever>
, utilisez HashMap<Pair<String,String>,Whatever>
Dans votre boucle étroite au lieu de map.get( str1 + str2 )
, vous utiliseriez map.get( Pair.create(str1,str2) )
.
Je recommanderais d'essayer la suggestion de Thorbjørn Ravn Andersens.
Si vous avez besoin des chaînes concaténées, en fonction de la longueur des deux parties, la création de l'instance de StringBuilder avec la taille requise peut être légèrement meilleure pour éviter la réallocation. Le constructeur par défaut de StringBuilder réserve 16 caractères dans l'implémentation actuelle, du moins sur ma machine. Ainsi, si la chaîne concaténée est plus longue que la taille de la mémoire tampon initiale, StringBuilder doit être réaffecté.
Essayez ceci et dites-nous ce que votre profileur a à dire à ce sujet:
StringBuilder ccyPair = new StringBuilder(ccy1.length()+ccy2.length());
ccyPair.append(ccy1);
ccyPair.append(ccy2);
Selon la spécification Java ( et depuis la toute première version de Java ), dans la section "Opérateur de concaténation de chaînes +", il est dit que:
Pour augmenter les performances de la concaténation de chaînes répétée, un fichier Java Le compilateur peut utiliser la classe StringBuffer ou une technique similaire à celle de Réduisez le nombre d'objets String intermédiaires créés par évaluation d'une expression
Donc, fondamentalement, l’utilisation du + operator
ou du StringBuilder.append
pour les variables est fondamentalement la même.
Autre chose, je sais que dans votre question vous avez mentionné l’ajout de 2 chaînes seulement, mais gardez à l’esprit que l’ajout de 3 chaînes ou plus donnera des résultats différents:
J'ai utilisé un exemple légèrement modifié de @Duncan McGregor. J'ai 5 méthodes concaténant 2 à 6 chaînes en utilisant concat et 5 méthodes concaténant 2 à 6 chaînes en utilisant StringBuilder:
// 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();
}
}
J'ai obtenu ces résultats (en secondes):
testConcatenation2stringsConcat: 0.018 |||||||||||||||| testConcatenation2stringsSB: 0.2 testConcatenation3stringsConcat: 0.35 |||||||||||||||||| testConcatenation3stringsSB: 0.25 testConcatenation4stringsConcat: 0.5 ||||||||||||||||||||| testConcatenation4stringsSB: 0.3 testConcatenation5stringsConcat: 0.67 ||||||||||||||||| testConcatenation5stringsSB: 0.38 testConcatenation5stringsConcat: 0.9 ||||||||||||||||||||| testConcatenation5stringsSB: 0.43
Il s’agit ici d’une implémentation complète de la carte à sonde linéaire avec des clés doubles, valeur unique. Il devrait également surperformer Java.util.HashMap.
Attention, il est écrit dans les toutes premières heures de la journée, il peut donc contenir des bugs. S'il vous plaît n'hésitez pas à le modifier.
La solution doit battre n'importe quel wrapper, concat un à tout moment. L'absence d'allocation sur get/put rend également la carte polyvalente rapide.
J'espère que cela résout le problème. (Le code vient avec quelques tests simples qui ne sont pas nécessaires)
package bestsss.util;
@SuppressWarnings("unchecked")
public class DoubleKeyMap<K1, K2, V> {
private static final int MAX_CAPACITY = 1<<29;
private static final Object TOMBSTONE = new String("TOMBSTONE");
Object[] kvs;
int[] hashes;
int count = 0;
final int rehashOnProbes;
public DoubleKeyMap(){
this(8, 5);
}
public DoubleKeyMap(int capacity, int rehashOnProbes){
capacity = nextCapacity(Math.max(2, capacity-1));
if (rehashOnProbes>capacity){
throw new IllegalArgumentException("rehashOnProbes too high");
}
hashes = new int[capacity];
kvs = new Object[kvsIndex(capacity)];
count = 0;
this.rehashOnProbes = rehashOnProbes;
}
private static int nextCapacity(int c) {
int n = Integer.highestOneBit(c)<<1;
if (n<0 || n>MAX_CAPACITY){
throw new Error("map too large");
}
return n;
}
//alternatively this method can become non-static, protected and overriden, the perfoamnce can drop a little
//but if better spread of the lowest bit is possible, all good and proper
private static<K1, K2> int hash(K1 key1, K2 key2){
//spread more, if need be
int h1 = key1.hashCode();
int h2 = key2.hashCode();
return h1+ (h2<<4) + h2; //h1+h2*17
}
private static int kvsIndex(int baseIdx){
int idx = baseIdx;
idx+=idx<<1;//idx*3
return idx;
}
private int baseIdx(int hash){
return hash & (hashes.length-1);
}
public V get(K1 key1, K2 key2){
final int hash = hash(key1, key2);
final int[] hashes = this.hashes;
final Object[] kvs = this.kvs;
final int mask = hashes.length-1;
for(int base = baseIdx(hash);;base=(base+1)&mask){
int k = kvsIndex(base);
K1 k1 = (K1) kvs[k];
if (k1==null)
return null;//null met; no such value
Object value;
if (hashes[base]!=hash || TOMBSTONE==(value=kvs[k+2]))
continue;//next
K2 k2 = (K2) kvs[k+1];
if ( (key1==k1 || key1.equals(k1)) && (key2==k2 || key2.equals(k2)) ){
return (V) value;
}
}
}
public boolean contains(K1 key1, K2 key2){
return get(key1, key2)!=null;
}
public boolean containsValue(final V value){
final Object[] kvs = this.kvs;
if (value==null)
return false;
for(int i=0;i<kvs.length;i+=3){
Object v = kvs[2];
if (v==null || v==TOMBSTONE)
continue;
if (value==v || value.equals(v))
return true;
}
return false;
}
public V put(K1 key1, K2 key2, V value){
int hash = hash(key1, key2);
return doPut(key1, key2, value, hash);
}
public V remove(K1 key1, K2 key2){
int hash = hash(key1, key2);
return doPut(key1, key2, null, hash);
}
//note, instead of remove a TOMBSTONE is used to mark the deletion
//this may leak keys but deletion doesn't need to shift the array like in Knuth 6.4
protected V doPut(final K1 key1, final K2 key2, Object value, final int hash){
//null value -> remove
int probes = 0;
final int[] hashes = this.hashes;
final Object[] kvs = this.kvs;
final int mask = hashes.length-1;
//conservative resize: when too many probes and the count is greater than the half of the capacity
for(int base = baseIdx(hash);probes<rehashOnProbes || count<(mask>>1);base=(base+1)&mask, probes++){
final int k = kvsIndex(base);
K1 k1 = (K1) kvs[k];
K2 k2;
//find a gap, or resize
Object old = kvs[k+2];
final boolean emptySlot = k1==null || (value!=null && old==TOMBSTONE);
if (emptySlot || (
hashes[base] == hash &&
(k1==key1 || k1.equals(key1)) &&
((k2=(K2) kvs[k+1])==key2 || k2.equals(key2)))
){
if (value==null){//remove()
if (emptySlot)
return null;//not found, and no value ->nothing to do
value = TOMBSTONE;
count-=2;//offset the ++later
}
if (emptySlot){//new entry, update keys
hashes[base] = hash;
kvs[k] = key1;
kvs[k+1] = key2;
}//else -> keys and hash are equal
if (old==TOMBSTONE)
old=null;
kvs[k+2] = value;
count++;
return (V) old;
}
}
resize();
return doPut(key1, key2, value, hash);//hack w/ recursion, after the resize
}
//optimized version during resize, doesn't check equals which is the slowest part
protected void doPutForResize(K1 key1, K2 key2, V value, final int hash){
final int[] hashes = this.hashes;
final Object[] kvs = this.kvs;
final int mask = hashes.length-1;
//find the 1st gap and insert there
for(int base = baseIdx(hash);;base=(base+1)&mask){//it's ensured, no equal keys exist, so skip equals part
final int k = kvsIndex(base);
K1 k1 = (K1) kvs[k];
if (k1!=null)
continue;
hashes[base] = hash;
kvs[k] = key1;
kvs[k+1] = key2;
kvs[k+2] = value;
return;
}
}
//resizes the map by doubling the capacity,
//the method uses altervative varian of put that doesn't check equality, or probes; just inserts at a gap
protected void resize(){
final int[] hashes = this.hashes;
final Object[] kvs = this.kvs;
final int capacity = nextCapacity(hashes.length);
this.hashes = new int[capacity];
this.kvs = new Object[kvsIndex(capacity)];
for (int i=0;i<hashes.length; i++){
int k = kvsIndex(i);
K1 key1 = (K1) kvs[k];
Object value = kvs[k+2];
if (key1!=null && TOMBSTONE!=value){
K2 key2 = (K2) kvs[k+1];
doPutForResize(key1, key2, (V) value, hashes[i]);
}
}
}
public static void main(String[] args) {
DoubleKeyMap<String, String, Integer> map = new DoubleKeyMap<String, String, Integer>(4,2);
map.put("eur/usd", "usd/jpy", 1);
map.put("eur/usd", "usd/jpy", 2);
map.put("eur/jpy", "usd/jpy", 3);
System.out.println(map.get("eur/jpy", "usd/jpy"));
System.out.println(map.get("eur/usd", "usd/jpy"));
System.out.println("======");
map.remove("eur/usd", "usd/jpy");
System.out.println(map.get("eur/jpy", "usd/jpy"));
System.out.println(map.get("eur/usd", "usd/jpy"));
System.out.println("======");
testResize();
}
static void testResize(){
DoubleKeyMap<String, Integer, Integer> map = new DoubleKeyMap<String, Integer, Integer>(18, 17);
long s = 0;
String pref="xxx";
for (int i=0;i<14000;i++){
map.put(pref+i, i, i);
if ((i&1)==1)
map.remove(pref+i, i);
else
s+=i;
}
System.out.println("sum: "+s);
long sum = 0;
for (int i=0;i<14000;i++){
Integer n = map.get(pref+i, i);
if (n!=null && n!=i){
throw new AssertionError();
}
if (n!=null){
System.out.println(n);
sum+=n;
}
}
System.out.println("1st sum: "+s);
System.out.println("2nd sum: "+sum);
}
}
N'oubliez pas que si vous concaténez des millions de chaînes, string.concat générera probablement des millions de nouvelles références d'objet de chaîne. Cela aura une utilisation accrue du processeur.
StringBuffer ccyPair = new StringBuffer();
ccyPair.append("ccy1").append("ccy2");
Avez-vous déjà essayé d’utiliser un tampon de chaîne puis d’utiliser un profileur pour déterminer le goulot d’étranglement? Essayez-le et voyez ce qui se passe.
La réponse de @Duncan McGregor donne des chiffres de référence pour un exemple particulier (tailles de la chaîne d'entrée) et une version de la JVM. Dans ce cas, il semble que String.concat()
soit le gagnant par un facteur important. Ce résultat peut ou non généraliser.
À part: cela me surprend! J'aurais pensé que les rédacteurs du compilateur auraient choisi d'utiliser String.concat dans les cas où cela serait probablement plus rapide. L'explication est dans l'évaluation de ce rapport de bogue ... et est enracinée dans la définition de l'opérateur de concaténation de chaînes.
(Si un opérande de type chaîne de +
est null
, le JLS indique que la chaîne "null"
est utilisée à sa place. Cela ne fonctionnerait pas si le code généré par s + s2
sous la forme s.concat(s2)
et s
ou s2
était réellement null
; Et le cas de s == null
signifie qu'une version alternative de concat
ne résout pas le problème de NPE.)
Cependant, la réponse de @winden m'a donné une idée d'une solution alternative évitant le recours à la concaténation de chaînes.
Si les concaténations de ccy1
et ccy2
ne servent qu'à associer deux clés, vous obtiendrez peut-être de meilleures performances en définissant une classe de table de hachage spéciale qui prend deux clés au lieu d'une. Il aurait des opérations comme:
public Object get(String key1, String key2) ...
public void put(String key1, String key2, Object value) ...
L'effet ressemblerait à un Map<Pair<String, String>, Object>
(voir la réponse de @ KitsuneYMG), sauf qu'il n'est pas nécessaire de créer des objets Pair<String, String>
à chaque fois que vous souhaitez créer une get
ou put
. L'inconvénient est:
Map
.Normalement, je ne recommanderais pas cela. Toutefois, si la concaténation de chaînes et la recherche de cartes constituent réellement un goulot d'étranglement, une table de hachage multi-clé personnalisée peut vous permettre de gagner beaucoup de temps.
Vous pouvez peut-être contourner le problème en calculant les hachages des deux chaînes individuellement, puis en les combinant, éventuellement avec une fonction de hachage distincte qui fonctionne sur des entiers?
Quelque chose comme:
int h1 = ccy1.hashCode(), h2 = ccy2.hashCode(), h = h1 ^ h2;
Cela pourrait bien être plus rapide, car concaténer des chaînes uniquement pour calculer le hash de la concaténation semble inutile.
Notez que ce qui précède combine les deux hachages avec binary-XOR (l'opérateur ^
), ce qui fonctionne souvent, mais vous voudrez peut-être approfondir cette question.
Ok, alors quelle est votre question? Rien à faire: si vous devez concaténer des chaînes, faites-le. C'est bien que vous ayez profilé votre code. Vous pouvez maintenant voir que l'opérateur de concaténation de chaînes + utilise automatiquement la méthode append () de StringBuilder.
StringBuilder ccyPair = new StringBuilder(ccy1)
ccyPair.append(ccy2);
ne vous donne pas d'avantages sérieux.
Le seul moyen sérieux d’optimiser votre code est probablement de modifier votre conception afin d’omettre la concaténation. Mais ne le faites que si vous en avez vraiment besoin, c’est-à-dire que la concaténation prend une partie importante du temps processeur.