Set中的元素为什么不允许重复

 版权所有,转载请声明出处 zhyiwww@163.com

 

为了弄清楚这个问题 , 我又看了一遍 Collection 部分 , 并且看了些其中的源码 , 觉得对其中的实现又明白了一点 , 现在说出来和大家共享 .

我们先看一下 Set 类的关系图: 

 

hashset.png​ 
现在我们就从 Set 说起。

Set 接口为我们提供了一个 add() 方法,以让我们添加元素。所以我们看一下在其实现类 HashSet 中是如何实现的呢?

我们看 HashSet 中的 add() 方法实现;

    public boolean add( E o ) {

              return  map.put(o, PRESENT)==null;

}

你可能回问怎么回出来 map 了呢?

Map 又是一个什么变量呢?

我们来看 map 是在在哪定义的。原来,在 HashSet 中定义了这样的两个变量:

    private transient HashMap<E,Object> map;

    private static final Object PRESENT = new Object();

我们再看一下上面的 add() 方法。

map.put(o, PRESENT)==null

实际执行的是 map 的方法,并且我们添加的对象是 map 中的 key,value 是执行的同一个对象PRESENT.

因为map中的key是不允许重复的,所以set中的元素不能重复。

 

下面我们再看一下, map 又是如何保证其 key 不重复的呢?

 hashmap.png

现在我们看一下 map 中的 put() 方法的实现:

HashMap 实现了 map ,在 HashMap 中是这样实现的:

    public V put(K key, V value) {

      K k = maskNull(key);

        int hash = hash(k);

        int i = indexFor(hash, table.length);

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {

            if (e.hash == hash && eq(k, e.key)) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

 

        modCount++;

        addEntry(hash, k, value, i);

        return null;

    }

我们我们按照方法的执行一步一步的来看一下,其实现的过程。

K k = maskNull(key);

这一步是要判断当前的要添加的对象的 key 是否为空,如果为空的话,那么就生成一个新的对象作为其 key 。实现如下:

    static final Object NULL_KEY = new Object();

     * Returns internal representation for key. Use NULL_KEY if key is null.

    static <T> T maskNull(T key) {

        return key == null ? (T)NULL_KEY : key;

    }

之后

int hash = hash(k);

我们看一下 hash() 方法的实现:

    static int hash(Object x) {

        int h = x.hashCode();

        h += ~(h << 9);

        h ^=  (h >>> 14);

        h +=  (h << 4);

        h ^=  (h >>> 10);

        return h;

    }

这一步其实是要得到当前要添加对象的 hashcode, 方法中,首先通过int h = x.hashCode() 取得了当前的要添加对象的 hashcode, 然后

        h += ~(h << 9);

        h ^=  (h >>> 14);

        h +=  (h << 4);

        h ^=  (h >>> 10);

生成一个新的 hashcode.

接着执行的是

(从此处开始,我理解的比较肤浅,请看到此出的朋友多多指点)

int i = indexFor(hash, table.length);

这个方法是要 Returns index for hash code h.

    static int indexFor(int h, int length) {

        return h & (length-1);

    }

      下面就要根据 hashmap 中的元素逐个的比较,看是否相同,如果不同就回添加一个新的元素。是通过循环判断来实现的。

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {

            if (e.hash == hash && eq(k, e.key)) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

这句我的理解是:在内存中的可以访问元素又多了一个。也就是说,添加之后,可以通过hashmap来访问此元素了。

                return oldValue;

            }

        }

通过循环判断是否有完全相同的对象,包括 hashcode 和 value 值。如果已经存在就返回其值,如果不存在就执行一下操作。

        modCount++;

        addEntry(hash, k, value, i);

        return null;

对象不存在,首先修改 hashmap 的修改次数,即 modCount 加 1. 然后将对象添加到数组中。

    void addEntry(int hash, K key, V value, int bucketIndex) {

      Entry<K,V> e = table[bucketIndex];

        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

        if (size++ >= threshold)

            resize(2 * table.length);

}

仍然是数组,我们来看一下 , 在 hashmap 中用来存放对象的数组的定义:

    transient Entry[] table;

至此,我想大家也许应该明白了,为什么在 set 中是不允许存放重复值的。

通过才的分析,我们可以看到, map 的一些特征:

1.       在 map 中也是不能存放完全相同的元素的

2.       如果你存入的对象的 key 值已经存在的话,那么新的 value 将会取代老的 value 值,但是并不会添加新的元素进去。

我们可以通过一个测试程序来证明这一点:

  public void mapTest() {

    Map m = new HashMap();

    /**

     * we  can  put  the  int  1 and the  value  1  into  the  map

     * but  we  cannot  get  the  1 as  int  from the map

     * why ?

     * we  only  get  the  1  as  integer  from  the map,

     * so  we  only  get the object  from the map,if we  put the  value into

     * the map,we  will get one  instance of  the wrapper  of  that

     */

    m.put(1, 1);

    //int i = m.get(1);

    Integer i = (Integer) m.get(1);

    System.out.println(i);

 

    m.put(1, 1);

    m.put(1, 1);

    System.out.println("map   :    " + m);

    System.out.println("map  size    :   " + m.size());

    m.put(1, 2);

    System.out.println("map   :    " + m);

    System.out.println("map  size    :   " + m.size());

  }

运行的结果是:

map   :    {1=1}

map  size    :   1

map   :    {1=2}

map  size    :   1

 

希望此文能大家有所帮助。

posted @ 2020-04-18 09:19  那些年的代码  阅读(2482)  评论(0编辑  收藏  举报