首页 文章

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

提问于
浏览 1745 次
317

我必须在内存中保留数千个字符串,以便在Java中以串行方式访问 . 我应该将它们存储在数组中还是应该使用某种List?

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

Answer: 共识是,绩效差异很小 . List接口提供更大的灵活性 .

30 回答

  • 82

    如果事先知道数据有多大,那么数组会更快 .

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

  • 11

    如果您有数千人,请考虑使用trie . trie是一种树状结构,它合并了存储字符串的公共前缀 .

    例如,如果字符串是

    intern
    international
    internationalize
    internet
    internets
    

    特里会存储:

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

    字符串需要57个字符(包括空终止符'\ 0')来存储,加上包含它们的String对象的大小 . (实际上,我们可能应该将所有大小四舍五入到16的倍数,但是......)称之为57 5 = 62字节,粗略 .

    trie需要29(包括空终止符,'\ 0')用于存储,加上trie节点的大小,它是对数组的引用和子trie节点的列表 .

    对于这个例子,这可能是相同的;对于成千上万的人来说,只要你有共同的前缀,它就可能会少出来 .

    现在,在其他代码中使用trie时,您必须转换为String,可能使用StringBuffer作为中介 . 如果许多字符串同时作为字符串使用,在特里,这是一个损失 .

    但是,如果你当时只使用一些 - 比如说,在字典中查找东西 - 特里可以为你节省很多空间 . 绝对比将它们存储在HashSet中的空间要小 .

    你说你正在“连续”地访问它们 - 如果这意味着按字母顺序依次访问它们,那么如果你以深度优先的方式迭代它,那么它也显然可以免费提供字母顺序 .

  • 2

    取决于实施 . 原始类型数组可能比ArrayList更小,更有效 . 这是因为数组将直接将值存储在连续的内存块中,而最简单的ArrayList实现将存储指向每个值的指针 . 特别是在64位平台上,这可以产生巨大的差异 .

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

  • 2

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

    您可以使用数组执行以下操作:

    • 创造它

    • 设置了一个项目

    • 得到一个项目

    • 克隆/复制它

    总结论

    Although get and set operations are somewhat slower on an ArrayList (在我的机器上每次呼叫分别为1和3纳秒), there is very little overhead of using an ArrayList vs. an array for any non-intensive use. 但是有一些事情要记住:

    • 列表上的调整操作(调用 list.add(...) 时)成本很高,并且应尽可能尝试将初始容量设置在适当的水平(请注意,使用数组时会出现同样的问题)

    • 处理基元时,数组可以明显更快,因为它们可以避免许多装箱/拆箱转换

    • 只在ArrayList中获取/设置值的应用程序(不常见!)通过切换到数组可以看到性能增益超过25%

    详细结果

    以下是我在标准x86台式机上使用JDK 7使用jmh benchmarking library(以纳秒为单位)的三个操作测量的结果 . 请注意,ArrayList永远不会在测试中调整大小以确保结果具有可比性 . Benchmark code available here .

    Array / 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]
    

    Conclusion: no noticeable difference .

    获取操作

    我运行了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]
    

    Conclusion: getting from an array is about 25% faster 比从ArrayList获取,虽然差异只有一纳秒的量级 .

    设置操作

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

    • setList: 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]
    

    Conclusion: set operations on arrays are about 40% faster 而不是列表,但是,至于get,每个设置操作需要几纳秒 - 因此差异达到1秒,需要在列表/数组中设置数亿次!

    克隆/复制

    ArrayList的复制构造函数委托给 Arrays.copyOf ,因此性能与数组副本相同(通过 cloneArrays.copyOfSystem.arrayCopy makes no material difference performance-wise复制数组) .

  • 1

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

    但是,如果你在软件图形渲染或自定义虚拟机上做很少的结构更改(没有添加和删除)的重复迭代,我的顺序访问基准测试显示我的系统上的 ArrayLists are 1.5x slower than arrays (我的Java 1.6)一岁的iMac) .

    一些代码:

    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
        }
    }
    
  • 334

    你可以使用固定大小,阵列将更快,需要更少的内存 .

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

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

  • 3

    我写了一个小基准来比较ArrayLists和Arrays . 在我的旧式笔记本电脑上,遍历5000个元素的arraylist 1000次的时间比等效的数组代码慢大约10毫秒 .

    所以,如果你做了很多事情,那么也许它会使用List,因为当你需要优化代码时它会更容易 .

    注:我注意到使用 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();
            }
        }
    }
    
  • 5

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

    Results (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
    

    Test

    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);
        }
    
    }
    
  • 91

    数组更快 - 所有内存都预先分配 .

  • 0

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

    Array is faster than Array 因为ArrayList内部使用数组 . 如果我们可以直接在Array中添加元素并通过ArrayList间接地在Array中添加元素,那么直接机制就比间接机制快 .

    ArrayList类中有两个重载的add()方法:

    1. add(Object) :将对象添加到列表的末尾 .
    2. add(int index , Object ) :将指定的对象插入列表中的指定位置 .

    How the size of ArrayList grows dynamically?

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

    从上面的代码中要注意的重点是我们在添加元素之前检查ArrayList的容量 . ensureCapacity()确定占用元素的当前大小是什么,以及数组的最大大小是多少 . 如果填充元素的大小(包括要添加到ArrayList类的新元素)大于数组的最大大小,则增加数组的大小 . 但是数组的大小不能动态增加 . 那么内部发生的是新的数据是用容量创建的

    直到Java 6

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

    (更新)来自Java 7

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

    此外,旧数组中的数据也会复制到新数组中 .

    在ArrayList中使用开销方法,这就是Array比 ArrayList 更快的原因 .

  • 1

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

  • 1

    由于这里已经有很多好的答案,我想给你一些实用观点的其他信息,这是 insertion and iteration performance comparison : primitive array vs Linked-list in 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.");
        }
    }
    

    表现结果如下:

    enter image description here

  • 2

    我来到这里是为了更好地了解使用列表对阵列的性能影响 . 我必须在这里为我的场景调整代码:使用大多数getter的~1000 int的数组/列表,意思是array [j] vs 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]而不是整数[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);
        }
    
  • 5

    这里给出的许多微基准测试已经发现像array / ArrayList读取这样的数字有几纳秒 . 如果所有内容都在您的L1缓存中,这是非常合理的 .

    更高级别的高速缓存或主存储器访问可以具有10nS-100nS之类的数量级,而更像是L1高速缓存的1nS . 访问ArrayList有一个额外的内存间接,在实际应用程序中,您可以从几乎从不到每次都支付这笔费用,具体取决于您的代码在访问之间的操作 . 当然,如果你有很多小的ArrayLists,这可能会增加你的内存使用量,并使你更有可能遇到缓存未命中 .

    原始海报似乎只使用一张,并在短时间内访问了很多内容,所以应该没有太大的困难 . 但对于其他人来说可能会有所不同,在解释微基准时你应该注意 .

    然而,Java Strings是非常浪费的,特别是如果你存储了很多小的(只需用内存分析器查看它们,对于一些字符串来说似乎> 60字节) . 字符串数组具有String对象的间接,另一个字符串从String对象到包含字符串本身的char [] . 如果有什么东西会打破你的L1缓存,那就是成千上万或几万个字符串 . 所以,如果你是认真的 - 非常认真 - 尽可能多地挖掘性能,那么你可以用不同的方式来做 . 比方说,你可以拥有两个数组,一个包含所有字符串的char [],一个接一个,以及一个带有偏移量的int [] . 这将是一个PITA做任何事情,你几乎肯定不需要它 . 如果你这样做,你选择了错误的语言 .

  • 2

    首先,值得澄清的是你的意思是“经典的comp sci数据结构意义上的”列表(即链表)或者你的意思是java.util.List?如果你的意思是java.util.List,那就是一个接口 . 如果你想使用数组,只需使用ArrayList实现,你将获得类似数组的行为和语义 . 问题解决了 .

    如果你的意思是一个数组与一个链表,那就是我们回到Big O的一个稍微不同的参数(如果这是一个不熟悉的术语,这里是plain English explanation .

    阵列;

    • 随机访问:O(1);

    • 插入:O(n);

    • 删除:O(n) .

    链接列表:

    • 随机访问:O(n);

    • 插入:O(1);

    • 删除:O(1) .

    因此,您可以选择最适合您调整阵列大小的那个 . 如果您调整大小,插入和删除很多,那么链接列表可能是更好的选择 . 如果随机访问很少,则同样如此 . 你提到串行访问 . 如果你主要是通过很少的修改来进行串行访问,那么你选择哪个并不重要 .

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

  • 4

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

    • 如果字符串数几乎不变,则使用数组(或ArrayList) . 但如果数字变化太大,那么你最好使用LinkedList .

    • 如果有(或将来)需要在中间添加或删除元素,那么你当然必须使用LinkedList .

  • 3

    list比数组慢 . 如果你需要效率使用数组 . 如果你需要灵活性使用列表 .

  • 152

    建议在任何地方使用阵列而不是列表,特别是如果您知道项目数量和大小不会改变 .

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

    当然,如果您需要多次从集合中添加和删除对象,请使用列表 .

  • 2

    更新:

    正如Mark所说,在JVM预热(几次测试通过)之后没有显着差异 . 检查重新创建的数组甚至是新的矩阵行开始的新传递 . 很有可能这标志着具有索引访问权限的简单数组不能用于集合 .

    仍然是第一次1-2次传递简单数组快2-3倍 .

    原始邮寄:

    对于主题太多的单词太简单无法检查 . Without any question array is several times faster than any class container . 我运行这个问题寻找我的性能关键部分的替代品 . 这是我为检查实际情况而构建的原型代码:

    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
    

    还有关于'faster'的评论?这是很清楚的 . 问题是,与List的灵活性相比,大约3倍的速度对你来说更好 . 但这是另一个问题 . 顺便说一句,我也根据手工构造的 ArrayList 进行了检查 . 几乎相同的结果 .

  • 22

    这取决于你如何访问它 .

    存储之后,如果你主要想做搜索操作,很少或没有插入/删除,那么转到数组(因为搜索在数组中的O(1)中完成,而添加/删除可能需要重新排序元素) .

    存储后,如果您的主要目的是添加/删除字符串,几乎没有搜索操作,则转到列表 .

  • 2

    Java的方式是你应该考虑哪种数据抽象最适合你的需求 . 请记住,在Java中,List是一个抽象,而不是具体的数据类型 . 您应该将字符串声明为List,然后使用ArrayList实现初始化它 .

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

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

    ArrayList使用数组作为其底层实现来实现List Abstract Data Type . 访问速度几乎与数组相同,还有一个额外的优点:能够向List添加和减去元素(虽然这是一个带有ArrayList的O(n)操作),如果您决定稍后更改底层实现您可以 . 例如,如果您意识到需要同步访问,则可以将实现更改为Vector,而无需重写所有代码 .

    实际上,ArrayList是专门为在大多数情况下替换低级数组构造而设计的 . 如果今天设计Java,完全有可能完全省略数组以支持ArrayList结构 .

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

    在Java中,所有集合仅存储对象的引用,而不存储对象本身 . 数组和ArrayList都会在连续数组中存储几千个引用,因此它们基本相同 . 您可以考虑在现代硬件上始终可以使用几千个32位引用的连续块 . 这并不能保证你不会完全耗尽内存,当然,只是连续的内存需求块并不难实现 .

  • 1

    我建议您使用分析器来测试哪个更快 .

    我个人认为你应该使用列表 .

    我在一个大型代码库上工作,而前一组开发人员使用的是数组 everywhere . 它使代码非常不灵活 . 在将大块的大块改为Lists后,我们注意到速度没有差异 .

  • 3

    没有适当的基准测试,不要陷入优化的陷阱 . 正如其他人建议在做出任何假设之前使用剖析器 .

    您枚举的不同数据结构具有不同的用途 . 列表在开头和结尾处插入元素非常有效,但在访问随机元素时会遇到很多问题 . 阵列具有固定存储但提供快速随机访问 . 最后,ArrayList通过允许它增长来改进数组的接口 . 通常,要使用的数据结构应由存储或添加的数据的方式决定 .

    关于内存消耗 . 你似乎在混合一些东西 . 数组只会为您拥有的数据类型提供连续的内存块 . 不要忘记java有一个固定的数据类型:boolean,char,int,long,float和Object(这包括所有对象,甚至数组都是Object) . 这意味着如果声明一个String字符串[1000]或MyObject myObjects [1000]的数组,则只能获得足够大的1000个内存框来存储对象的位置(引用或指针) . 你没有足够大的1000个内存盒来适应对象的大小 . 不要忘记您的对象首先使用“new”创建 . 这是在完成内存分配并稍后将引用(它们的内存地址)存储在数组中的时候 . 只有它的引用才会将对象复制到数组中 .

  • 12

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

    几乎只有在将数组放到ArrayList时才有意义的是当你存储基元时,即byte,int等,你需要使用原始数组获得特定的空间效率 .

  • 0

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

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

    • 在您拥有一个漂亮,干净且 working 版本的代码之前不要进行优化 . 在这一步骤中,很可能会改变通用类型 .

    • 如果您的版本很干净,请确定它是否足够快 .

    • 如果不够快, measure its performance . 这一步很重要,原因有两个 . 如果你不't measure you won't(1)知道您所做的任何优化的影响,以及(2)知道优化的位置 .

    • 优化代码中最热门的部分 .

    • Measure again. 这和以前测量一样重要 . 如果优化没有改善的话, revert it . 请记住,没有优化的代码是 clean, nice, and working.

  • 6

    List是java 1.5及更高版本中的首选方法,因为它可以使用泛型 . 数组不能有泛型 . 此外,数组具有预定义的长度,不能动态增长 . 初始化大尺寸的数组不是一个好主意 . ArrayList是使用泛型声明数组的方法,它可以动态增长 . 但是如果更频繁地使用delete和insert,那么链表是要使用的最快的数据结构 .

  • 4

    "Thousands"不是一个很大的数字 . 几千个段落长度的字符串大小为几兆字节 . 如果您只想连续访问这些,请使用an immutable singly-linked List .

  • 5

    我认为它不会对Strings产生真正的影响 . 字符串数组中的连续内容是对字符串的引用,字符串本身存储在内存中的随机位置 .

    数组与列表可以对基本类型产生影响,而不是对象 . 如果您事先知道元素的数量,那么为什么Java仍然使用字符串数组作为字符串,图像数据的整数数组等等 .

  • 1

    ArrayList 将其项存储在 Object[] 数组中,并使用无类型的 toArray 方法,该方法比键入的方法快得多(蓝色条) . 这是类型安全的,因为无类型数组包含在编译器检查的泛型类型 ArrayList<T> 中 .

    enter image description here

    此图表显示了Java 7上n = 5的基准测试 . 但是,对于更多项目或其他VM,图片不会发生太大变化 . CPU开销可能看起来不是很大,但它会增加 . 有可能数组的使用者必须将其转换为集合以便对其执行任何操作,然后将结果转换回数组以将其提供给另一个接口方法等 . 使用简单的 ArrayList 而不是数组可以提高性能,没有增加太多的足迹 . ArrayList 向包装数组添加32字节的常量开销 . 例如,带有十个对象的 array 需要104个字节, ArrayList 136个字节 .

    此操作在恒定时间内执行,因此它比上述任何一个(黄色条)快得多 . 这与防御性副本不同 . 当您的内部数据发生变化时,不可修改的集合将发生变化 . 如果发生这种情况,客户端可以在迭代项目时遇到 ConcurrentModificationException . 接口提供在运行时抛出 UnsupportedOperationException 的方法可以被认为是错误的设计 . 但是,至少在内部使用时,此方法可以是防御性副本的高性能替代方案 - 这是阵列无法实现的 .

  • 11

    我猜测原始海报来自C / STL背景,这引起了一些混乱 . 在C std::list 是一个双向链表 .

    在Java中, [java.util.]List 是一个无实现的接口(C语言中的纯抽象类) . List 可以是双向链表 - 提供 java.util.LinkedList . 但是,当你想要一个新的 List 时,你想要使用 java.util.ArrayList ,这就是C std::vector 的粗略等价物 . 还有其他标准实现,例如 java.util.Collections.emptyList()java.util.Arrays.asList() 返回的实现 .

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

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

相关问题