3

可能有数百个关于 Java 集合与数组的问题,但这是我真的没想到的。

我正在为我的游戏开发一个服务器,为了在客户端和服务器之间进行通信,你需要发送数据包(显然),所以我做了一些测试,我可以最好地使用哪个 Collection(或数组)来处理它们,HashMap、ArrayList 和一个PacketHandler 数组。结果出乎我的意料,因为 ArrayList 获胜。

数据包处理结构就像字典的使用(PacketHandler 的索引),因为数组是字典使用的最原始形式,我认为它比 ArrayList 更容易执行。有人可以解释一下为什么会这样吗?

我的测试

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

public class Main {
  /**
   * Packet handler interface.
   */
  private interface PacketHandler {
    void handle();
  }

  /**
   * A dummy packet handler.
   */
  private class DummyPacketHandler implements PacketHandler {
    @Override
    public void handle() { }
  }

  public Main() {
    Random r = new Random();
    PacketHandler[] handlers = new PacketHandler[256];
    HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
    ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();

    // packet handler initialization
    for (int i = 0; i < 255; i++) {
      DummyPacketHandler p = new DummyPacketHandler();
      handlers[i] = p;
      m.put(new Integer(i), p);
      list.add(p);
    }

    // array
    long time = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++)
      handlers[r.nextInt(255)].handle();
    System.out.println((System.currentTimeMillis() - time));

    // hashmap
    time = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++)
      m.get(new Integer(r.nextInt(255))).handle();
    System.out.println((System.currentTimeMillis() - time));

    // arraylist
    time = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++)
      list.get(r.nextInt(255)).handle();
    System.out.println((System.currentTimeMillis() - time));
  }

  public static void main(String[] args) {
    new Main();
  }
}

我觉得问题已经解决了,谢谢大家

4

2 回答 2

4

较短的答案是 ArrayList 第一次优化得稍微多一些,但从长远来看仍然较慢。

JVM 在完全预热之前如何以及何时优化代码并不总是显而易见的,并且可以根据您的命令行选项在版本之间进行更改。

真正有趣的是当你重复测试时你会得到什么。在这里有所不同的原因是代码是在后台分阶段编译的,因为您希望在代码已经与从一开始就立即运行的速度一样快的情况下进行测试。


您可以做一些事情来使您的基准测试更具可重复性。

  • 提前生成随机数,它们不是测试的一部分,但会减慢你的速度。
  • 将每个循环放在一个单独的方法中。第一个循环触发整个方法被编译为更好或更坏。
  • 重复测试 5 到 10 次,忽略第一次
  • 使用 System.nanoTime() 而不是 currentTimeMillis() 在这里可能没有任何区别,但这是一个好习惯。
  • 尽可能使用自动装箱,以便它使用整数缓存或 Integer.valueOf(n) 来做同样的事情。new Integer(n)将始终创建一个对象。
  • 确保您的内部循环执行某些操作,否则 JIT 很可能会将其优化为无。;)

.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

public class Main {

    /**
     * Packet handler interface.
     */
    private interface PacketHandler {
        void handle();
    }

    /**
     * A dummy packet handler.
     */
    static class DummyPacketHandler implements PacketHandler {

        @Override
        public void handle() {
        }

    }

    public static void main(String[] args) {
        Random r = new Random();

        PacketHandler[] handlers = new PacketHandler[256];
        HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
        ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();

        // packet handler initialization
        for (int i = 0; i < 256; i++) {
            DummyPacketHandler p = new DummyPacketHandler();
            handlers[i] = p;
            m.put(new Integer(i), p);
            list.add(p);
        }

        int runs = 10000000;
        int[] handlerToUse = new int[runs];
        for (int i = 0; i < runs; i++)
            handlerToUse[i] = r.nextInt(256);

        for (int i = 0; i < 5; i++) {
            testArray(handlers, runs, handlerToUse);
            testHashMap(m, runs, handlerToUse);
            testArrayList(list, runs, handlerToUse);
            System.out.println();
        }
    }

