eşzamanlılık
Java başlangıçtan itibaren eşzamanlılık düşünceleriyle tanımlandı. Sıkça bahsedildiği gibi, paylaşılan mutable problemlidir. Bir şey, başka bir ipliğin arkasında, o ipliğin farkında olmadan başka birini değiştirebilir.
Paylaşılan bir dize nedeniyle ortaya çıkan bir çok iş parçacıklı C ++ hata var - bir modül koddaki başka bir modül ona bir işaretçi kaydettiğinde ve aynı kalmasını beklediğinde değiştirmenin güvenli olduğunu düşünüyordu.
Bunun çözümü, her sınıfın kendisine aktarılan değiştirilebilir nesnelerin savunma kopyasını çıkarmasıdır. Değişken dizgelerde, kopya yapmak için bu O (n) 'dir. Değişmez dizeler için kopya yapmak O (1) 'dir, çünkü kopya değildir, değiştirilemeyen aynı nesnedir.
Çok iş parçacıklı bir ortamda, değişmez nesneler her zaman birbirleriyle güvenli bir şekilde paylaşılabilir. Bu, bellek kullanımında genel bir azalmaya yol açar ve bellek önbelleğe almayı iyileştirir.
Güvenlik
Dizeler çoğu zaman yapıcıların argümanları olarak iletilir - ağ bağlantıları ve protokoller en kolay akla gelen ikisidir. Bunu daha sonra uygulamada belirsiz bir zamanda değiştirebilmek, güvenlik sorunlarına yol açabilir (işlev, bir makineye bağlandığını düşündü, ancak diğerine yönlendirildi, ancak nesnedeki her şey, ilkine bağlı gibi görünüyor ... onun bile aynı dize).
Java tek bir yansıma kullanmasına izin verir - ve bunun için parametreler dizgedir. Birinden bir dize geçirme tehlikesi yansıtan başka bir yönteme giderken modifiye edilebilir. Bu çok kötü.
Hash Anahtarları
Karma tablo, en çok kullanılan veri yapılarından biridir. Veri yapısının anahtarları çok sık dizelidir. Değişmez dizgelere sahip olmak (yukarıdaki gibi) karma tablosunun her seferinde karma anahtarının bir kopyasını yapmasına gerek olmadığı anlamına gelir. Eğer dizeler değişebilirse ve karma tablo bunu yapmadıysa, karma anahtarını bir mesafeden değiştirmek bir şey mümkün olacaktır.
Java'daki Nesnenin çalışma şekli, her şeyin bir hash anahtarına sahip olmasıdır (hashCode () yöntemiyle erişilir). Değişmez bir dizgeye sahip olmak, hashCode öğesinin önbelleğe alınabileceği anlamına gelir. Dizelerin bir karma değerin anahtarları olarak ne sıklıkta kullanıldığı göz önüne alındığında, bu önemli bir performans artışı sağlar (karma kodunu her seferinde yeniden hesaplamak yerine).
altdizgelerin
String değişmez olması sayesinde, veri yapısını destekleyen temel karakter dizisi de değişmezdir. Bu, yapılması gereken substring
yöntem üzerinde belirli optimizasyonlara izin verir ( mutlaka yapılması gerekmez - ayrıca bazı bellek sızıntıları olasılığını da beraberinde getirir).
Yaparsan:
String foo = "smiles";
String bar = foo.substring(1,5);
Değeri bar
'mil'. Bununla birlikte, her ikisi de foo
ve bar
aynı karakter dizisi tarafından desteklenebilir, daha fazla karakter dizisinin başlatılmasını azaltır veya kopyalar - yalnızca dizgideki farklı başlangıç ve bitiş noktalarını kullanır.
foo | | (0, 6)
vv
gülümsüyor
^ ^
bar | | (1, 5)
Şimdi, bunun dezavantajı (bellek sızıntısı) eğer biri 1k uzunluğunda bir dizeye sahipse ve birinci ve ikinci karakterin alt dizisini aldıysa, aynı zamanda 1k uzun karakter dizisi tarafından da desteklenecektir. Bu dizi, tüm karakter dizisinin değerine sahip orijinal dize çöp toplanmış olsa bile bellekte kalır.
Bunu JDK 6b14'den String'de görebilirsiniz (aşağıdaki kod GPL v2 kaynağındandır ve örnek olarak kullanılır).
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
Alt dizinin, dizinin herhangi bir kopyasını içermeyen ve çok daha hızlı olacak olan paket düzeyindeki String yapıcısını nasıl kullandığına dikkat edin (büyük dizileri çoğaltmasa da, bazı büyük dizilerin etrafını tutmanın pahasına).
Yukarıdaki kodun Java 1.6 için olduğunu unutmayın. Belgelenen olarak alt dize yapıcısı uygulandığı yolu Java 1.7 ile değiştirildi Java 1.7.0_06 yapılan dize dahili gösterime Değişiklikler
- Sorunum yukarıda bahsedilen bu bellek sızıntısı bing. Java muhtemelen çok fazla String işlemi olan bir dil olarak görülmedi ve bu nedenle bir alt dize için performans artışı iyi bir şeydi. Şimdi, asla toplanmayan dizgilerde depolanan büyük XML belgeleriyle, bu bir sorun haline gelir ... ve böylece String
alt dizideki aynı diziyi kullanmama değişikliği , böylece daha büyük karakter dizisi daha hızlı toplanabilir.
Yığını kötüye kullanma
Bir verebilir dize değerini etrafında yerine değişkenlikle sorunları önlemek için değişmez dize başvurusunu geçirin. Ancak, büyük dizelerle, bunu istif üzerinde geçirmek ... sisteme zarar verir (tüm xml belgelerini istif olarak dizeler olarak koymak ve sonra bunları çıkarmak veya devam ettirmek ...).
Tekilleştirme olasılığı
Kabul edilirse, bu, Dizelerin neden değişmez olması gerektiği için bir ilk motivasyon değildi, ancak biri değişmez Dizelerin neden iyi bir şey olduğu rasyoneline bakıldığında, bu kesinlikle dikkate alınması gereken bir şey.
Dizelerle biraz çalışan herkes hafızayı emebileceklerini biliyor. Bu, özellikle bir süre etrafta dolaşan veritabanlarından veri çekmek gibi şeyler yaparken geçerlidir. Bu sokmalar ile defalarca, tekrar tekrar aynı dizge olurlar (her satır için bir kez).
Birçok büyük ölçekli Java uygulaması şu anda belleğe tıkanmıştır. Ölçümler, bu tür uygulamalarda ayarlanan Java yığını canlı verilerinin yaklaşık% 25'inin String nesneleri tarafından tüketildiğini göstermiştir. Ayrıca, bu String nesnelerinin kabaca yarısı kopyalardır, kopyaların string1.equals (string2) true olduğu anlamına gelir. Öbek üzerinde yinelenen String nesnelerine sahip olmak, esasen, sadece bir hafıza kaybıdır. ...
Java 8 güncellemesi 20 ile JEP 192 (yukarıda belirtilen motivasyon) bu sorunu çözmek için uygulanmaktadır. String tekilleştirme işleminin nasıl çalıştığının ayrıntılarına girmeden, String'lerin kendilerinin değişmez olması esastır. StringBuilders'ı tekilleştiremezsiniz çünkü değişebilirler ve birisinin altınızdan bir şey değiştirmesini istemezsiniz. Değiştirilemez Dizeler (bu String havuzuyla ilişkili), içinden geçebileceğiniz ve aynı olan iki dizeyi bulduğunuzda, bir dize başvurusunu diğerine işaret edebilir ve çöp toplayıcısının yeni kullanılmamış olanı kullanmasına izin verebilirsiniz.
Diğer diller
Nesnel C (Java'dan önce gelen) NSString
ve NSMutableString
.
C # ve .NET, varsayılan dizenin değişmez olduğu için aynı tasarım seçimlerini yaptı.
Lua dizeleri de değişmezdir.
Python da.
Tarihsel olarak, Lisp, Scheme, Smalltalk hepsi dizgede stajyerdir ve bu nedenle değişmez olmalarını sağlar. Daha modern dinamik diller genellikle dizeleri değişmez olmalarını gerektiren bir şekilde kullanır (bir String olmayabilir , ancak değişmezdir).
Sonuç
Bu tasarım düşünceleri birçok dilde tekrar tekrar yapılmıştır. Değişmez dizgelerin bütün garip olmaları için alternatiflerden daha iyi olduğu ve genel olarak daha iyi kod (daha az hata) ve daha hızlı çalıştırılabilir sonuçlara yol açtığı genel görüş birliğidir.