ArrayListのは、ピットを強化しました
一覧<文字列> TEMP =新しいArrayListを(); //获取一批数据 一覧<文字列>すべて=のgetData(); (文字列str:すべて)のために{ temp.add(STR)。 }
まず、問題が何であるか、このコードの事を見て?
実際には、ほとんどの場合、この問題は何も、サイクルへの以外の何物でもない ArrayList
だけ書き込みデータ。
しかし、そのような場合のような例外的な状況での getData()
リターンは巨大なフォローアップのデータが temp.add(str)
問題になります。
例えば、我々は review
2000Wまでのコードは、その後、とき時々 、このデータが返されることがわかった ArrayList
疑問が出てハイライトの上に書かれました。
満たされたピットガイド
適切なタイミングでのアレイの拡大の必要性;我々は、すべてのArrayListには、配列、およびデータの限られた長さによって実装されていることを知っています。
ここでの例では、尾の追加(E電子)に挿入されます。
ArrayList <ストリング> TEMP =新規のArrayList <>(2)。 temp.add( "1")。 temp.add( "2")。 temp.add( "3")。
我々は2の長さを初期化すると ArrayList
、3つのデータを書き込み、王Libianは ArrayList
長さ3の配列に新しい配列する前にデータをコピーする、つまり、拡張する必要があります。
理由は、元の長さの新しい長さ= 1.5 *、3です
通过源码我们可以得知 ArrayList
的默认长度为 10.
但其实并不是在初始化的时候就创建了 DEFAULT_CAPACITY = 10
的数组。
而是在往里边 add
第一个数据的时候会扩容到 10.
既然知道了默认的长度为 10 ,那说明后续一旦写入到第九个元素的时候就会扩容为 10*1.5 =15
。 这一步为数组复制,也就是要重新开辟一块新的内存空间存放这 15 个数组。
一旦我们频繁且数量巨大的进行写入时就会导致许多的数组复制,这个效率是极低的。
但如果我们提前预知了可能会写入多少条数据时就可以提前避免这个问题。
比如我们往里边写入 1000W 条数据,在初始化的时候就给定数组长度与用默认 10 的长度之间性能是差距巨大的。
我用 JMH 基准测试验证如下:
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) public class CollectionsTest { private static final int TEN_MILLION = 10000000; @Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) public void arrayList() { List<String> array = new ArrayList<>(); for (int i = 0; i < TEN_MILLION; i++) { array.add("123"); } } @Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) public void arrayListSize() { List<String> array = new ArrayList<>(TEN_MILLION); for (int i = 0; i < TEN_MILLION; i++) { array.add("123"); } } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(CollectionsTest.class.getSimpleName()) .forks(1) .build(); new Runner(opt).run(); } }
根据结果可以看出预设长度的效率会比用默认的效率高上很多(这里的 Score
指执行完函数所消耗的时间)。
所以这里强烈建议大家:在有大量数据写入 ArrayList
时,一定要初始化指定长度。
再一个是一定要慎用 add(int index, E element)
向指定位置写入数据。
通过源码我们可以看出,每一次写入都会将 index 后的数据往后移动一遍,其实本质也是要复制数组;
但区别于往常规的往数组尾部写入数据,它每次都会进行数组复制,效率极低。
LinkedList
提到 ArrayList
就不得不聊下 LinkedList
这个孪生兄弟;虽说都是 List
的容器,但本质实现却完全不同。
LinkedList
是由链表组成,每个节点又有头尾两个节点分别引用了前后两个节点;因此它也是一个双向链表。
所以理论上来说它的写入非常高效,将不会有 ArrayList 中效率极低的数组复制,每次只需要移动指针即可。
这里偷懒就不画图了,大家自行脑补下。
对比测试
坊间一直流传:
LinkedList 的写入效率高于 ArrayList,所以在写大于读的时候非常适用于 LinkedList 。
@Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) public void linkedList() { List<String> array = new LinkedList<>(); for (int i = 0; i < TEN_MILLION; i++) { array.add("123"); } }
这里测试看下结论是否符合;同样的也是对 LinkedList
写入 1000W
次数据,通过结果来看初始化数组长度的 ArrayList
效率明显是要高于 LinkedList
。
しかし、ここでの前提は、プリセット進めることである ArrayList
ので、アレイは、拡大を避けるために、配列の長さを ArrayList
書き込み効率が非常に高く、かつ LinkedList
メモリをコピーする必要がありますが、ポインタや他の操作を、オブジェクトを作成変換する必要はありませんが。
言うべきクエリ言うまでもないが、ArrayList
ランダム・アクセス・インデックスをサポートすることができ、効率が非常に高いです。
LinkedList
元の配列がないように、アクセスは、標準でサポートされているが、インデックス問合せ位置から先頭またはトラバースの端部から決定する必要がされていません。
しかし、関係なく、特に、一緒に行くためにポインタを移動する必要性を持っていたどのような種類の index
とき中央の位置に近い非常に遅くなることはありません。
概要
高性能アプリケーションは、ここで言及したような小さな詳細は、少し積もっている ArrayList
すべての小さな問題が大きな問題になり、データボリュームアップいったんピット、普段使いには何も大きな問題として。
それでは以下に要約:
ArrayListには、事前にデータサイズの量を予測することができる場合を再利用するとき、大きなは場合長さが指定されなければなりません。
可能な限りの使用を避ける
add(index,e)
APIを、それが効率が低下、配列をコピーするためにつながります。その後、我々は別の使用余分な言及
Map
コンテナはHashMap
拡大を避けるために、長さを初期化することをお勧めします。
本論文では、すべてのテストコード:
親指を共有し、私にとって最大のサポートです!!