Pemeriksaan keberadaan utama di HashMap

309

Apakah memeriksa keberadaan kunci di HashMap selalu diperlukan?

Saya memiliki HashMap dengan mengatakan 1000 entri dan saya sedang mencari cara untuk meningkatkan efisiensi. Jika HashMap sedang diakses sangat sering, maka memeriksa keberadaan kunci di setiap akses akan menghasilkan overhead yang besar. Alih-alih jika kunci tidak ada dan karenanya pengecualian terjadi, saya bisa menangkap pengecualian. (ketika saya tahu bahwa ini akan jarang terjadi). Ini akan mengurangi akses ke HashMap hingga setengahnya.

Ini mungkin bukan praktik pemrograman yang baik, tetapi ini akan membantu saya mengurangi jumlah akses. Atau apakah saya melewatkan sesuatu di sini?

[ Pembaruan ] Saya tidak memiliki nilai nol di HashMap.

athena
sumber
8
"karenanya dan pengecualian terjadi" - pengecualian apa? Ini bukan dari java.util.HashMap ...
serg10

Jawaban:

513

Apakah Anda pernah menyimpan nilai nol? Jika tidak, Anda bisa melakukannya:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // No such key
}

Jika tidak, Anda bisa memeriksa keberadaannya jika Anda mendapatkan nilai nol yang dikembalikan:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // Key might be present...
    if (map.containsKey(key)) {
       // Okay, there's a key but the value is null
    } else {
       // Definitely no such key
    }
}
Jon Skeet
sumber
1
@Amuel: Hanya ketika nol adalah nilai yang memungkinkan. Jika Anda benar-benar tidak memiliki nilai nol di peta, getbaik-baik saja, dan jangan melakukan dua pencarian saat Anda membutuhkannya juga.
Jon Skeet
Meskipun ini mungkin lebih jelas sebagai contoh, Anda juga bisa menulis if(value!=null || map.containsKey(key))untuk bagian kedua. Setidaknya jika Anda ingin melakukan hal yang sama - tidak ada kode yang diulang. Ini akan bekerja karena korsleting .
Cullub
66

Anda tidak akan mendapatkan apa-apa dengan memeriksa apakah kuncinya ada. Ini adalah kode dari HashMap:

@Override
public boolean containsKey(Object key) {
    Entry<K, V> m = getEntry(key);
    return m != null;
}

@Override
public V get(Object key) {
    Entry<K, V> m = getEntry(key);
    if (m != null) {
        return m.value;
    }
    return null;
}

Cukup periksa apakah nilai pengembalian get()berbeda null.

Ini adalah kode sumber HashMap.


Sumber:

Colin Hebert
sumber
2
Apa gunanya menunjukkan satu implementasi spesifik dari metode ini?
jarnbjo
2
Untuk menjelaskan bahwa dalam kebanyakan kasus, memeriksa bahwa kunci yang ada akan membutuhkan waktu yang sama daripada mendapatkan nilainya. Jadi tidak akan mengoptimalkan apa pun untuk memeriksa kunci yang benar-benar ada sebelum mendapatkan nilainya. Saya tahu ini generalisasi tetapi bisa membantu untuk memahami.
Colin Hebert
Tautan yang baik adalah grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… (OpenJDK sangat kuat berasal dari kode Sun) dan sepertinya saya salah. Saya membandingkan versi untuk Java5 dengan Java6; mereka bekerja secara berbeda di area ini (tetapi keduanya benar, seperti cuplikan yang Anda poskan).
Donal Fellows
2
Sebagaimana ditunjukkan dalam jawaban yang diterima, pengeluh ini jelas salah. Tentu saja Anda mendapatkan sesuatu dengan memeriksa keberadaan kunci dari membandingkan nilai - Anda dapat membedakan kunci yang tidak ada dari kunci yang ada tetapi dipetakan ke nol sebagai nilai.
Johannes H.
43

Cara yang lebih baik adalah dengan menggunakan containsKeymetode HashMap. Besok seseorang akan menambahkan null ke Peta. Anda harus membedakan antara keberadaan kunci dan kunci memiliki nilai nol.

Programmer Mati
sumber
Ya. Atau subkelas HashMap untuk mencegah penyimpanan sama sekali null.
RobAu
1
1+ untuk tipe primitif karena nilai pemeran yang tidak perlu tidak diperlukan dengan menggunakan jawaban ini
Prashant Bhanarkar
Juga lebih lancar menulis .containsKey () daripada memeriksa nol. Kita seharusnya lebih khawatir tentang mudah dibaca, yang menghemat waktu pengembang, daripada tentang beberapa optimasi kecil, dalam banyak kasus. Setidaknya tidak mengoptimalkan sebelum menjadi perlu.
Maks
23

