1. 集合的理解和好处

  1. 可以动态保存任意多个对象,使用比较方便!
  2. 提供了一系列方便的操作对象的方法:add,remove,set,get等
  3. 使用集合添加,删除新元素的示意代码-简洁了

集合的框架和体系

  1. 集合主要是两组(单列集合 , 双列集合)

  2. Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合 (ArrayList LinkedList Vector TreeSet HashSet)

  3. Map 接口的实现子类 是双列集合,存放的 K-V (key-value)
    (Hashtable(Properties) TreeMap HashMap(LinkedHashMap))

  4. 把老师梳理的两张图记住
    双列集合
    第14章 集合 - 图1

  1. **单列集合**
  2. ![](http://qiniu.oxidaner.top/img/image-20211024103037589.png#alt=)
  1. ArrayList arrayList = new ArrayList();
  2. arrayList.add("jack");
  3. arrayList.add("tom");
  4. HashMap hashMap = new HashMap();
  5. hashMap.put("NO1", "北京");
  6. hashMap.put("NO2", "上海");

2. Collection接口实现类

Collection接口实现类的特点

  1. public interface Collection<E> extends Itertable<E>
  1. Collection实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类,可以存放重复的元素,有些不可以
  3. 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
  4. Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的

3. Collection接口和常用方法

  • add : 添加单个元素
  • remove : 删除指定元素
  • contains : 查找元素是否存在
  • size : 获取元素个数
  • isEmpty : 判断是否为空
  • clear : 清空
  • addAll : 添加多个元素
  • containsAll : 查找多个元素是否都存在
  • removeAll : 删除多个元素
  1. List list = new ArrayList();
  2. // add:添加单个元素
  3. list.add("jack");
  4. list.add(10);//list.add(new Integer(10))自动装箱
  5. list.add(true);//自动装箱
  6. System.out.println("list=" + list);
  7. // remove:删除指定元素
  8. //list.remove(0);//删除第一个元素
  9. list.remove(true);//指定删除某个元素
  10. System.out.println("list=" + list);
  11. // contains:查找元素是否存在
  12. System.out.println(list.contains("jack"));//T
  13. // size:获取元素个数
  14. System.out.println(list.size());//2
  15. // isEmpty:判断是否为空
  16. System.out.println(list.isEmpty());//F
  17. // clear:清空
  18. list.clear();
  19. System.out.println("list=" + list);
  20. // addAll:添加多个元素
  21. ArrayList list2 = new ArrayList();
  22. list2.add("红楼梦");
  23. list2.add("三国演义");
  24. list.addAll(list2);
  25. System.out.println("list=" + list);
  26. // containsAll:查找多个元素是否都存在
  27. System.out.println(list.containsAll(list2));//T
  28. // removeAll:删除多个元素
  29. list.add("聊斋");
  30. list.removeAll(list2);
  31. System.out.println("list=" + list);//[聊斋]
  32. // 说明:以 ArrayList 实现类来演示.

Collection接口遍历元素方式1-使用Iterator(迭代器)

背景 : 一个集合

  1. Collection col = new ArrayList();
  2. col.add(new Book("三国演义", "罗贯中", 10.1));
  3. col.add(new Book("小李飞刀", "古龙", 5.1));
  4. col.add(new Book("红楼梦", "曹雪芹", 34.6));

迭代器执行原理

1.得到一个迭代器

  1. Iterator iterator = col.iterator();//得到一个集合的迭代器

hasNext() : 判断是否还有下一个元素,返回bolean

Next() : 1.下移 2. 将下移以后集合位置上的元素返回,类型是Object

2.使用while遍历interator

  1. while (iterator.hasNext()) {//判断是否还有数据
  2. //返回下一个元素,类型是Object
  3. Object obj = iterator.next();
  4. System.out.println("obj=" + obj);
  5. }

老师教大家一个快捷键,快速生成 while => itit
显示所有的快捷键的的快捷键 ctrl + j

提示 : 在调用 iterator.next() 方法之前必须要调用 iterator.hasNext() 进行检测.若不调用,且下一条记录无效直接调用 iterator.next() 会抛出 NoSuchElementException 异常.

3.当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素

  1. iterator.next();//NoSuchElementException

4.如果希望再次遍历,需要重置我们的迭代器

  1. iterator = col.iterator();
  1. **第二次遍历**
  1. while (iterator.hasNext()) {
  2. Object obj = iterator.next();
  3. System.out.println("obj=" + obj);
  4. }

Collection接口遍历元素方式2-使用增强for循环

背景 : 一个集合

  1. List list = new ArrayList();
  2. list.add(new Dog("小黑", 3));
  3. list.add(new Dog("大黄", 100));
  4. list.add(new Dog("大壮", 8));

增强for循环在集合中使用(底层仍是迭代器)

增强for就是简化版本的 迭代器

  1. for (Object dog : list) {
  2. System.out.println("dog=" + dog);
  3. }

快捷键 iter 或者 I 或者 col.for 或者 col.iter

增强for,也可以直接在数组使用

  1. int[] nums = {1, 8, 10, 90};
  2. for (int i : nums) {
  3. System.out.println("i=" + i);
  4. }

普通for循环

  1. for (int i = 0; i < list.size(); i++) {
  2. System.out.println("对象=" + list.get(i));
  3. }

lambda表达式遍历

lambda + foreach

  1. TreeSet ts = new TreeSet();
  2. ts.forEach(obj -> System.out.println("迭代集合元素" + obj));

lambda + Iterator

  1. TreeSet ts = new TreeSet();
  2. var it = ts.iterator();
  3. it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));

