Java中的数组或列表。哪个更快?


351

我必须在内存中保留数千个字符串才能以Java顺序访问。我应该将它们存储在数组中还是应该使用某种List?

由于数组将所有数据保存在连续的内存块中(与列表不同),使用数组存储数千个字符串会不会引起问题?


5
“由于数组将所有数据保存在连续的内存块中”,您是否有任何引用来支持Java?
马特b

1
没有哑光。我对C知道这一点。我猜Java将使用相同的方法。
euphoria83

我怀疑是否会将它们保留在单个内存中。
Fortyrunner

3
即使是单个内存块,它的价值也仍然只有1000 * 4 = 4kb,这并不是很多内存。
CookieOfFortune

3
@mattb这就是CS中“数组”的含义。无需引用。如果数组是连续的,则只能理解JLS和[JVM Spec]()中对数组长度的大量引用。
罗恩侯爵,

Answers:


358

我建议您使用探查器来测试哪个更快。

我个人认为您应该使用列表。

我在大型代码库上工作,以前的一组开发人员到处使用数组。它使代码非常不灵活。将其大块更改为列表后,我们注意到速度没有差异。


2
@Fortyrunner-根据您的经验,在Java和抽象数据形式之间是否有这样的选择对性能产生重大影响?
euphoria83

4
性能评估的问题之一是,您必须不断地对Java的新版本进行重新测试。我正在解决一个问题,当时有人在整个地图中使用int作为键(以节省空间/时间)。现在,我们需要将所有行更改为一个新对象-它很痛苦。
Fortyrunner,2009年

9
所以..我现在尝试远离原始数据。它很少产生显着差异。热点是一项了不起的技术,您永远不要尝试再猜一次。只需尝试编写简单,可维护的代码,剩下的就由Hotspot完成。
Fortyrunner,2009年

4
请记住,探查器结果仅对您运行探查器的Java平台有效。这可能与您的客户不同。
MikkelLøkke,

4
有效的Java建议使用列表,因为它们有助于API的互操作性,并且通过类型安全性也更安全。
juanmf 2014年

164

Java方式是您应考虑最适合您需求的数据抽象。请记住,在Java中,列表是抽象的,而不是具体的数据类型。您应该将字符串声明为List,然后使用ArrayList实现对其进行初始化。

List<String> strings = new ArrayList<String>();

抽象数据类型和特定实现的这种分离是面向对象编程的关键方面之一。

ArrayList使用数组作为其基础实现来实现List Abstract Data Type。访问速度实际上与数组相同,具有的附加优点是能够在List中添加和减去元素(尽管这是使用ArrayList的O(n)操作),并且如果您决定稍后更改基础实现,您可以。例如,如果您意识到需要同步访问,则可以将实现更改为Vector,而无需重写所有代码。

实际上,ArrayList是专门为在大多数情况下替换低级数组构造而设计的。如果今天要设计Java,则完全有可能将数组完全排除在外,而使用ArrayList构造。

由于数组将所有数据保存在连续的内存块中(与列表不同),使用数组存储数千个字符串会不会引起问题?

在Java中,所有集合仅存储对对象的引用,而不存储对象本身。数组和ArrayList都将在连续数组中存储数千个引用,因此它们本质上是相同的。您可以认为,在现代硬件上,将始终可以轻松获得数千个32位引用的连续块。当然,这并不保证您不会完全用完内存,只是连续的内存需求并不难满足。


当然,添加操作可能涉及重新分配支持数组,因此,如果性能很重要并且事先知道了数组的大小,则应考虑使用ArrayList#ensureCapacity。
JesperE

6
您不在这里支付动态绑定的费用吗?
Uri 2009年

2
我猜添加不为O(n)在ArrayList中,增加不止一次时,如容量增加了一倍,而不是只增加了1应该有一定的效果ammortization
zedoo

@zedoo我认为它们的意思是在中间加减。
MalcolmOcean 2012年

