首先,我们来对比的说一下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
的值(有符号的比较)。从而得知,如果插入的元素比父节点小,返回一个负数,插入父节点 在左边,如果比父节点大,返回正数,插入在父节点右边,如果相等的时候,返回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方法进行比较,如果两个方法都相等
那么,此时两个对象就一定是相同的对象。
相关推荐
同时,源码分析也能帮助我们理解HashMap的扩容机制,以及为什么即使两个对象的hashCode相同,它们仍然可以在HashSet中区分(因为equals()方法的正确实现)。 工具在学习和使用集合框架时也扮演着重要角色。例如,...
本文主要探讨了几个关键的集合接口和实现类的底层源码,包括List、HashMap、HashSet等,以及它们的基本操作。 首先,Collection接口是所有单值集合的父接口,提供了增加、删除、遍历元素的基本方法。例如,`add()`...
HashSet是通过HashMap内部实现的,这意味着它不保证元素的顺序,同时支持高效的添加、查找和删除操作。HashSet内部的元素存储是基于对象的哈希值,这使得元素的插入和查找具有O(1)的时间复杂度。HashSet允许存储一个...
Java集合类源码分析之Set详解 Java集合类中的Set Interface是用于存储无序、不可重复元素的集合接口。Set Interface继承自Collection Interface,提供了基本的集合操作,如add、remove、contains等。Set Interface...
Set接口则包括HashSet、LinkedHashSet和TreeSet等,其中元素无序且不允许重复。 ArrayList是基于动态数组实现的,它提供了快速的随机访问,但插入和删除元素时性能相对较慢,因为需要移动大量元素。LinkedList是双...
2. **add(E e)**:向HashSet添加元素时,实际上是将元素作为键添加到了内部的HashMap中,如果键已经存在,则add操作会返回false。 3. **iterator()**:遍历HashSet元素时,实际上是在遍历内部HashMap的键集合。 4....
总的来说,这个资料包是学习Java集合的宝贵资源,结合例题、源码分析和PPT教学,你可以深入理解Java集合的精髓,提升编程能力,为日后的开发工作打下坚实基础。无论是初学者还是有一定经验的开发者,都可以从中受益...
首先,Set集合是一个不允许重复元素的集合,它有多种实现方式,其中包括HashSet、TreeSet和LinkedHashSet。HashSet是基于HashMap实现的,其元素存储在HashMap的key上,而value使用一个静态的默认对象。为了保证元素...
Java集合框架是Java编程语言中的一个核心部分,它为数据结构和对象的存储、管理和操作提供了统一的接口和实现。这个框架包括了多种类型的集合,如List、Set、Queue和Map,以及它们的各种实现类,如ArrayList、...
学习HashSet的源码可以帮助我们理解无序性和不重复性是如何保证的,以及`add()`、`contains()`和`remove()`等方法的工作原理。同时,我们还可以看到如何通过重写`equals()`和`hashCode()`方法来确保对象的正确存储。...
1. Set:不允许重复的集合,常用的实现类有 HashSet、TreeSet、LinkedHashSet 等。 * HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。 * TreeSet:基于红黑树实现,支持有序性操作,但查找效率不如 ...
这些知识点仅仅是Java容器对象的一部分,实际的博客可能会包含更多细节,如源码分析、性能对比和最佳实践。通过阅读博客中的`持有对象.xmind`文件,可以进一步了解博主对这些概念的详细整理和分类。如果你对Java容器...
HashSet和TreeSet是Set接口的主要实现。 - **HashSet**:基于哈希表实现,插入和查找速度较快,但不保证元素的顺序。元素在集合中的顺序是不确定的。 - **TreeSet**:基于红黑树数据结构,它维护了元素的排序顺序...
在Java开发中,集合框架是不可或缺的一部分,它为我们提供了存储和操作对象的高效工具。"assemble:这是JDK中集合源码分析" 提到的是对Java Development Kit (JDK) 中集合类的深入源码分析。集合框架主要包括List、...
HashSet和TreeSet是两个常见的Set实现: 1. HashSet:基于哈希表(HashMap)实现,插入和查找速度快,但无特定顺序。 2. TreeSet:基于红黑树(Red-Black Tree)实现,提供了排序功能,插入和查找速度稍慢于HashSet...
Set接口不允许重复元素,HashSet、LinkedHashSet和TreeSet是其主要实现。HashSet基于哈希表,提供快速的插入和查找,但不保证元素顺序。LinkedHashSet保持了元素插入的顺序,而TreeSet则使用红黑树,按自然排序或...
源码分析对于理解类集的工作原理至关重要。通过查看源码,开发者可以了解内部的实现细节,比如哈希函数如何计算、链表和数组是如何管理内存的、何时会发生扩容等。这有助于优化代码性能,避免潜在的问题,并且可以...
本文将深入探讨集合框架的使用方法,包括其基本概念、主要类库以及常见操作,同时也会提及一些源码分析和实用工具。 一、集合框架概述 Java集合框架是一个统一的接口体系,它定义了各种数据结构(如列表、队列、...
`Set`接口不包含重复元素,如`HashSet`和`TreeSet`,前者基于哈希表,后者基于红黑树,提供了排序功能。`Map`接口则存储键值对,常见的实现有`HashMap`、`TreeMap`和`LinkedHashMap`,分别对应不同的性能特点和排序...
在源码分析中,我们可以深入理解这些数据结构的实现原理,从而提升我们的编程技能和问题解决能力。以下是关于Java集合框架的一些关键知识点: 1. **ArrayList与LinkedList**:ArrayList基于动态数组实现,适用于...