2012-02-16 87 views
6

您好我有以下問題: 我存儲字符串和整數值的對應列表中的MultiValueMap<String, Integer> 我存儲大約13 000 0億字符串和一個字符串最多可以有500以上值。 對於每一個值,我都會在地圖上隨機訪問。所以最壞的情況是撥打13000000 * 500個電話。現在地圖的速度很好,但內存開銷很高。一個MultiValueMap<String, Integer>是沒有別的然後HashMap/TreeMap<String, <ArrayList<Integer>>。 HashMap和TreeMap都有相當多的內存開銷。一旦完成,我不會修改地圖,但我需要它快速且儘可能小,以便在程序中進行隨機訪問。 (我將它存儲在磁盤上並在開始時加載它,序列化映射文件佔用大約600mb,但在內存中它大約3GB?)內存高效multivaluemap

最有效的內存效率將字符串存儲在有序字符串數組中並有值的相應的二維int數組。因此訪問將是對字符串數組的二進制搜索並獲取相應的值。

現在我有三種方式前往:

  1. 我使用了一個排序MultivalueMap(TreeMap中)爲創建階段存儲everything.After我與讓所有值完成後,我得到的字符串通過調用map.keyset().toArray(new String[0]);來創建一個二維int數組並獲取多值映射中的所有值。 專業版:易於實現,創建過程中仍然很快。 Con:它在從Map到Arrays的複製過程中佔用了更多的內存。

  2. 我從頭開始使用數組或者ArrayLists,並將所有內容存儲在那裏 Pro:最少的內存開銷。 缺點:這將是極其緩慢的,因爲我將不得不排序/每次添加一個新的密鑰副本的陣列,此外,我需要實現我自己的(propably更慢)排序,以保持相應的int數組以相同的順序像琴絃一樣。難以實現

  3. 我使用數組和MultivalueMap作爲緩衝。在程序完成創建階段的10%或20%之後,我會將這些值添加到數組中並保持它們的順序,然後啓動一個新的Map。 臨:還是足夠快,足夠的內存效率。騙局:很難實施。

這些解決方案都不適合我。你知道這個問題的任何其他解決方案,也許是一個內存有效(MultiValue)地圖實施?

我知道我可以使用一個數據庫,以便不打擾張貼作爲一個答案。我想知道如何在不使用數據庫的情況下做到這一點。

+3

快速問題:500 * 4 * 13,000,000是26,000,000,000字節或+/- 24GB - 您是否考慮將這些數據存儲在堆外? – 2012-02-16 21:34:54

+0

嗨500是最糟糕的情況下估計大多數字符串將只有1或2個值。現在我用-Xmx12g運行程序,但是我在另一個Map中存儲了額外的值。我悲傷的是,Map佔用了大約3g的內存和大約644mb的磁盤空間。 – samy 2012-02-16 21:43:37

+0

Sry我沒有得到off-Heap存儲,我只是用它來搜索,這聽起來很有趣。 – samy 2012-02-16 21:55:02

回答

2

根據其整數值,你在你的地圖儲存,大量的堆內存的開銷可以由具有不同的整數的情況下,其佔用比原始的int值更多的RAM引起的。

考慮使用從String一個Map到許多IntArrayList實現左右浮動的一個(例如在柯爾特原始類別爲Java),其基本上實現,而不是由一個int數組支持的List,由一個Integer實例數組支持。

2

您可以使用compressed strings大幅度減少內存使用情況。

此外,還有other更激烈的解決方法(這將需要一些重新實現):

+0

嗨,感謝這個提示並不知道這個選項,我會試試看,並檢查內存收益。 – samy 2012-02-16 22:41:50

+0

@ user1083775太棒了!將結果發佈給我們會很好,然後我們可以看到它是如何使用和不使用參數的。另外,儘管事實上我猜那些東西沒有那麼多,但是誰知道它可以以其他方式提供幫助,我已經用更多的選項更新了我的答案... – falsarella 2012-02-16 22:53:20

+0

我嘗試了參數,從地圖加載地圖磁盤到內存與:-Xmx16g:2,939.7998428344mb與-Xmx16g -XX:+ UseCompressedStrings:2,715.9821014404做了兩次運行始終相同的結果,所以稍微改善了一點我猜數值或索引結構佔用大部分內存 – samy 2012-02-17 00:03:01

5

如果您切換到番石榴的Multimap之 - 我不知道,如果可能的話你的應用程序 - 你可能能夠使用特羅韋並獲得

ListMultimap<String, Integer> multimap = Multimaps.newListMultimap(
    new HashMap<String, Collection<Integer>>(), 
    new Supplier<List<Integer>>() { 
    public List<Integer> get() { 
     return new TIntListDecorator(); 
    } 
    }); 

這將使一個使用一個ListMultimapHashMap映射到由int[]陣列支持List值,這應該是內存效率,但你會付,因爲拳擊的小速度處罰。您可能可以爲MultiValueMap做類似的事情,但我不知道它來自哪個庫。

2

首先,考慮由整數採取的內存。你說的範圍大概是0-4000000。 24位足以表示16777216個不同的值。如果這是可以接受的,你可以使用字節數組作爲整數,每個整數3個字節,並保存25%。你將不得不索引數組是這樣的:

int getPackedInt(byte[] array, int index) { 
    int i = index*3; 
    return ((array[i] & 0xFF)<<16) + ((array[i+1] & 0xFF) <<8) + (array[i+2] & 0xFF); 
} 
int storePackedInt(byte[] array, int index, int value) { 
    assert value >= 0 && value <= 0xFFFFFF; 
    int i = index*3; 
    array[i] = (byte)((value>>16) & 0xFF); 
    array[i+1] = (byte)((value>>8) & 0xFF); 
    array[i+2] = (byte)(value & 0xFF); 
} 

你能說的整數分配什麼?如果它們中的許多將適合16位,則可以使用每個數字具有可變字節數的編碼(類似UTF-8用於表示字符)。

接下來,考慮是否可以節省絃樂內存。字符串的特點是什麼?他們通常會持續多久?很多字符串會共享前綴嗎?針對您的應用程序特性量身定製的壓縮方案可以節省大量空間(如falsarella指出的那樣)。或者,如果許多字符串將共享前綴,則將它們存儲在某種類型的搜索樹中可能會更有效。 (有一種類型的線索叫做「帕特麗夏」這可能是適合這種應用的。)作爲獎勵,請注意,在搜索特里絃樂可以比搜索一個哈希表(雖然你不得不基準更快看看你的應用程序是否屬實)。

將絃樂都是ASCII?如果是這樣,用於字符串的50%的內存將被浪費,因爲Java char是16位。再次,在這種情況下,你可以考慮使用字節數組。

如果你只需要查看字符串,而不是遍歷存儲的字符串,你也可以考慮一些非常規的東西:散列字符串,並只保留散列。由於不同的字符串可以散列到相同的值,因此可能仍然會通過搜索「找到」一個從未存儲過的字符串。但是如果你使用了足夠的散列值(以及一個好的散列函數),那麼你可以使這個機會變得非常小,以至於在估計的宇宙壽命中幾乎肯定不會發生。

最後,存在用於結構本身,其保持字符串和整數的存儲器。我已經建議使用一個trie,但是如果你決定不這樣做,沒有什麼比平行數組使用更少的內存 - 一個有序的字符串數組(你可以按照你所說的做二進制搜索)和一個並行數組整數數組。在執行二進制搜索以查找字符串數組的索引後,可以使用相同的索引來訪問整數數組。

在構建結構時,如果您確定搜索結果樹是個不錯的選擇,那麼我會直接使用它。否則,你可以做2遍:一個建立一組字符串(然後把它們放入一個數組並排序),第二遍添加整數數組。

+0

範圍是0-40000000(目前是3900萬;維基百科中的artikels數量可以增長)。這些字符串是維基百科的錨文本。有人在評論中提出了一個提示。我會研究這一點。 – samy 2012-02-16 22:34:37

+0

@ user1083775,我只是增加了更多的信息和想法。 – 2012-02-17 05:39:41

2

如果你的關鍵字符串有特定的模式,特別是常見的根,那麼一個Trie可能是一個存儲少得多的數據的有效方法。

下面是工作TrieMap的代碼。

注:使用EntrySet跨越Map s到迭代不適用於Trie S中的常用建議。它們在Trie中效率非常低,所以請儘可能避免請求。

/** 
* Implementation of a Trie structure. 
* 
* A Trie is a compact form of tree that takes advantage of common prefixes 
* to the keys. 
* 
* A normal HashSet will take the key and compute a hash from it, this hash will 
* be used to locate the value through various methods but usually some kind 
* of bucket system is used. The memory footprint resulting becomes something 
* like O(n). 
* 
* A Trie structure essentuially combines all common prefixes into a single key. 
* For example, holding the strings A, AB, ABC and ABCD will only take enough 
* space to record the presence of ABCD. The presence of the others will be 
* recorded as flags within the record of ABCD structure at zero cost. 
* 
* This structure is useful for holding similar strings such as product IDs or 
* credit card numbers. 
* 
*/ 
public class TrieMap<V> extends AbstractMap<String, V> implements Map<String, V> { 