“如果今天正在设计Java,则完全有可能完全将数组排除在外,而支持ArrayList构造。” ……我严重怀疑这是真的。如果是今天要重写的JVM,那么您所说的肯定是可能的。但是有了JVM,数组是Java的基本类型。
斯科特,

100

尽管建议使用ArrayList的答案在大多数情况下确实有意义,但相对性能的实际问题尚未真正得到解答。

数组可以做一些事情:

  • 创造它
  • 设置一个项目
  • 得到一个项目
  • 克隆/复制它

一般结论

尽管在ArrayList上进行获取和设置操作要慢一些(在我的机器上,每次调用分别为1和3纳秒),但使用ArrayList与将数组用于任何非密集型用途的开销很少。但是,请记住以下几点:

  • 调整列表上大小的操作(调用时list.add(...))非常昂贵,应尽可能将初始容量设置为适当的水平(请注意,使用数组时也会出现相同的问题)
  • 处理基元时,数组可以大大加快速度,因为它们可以避免很多装箱/拆箱转换
  • 仅获取/设置ArrayList中的值的应用程序(不是很常见!)通过切换到数组,可以将性能提高25%以上

详细结果

这是我在标准x86台式机上使用jmh基准测试库和JDK 7 对这三个操作进行测量的结果(时间为纳秒)。请注意,在测试中永远不会调整ArrayList的大小,以确保结果可比。基准代码可在此处获得

数组/数组列表的创建

我运行了4个测试,执行以下语句:

  • createArray1: Integer[] array = new Integer[1];
  • createList1: List<Integer> list = new ArrayList<> (1);
  • createArray10000: Integer[] array = new Integer[10000];
  • createList10000: List<Integer> list = new ArrayList<> (10000);

结果(每次通话以纳秒为单位,置信度为95%):

a.p.g.a.ArrayVsList.CreateArray1         [10.933, 11.097]
a.p.g.a.ArrayVsList.CreateList1          [10.799, 11.046]
a.p.g.a.ArrayVsList.CreateArray10000    [394.899, 404.034]
a.p.g.a.ArrayVsList.CreateList10000     [396.706, 401.266]

结论:无明显差异

得到操作

我运行了2个测试,执行以下语句:

  • getList: return list.get(0);
  • getArray: return array[0];

结果(每次通话以纳秒为单位,置信度为95%):

a.p.g.a.ArrayVsList.getArray   [2.958, 2.984]
a.p.g.a.ArrayVsList.getList    [3.841, 3.874]

结论:从数组中获取比从ArrayList中获取大约快25%,尽管差异仅相差一纳秒。

设定操作

我运行了2个测试,执行以下语句:

  • 设置列表: list.set(0, value);
  • setArray: array[0] = value;

结果(每次通话以纳秒为单位):

a.p.g.a.ArrayVsList.setArray   [4.201, 4.236]
a.p.g.a.ArrayVsList.setList    [6.783, 6.877]

结论:数组的设置操作比列表上的速度快约40%,但是,就设置而言,每个设置操作都需要几纳秒的时间-因此,要达到1秒的差异,就需要在列表/数组中设置数百个项目百万次!

克隆/复制

ArrayList的副本构造函数委托给它,Arrays.copyOf因此性能与数组副本相同(通过进行数组复制cloneArrays.copyOf或者System.arrayCopy 在性能方面没有实质性差异)。


1
很好的分析。但是,关于您的注释“在处理基元时,数组可以显着更快,因为它们可以避免很多装箱/拆箱转换”,您可以使用由基元数组支持的List来吃蛋糕。实施;例如:github.com/scijava/scijava-common/blob/master/src/main/java/org/…。实际上,我很惊讶这种事情还没有成为核心Java。
2013年

2
@ctrueden是,此注释应用于标准JDK ArrayList。trove4j是一个众所周知的支持原始列表的库。Java 8通过几个原始的专用Stream带来了一些改进。
assylias 2013年

我不知道jmh基准测试如何工作,但是它们是否考虑了可能发生的JIT编译?随着JVM编译代码,Java应用程序的性能可能会随时间变化。
霍夫曼2014年

@Hoffmann是-它包括预热阶段,该阶段不包括在测量中。
assylias 2014年

97

您应该更喜欢泛型而不是数组。正如其他人所提到的,数组是不灵活的,并且不具有泛型类型的表达能力。(但是,它们确实支持运行时类型检查,但与泛型类型混合使用很糟糕。)

但是,一如既往,在优化时,您应始终遵循以下步骤:

  • 在拥有良好,干净且可运行的代码版本之前,请不要进行优化。在此步骤中,很可能已经激发了改用泛型类型的动机。
  • 当您拥有一个不错且干净的版本时,请确定它是否足够快。
  • 如果速度不够快,请测量其性能。此步骤很重要,原因有两个。如果您不进行衡量,您将不会(1)不知道所做的任何优化的影响,而且(2)不知道在何处进行优化。
  • 优化代码中最热的部分。
  • 再次测量。这与之前进行测量一样重要。如果优化没有改善,请还原它。记住,未经优化的代码是干净,漂亮且可以正常工作的。

24

我猜原来的海报来自C ++ / STL背景,这引起了一些混乱。在C ++中std::list是一个双链表。

Java中[java.util.]List是无实现接口(用C ++术语表示的纯抽象类)。List可以是双向链表- java.util.LinkedList已提供。但是,当您要新建一个时List,您要使用100中的99次来java.util.ArrayList代替,这与C ++大致等效std::vector。还有其他的标准实现,如由返回java.util.Collections.emptyList()java.util.Arrays.asList()

从性能的角度来看,必须通过一个接口和一个额外的对象的影响很小,但是运行时内联意味着这几乎没有任何意义。还要记住,String通常是对象加数组。因此,对于每个条目,您可能还有另外两个对象。在C ++中std::vector<std::string>,尽管按值复制时没有这样的指针,但是字符数组将形成字符串的对象(通常不会共享)。

如果此特定代码确实对性能敏感,则可以为所有字符串的所有字符创建单个char[]数组(甚至byte[]),然后创建偏移量数组。IIRC,这就是javac的实现方式。


1
感谢您的回答。但是,不,我不会将C ++列表与Java的接口List混淆。我之所以问这个问题,是因为我想将诸如ArrayList和Vector的List实现与原始数组的性能进行比较。
euphoria83

ArrayList和Vector都“将所有数据保留在连续的内存块中”。
Tom Hawtin-大头钉

13

我同意,在大多数情况下,您应该选择ArrayLists的灵活性和优雅程度,而不是在阵列中-在大多数情况下,对程序性能的影响可以忽略不计。

但是,如果您正在为软件图形渲染或自定义虚拟机进行恒定,繁重的迭代而几乎没有结构变化(没有添加和删除),我的顺序访问基准测试表明ArrayLists比我的数组慢1.5倍。系统(在一岁的iMac上使用Java 1.6)。

一些代码:

import java.util.*;

public class ArrayVsArrayList {
    static public void main( String[] args ) {

        String[] array = new String[300];
        ArrayList<String> list = new ArrayList<String>(300);

        for (int i=0; i<300; ++i) {
            if (Math.random() > 0.5) {
                array[i] = "abc";
            } else {
                array[i] = "xyz";
            }

            list.add( array[i] );
        }

        int iterations = 100000000;
        long start_ms;
        int sum;

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += array[j].length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (array)" );
        // Prints ~13,500 ms on my system

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += list.get(j).length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (ArrayList)" );
        // Prints ~20,800 ms on my system - about 1.5x slower than direct array access
    }
}

我找到了一个有趣的答案,但是我想知道,如果不使用内存中的初始大小初始化ArrayList,是否还会更糟。通常,从某种意义上说,使用ArrayList而不是本机数组的好处是您不会知道,也不必担心。默认情况下,使用初始长度10创建ArrayList,然后调整其大小。我认为调整大小很昂贵。我没有尝试过对它进行基准测试。
Zak Patterson

4
此微型基准测试有缺陷(没有热身,操作没有单独的方法,因此arraylist部分从来没有通过JIT进行优化)
assylias 2012年

