Petua #1: Ramalkan kapasiti koleksi
Semua koleksi Java standard, termasuk pelaksanaan tersuai dan lanjutan (seperti Trove dan Guava Google), menggunakan tatasusunan (sama ada jenis data asli atau jenis berasaskan objek) di bawah hud. Kerana sebaik sahaja tatasusunan diperuntukkan, saiznya tidak boleh diubah, jadi menambahkan elemen pada koleksi dalam kebanyakan kes akan mengakibatkan keperluan untuk memohon semula tatasusunan berkapasiti besar baharu untuk menggantikan tatasusunan lama (merujuk kepada tatasusunan yang digunakan oleh pelaksanaan asas koleksi).
Walaupun saiz untuk permulaan koleksi tidak disediakan, kebanyakan pelaksanaan koleksi cuba mengoptimumkan pemprosesan pengagihan semula tatasusunan dan melunaskan overhednya kepada minimum. Walau bagaimanapun, hasil terbaik boleh diperoleh dengan menyediakan saiz semasa membina koleksi.
Mari analisa kod berikut sebagai contoh mudah:
terbalik Senarai statik awam(Senarai & lt; ? memanjangkan senarai T & gt;) {
Hasil senarai = new ArrayList();
untuk (int i = list.size() - 1; i & gt; = 0; i--) {
result.add(list.get(i));
}
pulangkan hasil;
}
Kaedah ini memperuntukkan tatasusunan baharu, kemudian mengisinya dengan item daripada senarai lain, hanya dalam susunan terbalik.
Kaedah pemprosesan ini mungkin membayar kos prestasi yang tinggi, dan titik pengoptimuman ialah baris kod yang menambah elemen pada senarai baharu. Apabila setiap elemen ditambah, senarai perlu memastikan tatasusunan asasnya mempunyai ruang yang mencukupi untuk menampung elemen baharu. Jika terdapat slot percuma, elemen baharu hanya disimpan dalam slot percuma seterusnya. Jika tidak, tatasusunan asas baharu diperuntukkan, kandungan tatasusunan lama akan disalin ke tatasusunan baharu, dan elemen baharu ditambah. Ini akan menyebabkan tatasusunan diperuntukkan beberapa kali, dan tatasusunan lama yang tinggal akhirnya akan dituntut semula oleh GC.
Kita boleh mengelakkan peruntukan berlebihan ini dengan membiarkan tatasusunan asasnya mengetahui berapa banyak elemen yang akan disimpan semasa membina koleksi
terbalik Senarai statik awam(Senarai & lt; ? memanjangkan senarai T & gt;) {
Hasil senarai = ArrayList baharu(list.size());
untuk (int i = list.size() - 1; i & gt; = 0; i--) {
result.add(list.get(i));
}
pulangkan hasil;
}
Kod di atas menentukan ruang yang cukup besar untuk menyimpan elemen list.size() melalui pembina ArrayList, dan melengkapkan peruntukan semasa pemulaan, yang bermaksud bahawa Senarai tidak perlu memperuntukkan memori sekali lagi semasa proses lelaran.
Kelas koleksi jambu batu melangkah lebih jauh, membolehkan anda menentukan secara eksplisit bilangan elemen yang dijangkakan atau menentukan nilai yang diramalkan apabila memulakan koleksi.
1
2Hasil senarai = Lists.newArrayListWithCapacity(list.size());
Hasil senarai = Lists.newArrayListWithExpectedSize(list.size());
Dalam kod di atas, yang pertama digunakan apabila kita sudah mengetahui dengan tepat berapa banyak elemen yang akan disimpan oleh koleksi, manakala yang kedua diperuntukkan dengan cara yang mengambil kira anggaran yang salah.
Petua #2: Kendalikan strim data secara terus
Apabila berurusan dengan aliran data, seperti membaca data daripada fail atau memuat turun data daripada rangkaian, kod berikut adalah sangat biasa:
1bait[] fileData = readFileToByteArray(new File("myfile.txt"));
Tatasusunan bait yang terhasil boleh dihuraikan sebagai dokumen XML, objek JSON atau mesej buffer protokol, dengan beberapa pilihan biasa tersedia.
Pendekatan di atas adalah tidak bijak apabila berurusan dengan fail besar atau fail dengan saiz yang tidak dapat diramalkan, kerana OutOfMemoryErrors akan terhasil apabila JVM tidak boleh memperuntukkan penimbal untuk memproses fail sebenar.
Walaupun saiz data boleh diurus, menggunakan corak di atas masih akan menyebabkan overhed yang besar apabila melibatkan pengumpulan sampah kerana ia memperuntukkan kawasan yang sangat besar dalam timbunan untuk menyimpan data fail.
Cara yang lebih baik untuk mengendalikan perkara ini ialah menggunakan InputStream yang sesuai (seperti FileInputStream dalam contoh ini) untuk dihantar terus ke penghurai, dan bukannya membaca keseluruhan fail ke dalam tatasusunan bait sekali gus. Semua perpustakaan sumber terbuka arus perdana menyediakan API yang sepadan untuk menerima secara langsung aliran input untuk pemprosesan, seperti:
FileInputStream fis = baru FileInputStream(Nama Fail);
MyProtoBufMessage msg = MyProtoBufMessage.parseFrom(fis);
Petua #3: Gunakan objek tidak berubah
Ketidak ubah mempunyai begitu banyak faedah. Saya tidak perlu pergi ke butiran. Namun begitu, ada kelebihan yang memberi kesan kepada kutipan sampah yang harus dilihat.
Sifat objek tidak boleh ubah tidak boleh diubah suai selepas objek dibuat (contoh di sini menggunakan sifat jenis data rujukan), seperti:
kelas awam ObjectPair {
Objek akhir peribadi dahulu;
Objek akhir peribadi kedua;
public ObjectPair(Objek pertama, Objek kedua) {
ini.pertama = pertama;
this.second = second;
}
public Object getFirst() {
balik dulu;
}
public Object getSecond() {
kembali kedua;
}
}
Menghidupkan kelas di atas akan menghasilkan objek tidak berubah - semua sifatnya diubah suai dengan muktamad dan tidak boleh diubah selepas pembinaan selesai.
Ketidakbolehubah bermaksud bahawa semua objek yang dirujuk oleh bekas tidak berubah dicipta sebelum bekas itu dibina. Setakat GC berkenaan: bekas itu sekurang-kurangnya semuda rujukan termuda yang dipegangnya. Ini bermakna apabila melakukan kutipan sampah dalam generasi muda, GC melangkau objek tidak berubah kerana ia berada dalam generasi lama Ia tidak melengkapkan pengumpulan objek tidak berubah sehingga ditentukan bahawa objek tidak berubah ini tidak dirujuk oleh mana-mana objek dalam. generasi lama.
Lebih sedikit objek imbasan bermakna lebih sedikit imbasan halaman memori, yang bermaksud jangka hayat GC yang lebih pendek, yang bermaksud jeda GC yang lebih pendek dan pemprosesan keseluruhan yang lebih baik.
Petua #4: Berhati-hati dengan penggabungan rentetan
Rentetan mungkin merupakan struktur data bukan asli yang paling biasa digunakan dalam semua aplikasi berasaskan JVM. Walau bagaimanapun, disebabkan overhed tersirat dan kemudahan penggunaan, ia adalah sangat mudah untuk menjadi punca mengambil banyak memori.
Masalahnya jelas bukan dengan rentetan literal, tetapi dengan pemulaan memori yang diperuntukkan semasa runtime. Mari kita lihat sekilas contoh membina rentetan secara dinamik:
String statik awam kepadaString(T[] tatasusunan) {
Keputusan rentetan = "[";
untuk (int i = 0; i & lt; array.length; i++) {
hasil += (tatasusunan[i] == tatasusunan ? "ini" : tatasusunan[i]);
if (i & lt; array.length - 1) {
hasil += ", ";
}
}
hasil += "]";
pulangkan hasil;
}
Ini kelihatan seperti kaedah yang baik, mengambil pelbagai aksara dan mengembalikan rentetan. Tetapi ini adalah bencana untuk peruntukan memori objek.
Sukar untuk melihat di sebalik gula sintaksis ini, tetapi realiti di sebalik tabir adalah ini:
String statik awam kepadaString(T[] tatasusunan) {
Keputusan rentetan = "[";
untuk (int i = 0; i & lt; array.length; i++) {
StringBuilder sb1 = StringBuilder baharu(hasil);
sb1.append(array[i] == array ? "this" : array[i]);
hasil = sb1.toString();
if (i & lt; array.length - 1) {
StringBuilder sb2 = StringBuilder baharu(hasil);
sb2.append(", ");
hasil = sb2.toString();
}
}
StringBuilder sb3 = StringBuilder baharu(hasil);
sb3.append("]");
hasil = sb3.toString();
pulangkan hasil;
}
Rentetan tidak boleh diubah, yang bermaksud bahawa setiap kali penggabungan berlaku, ia sendiri tidak diubah suai, tetapi rentetan baharu diperuntukkan secara bergilir. Selain itu, pengkompil menggunakan kelas StringBuilder standard untuk melaksanakan operasi penggabungan ini. Ini bermasalah kerana setiap lelaran secara tersirat memperuntukkan rentetan sementara dan objek StringBuilder sementara untuk membantu membina hasil akhir.
Cara terbaik adalah untuk mengelakkan situasi di atas dan menggunakan StringBuilder dan tambah terus dan bukannya pengendali penggabungan asli ("+"). Berikut ialah contoh:
String statik awam kepadaString(T[] tatasusunan) {
StringBuilder sb = StringBuilder baharu("[");
untuk (int i = 0; i & lt; array.length; i++) {
sb.append(array[i] == array ? "this" : array[i]);
if (i & lt; array.length - 1) {
sb.append(", ");
}
}
sb.append("]");
kembalikan sb.toString();
}
Di sini, kami memperuntukkan satu-satunya StringBuilder pada permulaan kaedah. Pada ketika ini, semua rentetan dan elemen senarai telah dilampirkan pada StringBuilder tunggal. Akhir sekali, gunakan kaedah toString() untuk menukarnya menjadi rentetan dan mengembalikannya sekali gus.
Petua #5: Gunakan koleksi jenis asli tertentu
Pustaka koleksi standard Java adalah ringkas dan menyokong generik, membenarkan pengikatan jenis separa statik apabila menggunakan koleksi. Contohnya, jika anda ingin mencipta Set yang hanya menyimpan rentetan atau peta yang menyimpan Map
Masalah sebenar timbul apabila kita ingin menggunakan senarai untuk menyimpan jenis int, atau peta untuk menyimpan jenis berganda sebagai nilai. Oleh kerana generik tidak menyokong jenis data asli, pilihan lain ialah menggunakan jenis pembalut sebaliknya, di sini kami menggunakan List .
Kaedah pemprosesan ini sangat membazir, kerana Integer ialah objek lengkap Pengepala objek menduduki 12 bait dan sifat int dikekalkan di dalamnya. Setiap objek Integer menduduki sejumlah 16 bait. Ini menggunakan ruang empat kali lebih banyak daripada senarai jenis int yang menyimpan bilangan item yang sama! Masalah yang lebih serius daripada ini ialah hakikat bahawa kerana Integer ialah contoh objek sebenar, ia perlu dipertimbangkan oleh pemungut sampah semasa fasa pengumpulan sampah untuk dikitar semula.
Untuk menangani perkara ini, kami menggunakan perpustakaan koleksi Trove yang hebat di Takipi. Trove meninggalkan beberapa kekhususan generik dan memihak kepada koleksi khusus jenis asli yang lebih cekap memori. Contohnya, kami menggunakan Map
Peta TIntDoubleMap = TIntDoubleHashMap();
map.put(5, 7.0);
map.put(-1, 9.999);
...
Pelaksanaan asas Trove menggunakan tatasusunan jenis asli, jadi apabila mengendalikan koleksi, tinju (int->Integer) atau nyahboxing (Integer->int) elemen tidak akan berlaku dan tiada objek disimpan, kerana pelaksanaan asas menggunakan Storan jenis data asli.
Atas ialah kandungan terperinci Bagaimana untuk mengurangkan overhed kutipan sampah Java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!