Java'da String, StringBuilder ve StringBuffer'ı keşfedin

String sınıfının Java'da en sık kullanılan sınıflardan biri olduğuna inanıyorum ve aynı zamanda büyük şirketlerin röportajlarda sormaktan hoşlandıkları bir yer.Bugün sizlerle String, StringBuilder ve StringBuffer hakkında bilgi edinecek ve Her kategorinin uygulanabilir senaryolarının benzerlikleri, farklılıkları ve anlayışı. Bu makale için özetlenen içindekiler tablosu aşağıdadır:

1. String sınıfını biliyor musunuz?

2. String, StringBuffer, StringBuilder'ın derinlemesine anlaşılması

3. Farklı senaryolarda üç sınıfın performans testi

4. String ve StringBuffer hakkında genel mülakat soruları (İnternette dolaşan String sınıfı hakkındaki bazı yanlış anlamaları reddederek)

Yanlış bir şey varsa lütfen anlayın ve düzeltin, çok minnettarım.

1. String sınıfını biliyor musunuz?

Bir sınıfı anlamak için en iyi yol, bu sınıfın uygulama kaynak koduna bakmaktır. String sınıfının uygulaması şu şekildedir:

\ jdk1.6.0_14 \ src \ java \ lang \ String.java dosyası.

Bu sınıf dosyasını açın ve String sınıfının sonlandırıldığını göreceksiniz:

public final class String java.io'yu uygular.Serializable, Comparable < Dize > , CharSequence { / ** Değer, karakter depolamak için kullanılır. * / özel son karakter değeri; / ** Göreli konum, kullanılan depolamanın ilk dizinidir. * / özel nihai int göreli konum; / ** Sayı, Dizedeki karakter sayısıdır. * / özel nihai int sayısı; / ** Dize için hash kodunu önbelleğe al * / private int hash; // Varsayılan 0 / ** birlikte çalışabilirlik için JDK 1.0.2'den serialVersionUID kullanın * / özel statik son uzun serialVersionUID = -6849794470754667710L; ...... }

Yukarıdan birkaç nokta görülebilir:

1) String sınıfı nihaidir, bu da String sınıfının miras alınamayacağı ve üye yöntemlerinin varsayılan olarak nihai olduğu anlamına gelir. Java'da, son değiştirilmiş sınıfın miras alınmasına izin verilmez ve bu sınıftaki üye yöntemler varsayılan olarak nihai yöntemlere geçer. JVM uygulamasının erken sürümünde, son değiştirilmiş yöntem, yürütme verimliliğini artırmak için satır içi çağrılara dönüştürülecektir. Java SE5 / 6'dan bu yana, bu yaklaşım aşamalı olarak terk edilmiştir. Bu nedenle, Java SE'nin mevcut sürümünde, yöntem çağrısı verimliliğini artırmak için final kullanmayı düşünmeye gerek yoktur. Yalnızca yöntemin üzerine yazılmasını istemediğinizden emin olduğunuzda yöntemi son olarak ayarlayın.

2) String sınıfındaki tüm üye öznitelikleri yukarıda listelenmiştir Yukarıdan, String sınıfının aslında bir char dizisi aracılığıyla dizeleri depoladığı görülebilir.

String sınıfının bazı yöntem uygulamalarını görmeye devam edelim:

public String substring (int beginIndex, int endIndex) { eğer (beginIndex < 0) { yeni StringIndexOutOfBoundsException (beginIndex) atmak; } eğer (endIndex > Miktar) { yeni StringIndexOutOfBoundsException (endIndex); } eğer (beginIndex > endIndex) { yeni StringIndexOutOfBoundsException (endIndex-beginIndex); } return ((beginIndex == 0) (endIndex == count))? bu: new String (offset + beginIndex, endIndex-beginIndex, değer); } public String concat (String str) { int diğerLen = str.length (); eğer (otherLen == 0) { bunu geri ver; } char buf = yeni karakter; getChars (0, sayım, buf, 0); str.getChars (0, diğerLen, buf, sayım); yeni String (0, count + otherLen, buf) döndür; } public String replace (char oldChar, char newChar) { eğer (oldChar! = newChar) { int len = sayım; int i = -1; char val = değer; / * getfield işlem kodundan kaçının * / int off = offset; / * getfield opcode'dan kaçının * / süre (++ i < len) { if (val == oldChar) { kırmak; } } Eğer ben < len) { char buf = yeni karakter; for (int j = 0; j < i; j ++) { buf = val; } süre (ben < len) { char c = val; buf = (c == oldChar)? newChar: c; i ++; } yeni String (0, len, buf) döndür; } } bunu geri ver;

Yukarıdaki üç yöntemden, orijinal dizide alt işlem, birleştirme veya değiştirme işleminin yapılmadığı, ancak yeni bir dizge nesnesinin yeniden oluşturulduğu görülebilir. Yani bu işlemlerden sonra en orijinal dizge değiştirilmemiştir.

Burada her zaman hatırlanması gereken bir şey:

"String nesnesindeki herhangi bir değişiklik orijinal nesneyi etkilemeyecek ve ilgili herhangi bir değişiklik işlemi yeni bir nesne oluşturacaktır."

String sınıfının temel bilgilerini anladıktan sonra, normal kullanımda kolayca gözden kaçan ve karıştırılan bazı yerlere bakalım.

2. String, StringBuffer, StringBuilder'ın derinlemesine anlaşılması

1. String str = "merhaba dünya" ve String str = new String ("merhaba dünya") arasındaki fark

Herkesin yukarıdaki iki cümleye aşina olduğunu düşünüyorum ve kod yazma sürecinde sık sık onlarla karşılaşıyorlar, peki aralarındaki fark ve bağlantı nedir? Birkaç örneğe bakalım:

public class Ana { public static void main (String args) { String str1 = "merhaba dünya"; String str2 = new String ("merhaba dünya"); String str3 = "merhaba dünya"; String str4 = new String ("merhaba dünya"); System.out.println (str1 == str2); System.out.println (str1 == str3); System.out.println (str2 == str4); } }

Neden böyle bir sonuç var? Nedeni aşağıda açıklanmıştır:

JVM bellek mekanizması ile ilgili bir önceki blog yazısında, sınıf dosyasında derleme sırasında üretilen değişmez sabitleri ve sembol referanslarını saklamak için bir bölüm olduğundan bahsedilmişti.Bu bölüm, çalışma zamanı sırasında yöntem alanına karşılık gelen sınıf dosyası sabit havuzu olarak adlandırılıyordu. Çalışma zamanı sabit havuzu.

Bu nedenle, yukarıdaki kodda, String str1 = "merhaba dünya"; ve String str3 = "merhaba dünya"; her ikisi de derleme sırasında değişmez sabitler ve sembol referansları üretir ve çalışma süresi sırasında "merhaba dünya" değişmez sabiti çalışma zamanı sabit havuzunda saklanır (Tabii ki sadece bir kopya saklanır). Bu şekilde, String nesnesi referansa bağlıysa, JVM yürütme motoru ilk olarak çalışma zamanı sabit havuzunda aynı değişmez sabiti arayacaktır.Varsa, referansı doğrudan mevcut değişmez sabite yönlendirecektir; aksi takdirde, her zaman çalışma zamanında olacaktır. Ölçüm havuzu, değişmez sabiti depolamak için bir alan açar ve referansı değişmez sabite işaret eder.

Yeni anahtar sözcük aracılığıyla nesnelerin üretilmesinin yığın alanında gerçekleştirildiği ve öbek alanındaki nesne oluşturma işleminin nesnenin halihazırda var olup olmadığını tespit etmediği iyi bilinmektedir. Bu nedenle, new aracılığıyla bir nesne oluşturduğunuzda, dizenin içeriği aynı olsa bile farklı bir nesne oluşturmalısınız.

2. String, StringBuffer ve StringBuilder arasındaki fark

String sınıfı Java'da zaten mevcut olduğundan, neden StringBuilder ve StringBuffer sınıflarına ihtiyacımız var?

Ardından aşağıdaki koda bakın:

public class Ana { public static void main (String args) { String string = ""; for (int i = 0; i < 10000; i ++) { string + = "merhaba"; } } }

Bu cümle dizesinin + = "merhaba"; işlemi, orijinal dize değişkeninin gösterdiği nesnenin içeriğini çıkarmaya ve dizeyi "merhaba" ile ekleyip başka bir yeni String nesnesinde depolamaya ve ardından dize değişkeninin şunu göstermesine izin vermeye eşdeğerdir: Yeni oluşturulan nesne. Herhangi bir sorunuz varsa, bayt kodu dosyasını açıklığa kavuşturmak için derleyebilirsiniz:

Bu derlenmiş bayt kodu dosyasından açıkça görülebilir: 8. satırdan 35. satıra kadar, tüm döngünün yürütme süreci vardır ve her döngü bir StringBuilder nesnesi oluşturacak ve ardından ekleme işlemini gerçekleştirecektir. Son olarak, String nesnesi, toString yöntemi aracılığıyla döndürülür. Yani, bu döngü gerçekleştirildikten sonra 10.000 yeni nesne üretiliyor, bu nesneler geri dönüştürülmezse, çok fazla bellek kaynağı israfına neden olacağını hayal edin. Yukarıdan da görülebilir ki, string + = "merhaba" işlevi aslında JVM tarafından otomatik olarak şu şekilde optimize edilecektir:

StringBuilder str = new StringBuilder (string);

str.append ("merhaba");

str.toString ();

Aşağıdaki koda bakın:

public class Ana { public static void main (String args) { StringBuilder stringBuilder = new StringBuilder (); for (int i = 0; i < 10000; i ++) { stringBuilder.append ("merhaba"); } } }

Aşağıdakileri elde etmek için bayt kodu dosyasını yeniden derleyin:

Buradan, bu kodun for döngüsünün 13. satırdan 27. satıra başladığı ve yeni işlemin yalnızca bir kez gerçekleştirildiği, yani yalnızca bir nesne üretildiği ve ekleme işleminin orijinal nesne temelinde gerçekleştirildiği açıkça görülmektedir. nın-nin. Bu nedenle, 10.000 döngüden sonra, bu kodun kapladığı kaynak yukarıdakinden çok daha küçüktür.

Bu yüzden, bazı insanlar bir StringBuilder sınıfı olduğu için, StringBuffer sınıfına neden ihtiyacımız olduğunu sorabilir? Kaynak kodunu bir bakışta görüntüleyin.Aslında, StringBuilder ve StringBuffer sınıflarının üye özellikleri ve üye yöntemleri temelde aynıdır.Farkı, StringBuffer sınıfının üye yöntemlerinin önünde ek bir anahtar kelimenin olmasıdır: senkronize edilmiş. Söylemeye gerek yok, bu anahtar kelime çok iş parçacıklıdır. Erişim sırasında güvenlik koruması rolü oynar, bu da StringBuffer'ın iş parçacığı açısından güvenli olduğu anlamına gelir.

Aşağıdaki, ekleme yönteminin somut uygulaması olan StringBuffer ve StringBuilder'dan iki kod parçası ayıklar:

StringBuilder yöntemini ekle

public StringBuilder eklentisi (int dizin, karakter dizesi, int ofset, int len) { super.insert (indeks, str, ofset, len); bunu geri ver; }

StringBuffer'ın ekleme yöntemi:

genel senkronize StringBuffer ekleme (int dizin, karakter dizesi, int ofset, int len) { super.insert (indeks, str, ofset, len); bunu geri ver; }

3. Farklı senaryolarda üç sınıfın performans testi

İkinci bölümden üç sınıf arasındaki farkı gördük. Bu bölümde, üç sınıfın performans farkını test etmek için küçük bir test yapalım:

public class Ana { özel statik int zamanı = 50000; public static void main (String args) { testString (); testStringBuffer (); testStringBuilder (); test1String (); test2String (); } public static void testString () { Dize s = ""; long begin = System.currentTimeMillis (); for (int i = 0; i < zaman; i ++) { s + = "java"; } long over = System.currentTimeMillis (); System.out.println ("İşlem" + s.getClass (). GetName () + "Tür için kullanılan süre:" + (aşırı başlangıç) + "milisaniye"); } public static void testStringBuffer () { StringBuffer sb = new StringBuffer (); long begin = System.currentTimeMillis (); for (int i = 0; i < zaman; i ++) { sb.append ("java"); } long over = System.currentTimeMillis (); System.out.println ("İşlem" + sb.getClass (). GetName () + "Tür için kullanılan süre:" + (aşırı başlangıç) + "milisaniye"); } public static void testStringBuilder () { StringBuilder sb = new StringBuilder (); long begin = System.currentTimeMillis (); for (int i = 0; i < zaman; i ++) { sb.append ("java"); } long over = System.currentTimeMillis (); System.out.println ("İşlem" + sb.getClass (). GetName () + "Tür için kullanılan süre:" + (aşırı başlangıç) + "milisaniye"); } public static void test1String () { long begin = System.currentTimeMillis (); for (int i = 0; i < zaman; i ++) { String s = "I" + "aşk" + "java"; } long over = System.currentTimeMillis (); System.out.println ("Dizelerin doğrudan eklenmesi:" + (aşırı başlangıç) + "milisaniye"); } public static void test2String () { String s1 = "I"; String s2 = "aşk"; String s3 = "java"; long begin = System.currentTimeMillis (); for (int i = 0; i < zaman; i ++) { Dize s = s1 + s2 + s3; } long over = System.currentTimeMillis (); System.out.println ("Dizelerin dolaylı olarak eklenmesi:" + (aşırı başlangıç) + "milisaniye"); } }

Test sonuçları (win7, Eclipse, JDK6):

Yukarıda bahsedilen string + = "merhaba" işlemi aslında JVM tarafından otomatik olarak optimize edilecektir. Aşağıdaki koda bakın:

public class Ana { özel statik int zamanı = 50000; public static void main (String args) { testString (); testOptimalString (); } public static void testString () { Dize s = ""; long begin = System.currentTimeMillis (); for (int i = 0; i < zaman; i ++) { s + = "java"; } long over = System.currentTimeMillis (); System.out.println ("İşlem" + s.getClass (). GetName () + "Tür için kullanılan süre:" + (aşırı başlangıç) + "milisaniye"); } public static void testOptimalString () { Dize s = ""; long begin = System.currentTimeMillis (); for (int i = 0; i < zaman; i ++) { StringBuilder sb = yeni StringBuilder (lar); sb.append ("java"); s = sb.toString (); } long over = System.currentTimeMillis (); System.out.println ("JVM optimizasyon işlemini simüle etme zamanı:" + (aşırı başlangıç) + "milisaniye"); } }

Sonuçları:

Doğrulanacak.

Aşağıda, yukarıdaki yürütme sonuçlarının genel bir açıklaması bulunmaktadır:

1) Dizelerin doğrudan eklenmesi için verimlilik çok yüksektir, çünkü derleyici değerini belirler, yani derleme sırasında "I" + "aşk" + "java" formunun dizisi eklenir. "Ilovejava" olarak optimize edildi. Bu, oluşturulan sınıf dosyasını javap -c komutuyla yeniden derleyerek doğrulanabilir.

Dolaylı toplama için (yani, dize referansları dahil), form s1 + s2 + s3'tür; verimlilik, doğrudan toplamadan daha düşüktür, çünkü derleyici referans değişkenini optimize etmeyecektir.

2) String, StringBuilder, StringBuffer'ın yürütme verimliliği:

StringBuilder > StringBuffer > Dize

Elbette bu görecelidir, her durumda zorunlu değildir.

Örneğin, String str = "merhaba" + "dünya" nın verimliliği, StringBuilder st = new StringBuilder (). Append ("merhaba"). Append ("world") 'den daha yüksektir.

Bu nedenle, bu üç kategorinin kendi avantajları ve dezavantajları vardır ve farklı durumlara göre seçilmeli ve kullanılmalıdır:

Dize toplama işlemleri veya değişiklikleri küçük olduğunda, String str = "merhaba" bu formun kullanılması önerilir;

Çok sayıda string toplama işlemi olduğunda, StringBuilder kullanılması tavsiye edilir, eğer birden fazla thread kullanılıyorsa StringBuffer kullanılır.

4. String ve StringBuffer hakkında sıkça sorulan mülakat soruları

Aşağıda String ve StringBuffer hakkında bazı genel mülakat soruları yer almaktadır: Yanlış bir şey varsa, lütfen anlayın, eleştirin ve düzeltin.

1. Aşağıdaki kodun çıktısı nedir?

Dize a = "merhaba2"; Dize b = "merhaba" + 2; System.out.println ((a == b));

Çıktı sonucu: true. Nedeni basittir, "merhaba" +2, derleme sırasında "merhaba2" olarak optimize edilmiştir, bu nedenle çalışma süresi sırasında, değişken a ve değişken b aynı nesneyi işaret eder.

