Java:是不是有一个容器可以有效地结合 HashMap 和 ArrayList?

Posted

技术标签:

【中文标题】Java:是不是有一个容器可以有效地结合 HashMap 和 ArrayList?【英文标题】:Java: Is there a container which effectively combines HashMap and ArrayList?Java:是否有一个容器可以有效地结合 HashMap 和 ArrayList? 【发布时间】:2011-07-08 17:46:48 【问题描述】:

我不断发现需要一个容器,它既是HashMap(用于快速查找键类型)又是ArrayList(用于通过整数索引快速访问)。

LinkedHashMap 几乎是正确的,因为它保留了一个可迭代列表,但不幸的是它是一个链接 列表...检索第 N 个元素需要从 1 迭代到 N。

有没有一种容器类型符合这个要求,但我不知何故错过了它?当其他人需要通过键通过索引访问同一组数据时,他们会怎么做?

【问题讨论】:

您可以将所有对象同时添加到 HashMap 和 ArrayList 中吗? @Blorgbeard,那么当您从地图中删除时会发生什么,从 ArrayList 的中间删除?你能得到的最好的就是 LinkedHashMap 之类的(这是我在 java.util 中最喜欢的数据结构) 到目前为止,我想不出有一次我必须使用索引访问 LinkedHashMap,可能算法/数据结构可以重新设计。例如,如果您尝试基于某个行索引访问 Map,您最好将键也保留在该行中,这样您就可以使用键而不是索引来检索数据。 @bestsss - 是的,你必须从 ArrayList 中删除。因此,您在删除(或插入)时会受到 O(n) 的影响,但在按索引获取时会受到 O(1) 的影响——而 LinkedHashMap 则相反,对吧? @Blorgbeard,是的,当然。对于大 N 来说,它可能会使插入/删除相当重 【参考方案1】:

看看 Apache Commons LinkedMap。

【讨论】:

这正是我认为必须以标准形式存在的东西。它有最重要的 get(int index) 成员。非常感谢! 按名称,我仍然认为 get(index) 查找具有 O(index)(或 O(min(index, n-index)))复杂性。 @Paŭlo,是的,你是对的,它是基于链表实现的。你可以像 LinkedHashMap 一样做。 @Reuben,对于像 get(index) 这样的操作,没有办法实现获得 O(1) 的哈希映射。【参考方案2】:

如果您要删除(在中间)以及通过索引和键访问(这意味着索引正在更改),您可能会看不出来 - 我认为根本不可能有提供O(1) 用于remove(按索引、键或迭代器)和get(index)。这就是为什么我们在标准 API 中同时拥有 LinkedListO(1) 中的 iterator.remove()remove(0))和 ArrayList(O(1) 中的 get(index))。

