[英]Java HashMap that takes to much of the memory
問題是我的 hashmap 占用了太多空間。 我想知道代碼是否可以以更有效的方式完成,而不會占用那么多 memory。 我有一個巨大的數組,我使用 HashMap 的原因是因為我想要一種快速的方法來打印出 where key = 3 的第一次出現,如代碼所示。 但現在的問題是 memory。 我仍然希望它相對較快 O(n log n)
ArrayList<String> str = new ArrayList<>();
Map<String, Long> counts2 = new LinkedHashMap<String, Long>();
for(String val : str){
long count = counts2.getOrDefault(val, 0L);
counts2.put(val, ++count);
}
for(String key: counts2.keySet()){
if(counts2.get(key)==3){
System.out.println(list.indexOf(key));
break;
}
}
我只是將它留在這里一點,以了解不該做什么。 今天我了解到僅使用hashcode
進行比較是不夠的。 我認為短路的想法很好,但似乎並不令人擔憂。 HashMap 已經在解決沖突方面做得很好,並且復制的實現可能最終使用與初始版本一樣多的 memory。
相關問題:
Java:為方便起見,在 equals() 中使用 hashCode()?
兩個字符串:相同的哈希碼
對於文本字符串,Java 中什么是好的 64 位 hash function?
原答案如下:
一種方法是存儲 hash 而不是整個字符串:
...
var count = new HashMap<Integer, Long>();
for(String val: list) {
count.put(val.hashCode(), count.getOrDefault(val.hashCode(), 0L)+1);
}
擴展@Alexander的想法,我認為您可以通過保存 hash 和索引而不是普通字符串和重新計數(+短路)來節省空間和計算
所以:
import java.util.*;
class SpaceTime {
public static void main(String ... args) {
var input = Arrays.asList("one", "two", "three", "two", "three", "two");
var map = new HashMap<Integer, CountAndIndex>();
for (int i = 0 ; i < input.size(); i++ ) {
var s = input.get(i);
var hc = s.hashCode();
var cai = map.getOrDefault(hc, startAt(i));
cai.count++;
if (cai.count == 3) {
System.out.printf("We've got it!!. Item: '%s' appears for the first time at index: %d%n", s, cai.index);
break;
}
map.put(hc, cai);
}
}
static CountAndIndex startAt(int index) {
var cai = new CountAndIndex();
cai.count = 0;
cai.index = index;
return cai;
}
}
class CountAndIndex {
long count;
long index;
}
// output:
We've got it!!. Item: 'two' appears for the first time at index: 1
由於您主要關心的是空間,您可能會考慮以下性能權衡,這不需要分配額外的 memory。
for (int i = 1; i < strings.size(); i++) {
String next = strings.get(i);
if (Collections.frequency(strings,next) == 3) {
System.out.println(i);
break;
}
}
您可以在當前的實現上嘗試一些優化。 這些是低成本,快速的勝利:
通過使用適當的構造函數,您可以為 LinkedHashMap 指定初始容量和加載因子。
根據文檔,較高的值會減少空間開銷,但會增加查找成本。 您必須嘗試使用介於0.75
(默認值)和0.99
之間的值才能找到最佳位置。
通過使用較高的值,您可以最大限度地減少由於存儲桶已滿而導致的重新散列。 由於您使用的是 LinkedHashMap,因此大初始容量的影響不太重要,因為迭代時間不受影響。 如果您的用例允許,您甚至可以通過選擇一個足夠大的值來覆蓋所有不同的條目來消除重新散列(即,如果您有關於您計算多少不同鍵的歷史數據,或者您的數據集無論如何都具有有限元素)。 如果您可以最小化/消除重新散列,您還可以最大程度地減少由較大負載因子值引起的任何缺點。
似乎您只需要為每個頻率找到一個密鑰。 如果這是真的,您可以在完成后減少 memory 中保留的數據,並且每個頻率(計數)只保留一個鍵。
Map<String, Long> counts2 = new LinkedHashMap<String, Long>(10_000, 0.95f); //Using the appropriate constructor
for (String val : str) {
long count = counts2.getOrDefault(val, 0L);
counts2.put(val, ++count);
}
// Clean up unneeded (?) entries
final HashMap<Long, Integer> data = new HashMap<>();
for (Iterator<Map.Entry<String, Long>> it = counts2.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Long> entry = it.next();
if (data.containsKey(entry.getValue())) {
it.remove();//Already exists; this will save space
} else {
data.put(entry.getValue(), str.indexOf(entry.getKey()));
}
}
//You can now remove original counts2 now
Integer indexOf3 = data.get(Long.valueOf(3));
System.out.println(str.get(indexOf3) + " @ " + data.get(Long.valueOf(3)));
//Original code
for (String key : counts2.keySet()) {
if (counts2.get(key) == 3) {
System.out.println(key + " @ " + str.indexOf(key));
break;
}
}
獎金說明:
您的用例讓我想起了 Redis 如何優化 memory 對 hashes的使用。 如果您考慮將 Redis 添加到您的堆棧中,這是一種有趣的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.