Koleksiyonların İçi — ArrayList, LinkedList ve Iterator
Önce şunu oku: HashMap, equals ve hashCode
30 saniyede özet
ArrayList numaralı koltuklara benzer: istediğine hemen gidersin ama başa birini sokmak herkesi kaydırır. LinkedList el ele tutuşan bir zincirdir: araya girmek kolay, birini bulmak uzun yürüyüş.
Aynı adı taşıyan, aynı düğmelere sahip iki alet düşün. Aynı düğmeye basıyorsun: biri hemen işini bitiriyor, öteki uzun uzun uğraşıyor.
Adım adım oku
- ArrayList'te elemanlar numaralı koltuklarda oturur. Başa birini oturtmak için 0 numaralı koltuğun boşalması gerekir.
- Bunun için arkadaki herkes, en sondan başlayarak birer koltuk kayar.
- LinkedList'te elemanlar el ele tutuşan düğümlerdir. Yeni düğüm yalnızca eski ilk düğümün elini tutar.
- Aynı iş, çok farklı zahmet: bir tarafta dört kaydırma, öbür tarafta tek bir yeni bağ.
-
Bayt: İki listem var, ikisinin de tipi
List. Başa bir eleman ekledim: biri anında bitti, öteki oyalandı. -
Sen: Aynı metot, farklı hız mı?
-
Bayt: İçleri farklı. Biri numaralı koltuklar, öteki el ele tutuşan bir zincir.
-
Bayt: Hangi işi sık yaptığını bil, listeyi ona göre seç. Çoğu zaman cevap ArrayList!
Java’da ikisinin de tipi List, ikisi de aynı metotlara sahip. Ama aynı satır birinde tek adım, ötekinde yüzlerce adım sürer.
ArrayList: bir dizi ve bir sayaç
ArrayList’in içinde sıra sıra dizilmiş koltuklar (bir dizi) ve kaç koltuğun dolu olduğunu tutan bir sayaç var. İndeks doğrudan koltuk numarasıdır: get(5000) kimseyi aramaz, doğrudan 5000 numaraya gider.
Koltuklar dolunca yaklaşık 1,5 kat büyük yeni bir salon açılır ve herkes oraya taşınır. Taşınma pahalıdır ama seyrek olur; bu yüzden sona ekleme ortalamada ucuzdur. Buna amortized costBir işlemin tek tek değil, uzun bir dizi boyunca ortalama maliyeti. ArrayList'e ekleme arada bir tüm diziyi kopyalar ama ortalamada sabit maliyetlidir.Sözlükte gör → denir.
Başa ya da araya eklemek ise farklı: arkadaki herkes birer koltuk kaymak zorundadır.
Kafam karıştı, daha basit anlat
ArrayList numaralı koltuklardır: 5000 numarayı söyle, doğrudan oraya gidersin. Ama en öne biri oturacaksa arkadaki herkes bir koltuk kaymak zorunda.
`list.get(5000)` çağrısı ArrayList'te ve LinkedList'te nasıl çalışır?
İç dizisi tamamen dolu bir ArrayList'e `add(x)` çağrıldığında ne olur?
LinkedList: düğümler ve yürüyüş
64 elemanlı bir LinkedList'te get(32) kaç düğüm yürür? Cevabı göster
31. LinkedList indeksi saklamaz; yakın uçtan başlayıp düğüm düğüm ilerler. Aynı okuma ArrayList’te tek adımdır.
Her eleman, iki komşusunun elini tutan ayrı bir kişidir (düğüm). Araya birini sokmak için iki eli bırakıp yeniden tutmak yeter; ama doğru yeri bulmak için baştan saymak gerekir.
Kitaplarda yazan “araya ekleme çok ucuzdur” cümlesi, yalnızca o yere zaten gelmişsen doğrudur. add(i, x) önce oraya kadar yürümek zorundadır.
Kafam karıştı, daha basit anlat
LinkedList el ele tutuşan bir zincirdir. Araya birini sokmak kolaydır, ama doğru yeri bulmak için baştan tek tek saymak gerekir.
"LinkedList'te araya ekleme O(1)" cümlesi ne zaman doğrudur?
Kendin gör
ArrayList ve LinkedList — bir işlem kaç adım?
Tohum 882522ArrayList — 12 eleman, iç dizi kapasitesi 15
- 0. eleman
- 1. eleman
- 2. eleman
- 3. eleman
- 4. eleman
- 5. eleman
- 6. eleman
- 7. eleman
- 8. eleman
- 9. eleman
- 10. eleman
- 11. eleman
Şu an ne oldu?
ArrayList, 12 eleman: sona ekle — add(x)
Adım: kopyalanan eleman, izlenen düğüm ya da ayrılan yeni nesne. Süre değil, iş miktarı.
Görevler0/4
ArrayList'e 40+ adım yaptıran tek bir ekleme bulaçık
İpucu
Sona eklemek ucuz. Hangi ekleme her elemanı yerinden oynatır?
LinkedList'e 20+ düğüm yürüten bir okuma yaptıraçık
İpucu
LinkedList'te indeksle okumak, yakın uçtan başlayıp düğüm düğüm ilerler.
ArrayList'in iç dizisini büyütaçık
İpucu
Büyüme yalnızca dizi tam doluyken olur. Kapasiteler: 10, 15, 22, 33, 49...
Tek thread'le ConcurrentModificationException alaçık
İpucu
Bir for-each döngüsünün içinde listeyi değiştirmeyi dene.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat. ArrayList’e sona ekleme: tek yazma.
- Boyutu 15 yap. Dizi dolu; 15 eleman kopyalandı.
- “Başa ekle” seç, boyutu 40 yap. 40 eleman kaydı.
- LinkedList’e geç. Aynı ekleme tek bir düğüm ayırıyor.
- “Ortadakini oku” seç. Bu kez LinkedList yürüyor, ArrayList tek adımda.
Big-O'ya göre LinkedList'in avantajlı olması gereken bazı iş yüklerinde bile ArrayList ölçümde daha hızlı çıkıyor. En önemli sebep?
Satır satır: gezinirken silmek
Tek thread, yine de exception
List<String> names = new ArrayList<>(List.of("ali", "", "can", "ece")); for (String name : names) { if (name.isBlank()) { names.remove(name); }}Debug
for-each Gizli bir Iterator açılır ve listenin değişiklik sayacını ezberler.
- modCount
- = 0
- expectedModCount
- = 0
Sol/sağ ok tuşlarıyla da gezebilirsin.
Buna fail-fast iteratorGezildiği sırada koleksiyon kendi dışından değiştirilirse bunu fark edip hemen ConcurrentModificationException fırlatan iterator. Tek thread'de de çalışır.Koleksiyon her yapısal değişiklikte modCount'u artırır; iterator her next() çağrısında kendi beklediği değerle karşılaştırır. Bu bir en-iyi-çaba denetimidir, eşzamanlılık garantisi değildir.Sözlükte gör → denir: liste, sen gezinirken değişti mi diye bakar ve değiştiyse hemen durur. Birden çok thread gerekmez; kural şu: gezinirken listeyi yalnızca iterator değiştirebilir.
names.removeIf(String::isBlank); // tek geçiş, kaydırma bir kezKafam karıştı, daha basit anlat
Bir listeyi gezerken ona eleman ekleyip çıkarma. Silmen gerekiyorsa removeIf kullan: o, gezmeyi ve silmeyi senin yerine güvenle yapar.
Bu metot iptal edilmiş siparişleri temizlemek için yazıldı ama tek thread'de bile ConcurrentModificationException fırlatıyor. Hangi satır?
Tuzaklar
Sayı listesinden remove(1). Bu çağrı “1 değerini sil” değil, “1 numaralı sıradakini sil” demektir. Değeri silmek için remove(Integer.valueOf(1)) yaz.
List.of ve Arrays.asList. İlki hiç değiştirilemez, ikincisinin boyutu sabittir. İkisine de add dersen UnsupportedOperationException alırsın.
Kuyruk için LinkedList. ArrayDeque aynı işi, her eleman için ayrı bir düğüm yaratmadan ve daha hızlı yapar.
Kaç eleman geleceğini biliyorsan söyle. new ArrayList<>(n) salonu baştan büyük açar, taşınmaların hiçbiri yaşanmaz.
Program ne yazdırır?
Her satırı çalışıp çalışmayacağına göre ayır.
Aşağıdaki örnek bir bankadan ve koleksiyon seçimini gerçek işlere göre yapıyor: LRU cache, doğru kapasite, enum anahtarlar, kademeli faiz için sıralı sorgular ve iterasyon sırasında güvenli silme.
Derinleş · Bankada koleksiyonlar: doğru yapı, doğru iş 5 dosya · ~86 satır · ilk okumada atlayabilirsin
Kendini sına
ArrayList'te get(i) elemanı baştan sayarak bulur.
`[a, b, c]` listesinde for-each içinde `b` silindiğinde exception fırlamıyor, ama `c` hiç işlenmiyor. Neden?
Aklında kalacak üç şey
- 1 ArrayList'te okumak tek adımdır, sona eklemek de çoğu zaman ucuzdur. Başa ya da araya eklemek her elemanı kaydırır.
- 2 LinkedList'te araya girmek ucuzdur ama doğru yeri bulmak baştan yürümek demektir. Pratikte ArrayList ve ArrayDeque çoğu zaman daha hızlıdır.
- 3 for-each içinde listeden eleman silmek tek thread'de bile hata fırlatır. Silmek için removeIf ya da iterator.remove kullanılır.
5 kart sonraki derste seni bekliyor