java程序员从笨鸟到菜鸟之(二十五)集合之Set接口的子实现类HashSet,TreeSet

Set接口

Set集合继承自Collection集合

类似数学中的集合概念;特点:集合中元素不能重复,且元素无序排列

无序排列理解:元素所在的位置与向集合中添加元素的顺序没有关系

元素不重复原因:底层数据结构是一个哈希表,能保证元素是唯一的,元素不重复!!!
它通过它的接口子实现类HashSet集合去实例化,而HashSet集合底层是HashMap集合的实例!!!

实例1  

package org.westos_01;

import java.util.HashSet;
import java.util.Set;

public class SetDemo {
	/**
	 * 说明问题:
	 * 1)Set集合元素不重复(从打印的内容以及集合元素个数)
	 * 2)集合元素无序(添加的顺序与打印的顺序)
	 * 3)添加、增、删、查询方法的使用---这四个方法的返回值类型都是boolean类型
	 * 4) Set集合存储字符串元素并遍历---for增强循环(jdk5后的版本)
	 */
	public static void main(String[] args) {
		
		//创建Set集合对象
		Set<String> set = new HashSet<String>() ;
		
		//添加元素(增)
		set.add("hello");
		set.add("java") ;
		set.add("java") ;
		set.add("world") ;
		set.add("world") ;
		set.add("world") ;
		System.out.println("集合中添加的元素:"+set);//打印的是内容(原因:传入参数类型是String,底层重写了toString()方法)
		System.out.println("集合中元素的个数:"+set.size());
		//增强for遍历
		for(String s :set){
			System.out.println(s);
		}
		//删除元素(删)
		System.out.println("是否成功删除:"+set.remove("hello"));//删除是否成功
		System.out.println("删除后集合中的元素:"+set);
		//查询
		System.out.println("是否包含此元素:"+set.contains("hello"));
		
	}
}
HashSet类

特点:按照哈希算法(了解这个算法)来存取集合的元素

此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 Set 的迭代顺序;特别是它不保证该顺序恒久不变。

注:HashSet有一个子类:LinkedHashSet类

存取和查找的机理

机理:当向集合中添加一个元素时,HashSet会调用对象的hashCode()方法获取哈希码,然后根据这个哈希码进一步计算出对象在集合中存放的位置。

问题1   为什么能保证HashSet集合元素唯一?

当创建一个HashSet集合的对象时,会先调用其构造方法(蓝框标记)看源码1

即:

 

源码说明:可以看到实际上是创建了一个HashMap类对象的实例,map是HashMap类对象的实例

此时add()添加元素,看对应的源码2


源码说明:其实是调用了HashMap的put方法

看HashMap的put()方法的源码3


源码说明:调用了HashMap中的hash()方法和putVal()方法

看hash方法(重要)的源码4


源码说明:携带hashSet集合元素key,调用对象(传入参数类型)的hashCode()方法,进行位运算;涉及到Object类hashCode()方法重写

看putVal()方法(具体用处不知,后续会提到)

源码5

package 测试集;

import java.util.HashMap.Node;
import java.util.HashMap.TreeNode;

public class Demo {
	//对putVal方法说明
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
		Node<K, V>[] tab;
		Node<K, V> p;
		int n, i;
		
		/* 第一步
		 * 判断哈希表是否存在,如果不存在,创建一个哈希表
		 */
		if ((tab = table) == null || (n = tab.length) == 0)
			/* 当new HashMap实例时,
			 * 并没有初始化其成员变量transient Node<K,V>[] table;,
			 * 也就是说并没有为table分配内存,
			 * 只有当put元素时才通过resize()方法对table进行初始化。
			 * */
			n = (tab = resize()).length;
		if ((p = tab[i = (n - 1) & hash]) == null)
			tab[i] = newNode(hash, key, value, null);
		else {
			Node<K, V> e;
			K k;
			if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
				e = p;
			else if (p instanceof TreeNode)
				e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
			else {
				for (int binCount = 0;; ++binCount) {
					if ((e = p.next) == null) {
						p.next = newNode(hash, key, value, null);
						if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
							treeifyBin(tab, hash);
						break;
					}
					//如果hash码相同,则调用相应的集合元素(元素参数类型)的equals()方法判断,涉及到equals()方法的重写
					if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
						break;
					p = e;
				}
			}
			if (e != null) { // existing mapping for key
				V oldValue = e.value;
				if (!onlyIfAbsent || oldValue == null)
					e.value = value;
				afterNodeAccess(e);
				return oldValue;
			}
		}
		++modCount;//如果没有找到该key的结点,则执行插入操作,需要对modCount增1。 
		if (++size > threshold)
			resize();//在执行插入操作之后,如果size超过了threshold(阈值容量),这要扩容。
		afterNodeInsertion(evict);
		return null;
	}
}
/**
 (1)通过hash值得到所在bucket的下标,如果为null,表示没有发生碰撞,则直接put;散列表中的slot通常称为bucket
 (2)如果发生了碰撞,则解决发生碰撞的实现方式:链表还是树。 
(3)如果没有找到该key的结点,则执行插入操作,需要对modCount增1。 
(5)在执行插入操作时,如果bucket中bin的数量超过TREEIFY_THRESHOLD,则要树化。 
(6)在执行插入操作之后,如果size超过了threshold,这要扩容。
*/

源码说明:根据获取的hashCode进一步计算对象是否重复

源码6----table 哈希表


源码说明:当new HashMap实例时,并没有初始化其成员变量table(也即并没有为table分配内存)。只有当put元素时才通过resize方法对table进行初始化

源码7----resize()方法(随后补充)

相关链接:1 点击打开链接(比较好),2  点击打开链接,3  点击打开链接,4  点击打开链接,5  点击打开链接,6  点击打开链接

  HashSet集合的add()方法,底层是依赖于双列集合HashMap<K,V>的put(K key,V value)来实现的
  put(K key,V value):
   底层又依赖于HashCode()和equals()方法,传递添加元素的时候,首先判断的是
每一个元素对应的HashCode值是否一样,如果HashCode值一样,还比较他们的equals()方法,由于现在集合存储的是String类型,String类型本身重写了equals()方法,所以,默认比较的是内容是否相同,如果内容相同,这里最终返回的就是第一次存储的那个元素,由这两个方法保证元素唯一性!


猜你喜欢

转载自blog.csdn.net/wzj_110/article/details/78517339