Kısa bir süre önce, meslektaşlarımın yavaş bir iş yürütme sorununu gözden geçirmesine yardım ettiğimde, birçok arkadaşımın işlevi uygulamak için koda bastığında ayrıntılara hala dikkat etmesi gerektiğini fark ettim, bu yüzden bu makaleyi buldum.
Her şeyden önce, bu kodda yanlış olan ne?
Aslında, çoğu durumda bu sorun değildir, verileri ArrayList'e döngüsel olarak yazmaktan başka bir şey değildir.
Ancak özel durumlarda, örneğin getData () tarafından döndürülen veriler çok büyük olduğunda, sonraki temp.add (str) sorunları yaşayacaktır.
Örneğin, kodu gözden geçirirken, burada döndürülen verilerin bazen 2000W'a kadar ulaştığını gördük.Bu sırada, ArrayList yazma sorunu vurgulanmıştır.
Herkes ArrayList'in bir dizi tarafından uygulandığını bilir ve verilerin uzunluğu sınırlıdır; dizinin doğru zamanda genişletilmesi gerekir.
İşte sonuna ekleme örneği (E e).
ArrayListtemp = newArrayList < > (2); temp.add ("1"); temp.add ("2"); temp.add ("3");2 uzunluğunda bir ArrayList'i başlattığımızda ve içine üç parça veri yazdığımızda, ArrayList'in genişletilmesi gerekir, yani önceki verileri 3 uzunluğundaki yeni diziye kopyalar.
Nedeni 3, çünkü yeni uzunluk = orijinal uzunluk * 1.5
Kaynak koddan ArrayList'in varsayılan uzunluğunun 10 olduğunu bilebiliriz.
Fakat gerçekte, DEFAULT_CAPACITY = 10 dizisi başlatma sırasında yaratılmaz.
Ancak içine ilk veriyi eklediğinizde, 10'a genişleyecektir.
Artık varsayılan uzunluğun 10 olduğunu bildiğinize göre, bu, dokuzuncu öğe daha sonra yazıldığında 10 * 1.5 = 15'e genişleyeceği anlamına gelir. Bu adım, dizi kopyalama, yani bu 15 diziyi depolamak için yeni bir bellek alanı oluşturmak içindir.
Sık sık ve büyük miktarda yazdığımızda, son derece verimsiz olan birçok dizi kopyasına neden olur.
Ancak önceden kaç adet veri yazılabileceğini tahmin edersek, bu problem önceden önlenebilir.
Örneğin, içine 1000W veri yazdığımızda, başlatma sırasında verilen dizi uzunluğu ile varsayılan 10 olan uzunluk arasında performansta büyük bir boşluk var.
Aşağıdakileri doğrulamak için JMH kıyaslama testini kullanıyorum:
@Warmup (iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement (iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) public class CollectionsTest { özel statik final int TEN_MILLION = 10000000; @Benchmark @BenchmarkMode (Mode.AverageTime) @OutputTimeUnit (TimeUnit.MICROSECONDS) public void arrayList () { Liste < Dize > dizi = new ArrayList < > (); for (int i = 0; i < TEN_MILLION; i ++) {array.add ("123");} } @Benchmark @BenchmarkMode (Mode.AverageTime) @OutputTimeUnit (TimeUnit.MICROSECONDS) public void arrayListSize () {List < Dize > dizi = new ArrayList < > (ON MİLYON); for (int i = 0; i < TEN_MILLION; i ++) {array.add ("123");} } public static void main (String argümanları) RunnerException {Options opt = new OptionsBuilder () .include (CollectionsTest.class.getSimpleName ()) .forks (1) .build (); new Runner (opt) .run ();}}Sonuçlara göre, önceden ayarlanmış uzunluğun verimliliğinin varsayılanı kullanmanın verimliliğinden çok daha yüksek olduğu görülebilir (buradaki Puan, işlevi gerçekleştirmek için geçen süreyi ifade eder).
Bu yüzden herkese şiddetle tavsiye ediyorum: ArrayList'e büyük miktarda veri yazıldığında, belirtilen uzunluk başlatılmalıdır.
Bir diğeri ise, belirtilen konuma veri yazmak için add (intindex, E element) kullanmanız gerektiğidir.
Kaynak koddan, her yazma işleminin veriyi indeksten sonra tekrar geri taşıyacağını görebiliriz, aslında esas olan diziyi kopyalamaktır;
Ancak dizinin sonuna veri yazmaktan farklı olarak, diziyi her seferinde kopyalayacaktır, bu son derece verimsizdir.
ArrayList söz konusu olduğunda, LinkedList'in ikiz kardeşi hakkında konuşmalısınız; hepsi List kapsayıcıları olmasına rağmen, temel uygulama tamamen farklıdır.
LinkedList, bağlantılı listelerden oluşur ve her düğümün başında ve sonunda iki düğüme referans veren iki düğüm vardır; bu nedenle, aynı zamanda çift bağlantılı bir listedir.
Yani teoride, yazımı çok verimli, ArrayList'te çok verimsiz dizi kopyası olmayacak, sadece işaretçiyi her seferinde hareket ettirmek yeterli.
Burada tembel olmayacağım ve herhangi bir resim çizmeyeceğim.
Dolaşıyor:
LinkedList'in yazma verimliliği ArrayList'inkinden daha yüksektir, bu nedenle yazma okumadan daha büyük olduğunda LinkedList için çok uygundur.
@Benchmark @BenchmarkMode (Mode.AverageTime) @OutputTimeUnit (TimeUnit.MICROSECONDS) public void linkedList () {List < Dize > dizi = yeni LinkedList < > (); for (int i = 0; i < TEN_MILLION; i ++) {array.add ("123");} }Sonucun tutarlı olup olmadığını görmek için buradaki test; aynı şey LinkedList'e 1000W veri yazmaktır ve dizi uzunluğunu başlatan ArrayList'in verimliliği, sonuçlardan LinkedList'inkinden açıkça daha yüksektir.
Ancak buradaki öncül, dizi genişlemesini önlemek için ArrayList'in dizi uzunluğunu önceden ayarlamaktır, böylece ArrayList'in yazma verimliliği çok yüksektir.BağlantılıList'in belleği kopyalamasına gerek olmasa da, nesneleri oluşturması, işaretçileri dönüştürmesi ve diğer işlemleri yapması gerekir.
Sorgulamaya gerek yok, ArrayList çok verimli olan alt simge rasgele erişimi destekleyebilir.
Altta yatan LinkedList bir dizi olmadığından, alt simge erişimini desteklemez, bunun yerine sorgu indeksinin konumuna göre baştan veya sondan geçilmesi gerekir.
Ancak hangisi olursa olsun, işaretçiyi birer birer hareket ettirmeniz gerekir, özellikle indeks orta konuma yakın olduğunda çok yavaş olacaktır.
Yüksek performanslı uygulamalar küçük detaylardan toplanır.Tıpkı burada bahsedilen ArrayList çukurunda olduğu gibi, günlük kullanımda büyük bir problem yoktur.Veri miktarı arttıkça, tüm küçük problemler büyük problemlere dönüşecektir.
Özetlemek gerekirse: