在Java中连接两个字符串的最快方法是什么?

时间:2011-02-22 10:08:52

标签: java performance string

在Java中连接两个字符串的最快方法是什么?

String ccyPair = ccy1 + ccy2;

我使用cyPair作为HashMap中的一个键,并且在一个非常紧凑的循环中调用它来检索值。

当我描述时,这是瓶颈

java.lang.StringBuilder.append(StringBuilder.java:119)  
java.lang.StringBuilder.(StringBuilder.java:93)

17 个答案:

答案 0 :(得分:39)

很多理论 - 一些练习的时间!

private final String s1 = new String("1234567890");
private final String s2 = new String("1234567890");

在热备的64位热点上使用plain for 10,000,000循环,在Intel Mac OS上使用1.6.0_22。

例如

@Test public void testConcatenation() {
    for (int i = 0; i < COUNT; i++) {
        String s3 = s1 + s2;
    }
}

在循环中使用以下语句

String s3 = s1 + s2; 

1.33s

String s3 = new StringBuilder(s1).append(s2).toString();

1.28秒

String s3 = new StringBuffer(s1).append(s2).toString();

1.92s

String s3 = s1.concat(s2);

0.70s

String s3 = "1234567890" + "1234567890";

0.0秒

所以concat是明显的赢家,除非你有静态字符串,在这种情况下编译器已经照顾你了。

答案 1 :(得分:19)

这些例程出现在基准测试中的原因是因为这就是编译器在封面下实现“+”的方式。

如果你真的需要连接字符串,你应该让编译器用“+”来实现它的魔力。如果您只需要一个用于地图查找的键,那么包含具有适当equalshashMap实现的两个字符串的键类可能是一个好主意,因为它可以避免复制步骤。

答案 2 :(得分:17)

我相信答案可能已经确定,但我发帖分享代码。

简短的回答,如果您正在寻找纯粹的连接,那就是: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);
    }

}

答案 3 :(得分:7)

您应该使用在运行时生成的String(如UUID.randomUUID()。toString())进行测试,而不是在编译时(例如&#34; my string&#34;)。我的结果是

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();
}

答案 4 :(得分:5)

对于标题中的问题:String.concat通常是连接两个String的最快方式(但请注意null s)。不涉及[超大]中间缓冲区或其他对象。奇怪的是,+被编译成涉及StringBuilder的相对低效的代码。

然而,你的身体问题指向其他问题。用于为地图生成键的字符串连接是常见的“反成语”。这是一个黑客,容易出错。您确定生成的密钥是唯一的吗?在为某些尚未知的要求维护代码后,它是否仍然是唯一的?最好的方法是为密钥创建一个不可变的值类。使用List和通用元组类是一个草率的黑客。

答案 5 :(得分:3)

对我来说,concat3方法如下是在我的Windows和远程linux机器上进行基准测试后最快的方法: - 虽然我认为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;
        }
    }
}

答案 6 :(得分:1)

也许不是连接,而是应该创建一个Pair类?

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<String,Whatever>使用HashMap<Pair<String,String>,Whatever>

在紧密循环而不是map.get( str1 + str2 )中,您使用map.get( Pair.create(str1,str2) )

答案 7 :(得分:1)

我建议尝试ThorbjørnRavnAndersens的建议。

如果需要连接的字符串,取决于两个部分的长度,创建具有所需大小的StringBuilder实例可能会稍微好一点,以避免重新分配。默认的StringBuilder构造函数在当前实现中保留16个字符 - 至少在我的机器上。因此,如果连接的String长于初始缓冲区大小,则StringBuilder必须重新分配。

试试这个并告诉我们你的探查者对此有何看法:

StringBuilder ccyPair = new StringBuilder(ccy1.length()+ccy2.length());
ccyPair.append(ccy1); 
ccyPair.append(ccy2); 

答案 8 :(得分:0)

我决定尝试对其进行基准测试,这就是我的结果。我猜想使用默认的“ +”串联是最简单,最快(或几乎最快)的方法。

JMH version: 1.19
VM version: JDK 1.8.0_211, VM 25.211-b12
VM options: -Xms2G -Xmx2G
Warmup: 10 iterations, 1 s each
Measurement: 30 iterations, 1 s each
Timeout: 10 min per iteration
Threads: 1 thread, will synchronize iterations
Benchmark mode: Average time, time/op
Parameters: (N = 1000000)

Benchmark          (N)  Mode  Cnt  Score     Error  Units
concat         1000000  avgt   30  24.839  ± 0.211  ms/op
plus           1000000  avgt   30  15.072  ± 0.155  ms/op
stringBuffer   1000000  avgt   30  14.835  ± 0.118  ms/op
stringBuilder  1000000  avgt   30  14.775  ± 0.205  ms/op

这是基准代码:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 10)
@Measurement(iterations = 30)
public class BenchmarkString {

    @Param({"1000000"})
    private int N;
    private final String s1 = new String("1234567890124567890");
    private final String s2 = new String("1234567890124567890");

