Java'da dizeleri birleştirirken bellekte kaç dize oluşturulur?


17

Bana Java'daki değişmez dizeler hakkında sorular soruldu. Bir dizeye bir dizi "a" s birleştirilmiş bir işlev yazma ile görev.

Ne yazdım:

public String foo(int n) {
    String s = "";
    for (int i = 0; i < n; i++) {
        s = s + "a"
    }
    return s;
}

Daha sonra, çöp toplama işleminin gerçekleşmediğini varsayarak, bu programın kaç dizeyi üreteceği soruldu. N = 3 için düşüncelerim

  1. ""
  2. "A"
  3. "A"
  4. "Aa"
  5. "A"
  6. "Aaa"
  7. "A"

Temelde, döngünün her yinelemesinde 2 dize oluşturulur. Ancak cevap n oldu 2 . Bu işlev tarafından bellekte hangi dizeler oluşturulacak ve neden bu şekilde?


15
Bu işi teklif ederseniz, kaçın, çok hızlı
koşun

@mattnz birçok nedenden ötürü (yalnızca yazılı kod nedeniyle değil).

3
Bu, JIT döngüyü optimize etmediği halde O (n ^ 2) çalışma zamanını alır, ancak n ^ 2 dizeleri oluşturmaz.
user2357112 Monica

Yanıtlar:


26

Daha sonra, çöp toplama işleminin gerçekleşmediğini varsayarak, bu programın kaç dizeyi üreteceği soruldu. N = 3 için düşüncelerim (7)

Dizeler 1 ( "") ve 2 ( "a"), programdaki sabitlerdir, bunlar bir şeylerin parçası olarak oluşturulmaz, ancak derleyicinin bildiği sabitler oldukları için 'interned' olurlar. Wikipedia hakkında String interning adresinde bununla ilgili daha fazla bilgi bulabilirsiniz .

Bu, 5 ve 7 dizelerini sayıdan kaldırır ve "a"Dize # 2 ile aynıdır . Bu, # 3, # 4 ve # 6 dizelerini bırakır. Yanıt, kodunuzu kullanarak "n = 3 için 3 dize oluşturulur" dır.

N sayısı 2 n = 3, bu 9 olur ve hatta olmayan enterne dizeleri doğru olsaydı sadece 7 yaşında en kötü durum cevap tarafından, cevap çünkü besbelli yanlıştır gerektiğini 2n + 1 olmuştur.

Peki, bunu nasıl yapmalısınız ?

Dize değişmez olduğu için , değiştirilebilir bir şey istiyorsunuz - yeni nesneler oluşturmadan değiştirebileceğiniz bir şey. Bu StringBuilder .

Bakılması gereken ilk şey yapıcılar. Bu durumda ipin ne kadar süreceğini biliyoruz ve bir kurucu var, StringBuilder(int capacity) bu da tam olarak ihtiyacımız olduğu kadar tahsis ettiğimiz anlamına geliyor.

Daha sonra, "a"bir String olması gerekmez , aksine bir karakter olabilir 'a'. Bu, append(String)vs append(char)ile çağrılırken bazı küçük performans artışı sağlar append(String), yöntem, String'in ne kadar uzun olduğunu bulmalı ve bu konuda biraz çalışmalıdır. Öte yandan, charher zaman tam olarak bir karakter uzunluğundadır.

Kod farklılıkları StringBuilder.append (String) ve StringBuilder.append (char) ' da görülebilir . Bu çok endişe edilecek bir şey değil , ancak işvereni etkilemeye çalışıyorsanız, mümkün olan en iyi uygulamaları kullanmak en iyisidir.

Peki, bir araya getirdiğinizde bu nasıl görünüyor?

public String foo(int n) {
    StringBuilder sb = new StringBuilder(n);
    for (int i = 0; i < n; i++) {
        sb.append('a');
    }
    return sb.toString();
}