    private static void testArray(PacketHandler[] handlers, int runs, int[] handlerToUse) {
        // array
        long time = System.nanoTime();
        for (int i = 0; i < runs; i++)
            handlers[handlerToUse[i]].handle();
        System.out.print((System.nanoTime() - time)/1e6+" ");
    }

    private static void testHashMap(HashMap<Integer, PacketHandler> m, int runs, int[] handlerToUse) {
        // hashmap
        long time = System.nanoTime();
        for (int i = 0; i < runs; i++)
            m.get(handlerToUse[i]).handle();
        System.out.print((System.nanoTime() - time)/1e6+" ");
    }

    private static void testArrayList(ArrayList<PacketHandler> list, int runs, int[] handlerToUse) {
        // arraylist
        long time = System.nanoTime();
        for (int i = 0; i < runs; i++)
            list.get(handlerToUse[i]).handle();
        System.out.print((System.nanoTime() - time)/1e6+" ");
    }
}

打印array HashMap ArrayList

24.62537 263.185092 24.19565 
28.997305 206.956117 23.437585 
19.422327 224.894738 21.191718 
14.154433 194.014725 16.927638 
13.897081 163.383876 16.678818 

代码热身后,数组会稍微快一些。

于 2012-09-13T17:53:29.273 回答
2

您的基准测试至少存在一些问题:

  • 您直接在 main 中运行测试,这意味着当您编译 main 方法时,JIT 编译器还没有时间优化所有代码,因为它还没有运行它
  • map 方法每次都会创建一个新的整数,这是不公平的:使用m.get(r.nextInt(255)).handle();允许使用 Integer 缓存
  • 您需要多次运行测试才能得出结论
  • 您没有使用您在循环中执行的操作的结果,因此允许 JIT 简单地忽略它们
  • 监控 GC,因为它可能总是同时运行并偏向一个循环的结果,并System.gc()在每个循环之间添加一个调用。

但在做所有这些之前,请阅读这篇文章;-)

稍微调整一下代码后,我得到了以下结果:

Array: 116
Map: 139
List: 117

因此数组和列表在编译后几乎相同,而 map 稍微慢一些。

代码:

公共类主要{

/**
 * Packet handler interface.
 */
private interface PacketHandler {

    int handle();
}

/**
 * A dummy packet handler.
 */
private class DummyPacketHandler implements PacketHandler {

    @Override
    public int handle() {
        return 123;
    }
}

public Main() {
    Random r = new Random();

    PacketHandler[] handlers = new PacketHandler[256];
    HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
    ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();

    // packet handler initialization
    for (int i = 0; i < 255; i++) {
        DummyPacketHandler p = new DummyPacketHandler();
        handlers[i] = p;
        m.put(new Integer(i), p);
        list.add(p);
    }

    long sum = 0;
    runArray(handlers, r, 20000);
    runMap(m, r, 20000);
    runList(list, r, 20000);

    // array
    long time = System.nanoTime();
    sum += runArray(handlers, r, 10000000);
    System.out.println("Array: " + (System.nanoTime() - time) / 1000000);

    // hashmap
    time = System.nanoTime();
    sum += runMap(m, r, 10000000);
    System.out.println("Map: " + (System.nanoTime() - time) / 1000000);

    // arraylist
    time = System.nanoTime();
    sum += runList(list, r, 10000000);
    System.out.println("List: " + (System.nanoTime() - time) / 1000000);

    System.out.println(sum);
}

public static void main(String[] args) {
    new Main();
}


private long runArray(PacketHandler[] handlers, Random r, int loops) {
    long sum = 0;
    for (int i = 0; i < loops; i++)
        sum += handlers[r.nextInt(255)].handle();
    return sum;
}

private long runMap(HashMap<Integer, PacketHandler> m, Random r, int loops) {
    long sum = 0;
    for (int i = 0; i < loops; i++)
        sum += m.get(new Integer(r.nextInt(255))).handle();
    return sum;
}

private long runList(List<PacketHandler> list, Random r, int loops) {
    long sum = 0;
    for (int i = 0; i < loops; i++)
        sum += list.get(r.nextInt(255)).handle();
    return sum;
}

}

于 2012-09-13T17:54:16.240 回答