使用Predicate操作集合

4. List接口和常用方法

List接口基本介绍

  1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复

  2. List 集合中的每个元素都有其对应的顺序索引,即支持索引

  3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素

  4. JDK API中的List接口实现类有:
    第14章 集合 - 图2

List接口常用方法

  1. List list = new ArrayList();
  2. list.add("张三丰");
  3. list.add("贾宝玉");
  4. // void add(int index, Object ele):在 index 位置插入 ele 元素
  5. //在 index = 1 的位置插入一个对象
  6. list.add(1, "韩顺平");
  7. System.out.println("list=" + list);
  8. // boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
  9. List list2 = new ArrayList();
  10. list2.add("jack");
  11. list2.add("tom");
  12. list.addAll(1, list2);
  13. System.out.println("list=" + list);
  14. // Object get(int index):获取指定 index 位置的元素
  15. //说过
  16. // int indexOf(Object obj):返回 obj 在集合中首次出现的位置
  17. System.out.println(list.indexOf("tom"));//2
  18. // int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
  19. list.add("韩顺平");
  20. System.out.println("list=" + list);
  21. System.out.println(list.lastIndexOf("韩顺平"));
  22. // Object remove(int index):移除指定 index 位置的元素,并返回此元素
  23. list.remove(0);
  24. System.out.println("list=" + list);
  25. // Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换. list.set(1, "玛丽");
  26. System.out.println("list=" + list);
  27. // List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
  28. // 注意返回的子集合 fromIndex <= subList < toIndex
  29. List returnlist = list.subList(0, 2);
  30. System.out.println("returnlist=" + returnlist);

5. ArrayList

ArrayList注意事项

  1. permits all elements, including null ,ArrayList可以加入null,并且多个
  2. ArrayList是由数组来实现数组存储的
  3. ArrayList基本等同于Vector,除了ArrayList是线程不安全的(执行效率高),再多现成的情况下,不建议使用ArrayList