我同意亚述。该基准测试的结果不值得信任。
Stephen C

@StephenC我添加了一个适当的微基准测试(表明get操作是可比较的)。
亚述

11

首先,有必要澄清一下,您是在经典comp sci数据结构意义上是指“列表”(即链接列表)还是java.util.List?如果您的意思是java.util.List,它是一个接口。如果要使用数组,只需使用ArrayList实现,您将获得类似数组的行为和语义。问题解决了。

如果您的意思是数组与链表,这是一个稍有不同的论点,我们可以追溯到Big O(如果这是一个陌生的术语,这里有一个简单的英语解释

数组;

  • 随机访问:O(1);
  • 插入:O(n);
  • 删除:O(n)。

链接列表:

  • 随机访问:O(n);
  • 插入:O(1);
  • 删除:O(1)。

因此,您可以选择最适合您调整数组大小的方法。如果您调整大小,插入和删除很多内容,那么链表也许是一个更好的选择。如果随机访问很少,则同样适用。您提到了串行访问。如果您主要是进行串行访问而几乎没有修改,那么选择哪种可能都没有关系。

链接列表的开销略高,因为,就像您说的那样,您正在处理潜在的不连续内存块和(有效)指向下一个元素的指针。除非您要处理数百万个条目,否则这可能不是重要因素。


我的意思是java.util.List接口
euphoria83

1
对我来说,链表上的随机访问O(n)似乎很重要。
比约恩

11

我写了一个基准测试来比较ArrayLists和Arrays。在我的老式笔记本电脑上,遍历具有5000个元素的数组列表的时间为1000次,比等效的数组代码慢大约10毫秒。

所以,如果你在做什么,但迭代的列表,你做了很多,那么也许它的价值的最优化。否则,我会使用列表中,因为它会更容易,当你这样做需要优化的代码。

确实注意到使用for String s: stringsList它比使用老式的for循环访问列表要慢50%。走吧,这是我计时的两个功能。数组和列表中填充了5000个随机(不同)字符串。

private static void readArray(String[] strings) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < strings.length; i++) {
            totalchars += strings[i].length();

        }
    }
}

private static void readArrayList(List<String> stringsList) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < stringsList.size(); i++) {
            totalchars += stringsList.get(i).length();
        }
    }
}

// @克里斯·梅:很棒!两者的实际运行时间是多少?您能告诉我您使用的字符串的大小吗?另外,由于使用'String s:stringsList'会花费更长的时间,所以这是我通常在Java中使用更高抽象层的主要担心。
euphoria83

此mcirobenchmark的字符串有多长时间并不重要。没有gc,并且char[]不会被触碰(这不是C)。
Tom Hawtin-大头钉

对于我来说,典型的时间是数组版本为25ms,ArrayList版本为35ms。琴弦长15至20个字符。正如汤姆所说,字符串的大小并没有太大的区别,对于一个100字符的字符串,时间大约是相同的。
克里斯,2009年5

3
您如何衡量?Java微型基准测试中的幼稚测量通常会产生比信息更多的错误信息。当心以上声明。
jmg 2011年

6

否,因为从技术上讲,该数组仅存储对字符串的引用。字符串本身分配在不同的位置。对于一千个项目,我想说一个列表会更好,更慢,但它提供了更大的灵活性并且更易于使用,特别是如果您要调整它们的大小。


5
列表还仅存储对字符串的引用。
PeterŠtibraný09年

6

如果您有成千上万,请考虑使用特里。特里树是一种树状结构,可合并存储字符串的公共前缀。

例如,如果字符串是

intern
international
internationalize
internet
internets

特里将存储:

intern
 -> \0
 international
 -> \0
 -> ize\0
 net
 ->\0
 ->s\0

这些字符串需要57个字符(包括空终止符'\ 0')进行存储,再加上容纳它们的String对象的大小。(实际上,我们应该将所有大小四舍五入为16的倍数,但是...)将其大致称为57 + 5 = 62字节。

特里需要29个(包括空终止符'\ 0')进行存储,再加上特里节点的大小,这是对数组的引用和子特里节点的列表。

对于这个例子,结果可能差不多。对于成千上万的内容,只要您具有通用前缀,它的显示量就可能更少。

现在,当在其他代码中使用trie时,您必须转换为String,可能使用StringBuffer作为中介。如果在Trie之外立即将许多字符串作为Strings使用,那是一种损失。

但是,如果您一次只使用少数几个字(例如,在字典中查找内容),则Trie可以节省大量空间。比将它们存储在HashSet中的空间要少得多。

您说您正在“串行”访问它们-如果这意味着按字母顺序顺序访问,则如果您先进行深度优先迭代,则trie显然也会免费为您提供字母顺序。


1
就像图书馆一样,还是我该如何创建?
euphoria83

只有在标记字符串的情况下,特里树才有用,如果有人将运行文本存储为字符串,则树状结构才有用。
MN

5

更新:

正如Mark所指出的,JVM预热(几次测试通过)后没有显着差异。用重新创建的数组检查,甚至检查从新的矩阵行开始的新检查。这很有可能不使用带有索引访问权限的简单数组来支持集合。

最初的1-2通过简单阵列仍然快2-3倍。

原始帖子:

该主题的字词过多,难以检查。没有任何问题数组比任何类容器快几倍。我在这个问题上一直在寻找性能关键部分的替代方案。这是我用来检查实际情况的原型代码:

import java.util.List;
import java.util.Arrays;

public class IterationTest {

    private static final long MAX_ITERATIONS = 1000000000;

    public static void main(String [] args) {

        Integer [] array = {1, 5, 3, 5};
        List<Integer> list = Arrays.asList(array);

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i) {
//            for (int e : array) {
            for (int e : list) {
                test_sum += e;
            }
        }
        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }
}

这是答案:

基于数组(第16行处于活动状态):

Time: 7064

基于列表(第17行处于活动状态):

Time: 20950

还有关于“更快”的评论吗?这是相当了解的。问题是,比List的灵活性快3倍左右对您来说更好。但这是另一个问题。顺便说一句,我也基于手动构建检查了这一点ArrayList。几乎相同的结果。


2
3真实的速度快了十倍,但微不足道。14ms时间不长
0x6C38

1
基准测试不考虑JVM预热。将main()更改为test()并从main重复调用test。通过第3或第4次测试,运行速度提高了很多倍。到那时,我看到该数组比数组快9倍。
Mike

5

由于这里已经有了很多好的答案,因此,我想为您提供一些实用的观点,即插入和迭代性能比较:Java中的原始数组与链接列表。

这是实际的简单性能检查。
因此,结果将取决于机器性能。

用于此目的的源代码如下:

import java.util.Iterator;
import java.util.LinkedList;

public class Array_vs_LinkedList {

    private final static int MAX_SIZE = 40000000;

    public static void main(String[] args) {

        LinkedList lList = new LinkedList(); 

        /* insertion performance check */

        long startTime = System.currentTimeMillis();

        for (int i=0; i<MAX_SIZE; i++) {
            lList.add(i);
        }

        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        System.out.println("[Insert]LinkedList insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");

        int[] arr = new int[MAX_SIZE];

        startTime = System.currentTimeMillis();
        for(int i=0; i<MAX_SIZE; i++){
            arr[i] = i; 
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Insert]Array Insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        /* iteration performance check */

        startTime = System.currentTimeMillis();

        Iterator itr = lList.iterator();

        while(itr.hasNext()) {
            itr.next();
            // System.out.println("Linked list running : " + itr.next());
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]LinkedList iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        startTime = System.currentTimeMillis();

        int t = 0;
        for (int i=0; i < MAX_SIZE; i++) {
            t = arr[i];
            // System.out.println("array running : " + i);
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]Array iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
    }
}

效果结果如下:

在此处输入图片说明


4

list比数组慢。如果需要效率,请使用数组。如果需要灵活性,请使用list。


4