    public static void main(String[] args) throws RunnerException {

        Options opt = new OptionsBuilder()
                .include(BenchmarkString.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }

    @Benchmark
    public void plus() {
        for (int i = 0; i < N; i++) {
            String s = s1 + s2;
        }
    }

    @Benchmark
    public void stringBuilder() {
        for (int i = 0; i < N; i++) {
            String s = new StringBuilder(s1).append(s2).toString();
        }
    }

    @Benchmark
    public void stringBuffer() {
        for (int i = 0; i < N; i++) {
            String s = new StringBuffer(s1).append(s2).toString();
        }
    }

    @Benchmark
    public void concat() {
        for (int i = 0; i < N; i++) {
            String s = s1.concat(s2);
        }
    }
}

答案 9 :(得分:0)

有趣的是, ", " 这里没有提到……

通常,必须在字符串之间插入分隔符,例如。 StringJoiner
使用StringBuilder比使用StringJoiner joiner = new StringJoiner( ", " ); joiner.add( ccy1 ).add( ccy2 ); 更容易阅读代码,并且同样快。

document.getElementById("id of element").innnerHTML = null;

答案 10 :(得分:0)

根据Java specificationand since the very first version of Java),在“字符串连接运算符+”部分中说:

  

为了提高重复字符串连接的性能,Java   编译器可以使用StringBuffer类或类似的技术来   减少由创建的中间String对象的数量   表达式的评估

因此,基本上,对变量使用+ operatorStringBuilder.append基本上是相同的。


另一件事,我知道在您的问题中您提到仅添加2个字符串,但是请记住,添加3个或更多字符串会导致不同的结果:

我使用了一个稍作修改的@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();
        }
    }

我获得了这些结果(以秒为单位):

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

  • 您可以看到concat仅在以下情况下比StringBuilder更快 仅连接2个字符串
  • 请参见,当添加越来越多的字符串时,StringBuilder产生的时间越来越多 慢慢地使用concat
  • 请注意,当字符串不同时,差异会更大 很长

答案 11 :(得分:0)

请记住,如果要串联数百万个字符串,则string.concat很可能会生成数百万个新的字符串对象引用。这将提高CPU利用率。

答案 12 :(得分:0)

这里是线性探测图的完整实现,带有双键,单值。它也应该优于java.util.HashMap。

警告,它是从头开始写的,所以它可能包含错误。请随时编辑它。

解决方案必须击败任何包装器,随时连接。 get / put上的no分配也使它成为快速的通用映射。

希望这能解决问题。 (代码带有一些不需要的简单测试)


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);


    }
}

答案 13 :(得分:0)

好的,你的问题是什么? 无所事事:如果你必须连接字符串就行了。您可以分析您的代码。现在您可以看到字符串连接运算符+自动使用StringBuilder的append()方法,因此使用

StringBuilder ccyPair = new StringBuilder(ccy1)
ccyPair.append(ccy2);

并没有给你带来很大的好处。

优化代码的唯一严肃方法可能是更改设计以省略连接。但只有当你真的需要它时才这样做,即连接占用CPU时间的很大一部分。

答案 14 :(得分:0)

也许你可以通过单独计算两个字符串的哈希值来解决这个问题,然后将它们组合起来,或许可以使用一个对整数有效的单独哈希函数?

类似的东西:

int h1 = ccy1.hashCode(), h2 = ccy2.hashCode(), h = h1 ^ h2;

这可能会更快,因为连接字符串只是为了计算串联的哈希值似乎很浪费。

请注意,上面的两个哈希值与二进制XOR(^运算符)相结合,这些哈希值通常有效,但您可能需要进一步调查。

答案 15 :(得分:0)

@Duncan McGregor的答案为一个特定的例子(输入字符串的大小)和一个JVM版本提供了一些基准数字。在这种情况下,看起来String.concat()是一个重要因素的赢家。这个结果可能会也可能不会概括。

除此之外:这让我感到惊讶!我原以为编译器编写者会选择在可能更快的情况下使用String.concat。解释是在this bug report ...的评估中,并且根植于字符串连接运算符的定义。

(如果+的字符串类型操作数为null,则JLS声明字符串"null"在其位置使用。如果它们生成代码{ {1}} s + s2s.concat(s2)s恰好是s2;你会得到NPE。null的情况意味着另一种选择版本s == null无法解决NPE问题。)


然而,@ unwind的回答让我想到了一个替代解决方案,它避免了字符串连接的需要。

如果concatccy1的连接只是为了连接两个键,那么通过定义一个带有两个键而不是一个键的特殊哈希表类,可能会获得更好的性能。它将有如下操作:

ccy2

效果类似于 public Object get(String key1, String key2) ... public void put(String key1, String key2, Object value) ... (请参阅@ KitsuneYMG的回答),除了每次要Map<Pair<String, String>, Object>或{{Pair<String, String>时都不需要创建get个对象1}}。缺点是:

  • 你必须从头开始实现一个新的哈希表类,
  • 新类不符合put界面。

通常情况下,我不建议这样做。但是,如果字符串连接和映射查找确实是一个关键的瓶颈,那么自定义多键哈希表可能会为您带来显着的加速。

答案 16 :(得分:0)

StringBuffer ccyPair =  new StringBuffer();      
ccyPair.append("ccy1").append("ccy2"); 

您是否尝试过使用字符串缓冲区,然后使用分析器来检查瓶颈在哪里。试一试,看看会发生什么。