底层源码

  1. Array List中维护了一个 Object类型的数组 elementData.[ debug看源码
    transient Object[] elementData; transient表示瞬间短暂的,表示该属性不会被序列号

  2. 当创建 ArrayList对象时,如果使用的是无参构造器,则初始 elementData容量为0,第1次添加,则扩容 elementData为10,如需要再次扩容,则扩容 elementData为1.5倍。

  3. 如果使用的是指定大小的构造器,则初始 elementData容量为指定大小,如果需要扩容,则直接扩容 elementData为1.5倍

老师建议:自己去 debug一把我们的 ArrayList的创建和扩容的流程

  1. //老韩解读源码
  2. //注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据
  3. //需要做设置. //使用无参构造器创建 ArrayList 对象
  4. //ArrayList list = new ArrayList();
  5. ArrayList list = new ArrayList(8);
  6. //使用 for 给 list 集合添加 1-10 数据
  7. for (int i = 1; i <= 10; i++) {
  8. list.add(i);
  9. }
  10. //使用 for 给 list 集合添加 11-15 数据
  11. for (int i = 11; i <= 15; i++) {
  12. list.add(i);
  13. }
  14. list.add(100);
  15. list.add(200);
  16. list.add(null);
  17. System.out.println(list);
  18. list.remove((Integer)100);
  19. System.out.println(list);

第14章 集合 - 图3

第14章 集合 - 图4

6. Vector

Vector 的基本介绍

  1. Vector类的定义说明
  1. public class Vector<E>
  2. extends abstractlist<E>
  3. implements List<E>, RandomAccess, Cloneable, Serializable
  1. Vector底层也是一个对象数组 protected object[] elementData;`

  2. Vector是线程同步的,即线程安全, Vector类的操作方法带有 synchronized

  1. public synchronized E get(int index)(
  2. if (index > elementCount)
  3. throw new ArraylndexOutofBounds Exception(index);
  4. return elementData(index);
  1. 在开发中,需要线程同步安全时,考虑使用 Vector
  1. //无参构造器
  2. //有参数的构造
  3. Vector vector = new Vector(8);
  4. for (int i = 0; i < 10; i++) {
  5. vector.add(i);
  6. }
  7. vector.add(100);
  8. System.out.println("vector=" + vector);
  9. //老韩解读源码
  10. //1. new Vector() 底层
  11. /*
  12. public Vector() {
  13. this(10);
  14. }
  15. 补充:如果是 Vector vector = new Vector(8);
  16. 走的方法:
  17. public Vector(int initialCapacity) {
  18. this(initialCapacity, 0);
  19. }
  20. 2. vector.add(i)
  21. 2.1 //下面这个方法就添加数据到 vector 集合
  22. public synchronized boolean add(E e) {
  23. modCount++;
  24. ensureCapacityHelper(elementCount + 1);
  25. elementData[elementCount++] = e;
  26. return true;
  27. }
  28. 2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0
  29. private void ensureCapacityHelper(int minCapacity) {
  30. // overflow-conscious code
  31. if (minCapacity - elementData.length > 0)
  32. grow(minCapacity);
  33. }
  34. 2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法
  35. //newCapacity = oldCapacity + ((capacityIncrement > 0) ?
  36. // capacityIncrement : oldCapacity);
  37. //就是扩容两倍. private void grow(int minCapacity) {
  38. // overflow-conscious code
  39. int oldCapacity = elementData.length;
  40. int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
  41. capacityIncrement : oldCapacity);
  42. if (newCapacity - minCapacity < 0)
  43. newCapacity = minCapacity;
  44. if (newCapacity - MAX_ARRAY_SIZE > 0)
  45. newCapacity = hugeCapacity(minCapacity);
  46. elementData = Arrays.copyOf(elementData, newCapacity);
  47. }

7. LinkedList 底层结构

LinkedList 的全面说明

  1. LinkedList.底层实现了双向链表和双端队列特点

  2. 可以添加任意元素(元素可以重复),包括null

  3. 线程不安全,没有实现同步

LinkedList 的底层操作机制

  1. LinkedList底层维护了一个双向链表
  2. LinkedList中维护了两个属性frst和last分别指向首节点和尾节点
  3. 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
  4. 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高.
  5. 模拟一个简单的双向链表【走代码】
  1. //模拟一个简单的双向链表
  2. Node jack = new Node("jack");
  3. Node tom = new Node("tom");
  4. Node hsp = new Node("老韩");
  5. //连接三个结点,形成双向链表
  6. //jack -> tom -> hsp
  7. jack.next = tom;
  8. tom.next = hsp;
  9. //hsp -> tom -> jack
  10. hsp.pre = tom;
  11. tom.pre = jack;
  12. Node first = jack;//让first引用指向jack,就是双向链表的头结点
  13. Node last = hsp; //让last引用指向hsp,就是双向链表的尾结点
  14. //演示,从头到尾进行遍历
  15. System.out.println("===从头到尾进行遍历===");
  16. while (true) {
  17. if(first == null) {
  18. break;
  19. }
  20. //输出first 信息
  21. System.out.println(first);
  22. first = first.next;
  23. }
  24. //演示,从尾到头的遍历
  25. System.out.println("====从尾到头的遍历====");
  26. while (true) {
  27. if(last == null) {
  28. break;
  29. }
  30. //输出last 信息
  31. System.out.println(last);
  32. last = last.pre;
  33. }

LinkedList CRUD

添加

  1. 1. LinkedList linkedList = new LinkedList();
  2. public LinkedList() {}
  3. 2. 这时 linkeList 的属性 first = null last = null
  4. 3. 执行 添加
  5. public boolean add(E e) {
  6. linkLast(e);
  7. return true;
  8. }
  9. 4.将新的结点,加入到双向链表的最后
  10. void linkLast(E e) {
  11. final Node<E> l = last;
  12. final Node<E> newNode = new Node<>(l, e, null);
  13. last = newNode;
  14. if (l == null)
  15. first = newNode;
  16. else
  17. l.next = newNode;
  18. size++;
  19. modCount++;
  20. }

添加一个节点

第14章 集合 - 图5

添加两个节点

第14章 集合 - 图6

删除

  1. 老韩读源码 linkedList.remove(); // 这里默认删除的是第一个结点
  2. 1. 执行 removeFirst
  3. public E remove() {
  4. return removeFirst();
  5. }
  6. 2. 执行
  7. public E removeFirst() {
  8. final Node<E> f = first;
  9. if (f == null)
  10. throw new NoSuchElementException();
  11. return unlinkFirst(f);
  12. }
  13. 3. 执行 unlinkFirst, f 指向的双向链表的第一个结点拿掉
  14. private E unlinkFirst(Node<E> f) {
  15. // assert f == first && f != null;
  16. final E element = f.item;
  17. final Node<E> next = f.next;
  18. f.item = null;
  19. f.next = null; // help GC
  20. first = next;
  21. if (next == null)
  22. last = null;
  23. else
  24. next.prev = null;
  25. size--;
  26. modCount++;
  27. return element;
  28. }
  1. **用对象删除一个数把它先(Integer)一下变成Object类型的就行了**

8. Vector 和 ArrayList 的比较

底层结构 版本 线程安全(同步)效率 扩容倍数
ArrayList 可变数组 jdk1.2 不安全,效率高 如果有参构造1.5倍,
如果是无参构造 1. 10倍 2. 从第二次开始按1.5倍扩展
Vector 可变数组Object[] jdk1.0 安全,效率不高 如果是无参 默认是10 满后,就按2倍
如果指定大小,直接按2倍扩容

9. ArrayList 和 LinkedList比较

底层结构 增删的效率 改查的效率
ArrayList 可变的数组 较低
数组扩容
较高
LinkedList 双向链表 较高
通过链表追加
较低

如何选择 ArrayListLinkedList

  1. 如果我们改查的操作多,选择 ArrayList
  2. 如果我们增删的操作多,选择 LinkedList
  3. 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择 ArrayList
  4. 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是 ArrayList,另外一个模块是 LinkedList,也就是说,要根据业务来进行选择

ArrayListLinkedList他们两都是线程不安全的

10. Set 接口和常用方法

Set 接口基本介绍

  1. 无序 (添加和取出的顺序不一致),没有索引
  2. 不允许重复元素,所以最多包含一个null
  3. 可以自动去重
  4. JDK API Set接口的实现类有:

Set 接口的常用方法

和 List 接口一样, Set 接口也是 Collection 的子接口,因此,常用方法和 Collection 接口一样.

Set 接口的遍历方式

同 Collection的遍历方式一样,因为set接口是 Collection接口的子接口

  1. 可以使用迭代器
  2. 增强for
  3. 可以用 stream().forEach() 方法遍历
  4. 不能使用索引的方式来获取

Set 接口的常用方法举例

  1. Set set = new HashSet();
  2. set.add("john");
  3. set.add("lucy");
  4. set.add("john");//重复
  5. set.add("jack");
  6. set.add("hsp");
  7. set.add("mary");
  8. set.add(null);//
  9. set.add(null);//再次添加null
  10. for(int i = 0; i <10;i ++) {
  11. System.out.println("set=" + set);
  12. }
  13. //遍历
  14. //方式1: 使用迭代器
  15. System.out.println("=====使用迭代器====");
  16. Iterator iterator = set.iterator();
  17. while (iterator.hasNext()) {
  18. Object obj = iterator.next();
  19. System.out.println("obj=" + obj);
  20. }
  21. set.remove(null);
  22. //方式2: 增强for
  23. System.out.println("=====增强for====");
  24. for (Object o : set) {
  25. System.out.println("o=" + o);
  26. }
  27. //set 接口对象,不能通过索引来获取
  1. 以 Set 接口的实现类 HashSet 来讲解 Set 接口的方法
  2. set 接口的实现类的对象(Set 接口对象), 不能存放重复的元素, 可以添加一个 null
  3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
  4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定.

11. Set 接口实现-HashSet

  1. Hash Set实现了Set接口

  2. Hash Set实际上是 HashMap,看下源码.(图)

  1. public HashSet() {
  2. map = new HashMap<>();
  3. }
  1. 可以存放null值,但是只能有一个null

  2. HashSet不保证元素是有序的取决于hash后,再确定索引的结果(即,不保证存放元素的顺序和取出的一致).

  3. 不能有重复元素/对象在前面Set接口使用已经讲过

  1. //说明
  2. //1. 在执行add方法后,会返回一个boolean值
  3. //2. 如果添加成功,返回 true, 否则返回false
  4. //3. 可以通过 remove 指定删除哪个对象
  5. System.out.println(set.add("john"));//T
  6. System.out.println(set.add("lucy"));//T
  7. System.out.println(set.add("john"));//F
  8. System.out.println(set.add("jack"));//T
  9. System.out.println(set.add("Rose"));//T
  10. set.remove("john");
  11. System.out.println("set=" + set);//3个
  12. //
  13. set = new HashSet();
  14. System.out.println("set=" + set);//0
  15. //4 Hashset 不能添加相同的元素/数据?
  16. set.add("lucy");//添加成功
  17. set.add("lucy");//加入不了
  18. set.add(new Dog("tom"));//OK
  19. set.add(new Dog("tom"));//Ok
  20. System.out.println("set=" + set);
  21. //在加深一下. 非常经典的面试题.
  22. //看源码,做分析, 先给小伙伴留一个坑,以后讲完源码,你就了然
  23. //去看他的源码,即 add 到底发生了什么?=> 底层机制.
  24. set.add(new String("hsp"));//ok
  25. set.add(new String("hsp"));//加入不了.
  26. System.out.println("set=" + set);
  1. 在执行add方法后,会返回一个boolean值

  2. 如果添加成功,返回 true, 否则返回false

  3. 可以通过 remove 指定删除哪个对象

  4. Hashset 不能添加相同的元素/数据?

看源码:

分析 HashSet底层是 HashMap, HashMap底层是(数组+链表+红黑树)

  1. public class HashSetStructure {
  2. public static void main(String[] args) {
  3. //模拟一个HashSet的底层 (HashMap 的底层结构)
  4. //1. 创建一个数组,数组的类型是 Node[]
  5. //2. 有些人,直接把 Node[] 数组称为 表
  6. Node[] table = new Node[16];
  7. //3. 创建结点
  8. Node john = new Node("john", null);
  9. table[2] = john;
  10. Node jack = new Node("jack", null);
  11. john.next = jack;// 将jack 结点挂载到john
  12. Node rose = new Node("Rose", null);
  13. jack.next = rose;// 将rose 结点挂载到jack
  14. Node lucy = new Node("lucy", null);
  15. table[3] = lucy; // 把lucy 放到 table表的索引为3的位置.
  16. System.out.println("table=" + table);
  17. }
  18. }
  19. class Node { //结点, 存储数据, 可以指向下一个结点,从而形成链表
  20. Object item; //存放数据
  21. Node next; // 指向下一个结点
  22. public Node(Object item, Node next) {
  23. this.item = item;
  24. this.next = next;
  25. }
  26. }
  1. HashSet底层是 Hash Map
  2. 添加一个元素时,先得到hash值-会转成->索引值
  3. 找到存储数据表 table,看这个索引位置是否已经存放的有元素
  4. 如果没有,直接加入
  5. 如果有,调用 equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
  6. 在Java8中,如果一条链表的元素个数到达 TREEIFY THRESHOLD默认是8),井且 table的大小>= MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)

HashSet源码解读

  1. 执行 HashSet()
  1. public HashSet() {
  2. map = new HashMap<>();
  3. }
  1. 执行 add()
  1. public boolean add(E e) {//e = "java"
  2. return map.put(e, PRESENT)==null;
  3. //(static) PRESENT = new Object();
  4. }
  1. 执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值 算法 h = key.hashCode()) ^ (h >>> 16)
  1. public V put(K key, V value) {
  2. //key = "java" value = PRESENT 共享
  3. return putVal(hash(key), key, value, false, true);
  4. }

4.执行 putVal

  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
  2. Node<K,V>[] tab; Node<K,V> p; int n, i;
  3. //定义了辅助变量
  4. //table 就是 HashMap 的一个数组,类型是 Node[]
  5. //if 语句表示如果当前 table 是 null, 或者 大小=0
  6. //就是第一次扩容,到 16 个空间.
  7. if ((tab = table) == null || (n = tab.length) == 0)
  8. n = (tab = resize()).length;
  9. //(1)根据key得到hash 去计算该key应该存放到table表的哪个索引位置并把这个位置的对象赋给p
  10. //(2)判断p 是否为null
  11. //(2.1)如果p为null 证明还没有存放过元素,就创建一个Node(key = "java",value = PRESENT)
  12. //(2.2)就放在该位置tab[i] = newNode(hash, key, value,null);
  13. //取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v 创建成一个 Node ,加入该位置即可
  14. //如果没有Hash冲突就将Node对象创建出来,放到table数组中,其中就利用了按位与操作的一个特点,必须两个位都为1,才是1,那么也就是说,如果数组最大下标为15,那么不管Hash(key)是多少都不会大于15,也就不会数组越界
  15. if ((p = tab[i = (n - 1) & hash]) == null)
  16. tab[i] = newNode(hash, key, value, null);
  17. else {
  18. //一个开发技巧提示:在需要局部变量(辅助变量)的时候再创建
  19. Node<K,V> e; K k;
  20. // 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同, 并 满足(table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals 返回真) 就认为不能加入新的 k-v
  21. if (p.hash == hash && //如果当前索引对应的链表的第一个元素和准备添加的key的hash值一样并且满足下面两个条件之一:
  22. //(1)准备加入的 key 和 p 指向的 node 节点的 key 是同一个对象
  23. //(2) p 指向的 Node 节点的 key 的 equals() 方法和准备加入的key比较后相同
  24. //就不能加入
  25. ((k = p.key) == key || (key != null && key.equals(k))))
  26. e = p;
  27. //再判断 p 是不是一颗红黑树
  28. //如果是一颗红黑树,就调用 putTreeVal ,来进行添加
  29. else if (p instanceof TreeNode)//如果当前的 table 的已有的 Node 是红黑树,就按照红黑树的方式处
  30. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  31. else {
  32. //如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较
  33. //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
  34. // 注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点
  35. // , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
  36. // 注意,在转成红黑树时,要进行判断, 判断条件
  37. // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
  38. // resize();
  39. // 如果上面条件成立,先 table 扩容. // 只有上面条件不成立时,才进行转成红黑树
  40. //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
  41. for (int binCount = 0; ; ++binCount) {//死循环
  42. if ((e = p.next) == null) {{//如果整个链表,没有和他相同,就加到该链表的最后
  43. p.next = newNode(hash, key, value, null);
  44. //加入后,判断当前链表的个数,是否已经到 8 个,到 8 个,后就调用 treeifyBin 方法进行红黑树的转换
  45. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  46. treeifyBin(tab, hash);
  47. break;
  48. }
  49. if (e.hash == hash && //如果在循环比较过程中,发现有相同,就 break,就只是替换 value
  50. ((k = e.key) == key || (key != null && key.equals(k))))
  51. break;
  52. p = e;
  53. }
  54. }
  55. if (e != null) { // existing mapping for key
  56. V oldValue = e.value;
  57. if (!onlyIfAbsent || oldValue == null)
  58. e.value = value;//替换,key 对应 value
  59. afterNodeAccess(e);
  60. return oldValue;
  61. }
  62. }
  63. ++modCount;//每增加一个 Node ,就 size++
  64. //size 就是我们每加入一个结点 Node(k,v,h,next), size++
  65. if (++size > threshold)//如 size > 临界值,就扩容
  66. resize();
  67. afterNodeInsertion(evict);
  68. return null;
  69. }
  1. 关于树化(转成红黑树)
    如果 table 为 null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
    否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
    resize();
}
/*
HashSet 底层是 HashMap, 第一次添加时,table 数组扩容到 16,
临界值(threshold)是 16*加载因子(loadFactor)是 0.75 = 12
如果 table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,
新的临界值就是 32*0.75 = 24, 依次类推
*/
HashSet hashSet = new HashSet();
// for(int i = 1; i <= 100; i++) {
// hashSet.add(i);//1,2,3,4,5...100
// }
/*
在 Java8 中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),
并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认 64),就会进行树化(红黑树), 否则仍然采用数组扩容机制
*/
// for(int i = 1; i <= 12; i++) {
// hashSet.add(new A(i));//
// }
/*
当我们向 hashset 增加一个元素,-> Node -> 加入 table , 就算是增加了一个 size++
*/
for(int i = 1; i <= 7; i++) {//在 table 的某一条链表上添加了 7 个 A 对象
    hashSet.add(new A(i));
}
for(int i = 1; i <= 7; i++) {//在 table 的另外一条链表上添加了 7 个 B 对象
    hashSet.add(new B(i));//
}
class B {
        private int n;
        public B(int n) {
            this.n = n;
        }
        @Override
        public int hashCode() {
            return 200;
        }
    }
    class A {
        private int n;
        public A(int n) {
            this.n = n;
        }
        @Override
        public int hashCode() {
            return 100;
        }
}

12. LinkedHashSet

  1. LinkedHashSet是 HashSet 的子类

  2. LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+双
    向链表

  3. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使
    用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的
  4. LinkedHashSet 不允许添重复元素

第14章 集合 - 图7

底层源码解读

  1. LinkedHashSet 加入顺序和取出元素/数据顺序一致
  2. LinkedHashSet 底层是一个 LinkedHashMap(是HashMap的子类),底层维护了一个数组+双
    向链表
  3. 添加第一次时,直接将数组 table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry
  4. 数组是 HashMap第14章 集合 - 图8Entry 类型
//继承关系是在内部类完成.
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) {
        super(hash, key, value, next);
    }
}

13. Map和常用方法

  1. Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
  2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node 对象中
  3. Map 中的 key 不允许重复,原因和HashSet 一样,前面分析过源码.
  4. Map 中的 value 可以重复
  5. Map 的key 可以为 null, value 也可以为null ,注意 key 为null, 只能有一个,value 为null ,可以多个
  6. 常用String类作为Map的 key
  7. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value

不是add是put

Map map = new HashMap();
        map.put("no1", "韩顺平");//k-v
        map.put("no2", "张无忌");//k-v
        map.put("no1", "张三丰");//当有相同的k , 就等价于替换.
        map.put("no3", "张三丰");//k-v
        map.put(null, null); //k-v
        map.put(null, "abc"); //等价替换
        map.put("no4", null); //k-v
        map.put("no5", null); //k-v
        map.put(1, "赵敏");//k-v
        map.put(new Object(), "金毛狮王");//k-v
        // 通过get 方法,传入 key ,会返回对应的value
        System.out.println(map.get("no2"));//张无忌

        System.out.println("map=" + map);

第14章 集合 - 图9

  1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)

  2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry 对象就有k,v EntrySet> 即: transient Set> entrySet;
    只是让Set> 的 K 指向 newNode(hash, key, value, null) 的key
    只是让Set> 的 V 指向 newNode(hash, key, value, null) 的value
    只是为了遍历方便将 k,v 封装到 Entry 集合中,再将 Entry 封装到 EntrySet 集合中

  3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node 这时因为
    static class Node implements Map.Entry

  4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法 K getKey(); V getValue();

 Set set = map.entrySet();
        System.out.println(set.getClass());// HashMap$EntrySet
for (Object obj : set) {

            //System.out.println(obj.getClass()); //HashMap$Node
            //为了从 HashMap$Node 取出k-v
            //1. 先做一个向下转型
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + "-" + entry.getValue() );
        }

Map常用方法

常用方法:

Map map = new HashMap();
        map.put("邓超", "孙俪");
        map.put("王宝强", "马蓉");
        map.put("宋喆", "马蓉");
        map.put("刘令博", null);
        map.put(null, "刘亦菲");
        map.put("鹿晗", "关晓彤");
//        remove:根据键删除映射关系
        map.remove(null);
        System.out.println("map=" + map);
//        get:根据键获取值
        Object val = map.get("鹿晗");
        System.out.println("val=" + val);
//        size:获取元素个数
        System.out.println("k-v=" + map.size());
//        isEmpty:判断个数是否为0
        System.out.println(map.isEmpty());//F
//        clear:清除k-v
        //map.clear();
        System.out.println("map=" + map);
//        containsKey:查找键是否存在
        System.out.println("结果=" + map.containsKey("hsp"));//T

14. Map VS Set

Set

[k-v]

k : 对象

v : present 默认常量对象

Map

[k-v]

k : 对象

v : 输入的具体对象

15. MapFor

Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
  1. containsKey : 查找键是否存在
  2. keySet : 获取所有的键
  3. entryset : 获取所有关系kv
  4. values : 获取所有的值
 //第一组: 先取出 所有的Key , 通过Key 取出对应的Value
        Set keyset = map.keySet();
        //(1) 增强for
        System.out.println("-----第一种方式-------");
        for (Object key : keyset) {
            System.out.println(key + "-" + map.get(key));
        }
        //(2) 迭代器
        System.out.println("----第二种方式--------");
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            System.out.println(key + "-" + map.get(key));
        }

        //第二组: 把所有的values取出
        Collection values = map.values();
        //这里可以使用所有的Collections使用的遍历方法
        //(1) 增强for
        System.out.println("---取出所有的value 增强for----");
        for (Object value : values) {
            System.out.println(value);
        }
        //(2) 迭代器
        System.out.println("---取出所有的value 迭代器----");
        Iterator iterator2 = values.iterator();
        while (iterator2.hasNext()) {
            Object value =  iterator2.next();
            System.out.println(value);

        }

        //第三组: 通过EntrySet 来获取 k-v
        Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
        //(1) 增强for
        System.out.println("----使用EntrySet 的 for增强(第3种)----");
        for (Object entry : entrySet) {
            //将entry 转成 Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
        //(2) 迭代器
        System.out.println("----使用EntrySet 的 迭代器(第4种)----");
        Iterator iterator3 = entrySet.iterator();
        while (iterator3.hasNext()) {
            Object entry =  iterator3.next();
            //System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
            //向下转型 Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }

Map 练习

public class MapExercise {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put(1,new Employee("marry",1000,1));
        hashMap.put(2,new Employee("jack",2000,2));
        hashMap.put(3,new Employee("smith",30000,3));

        //增强for
        Set keyset = hashMap.keySet();
        for (Object key : keyset) {
            Employee o = (Employee) hashMap.get(key);
            if(o.getSal() > 18000){
                System.out.println(o);
            }
        }
        //迭代器

        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            Employee o = (Employee) hashMap.get(key);
            if(o.getSal() >18000){
                System.out.println(o);
            }
        }
        //entryset迭代器
        Set entrySet = hashMap.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()) {
            Object key =  iterator1.next();
            Map.Entry entry = (Map.Entry) key;
            Employee o = (Employee) hashMap.get(entry.getKey());
            if (o.getSal() > 18000){
                System.out.println(entry.getValue());
            }
        }

    }
}
class Employee{
    private String name;
    private double sal;
    private int id;

    public Employee(String name, double sal, int id) {
        this.name = name;
        this.sal = sal;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", id=" + id +
                '}';
    }

    public void setId(int id) {
        this.id = id;
    }
}

16. HashMap

HashMap小结

  1. Map接口的常用实现类: HashMap、 Hashtable和 Properties

  2. HashMap是Map接口使用频率最高的实现类。

  3. Hash Map是以 key-val对的方式来存储数据( Hash Map$Node类型)[案例Enty]
    4)key不能重复,但是值可以重复允许使用null键和null值。
    5)如果添加相同的key,则会覆盖原来的key-val,等同于修改(key不会替换,val会替换)
    6)与 Hash Set一样,不保证映射的顺序,因为底层是以hash表的方式来存储的.(jdk8的hashMap底层数组+链表+红黑树)
  4. Hash Map没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized

HashMap 底层机制及源码剖析(和HashSet一样)

第14章 集合 - 图10

Hash Source java先说结论 —》 debug源码扩容机制[和 HashSet相同]

  1. HashMap底层维护了Node类型的数组 table,默认为null
  2. 当刨建对象时,将加载因子( loadfactor)初始化为0.75
  3. 当添加key-val时,通过key的哈希值得到在 table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key和准备加入的key相是否等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做岀相应处理。如果添加时发现容量不够,则需要扩容。
  4. 第1次添加,则需要扩容 table容量为16,临界值( threshold)为12(160.75)
  5. 以后再扩容,则需要扩容 table容量为原来的2倍,临界值为原来的2倍即24依次类推
  6. 在java8中如果一条链表的元素个数超过 TREEIFY THRESHOLD默认是8),并且
    table的大小>= MIN TREEIFY CAPACITY(默认64)就会进行树化(红黑树)

源码扩容机制

public class HashMapSource1 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java", 10);//ok
        map.put("php", 10);//ok
        map.put("java", 20);//替换value

        System.out.println("map=" + map);//

        /*老韩解读HashMap的源码+图解
        1. 执行构造器 new HashMap()
           初始化加载因子 loadfactor = 0.75
           HashMap$Node[] table = null
        2. 执行put 调用 hash方法,计算 key的 hash值 (h = key.hashCode()) ^ (h >>> 16)
            public V put(K key, V value) {//K = "java" value = 10
                return putVal(hash(key), key, value, false, true);
            }
         3. 执行 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;//辅助变量
                //如果底层的table 数组为null, 或者 length =0 , 就扩容到16
                if ((tab = table) == null || (n = tab.length) == 0)
                    n = (tab = resize()).length;
                //取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v
                //, 创建成一个 Node ,加入该位置即可
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
                else {
                    Node<K,V> e; K k;//辅助变量
                // 如果table的索引位置的key的hash相同和新的key的hash值相同,
                 // 并 满足(table现有的结点的key和准备添加的key是同一个对象  || equals返回真)
                 // 就认为不能加入新的k-v
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    else if (p instanceof TreeNode)//如果当前的table的已有的Node 是红黑树,就按照红黑树的方式处理
                        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 方法进行红黑树的转换
                                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            if (e.hash == hash && //如果在循环比较过程中,发现有相同,就break,就只是替换value
                                ((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; //替换,key对应value
                        afterNodeAccess(e);
                        return oldValue;
                    }
                }
                ++modCount;//每增加一个Node ,就size++
                if (++size > threshold[12-24-48])//如size > 临界值,就扩容
                    resize();
                afterNodeInsertion(evict);
                return null;
            }

              5. 关于树化(转成红黑树)
              //如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
              //否则才会真正的树化 -> 剪枝
              final void treeifyBin(Node<K,V>[] tab, int hash) {
                int n, index; Node<K,V> e;
                if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                    resize();

            }
         */


    }
}

HashMap 树化 扩容

public class HashMapSource2 {
    public static void main(String[] args) {


        HashMap hashMap = new HashMap();
        for(int i = 1; i <= 12; i++) {
            hashMap.put(i, "hello");
        }

        hashMap.put("aaa", "bbb");

        System.out.println("hashMap=" + hashMap);//12个 k-v

        //布置一个任务,自己设计代码去验证,table 的扩容
        //0 -> 16(12) -> 32(24) -> 64(64*0.75=48)-> 128 (96) ->
        //自己设计程序,验证-》 增强自己阅读源码能力. 看别人代码.
    }
}

class A  {
    private int num;

    public A(int num) {
        this.num = num;
    }

    //所有的A对象的hashCode都是100
//    @Override
//    public int hashCode() {
//        return 100;
//    }

    @Override
    public String toString() {
        return "\nA{" +
                "num=" + num +
                '}';
    }
}

17. ashtable

Hashtable 基本介绍

  1. 存放的元素是键值对:即K-V

  2. hashtable的键和值都不能为null,否则会抛出NullPointerException

  3. hashtable使用方法基本上和 HashMap一样
  4. hashtable是线程安全的(synchronized), hashMap是线程不安全的
  5. 简单看下底层结构
 Hashtable table = new Hashtable();//ok
        table.put("john", 100); //ok
        //table.put(null, 100); //异常 NullPointerException
        //table.put("john", null);//异常 NullPointerException
        table.put("lucy", 100);//ok
        table.put("lic", 100);//ok
        table.put("lic", 88);//替换
        table.put("hello1", 1);
        table.put("hello2", 1);
        table.put("hello3", 1);
        table.put("hello4", 1);
        table.put("hello5", 1);
        table.put("hello6", 1);
        System.out.println(table);

简单说明一下Hashtable的底层

    1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
       2. 临界值 threshold 8 = 11 * 0.75
       3. 扩容: 按照自己的扩容机制来进行即可.
       4. 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
       5.  当 if (count >= threshold) 满足时,就进行扩容
       6. 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.

Hashtable 和 HashMap 对比

第14章 集合 - 图11

18. Map 接口实现类-Properties

基本介绍

  1. Properties类继承自 Hashtable类并且实现了Map接口,也是使用一种键值对的形
    式来保存数据。
  2. 他的使用特点和 Hashtable类似
  3. Properties还可以用于从xxx, properties文件中,加载数据到 Properties类对象
    井进行读取和修改
  4. 说明:工作后 xxx.properties文件通常作为配置文件,这个知识点在IO流举例有兴
    趣可先看文章

基本使用

//老韩解读
//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
//增加
Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换
System.out.println("properties=" + properties);
//通过 k 获取对应值
System.out.println(properties.get("lic"));//88
//删除
properties.remove("lic");
System.out.println("properties=" + properties);
//修改
properties.put("john", "约翰");
System.out.println("properties=" + properties);

19. TreeMap & TreeSet

TreeMap

@SuppressWarnings({"all"})
public class TreeMap_ {
    public static void main(String[] args) {

        //使用默认的构造器,创建TreeMap, 是无序的(也没有排序)
        /*
            老韩要求:按照传入的 k(String) 的大小进行排序
         */
//        TreeMap treeMap = new TreeMap();
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //按照传入的 k(String) 的大小进行排序
                //按照K(String) 的长度大小排序
                //return ((String) o2).compareTo((String) o1);
                return ((String) o2).length() - ((String) o1).length();
            }
        });
        treeMap.put("jack", "杰克");
        treeMap.put("tom", "汤姆");
        treeMap.put("kristina", "克瑞斯提诺");
        treeMap.put("smith", "斯密斯");
        treeMap.put("hsp", "韩顺平");//加入不了

        System.out.println("treemap=" + treeMap);

        /*

            老韩解读源码:
            1. 构造器. 把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator
             public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
            2. 调用put方法
            2.1 第一次添加, 把k-v 封装到 Entry对象,放入root
            Entry<K,V> t = root;
            if (t == null) {
                compare(key, key); // type (and possibly null) check
                //只是传入两个相同的 目的主要是为了判断第一个值是否为空 如果为空抛出异常
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            2.2 以后添加
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do { //遍历所有的key , 给当前key找到适当位置
                    parent = t;
                    cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare方法(由你写的匿名内部类决定)
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;  //交换
                    else  //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,就不添加
                        return t.setValue(value);
                } while (t != null);
            }
         */

    }
}

TreeSet

@SuppressWarnings({"all"})
public class TreeSet_ {
    public static void main(String[] args) {

        //老韩解读
        //1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的
        //2. 老师希望添加的元素,按照字符串大小来排序
        //3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
        //   并指定排序规则
        //4. 简单看看源码
        //老韩解读
        /*
        1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator

         public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
         2. 在 调用 treeSet.add("tom"), 在底层会执行到

             if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
                do {
                    parent = t;
                    //动态绑定到我们的匿名内部类(对象)compare
                    cmp = 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() {
            @Override
            public 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");//3
        treeSet.add("sp");
        treeSet.add("a");
        treeSet.add("abc");//3


        System.out.println("treeSet=" + treeSet);




    }
}

20. 总结-开发中如何选择集合实现类(记住)

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行
选择,分析如下:

  1. 先判断存储的类型(一组对象[单列]或一组键值对双列)

  2. 一组对象[单列]: Collection接口

    • 允许重复:List
      增删多: LinkedList[底层维护了一个双向链表]
      改查多: Array List[底层维护 Object类型的可变数组]

    • 不允许重复:Set
      无序: Hash Set[底层是 HashMap,维护了一个哈希表 即数组+链表+红黑树)
      排序: Tree Set [老韩举例说明]
      插入和取出顺序一致: LinkedHashSet, 维护数组+双向链

  3. 一组键值对[双列]:Map

    • 键无序: HashMap[底层是:哈希表 jdk7:数组+链表 ,jdk8:数组+链表+红黑树]
    • 键排序: TreeMap
    • 键插入和取出顺序一致: LinkedHashMap
    • 读取文件 Properties

21. Collections工具类

Collections 工具类介绍

  1. Collections 是一个操作 Set, List和Map等集合的工具类
  2. Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

排序操作:(均为 static 方法)

  1. reverse(List) : 反转List中元素的顺序

  2. shuffle(List) : 对List集合元素进行随机排序

  3. sort(List) : 根据元素的自然顺序对指定List集合元素按升序排序
  4. sort(List, Comparator) : 根据指定的 Comparator 产生的顺序对List集合元素进行排序
  5. swap(List, int, int) : 将指定 list 集合中的 i 处元素和 j 处元素进行交换
  6. 应用案例演示 Collections.java
@SuppressWarnings({"all"})
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() {
            @Override
            public 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() {
            @Override
            public 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);


    }
}

22 . 本章作业

A.简答题 Homework04java
试分析 Hash Set和 TreeSet分别如何实现去重的

  1. Hash Set的去重机制: hash Code0+ equals0,底层先通过存入对象进行运算得到一个
    hash值,通过hash值得到对应的索引,如果发现tabe索引所在的位置,没有数据,就直接存
    如果有数据,就进行 equals比较遍历比较],如果比较后,不相同就加入,否则就不加入
    (2) Treese的去重机制:如果你传入了一个 Comparator匿名对象,就使用实现的 compare去
    重,如果方法返回0就认为是相同的元素数据,就不添加,如果你没有传入一个 Comparator
    匿名对象则以你添加的对象实现的 Compareable授口的 comparEto去重
    5代码分析题: Homework05. java
    下面代码运行会不会抛出异常,并从源码层面说明原因[考察读源码+接口编程+动态绑定
    TreeSet tree Set new TreeSetO
    treeSet. add (new Person()

23. stream().forEach()

list 和 set 都可以使用 stream方法来遍历

teachers.stream().forEach(e -> System.out.println(e));