当前位置: 首页>编程语言>正文

java索引建立多少个合适 java map索引

Map

      在数组是通过数组下角标来对内容进行索引的,而在Map中是通过对象来对对象进行索引,用来索引的对象叫Key,对应的对象叫Value,就是平时说的键值对。

 

publicinterface Map<K,V>

     

      Map接口是用来取代过时的Dictionary抽象类的,这个接口提供了一种映射关系,即元素是以键值对(K-V)的形式存储的,能够实现根据key快速查找到Value,并且还定义了map的基本行为方法,包括最核心的get和put操作。

      Map常用实现,AbstractMap、HashMap、LinkedHashMap、Hashtable、Properties、TreeMap,为重Hashtable是已过时了的。

      其中是map的key是不重复可为null的,每个key只能映射一个value。map中的元素顺序取决于Iterator的具体实现,即迭代器的顺序,如TreeMap是有序的,HashMap是无序的。

       注:当使用一个可变对象作为key时,map是根据hashCode和equals方法来决定存放的位置,但是有一个特殊的案例是不允许一个map将自己作为一个key,但允许将自己作为一个value。

 

       Map的键值对是以Entry类型的对象实例形式存在的,这个接口是Map接口内的一个内部接口,存储Map中的数据需要实现此接口。

在JDK1.8时,新增了数个比较器的静态方法。

   

interface Entry<K,V> {
       
        K getKey();
 
       
        V getValue();
 
       
        V setValue(V value);
 
       
n equals(Object o);
 
       
        int hashCode();
 
/**
         * @since JDK 1.8
         */
        publicstatic <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getKey().compareTo(c2.getKey());
        }
 
/**
         * @since JDK 1.8
         */
        publicstatic <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getValue().compareTo(c2.getValue());
        }
 
/**
         * @since JDK 1.8
         */
        publicstatic <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
        }
 
/**
         * @since JDK 1.8
         */
        publicstatic <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
        }
    }

AbstractMap

publicabstractclass AbstractMap<K,V> implements Map<K,V>

     

AbstractMap和AbstractList抽象类是类似的,还实现了Map接口,并提供了一些基础实现,如get()、remove()、clear()、put()、putAll()、isEmpty()等,其中put()和putAll()子类必须重写,这里仅仅是抛出异常。

      在使用中,是要实现不可修改的map的话,可以扩展这个类,并为entrySet方法提供一个实现,该方法返回映射的集合视图。通常,返回的集合将依次在AbstractSet之上实现,这个集合不应该支持add或remove方法,它的迭代器也不应该实现remove方法。

      要实现可修改的map,必须另外重写该类的put方法,否则使用put方法会抛出UnsupportedOperationException,而由entrySet().iterator()返回的迭代器,必须另外实现其remove()方法。

 

在查阅源码时,可以发现这个类的方法基本都是通过Entry这个对象来完成的,而获取这个对象的方法时entrySet()。

public V get(Object key) {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        if (key==null) {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)
                    return e.getValue();
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    return e.getValue();
            }
        }
        returnnull;
    }

 

      继续追踪entrySet()方法,发现这却是一个抽象方法,具体实现类需要子类来完成,这是典型的模板方法设计模式,即在一个方法中定义一个算法的骨架,而将一些步骤的实现延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的具体实现。

publicabstract Set<Entry<K,V>> entrySet();

 

      继续跟进下去,AbstractMap中定义了两个用transient修饰的成员变量。

      在JDK7中keySet变量是由volatile修饰的,但在JDK8中并没有使用volatile修饰,并且从注释中可以看到是这样解释的,访问这些字段的方法本身就没有同步,加上volatile也不能保证线程安全。

 

/**
     * Each of these fields are initialized to contain an instance of the
     * appropriate view the first time this view is requested.  The views are
     * stateless, so there's no reason to create more than one of each.
     *
     * <p>Since there is no synchronization performed while accessing these fields,
     * it is expected that java.util.Map view classes using these fields have
     * no non-final fields (or any fields at all except for outer-this). Adhering
     * to this rule would make the races on these fields benign.
     *
     * <p>It is also imperative that implementations read the field only once,
     * as in:
     *
     * <pre> {@code
     * public Set<K> keySet() {
     *   Set<K> ks = keySet;  // single racy read
     *   if (ks == null) {
     *     ks = new KeySet();
     *     keySet = ks;
     *   }
     *   return ks;
     * }
     *}</pre>
     */
    transient Set<K>        keySet;
    transient Collection<V> values;

     

上面源码,注释中还有一段代码,这段代码是描述了key值的存储容器,那么返回key值的Set集合元素的方法,最简单实现,自然是遍历Entry数组取出key值放到Set集合中。

      如果真的是猜测的这样的话,就意味着每次调用keySet方法都会遍历Entry数组,数据量大时,效率会大大降低,可是如果不采取遍历的方式,如何知道Map新增了一个K-V键值对?

      继续阅读源码,会发现有一个keySet方法,这个方法内部重新实现了一个新的自定义Set集合,在这个自定义Set集合中又重写了iterator方法,这里是关键,iterator方法返回Iterator接口,这里又重写实现了Iterator迭代器,通过调用entrySet方法在调用它的iterator方法。

/**
 * {@inheritDoc}
 *
 * @implSpec
 * This implementation returns a set that subclasses {@link AbstractSet}.
 * The subclass's iterator method returns a "wrapper object" over this
 * map's <tt>entrySet()</tt> iterator.  The <tt>size</tt> method
 * delegates to this map's <tt>size</tt> method and the
 * <tt>contains</tt> method delegates to this map's
 * <tt>containsKey</tt> method.
 *
 * <p>The set is created the first time this method is called,
 * and returned in response to all subsequent calls.  No synchronization
 * is performed, so there is a slight chance that multiple calls to this
 * method will not all return the same set.
 */
public Set<K> keySet() {
// 获取到的是transient Set<K> keySet
    Set<K> ks = keySet;
//首次调用会为空,那么自然要创建一个Set
    if (ks == null) {
//创建一个自定义Set
ks = new AbstractSet<K>() {
//重写Set集合的iterator方法
            public Iterator<K> iterator() {
//重写实现Iterator接口
                returnnew Iterator<K>() {
//引用Entry的Set集合Iterator迭代器
                    private Iterator<Entry<K,V>> i = entrySet().iterator();
                   
//对key值判断,就是对entry判断
                    publicboolean hasNext() {
                        returni.hasNext();
                    }
                   
//取下一个key值,就是取entry#getKey
                    publicK next() {
                        returni.next().getKey();
                    }
                   
//删除key值,就是删除entry
                    publicvoid remove() {
                        i.remove();
                    }
                };
            }
           
//重写Set#size方法
            publicint size() {
//key值又多少就是整个Map有多大,所以调用本类的size方法即可。
//这个是内部类直接使用this关键字代表这个类,应该指明是AbstractMap中的size方法没有this则表示static静态方法
                returnAbstractMap.this.size();
            }
           
//重写Set#isEmpty方法
            publicboolean isEmpty() {
//对是否有key值,就是判断Map是否为空,所以调用本类的isEmpty方法即可
                returnAbstractMap.this.isEmpty();
            }
           
//重写Set#clear方法
            publicvoid clear() {
//清空key值,就是清空Map,所以调用本类的clear方法即可
                AbstractMap.this.clear();
            }
           
//重写Set#contains方法
            publicboolean contains(Object k) {
//判断Set是否包含数组K,就是判断Map中是否包含key值,所以调用本类的containsKey方法即可。
                returnAbstractMap.this.containsKey(k);
            }
        };
//将这个自定义的Set集合赋值给变量keySet,在以后再次调用keySet方法时,keySet不为null,只需要直接返回
        keySet = ks;
    }
    return ks;
}

 

      可以看到实际上就是通过一个AbstractSet的匿名内部类,而该内部类的迭代器实际上就是该entrySet()迭代器,只不过在next()的时候调用了getKey(),而其他方法的实现都是调用了AbstractMap中的方法,而values()方法的实现也与这个差不多。

      尽管这个方法是围绕着Key值,但实际上可以结合Entry来实现,而不用遍历Entry,同时提到的entrySet#iterator方法,这里就是之前说的模板方法模式的实践,因为EntrySet在AbstractMap中并未实现,而是交给子类去实现了,但是对于keySet方法却可以对它进行一个算法骨架实现。

      可以看到values()方法也是类似的。

/**
     * {@inheritDoc}
     *
     * @implSpec
     * This implementation returns a collection that subclasses {@link
     * AbstractCollection}.  The subclass's iterator method returns a
     * "wrapper object" over this map's <tt>entrySet()</tt> iterator.
     * The <tt>size</tt> method delegates to this map's <tt>size</tt>
     * method and the <tt>contains</tt> method delegates to this map's
     * <tt>containsValue</tt> method.
     *
     * <p>The collection is created the first time this method is called, and
     * returned in response to all subsequent calls.  No synchronization is
     * performed, so there is a slight chance that multiple calls to this
     * method will not all return the same collection.
     */
    public Collection<V> values() {
        Collection<V> vals = values;
        if (vals == null) {
vals = new AbstractCollection<V>() {
                public Iterator<V> iterator() {
                    returnnew Iterator<V>() {
                        private Iterator<Entry<K,V>> i = entrySet().iterator();
 
                        publicboolean hasNext() {
                            return i.hasNext();
                        }
 
                        public V next() {
                            return i.next().getValue();
                        }
 
                        publicvoid remove() {
i.remove();
                        }
                    };
                }
 
                publicint size() {
                    return AbstractMap.this.size();
                }
 
                publicboolean isEmpty() {
                    return AbstractMap.this.isEmpty();
                }
 
                publicvoid clear() {
                    AbstractMap.this.clear();
                }
 
                publicboolean contains(Object v) {
                    return AbstractMap.this.containsValue(v);
                }
            };
values = vals;
        }
        return vals;
    }

 

      继续阅读源码,AbstractMap重写的equals方法。