    /** 
    * Map each character to a sub-trie. 
    * 
    * Could replace this with a 256 entry array of Tries but this will handle 
    * multibyte character sets and I can discard empty maps. 
    * 
    * Maintained at null until needed (for better memory footprint). 
    * 
    */ 
    protected Map<Character, TrieMap<V>> children = null; 

    /** 
    * Here we store the map contents. 
    */ 
    protected V leaf = null; 

    /** 
    * Set the leaf value to a new setting and return the old one. 
    * 
    * @param newValue 
    * @return old value of leaf. 
    */ 
    protected V setLeaf(V newValue) { 
    V old = leaf; 
    leaf = newValue; 
    return old; 
    } 

    /** 
    * I've always wanted to name a method something like this. 
    */ 
    protected void makeChildren() { 
    if (children == null) { 
     // Use a TreeMap to ensure sorted iteration. 
     children = new TreeMap<Character, TrieMap<V>>(); 
    } 
    } 

    /** 
    * Finds the TrieMap that "should" contain the key. 
    * 
    * @param key 
    * 
    * The key to find. 
    * 
    * @param grow 
    * 
    * Set to true to grow the Trie to fit the key. 
    * 
    * @return 
    * 
    * The sub Trie that "should" contain the key or null if key was not found and 
    * grow was false. 
    */ 
    protected TrieMap<V> find(String key, boolean grow) { 
    if (key.length() == 0) { 
     // Found it! 
     return this; 
    } else { 
     // Not at end of string. 
     if (grow) { 
     // Grow the tree. 
     makeChildren(); 
     } 
     if (children != null) { 
     // Ask the kids. 
     char ch = key.charAt(0); 
     TrieMap<V> child = children.get(ch); 
     if (child == null && grow) { 
      // Make the child. 
      child = new TrieMap<V>(); 
      // Store the child. 
      children.put(ch, child); 
     } 
     if (child != null) { 
      // Find it in the child. 
      return child.find(tail(key), grow); 
     } 
     } 
    } 
    return null; 

    } 

    /** 
    * Remove the head (first character) from the string. 
    * 
    * @param s 
    * 
    * The string. 
    * 
    * @return 
    * 
    * The same string without the first (head) character. 
    * 
    */ 
    // Suppress warnings over taking a subsequence 
    private String tail(String s) { 
    return s.substring(1, s.length()); 
    } 

    /** 
    * 
    * Add a new value to the map. 
    * 
    * Time footprint = O(s.length). 
    * 
    * @param s 
    * 
    * The key defining the place to add. 
    * 
    * @param value 
    * 
    * The value to add there. 
    * 
    * @return 
    * 
    * The value that was there, or null if it wasn't. 
    * 
    */ 
    @Override 
    public V put(String key, V value) { 
    V old = null; 

    // If empty string. 
    if (key.length() == 0) { 
     old = setLeaf(value); 
    } else { 
     // Find it. 
     old = find(key, true).put("", value); 
    } 

    return old; 
    } 

    /** 
    * Gets the value at the specified key position. 
    * 
    * @param o 
    * 
    * The key to the location. 
    * 
    * @return 
    * 
    * The value at that location, or null if there is no value at that location. 
    */ 
    @Override 
    public V get(Object o) { 
    V got = null; 
    if (o != null) { 
     String key = (String) o; 
     TrieMap<V> it = find(key, false); 
     if (it != null) { 
     got = it.leaf; 
     } 
    } else { 
     throw new NullPointerException("Nulls not allowed."); 
    } 
    return got; 
    } 

    /** 
    * Remove the value at the specified location. 
    * 
    * @param o 
    * 
    * The key to the location. 
    * 
    * @return 
    * 
    * The value that was removed, or null if there was no value at that location. 
    */ 
    @Override 
    public V remove(Object o) { 
    V old = null; 
    if (o != null) { 
     String key = (String) o; 
     if (key.length() == 0) { 
     // Its me! 
     old = leaf; 
     leaf = null; 
     } else { 
     TrieMap<V> it = find(key, false); 
     if (it != null) { 
      old = it.remove(""); 
     } 
     } 
    } else { 
     throw new NullPointerException("Nulls not allowed."); 
    } 
    return old; 
    } 

    /** 
    * Count the number of values in the structure. 
    * 
    * @return 
    * 
    * The number of values in the structure. 
    */ 
    @Override 
    public int size() { 
    // If I am a leaf then size increases by 1. 
    int size = leaf != null ? 1 : 0; 
    if (children != null) { 
     // Add sizes of all my children. 
     for (Character c : children.keySet()) { 
     size += children.get(c).size(); 
     } 
    } 
    return size; 
    } 

