常用集合类
大家好呀!我是小笙!我学习了韩顺平老师的集合类的知识,收获颇丰!现在来和大家分享笔记!
集合类
集合框架体系
Collection接口框架
补充框架图
Map接口框架
补充框架图
Collection接口
Collection 实现 Iterable接口 : public interface Collection extends Iterable
遍历方式
遍历方式-迭代器
Iterator接口 又称为 迭代器,主要用于遍历Collcection集合中的元素
所有实现了Collection接口的集合类都有一个Iterator()方法,用来返回一个迭代器
迭代器方法
变量和类型 | 方法 | 描述 |
---|---|---|
default void | forEachRemaining(Consumer<? super E> action) | 对每个剩余元素执行给定操作,直到处理完所有元素或操作引发异常。 |
boolean | hasNext() | 如果迭代具有更多元素,则返回 true 。 |
E | next() | 返回迭代中的下一个元素。 |
default void | remove() | 从底层集合中移除此迭代器返回的最后一个元素(可选操作)。 |
// Iterator方法的使用实例
public class Iterator01 {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Book("三国演义",10));
list.add(new Book("水浒传",20));
list.add(new Book("西游记",15));
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
// 退出循环之后,迭代器指向最后一个元素
// iterator.next(); // 抛出异常 NoSuchElementException
// 需要重置迭代器 iterator = list.iterator();
}
static class Book{
private String name;
private int price;
public Book(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
}
遍历方式-for增强
特点:只能用于遍历集合和数组(简化版迭代器)
基本语法:
for(元素类型 元素名:集合名或数组名){
// 访问元素
}
实例
public class ForS {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Toy("猛虎王",10));
list.add(new Toy("霹雳火",20));
list.add(new Toy("洛洛",15));
// 增强for 本质仍然是迭代器
// 集合
for (Object b:list) {
System.out.println(b.toString());
}
// 数组
int[] num = {2,4,5,6};
for (int n:num
) {
System.out.println(n+" ");
}
}
static class Toy{
private String name;
private int price;
public Toy(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Toy{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
}
// debug 跳转 底层也是迭代器
// 跳转1
public Iterator<E> iterator() {
return new Itr();
}
// 跳转2
public boolean hasNext() {
return cursor != size;
}
// 跳转3
public E next() ......等等
遍历方式-普通for循环
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
list.add("秦天柱");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
Collection接口特征
- 可以实现存放多个元素,每个元素可以是Object类
- 没有直接实现的子类,都是通过它的子接口Set和List来实现的
Collection的常用方法
List list = new ArrayList();
// add 添加单个元素
list.add("lns");
list.add(520); // 自动装箱 new Integer(520)
list.add("zlr");
System.out.println(list.toString()); // [lns, 520, zlr]
/*
* 父类AbstractCollection: toString方法
* public String toString() {
* Iterator<E> it = iterator(); // 创建迭代器进行遍历该集合
* if (! it.hasNext())
* return "[]";
*
* StringBuilder sb = new StringBuilder();
* sb.append('[');
* for (;;) {
* E e = it.next();
* sb.append(e == this ? "(this Collection)" : e);
* if (! it.hasNext())
* return sb.append(']').toString();
* sb.append(',').append(' ');
* }
* }
*/
// remove 删除指定元素
list.remove(1); // 等价于 list.remove(520);
System.out.println(list); // [lns, zlr]
// contains 查找元素是否存在
System.out.println(list.contains("lns")); // true
// size 获取元素个数
System.out.println(list.size()); // 2
// isEmpty 判读是否为空
System.out.println(list.isEmpty()); // false
// clear 清空
list.clear();
System.out.println(list); // []
// addAll 添加多个元素
List list2 = new ArrayList();
list2.add("lns");
list2.add("love");
list2.add("zlr");
list.addAll(list2);
System.out.println(list); // [lns, love, zlr]
// containsAll 查找多个元素是否存在
System.out.println(list.containsAll(list2)); // true
// removeAll 删除多个元素
list.add("!");
list.removeAll(list2);
System.out.println(list); // [!]
List接口
Collection接口的子接口 (队列数据结构)
特点
- List集合类元素顺序有序,且可以重复
- List集合类中的每个元素都有其对应的顺序索引
// List集合类元素顺序有序,且可以重复
List list = new ArrayList();
list.add("das");
list.add("tom");
list.add("tom");
System.out.println(list); // [das, tom, tom]
// List集合类中的每个元素都有其对应的顺序索引
System.out.println(list.get(0)); // das
List常用方法
public class ListMethod {
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
list.add("秦天柱");
// add 插入一个对象
list.add(1,"lns");
System.out.println(list); // [张三丰, lns, 秦天柱]
List list2 = new ArrayList();
list2.add("风火轮");
list2.add("大黄蜂");
// addAll 插入所有元素
list.addAll(1,list2);
System.out.println(list); // [张三丰, 风火轮, 大黄蜂, lns, 秦天柱]
// indexOf 返回该对象首次出现的索引位置
System.out.println(list.indexOf("lns")); // 3
// lastIndexOf 返回该对象最后一次出现的索引位置
list.add(1,"张三丰");
System.out.println(list ); // [张三丰, 张三丰, 风火轮, 大黄蜂, lns, 秦天柱]
System.out.println(list.lastIndexOf("张三丰")); // 1
// set 替换对象数据
list.set(1,"妞妞");
System.out.println(list); // [张三丰, 妞妞, 风火轮, 大黄蜂, lns, 秦天柱]
// subList 返回范围为[fromIndex,toIndex)位置的子集合 该方法返回的是子串集合的地址索引
list = list.subList(0, 3);
System.out.println(list); // [张三丰, 妞妞, 风火轮]
}
}
ArrayList类
是由数组实现数据存储
特点:
-
元素可以是null,并且可以有多个
-
ArrayList是线程不安全的,不能在多线程的情况下使用
// 对比这两种源码 区别在于是否线程安全 synchronized // ArrayList源码 public boolean add(E e) { modCount++; add(e, elementData, size); return true; } // Vector源码 public synchronized boolean add(E e) { modCount++; add(e, elementData, elementCount); return true; }
ArrayList源码分析
-
ArrayList类数据存储在Object类数组中(elementData)
transient Object[] elementData; // transient 表示该属性不会被序列化
-
当使用无参构造方法创建该对象,初始elementData容量为0,第一次添加数据,扩容到10容量,以后每次扩容,则会扩大当前容量的1.5倍
-
如果使用指定大小的有参构造器,则初始elementData容量为指定大小,如果需要扩容,也是直接扩容1.5倍
扩容机制
无参构造器
1.设置断点
2.debug跳转
// 跳转1
public boolean add(E e) {
modCount++; // 记录集合被修改的次数 如果madCount的值因为线程原因意外改变,则抛出异常
add(e, elementData, size); // e:传入的数据 elementDate:Object数组 size:数组元素数量
return true;
}
// 跳转2
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) // s:数组元素数量 elementDate.length:数组容量
elementData = grow(); // 只有当容量够用,不会调用该方法
elementData[s] = e; // 数组添加数据
size = s + 1;
}
// 跳转3
private Object[] grow() {
return grow(size + 1);
}
// 跳转4
private Object[] grow(int minCapacity) { // minCapacity:当前元素个数+1
int oldCapacity = elementData.length; // 记录原容量
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA){ //判断容量是否为0
// 传入newLength方法的参数:原容量,超出容量,0.5倍容量大小
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, oldCapacity >> 1 ); // >> 1 相当于乘0.5
return elementData = Arrays.copyOf(elementData, newCapacity); // 扩容,保留原数据
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
} // DEFAULT_CAPACITY 10 如果是容量为0,第一次扩容默认为10
}
// 当传入第11个数据时候跳转5
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// assert oldLength >= 0
// assert minGrowth > 0
int newLength = Math.max( minGrowth,prefGrowth ) + oldLength; // 当超出容量,则扩容1.5倍
if (newLength - MAX_ARRAY_LENGTH <= 0) {
return newLength;
}
return hugeLength(oldLength, minGrowth);
}
有参构造器
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
Vector类
是由数组实现数据存储
Vector的基本介绍
// Vector的类定义
public class Vector<E>extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
// Vector底层本质也是对象数组
protected Object[] elementData;
// Vector是线程安全
// 通常方法被 synchronized 关键字修饰
Vector源码分析
/*
* The amount by which the capacity of the vector is automatically
* incremented when its size becomes greater than its capacity. If
* the capacity increment is less than or equal to zero, the capacity
* of the vector is doubled each time it needs to grow.
* 当向量的大小变得大于其容量时,向量的容量自动增加的量。如果容量增量小于或等于零,
* 则每次需要增长时,向量的容量都会增加一倍
* @serial
*/
protected int capacityIncrement;
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, capacityIncrement > 0 ? capacityIncrement : oldCapacity);
return elementData = Arrays.copyOf(elementData, newCapacity);
}
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// assert oldLength >= 0
// assert minGrowth > 0
int newLength = Math.max(minGrowth, prefGrowth) + oldLength; // 2倍
if (newLength - MAX_ARRAY_LENGTH <= 0) {
return newLength;
}
return hugeLength(oldLength, minGrowth);
}
Vector和ArrayList的比较
底层结构 | 版本 | 线程安全 | 扩容机制 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,但效率高 | 如果有参构造 扩容1.5倍;如果是无参1.第一次默认10,第二次扩容1.5倍 |
Vector | 可变数组 | jdk1.0 | 安全,但效率不高 | 如果有参构造 扩容2倍;如果是无参1.第一次默认10,第二次扩容2倍 |
LinkedList类
LinkedList类基本介绍
- LinkedList类底层实现了双向链表和双端队列特点
- 可以添加任意元素包括null,并且可以重复
- 线程不安全没有实现同步
LinkedList类的底层结构
-
该类底层是一个双向链表
-
其中含有两个属性:first 和 last 分别指向首节点和尾节点
-
每个节点里面含有prev,next,item三个属性,其中通过prev指向前一个节点,通过next指向后一个节点,用item来存储数据
-
进行添加和删除操作,效率比数组高
添加数据源码分析
// 添加第一个数据
// 跳转1
public boolean add(E e) { // 增加数据
linkLast(e);
return true;
}
// 跳转2
void linkLast(E e) {
final Node<E> l = last; // last:null 第一次添加last为null
final Node<E> newNode = new Node<>(l, e, null); //l:null e:"lns" 说明prev和next指向null
last = newNode; // last 指向尾节点
if (l == null) // 添加第一个节点
first = newNode; // first和last都指向同一个节点
else
l.next = newNode;
size++;
modCount++;
}
// 添加第二个数据: 省略部分不重要的
void linkLast(E e) {
// 总结: l这个变量可以当成连接器,连接新节点和原来最后一个节点
final Node<E> l = last; // l:链表的最后一个节点
final Node<E> newNode = new Node<>(l, e, null); // 创建连接上一个节点的新节点,e:"zlr"
last = newNode; // last指向新的节点(该节点就是新的最后一个节点)
if (l == null)
first = newNode;
else
l.next = newNode; // l指向新节点:节点变成l的下一个节点
size++;
modCount++;
}
// 删除数据还是更改数据...等等看源码
常用方法
// add 增加节点
LinkedList linkedList = new LinkedList();
linkedList.add("lns");
linkedList.add("zlr");
System.out.println(linkedList.toString()); // [lns, zlr]
// remove 删除节点
linkedList.remove(); // 默认删除第一个节点
System.out.println(linkedList); // [zlr]
// set 修改节点
linkedList.set(0,"奥里给");
System.out.println(linkedList); // [奥里给]
// get 根据索引获得某个节点数据
System.out.println(linkedList.get(0)); // 奥里给
ArrayList和LinkedList的比较
底层结构 | 增删效率 | 改查效率 | |
---|---|---|---|
ArrayList | 可变数组 | 低,数组扩容 | 高 |
LinkedList | 双向链表 | 高,动态扩容 | 低 |
Set接口
public interface Set extends Collection
Set接口基本介绍
- 无序(添加和取出顺序不一致),没有索引,因此该接口不能再使用普通for循环索引的方式遍历
- 不允许重复数据,因此可以有null 但是只能有一个null
- Set接口实现了Collection接口,所以可以使用该接口的所有方法
Set set = new HashSet();
set.add("lns");
set.add("null");
set.add("null"); // 只会存入一个数据
set.add("zlr");
System.out.println(set); // [lns, zlr, null] 无序
HashSet类
HashSet的底层实际上就是HashMap
/**
* Constructs a new, empty set; the backing {@code HashMap} instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
HashSet框架图
特点:
-
可以存放null值,但是只能存放一个
-
不能存放重复元素
public class HashSet01 { public static void main(String[] args) { Set set = new HashSet(); System.out.println(set.add("lns")); // true System.out.println(set.add("lns")); // false System.out.println(set.add(new String("lns"))); // false System.out.println("lns".hashCode() == new String("lns").hashCode()); // true System.out.println(set.add(new person("zlr"))); // true System.out.println(set.add(new person("zlr"))); // true // new String("lns") 的hashCode和"lns"相同 } } class person{ private String name; public person(String name) { this.name = name; } }
-
元素是无序的,创建后顺序是固定的
HashSet底层机制
先介绍散列表:数组 + 链表
实例代码
public class HashSetStructure {
public static void main(String[] args) {
// 数组 + 链表
Node[] table = new Node[16];
// 创建节点
Node node21 = new Node("lns", null);
table[2] = node21;
Node node22 = new Node("zlr", null);
node21.next = node22;
Node node23 = new Node("lp", null);
node22.next = node23;
Node node31 = new Node("cyj", null);
table[3] = node31;
System.out.println(Arrays.toString(table));
}
}
class Node{ // 节点
Object item; // 数据
Node next; // 指向下个节点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
图解
用该图分析HashSet底层过程
-
添加一个元素时,先得到hash值 => 索引值(类似图中 0-16索引)
-
找到存储数据表table,看这个索引上是否有存放数据
- 如果没有找到,就直接加入(如图中的node21,node22,node23);如果有元素并且hashCode值相同,则调用equals方法进行比较,如果相同则不添加,反之添加
// 实例说明:如果有元素并且hashCode值相同,则调用equals方法进行比较,如果相同则不添加,反之添加
// 这就是为什么new String("lns")不会被添加以及为什么new person("zlr")会被添加2次
// 核心就在于equals方法
// 但是为什么"lns"的hashCode会和 new String("lns")相同呢(不是不同的地址吗)
// 关键就在String类重写了hashCode方法,字符串的hashCode是根据字符算出来的
/*
String类计算hashCode的算法
public static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) { // value:传入的字符串
h = 31 * h + (v & 0xff);
}
return h;
}
*/
public class HashSet01 {
public static void main(String[] args) {
Set set = new HashSet();
System.out.println(set.add("lns")); // true
System.out.println(set.add("lns")); // false
System.out.println(set.add(new String("lns"))); // false
System.out.println(set.add(new person("zlr"))); // true
System.out.println(set.add(new person("zlr"))); // true
}
}
class person{
private String name;
public person(String name) {
this.name = name;
}
}
- 在jdk8中,一个链表的元素达到8个以及table数据表长度达到64.则将 数组+链表 => 红黑树
public class HashSet02 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("罗念笙");
hashSet.add("张洛融");
hashSet.add("罗念笙");
System.out.println(hashSet); // [张洛融, 罗念笙]
/*
* 分析源码
* 假设传入"罗念笙"
* 跳转1:add方法
* public boolean add(E e) {
* return map.put(e, PRESENT)==null; // PRESENT:占位 new Object()
* }
*
* 跳转2:put方法
* public V put(K key, V value) {
* return putVal(hash(key), key, value, false, true);
* }
*
* 跳转3:hash方法 计算hash值并返回给put方法中hash(key)
* static final int hash(Object key) {
* int h;
* return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
* }
*
* 跳转4:pubVal方法 传入形参:hash值, "罗念笙", PRESENT, false, true
* final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
* Node<K,V>[] tab; Node<K,V> p; int n, i; // 定义辅助变量
* // transient Node<K,V>[] table; table:数组+链表形式
* if ((tab = table) == null || (n = tab.length) == 0) // 没有分配数组空间
* // 分配数组空间 newCap = DEFAULT_INITIAL_CAPACITY 默认16
* n = (tab = resize()).length;
* if ((p = tab[i = (n - 1) & hash]) == null) // i = (n - 1) & hash 通过hash值计算索引
* tab[i] = newNode(hash, key, value, null); // 该索引下数组值为null,就直接添加节点
* else {
* Node<K,V> e; K k; // 定义辅助变量
* // 比较索引处首节点的 hash值 以及 指向地址是否相同 或者 比较值是否相同(重写的情况)
* if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
* e = p;
* // 判断 索引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;
* }
* 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;
* // threshold 阈值,用来提前给数组扩容 :threshold = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
* // size 大小值得是加入的节点个数达到了阈值
* if (++size > threshold)
* resize();
* afterNodeInsertion(evict); // HashMap类留给子类继承使用的方法
* return null;
* }
*
* // treeifyBin方法 一个链表的元素达到8个以及table数据表长度达到64.则将 数组+链表 => 红黑树
* final void treeifyBin(Node<K,V>[] tab, int hash) {
* int n, index; Node<K,V> e;
* // 如果表的长度(数组长度)小于64,则扩容
* // MIN_TREEIFY_CAPACITY:64
* if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
* resize();
* // 如果表的长度(数组长度)大于等于64,则数组+链表 => 红黑树
* else if ((e = tab[index = (n - 1) & hash]) != null) {
* TreeNode<K,V> hd = null, tl = null;
* do {
* TreeNode<K,V> p = replacementTreeNode(e, null);
* if (tl == null)
* hd = p;
* else {
* p.prev = tl;
* tl.next = p;
* }
* tl = p;
* } while ((e = e.next) != null);
* if ((tab[index] = hd) != null)
* hd.treeify(tab);
* }
* }
*
* // 补充信息 非debug内容
* 节点Node代码
* static class Node<K,V> implements Map.Entry<K,V> {
* final int hash;
* final K key;
* V value;
* Node<K,V> next;
*
* Node(int hash, K key, V value, Node<K,V> next) {
* this.hash = hash;
* this.key = key;
* this.value = value;
* this.next = next;
* }
*
* public final K getKey() { return key; }
* public final V getValue() { return value; }
* public final String toString() { return key + "=" + value; }
*
* public final int hashCode() {
* return Objects.hashCode(key) ^ Objects.hashCode(value);
* }
*
* public final V setValue(V newValue) {
* V oldValue = value;
* value = newValue;
* return oldValue;
* }
*
* public final boolean equals(Object o) {
* if (o == this)
* return true;
* if (o instanceof Map.Entry) {
* Map.Entry<?,?> e = (Map.Entry<?,?>)o;
* if (Objects.equals(key, e.getKey()) &&
* Objects.equals(value, e.getValue()))
* return true;
* }
* return false;
* }
* }
*/
}
}
LinkedHahSet类
LinkedHashSet类底层是一个LinkedHashMap,是一个数组+双向链表的结构
// 初始化LinkedHashSet
// LinkedHashSet类构造器
public LinkedHashSet() {
super(16, .75f, true); // 初始化容量16;负载因子0.75
}
// 调用父类HashSet构造器,初始化LinkedHashMap
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
// LinkedHashMap调用父类的HashMap的构造器
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
// 初始化HashMap
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) // MAXIMUM_CAPACITY = 1 << 30
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
LinkedHashSet框架图
特点:
- 根据hashCode值来决定元素的存储位置,同时使用双向链表来维护元素的次序,所以在一定程度上是有序的
- 不允许重复添加元素
LinkedHashSet类的底层分析
// LinkedHashSet节点源码
// 继承Node节点(HashMap的静态内部类)
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 增加前驱节点和后继节点 => 双向链表
// 构造器
Entry(int hash, K key, V value, Node<K,V> next) {
// Node节点构造器
super(hash, key, value, next);
}
}
// 实现Map接口里的Entry接口
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
TreeSet类
基本介绍
TreeSet类本质就是调用TreeMap,源码,比较机制等会在TreeMap中详细述说
public TreeSet() {
this(new TreeMap<>());
}
Map接口
public interface Map<K, V> 与Collection接口并列存在
Map接口特点
- 保存具有映射关系的数据:Key - Value
- Map 中的Key - Value数据可以是任何的引用数据类型,会封装到HashMap$Node对象中
- Map中的Key不允许重复,但是Value可以重复(并且数据都可以是null)
- Key 和 Value存在一对一的关系,总是能通过Key找到对应得Value
深入理解map接口的Node节点
// Node节点属于HashMap内部类,实现了Map.Entry<K,V>接口 ; 一个Node节点对象含有一个key和value
// Map.Entry<K,V> 为Map接口的内部接口
static class Node<K,V> implements Map.Entry<K,V>
// key-value值是存储在Node节点对象中,Map.Entry中key-value指向Node节点对象的key-value值的引用(类似对象名对对象的引用)
final int hash;
final K key;
V value;
Node<K,V> next;
// 那么EntrySet又是什么呢
// Set接口都可理解就是单列集合,其实EntrySet就是存放Map.Entry数据类型的集合
transient Set<Map.Entry<K,V>> entrySet;
// 代码示例
Map map = new HashMap();
map.put("name","罗念笙"); // key: name value: 罗念笙
map.put("name","张洛融"); // key值不能重复,如果重复会覆盖之前相同key值的value值
map.put("person","张洛融"); // value值能重复
System.out.println(map); // {person=张洛融, name=张洛融}
System.out.println(map.get("name")); // 张洛融 ; 能通过Key找到对应得Value
// 着重点看运行类型
Set set = map.entrySet();
System.out.println(set.getClass()); // class java.util.HashMap$EntrySet
for (Object obj: set
) {
System.out.println(obj.getClass()); // class java.util.HashMap$Node
}
Map常用方法
// 常用方法
// put方法 添加key-value
Map map = new HashMap();
map.put("name","lns");
map.put("age",18);
map.put("grade",99);
map.put("grade",60); // 重复key,覆盖之前的value
System.out.println(map); // {grade=60, name=lns, age=18}
// remove方法 根据key值删除映射关系
map.remove("grade");
System.out.println(map); // {name=lns, age=18}
// get方法 根据key获得value值
System.out.println(map.get("age")); // 18
// size方法 获取元素个数
System.out.println(map.size()); // 2
// isEmpty方法 判断元素是否为空
System.out.println(map.isEmpty()); // false
// containsKey 查找该键是否存在
System.out.println(map.containsKey("name")); // true
// clear 清空键值对]
map.clear();
System.out.println(map); // {}
Map接口遍历方式
// Map接口遍历方式
Map map = new HashMap();
map.put("name","lns");
map.put("age",18);
map.put("grade",99);
// 第一组: 先取出key(keySet方法,通过key取出value (get方法)
Set set = map.keySet();
// 方式1: 增强for
for (Object key:set
) {
// key: grade value: 99 key: name value: lns key: age value: 18
System.out.print("key: "+key+" value: "+map.get(key)+" ");
}
System.out.println();
// 方式2: 迭代器
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Object key = iterator.next();
// key: grade value: 99 key: name value: lns key: age value: 18
System.out.print("key: "+key+" value: "+map.get(key)+" ");
}
System.out.println();
// 第二组: 通过EntrySet来获取key-value
Set entrySet = map.entrySet();
// 方式3: 用getKey方法 和 getValue方法
for (Object obj: entrySet
) {
Map.Entry entry = (Map.Entry)obj;
// key: grade value: 99 key: name value: lns key: age value: 18
System.out.print("key: "+entry.getKey()+" value: "+entry.getValue()+" ");
}
System.out.println();
// 方式4: 迭代器
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
Map.Entry entry = (Map.Entry)iterator1.next();
// key: grade value: 99 key: name value: lns key: age value: 18
System.out.print("key: "+entry.getKey()+" value: "+entry.getValue()+" ");
}
HashMap类
HashMap底层是数组+链表+红黑树
HashMap类特点
-
保存具有映射关系的数据:Key - Value
-
Key不允许重复,但是Value可以重复(并且数据都可以是null); 如果重复,将会替换掉value值
-
Key 和 Value存在一对一的关系,总是能通过Key找到对应得Value
-
HashMap没有实现线程同步,是线程不安全的
HashMap框架图
注意 HashMap扩容机制等价于HashSet扩容机制,如上述
Hashtable类
Hashtable类特点
-
保存具有映射关系的数据:Key - Value
-
Hashtable的key和value都不允许是null,如果是,将会抛出空指针异常
-
Hashtable是线程安全的,与HashMap不同
// Hashtable的put方法 public synchronized V put(K key, V value)
Hashtable框架图
Hashtable扩容机制
//Hashtable构造器初始化容量11
public Hashtable() {
this(11, 0.75f);
}
// put方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Hashtable.Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; // 索引值的计算方式: 散列码(hash)% 散列表的长度(tab.length)
@SuppressWarnings("unchecked") // 抑制警告
Hashtable.Entry<K,V> entry = (Hashtable.Entry<K,V>)tab[index]; // 创建entry节点
// 判断是否有相同的key的entry节点,如果有,就替换掉原来的value值;反之则添加entry节点
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
// 添加entry节点
addEntry(hash, key, value, index);
return null;
}
// addEntry方法
private void addEntry(int hash, K key, V value, int index) {
Entry<?,?> tab[] = table; // 成员变量table: 用来存储之前添加的键值对
if (count >= threshold) { // 判断是否需要扩容
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
modCount++;
}
// rehash方法 用于扩容
protected void rehash() {
int oldCapacity = table.length; // 记录原来的散列表的长度(table.length)
Entry<?,?>[] oldMap = table; // 记录原来的散列表(table)
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1; // 新容量 = 旧容量 * 2 + 1
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; // 数组扩容
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
// 添加原来的数组数据
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
细节说明
// 源码说明
// addEntry方法
private void addEntry(int hash, K key, V value, int index) {
Entry<?,?> tab[] = table; // 成员变量table: 用来存储之前添加的键值对
if (count >= threshold) { // 判断是否需要扩容
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index]; // 该索引该的引用节点的引用赋值给e
tab[index] = new Entry<>(hash, key, value, e); // 加入当前的节点,并且指向下一个节点e
count++;
modCount++;
}
// Entry内部类的构造器 说明e 为当前节点的下一个节点
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
Hashtable类和HashMap类的区别
版本 | 线程(安全) | 效率 | 允许(null键 ,null值) | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 允许 |
Hashtable | 1.0 | 安全 | 较低 | 不允许 |
TreeMap类
基本介绍
使用TreeMap
时,如果是调用无参构造器,则放入的Key对象必须实现Comparable
接口。String、Integer这些类已经实现了Comparable
接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。如果作为Key的class没有实现Comparable
接口,那么,必须在创建TreeMap
时同时指定一个自定义排序算法
TreeMap类的有序是按一定规则的有序,而非LinkedHashSet的插入和取出顺序一致
注意:红黑树的具体结构,我会放在数据结构里详细介绍
TreeMap源码分析
- 无参构造器
// debug 代码示例
public class TreeMap01 {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap();
treeMap.put("name","罗念笙");
treeMap.put("age",18);
System.out.println(treeMap); // {age=18, height=192, name=罗念笙}
}
}
// 无参构造器: new TreeMap();
public TreeMap() {
comparator = null; // comparator:TreeMap中的属性(用来存储comparator内部类的对象)
}
// 传入第一个参数:put("name","罗念笙");
// 跳转1
public V put(K key, V value) { //key: name value: 罗念笙
return put(key, value, true);
}
// 跳转2
private V put(K key, V value, boolean replaceOld) {
Entry<K,V> t = root; // t指向根节点
if (t == null) { // 如果根节点没数据
addEntryToEmptyMap(key, value); // 增加Entry节点
return null;
}
// 删除的一部分代码,在添加第一个节点不会遍历
}
// 跳转3
private void addEntryToEmptyMap(K key, V value) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null); // 创建新的节点
size = 1;
modCount++;
}
// 跳转4
final int compare(Object k1, Object k2) {
// 如果构造器没有传入comparator内部类的对象,则k1对象对应的类必须实现了compareTo方法
// 就是实现了Comparable接口并且重写compareTo方法,并且k1和k2是可以比较的
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
// 跳转5 跳转到String类的compareTo方法
public int compareTo(String anotherString) {
byte v1[] = value;
byte v2[] = anotherString.value;
byte coder = coder();
if (coder == anotherString.coder()) {
return coder == LATIN1 ? StringLatin1.compareTo(v1, v2)
: StringUTF16.compareTo(v1, v2);
}
return coder == LATIN1 ? StringLatin1.compareToUTF16(v1, v2)
: StringUTF16.compareToLatin1(v1, v2);
}
// 传入第二数:put("age",18);
// 跳转1
public V put(K key, V value) { // key: age value: 18
return put(key, value, true);
}
// 跳转2
private V put(K key, V value, boolean replaceOld) {
Entry<K,V> t = root;
if (t == null) { // 跳过
addEntryToEmptyMap(key, value);
return null;
}
// 辅助变量
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
// 无参构造器中没有是实现该匿名内部类,所以comparator = null
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 {
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
} else {
Objects.requireNonNull(key); // 检查key是否为null
@SuppressWarnings("unchecked") // 抑制警告
// 强转key为Comparable接口(接口多态):判断key是否实现了Comparable接口,如果不是将会抛出 ClassCastException异常
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 {
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
}
addEntry(key, value, parent, cmp < 0);
return null;
}
// 跳转3
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
// 跳转4 跳转到String类的compareTo方法
public int compareTo(String anotherString) {
byte v1[] = value;
byte v2[] = anotherString.value;
byte coder = coder();
if (coder == anotherString.coder()) {
return coder == LATIN1 ? StringLatin1.compareTo(v1, v2)
: StringUTF16.compareTo(v1, v2);
}
return coder == LATIN1 ? StringLatin1.compareToUTF16(v1, v2)
: StringUTF16.compareToLatin1(v1, v2);
}
// 跳转5
private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {
Entry<K,V> e = new Entry<>(key, value, parent); // 创建新节点
if (addToLeft)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
}
// 跳转6:设置节点颜色:红和黑
/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(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 = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
2.有参构造器
public class TreeMap02 {
public static void main(String[] args) {
// 通过key中的字符大小进行排序 降序: name > height > age
TreeMap treeMap1 = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((car)o2).compareName((car)o1);
}
});
treeMap1.put(new car("e"),"LNS");
treeMap1.put(new car("da"),18);
treeMap1.put(new car("dwes"),185);
// {com.Al_tair.map_.treeMap_.car@27d6c5e0=185,
// com.Al_tair.map_.treeMap_.car@4f3f5b24=18,
// com.Al_tair.map_.treeMap_.car@15aeb7ab=LNS}
System.out.println(treeMap1);
}
}
class car{
String name;
public car(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int compareName(Object o) {
return this.getName().length()-((car)o).getName().length();
}
}
Properties类
基本介绍
Properties类特点
- 保存具有映射关系的数据:Key - Value
- Hashtable的key和value都不允许是null,如果是,将会抛出空指针异常
常用方法
// 常用方法
Properties properties = new Properties();
// put方法 添加数据,修改数据
properties.put("name","罗念笙");
properties.put("age",18);
properties.put("grade",99);
properties.put("grade",98); //修改数据
System.out.println(properties); // {grade=98, name=罗念笙, age=18}
// 通过key获取value
System.out.println(properties.get("name")); // 罗念笙
// remove方法 删除数据
properties.remove("grade");
System.out.println(properties); // {name=罗念笙, age=18}
如何选择集合实现类
- 先判断存储的数据类型(一组对象或者一组键值对)
- 一组对象【单列集合】:Collection接口
- 允许重复并且有序:List接口
- 线程安全:Vector【底层是一个Object类型的可变数组】
- 线程不安全:
- 增删多:LinkedList【底层是一个双向链表】
- 改查多:ArrayLIst【底层是一个Object类型的可变数组】
- 不允许重复:Set接口
- 无序并且线程不安全:HashSet【底层就是HashMap,数组+链表+红黑数】
- 定制排序并且线程不安全:TreeSet
- 插入和取出顺序一致,并且线程不安全: LinkedHashSet 【数组+双向链表】
- 允许重复并且有序:List接口
- 一组键值对:Map接口
- 线程不安全:
- 键无序:HashMap类 【底层就是:数组+链表+红黑数】
- 定制排序:TreeMap类
- 键插入和取出顺序一致:LinkedHashMap类
- 线程安全:读取配置文件:Properties类
- 线程不安全:
Colleactions工具类
Collections是一个操作Set,List,Map等集合的工具类,提供了一系列静态的方法对集合元素进行排序,查找和修改等操作
// Collections工具类中常用方法
// 创建测试类
ArrayList arrayList = new ArrayList();
arrayList.add("cyj");
arrayList.add("lns");
arrayList.add("zlr");
System.out.println(arrayList); // [cyj, lns, zlr]
// reverse(List集合) 反转List中元素的顺序
Collections.reverse(arrayList);
System.out.println(arrayList); // [zlr, lns, cyj]
// shuffle(List集合) 对List集合元素进行随机排序
for (int i = 0; i < 3; i++) {
Collections.shuffle(arrayList);
System.out.println(arrayList); // 随机出现
}
// shuffle方法的源码
public static void shuffle(List<?> list) {
Random rnd = r;
if (rnd == null)
r = rnd = new Random(); // harmless race.
shuffle(list, rnd);
}
// sort(List集合) 根据元素的自然排序对List集合进行升序 比如:字符串比较的是字符大小
arrayList.add("aaa");
Collections.sort(arrayList);
System.out.println(arrayList); // [aaa, cyj, lns, zlr]
// 用比较器Comparator自定义规则进行排序 sort(arrayList,new Comparator(){});
Collections.sort(arrayList,new Comparator(){
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
});
System.out.println(arrayList); // [zlr, lns, cyj, aaa]
// swap(List集合,int i,int j) 交换集合索引为i和j的位置
Collections.swap(arrayList,1,2);
System.out.println(arrayList); // [zlr, cyj, lns, aaa]
// max(Collection集合) 返回给定集合中自然排序最大的元素
System.out.println(Collections.max(arrayList)); // zlr
// max(Collection集合,new Comparator(){}) 返回给定集合中自治排序最大的元素
System.out.println(Collections.max(arrayList,new Comparator(){
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
})); // aaa
// frequency(Collection集合,集合中的元素) 返回该元素在集合中出现的频率
System.out.println(Collections.frequency(arrayList,"aaa")); // 1
// copy(List dest,List src) 复制src的元素到dest集合中
// 注意: 目标集合元素个数必须大于等于原来集合元素的个数
ArrayList dest = new ArrayList();
for (int i = 0; i < 6; i++) {
dest.add(i);
}
Collections.copy(dest,arrayList);
System.out.println(dest); // [zlr, cyj, lns, aaa, 4, 5]
相关面试题
// 补充测试题
// 注意remove方法 是否能删除
HashSet hashSet = new HashSet();
Person person = new Person(1, "lns");
Person person2 = new Person(1, "zlr");
hashSet.add(person);
hashSet.add(person2);
System.out.println(hashSet); // [Person{id=1, name='lns'}, Person{id=1, name='zlr'}]
person.setName("4"); // 当重新设置名字,改变了hash值间接改变了索引,会导致接下来的删除不成功
hashSet.remove(person);
System.out.println(hashSet); // [Person{id=1, name='4'}, Person{id=1, name='zlr'}]
Java中有哪些容器(集合类)?
Java中的集合类主要由Collection和Map这两个接口派生而出,其中Collection接口又派生出三个子接口,分别是Set、List、Queue。所有的Java集合类,都是Set、List、Queue、Map这四个接口的实现类,这四个接口将集合分成了四大类,其中
Set代表无序的,元素不可重复的集合;
List代表有序的,元素可以重复的集合;
Queue代表先进先出(FIFO)的队列;
Map代表具有映射关系(key-value)的集合。
这些接口拥有众多的实现类,其中最常用的实现类有HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap等。
Java中的容器,线程安全和线程不安全的分别有哪些?
java.util包下的集合类大部分都是线程不安全的,例如我们常用的HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap,这些都是线程不安全的集合类,但是它们的优点是性能好。如果需要使用线程安全的集合类,则可以使用Collections工具类提供的synchronizedXxx()方法,将这些集合类包装成线程安全的集合类。
java.util包下也有线程安全的集合类,例如Vector、Hashtable。这些集合类都是比较古老的API,虽然实现了线程安全,但是性能很差。所以即便是需要使用线程安全的集合类,也建议将线程不安全的集合类包装成线程安全集合类的方式,而不是直接使用这些古老的API。
从Java5开始,Java在java.util.concurrent包下提供了大量支持高效并发访问的集合类,它们既能包装良好的访问性能,有能包装线程安全。这些集合类可以分为两部分,它们的特征如下:
-
以Concurrent开头的集合类:
以Concurrent开头的集合类代表了支持并发访问的集合,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。以Concurrent开头的集合类采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。
-
以CopyOnWrite开头的集合类:
以CopyOnWrite开头的集合类采用复制底层数组的方式来实现写操作。当线程对此类集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对此类集合执行写入操作时,集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。由于对集合的写入操作都是对数组的副本执行操作,因此它是线程安全的。
描述一下Map put的过程
如上述我的HashMap和Hashtable的与添加源码分析
如何得到一个线程安全的Map?
- 使用Collections工具类,将线程不安全的Map包装成线程安全的Map;
- 使用java.util.concurrent包下的Map,如ConcurrentHashMap;
- 不建议使用Hashtable,虽然Hashtable是线程安全的,但是性能较差。
说一说你对LinkedHashMap的理解
LinkedHashMap使用双向链表来维护key-value对的顺序(其实只需要考虑key的顺序),该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。
LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可),同时又可避免使用TreeMap所增加的成本。
LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能。但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。
请介绍TreeMap的底层原理
TreeMap基于红黑树(Red-Black tree)实现。映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap的基本操作containsKey、get、put、remove方法,它的时间复杂度是log(N)。
TreeMap包含几个重要的成员变量:root、size、comparator。其中root是红黑树的根节点。它是Entry类型,Entry是红黑树的节点,它包含了红黑树的6个基本组成:key、value、left、right、parent和color。Entry节点根据根据Key排序,包含的内容是value。Entry中key比较大小是根据比较器comparator来进行判断的。size是红黑树的节点个数。