双列集合
键值对对象(Entry)
特点:
1.双列集合一次需要存一堆数据,分别为键和值
2.键不能重复,值可以重复
3.键和值是一一对应的,每个键只能找到自己对应的值
4.键+值这个整体,我们称之为“键值对”或者“键值对象”,在Java中叫做“Entry对象”
体系结构
Map --> HashMap 、 TreeMap --> LinekedHashMap(继承子HashMap)
Map中常见的API
put( K \ V) 添加元素
remove(object key) 删除键值对元素
clear 移除所有的键值对元素
containsKey(object key) 判断集合是否包含指定的键
containsValue(Object value) 判断集合是否包含指定的值
isEmpty 判断集合是否为空
size 集合的长度,也就是集合中键值对的个数
put方法
再添加数据的时候,如果键不存在,那么直接把键值对对象添加到map集合中,返回null,如果键是存在的,则会把原有的键值对覆盖,会返回被覆盖的值
remove方法
返回值是被删除的键值对的值
Map的遍历方式
1.通过键找值的方式遍历
public static void main(String[] args) { HashMap<String, String> m = new HashMap<>(); m.put("11" , "55"); m.put("22" , "66"); m.put("33" , "77"); m.put("44" , "88"); // 1. 使用迭代器遍历 // 1. 1 将Map中的所有的键存入到一个单列集合 Set<String> hs = new HashSet<>(); hs = m.keySet(); Iterator<String> it = hs.iterator(); while (it.hasNext()){ String s = it.next(); System.out.println(s + "-" + m.get(s)); } // 2. 使用增强for遍历 for (String h : hs) { System.out.println(h + "-" + m.get(h)); } // 3. 使用lambda表达式遍历 hs.forEach(h -> System.out.println(h + "-" + m.get(h))); }
2.通过键值对对象遍历
// 1. 迭代器方式遍历 Set<Map.Entry<String, String>> entries = m.entrySet(); Iterator<Map.Entry<String, String>> it = entries.iterator(); Map.Entry e = null; while (it.hasNext()){ e = it.next(); System.out.println(e.getKey() + "++" + e.getValue()); } // 2.增强for循环遍历 for (Map.Entry<String, String> sse : m.entrySet()) { System.out.println(sse.getKey() + "--" + sse.getValue()); } // 3. lambda遍历 entries.forEach(entry -> System.out.println(entry.getKey() + "//" + entry.getValue()));
3.Map的遍历方式(lambda表达式)
forEach(BiConsumer< ? super K , ? super V> action) 结合lambda遍历Map集合
entries.forEach(entry -> System.out.println(entry.getKey() + " +++" + entry.getValue()));
HashMap
是Map的实现类
键:无序、不重复(会覆盖原有的entry对象)、无索引
HashMap跟HashSet底层原理是一样的,都是哈希表结构(根据键计算出hash值即可)
依赖HashCode和equals方法保证键的唯一(如果键存储的是自定义对象)
LinkedHashMap:是HashMap的子类
由键决定:有序、不重复、无索引
保证存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个键值对元素有额外的多了一个双链表的机制记录存储的顺序
TreeMap(底层基于红黑树实现排序,增删改查性能较好)
底层和tree底层原理一样,都是红黑树结构
由键决定特性:不重复、无索引、可排序
可排序:对键进行排序
默认按照键从小到大进行排序,也可以自己规定键的排序规则
实现:Comparable接口,指定比较规则
创建集合时传递Comparator比较器对象,指定比较规则
如果两种方式都存在,则主要以第二种方式比较
// TreeMap默认按照键升序,同时可以降序(需自己制定) TreeMap<Integer, String> isl = new TreeMap<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); // 简化后的lambda // 此为降序算法 //TreeMap<Integer, String> isl = new TreeMap<>((Integer o1, Integer o2) -> o2 - o1); Set<Map.Entry<Integer, String>> entries = isl.entrySet(); isl.put(22435 , "23333"); isl.put(234123 , "2222"); isl.put(1223 , "233333333"); entries.forEach(s -> System.out.println(s.getKey() + " +++" + s.getValue())); }
// TreeMap自定义排序规则 public int compareTo(Student o) { //this:表示当前要添加的元素 //o:表示已经在红黑树中存在的元素 //返回值 //负数:表示当前要添加的元素是小的,存左边 //正数:表示当前要添加的元素是大的,存右边 //0:表示相同,舍弃 // 比较的逻辑 i = this.getAge() - o.getAge() ? return this.getAge() - o.getAge(); }
利用map集合进行统计(K表示要统计的内容 - V 表示次数)
如果对结果不要求排序,默认使用HashMap
如果要求结果排序,使用TreeMap
// 统计字符出现次数 // 1.定义需要统计的字符串 String str = "giughiuahzzzzzzzzzzziasighuraghpoiaygreugasjhgfmzxkhigoai"; // 2. 创建集合用来接收数据 TreeMap<Character , Integer> tm = new TreeMap<>(); // 3. 将转换成char的字符串诸葛放入集合 for (int i = 0; i < str.length(); i++) { char a = str.charAt(i); if(tm.containsKey(a)){ Integer count = tm.get(a); System.out.println(count); count += 1; tm.put(a , count); }else{ tm.put(a , 1); } } System.out.println(tm);
String拼接字符串(在map的基础上进行拼接)
//StringBuilder拼接字符串用法 StringBuilder sb = new StringBuilder(); tm.forEach((key , value) -> sb.append(key).append(" (").append(tm.get(key)).append(") ")); // StringJoiner拼接字符串用法 StringJoiner sj = new StringJoiner("" , "" , ""); tm.forEach((key , value) -> sj.add(key + "").add(" (").add(tm.get(key) + "").add(") ")); System.out.println(sj);
TreeMap集合排序的两种方式
1.实现Comparable接口,指定比较规则
2.创建集合时传递Comparator比较器对象,指定比较规则
源码解析
与类名相同的方法,均是类中的构造方法,和类名不同的均为类内部的成员方法→↑↓←
↑+父类或接口名 表示该方法是重写父类的方法或者实现接口的方法
本身就是灰色方法(从→后类名继承过来的方法)灰直接跳转到→类名
f表示hash的属性,也可能是成员属性
HashMap底层源码
当添加键值对对象的时候,如果键值对对象的键在集合中已经存在,则会对已经存在的键值对对象进行覆盖,此时的覆盖只是将键值对的值进行修改(覆盖),并没有将其已经存在的键值对对象从内存中移除,仅仅修改了内部的值,然后将已经被覆盖的值作为返回值返回
底层是数组+链表+红黑树组成的
hashMap的成员变量:
table记录了数组的地址值
DEFAULT_INITIAL_CAPACITY = 1 << 4; 16 //初始容量
DEFULT_LOAD_FACTOR = 0.75f; // 默认扩展因子(数组每次扩容至原来容量的两倍)
MAXIMUM_CAPACITY = 1 << 30; // 表示数组最大容量
构造方法:
hash(Object obj) 表示计算处该对象的hash值
无参构造:只是默认将加载因子0.75赋给loadFactor(此时底层的数组还未创建,在添加元素put的时候才会进行数组的创建)
添加第一个元素
put方法:返回被覆盖元素的值,如果没有覆盖返回null
putVal(hash(key) , key , value , false , true);
1.hash(key) 通过key计算出hash值
onlyIfAbsen:默认为false: 表示重复的数据会将原来的数据覆盖,如果为true,则表示老元素的值保留,不会覆盖
Node<K,V> [] tab; // 定义一个局部变量,用来记录哈希表中数组的地址值(Hash已经有一个成员变量是tab了,这时定义tab的意义是因为,如果不定义,每次调用tab的值的时候,都要和堆进行交互,从堆中寻找值,而方法存在栈中,浪费资源,如果定义在方法内,则不需要在堆栈之间来回交互)
Node<K,V> p; 临时的第三方变量,用来记录键值对对象的地址值
int n; 表示当前数组的长度
int i; 表示索引
tab = table; // 将哈希表中的数组的地址值,赋值给局部变量tab // 此时||的精妙之处在于 无论前面一个条件是否是true,后面的n = tab.length;都会执行,减少赋值 if(tab == null || (n = tab.length) == 0){ // resize // 1. 如果是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组 // 2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件,如果没有达到扩容条件,底层不会做任何操作,如果达到了扩容的条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中 // 再把对当前数组的长度赋值给n n = (tab = resize()).length; }
// i = (n - 1) & hash; // index 索引的位置 // 拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置 // p = tab[i] 获取数组中对应元素的数据 if((p = tab[i = (n - 1) & hash]) == null){ // 通过new Node创建出一个键值对对象,将键值对对象放在数组下标为i的位置 tab[i] = new Node(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 = new Node(hash , key , value , null); if(binCount >= TREEIFY_THRESHOLD - 1){ treeifyBin(tab , hash); break; } if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if(e != null){ V oldValue = e.value; if(!onlyIfAbsent || oldValue == null){ e.value = value; } afterNodeAccess(e); return oldValue; } } ++modCount; // 并发修改时产生作用 // threshold: 记录的是数组的长度 * 0.75/哈希表的扩容时机 if(++size > threshold){ resize(); } afterNodeInsertion(evict); // LinkedHashMap中产生作用 return null; // 当前没有覆盖任何元素,返回null }
添加第二个元素
拿着第二个元素的键的hash值和数组的长度做运算,算出其应该所在的位置,判断所在位置是否为null,如果为null,则直接添加
如果数组的位置不为null,键不重复,挂在下面形成链表或者红黑树
依然调用PutVal
获取数组的地址值,赋值给局部变量// 减少堆栈之间来回取值造成的资源浪费
第三方变量p,用来记录键值对对象的地址值
int n 表示当前数组的长度
int i 表示索引
判断数组是否为空,如果不为空,则继续||后面的n = tab.length// 将数组的长度赋给n
拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象在数组中应存入的位置
i = (n - 1) & hash;
获取数组中对应元素的数据
p = tab[i];
i = (n - 1) & hash; // index 索引的位置 // 拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置 p = tab[i] 获取数组中对应元素的数据 if ((p = tab[i = (n - 1) & hash]) == null) { // 通过new Node创建出一个键值对对象,将键值对对象放在数组下标为i的位置 tab[i] = new Node(hash, key, value, null); } else { Node<K, V> e; K K; // p.hash == hash // 等号的左边 是当前数组元素中的键值对对象的hash值 // 等号的右边 是当前要添加的键值对的hash值 // 如果不一样 返回的false if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) { e = p; } else if (p instanceof TreeNode) {// 判断数组中获取到的键值对p是不是树上的节点 ,如果是则会调用putTreeVal方法 将当前要添加的键值对按照红黑树的规则添加,如果不是 则会走下一个else e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); } else { // 如果不是树上的节点,则会以链表的方式进行添加 // for循环的判断条件不写,默认为true for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { // 判断从数组中获取到的键值对对象是否还有下一个对象 // 创建一个新的节点,挂在p对应的数组中的值的下面 // 如果条件判读为true 则将要添加的元素挂在p下面 p.next = new Node(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) { // 判断当前链表长度是否大于8,如果大于8则会调用treeifyBin treeifyBin(tab, hash); // treeifyBin底层还会继续判断数组的长度是否大于64,如果同时满足两个条件,则将链表转为红黑树 break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // 如果e为空,则不需要覆盖原来的键值对对象 V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) { e.value = value; } afterNodeAccess(e); return oldValue; } } ++modCount; // 并发修改时产生作用 // threshold: 记录的是数组的长度 * 0.75/哈希表的扩容时机 if (++size > threshold) { resize(); } afterNodeInsertion(evict); // LinkedHashMap中产生作用 return null; // 当前没有覆盖任何元素,返回null }
如果数组的位置不为null,键重复
put方法:返回被覆盖元素的值,如果没有覆盖返回null
putVal(hash(key) , key , value , false , true);
参数以、hash(key) 通过key计算出hash值
参数四、onlyIfAbsen:默认为false: 表示重复的数据会将原来的数据覆盖,如果为true,则表示老元素的值保留,不会覆盖
如果和数组中的元素比较后,不一样,则会依次和该位置下的链表中的所有的对象进行比较,
依然调用PutVal
获取数组的地址值,赋值给局部变量// 减少堆栈之间来回取值造成的资源浪费
第三方变量p,用来记录键值对对象的地址值
int n 表示当前数组的长度
int i 表示索引
判断数组是否为空,如果不为空,则继续||后面的n = tab.length// 将数组的长度赋给n
拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象在数组中应存入的位置
i = (n - 1) & hash;
获取数组中对应元素的数据
p = tab[i];
i = (n - 1) & hash; // index 索引的位置 // 拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置 p = tab[i] //获取数组中对应元素的数据 if ((p = tab[i = (n - 1) & hash]) == null) { // 通过new Node创建出一个键值对对象,将键值对对象放在数组下标为i的位置 tab[i] = new Node(hash, key, value, null); } else { Node<K, V> e; K K; // p.hash == hash // 等号的左边 是当前数组元素中的键值对对象的hash值 // 等号的右边 是当前要添加的键值对的hash值 // 如果不一样 返回的false if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) { e = p; } else if (p instanceof TreeNode) {// 判断数组中获取到的键值对p是不是树上的节点 ,如果是则会调用putTreeVal方法 将当前要添加的键值对按照红黑树的规则添加,如果不是 则会走下一个else e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); } else { // 如果不是树上的节点,则会以链表的方式进行添加 // for循环的判断条件不写,默认为true for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { // 判断从数组中获取到的键值对对象是否还有下一个对象 // 创建一个新的节点,挂在p对应的数组中的值的下面 // 如果条件判读为true 则将要添加的元素挂在p下面 p.next = new Node(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) { // 判断当前链表长度是否大于8,如果大于8则会调用treeifyBin treeifyBin(tab, hash); // treeifyBin底层还会继续判断数组的长度是否大于64,如果同时满足两个条件,则将链表转为红黑树 break; } // 此情况逻辑主要为当添加的元素和数组或链表内存储的对象重复 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // 先比较当前要添加的元素的hash值和将要预期进行比较的对象的hash值是否相同 // 如果为true 还要调用equals进行对键内部的属性值进行比较(此做法为了避免hash碰撞造成的误判问题) // 如果一样则会break,跳出当前判断 break; } p = e; // 在经过一次循环后,将e的值(为p下链表中第一个元素)赋给p,由此形成新添加的对象和数组下链表的所有的对象进行比较 } if (e != null) { // 如果e为空,则不需要覆盖原来的键值对对象 // 如果e不为null,表示当前的键是一样的,值才会被覆盖 V oldValue = e.value; // 记录将要被覆盖的键值对对象的value if (!onlyIfAbsent || oldValue == null) { // 判断此集合是否允许进行值覆盖 // 等号的左边 需要覆盖的值的value // 等号的右边 当前要添加的键值对对象的value e.value = value; // 执行覆盖值的操作 } afterNodeAccess(e); return oldValue; // 返回被覆盖的值 } } ++modCount; // 并发修改时产生作用 // threshold: 记录的是数组的长度 * 0.75/哈希表的扩容时机 if (++size > threshold) { resize(); } afterNodeInsertion(evict); // LinkedHashMap中产生作用 return null; // 当前没有覆盖任何元素,返回null } }
TreeMap底层源码
每一个元素都是node的对象,Node方法又实现了Map.Entry接口
每个Entry又四个成员属性:hash(通过键计算出来的hash值) 、 key 、 value 、 next(记录下一个节点的地址值)
当到达指定阈值,将链表扩展为红黑树时,则开始调用TreeNode()继承了LinkedHashMap.Entry<K,V>含四个属性(在有Entry的四个属性之后再加以下五个属性)
parent:父节点 left:左子结点 right:右子节点 prev: boolean red;(true为红色/false为黑色)
底层是红黑树结构
每一个元素都是一个Entry对象
由默认常量 RED = false;BLACK = true;
comparator表示比较的规则
root表示根节点
size表示数组的长度也表示红黑树中节点的个数
TreeMap中有默认的六个属性
K key V value
Entry<K,V> left
Entry<K,V> right
Entry<K,V> parent
boolean color = BLACK; // 记录节点颜色
空参构造只是将null赋值给comparator // 表示没有比较器对象
带参构造只是将传递过来的比较器对象传递给comparator
put方法底层原理
// 1.TreeMap中每一个节点的内部属性 K key; // 键 V value; // 值 Entry<K,V> left; // 左子结点 Entry<K,V> right; // 右子结点 Entry<K,V> parent; // 父结点 boolean color; // 节点的颜色 // 2. TreeMap类中要知道的一些成员变量 public class TreeMap<K,V>{ // 比较器对象 private final Comparator<? super K> comparator; // 根节点 private transient Entry<K,V> root; // 集合的长度 private transient int size = 0; // 3.空参构造 public TreeMap(){ // 空参构造就是没有传递比较器对象 comparator = null; } // 4. 带参构造 // 带参构造就是传递了比较器对象 public TreeMap(Comparator<? super K> comparator){ this.comparator = comparator; } // 5. 添加元素 public V put(K key , V value){ return put(key , value , true); } // 参数三 当键重复的时候,是否需要覆盖,如果为true, 覆盖, false不覆盖 private V put(K key , V value , boolean replaceOld){ Entry<K , V> t = root; // 获取根节点的地址值,将其赋值给局部变量t,避免了重复获取的资源浪费 if(t == null){ // 判断根节点是否为空 如果为空,表示当前为第一次添加元素,则将当前要添加的元素当做根节点, 如果不为空,则继续执行下面的代码 addEntryToEmptyMap(key , value); // 在底层会创建(new) 一个entry对象,并将其地址值赋给root根节点 return null; // 直接返回null 表示此时并没有覆盖任何元素 } int cmp; // 表示当前要添加元素和已经存储的元素的key比较后的值, 如果是负数,则存储在左节点,如果是正数,则存在右节点 Entry<K , V> PARENT; // 表示当前要添加节点的父节点 Comparator<? super K> cpr = comparator; // 表示当前的比较规则,并将其赋值给cpr , 如果没有自定义比较器,那么此时传入的comparator为null,cpr也为null if(cpr != null){ // 表示此时如果传入了比较器,那么cpr就不为null 就按照if下的逻辑执行,如果没有给定比较器,那么就是null,此时就会执行else内的逻辑 do{ // 以下逻辑和使用默认排序规则排序方式相同 parent = t; cmp = cpr.compare(key, t.key); // 此处为唯一的区别,就是调用的是自定义的比较规则,使用自定义比较规则之后,对其进行排序 if(cmp < 0) t = e.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 { // 这里将k强转为Comparator类型的,这就要求k必须实现comparable接口,如果没有实现,就会在强转的时候出错 Comparator<? super K> k = (Comparator<? super K>) key; do{ parent = t; // 此时将根节点当做当前节点的父节点 也记录经过一次比较之后,需要放在某个元素后面的某个元素的地址值 cmp = k.compareTo(t.key); // 此时调用compareTo方法比较两个key的大小关系 if(cmp < 0) // 如果比较的结果为负数 t =t.left; // 那么继续到根节点的左边去找 else if (cmp > 0) // 如果比较的结果为正数, t = t.right; // 那么继续到根节点的右边去找 else{ V oldValue = t.value; // 如果比较结果为0 , 那么就会覆盖 此时oldValue记录的是老元素的值 if(replaceOld || oldValue == null){ // 同hashmap完全相反 如果为true覆盖,hashmap中是!replaceOld t.value = value; // 将老元素的值覆盖 } return oldValue; // 将被覆盖的值进行返回 } } while (t != null); // 循环遍历t的左右子节点 , 直至没有元素时,这时t才为null } addEntry(key , value , parent , cmp < 0); // 把当前元素按照指定的规则进行添加 return null; } private void addEntry(K key , V value , Entry<K , V> parent , boolean addToLeft) { Entry<K, V> e = new Entry<>(key, value, parent); // 创建一个entry对象,并且记录了他的父节点的值 if (addToLeft) //如果是添加到父节点的左边 parent.left = e; // 将父节点的left节点记录为当前entry else parent.right = e; // 如果添加的entry对象添加在父节点的右边 则将父节点的left节点记录为当前entry fixAfterInsertion(e); // 添加完之后需要按照红黑树的规则进行调整 size++; modCount++; } public void fixAfterInsertion(Entry<K , V> x){ // x表示当前要添加的节点 x.Color = RED; // 调整的第一件事,就是将节点变成红色 此时便应了当需要往TreeMap内添加元素的时候,默认为红色的理论 // 按照红黑规则进行调整 // x != null 对x对象进行费控判断 // x != root 如果此时x为根,那么循环体直接结束,直接将根节点变为黑色即可 // x.parent.color == RED 并且判断当前节点的父节点是否为红色 // parentof 此方法为获取到传递节点对象的父节点 leftof获取左子结点 while (x != null && x != root && x.parent.color == RED) { // parentof(parentof(x))) 获取x的爷爷节点 if (parentof(x) == leftof(parentof(parentof(x)))) { //判断当前节点的父节点是当前节点的爷爷节点的左子结点还是右子节点,为了获取当前节点的叔叔节点 // 如果进入此循环体,则表明当前节点的父节点是当前节点的爷爷节点的左子结点 Entry<K, V> y = rightof(parentof(parentof(x))); // 获取当前节点的爷爷节点的右子节点(即当前节点的叔叔节点),同时将其赋值给entry对象y 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))) { // 如果当前节点是当前节点的父节点的右子节点 , 则进入if内部的逻辑 x = parentof(x); // 将当前节点的父节点赋值给当前节点 , rotateLeft(x); // 以当前节点为参照物进行左旋 此时当前节点已经是老的当前节点的父节点 } setColor(parentof(x), BLACK); // 将当前节点的父节点设置为黑色 setColor(parentof(parentof(x)), RED); // 将当前节点的爷爷节点设置为红色 rotateRight(parentof(parentof(x))); // 以当前节点的爷爷节点作为参照物进行右旋 } } else { // 进入else则证明 则表明当前节点的父节点是当前节点的爷爷节点的右子结点 Entry<K, V> y = leftof(parentof(parentof(x))); // 获取当前节点的爷爷节点的左子节点(即当前节点的叔叔节点),同时将其赋值给entry对象y if (colorof(y) == RED) { // colorof获取当前节点的颜色 (即获取当前叔叔节点的颜色) // 进入此逻辑则表明,当前节点的叔叔节点为红色 , 则采取对应的处置规则 setColor即设置节点的颜色 setColor(parentof(x), BLACK); // 将父节点设置成黑色 setcolor(y, BIACK); // 把叔叔的颜色设置为黑色 此时叔叔是当前节点的爷爷节点的左子结点 setColor(parentof(parentof(x)), RED); // 将当前节点的爷爷节点设置为红色 x = parentof(parentof(x)); // 将爷爷节点赋值给当前节点,表示以当前节点的爷爷节点作为参照物在进行判断 如果当前节点的爷爷是根节点,则直接将其设置为黑色 } else { if (x == rightof(parentof(x))) { // 判断当前节点是否为父节点的右子节点 ,此判断为了将当前节点变为当前节点的爷爷节点的左子结点 x = parentof(x); // 如果当前节点是当前节点的父节点的右子节点,会将当前节点的父节点作为参照物,然后进行左旋,并继续循环判断 rotateLeft(x); // 此时x已经是当前节点父节点,这就表示将以当前节点的父节点为参照物进行左旋, 然后在进行其他判断 } setColor(parentof(x), BLACK); // 将当前节点的父节点设置为黑色 此时如果已经经过了上面的那个if判断,那么当前节点就为老的当前节点的父节点 setColor(parentof(parentof(x)), RED); // 将当前节点的爷爷节点设置为红色 rotateRight(parentof(parentof(x))); // 以当前节点的爷爷节点为参照物进行右旋 } } } root.color = BLACK; // 将根节点设置为黑色 } }
问题
1.TreeMap添加元素的时候,键是否需要重写hashCoad和equals方法
不需要重写,TreeMap的底层没有用到hash和equals
2.HashMap是哈希表结构的,JDK8开始由数组,链表,红黑树组成的,既然有红黑树,HashMap是否需要利用Comparable指定排序规则或者传递比较器comparator指定比较规则?
不需要,因为在hashmap的底层,默认是利用哈希值的大小关系来创建红黑树的
3.TreeMap和HashMap谁的效率更高
如果是最坏(极端的情况)情况,那此时tremap的效率高
但是一般还是hashmap的效率更高
4.你觉得在Map集合中,java会提供一个如果键重复了,不会覆盖的put方法吗
hashmap和treemap都提供了键重复不覆盖的方法
代码中的两面性,如果我们只知道了其中的A面,并且发现代码中还有变量控制两面性,那么该逻辑一定会有B面
习惯:
boolean类型的变量控制,一般只有AB两面
int类型的变量控制,一般至少有三面
5.三种双列集合,以后如何选择?
HashMap LinkedHashMap TreeMap
默认用hashmap(效率高)
如果要保证存取有序:LinkedHashMap
如果要进行排序:TreeMap
可变参数
...
集合工具类Collections
addAll(Collection<T> c , T ... elements) 批量添加元素
shuffle(List<?> list) 打乱List集合元素的顺序
sort(List<T>) 排序
sort(List<T> list , Co,parator<T> c) 按照制定规则排序
binarySearch(List<T> list , T key) 以二分查找法查找元素
copy(List<T> dest , List<T> src) 拷贝集合中的元素
fill(List<T> list , T obj) 使用指定的元素填充集合
max/min (Collection<T> coll) 根据默认的自然排序获取最大/最小值
swap(List<?> list , int i , int j) 交换集合中指定位置的元素