Projenizde Java koleksiyonlarını kullanırken kaçınmanız gereken bazı tuzaklar

Önsöz

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.

ArrayList çukura bastı

Liste < Dize > temp = new ArrayList (); // Bir toplu veri listesi alın < Dize > all = getData (); for (Dize str: tümü) {temp.add (str);}

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.

Çukur doldurma kılavuzu

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.

Bağlantılı liste

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.

Karşılaştırma testi

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.

sonuç olarak

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:

  • ArrayList'i kullanırken veri miktarını önceden tahmin edebiliyorsanız, daha büyük olduğunda uzunluğunu belirtmelisiniz.
  • Add (index, e) api'yi mümkün olduğunca kullanmaktan kaçının, bu dizinin kopyalanmasına ve verimliliğin düşmesine neden olur.
  • Bir şey daha, HashMap'i yaygın olarak kullandığımız başka bir Harita kapsayıcısının da genişlemeyi önlemek için uzunluğu başlatması önerilir.
Dizini kullandığımda sorgu neden hala yavaş?
önceki
Röportajda Java'nın çok iş parçacıklı güvenliği soruldu, bu yüzden ona cevap verdim
Sonraki
Bu makale, Java eşzamanlılığında kilit optimizasyonunu ve iş parçacığı havuzu optimizasyonunu anlamanızı sağlar
Nginx'in tüm sihirli işlevlerine sahip misiniz?
Sıfırdan yeni başlayanlar için bir arka uç teknoloji yığını oluşturun
RocketMQ işlem mekanizmasının uygulama süreci, mesaj göndermede neden sıfır kayıp elde edebiliyor?
SpringBoot projesi: RedisTemplate hafif mesaj kuyruğu uygular
Birden fazla deneyin paralel yinelemesini nasıl elde edebilirsiniz, Alimama'nın A / B testi uygulaması hakkında konuşun
On milyarlarca trafik taşıyan yüksek performanslı bir mimari nasıl tasarlanır?
Ant Financial'ın 100 milyon düzeyinde eşzamanlılık altında mobil uçtan uca ağ erişim mimarisinin analizi
Zookeeper tarafından dağıtılmış kilit ve Zookeeper'a dayalı liderlik seçimi hakkında derinlemesine anlayış
Kandırılmaktan bunalıma giren sarhoş bir adam yüz dolarlık banknot dağıtır ve intihar etmek ister.
Tayland'da okumak için sahip olmanız gereken yetenekleri biliyor musunuz?
Lisansüstü eğitim için Tayland'a gitmek ister misiniz, bunların hepsini biliyor musunuz?
To Top