Mengapa menambahkan "" ke String menghemat memori?

193

Saya menggunakan variabel dengan banyak data di dalamnya, katakanlah String data. Saya ingin menggunakan sebagian kecil dari string ini dengan cara berikut:

this.smallpart = data.substring(12,18);

Setelah beberapa jam melakukan debug (dengan visualizer memori) saya menemukan bahwa bidang objek smallpartmengingat semua data dari data, meskipun hanya berisi substring.

Ketika saya mengubah kode menjadi:

this.smallpart = data.substring(12,18)+""; 

..masalah terselesaikan! Sekarang aplikasi saya menggunakan sangat sedikit memori sekarang!

Bagaimana mungkin? Adakah yang bisa menjelaskan ini? Saya pikir ini. Sebagian kecil terus merujuk pada data, tapi mengapa?

PEMBARUAN: Bagaimana saya bisa menghapus String besar itu? Apakah data = String baru (data.substring (0,100)) melakukan hal itu?

hsmit
sumber
Baca lebih lanjut tentang maksud utama Anda di bawah ini: Dari mana asal string besar itu? Jika membaca dari file atau database CLOB atau sesuatu, maka hanya membaca apa yang Anda butuhkan saat parsing akan optimal sepanjang jalan.
PSpeed
4
Luar biasa ... Saya bekerja di java lebih dari 4 hingga 5 tahun, masih ini baru untuk saya :). terima kasih atas info bro.
Parth
1
Ada kehalusan untuk menggunakan new String(String); lihat stackoverflow.com/a/390854/8946 .
Lawrence Dol

Jawaban:

159

Melakukan hal berikut:

data.substring(x, y) + ""

menciptakan objek String baru (lebih kecil), dan membuang referensi ke String yang dibuat oleh substring (), sehingga memungkinkan pengumpulan sampah ini.

Hal penting untuk disadari adalah substring()memberikan jendela ke String yang ada - atau lebih tepatnya, array karakter yang mendasari String asli. Karenanya ia akan menggunakan memori yang sama dengan String asli. Ini bisa menguntungkan dalam beberapa keadaan, tetapi bermasalah jika Anda ingin mendapatkan substring dan membuang String asli (seperti yang Anda temukan).

Lihatlah metode substring () di sumber String JDK untuk info lebih lanjut.

EDIT: Untuk menjawab pertanyaan tambahan Anda, membangun sebuah String baru dari substring akan mengurangi konsumsi memori Anda, asalkan Anda membuang referensi ke String asli.

CATATAN (Jan 2013). Perilaku di atas telah berubah di Java 7u6 . Pola kelas terbang tidak lagi digunakan dan substring()akan berfungsi seperti yang Anda harapkan.

Brian Agnew
sumber
89
Itu salah satu dari sedikit kasus di mana String(String)konstruktor (yaitu konstruktor String mengambil String sebagai input) berguna: new String(data.substring(x, y))melakukan hal yang sama efektifnya dengan menambahkan "", tetapi itu membuat maksudnya menjadi lebih jelas.
Joachim Sauer
3
hanya untuk tepat, substring menggunakan valueatribut dari string asli. Saya pikir itu sebabnya referensi disimpan.
Valentin Rocher
@ Bishiboosh - ya, itu benar. Saya tidak ingin mengungkapkan kekhasan implementasi, tetapi justru itulah yang terjadi.
Brian Agnew
5
Secara teknis ini adalah detail implementasi. Tapi tetap saja frustasi, dan menangkap banyak orang.
Brian Agnew
1
Saya ingin tahu apakah mungkin untuk mengoptimalkan ini di JDK menggunakan referensi yang lemah atau semacamnya. Jika saya orang terakhir yang membutuhkan karakter ini [], dan saya hanya perlu sedikit, buat array baru untuk saya gunakan secara internal.
WW.
28

Jika Anda melihat sumbernya substring(int, int), Anda akan melihatnya kembali:

new String(offset + beginIndex, endIndex - beginIndex, value);

dimana valueaslinyachar[] . Jadi Anda mendapatkan String baru tetapi dengan dasar yang samachar[] .

Saat kamu melakukan, data.substring() + "" Anda mendapatkan String baru dengan dasar yang baruchar[] .

Sebenarnya, use case Anda adalah satu-satunya situasi di mana Anda harus menggunakan String(String)konstruktor:

String tiny = new String(huge.substring(12,18));
Thivent Pascal
sumber
1
Ada kehalusan untuk menggunakan new String(String); lihat stackoverflow.com/a/390854/8946 .
Lawrence Dol
17

Saat Anda menggunakannya substring, itu tidak benar-benar membuat string baru. Itu masih mengacu pada string asli Anda, dengan batasan offset dan ukuran.

Jadi, untuk memungkinkan string asli Anda dikumpulkan, Anda perlu membuat string baru (menggunakan new String, atau apa yang Anda punya).

Chris Jester-Young
sumber
5

Saya pikir ini. Sebagian kecil terus merujuk pada data, tapi mengapa?