请记住,ArrayList封装了一个数组,因此与使用原始数组相比,差异不大(除了List在Java中更容易使用之外)。

相对于ArrayList而言,唯一有意义的一次是在存储基元(即字节,整数等)并且需要使用基元数组获得特定的空间效率时。


4

在存储字符串对象的情况下,数组与列表的选择不是那么重要(考虑性能)。因为array和list都将存储字符串对象引用,而不是实际对象。

  1. 如果字符串的数量几乎是恒定的,则使用一个数组(或ArrayList)。但是,如果数字变化太大,则最好使用LinkedList。
  2. 如果需要(或将需要)在中间添加或删除元素,那么您当然必须使用LinkedList。

4

我来这里的目的是更好地了解在数组上使用列表对性能的影响。我必须在这里适应我的情况的代码:数组/列表(约1000个整数)主要使用吸气剂,这意味着array [j]与list.get(j)

以7的最佳表现为科学依据(前几项的速度慢2.5倍),我得到以下信息:

array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator

array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)

-因此,使用阵列的速度大约快30%

现在发布的第二个原因是,如果您使用嵌套循环执行数学/矩阵/模拟/优化代码,没有人提及其影响。

假设您有三个嵌套级别,而内部循环的速度是性能命中率的8倍,是您的两倍。现在一天要运行的东西需要一周的时间。

*编辑在这里非常震惊,我尝试声明int [1000]而不是Integer [1000]来踢

array int[] best 299ms iterator
array int[] best 296ms getter

使用Integer []与int []表示性能提高了两倍,带有迭代器的ListArray比int []慢3倍。真的认为Java的列表实现类似于本机数组...

参考代码(多次调用):

    public static void testArray()
    {
        final long MAX_ITERATIONS = 1000000;
        final int MAX_LENGTH = 1000;

        Random r = new Random();

        //Integer[] array = new Integer[MAX_LENGTH];
        int[] array = new int[MAX_LENGTH];

        List<Integer> list = new ArrayList<Integer>()
        {{
            for (int i = 0; i < MAX_LENGTH; ++i)
            {
                int val = r.nextInt();
                add(val);
                array[i] = val;
            }
        }};

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i)
        {
//          for (int e : array)
//          for (int e : list)          
            for (int j = 0; j < MAX_LENGTH; ++j)
            {
                int e = array[j];
//              int e = list.get(j);
                test_sum += e;
            }
        }

        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }

3

如果您事先知道数据有多大,那么数组将更快。

列表更灵活。您可以使用由数组支持的ArrayList。


ArrayList具有sureCapacity()方法,该方法将后备数组预分配为指定的大小。
JesperE

或者,您可以在施工时指定尺寸。同样,“更快”在这里表示“分配几个内存区域而不是分配一个内存区域只需几微秒”
Aaron Digulla 09年

3

如果您可以使用固定大小,则阵列将更快并且需要更少的内存。

如果您需要通过添加和删除元素来灵活使用List接口,则问题仍然是应该选择哪种实现。通常建议使用ArrayList并将其用于任何情况,但是如果必须删除或插入列表开头或中间的元素,则ArrayList也会出现性能问题。

因此,您可能需要查看介绍GapList的http://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list。这种新的列表实现结合了ArrayList和LinkedList的优势,从而为几乎所有操作提供了非常好的性能。


2

取决于实现。原始类型的数组可能会比ArrayList更小,更有效。这是因为数组会将值直接存储在连续的内存块中,而最简单的ArrayList实现将存储指向每个值的指针。特别是在64位平台上,这可以带来很大的不同。

当然,对于这种情况,jvm实现可能会有特殊情况,在这种情况下,性能将是相同的。


2

List是Java 1.5及更高版本中的首选方式,因为它可以使用泛型。数组不能具有泛型。另外,数组具有预定义的长度,不能动态增长。初始化大型数组不是一个好主意。ArrayList是使用泛型声明数组的方法,它可以动态增长。但是,如果更频繁地使用删除和插入,则链表是要使用的最快的数据结构。


2

