集合的体系结构
- 集合主要分为两类:
- 单列集合
- 双列集合
一、单列集合
-
list系列集合:添加的元素是有序、可重复、有索引的。
- 有序:指的是存和取的顺序是一致的
-
set系列集合:添加的元素是无序、不可重复、无索引的。
-
collection:collection是一个接口,是单列集合的最顶层接口,它的功能是全部单列集合都可以继承使用。
-
collection:常见的方法:
collection集合的遍历方式
- 迭代器遍历
- 迭代器遍历是集合的专用遍历方法
- Iterator中的常用方法
- boolean hasNext(): 判断当前位置是否有元素可以被取出
- E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置(E是泛型,你集合存储的数据的类型)
- 注意点:
- 1.报错NoSuchElementException(当迭代器的指针遍历完毕后,再使用next方法获取数据就会报错)
- 2.迭代器遍历完毕,==指针不会复位 ==
- 3.循环中只能用一次next方法
- 4.迭代器遍历时,不能用集合的方法进行增加或者删除
- 5.想要继续第二次遍历集合,只能再次获取一个新的迭代器对象
public class IteratorDemo1 {public static void main(String[] args) {//创建集合对象Collection<String> c = new ArrayList<>();//添加元素c.add("hello");c.add("world");c.add("java");c.add("javaee");//Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到Iterator<String> it = c.iterator();// 获取集合的迭代器//用while循环改进元素的判断和获取while (it.hasNext()) { // 判断迭代器是否有元素String s = it.next(); // 获取元素并把指针往下传System.out.println(s);}}
}
- 增强for循环
- 增强for循环的底层就是一个迭代器,就是为了简化迭代器的代码书写的
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
- 实现Iterable接口的类才可以使用迭代器和增强for(所有的单列集合和数组才可以使用增强for循环进行遍历)
- 修改增强for循环中的变量,不会改变集合中原来的数据
public class AddFor {public static void main(String[] args) {/*语法:for(集合/数组中元素的数据类型 变量名 : 集合/数组名) {// 已经将当前遍历到的元素封装到变量中了,直接使用变量即可}*/ArrayList<String> list = new ArrayList<>();list.add("a");list.add("b");list.add("c");//1,数据类型一定是集合或者数组中元素的类型//2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素//3,list就是要遍历的集合或者数组for(String str : list){System.out.println(str); }for(String str : list){str="999"; //这里即时修改了str的值,但是集合里面的数据依旧是原来的数据}System.out.println(list); //集合的数据不会被修改}
}
- lambda表达式
- 利用forEach方法,再结合lambda表达式的方式进行遍历
- 利用forEach方法,再结合lambda表达式的方式进行遍历
public class lambda {public static void main(String[] args) {/*lambda表达式遍历:default void forEach(Consumer<? super T> action):*///1.创建集合并添加元素Collection<String> coll = new ArrayList<>();coll.add("zhangsan");coll.add("lisi");coll.add("wangwu");//2.利用匿名内部类的形式//底层原理://其实是通过for循环遍历集合,依次得到每一个元素//然后把得到的每一个元素,传递给下面的accept方法// s依次表示集合中的每一个数据/* coll.forEach(new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}});*///lambda表达式/* coll.forEach((String s) -> {System.out.println(s)});*/
// 简写coll.forEach(s -> System.out.println(s));}
}
一、1、List系列集合
-
List集合的特点
- 存取有序
- 可以重复
- 有索引
-
list集合特有的方法:
- list实现了Collection接口,collection接口的所有方法它都有
- list实现了Collection接口,collection接口的所有方法它都有
-
list集合的五种遍历方法:(collection集合的三种+独有的两种)、
-
- 迭代器
-
- 列表迭代器(是迭代器的子接口,额外添加了一个方法:在遍历的过程中,可以添加元素)
-
- 增强for
-
- Lambda表达式
-
- 普通for循环
-
-
list集合删除元素的方式:
- 1.直接删除元素
- 2.通过索引进行删除
数据结构
-
数据结构:计算机存储、组织数据的方式
- 是指数据相互之间是以什么方式排列在一起的
-
数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择
-
常见的数据结构:
-
栈:后进先出,先进后出
- 栈的应用场景:java的内存结构——栈内存:先运行的方法最后再出栈
- 栈的应用场景:java的内存结构——栈内存:先运行的方法最后再出栈
-
队列:先进先出,后进后出;
-
数组:查询快,增删慢;
- 每次删除都要把数组的数据前移
- 每次新增都要把数组的数据后移
-
链表:链表中的结点是独立的对象,在内存中是不连续的,每个结点(单向链表的结点)包含数据值和下一个结点的地址。(双向链表的结点包含三部分:前一个节点的地址,数据值,下一个结点的地址)
- 链表查询慢,无论查询哪个数据都要从头开始找
- 链表增删相对快(相比数组而言)
-
二叉树
-
二叉查找树
-
平衡二叉树
-
红黑树
-
ArrayList底层源码分析
-
ArrayLisr的底层数据结构是数组,查询快、增删慢
-
核心步骤:
- 第一步:在使用
空参
创建ArrayList对象的时候,它在底层会先创建一个长度=0的数组;- 底层数组的名称:elementDate
- 变量记录元素的个数的名称:size
- size的两层含义:元素的个数,下一个元素的存入位置
- 第二步:添加第一个元素的时候,元素会新的长度=10(10是数组底层的默认长度)的数组
- 第三步:继续添加元素,添加完毕后,size++;
- 第四步:当添加的元素存满的时候,就会触发数组的扩容机制。
- 扩容时机一:当数组存满的时候,会创建一个新的数组,
新数组的长度=原来的1.5倍
,再把原来数组的所有元素全拷贝到新数组中。’ - 扩容时机二:一次性添加多个数据的时候,如果扩容1.5倍数组的容量还不够,那么新创建的数组的长度则以实际的长度为准(即添加后的数组有多少个元素,数组的长度就是多少)
- 扩容时机一:当数组存满的时候,会创建一个新的数组,
- 第一步:在使用
-
扩容时机1:
-
扩容时机2:
LinkedList底层源码分析
- linkedList的底层数据结构是双向链表,查询慢,增删相对较快,但是首尾元素的操作的速度快,因为提供了很多首尾操作的特有的API。
- 底层源码分析:
-
- 刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
-
- 添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
-
- 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值
- 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值
-
-
添加第一个元素
-
添加第二个元素
-
添加三个元素
迭代器源码分析:
迭代器遍历相关的三个方法:
-
Iterator iterator() :获取一个迭代器对象
-
boolean hasNext() :判断当前指向的位置是否有元素
-
E next() :获取当前指向的元素并移动指针
泛型的使用
-
泛型可以在编译阶段约束操作的数据类型,并进行检查
-
泛型的格式:<数据类型>
-
泛型只能支持引用数据类型
-
泛型的好处:
- 统一数据类型
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来了。
java中的泛型是伪泛型
-
泛型的细节:
- 泛型中不可以写基本数据类型(在定义集合的泛型的时候,会把存入的数据的类型由定义的泛型转换成Object类型的,如果是基本数据类型则无法转换成object类型)
- 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类型
- 如果不写泛型,类型默认是Object
-
泛型类、泛型方法、泛型接口:
一、2、set系列集合
数据结构
-
数据结构:计算机存储、组织数据的方式
- 是指数据相互之间是以什么方式排列在一起的
-
数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择
-
常见的数据结构:
-
栈:后进先出,先进后出
- 栈的应用场景:java的内存结构——栈内存:先运行的方法最后再出栈
- 栈的应用场景:java的内存结构——栈内存:先运行的方法最后再出栈
-
队列:先进先出,后进后出;
-
数组:查询快,增删慢;
- 每次删除都要把数组的数据前移
- 每次新增都要把数组的数据后移
-
链表:链表中的结点是独立的对象,在内存中是不连续的,每个结点(单向链表的结点)包含数据值和下一个结点的地址。(双向链表的结点包含三部分:前一个节点的地址,数据值,下一个结点的地址)
- 链表查询慢,无论查询哪个数据都要从头开始找
- 链表增删相对快(相比数组而言)
-
二叉树:二叉树中,任意一个节点的度要小于等于2
- 节点: 在树结构中,每一个元素称之为节点
- 度: 每一个节点的子节点数量称之为度
-
二叉查找树:二叉查找树,又称二叉排序树或者二叉搜索树
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
-
平衡二叉树:任意一个结点的左右子树高度差不能超过1
- 旋转的触发机制:
- 当添加一个节点之后,该树不再是一颗平衡二叉树
- 当添加一个节点之后,该树不再是一颗平衡二叉树
- 旋转的触发机制:
-
左左: 当根节点左子树的左子树有节点插入,导致二叉树不平衡
-
左右: 当根节点左子树的右子树有节点插入,导致二叉树不平衡
-
右右: 当根节点右子树的右子树有节点插入,导致二叉树不平衡
-
右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡
-
红黑树:红黑树增删改查性能都很好。
-
-
set系列集合特点:
- 无序:存取顺序不一致
- 不重复:可以去除重复的元素
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
-
set集合的实现类
- HashSet:无序、重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:可排序、不重复、无索引
Set接口中的方法基本上与Collection的API一致
Hashset集合
-
hashSet集合底层数据结构是采取哈希表(数组+链表+红黑树) 存储数据
- 哈希表是一种对于增删改查性能都较好的数据结构
- 哈希表的组成:
- JDK8之前:数组+链表
- JDK8之后:
数组+链表+红黑树
- 哈希表的组成:
- 哈希表是一种对于增删改查性能都较好的数据结构
-
哈希值:
- 哈希值是根据hashCode方法算出来的int类型的整数
- 该方法定义在Object类中,所有对象都可以调用,
默认使用地址值进行计算
- 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
-
对象的哈希值特点:
- 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
- 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
- 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可以一样(哈希碰撞)(比如当创建对象的个数大于int类型的范围的时候,超出来的哈希对象的哈希值必定就是重复的)
-
底层原理:
- hashSet的底层是
hashMap
,第一次添加元素的时候,创建一个默认长度为16
的数组,默认的加载因子为0.75
,数组名table。 -
- 添加一个元素时,先得到该元素的
hash值
,然后根据hash值和数组的长度的与运算
计算出对应的索引值(元素存储的位置)
- 添加一个元素时,先得到该元素的
-
- 判断当前索引位置的值是否为null:
- 如果是null就直接存入
- 如果不是null,表示该位置有元素,则调用equals与该位置的元素进行比较属性值:
- 属性值一样:放弃添加数据
- 属性值不一样,
存入数组,形成链表,新元素直接挂载老元素的下面
-
- 扩容机制:
- 第一次添加元素,数组table扩容到16,临界值=16*0.75=12,0.75是加载因子
- 如果table数组使用到了临界值,数组的大小就会扩容到原来的两倍,新的临界值=16x2x0.75=24,依此类推。
- 当一条链表的元素个数到达
8
时,且table数组的大小>=64
,就会进行树化
(链表直接变成红黑树),否则仍然采用数据的扩容机制。
- hashSet的底层是
集合中存储的数组是自定义对象,必须重写hashCode方法和equals方法。
LinkedHashset集合
- 底层原理:底层数据结构也是使用哈希表(数组+双向链表),只是每个元素又额外多了一个
双链表的机制
记录存储的顺序。(底层维护的是一个LinkedHashMap(是HashMap的子类)
- 有序:LinkedHashSet内部使用了一个双向链表来维护元素的插入顺序,因此遍历LinkedHashSet时,元素的顺序将与它们的插入顺序一致。
TreeSet集合
- TreeSet的底层是基于红黑树的数据结构实现排序的,增删改查性能都很好
public class TreeSet_ {public static void main(String[] args) {//解读//1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的//2. 如果希望添加的元素,按照字符串大小来排序//3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)// 并指定排序规则//4. 简单看看源码//解读/*1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparatorpublic TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;}2. 在 调用 treeSet.add("tom"), 在底层会执行到if (cpr != null) {//cpr 就是我们的匿名内部类(对象)do {parent = t;//动态绑定到我们的匿名内部类(对象)comparecmp = cpr.compare(key, t.key);if (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;else //如果相等,即返回0,这个Key就没有加入return t.setValue(value);} while (t != null);}*/// TreeSet treeSet = new TreeSet();TreeSet treeSet = new TreeSet(new Comparator() {@Overridepublic int compare(Object o1, Object o2) {//下面 调用String的 compareTo方法进行字符串大小比较//如果老韩要求加入的元素,按照长度大小排序//return ((String) o2).compareTo((String) o1);return ((String) o1).length() - ((String) o2).length();}});//添加数据.treeSet.add("jack");treeSet.add("tom");//3treeSet.add("sp");treeSet.add("a");treeSet.add("abc");//如果是按照长度大小的规则来比较的,这个abc是加不进去的,因为上面的tom的长度也是3.(总的来说就是:return ((String) o1).length() - ((String) o2).length();如果return的值为0,那么这个key就加入不了。System.out.println("treeSet=" + treeSet);}
}
二、双列集合
- 双列集合的特点:用于报错具有映射关系的数据:key-value
-
- 双列集合一次需要
存一对数据
,分别是键和值(k,v)
- 双列集合一次需要
-
键(key)不能重复
,值(value)可以重复
-
- 双列集合中的
key可以为nu
ll,value也可以为null,key为null只有一个,value为null可以有多个。
- 双列集合中的
-
- 键和值是一一对应的,每一个键只能找到自己对应的值
-
- 键+值这个整体我们称之为“键值对”或者“键值对对象“,在java中叫做”Entry对象”。
-
Map系列集合常见的API
- map集合的获取功能
- map集合的遍历方法1:使用keySet()
public class MapDemo01 {public static void main(String[] args) {//创建集合对象Map<String, String> map = new HashMap<String, String>();//添加元素map.put("张无忌", "赵敏");map.put("郭靖", "黄蓉");map.put("杨过", "小龙女");//获取所有键的集合。用keySet()方法实现Set<String> keySet = map.keySet();//遍历键的集合,获取到每一个键。用增强for实现for (String key : keySet) {//根据键去找值。用get(Object key)方法实现String value = map.get(key);System.out.println(key + "," + value);}}
}
- map集合的遍历方法2:Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
public class MapDemo02 {public static void main(String[] args) {//创建集合对象Map<String, String> map = new HashMap<String, String>();//添加元素map.put("张无忌", "赵敏");map.put("郭靖", "黄蓉");map.put("杨过", "小龙女");//获取所有键值对对象的集合Set<Map.Entry<String, String>> entrySet = map.entrySet();//遍历键值对对象的集合,得到每一个键值对对象for (Map.Entry<String, String> me : entrySet) {//根据键值对对象获取键和值String key = me.getKey();String value = me.getValue();System.out.println(key + "," + value);}}
}
HashMap源码解读
/*
1.看源码之前需要了解的一些内容Node<K,V>[] table 哈希表结构中数组的名字DEFAULT_INITIAL_CAPACITY: 数组默认长度16DEFAULT_LOAD_FACTOR: 默认加载因子0.75HashMap里面每一个对象包含以下内容:
1.1 链表中的键值对对象包含: int hash; //键的哈希值final K key; //键V value; //值Node<K,V> next; //下一个节点的地址值1.2 红黑树中的键值对对象包含:int hash; //键的哈希值final K key; //键V value; //值TreeNode<K,V> parent; //父节点的地址值TreeNode<K,V> left; //左子节点的地址值TreeNode<K,V> right; //右子节点的地址值boolean red; //节点的颜色*/
//2.添加元素
HashMap<String,Integer> hm = new HashMap<>();
hm.put("aaa" , 111);
hm.put("bbb" , 222);
hm.put("ccc" , 333);
hm.put("ddd" , 444);
hm.put("eee" , 555);/*
添加元素的时候至少考虑三种情况:
2.1数组位置为null
2.2数组位置不为null,键不重复,挂在下面形成链表或者红黑树
2.3数组位置不为null,键重复,元素覆盖
*///参数一:键
//参数二:值//返回值:被覆盖元素的值,如果没有覆盖,返回null
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}//利用键计算出对应的哈希值,再把哈希值进行一些额外的处理
//简单理解:返回值就是返回键的哈希值
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}//参数一:键的哈希值
//参数二:键
//参数三:值
//参数四:如果键重复了是否保留
// true,表示老元素的值保留,不会覆盖
// false,表示老元素的值不保留,会进行覆盖
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {//定义一个局部变量,用来记录哈希表中数组的地址值。Node<K,V>[] tab;//临时的第三方变量,用来记录键值对对象的地址值Node<K,V> p;//表示当前数组的长度int n;//表示索引int i;//把哈希表中数组的地址值,赋值给局部变量tabtab = table;if (tab == null || (n = tab.length) == 0){//1.如果当前是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组//2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件//如果没有达到扩容条件,底层不会做任何操作//如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中tab = resize();//表示把当前数组的长度赋值给nn = tab.length;}//拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置i = (n - 1) & hash;//index//获取数组中对应元素的数据p = tab[i];if (p == null){//底层会创建一个键值对对象,直接放到数组当中tab[i] = newNode(hash, key, value, null);}else {Node<K,V> e;K k;//等号的左边:数组中键值对的哈希值//等号的右边:当前要添加键值对的哈希值//如果键不一样,此时返回false//如果键一样,返回trueboolean b1 = p.hash == hash;if (b1 && ((k = p.key) == key || (key != null && key.equals(k)))){e = p;} else if (p instanceof TreeNode){//判断数组中获取出来的键值对是不是红黑树中的节点//如果是,则调用方法putTreeVal,把当前的节点按照红黑树的规则添加到树当中。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);//判断当前链表长度是否超过8,如果超过8,就会调用方法treeifyBin//treeifyBin方法的底层还会继续判断//判断数组的长度是否大于等于64//如果同时满足这两个条件,就会把这个链表转成红黑树if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);break;}//e: 0x0044 ddd 444//要添加的元素: 0x0055 ddd 555//如果哈希值一样,就会调用equals方法比较内部的属性值是否相同if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){break;}p = e;}}//如果e为null,表示当前不需要覆盖任何元素//如果e不为null,表示当前的键是一样的,值会被覆盖//e:0x0044 ddd 555//要添加的元素: 0x0055 ddd 555if (e != null) {V oldValue = e.value;if (!onlyIfAbsent || oldValue == null){//等号的右边:当前要添加的值//等号的左边:0x0044的值e.value = value;}afterNodeAccess(e);return oldValue;}}//threshold:记录的就是数组的长度 * 0.75,哈希表的扩容时机 16 * 0.75 = 12if (++size > threshold){resize();}//表示当前没有覆盖任何元素,返回nullreturn null;}
TreeMap源码解读
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) {//获取根节点的地址值,赋值给局部变量tEntry<K,V> t = root;//判断根节点是否为null//如果为null,表示当前是第一次添加,会把当前要添加的元素,当做根节点//如果不为null,表示当前不是第一次添加,跳过这个判断继续执行下面的代码if (t == null) {//方法的底层,会创建一个Entry对象,把他当做根节点addEntryToEmptyMap(key, value);//表示此时没有覆盖任何的元素return null;}//表示两个元素的键比较之后的结果int cmp;//表示当前要添加节点的父节点Entry<K,V> parent;//表示当前的比较规则//如果我们是采取默认的自然排序,那么此时comparator记录的是null,cpr记录的也是null//如果我们是采取比较去排序方式,那么此时comparator记录的是就是比较器Comparator<? super K> cpr = comparator;//表示判断当前是否有比较器对象//如果传递了比较器对象,就执行if里面的代码,此时以比较器的规则为准//如果没有传递比较器对象,就执行else里面的代码,此时以自然排序的规则为准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 {//把键进行强转,强转成Comparable类型的//要求:键必须要实现Comparable接口,如果没有实现这个接口//此时在强转的时候,就会报错。Comparable<? super K> k = (Comparable<? super K>) key;do {//把根节点当做当前节点的父节点parent = t;//调用compareTo方法,比较根节点和当前要添加节点的大小关系cmp = k.compareTo(t.key);if (cmp < 0)//如果比较的结果为负数//那么继续到根节点的左边去找t = t.left;else if (cmp > 0)//如果比较的结果为正数//那么继续到根节点的右边去找t = t.right;else {//如果比较的结果为0,会覆盖V oldValue = t.value;if (replaceOld || oldValue == null) {t.value = value;}return oldValue;}} while (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);if (addToLeft)parent.left = e;elseparent.right = e;//添加完毕之后,需要按照红黑树的规则进行调整fixAfterInsertion(e);size++;modCount++;}private void fixAfterInsertion(Entry<K,V> x) {//因为红黑树的节点默认就是红色的x.color = RED;//按照红黑规则进行调整//parentOf:获取x的父节点//parentOf(parentOf(x)):获取x的爷爷节点//leftOf:获取左子节点while (x != null && x != root && x.parent.color == RED) {//判断当前节点的父节点是爷爷节点的左子节点还是右子节点//目的:为了获取当前节点的叔叔节点if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//表示当前节点的父节点是爷爷节点的左子节点//那么下面就可以用rightOf获取到当前节点的叔叔节点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 {//表示当前节点的父节点是爷爷节点的右子节点//那么下面就可以用leftOf获取到当前节点的叔叔节点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;}6.课堂思考问题:
6.1TreeMap添加元素的时候,键是否需要重写hashCode和equals方法?
此时是不需要重写的。6.2HashMap是哈希表结构的,JDK8开始由数组,链表,红黑树组成的。
既然有红黑树,HashMap的键是否需要实现Compareable接口或者传递比较器对象呢?
不需要的。
因为在HashMap的底层,默认是利用哈希值的大小关系来创建红黑树的6.3TreeMap和HashMap谁的效率更高?
如果是最坏情况,添加了8个元素,这8个元素形成了链表,此时TreeMap的效率要更高
但是这种情况出现的几率非常的少。
一般而言,还是HashMap的效率要更高。6.4你觉得在Map集合中,java会提供一个如果键重复了,不会覆盖的put方法呢?
此时putIfAbsent本身不重要。
传递一个思想:代码中的逻辑都有两面性,如果我们只知道了其中的A面,而且代码中还发现了有变量可以控制两面性的发生。那么该逻辑一定会有B面。习惯:boolean类型的变量控制,一般只有AB两面,因为boolean只有两个值int类型的变量控制,一般至少有三面,因为int可以取多个值。6.5三种双列集合,以后如何选择?HashMap LinkedHashMap TreeMap默认:HashMap(效率最高)如果要保证存取有序:LinkedHashMap如果要进行排序:TreeMap
三、Collections工具类
public class Collections_ {public static void main(String[] args) {//创建ArrayList 集合,用于测试.List list = new ArrayList();list.add("tom");list.add("smith");list.add("king");list.add("milan");list.add("tom");// reverse(List):反转 List 中元素的顺序Collections.reverse(list);System.out.println("list=" + list);
// shuffle(List):对 List 集合元素进行随机排序
// for (int i = 0; i < 5; i++) {
// Collections.shuffle(list);
// System.out.println("list=" + list);
// }// sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序Collections.sort(list);System.out.println("自然排序后");System.out.println("list=" + list);
// sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序//我们希望按照 字符串的长度大小排序Collections.sort(list, new Comparator() {@Overridepublic int compare(Object o1, Object o2) {//可以加入校验代码.return ((String) o2).length() - ((String) o1).length();}});System.out.println("字符串长度大小排序=" + list);
// swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换//比如Collections.swap(list, 0, 1);System.out.println("交换后的情况");System.out.println("list=" + list);//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素System.out.println("自然顺序最大元素=" + Collections.max(list));//Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素//比如,我们要返回长度最大的元素Object maxObject = Collections.max(list, new Comparator() {@Overridepublic int compare(Object o1, Object o2) {return ((String)o1).length() - ((String)o2).length();}});System.out.println("长度最大的元素=" + maxObject);//Object min(Collection)//Object min(Collection,Comparator)//上面的两个方法,参考max即可//int frequency(Collection,Object):返回指定集合中指定元素的出现次数System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));//void copy(List dest,List src):将src中的内容复制到dest中ArrayList dest = new ArrayList();//为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样for(int i = 0; i < list.size(); i++) {dest.add("");}//拷贝Collections.copy(dest, list);System.out.println("dest=" + dest);//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值//如果list中,有tom 就替换成 汤姆Collections.replaceAll(list, "tom", "汤姆");System.out.println("list替换后=" + list);}
}