如果您使用树结构而不是数组或链表(可以与基于 O(1) 键的读取访问相结合 - 获取您的键的索引,则您可以在 O(log n) 中同时进行删除和索引获取不过,-value-pair 仍然需要 O(log n)。

如果您不想删除任何内容,或者可以忍受以下索引未移动(即remove(i) 等同于set(i, null),则没有任何内容禁止同时拥有 O(1) 索引和键访问 - 在事实上,这里的索引只是第二个键,所以你可以简单地使用一个 HashMap 和一个 ArrayList(或两个 HashMap),然后用一个瘦包装器将两者结合起来。


编辑:因此,这里是ArrayHashMap 的实现,如上面最后一段所述(使用“昂贵的删除”变体)。它实现了下面的接口IndexedMap。 (如果你不想在这里复制+粘贴,两者也在我的github account 中,以防以后更改)。

package de.fencing_game.paul.examples;

import java.util.*;

/**
 * A combination of ArrayList and HashMap which allows O(1) for read and
 * modifiying access by index and by key.
 * <p>
 *   Removal (either by key or by index) is O(n), though,
 *   as is indexed addition of a new Entry somewhere else than the end.
 *   (Adding at the end is in amortized O(1).)
 * </p>
 * <p>
 *   (The O(1) complexity for key based operations is under the condition
 *    "if the hashCode() method of the keys has a suitable distribution and
 *     takes constant time", as for any hash-based data structure.)
 * </p>
 * <p>
 *  This map allows null keys and values, but clients should think about
 *  avoiding using these, since some methods return null to show
 *  "no such mapping".
 * </p>
 * <p>
 *   This class is not thread-safe (like ArrayList and HashMap themselves).
 * </p>
 * <p>
 *  This class is inspired by the question
 *    <a href="http://***.com/questions/5192706/java-is-there-a-container-which-effectively-combines-hashmap-and-arraylist">Is there a container which effectively combines HashMap and ArrayList?</a> on ***.
 * </p>
 * @author Paŭlo Ebermann
 */
public class ArrayHashMap<K,V>
    extends AbstractMap<K,V>
    implements IndexedMap<K,V>


    /**
     * Our backing map.
     */
    private Map<K, SimpleEntry<K,V>> baseMap;
    /**
     * our backing list.
     */
    private List<SimpleEntry<K,V>> entries;

    /**
     * creates a new ArrayHashMap with default parameters.
     * (TODO: add more constructors which allow tuning.)
     */
    public ArrayHashMap() 
        this.baseMap = new HashMap<K,SimpleEntry<K,V>>();
        this.entries = new ArrayList<SimpleEntry<K,V>>();
    


    /**
     * puts a new key-value mapping, or changes an existing one.
     *
     * If new, the mapping gets an index at the end (i.e. @link #size()
     * before it gets increased).
     *
     * This method runs in O(1) time for changing an existing value,
     *  amortized O(1) time for adding a new value.
     *
     * @return the old value, if such, else null.
     */
    public V put(K key, V value) 
        SimpleEntry<K,V> entry = baseMap.get(key);
        if(entry == null) 
            entry = new SimpleEntry<K,V>(key, value);
            baseMap.put(key, entry);
            entries.add(entry);
            return null;
        
        return entry.setValue(value);
    

    /**
     * retrieves the value for a key.
     *
     *   This method runs in O(1) time.
     *
     * @return null if there is no such mapping,
     *   else the value for the key.
     */
    public V get(Object key) 
        SimpleEntry<K,V> entry = baseMap.get(key);
        return entry == null ? null : entry.getValue();
    

    /**
     * returns true if the given key is in the map.
     *
     *   This method runs in O(1) time.
     *
     */
    public boolean containsKey(Object key) 
        return baseMap.containsKey(key);
    

    /**
     * removes a key from the map.
     *
     *   This method runs in O(n) time, n being the size of this map.
     *
     * @return the old value, if any.
     */
    public V remove(Object key) 
        SimpleEntry<K,V> entry = baseMap.remove(key);
        if(entry == null) 
            return null;
        
        entries.remove(entry);
        return entry.getValue();
    


    /**
     * returns a key by index.
     *
     *   This method runs in O(1) time.
     *
     */
    public K getKey(int index) 
        return entries.get(index).getKey();
    

    /**
     * returns a value by index.
     *
     *   This method runs in O(1) time.
     *
     */
    public V getValue(int index) 
        return entries.get(index).getValue();
    

    /**
     * Returns a set view of the keys of this map.
     *
     * This set view is ordered by the indexes.
     *
     * It supports removal by key or iterator in O(n) time.
     * Containment check runs in O(1).
     */
    public Set<K> keySet() 
        return new AbstractSet<K>() 
            public void clear() 
                entryList().clear();
            

            public int size() 
                return entries.size();
            

            public Iterator<K> iterator() 
                return keyList().iterator();
            

            public boolean remove(Object key) 
                return keyList().remove(key);
            

            public boolean contains(Object key) 
                return keyList().contains(key);
            
        ;
      // keySet()

    /**
     * Returns a set view of the entries of this map.
     *
     * This set view is ordered by the indexes.
     *
     * It supports removal by entry or iterator in O(n) time.
     *
     * It supports adding new entries at the end, if the key
     * is not already used in this map, in amortized O(1) time.
     *
     * Containment check runs in O(1).
     */
    public Set<Map.Entry<K,V>> entrySet() 
        return new AbstractSet<Map.Entry<K,V>>() 

            public void clear() 
                entryList().clear();
            

            public int size() 
                return entries.size();
            
            public Iterator<Map.Entry<K,V>> iterator() 
                return entryList().iterator();
            
            public boolean add(Map.Entry<K,V> e) 
                return entryList().add(e);
            

            public boolean contains(Object o) 
                return entryList().contains(o);
            

            public boolean remove(Object o) 
                return entryList().remove(o);
            


        ;
      // entrySet()

    /**
     * Returns a list view of the entries of this map.
     *
     * This list view is ordered by the indexes.
     *
     * It supports removal by entry, iterator or sublist.clear in O(n) time.
     * (n being the length of the total list, not the sublist).
     *
     * It supports adding new entries at the end, if the key
     * is not already used in this map, in amortized O(1) time.
     *
     * Containment check runs in O(1).
     */
    public List<Map.Entry<K,V>> entryList() 
        return new AbstractList<Map.Entry<K,V>>() 
            public void clear() 
                baseMap.clear();
                entries.clear();
            
            public Map.Entry<K,V> get(int index) 
                return entries.get(index);
            
            public int size() 
                return entries.size();
            
            public Map.Entry<K,V> remove(int index) 
                Map.Entry<K,V> e = entries.remove(index);
                baseMap.remove(e.getKey());
                return e;
            
            public void add(int index, Map.Entry<K,V> newEntry) 
                K key = newEntry.getKey();
                SimpleEntry<K,V> clone = new SimpleEntry<K,V>(newEntry);
                if(baseMap.containsKey(key)) 
                    throw new IllegalArgumentException("duplicate key " +
                                                       key);
                
                entries.add(index, clone);
                baseMap.put(key, clone);
            

            public boolean contains(Object o) 
                if(o instanceof Map.Entry) 
                    SimpleEntry<K,V> inMap =
                        baseMap.get(((Map.Entry<?,?>)o).getKey());
                    return inMap != null &&
                        inMap.equals(o);
                
                return false;
            

            public boolean remove(Object o) 
                if (!(o instanceof Map.Entry)) 
                    Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                    SimpleEntry<K,V> inMap = baseMap.get(e.getKey());
                    if(inMap != null && inMap.equals(e)) 
                        entries.remove(inMap);
                        baseMap.remove(inMap.getKey());
                        return true;
                    
                
                return false;
            

            protected void removeRange(int fromIndex, int toIndex) 
                List<SimpleEntry<K,V>> subList =
                    entries.subList(fromIndex, toIndex);
                for(SimpleEntry<K,V> entry : subList)
                    baseMap.remove(entry.getKey());
                
                subList.clear();
            

        ;
       // entryList()


    /**
     * Returns a List view of the keys in this map.
     *
     * It allows index read access and key containment check in O(1).
     * Changing a key is not allowed.
     *
     * Removal by key, index, iterator or sublist.clear runs in O(n) time
     * (this removes the corresponding values, too).
     */
    public List<K> keyList() 
        return new AbstractList<K>() 
            public void clear() 
                entryList().clear();
            
            public K get(int index) 
                return entries.get(index).getKey();
            
            public int size() 
                return entries.size();
            
            public K remove(int index) 
                Map.Entry<K,V> e = entries.remove(index);
                baseMap.remove(e.getKey());
                return e.getKey();
            

            public boolean remove(Object key) 
                SimpleEntry<K,V> entry = baseMap.remove(key);
                if(entry == null) 
                    return false;
                
                entries.remove(entry);
                return true;
            

            public boolean contains(Object key) 
                return baseMap.containsKey(key);
            

            protected void removeRange(int fromIndex, int toIndex) 
                entryList().subList(fromIndex, toIndex).clear();
            
        ;
      // keyList()

    /**
     * Returns a List view of the values in this map.
     *
     * It allows get and set by index in O(1) time (set changes the mapping).
     *
     * Removal by value, index, iterator or sublist.clear is possible
     * in O(n) time, this removes the corresponding keys too (only the first
     * key with this value for remove(value)).
     *
     * Containment check needs an iteration, thus O(n) time.
     */
    public List<V> values() 
        return new AbstractList<V>() 
            public int size() 
                return entries.size();
            
            public void clear() 
                entryList().clear();
            
            public V get(int index) 
                return entries.get(index).getValue();
            
            public V set(int index, V newValue) 
                Map.Entry<K,V> e = entries.get(index);
                return e.setValue(newValue);
            

            public V remove(int index) 
                Map.Entry<K,V> e = entries.remove(index);
                baseMap.remove(e.getKey());
                return e.getValue();
            
            protected void removeRange(int fromIndex, int toIndex) 
                entryList().subList(fromIndex, toIndex).clear();
            
        ;
      // values()


    /**
     * an usage example method.
     */
    public static void main(String[] args) 
        IndexedMap<String,String> imap = new ArrayHashMap<String, String>();

        for(int i = 0; i < args.length-1; i+=2) 
            imap.put(args[i], args[i+1]);
        
        System.out.println(imap.values());
        System.out.println(imap.keyList());
        System.out.println(imap.entryList());
        System.out.println(imap);
        System.out.println(imap.getKey(0));
        System.out.println(imap.getValue(0));

    



界面如下:

package de.fencing_game.paul.examples;


import java.util.*;

/**
 * A map which additionally to key-based access allows index-based access
 * to keys and values.
 * <p>
 * Inspired by the question <a href="http://***.com/questions/5192706/java-is-there-a-container-which-effectively-combines-hashmap-and-arraylist">Is there a container which effectively combines HashMap and ArrayList?</a> on ***.
 * </p>
 * @author Paŭlo Ebermann
 * @see ArrayHashMap
 */
public interface IndexedMap<K,V>
    extends Map<K,V>


    /**
     * returns a list view of the @link #entrySet of this Map.
     *
     * This list view supports removal of entries, if the map is mutable.
     *
     * It may also support indexed addition of new entries per the
     *  @link List#add add method - but this throws an
     *  @link IllegalArgumentException if the key is already used.
     */
    public List<Map.Entry<K,V>> entryList();


    /**
     * returns a list view of the @link #keySet.
     * 
     * This list view supports removal of keys (with the corresponding
     * values), but does not support addition of new keys.
     */
    public List<K> keyList();


    /**
     * returns a list view of values contained in this map.
     *
     * This list view supports removal of values (with the corresponding
     * keys), but does not support addition of new values.
     * It may support the @link List#set set operation to change the
     * value for a key.
     */
    public List<V> values();


    /**
     * Returns a value of this map by index.
     *
     * This is equivalent to
     *   @ #values() values().@link List#get get@code (index).
     */
    public V getValue(int index);

    /**
     * Returns a key of this map by index.
     *
     * This is equivalent to
     *   @ #keyList keyList().@link List#get get@code (index).
     */
    public K getKey(int index);


【讨论】:

ŭlo,你知道 LinkedList 的 remove(index) O(1) 有什么好玩的,找到索引本身是 O(n) 遍历链接结构(这比 ArrayList 变体慢) 对于迭代器删除它是 O(1),不过。是的,也应该这样写。【参考方案3】:

你可以自己做,但这里是example implemenation。相应的谷歌搜索词将是“ArrayMap”。

我不确定,但也许 commons 收藏或 google 收藏有这样的地图。

编辑:

您可以创建一个使用 arraylist 实现的 hashmap,即它可以像 LinkedHashMap 一样工作,因为插入顺序定义了列表索引。这将提供快速 get(Index) (O(1)) 和 get(Name) (O(1)) 访问,插入也将是 O(1)(除非必须扩展数组),但删除将是 O (n) 因为删除第一个元素需要更新所有索引。

这个技巧可以通过一个内部保存一个 Map for key-> index 然后是一个 ArrayList 的映射来完成。

get(Key) 将是(没有错误检查的简单示例):

list.get(keyIndexMap.get(key));

【讨论】:

这有一个 O(1) 索引访问(如果我们要添加一个 get(index) 方法),但 get(key) 有一个 O(n) 复杂度。【参考方案4】:

您为什么不简单地保留HashMap,然后按照建议的here 使用hashMap.entrySet().toArray();

【讨论】:

这不是复制吗?我一直认为 toArray() 非常昂贵(至少对于大列表)... 大约是O(n+capacity) - 所以你不会每次都这样做,只有在填满地图之后。它不适合经常变化的地图。

以上是关于Java:是不是有一个容器可以有效地结合 HashMap 和 ArrayList?的主要内容,如果未能解决你的问题,请参考以下文章

Redis 如何高效安全删除大 Hash Key

JAVA中的JPanel怎么使用呢?

java之地址值和hash值的关系

正则表达式是不是有效地搜索 int 列?

Jenkins 结合Azure容器服务

关于编程,你的练习是不是有效的?