publicboolean equals(Object o) {
//如果是本对象返回true
    if (o == this)
        returntrue;
 
//非Map子类的话,直接返回false
    if (!(o instanceof Map))
        returnfalse;
   
//强制将Object o转换为Map,判断是否和本对象size相等,如果不相等直接返回false
//这里的参数用的是?,而不是K-V,是因为泛型在运行时类型会被擦除,编译器不知道具体的K--V是什么类型
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        returnfalse;
 
    try {
//取出每一个元素
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
           
//循环的与传进来的Map的元素进行对比,发现不相等的元素,直接返回false
//所有内容相等了,则跳出循环,返回true
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    returnfalse;
            } else {
                if (!value.equals(m.get(key)))
                    returnfalse;
            }
        }
    } catch (ClassCastException unused) {
        returnfalse;
    } catch (NullPointerException unused) {
        returnfalse;
    }
 
    returntrue;
}

 

      继续阅读源码,AbstractMap重写的hashCode方法。

publicint hashCode() {
    int h = 0;
   
//取出取出集合中的元素
    Iterator<Entry<K,V>> i = entrySet().iterator();
   
//循环累加
    while (i.hasNext())
        h += i.next().hashCode();
   
//返回哈希值累加的结果
    return h;
}

 

      在源码中,这个方法的注释是说是给SimpleEntry和SimpleImmutableEntry使用的,测试是否相等,是否为空。

      虽然这个方法中的o1、o2是Object类型,Object类型的equals方法是通过==比较的引用,这里是没有问题的,因为在实际中,o1类型有可能是String(常量池地址),尽管转为了Object,所以为此时调用equals方法时,还是调用了String#equals方法。

/**
     * Utility method for SimpleEntry and SimpleImmutableEntry.
     * Test for equality, checking for nulls.
     *
     * NB: Do not replace with Object.equals until JDK-8015417 is resolved.
     */
    privatestaticboolean eq(Object o1, Object o2) {
        return o1 == null ? o2 == null : o1.equals(o2);
    }

 

      上面eq方法说到给SimpleEntry、SimpleImmutableEntry这两个类用的,在继续阅读代码会发现,SimpleEntry、SimpleImmutableEntry是属于AbstractMap的静态内部类。

      SimpleEntry定义为可变entry,实现了Serializable接口,是可以被实例化,简化了构建自定义map实现的过程,如可以非常方便的在方法Map.entrySet().toArray中返回SimpleEntry实例的数组。

      其中包含了两个private修饰符的属性,可以看到key被final修饰,而value没有,意味着key只要被初始化后旧不能更改,而value可以。另外,setValue方法是比较特殊的,存入的value返回的并不是存入的值,而是返回以前的旧值。

publicstaticclass SimpleEntry<K,V>
        implements Entry<K,V>, java.io.Serializable
    {
        privatestaticfinallongserialVersionUID = -8499721149061103585L;
 
        privatefinal K key;
        private V value;
/**
     * Replaces the value corresponding to this entry with the specified
     * value.
     *
     * @param value new value to be stored in this entry
     * @return the old value corresponding to the entry
     */
     public V setValue(V value) {
         V oldValue = this.value;
         this.value = value;
         return oldValue;
     }

 

      SimpleImmutableEntry定位可变的entry,也是实现了Serializable接口,不提供setValue方法,在多个线程同时访问时,自然不能进行修改了,相比SimpleEntry的K-V成员变量都被定义为final。

这个类在返回键值映射的线程安全快照方法中会很方便。

      区别SimpleEntry在于setValue方法。

publicstaticclass SimpleImmutableEntry<K,V>
        implements Entry<K,V>, java.io.Serializable
    {
       
        privatefinal K key;
        privatefinal V value;
 
/**
         * Replaces the value corresponding to this entry with the specified
         * value (optional operation).  This implementation simply throws
         * <tt>UnsupportedOperationException</tt>, as this class implements
         * an <i>immutable</i> map entry.
         *
         * @param value new value to be stored in this entry
         * @return (Does not return)
         * @throws UnsupportedOperationException always
         */
        public V setValue(V value) {
            thrownew UnsupportedOperationException();
        }

 


https://www.xamrdz.com/lan/5g71960534.html

相关文章: