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

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

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

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

现在我们就从 Set 说起。

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

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

    public boolean add( E o ) {

returnmap.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 不重复的呢?

现在我们看一下 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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容