建议在任何地方使用数组而不是列表,尤其是在您知道项目数和大小不会改变的情况下。

请参阅Oracle Java最佳实践:http : //docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

当然,如果您需要多次从集合中添加和删除对象,则使用方便。


您链接到的文档已有10多年的历史,即适用于Java 1.3。从那时起,我们就进行了重大的性能改进……
assylias 2014年

@assylias在上面看到了答案,其中包含性能测试,表示阵列速度更快
Nik

3
我知道我写了其中之一。但是我不认为“ 建议在任何可以使用它们而不是列表的地方都推荐使用数组 ”是一个不错的建议。在大多数情况下,除非您正在处理原语并且代码对性能敏感,否则ArrayList应该是默认选择。
assylias 2014年

2

没有一个答案有我感兴趣的信息-多次重复扫描同一阵列。不得不为此创建一个JMH测试。

结果(Java 1.8.0_66 x32,迭代普通数组至少比ArrayList快5倍):

Benchmark                    Mode  Cnt   Score   Error  Units
MyBenchmark.testArrayForGet  avgt   10   8.121 ? 0.233  ms/op
MyBenchmark.testListForGet   avgt   10  37.416 ? 0.094  ms/op
MyBenchmark.testListForEach  avgt   10  75.674 ? 1.897  ms/op

测试

package my.jmh.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyBenchmark {

    public final static int ARR_SIZE = 100;
    public final static int ITER_COUNT = 100000;

    String arr[] = new String[ARR_SIZE];
    List<String> list = new ArrayList<>(ARR_SIZE);

    public MyBenchmark() {
        for( int i = 0; i < ARR_SIZE; i++ ) {
            list.add(null);
        }
    }

    @Benchmark
    public void testListForEach() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( String str : list ) {
                if( str != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testListForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( list.get(j) != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testArrayForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( arr[j] != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

}

2

“千”不是一个大数目。几千个段落长度的字符串的大小约为几兆字节。如果您只想串行访问这些文件,请使用不可变的单链接List


大多数64位实现中为8个字节。
Tom Hawtin-大头钉

有没有证据表明这件事比java.util.LinkedList快?哪个也是“内存中”?也可以将其设置为不可变的,好像有什么不同。
洛恩侯爵

1

没有适当的基准测试,不要陷入优化的陷阱。正如其他人所建议的,请在进行任何假设之前使用分析器。

您枚举的不同数据结构具有不同的用途。列表在开头和结尾插入元素非常有效,但是在访问随机元素时会遇到很多麻烦。阵列具有固定的存储,但提供快速的随机访问。最后,ArrayList通过允许其增长来改善与该接口的接口。通常,要使用的数据结构应由如何访问或添加存储的数据来决定。

关于内存消耗。您似乎在混入一些东西。数组只会为您拥有的数据类型提供连续的内存块。不要忘记Java具有固定的数据类型:布尔值,char,int,long,float和Object(这包括所有对象,甚至数组都是Object)。这意味着,如果声明字符串字符串[1000]或MyObject myObjects [1000]的数组,则只会得到一个足够大的1000个存储盒来存储对象的位置(引用或指针)。您没有足够大的1000个存储盒来容纳对象的大小。不要忘记,您的对象首先是用“ new”创建的。这是在完成内存分配并随后将引用(其内存地址)存储在数组中时。仅将对象作为参考,该对象不会复制到数组中。


1

我认为这对于Strings并没有真正的区别。字符串数组中连续的是对字符串的引用,字符串本身存储在内存中的随机位置。

数组与列表可以对基本类型(而不是对象)产生影响。如果您事先知道元素的数量,并且不需要灵活性,那么与列表相比,数百万个整数或双精度数组在内存和速度上的效率将更高,因为实际上它们将连续存储并可以立即访问。这就是Java仍将char数组用于字符串,int数组用于图像数据等的原因。



1

此处给出的许多微基准已发现诸如array / ArrayList读取之类的东西只有几纳秒的数量。如果所有内容都在您的L1缓存中,这是非常合理的。

较高级别的高速缓存或主存储器访问的量级时间大约为10nS-100nS,而对于L1高速缓存则为1nS。访问ArrayList有一个额外的内存间接方式,在实际的应用程序中,您几乎可以从不支付任何费用到每次都支付此费用,具体取决于两次访问之间代码的作用。而且,当然,如果您有很多小的ArrayList,则这可能会增加您的内存使用量,并更有可能导致缓存未命中。

原始海报似乎只使用其中一个,并且可以在很短的时间内访问很多内容,因此应该不会有太大的困难。但这对于其他人可能有所不同,并且在解释微基准测试时应格外小心。

但是,Java字符串非常浪费,特别是如果您存储许多小字符串(仅使用内存分析器查看它们,对于几个字符的字符串来说,似乎大于60字节)。字符串数组具有对String对象的间接寻址,以及从String对象到包含字符串本身的char []的间接寻址。如果有什么要破坏您的L1缓存的,那就是它,结合成千上万的字符串。因此,如果您真的很认真-真的很认真-希望尽可能地提高性能,那么您可以考虑采用不同的方法。您可以说,包含两个数组,一个包含所有字符串的char [],一个接一个,另一个int [],其起始位置偏移。这将是PITA可以执行的任何操作,并且您几乎肯定不需要它。如果这样做的话,


0

这取决于您如何访问它。

存储后,如果您主要想执行搜索操作,而几乎没有插入/删除操作,则选择Array(因为搜索是在数组的O(1)中完成的,而add / delete可能需要对元素进行重新排序) 。

存储后,如果您的主要目的是添加/删除字符串,而很少执行搜索操作或不执行搜索操作,则选择List。


0

ArrayList在内部使用数组对象添加(或存储)元素。换句话说,ArrayList由Array数据结构支持.ArrayList的数组是可调整大小的(或动态的)。

数组比数组快因为ArrayList在内部使用数组。如果我们可以直接在Array中添加元素并通过ArrayList间接在Array中添加元素,那么直接机制总是比间接机制快。

ArrayList类中有两个重载的add()方法:
1 . add(Object) :将对象添加到列表的末尾。
2 add(int index , Object ) .:将指定的对象插入列表中的指定位置。

ArrayList的大小如何动态增长?

public boolean add(E e)        
{       
     ensureCapacity(size+1);
     elementData[size++] = e;         
     return true;
}

上面代码中需要注意的重要一点是,在添加元素之前,我们正在检查ArrayList的容量。guaranteeCapacity()确定当前已占用元素的大小以及数组的最大大小。如果填充元素的大小(包括要添加到ArrayList类的新元素)大于数组的最大大小,则增加数组的大小。但是数组的大小不能动态增加。所以内部发生的事情是创建具有容量的新阵列

直到Java 6

int newCapacity = (oldCapacity * 3)/2 + 1;

(更新)从Java 7

 int newCapacity = oldCapacity + (oldCapacity >> 1);

同样,旧阵列中的数据也将复制到新阵列中。

ArrayList中有开销方法,这就是Array比快的原因ArrayList


0

数组-当我们必须更快地获取结果时,总会更好

列表-执行插入和删除的结果,因为它们可以在O(1)中完成,并且还提供了轻松添加,获取和删除数据的方法。更容易使用。

但是请始终记住,当存储数据的数组中的索引位置已知时,数据的获取会很快。

通过对数组排序可以很好地实现。因此,这增加了获取数据的时间(即:存储数据+对数据进行排序+寻找找到数据的位置)。因此,这增加了从阵列中获取数据的额外等待时间,即使它们可能擅长于更快地获取数据。

因此,这可以通过特里数据结构或三元数据结构解决。如上所述,特里数据结构在搜索数据中将非常有效,可以以O(1)的幅度进行特定单词的搜索。当时间很重要时 如果必须快速搜索和检索数据,则可以使用trie数据结构。

如果您希望减少内存空间的消耗,并且希望获得更好的性能,则可以使用三元数据结构。两者都适合存储大量的字符串(例如,字典中包含的单词)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.