`

TreeSet 和 HashSet如何实现添加无重复对象(源码分析)

 
阅读更多

  首先,我们来对比的说一下set集合和list集合,list集合就好比女生的衣柜,女生的衣柜都非常的整齐,因为女生大多爱好购物,一般衣柜里面有几件相同的衣服,所以list集合的特点就是有序,可以包含重复的元素,有序就是按顺序输出,下面我们来说一下set集合,set集合就好比是一篮鸡蛋,你想呀,一篮子鸡蛋,肯定没有两个相同的鸡蛋,而且,由于鸡蛋的形态,所以,这些鸡蛋都不是很整齐的排放,所以,set集合的特点就是,无序,且集合中没有相同的元素

  下面,让我来说一下,set集合是通过什么实现的集合没有相同的元素的,以TreeSet集合为例子,

  1 首先科普一下,我们查看一下TreeSet集合中的构造方法

  public TreeSet() {
        this(new TreeMap<E,Object>());
    }

   可以清楚的看到,这个集合的底层是一个TreeMap集合

 

2 我们查看TreeSet集合中的add方法

 public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
我们查看PRESENT,发现是这个东西

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    通过上面的构造方法,我们可以证实,Set集合的本质是map,map在存储值的时候,
   它是以键值对的形式进行存储的,通过观察,我们可以发现,它put的时候,
   把对象放在了map键值对中的键上,那么那个值是什么,通过代码的解读,
  认为他是判断是否有重复的元素的,也就是一个boolean。

  

   既然我们知道了,它是把对象放在了TreeMap的键上,那么我们就看一下TreeMap集合是怎么实现键的唯  一性的,我们看一下TreeMap 的put方法

 /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     *
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code key}.)
     * @throws ClassCastException if the specified key cannot be compared
     *         with the keys currently in the map
     * @throws NullPointerException if the specified key is null
     *         and this map uses natural ordering, or its comparator
     *         does not permit null keys
     */


 public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

   不要慌,看我怎么和你解释这个方法,首先讲解一下,TreeMap的底层是一个红黑树(平衡二叉树),讲解一下红色的代码,红色的代码就是在创建一个根节点,我们会可以看一下root是怎么定义的

  private transient Entry<K,V> root = null; 在根据资料进行查询,root就是集合中的根节点,然后,当该集合为空的时候,也就是还没有添加元素,所以这个时候,不需要进行相应的比较,直接添加过去就好,下面我来说一下,在进行比较的时候,有两种比较的方法(① 通过创建自定义比较器进行比较,在创建TreeSet的时候,把相应的的比较器传入 ②使用自然排序进行比较), 假设,我们这里使用的是自然排序进行比较,也就是蓝色的代码,他也就是使用自然排序进行比较,具体比较的方法,看下面的图片

   让我来讲解一下,假设我添加的是一些数字,当我在添加第一个数字20的时候,还没有根节点,这个时候直接添加,然后我们继续执行添加操作,这个时候,添加到了18号元素,我们看Integer的compareTo方法

<!-- Generated by javadoc (build 1.6.0-beta2) on Thu Jan 11 21:30:53 CST 2007 -->

<noscript></noscript>

compareTo

public int compareTo(Integer anotherInteger)
在数字上比较两个 Integer 对象。
指定者:
接口 Comparable<Integer> 中的 compareTo
参数:
anotherInteger - 要比较的 Integer
返回:
如果该 Integer 等于 Integer 参数,则返回 0 值;如果该 Integer 在数字上小于 Integer 参数,则返回小于 0 的值;如果 Integer 在数字上大于 Integer 参数,则返回大于 0 的值(有符号的比较)。
从以下版本开始:
1.2

  从而得知,如果插入的元素比父节点小,返回一个负数,插入父节点 在左边,如果比父节点大,返回正数,插入在父节点右边,如果相等的时候,返回0 ,这个时候也就是两个数字相等,也就是此时的两个Integer对象是相等的时候,我们查看TreeSet里面执行添加的源码在CompareTo返回为0的时候,它是这样进行操纵的return t.setValue(value); 也就是说他没有执行插入的操作,他还是直接保存了原来的节点,我们在查看一下,相应的取出顺序,按着左中右的方式进行取出,想要查看更加详细的存储和取出方式,推荐资料   http://shmilyaw-hotmail-com.iteye.com/blog/1836431    https://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html

 

  接下来,我们说一下通过自定义比较器来鉴别是否是重复的元素

TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                // 姓名长度
                int num = s1.getName().length() - s2.getName().length();
                // 姓名内容
                int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
                        : num;
                // 年龄
                int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
                return num3;
            }
        });

 

通过返回的值,来判断值的大小。

 

 

下面,我们讲解一下HaseSet比较对象是否相同的原因,我们查看HashSet对象源码

1 首先看HashSet 的源码

     public HashSet() {
        map = new HashMap<>();
    }

2   然后查看一下HashSet 的add方法

   public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

3  同理,我们查看HashMap 的 put方法

  public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

    我们在查看一下 Hash 方法,

   final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

  transient int hashSeed = 0;

    首先查看该对象对应的Hash值   然后在哈希表中查找hash值,里面的参数是需要查询的Hash值和hash表的长度

int i = indexFor(hash, table.length); 
这个就是在Hash表中查找他的哈希值

  然后

  for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

  首先for循环的作用就是对象的Hash值去比较Hash表中的所有对象的Hash值,如果Hash值不等的时候,&&运算,直接跳过,否则,通过equals方法进行比较,如果两个方法都相等

 那么,此时两个对象就一定是相同的对象。

 

 

 

 

 

  • 大小: 9.9 KB
0
0
分享到:
评论

相关推荐

    集合的概念及应用和HashSet保证数据不重复的原理

    同时,源码分析也能帮助我们理解HashMap的扩容机制,以及为什么即使两个对象的hashCode相同,它们仍然可以在HashSet中区分(因为equals()方法的正确实现)。 工具在学习和使用集合框架时也扮演着重要角色。例如,...

    对java基础集合部分(List、HashMap、HashSet、ArrayList等)底层源码的分析与总结

    本文主要探讨了几个关键的集合接口和实现类的底层源码,包括List、HashMap、HashSet等,以及它们的基本操作。 首先,Collection接口是所有单值集合的父接口,提供了增加、删除、遍历元素的基本方法。例如,`add()`...

    java集合类源码分析之Set详解.docx

    HashSet是通过HashMap内部实现的,这意味着它不保证元素的顺序,同时支持高效的添加、查找和删除操作。HashSet内部的元素存储是基于对象的哈希值,这使得元素的插入和查找具有O(1)的时间复杂度。HashSet允许存储一个...

    java集合类源码分析之Set详解

    Java集合类源码分析之Set详解 Java集合类中的Set Interface是用于存储无序、不可重复元素的集合接口。Set Interface继承自Collection Interface,提供了基本的集合操作,如add、remove、contains等。Set Interface...

    常见的java集合源码分析,以及面试题

    Set接口则包括HashSet、LinkedHashSet和TreeSet等,其中元素无序且不允许重复。 ArrayList是基于动态数组实现的,它提供了快速的随机访问,但插入和删除元素时性能相对较慢,因为需要移动大量元素。LinkedList是双...

    Java基础学习25.pdf

    2. **add(E e)**:向HashSet添加元素时,实际上是将元素作为键添加到了内部的HashMap中,如果键已经存在,则add操作会返回false。 3. **iterator()**:遍历HashSet元素时,实际上是在遍历内部HashMap的键集合。 4....

    Java-集合的例题 & 例题源码 & PPT教学文档(黑马程序员详细版).rar

    总的来说,这个资料包是学习Java集合的宝贵资源,结合例题、源码分析和PPT教学,你可以深入理解Java集合的精髓,提升编程能力,为日后的开发工作打下坚实基础。无论是初学者还是有一定经验的开发者,都可以从中受益...

    Java源码分析:集合-容器.pdf

    首先,Set集合是一个不允许重复元素的集合,它有多种实现方式,其中包括HashSet、TreeSet和LinkedHashSet。HashSet是基于HashMap实现的,其元素存储在HashMap的key上,而value使用一个静态的默认对象。为了保证元素...

    Java集合框架常用集合源代码及其实现

    Java集合框架是Java编程语言中的一个核心部分,它为数据结构和对象的存储、管理和操作提供了统一的接口和实现。这个框架包括了多种类型的集合,如List、Set、Queue和Map,以及它们的各种实现类,如ArrayList、...

    java 实例源码 集合

    学习HashSet的源码可以帮助我们理解无序性和不重复性是如何保证的,以及`add()`、`contains()`和`remove()`等方法的工作原理。同时,我们还可以看到如何通过重写`equals()`和`hashCode()`方法来确保对象的正确存储。...

    Java 容器.pdf_电子版pdf版

    1. Set:不允许重复的集合,常用的实现类有 HashSet、TreeSet、LinkedHashSet 等。 * HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。 * TreeSet:基于红黑树实现,支持有序性操作,但查找效率不如 ...

    JAVA容器对象整理

    这些知识点仅仅是Java容器对象的一部分,实际的博客可能会包含更多细节,如源码分析、性能对比和最佳实践。通过阅读博客中的`持有对象.xmind`文件,可以进一步了解博主对这些概念的详细整理和分类。如果你对Java容器...

    自己对List,Set,Map等集合类的理解

    HashSet和TreeSet是Set接口的主要实现。 - **HashSet**:基于哈希表实现,插入和查找速度较快,但不保证元素的顺序。元素在集合中的顺序是不确定的。 - **TreeSet**:基于红黑树数据结构,它维护了元素的排序顺序...

    assemble:这是JDK中集合源码分析

    在Java开发中,集合框架是不可或缺的一部分,它为我们提供了存储和操作对象的高效工具。"assemble:这是JDK中集合源码分析" 提到的是对Java Development Kit (JDK) 中集合类的深入源码分析。集合框架主要包括List、...

    第一章 Java常用集合类总览1

    HashSet和TreeSet是两个常见的Set实现: 1. HashSet:基于哈希表(HashMap)实现,插入和查找速度快,但无特定顺序。 2. TreeSet:基于红黑树(Red-Black Tree)实现,提供了排序功能,插入和查找速度稍慢于HashSet...

    第17章 - 深入研究容器 - Collection(List,Set,Queue)的性能测试框架(单线程中)(P501)

    Set接口不允许重复元素,HashSet、LinkedHashSet和TreeSet是其主要实现。HashSet基于哈希表,提供快速的插入和查找,但不保证元素顺序。LinkedHashSet保持了元素插入的顺序,而TreeSet则使用红黑树,按自然排序或...

    最核心的部分 —— 类集

    源码分析对于理解类集的工作原理至关重要。通过查看源码,开发者可以了解内部的实现细节,比如哈希函数如何计算、链表和数组是如何管理内存的、何时会发生扩容等。这有助于优化代码性能,避免潜在的问题,并且可以...

    集合框架的使用方法

    本文将深入探讨集合框架的使用方法,包括其基本概念、主要类库以及常见操作,同时也会提及一些源码分析和实用工具。 一、集合框架概述 Java集合框架是一个统一的接口体系,它定义了各种数据结构(如列表、队列、...

    集合框架的总结

    `Set`接口不包含重复元素,如`HashSet`和`TreeSet`,前者基于哈希表,后者基于红黑树,提供了排序功能。`Map`接口则存储键值对,常见的实现有`HashMap`、`TreeMap`和`LinkedHashMap`,分别对应不同的性能特点和排序...

    java集合源码、设计模式、常用算法、Mysql原理.zip

    在源码分析中,我们可以深入理解这些数据结构的实现原理,从而提升我们的编程技能和问题解决能力。以下是关于Java集合框架的一些关键知识点: 1. **ArrayList与LinkedList**:ArrayList基于动态数组实现,适用于...

Global site tag (gtag.js) - Google Analytics