2. Aşağıdaki kodun çıktısı nedir?

Dize a = "merhaba2"; Dize b = "merhaba"; Dize c = b + 2; System.out.println ((a == c));

Çıktı sonucu: yanlış. Sembolik referansların varlığından dolayı, String c = b + 2; derleme sırasında optimize edilmeyecek ve b + 2 değişmez bir sabit olarak değerlendirilmeyecektir, bu nedenle bu şekilde üretilen nesneler aslında yığın üzerinde depolanır. nın-nin. Bu nedenle, a ve c aynı nesneyi işaret etmiyor. Javap -c ne alır:

3. Aşağıdaki kodun çıktısı nedir?

Dize a = "merhaba2"; final Dize b = "merhaba"; Dize c = b + 2; System.out.println ((a == c));

Çıktı sonucu: true. Final tarafından değiştirilen değişkenler için, sınıf dosyası sabit havuzuna bir kopya kaydedilir, bu da bağlantı yoluyla erişilemeyecekleri anlamına gelir.Son değişkenlere erişim, derleme sırasında doğrudan gerçek değerlerle değiştirilecektir. Ardından String c = b + 2; derleme sırasında optimize edilecek: String c = "merhaba" + 2; Aşağıdaki şekil javap -c'nin içeriğidir:

4. Aşağıdaki kodun çıktısı:

public class Ana { public static void main (String args) { String a = "merhaba2"; final String b = getHello (); Dize c = b + 2; System.out.println ((a == c)); } public static String getHello () { "merhaba" döndür; } }

Çıktı sonucu yanlıştır. B final ile değiştirilse de, ataması bir yöntem çağrısı tarafından döndürüldüğünden, değeri yalnızca çalışma zamanı sırasında belirlenebilir, bu nedenle a ve c aynı nesneyi işaret etmez.

5. Aşağıdaki kodun çıktısı nedir?

public class Ana { public static void main (String args) { String a = "merhaba"; Dize b = new String ("merhaba"); String c = new String ("merhaba"); Dize d = b.intern (); System.out.println (a == b); System.out.println (b == c); System.out.println (b == d); System.out.println (a == d); } }

Çıktı sonucu (JDK sürümü JDK6):

Bu, String.intern yönteminin kullanılmasını içerir. String sınıfında intern yöntemi yerel bir yöntemdir.JAVA SE6'dan önce intern yöntemi, çalışma zamanı sabit havuzunda aynı içeriğe sahip bir dizeyi arar ve varsa dizeye bir başvuru döndürür.Eğer yoksa, Dize havuzlanır ve dizeye bir başvuru döndürülür. Bu nedenle, a ve d aynı nesneyi gösterir.

6. String str = new String ("abc") kaç nesne yarattı?

Bu soruya "Java Programmer Interview Collection" gibi pek çok kitapta değinilmektedir, birçok yerli büyük firmanın yazılı sınav mülakat soruları da dahil olmak üzere, internetin çoğu dolaşmaktadır ve bazı mülakat kitaplarında iki nesne olduğunu söylemektedir. Tartışma tek taraflı.

Bir şey bilmiyorsanız, bu gönderiye başvurabilirsiniz:

Öncelikle, bir nesneyi yaratmanın anlamını bulmalıyız, ne zaman yaratıldı? Bu kod çalışma sırasında 2 nesne oluşturacak mı? Kuşkusuz imkansızdır. JVM tarafından çalıştırılan bayt kodu içeriğini javap -c ile derlemeyi çözerek elde edebilirsiniz:

Açıkçası, yeni yalnızca bir kez çağrılır, bu da yalnızca bir nesnenin yaratıldığı anlamına gelir.

Bu konunun kafa karıştırıcı olduğu yer burasıdır.Bu kod, işlem sırasında yalnızca bir nesne oluşturur, yani, yığın üzerinde bir "abc" nesnesi oluşturulur. Ve neden herkes iki nesneden bahsediyor? Açıklığa kavuşturulması gereken bir kavram var.Kod çalıştırma süreci ile sınıf yükleme süreci arasında bir fark var. Sınıf yükleme işleminde, çalışma zamanı sabit havuzunda gerçekten bir "abc" nesnesi oluşturulur ve kodun yürütülmesi sırasında gerçekten yalnızca bir String nesnesi oluşturulur.

Bu nedenle, bu soru String str = new String ("abc") ile değiştirilirse, kaç String nesnesi vardır? İki makul açıklama var.

Kişisel olarak, görüşme sırasında bu sorunla karşılaşırsanız, görüşmeciye açık bir şekilde "bu kodun yürütülmesi sırasında kaç nesne yaratıldığını veya kaç nesnenin dahil olduğunu" sorabilir ve ardından bunları ayrıntılara göre yanıtlayabilirsiniz.

7. Aşağıdaki kodun 1) ve 2) arasındaki fark nedir?

public class Ana { public static void main (String args) { Dize str1 = "I"; // str1 + = "aşk" + "java"; 1) str1 = str1 + "aşk" + "java"; // 2) } }

1) 'in verimliliği 2)' nin veriminden daha yüksektir. 1) 'deki "love" + "java", derleme sırasında "lovejava" olarak optimize edilirken, 2)' nin etkinliği optimize edilmeyecektir. Aşağıda bayt kodu iki şekilde verilmiştir:

1) Bayt kodu:

2) Bayt kodu:

1) şıkkında sadece bir ekleme işlemi gerçekleştirilirken 2) iki ekleme işlemi gerçekleştirildiği görülmektedir.

Orijinal adres:

Herhangi bir ihlal varsa, lütfen silmek için iletişime geçin, teşekkür ederim.

"The Valley of Light", yaratıcılık açısından biraz daha cesur olan Pekin Operası'na bir müzik tarzı dahil ediyor
önceki
Dünya Kupası Günlük Hatırlama: Final 1V3 Sıfır Dünya Dalgası! Ama sonraki nesiller yavaş yavaş unutur
Sonraki
Zhengzhou-Jiangsu Yüksek Hızlı Demiryolu üzerindeki Zhengzhou Sarı Nehir Köprüsü'nün ilk sürekli çelik makas kirişi başarıyla inşa edildi
Shen Libin: Kurumsal Hizmette Yapay Zeka Uygulaması ve Keşfi
"Xaiyouji" Girişi: Akdeniz-Beş Günlük Malta Turunun Kalbi
Bu düşük maliyetli ofis eseri, çalışmanızı daha verimli hale getirir
Lei Feng'den iş başında öğrenin
Milan'da 7 yaşındaki bir çocuk, McDonald's'ı yerken bir ilan panosundan başından yaralandı
21. seviyede aylık 270.000 maaşı olan bir programcı, ağlayan netizenlerin kıskançlığını çekiyor: Yoksulluk hayal gücümü sınırlıyor!
Haftanın en değerli cep telefonu önerisi iPhone'un fiyatına indirim yapıyor
E-kitaptan kurtulun! Okumayı telefonunuzu kaydırmak kadar kolaylaştırın!
Programcının büyük sırrı: Tencent, Baidu ve Ali'de çalışmak, aynı büyük fabrika arasındaki fark çok büyük
Ocak ayında buz şehrini ziyaret etmek için OnePlus 6T'yi getirin ve eksi 20 derecelik çarpıcı manzarayı kaydedin
Programcılar tarafından en yaygın olarak hangi anahtarlar kullanılır? Ctrl ve c kullanıyorum, v!
To Top