Maksud Anda kode seperti itu

if(map.containsKey(key)) doSomethingWith(map.get(key))

seluruh tempat ? Maka Anda cukup memeriksa apakah map.get(key)dikembalikan nol dan hanya itu. Omong-omong, HashMap tidak melempar pengecualian untuk kunci yang hilang, melainkan mengembalikan null. Satu-satunya kasus di mana containsKeydiperlukan adalah ketika Anda menyimpan nilai nol, untuk membedakan antara nilai nol dan nilai yang hilang, tetapi ini biasanya dianggap praktik buruk.

Jkff
sumber
8

Cukup gunakan containsKey()untuk kejelasan. Cepat dan menjaga kode tetap bersih dan mudah dibaca. Inti dari HashMaps adalah bahwa pencarian kunci cepat, pastikan hashCode()dan equals()diterapkan dengan benar.

Mikko Wilkman
sumber
4
if(map.get(key) != null || (map.get(key) == null && map.containsKey(key)))
Erlan
sumber
3

Anda juga dapat menggunakan computeIfAbsent()metode di HashMapkelas.

Dalam contoh berikut, mapmenyimpan daftar transaksi (bilangan bulat) yang diterapkan pada kunci (nama rekening bank). Untuk menambahkan 2 transaksi dari 100dan 200ke checking_accountAnda dapat menulis:

HashMap<String, ArrayList<Integer>> map = new HashMap<>();
map.computeIfAbsent("checking_account", key -> new ArrayList<>())
   .add(100)
   .add(200);

Dengan cara ini Anda tidak perlu memeriksa untuk melihat apakah kunci itu checking_accountada atau tidak.

  • Jika tidak ada, seseorang akan dibuat dan dikembalikan oleh ekspresi lambda.
  • Jika ada, maka nilai untuk kunci akan dikembalikan oleh computeIfAbsent().

Sangat elegan! 👍

nazmul idris
sumber
0

Saya biasanya menggunakan idiom

Object value = map.get(key);
if (value == null) {
    value = createValue(key);
    map.put(key, value);
}

Ini berarti Anda hanya menekan peta dua kali jika kuncinya hilang

Jon Freedman
sumber
0
  1. Jika kelas kunci milik Anda, pastikan metode hashCode () dan equals () diimplementasikan.
  2. Pada dasarnya akses ke HashMap harus O (1) tetapi dengan implementasi metode hashCode yang salah itu menjadi O (n), karena nilai dengan kunci hash yang sama akan disimpan sebagai daftar Linked.
Boris
sumber
0

Jawaban Jon Skeet membahas dengan baik dua skenario (peta dengan nullnilai dan bukan nullnilai) dengan cara yang efisien.

Tentang entri angka dan masalah efisiensi, saya ingin menambahkan sesuatu.

Saya memiliki HashMap dengan mengatakan 1.000 entri dan saya sedang mencari cara untuk meningkatkan efisiensi. Jika HashMap sedang diakses sangat sering, maka memeriksa keberadaan kunci di setiap akses akan menghasilkan overhead yang besar.

Peta dengan 1.000 entri bukan peta besar.
Serta peta dengan 5.000 atau 10.000 entri.
Mapdirancang untuk melakukan pengambilan cepat dengan dimensi seperti itu.

Sekarang, diasumsikan bahwa hashCode()kunci peta menyediakan distribusi yang baik.

Jika Anda dapat menggunakan Integerjenis kunci sebagai, lakukan.
Its hashCode()metode ini sangat efisien karena tabrakan tidak mungkin untuk unik intnilai-nilai:

public final class Integer extends Number implements Comparable<Integer> {
    ...
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    public static int hashCode(int value) {
        return value;
    }
    ...
}

Jika untuk kuncinya, Anda harus menggunakan tipe bawaan lain seperti Stringmisalnya yang sering digunakan Map, Anda mungkin memiliki beberapa tabrakan tetapi dari 1.000 hingga ribuan objek di Mapdalamnya, Anda harus memilikinya sangat sedikit sebagai String.hashCode()metode memberikan distribusi yang baik.

Jika Anda menggunakan jenis khusus, timpa hashCode()dan equals()dengan benar dan pastikan keseluruhan yang hashCode()menyediakan distribusi yang adil.
Anda dapat merujuk ke item 9 dari Java Effectivemerujuknya.
Inilah pos yang merinci caranya.

davidxxx
sumber