Bir StringBuilder ve bir String oluşturuldu. Staj yapmak için ekstra dizeye gerek yok.


Eclipse'de başka basit programlar yazın. Pmd'yi yükleyin ve yazdığınız kod üzerinde çalıştırın. Ne şikayet ettiğine dikkat edin ve bunları düzeltin. Bu bir döngü içinde + ile dize değişiklik bulurdum ve StringBuilder o değişirse, o olurdu belki başlangıç kapasitesi bulunan, ama kesinlikle arasındaki farkı yakalamak istiyorsunuz .append("a")ve.append('a')


9

Her yinelemede, operatör Stringtarafından yeni bir oluşturulur +ve atandı s. Döndükten sonra, sonuncusu hariç hepsi çöp toplanır.

Her seferinde gibi ""ve sabit "a"olmayan dize sabitleri , bunlar iç içe dizelerdir . Dizeler değişmez olduğu için serbestçe paylaşılabilirler; bu dize sabitlerine olur.

Dizeleri verimli bir şekilde birleştirmek için kullanın StringBuilder.


Röportajdaki insanlar gerçekte gerçek bilginin olup olmadığını tartıştılar ve her seferinde harflerin oluşturulmasına karar verdiler. Ama bu daha mantıklı.
ahalbert

6
Bir dilin ne yaptığını "tartışırsınız", kesinlikle şartnameyi okudunuz ve kesin olarak biliyorsunuz ya da tanımlanmamıştır ve bu nedenle doğru bir cevap yoktur .....
mattnz

@mattnz Uygulama ayrıntıları söz konusu olduğunda bile, kullandığınız derleyici / çalışma zamanının ne yaptığını bilmek ilginç olabilir. Bu özellikle performans için geçerlidir.
svick

1
@svick: Varsayımlar yaparak çok şey kazanabilirsiniz, daha sonra derleyici yükseltilir, optimizasyon vb. değişir. Tanımlanan davranış yerine belirtilmemiş davranışa güvendiğiniz için davranış hatalara neden olur. Optimizasyon hakkında ne söylediklerini biliyorsunuz - a) uzmanlara bırakın ve b) henüz uzman değilsiniz. :) Güven yalnızca performansa dayalıysa, ancak yine de dil belirtimindeyse, yalnızca performansı kaybedersiniz. Çoğu kez beklenmedik şekillerde (çoğunlukla C ve C ++) belirtilmemiş veya derleyiciye özgü davranış mola dayanan kod gördüm.
mattnz

@mattnz Peki performansla ilgili kararlar almayı nasıl öneriyorsunuz? Genellikle, teknik özelliklerden / dokümantasyondan alabileceğiniz en iyi şey büyük O karmaşıklıklarıdır, ancak bu yeterli değildir. Her durumda, performans her zaman uygulamaya bağlıdır, bu nedenle performans söz konusu olduğunda uygulama ayrıntılarına güvenmenin uygun olduğunu düşünüyorum.
svick

4

MichaelT'nin cevabında açıkladığı gibi, kodunuz O (n) dizelerini ayırır. Ancak aynı zamanda O (n 2 ) bayt belleğini tahsis eder ve O (n 2 ) sürede çalışır.

O (n 2 ) baytını ayırır, çünkü ayırdığınız dizelerin uzunlukları 0, 1, 2,…, n-1, n'dir, bu da (n 2 + n) / 2 = O (n 2 ) 'dir.

Zaman ayrıca O (n 2 ) 'dir, çünkü i-th dizesinin tahsisi, i-1 uzunluğuna sahip (i-1) -th dizesinin kopyalanmasını gerektirir. Bu, ayrılan her baytın kopyalanması gerektiği anlamına gelir; bu O (n) 2 ) zaman .

Belki görüşmeciler bunu kastediyordu?


Denklem (n ^ 2 + n) / 2, böyle olmamalı burada ?
HeyJude
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.