    /** 
    * Is the tree empty? 
    * 
    * @return 
    * 
    * true if the tree is empty. 
    * false if there is still at least one value in the tree. 
    */ 
    @Override 
    public boolean isEmpty() { 
    // I am empty if I am not a leaf and I have no children 
    // (slightly quicker than the AbstaractCollection implementation). 
    return leaf == null && (children == null || children.isEmpty()); 
    } 

    /** 
    * Returns all keys as a Set. 
    * 
    * @return 
    * 
    * A HashSet of all keys. 
    * 
    * Note: Although it returns Set<S> it is actually a Set<String> that has been 
    * home-grown because the original keys are not stored in the structure 
    * anywhere. 
    */ 
    @Override 
    public Set<String> keySet() { 
    // Roll them a temporary list and give them a Set from it. 
    return new HashSet<String>(keyList()); 
    } 

    /** 
    * List all my keys. 
    * 
    * @return 
    * 
    * An ArrayList of all keys in the tree. 
    * 
    * Note: Although it returns List<S> it is actually a List<String> that has been 
    * home-grown because the original keys are not stored in the structure 
    * anywhere. 
    * 
    */ 
    protected List<String> keyList() { 
    List<String> contents = new ArrayList<String>(); 

    if (leaf != null) { 
     // If I am a leaf, a null string is in the set. 
     contents.add((String) ""); 
    } 

    // Add all sub-tries. 
    if (children != null) { 
     for (Character c : children.keySet()) { 
     TrieMap<V> child = children.get(c); 
     List<String> childContents = child.keyList(); 
     for (String subString : childContents) { 
      // All possible substrings can be prepended with this character. 
      contents.add((String) (c + subString.toString())); 
     } 
     } 
    } 

    return contents; 
    } 

    /** 
    * Does the map contain the specified key. 
    * 
    * @param key 
    * 
    * The key to look for. 
    * 
    * @return 
    * 
    * true if the key is in the Map. 
    * false if not. 
    */ 
    public boolean containsKey(String key) { 
    TrieMap<V> it = find(key, false); 
    if (it != null) { 
     return it.leaf != null; 
    } 
    return false; 
    } 

    /** 
    * Represent me as a list. 
    * 
    * @return 
    * 
    * A String representation of the tree. 
    */ 
    @Override 
    public String toString() { 
    List<String> list = keyList(); 
    //Collections.sort((List<String>)list); 
    StringBuilder sb = new StringBuilder(); 
    Separator comma = new Separator(","); 
    sb.append("{"); 
    for (String s : list) { 
     sb.append(comma.sep()).append(s).append("=").append(get(s)); 
    } 
    sb.append("}"); 
    return sb.toString(); 
    } 

    /** 
    * Clear down completely. 
    */ 
    @Override 
    public void clear() { 
    children = null; 
    leaf = null; 
    } 

    /** 
    * Return a list of key/value pairs. 
    * 
    * @return 
    * 
    * The entry set. 
    */ 
    public Set<Map.Entry<String, V>> entrySet() { 
    Set<Map.Entry<String, V>> entries = new HashSet<Map.Entry<String, V>>(); 
    List<String> keys = keyList(); 
    for (String key : keys) { 
     entries.add(new Entry<String,V>(key, get(key))); 
    } 
    return entries; 
    } 

    /** 
    * An entry. 
    * 
    * @param <S> 
    * 
    * The type of the key. 
    * 
    * @param <V> 
    * 
    * The type of the value. 
    */ 
    private static class Entry<S, V> implements Map.Entry<S, V> { 

    protected S key; 
    protected V value; 

    public Entry(S key, V value) { 
     this.key = key; 
     this.value = value; 
    } 

    public S getKey() { 
     return key; 
    } 

    public V getValue() { 
     return value; 
    } 

    public V setValue(V newValue) { 
     V oldValue = value; 
     value = newValue; 
     return oldValue; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (!(o instanceof TrieMap.Entry)) { 
     return false; 
     } 
     Entry e = (Entry) o; 
     return (key == null ? e.getKey() == null : key.equals(e.getKey())) 
       && (value == null ? e.getValue() == null : value.equals(e.getValue())); 
    } 

    @Override 
    public int hashCode() { 
     int keyHash = (key == null ? 0 : key.hashCode()); 
     int valueHash = (value == null ? 0 : value.hashCode()); 
     return keyHash^valueHash; 
    } 

    @Override 
    public String toString() { 
     return key + "=" + value; 
    } 
    } 
}