- What's the fastest way to concatenate two Strings in Java?
- String ccyPair = ccy1 + ccy2;
- 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");
- @Test public void testConcatenation() {
- for (int i = 0; i < COUNT; i++) {
- String s3 = s1 + s2;
- }
- }
- String s3 = s1 + s2;
- String s3 = new StringBuilder(s1).append(s2).toString();
- String s3 = new StringBuffer(s1).append(s2).toString();
- String s3 = s1.concat(s2);
- String s3 = "1234567890" + "1234567890";
- 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;
- }
- }
- int h1 = ccy1.hashCode(), h2 = ccy2.hashCode(), h = h1 ^ h2;
- StringBuilder ccyPair = new StringBuilder(ccy1)
- ccyPair.append(ccy2);
- StringBuilder ccyPair = new StringBuilder(ccy1.length()+ccy2.length());
- ccyPair.append(ccy1);
- ccyPair.append(ccy2);
- public Object get(String key1, String key2) ...
- public void put(String key1, String key2, Object value) ...
- 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);
- }
- }
- 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);
- }
- }