Karena string Java terdiri dari array char, start offset dan panjang (dan kode hash yang di-cache). Beberapa operasi String seperti substring()membuat objek String baru yang berbagi array char asli dan hanya memiliki bidang offset dan / atau panjang yang berbeda. Ini berfungsi karena array char dari String tidak pernah dimodifikasi begitu telah dibuat.

Ini dapat menghemat memori saat banyak substring merujuk ke string dasar yang sama tanpa mereplikasi bagian yang tumpang tindih. Seperti yang Anda perhatikan, dalam beberapa situasi, ini dapat menjaga data yang tidak diperlukan lagi dari pengumpulan sampah.

Cara "benar" untuk memperbaikinya adalah new String(String)konstruktor, yaitu

this.smallpart = new String(data.substring(12,18));

BTW, solusi terbaik secara keseluruhan adalah untuk menghindari memiliki String yang sangat besar di tempat pertama, dan memproses input dalam potongan yang lebih kecil, beberapa KB sekaligus.

Michael Borgwardt
sumber
Ada kehalusan untuk menggunakan new String(String); lihat stackoverflow.com/a/390854/8946 .
Lawrence Dol
5

Di Jawa string adalah objek yang dapat diubah dan setelah sebuah string dibuat, ia tetap berada di memori sampai dibersihkan oleh pengumpul sampah (dan pembersihan ini bukanlah sesuatu yang dapat Anda terima begitu saja).

Saat Anda memanggil metode substring, Java tidak membuat string yang benar-benar baru, tetapi hanya menyimpan serangkaian karakter di dalam string asli.

Jadi, ketika Anda membuat string baru dengan kode ini:

this.smallpart = data.substring(12, 18) + ""; 

Anda benar-benar membuat string baru ketika Anda menggabungkan hasilnya dengan string kosong. Itu sebabnya.

Kico Lobo
sumber
3

Seperti yang didokumentasikan oleh jwz pada tahun 1997 :

Jika Anda memiliki string besar, tarik substring () darinya, pegang substring dan biarkan string yang lebih panjang menjadi sampah (dengan kata lain, substring memiliki masa pakai lebih lama) byte yang mendasari string besar tidak pernah pergi jauh.

Ken
sumber
2

Singkatnya, jika Anda membuat banyak substring dari sejumlah kecil string besar, maka gunakan

   String subtring = string.substring(5,23)

Karena Anda hanya menggunakan ruang untuk menyimpan string besar, tetapi jika Anda mengekstraksi beberapa string kecil, dari kehilangan string besar, maka

   String substring = new String(string.substring(5,23));

Akan membuat daya ingat Anda tetap rendah, karena string besar dapat direklamasi saat tidak lagi diperlukan.

Bahwa Anda menelepon new Stringadalah pengingat yang membantu bahwa Anda benar-benar mendapatkan string baru, daripada referensi ke yang asli.

mdma
sumber
Ada kehalusan untuk menggunakan new String(String); lihat stackoverflow.com/a/390854/8946 .
Lawrence Dol
2

Pertama, panggilan java.lang.String.substringmenciptakan jendela baru pada dokumen asliString dengan penggunaan offset dan panjang alih-alih menyalin bagian penting dari array yang mendasarinya.

Jika kita melihat lebih dekat pada substringmetode ini kita akan melihat panggilan konstruktor stringString(int, int, char[]) dan meneruskannya keseluruhan char[]yang mewakili string . Itu berarti substring akan menempati jumlah memori sebanyak string asli .

Ok, tapi mengapa + ""menghasilkan permintaan untuk memori lebih sedikit daripada tanpa itu ??

Melakukan +aktif stringsdiimplementasikan melalui StringBuilder.appendpemanggilan metode. Lihatlah implementasi metode ini di AbstractStringBuilderkelas akan memberitahu kita bahwa itu akhirnya dilakukan arraycopydengan bagian yang benar-benar kita butuhkan (itu substring).

Ada solusi lain ??

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();
Laika
sumber
0

Menambahkan "" ke string terkadang menghemat memori.

Katakanlah saya memiliki string besar yang berisi seluruh buku, satu juta karakter.

Lalu saya membuat 20 string yang berisi bab-bab buku sebagai substring.

Lalu saya membuat 1000 string yang berisi semua paragraf.

Lalu saya membuat 10.000 string yang berisi semua kalimat.

Lalu saya membuat 100.000 string yang berisi semua kata.

Saya masih menggunakan 1.000.000 karakter. Jika Anda menambahkan "" ke setiap bab, paragraf, kalimat, dan kata, Anda menggunakan 5.000.000 karakter.

Tentu saja sama sekali berbeda jika Anda hanya mengekstrak satu kata dari seluruh buku, dan keseluruhan buku itu bisa menjadi sampah yang dikumpulkan tetapi bukan karena satu kata itu memiliki referensi untuk itu.

Dan lagi berbeda jika Anda memiliki string karakter satu juta dan menghapus tab dan spasi di kedua ujungnya, membuat 10 panggilan untuk membuat substring. Cara kerja atau kerja Java menghindari menyalin satu juta karakter setiap kali. Ada kompromi, dan ada baiknya jika Anda tahu apa kompromi itu.

gnasher729
sumber