HashSet
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
HashSet源码:
常量: 第一个定义一个 HashMap,作为实现 HashSet 的数据结构;第二个 PRESENT 对象,因为前面讲过 HashMap 是作为键值对 key-value 进行存储的,而 HashSet 不是键值对,那么选择 HashMap 作为实现,其原理就是存储在 HashSet 中的数据 作为 Map 的 key,而 Map 的value 统一为 PRESENT
HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束。
HashSet要求放入的对象必须实现HashCode()方法,放入的对象**,是以hashcode码作为标识的**,而具有相同内容的 String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例 。
SortedSet
SortedSet继承自Set,他根据对象的比较顺序(可以是自然顺序,也可以是自定义的顺序),而不是插入顺序进行排序;用LinkedHashSet,维护的是插入时的顺序;
TreeSet是SortedSet的唯一实现类,红黑树实现,树形结构,它的本质可以理解为是有序,无重复的元素的集合。
因为都是有序的,所以相应的就有get,remove和add方法。
TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不允许放入null值。
Map
HashMap
是一个“链表散列”的数据结构,即数组和链表的结合体
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
public V put(K key, V value) {
// HashMap允许存放null键和null值。
// 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
if (key == null)
return putForNullKey(value);
// 根据key的keyCode重新计算hash值。
int hash = hash(key.hashCode());
// 搜索指定hash值在对应table中的索引。
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
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;
}
}
// 如果i索引处的Entry为null,表明此处还没有Entry。
modCount++;
// 将key、value添加到i索引处。
addEntry(hash, key, value, i);
return null;
}
/**
* 当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
* 如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。addEntry(hash, key, value, i)方法根据计算出的hash值,将key-value对放在数组table的i索引处。
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
// 获取指定 bucketIndex 索引处的 Entry
Entry<K,V> e = table[bucketIndex];
// 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
// 如果 Map 中的 key-value 对的数量超过了极限
if (size++ >= threshold)
// 把 table 对象的长度扩充到原来的2倍。
resize(2 * table.length);
}
//当系统决定存储HashMap中的key-value对时,完全没有考虑Entry中的value,仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。
/**
* 在HashMap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。但是HashMap的数据结构是数组和链表的结合,所以我们希望这个HashMap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表,这样就大大优化了查询的效率。
* 对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方法所计算得到的 hash 码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,在HashMap中是这样做的:调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。
*
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
//这个方法非常巧妙,它通过 h & (table.length -1) 来得到该对象的保存位,而HashMap底层数组的长度总是 2 的 n 次方,这是HashMap在速度上的优化。
//在 HashMap 构造器中有如下代码:
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
//这段代码保证初始化时HashMap的容量总是2的n次方,即底层数组的长度总是为2的n次方。当length总是 2 的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。
HashMap 是基于“拉链法”实现的散列表。一般用于单线程程序中。
Hashtable 也是基于“拉链法”实现的散列表。它一般用于多线程程序中。
WeakHashMap 也是基于“拉链法”实现的散列表,它一般也用于单线程程序中。相比HashMap,WeakHashMap中的键是“弱键”,当“弱键”被GC回收时,它对应的键值对也会被从WeakHashMap中删除;而HashMap中的键是强键。
TreeMap 是有序的散列表,它是通过红黑树实现的。它一般用于单线程中存储有序的映射。
HashMap和Hashtable的相同点
HashMap和Hashtable都是存储“键值对(key-value)”的散列表,而且都是采用拉链法实现的。
存储的思想都是:通过table数组存储,数组的每一个元素都是一个Entry;而一个Entry就是一个单向链表,Entry链表中的每一个节点就保存了key-value键值对数据。
添加key-value键值对:首先,根据key值计算出哈希值,再计算出数组索引(即,该key-value在table中的索引)。然后,根据数组索引找到Entry(即,单向链表),再遍历单向链表,将key和链表中的每一个节点的key进行对比。若key已经存在Entry链表中,则用该value值取代旧的value值;若key不存在Entry链表中,则新建一个key-value节点,并将该节点插入Entry链表的表头位置。
删除key-value键值对:删除键值对,相比于“添加键值对”来说,简单很多。首先,还是根据key计算出哈希值,再计算出数组索引(即,该key-value在table中的索引)。然后,根据索引找出Entry(即,单向链表)。若节点key-value存在与链表Entry中,则删除链表中的节点即可。
HashMap和Hashtable的不同点
1 继承和实现方式不同
HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
2 线程安全不同
Hashtable的几乎所有函数都是同步的,即它是线程安全的,支持多线程。
而HashMap的函数则是非同步的,它不是线程安全的。若要在多线程中使用HashMap,需要我们额外的进行同步处理。 对HashMap的同步处理可以使用Collections类提供的synchronizedMap静态方法,或者直接使用JDK 5.0之后提供的java.util.concurrent包里的ConcurrentHashMap类。
3 对null值的处理不同。(原因)
HashMap的key、value都可以为null。
Hashtable的key、value都不可以为null。
4 支持的遍历种类不同
HashMap只支持Iterator(迭代器)遍历。
而Hashtable支持Iterator(迭代器)和Enumeration(枚举器)两种方式遍历。
Enumeration 是JDK 1.0添加的接口,只有hasMoreElements(), nextElement() 两个API接口,不能通过Enumeration()对元素进行修改 。
而Iterator 是JDK 1.2才添加的接口,支持hasNext(), next(), remove() 三个API接口。HashMap也是JDK 1.2版本才添加的,所以用Iterator取代Enumeration,HashMap只支持Iterator遍历。
5 通过Iterator迭代器遍历时,遍历的顺序不同
HashMap是“从前向后”的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。
Hashtabl是“从后往前”的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。
6 容量的初始值 和 增加方式都不一样
HashMap默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”。
Hashtable默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”。
7 添加key-value时的hash值算法不同
HashMap添加元素时,是使用自定义的哈希算法。
Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。
8 部分API不同
Hashtable支持contains(Object value)方法,而且重写了toString()方法;
而HashMap不支持contains(Object value)方法,没有重写toString()方法。
TreeMap
原文链接
文章中对其存储结构——红黑树进行讲解,可参照链接深入学习
TreeMap提供了四个构造方法,实现了方法的重载。无参构造方法中比较器的值为null,采用自然排序的方法,如果指定了比较器则称之为定制排序.
自然排序:TreeMap的所有key必须实现Comparable接口,所有的key都是同一个类的对象
定制排序:创建TreeMap对象传入了一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序不要求Map的key实现Comparable接口。等下面分析到比较方法的时候在分析这两种比较有何不同。
对于Map来说,使用的最多的就是put()/get()/remove()等方法,下面依次进行分析
put()
public V put(K key, V value) {
Entry<K,V> t = root; //红黑树的根节点
if (t == null) { //红黑树是否为空
compare(key, key); // type (and possibly null) check
//构造根节点,因为根节点没有父节点,传入null值。
root = new Entry<>(key, value, null);
size = 1; //size值加1
modCount++; //改变修改的次数
return null; //返回null
}
int cmp;
Entry<K,V> parent; //定义节点
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; //获取比较器
if (cpr != null) { //如果定义了比较器,采用自定义比较器进行比较
do {
parent = t; //将红黑树根节点赋值给parent
cmp = cpr.compare(key, t.key); //比较key, 与根节点的大小
if (cmp < 0) //如果key < t.key , 指向左子树
t = t.left; //t = t.left , t == 它的做孩子节点
else if (cmp > 0)
t = t.right; //如果key > t.key , 指向它的右孩子节点
else
return t.setValue(value); //如果它们相等,替换key的值
} 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) // key < t.key
t = t.left; //左孩子
else if (cmp > 0) // key > t.key
t = t.right; //右孩子
else
return t.setValue(value); //t == t.key , 替换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;
}
//比较方法,如果comparator==null ,采用comparable.compartTo进行比较,否则采用指定比较器比较大小
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
private void fixAfterInsertion(Entry<K,V> x) {
//插入的节点默认的颜色为红色
x.color = RED; //
//情形1: 新节点x 是树的根节点,没有父节点不需要任何操作
//情形2: 新节点x 的父节点颜色是黑色的,也不需要任何操作
while (x != null && x != root && x.parent.color == RED) {
//情形3:新节点x的父节点颜色是红色的
//判断x的节点的父节点位置,是否属于左孩子
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//获取x节点的父节点的兄弟节点,上面语句已经判断出x节点的父节点为左孩子,所以直接取右孩子
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//判断是否x节点的父节点的兄弟节点为红色。
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK); // x节点的父节点设置为黑色
setColor(y, BLACK); // y节点的颜色设置为黑色
setColor(parentOf(parentOf(x)), RED); // x.parent.parent设置为红色
x = parentOf(parentOf(x)); // x == x.parent.parent ,进行遍历。
} else {
//x的父节点的兄弟节点是黑色或者缺少的
if (x == rightOf(parentOf(x))) { //判断x节点是否为父节点的右孩子
x = parentOf(x); //x == 父节点
rotateLeft(x); //左旋转操作
}
//x节点是其父的左孩子
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED); //上面两句将x.parent 和x.parent.parent的颜色做调换
rotateRight(parentOf(parentOf(x))); //进行右旋转
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x))); //y 是x 节点的祖父节点的左孩子
if (colorOf(y) == RED) { //判断颜色
setColor(parentOf(x), BLACK); //父节点设置为黑色
setColor(y, BLACK); //父节点的兄弟节点设置为黑色
setColor(parentOf(parentOf(x)), RED); //祖父节点设置为红色
x = parentOf(parentOf(x)); //将祖父节点作为新插入的节点,遍历调整
} else {
if (x == leftOf(parentOf(x))) { //x 是其父亲的左孩子
x = parentOf(x);
rotateRight(x); //以父节点为旋转点,进行右旋操作
}
setColor(parentOf(x), BLACK); //父节点为设置为黑色
setColor(parentOf(parentOf(x)), RED); //祖父节点设置为红色
rotateLeft(parentOf(parentOf(x))); //以父节点为旋转点,进行左旋操作
}
}
}
root.color = BLACK; //通过节点位置的调整,最终将红色的节点条调换到了根节点的位置,根节点重新设置为黑色
}
remove()
public V remove(Object key) {
Entry<K,V> p = getEntry(key); //根据key查找节点,并返回该节点
if (p == null)
return null;
V oldValue = p.value; //获取key对应的值
deleteEntry(p); //删除节点
return oldValue; //返回key对应的值
}
final Entry<K,V> getEntry(Object key) {
//根据键寻找节点,有非为两种方式,如果定制了比较器,采用定制排序方式,否则使用自然排序
if (comparator != null)
return getEntryUsingComparator(key); //循环遍历树,寻找和key相等的节点
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) { //循环遍历树,寻找和key相等的节点
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
//删除节点
private void deleteEntry(Entry<K,V> p) {
modCount++; //记录修改的次数
size--; //数量减1
//当前节点的两个孩子都不为空
if (p.left != null && p.right != null) {
//寻找继承者,继承者为当前节点的右孩子节点或者右孩子节点的最小左孩子
Entry<K,V> s = successor(p);
p.key = s.key; //key - value 的替换 ,并没有替换颜色
p.value = s.value;
p = s; //指向继承者
} // p has 2 children
// Start fixup at replacement node, if it exists.
//开始修复树结构,继承者的左孩子不为空,返回左孩子,否则返回右孩子
//不可能存在左右两个孩子都存在的情况,successor寻找的就是最小节点,它的左孩子节点为null
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
//已经被选为继承者,当前拥有的一切放弃,所以将孩子交给爷爷抚养
replacement.parent = p.parent;
//p节点没有父节点,则指向根节点
if (p.parent == null)
root = replacement;
//如果p为左孩子,如果p为左孩子,则将p.parent.left = p.left
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
//删除p节点到左右分支,和父节点的引用
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
//恢复颜色分配
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
//红黑书中父节点为空的只能是根节点。
root = null;
} else { // No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
fixAfterDeletion(p);
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
private void fixAfterDeletion(Entry<K,V> x) {
//不是根节点,颜色为黑色,调整结构
while (x != root && colorOf(x) == BLACK) {
//判断x是否为左孩子
if (x == leftOf(parentOf(x))) {
//x的兄弟节点
Entry<K,V> sib = rightOf(parentOf(x));
//若兄弟节点是红色
if (colorOf(sib) == RED) {
setColor(sib, BLACK); //设置兄弟节点变为黑色
setColor(parentOf(x), RED); //父节点设置为红色
rotateLeft(parentOf(x)); //左旋父节点
sib = rightOf(parentOf(x)); //重新设置x的兄弟节点
}
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED); //兄弟节点的两个孩子都是黑色的重新设置兄弟节点的颜色,修改为红色
x = parentOf(x); //将x定位到父节点
} else {
if (colorOf(rightOf(sib)) == BLACK) { //兄弟节点的右孩子是黑色的,左孩子是红色的
setColor(leftOf(sib), BLACK); //设置左孩子节点为黑色
setColor(sib, RED); //兄弟节点为红色
rotateRight(sib); //右旋
sib = rightOf(parentOf(x)); //右旋后重新设置兄弟节点
}
setColor(sib, colorOf(parentOf(x))); //兄弟节点颜色设置和父节点的颜色相同
setColor(parentOf(x), BLACK); //父节点设置为黑色
setColor(rightOf(sib), BLACK); //将兄弟节点的有孩子设置为黑色
rotateLeft(parentOf(x)); //左旋
x = root; //设置x为根节点
}
} else { // symmetric
//x为父节点的右节点,参考上面的操作
Entry<K,V> sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &&colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
setColor(x, BLACK);
}
lianjie