Bagaimana cara memperbarui nilai, diberi kunci dalam hashmap?

624

Misalkan kita punya HashMap<String, Integer>di Jawa.

Bagaimana cara memperbarui (kenaikan) nilai integer dari kunci-string untuk setiap keberadaan string yang saya temukan?

Satu bisa menghapus dan masuk kembali pasangan, tetapi overhead akan menjadi perhatian.
Cara lain adalah dengan hanya menempatkan pasangan baru dan yang lama akan diganti.

Dalam kasus terakhir, apa yang terjadi jika ada tabrakan kode hash dengan kunci baru yang saya coba masukkan? Perilaku yang benar untuk hashtable adalah dengan menetapkan tempat yang berbeda untuk itu, atau membuat daftar dari itu dalam ember saat ini.

laertis
sumber

Jawaban:

972
map.put(key, map.get(key) + 1);

harus baik-baik saja. Ini akan memperbarui nilai untuk pemetaan yang ada. Perhatikan bahwa ini menggunakan tinju otomatis. Dengan bantuan map.get(key)kami mendapatkan nilai kunci yang sesuai, maka Anda dapat memperbarui dengan kebutuhan Anda. Di sini saya memperbarui ke nilai tambah sebesar 1.

Matthew Flaschen
sumber
21
Sebenarnya itu adalah solusi perusahaan yang paling kuat dan terukur.
Lavir the Whiolet
12
@ Lavir, ini bukan solusi yang buruk, tapi jangan lihat bagaimana ini yang paling kuat dan terukur. Sebaliknya atomicinteger jauh lebih scalable.
John Vint
13
ini mengasumsikan kuncinya ada, kan? Saya mendapatkan Pengecualian nullPointer ketika tidak.
Ian
84
Dengan Java 8, ini dapat dengan mudah dihindari dengan menggunakan getOrDefault, misalnya:map.put(key, count.getOrDefault(key, 0) + 1);
Martin
2
@Martin .. map.put (key, map.getOrDefault (key, 0) +1)
Sathesh
112

Java 8 way:

Anda dapat menggunakan computeIfPresentmetode dan menyediakannya fungsi pemetaan, yang akan dipanggil untuk menghitung nilai baru berdasarkan yang sudah ada.

Sebagai contoh,

Map<String, Integer> words = new HashMap<>();
words.put("hello", 3);
words.put("world", 4);
words.computeIfPresent("hello", (k, v) -> v + 1);
System.out.println(words.get("hello"));

Alternatifnya, Anda bisa menggunakan mergemetode, di mana 1 adalah nilai default dan fungsi menambah nilai yang ada dengan 1:

words.merge("hello", 1, Integer::sum);

Selain itu, ada banyak metode lain yang berguna, seperti putIfAbsent, getOrDefault, forEach, dll

damluar
sumber
3
Saya baru saja menguji solusi Anda. Yang kedua, yang dengan referensi metode, berfungsi. Yang pertama, ekspresi lambda, tidak berfungsi secara konsisten ketika nilai peta Anda null(katakanlah words.put("hello", null);), hasilnya masih nulltidak 1seperti yang saya harapkan.
Tao Zhang
4
Dari Javadoc: "Jika nilai untuk kunci yang ditentukan ada dan tidak nol, upaya untuk menghitung pemetaan baru". Anda dapat menggunakannya compute()sebagai gantinya, itu akan menangani nullnilai juga.
damluar
Saya ingin menambah nilai saya dengan 1. .mergeadalah solusi saya Integer::sum.
S_K
48
hashmap.put(key, hashmap.get(key) + 1);

Metode ini putakan mengganti nilai kunci yang ada dan akan membuatnya jika tidak ada.

oracleruiz
sumber
55
Tidak, itu tidak membuat, itu memberi nullPointer Exception.
smttsp
13
Kode adalah jawaban yang benar untuk pertanyaan yang diberikan, tetapi diposting setahun setelah kode yang sama persis diposting dalam jawaban yang diterima. Hal yang membedakan jawaban ini adalah menyatakan put dapat membuat entri baru, yang bisa, tetapi tidak dalam contoh ini. Jika Anda menggunakan hashmap.get (kunci) untuk kunci / nilai yang tidak ada, Anda akan mendapatkan nol dan ketika Anda mencoba untuk meningkatkan, seperti @smttsp mengatakan itu akan NPE. -1
Zach
8
Jawaban ini salah. NullPointerException untuk kunci yang tidak ada
Eleanore
@smttp NullpointterException hanya jika Anda tidak menginisialisasi nilai (seperti yang Anda tahu Anda tidak dapat menambah nol)
Mehdi
Duplikasi dan penjelasan yang salah ... dan akan membuatnya jika tidak ada. Anda tidak dapat melakukannya null + 1karena ini akan mencoba membuka kotaknya nullmenjadi bilangan bulat untuk melakukan penambahan.
AxelH
43

Cara Java 8 yang disederhanakan :

map.put(key, map.getOrDefault(key, 0) + 1);

Ini menggunakan metode HashMap yang mengambil nilai untuk kunci, tetapi jika kunci tidak dapat diambil itu mengembalikan nilai default yang ditentukan (dalam hal ini a '0').

Ini didukung dalam Java inti: HashMap <K, V> getOrDefault (Kunci objek, V defaultValue)

Christopher Bull
sumber
3
Ini adalah salah satu yang lebih baik jika Anda berada di Jawa 1,8
Hemant Nagpal
30

Ganti Integeroleh AtomicIntegerdan panggil salah satu dari incrementAndGet/getAndIncrement metode di atasnya.

Alternatifnya adalah dengan membungkus sebuah intdi MutableIntegerkelas Anda sendiri yang memiliki increment()metode, Anda hanya memiliki masalah keamanan benang untuk dipecahkan.

BalusC
sumber
37
AtomicInteger adalah Integer Mutable tetapi builtin. Saya sangat ragu menulis MutableInteger Anda sendiri adalah ide yang lebih baik.
Peter Lawrey
Kustom MutableIntegerlebih baik, seperti AtomicIntegerpenggunaan volatile, yang memiliki overhead. Saya akan menggunakan int[1]sebagai gantinya MutableInteger.
Oliv
@ Eliv: tidak bersamaan.
BalusC
@ BalusC tapi tetap saja, menulis volatile lebih mahal. Itu membatalkan cache. Jika tidak ada perbedaan, semua variabel akan berubah-ubah.
Oliv
@Oliv: pertanyaan secara eksplisit menyebutkan tabrakan kode hash, jadi konkurensi penting untuk OP.
BalusC
19

Solusi satu baris:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);
Punktum
sumber
4
Itu tidak menambahkan sesuatu yang baru pada jawaban yang ada, bukan?
Robert
1
Ya, benar. Benar jawaban yang ditandai akan melempar NullPointerException jika kunci tidak ada. Solusi ini akan bekerja dengan baik.
Hemant Nagpal
18

Solusi @ Matthew adalah yang paling sederhana dan akan tampil cukup baik dalam banyak kasus.

Jika Anda membutuhkan kinerja tinggi, AtomicInteger adalah solusi yang lebih baik ala @BalusC.

Namun, solusi yang lebih cepat (asalkan keselamatan thread tidak menjadi masalah) adalah menggunakan TObjectIntHashMap yang menyediakan metode kenaikan (kunci) dan menggunakan primitif dan objek yang lebih sedikit daripada membuat AtomicIntegers. misalnya

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");
Peter Lawrey
sumber
13

Anda dapat menambahkan seperti di bawah ini tetapi Anda perlu memeriksa keberadaannya agar NullPointerException tidak dibuang

if(!map.containsKey(key)) {
 p.put(key,1);
}
else {
 p.put(key, map.getKey()+1);
}
isuru
sumber
9

Apakah hash ada (dengan 0 sebagai nilainya) atau apakah "dimasukkan" ke peta pada kenaikan pertama? Jika "diletakkan" pada kenaikan pertama, kode tersebut akan terlihat seperti:

if (hashmap.containsKey(key)) {
    hashmap.put(key, hashmap.get(key)+1);
} else { 
    hashmap.put(key,1);
}
sudoBen
sumber
7

Mungkin sedikit terlambat tapi ini dua sen saya.

Jika Anda menggunakan Java 8 maka Anda dapat menggunakan metode computeIfPresent . Jika nilai untuk kunci yang ditentukan ada dan bukan nol maka akan mencoba menghitung pemetaan baru yang diberikan kunci dan nilai yang dipetakan saat ini.

final Map<String,Integer> map1 = new HashMap<>();
map1.put("A",0);
map1.put("B",0);
map1.computeIfPresent("B",(k,v)->v+1);  //[A=0, B=1]

Kita juga dapat menggunakan metode lain putIfAbsent untuk meletakkan kunci. Jika kunci yang ditentukan belum dikaitkan dengan nilai (atau dipetakan ke nol) maka metode ini mengaitkannya dengan nilai yang diberikan dan mengembalikan nol, jika tidak mengembalikan nilai saat ini.

Jika peta dibagikan di seluruh utas maka kita dapat menggunakan ConcurrentHashMapdan AtomicInteger . Dari dokumen:

Sebuah AtomicIntegeradalah nilai int yang dapat diperbarui atom. AtomicInteger digunakan dalam aplikasi seperti penghitung yang bertambah secara atom, dan tidak dapat digunakan sebagai pengganti Integer. Namun, kelas ini memperluas Nomor untuk memungkinkan akses seragam oleh alat dan utilitas yang berhubungan dengan kelas berbasis numerik.

Kita dapat menggunakannya seperti yang ditunjukkan:

final Map<String,AtomicInteger> map2 = new ConcurrentHashMap<>();
map2.putIfAbsent("A",new AtomicInteger(0));
map2.putIfAbsent("B",new AtomicInteger(0)); //[A=0, B=0]
map2.get("B").incrementAndGet();    //[A=0, B=1]

Satu hal yang perlu diperhatikan adalah kita memohon getuntuk mendapatkan nilai untuk kunci Bdan kemudian meminta incrementAndGet()nilainya yang tentu saja AtomicInteger. Kami dapat mengoptimalkannya karena metode putIfAbsentmengembalikan nilai kunci jika sudah ada:

map2.putIfAbsent("B",new AtomicInteger(0)).incrementAndGet();//[A=0, B=2]

Di samping catatan jika kita berencana untuk menggunakan AtomicLong maka sesuai dokumentasi di bawah pertentangan yang tinggi diharapkan throughput LongAdder secara signifikan lebih tinggi, dengan mengorbankan konsumsi ruang yang lebih tinggi. Periksa juga pertanyaan ini .

akhil_mittal
sumber
5

Solusi bersih tanpa NullPointerException adalah:

map.replace(key, map.get(key) + 1);
Sergey Dirin
sumber
5
jika kunci tidak ada maka map.get (kunci) akan melempar NPE
Navi
Ya itu benar
Sergey Dirin
2

Karena saya tidak dapat mengomentari beberapa jawaban karena reputasi yang kurang, saya akan memposting solusi yang saya terapkan.

for(String key : someArray)
{
   if(hashMap.containsKey(key)//will check if a particular key exist or not 
   {
      hashMap.put(hashMap.get(key),value+1);// increment the value by 1 to an already existing key
   }
   else
   {
      hashMap.put(key,value);// make a new entry into the hashmap
   }
}
aayush nigam
sumber
1

Gunakan forloop untuk menambah indeks:

for (int i =0; i<5; i++){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("beer", 100);

    int beer = map.get("beer")+i;
    System.out.println("beer " + beer);
    System.out ....

}
VanHoutte
sumber
3
Itu hanya akan menimpa Peta pada setiap iterasi. Lihat jawaban Matius untuk pendekatan yang benar.
Leigh
1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

atau

Integer i = map.get(key);
map.put(key, i == null ? newValue : i + 1);

Integer adalah tipe data Primitif http://cs.fit.edu/~ryan/java/language/java-data.html , jadi Anda harus mengeluarkannya, membuat beberapa proses, lalu memasangnya kembali. jika Anda memiliki nilai yang bukan tipe data Primitive, Anda hanya perlu mengeluarkannya, memprosesnya, tidak perlu memasukkannya kembali ke dalam hashmap.

Kreedz Zhen
sumber
1
Terima kasih atas cuplikan kode ini, yang dapat memberikan bantuan segera. Penjelasan yang tepat akan sangat meningkatkan nilai pendidikannya dengan menunjukkan mengapa ini adalah solusi yang baik untuk masalah ini, dan akan membuatnya lebih bermanfaat bagi pembaca masa depan dengan pertanyaan yang serupa, tetapi tidak sama. Harap edit jawaban Anda untuk menambahkan penjelasan, dan berikan indikasi batasan dan asumsi apa yang berlaku.
Toby Speight
0

Mencoba:

HashMap hm=new HashMap<String ,Double >();

CATATAN:

String->give the new value; //THIS IS THE KEY
else
Double->pass new value; //THIS IS THE VALUE

Anda bisa mengubah kunci atau nilai dalam hashmap Anda, tetapi Anda tidak bisa mengubah keduanya sekaligus.

NARAYANAN.M
sumber
0

Gunakan Java8 bawaan dalam fungsi 'computeIfPresent'

Contoh:

public class ExampleToUpdateMapValue {

    public static void main(String[] args) {
        Map<String,String> bookAuthors = new TreeMap<>();
        bookAuthors.put("Genesis","Moses");
        bookAuthors.put("Joshua","Joshua");
        bookAuthors.put("Judges","Samuel");

        System.out.println("---------------------Before----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
        // To update the existing value using Java 8
        bookAuthors.computeIfPresent("Judges", (k,v) -> v = "Samuel/Nathan/Gad");

        System.out.println("---------------------After----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
    }
}
Rajesh D
sumber