06-java基础——集合的复习

集合的体系结构

  1. 集合主要分为两类:
    • 单列集合
    • 双列集合

一、单列集合

在这里插入图片描述
在这里插入图片描述

  • list系列集合:添加的元素是有序、可重复、有索引的。

    • 有序:指的是存和取的顺序是一致的
  • set系列集合:添加的元素是无序、不可重复、无索引的。

  • collection:collection是一个接口,是单列集合的最顶层接口,它的功能是全部单列集合都可以继承使用。

  • collection:常见的方法:

    • 在这里插入图片描述

collection集合的遍历方式

  1. 迭代器遍历
    • 迭代器遍历是集合的专用遍历方法
    • 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);
        }
    }
}
  1. 增强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);  //集合的数据不会被修改
    }
}
  1. 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>() {
            @Override
            public 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集合的三种+独有的两种)、

      1. 迭代器
      1. 列表迭代器(是迭代器的子接口,额外添加了一个方法:在遍历的过程中,可以添加元素
      1. 增强for
      1. Lambda表达式
      1. 普通for循环
  • list集合删除元素的方式:

    • 1.直接删除元素
    • 2.通过索引进行删除

数据结构

  • 数据结构:计算机存储、组织数据的方式

    • 是指数据相互之间是以什么方式排列在一起的
  • 数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择

  • 常见的数据结构:

    • :后进先出,先进后出

      • 栈的应用场景:java的内存结构——栈内存:先运行的方法最后再出栈
        在这里插入图片描述
    • 队列:先进先出,后进后出;

    • 数组:查询快,增删慢;

      • 每次删除都要把数组的数据前移
      • 每次新增都要把数组的数据后移
    • 链表:链表中的结点是独立的对象,在内存中是不连续的,每个结点(单向链表的结点)包含数据值和下一个结点的地址。(双向链表的结点包含三部分:前一个节点的地址,数据值,下一个结点的地址)

      • 链表查询慢,无论查询哪个数据都要从头开始找
      • 链表增删相对快(相比数组而言)
    • 二叉树

    • 二叉查找树

    • 平衡二叉树

    • 红黑树

ArrayList底层源码分析

  • ArrayLisr的底层数据结构是数组,查询快、增删慢

  • 核心步骤:

    • 第一步:在使用空参创建ArrayList对象的时候,它在底层会先创建一个长度=0的数组;
      • 底层数组的名称:elementDate
      • 变量记录元素的个数的名称:size
        • size的两层含义:元素的个数,下一个元素的存入位置
    • 第二步:添加第一个元素的时候,数组会扩容为长度=10(10是数组底层的默认长度)的数组
    • 第三步:继续添加元素,添加完毕后,size++;
    • 第四步:当添加的元素存满的时候,就会触发数组的扩容机制
      • 扩容时机一:当数组存满的时候,会创建一个新的数组,新数组的长度=原来的1.5倍,再把原来数组的所有元素全拷贝到新数组中。’
      • 扩容时机二一次性添加多个数据的时候,如果扩容1.5倍数组的容量还不够,那么新创建的数组的长度则以实际的长度为准(即添加后的数组有多少个元素,数组的长度就是多少)
  • 扩容时机1:
    在这里插入图片描述

  • 扩容时机2:
    在这里插入图片描述

LinkedList底层源码分析

  1. linkedList的底层数据结构是双向链表,查询慢,增删相对较快,但是首尾元素的操作的速度快,因为提供了很多首尾操作的特有的API。
    在这里插入图片描述
  2. 底层源码分析:
      1. 刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
      1. 添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
      1. 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值
        在这里插入图片描述
  • 添加第一个元素
    在这里插入图片描述

  • 添加第二个元素
    在这里插入图片描述

  • 添加三个元素在这里插入图片描述

迭代器源码分析:

迭代器遍历相关的三个方法:

  • Iterator iterator() :获取一个迭代器对象

  • boolean hasNext() :判断当前指向的位置是否有元素

  • E next() :获取当前指向的元素并移动指针
    在这里插入图片描述

泛型的使用

  1. 泛型可以在编译阶段约束操作的数据类型,并进行检查

  2. 泛型的格式:<数据类型>

  3. 泛型只能支持引用数据类型

  4. 泛型的好处:

    • 统一数据类型
    • 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来了。
    • java中的泛型是伪泛型
  5. 泛型的细节:

    • 泛型中不可以写基本数据类型(在定义集合的泛型的时候,会把存入的数据的类型由定义的泛型转换成Object类型的,如果是基本数据类型则无法转换成object类型)
    • 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类型
    • 如果不写泛型,类型默认是Object
  6. 泛型类、泛型方法、泛型接口:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

一、2、set系列集合

数据结构

  • 数据结构:计算机存储、组织数据的方式

    • 是指数据相互之间是以什么方式排列在一起的
  • 数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择

  • 常见的数据结构:

    • :后进先出,先进后出

      • 栈的应用场景:java的内存结构——栈内存:先运行的方法最后再出栈
        在这里插入图片描述
    • 队列:先进先出,后进后出;

    • 数组:查询快,增删慢;

      • 每次删除都要把数组的数据前移
      • 每次新增都要把数组的数据后移
    • 链表:链表中的结点是独立的对象,在内存中是不连续的,每个结点(单向链表的结点)包含数据值和下一个结点的地址。(双向链表的结点包含三部分:前一个节点的地址,数据值,下一个结点的地址)

      • 链表查询慢,无论查询哪个数据都要从头开始找
      • 链表增删相对快(相比数组而言)
    • 二叉树:二叉树中,任意一个节点的度要小于等于2

      • 节点: 在树结构中,每一个元素称之为节点
      • : 每一个节点的子节点数量称之为度
        在这里插入图片描述
        在这里插入图片描述
    • 二叉查找树:二叉查找树,又称二叉排序树或者二叉搜索树

      • 每一个节点上最多有两个子节点
      • 左子树上所有节点的值都小于根节点的值
      • 右子树上所有节点的值都大于根节点的值
        在这里插入图片描述
        在这里插入图片描述
        在这里插入图片描述
    • 平衡二叉树任意一个结点的左右子树高度差不能超过1
      在这里插入图片描述

      • 旋转的触发机制:
        • 当添加一个节点之后,该树不再是一颗平衡二叉树
          在这里插入图片描述
          在这里插入图片描述
          在这里插入图片描述
          在这里插入图片描述
    • 左左: 当根节点左子树的左子树有节点插入,导致二叉树不平衡

    • 左右: 当根节点左子树的右子树有节点插入,导致二叉树不平衡

    • 右右: 当根节点右子树的右子树有节点插入,导致二叉树不平衡

    • 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡 在这里插入图片描述

    • 红黑树:红黑树增删改查性能都很好。
      在这里插入图片描述
      在这里插入图片描述

  1. set系列集合特点:

    • 无序:存取顺序不一致
    • 不重复:可以去除重复的元素
    • 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
  2. set集合的实现类

    • HashSet:无序、不重复、无索引
    • LinkedHashSet:有序、不重复、无索引
    • TreeSet:可排序、不重复、无索引
  • Set接口中的方法基本上与Collection的API一致

Hashset集合

  1. hashSet集合底层数据结构是采取哈希表(数组+链表+红黑树) 存储数据

    • 哈希表是一种对于增删改查性能都较好的数据结构
      • 哈希表的组成:
        • JDK8之前:数组+链表
        • JDK8之后数组+链表+红黑树
  2. 哈希值:

    • 哈希值是根据hashCode方法算出来的int类型的整数
    • 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
    • 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
  3. 对象的哈希值特点:

    • 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
    • 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
    • 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可以一样(哈希碰撞)(比如当创建对象的个数大于int类型的范围的时候,超出来的哈希对象的哈希值必定就是重复的)
  4. 底层原理:

    • hashSet的底层是hashMap,第一次添加元素的时候,创建一个默认长度为16的数组,默认的加载因子为0.75,数组名table。
      1. 添加一个元素时,先得到该元素的hash值,然后根据hash值和数组的长度的与运算计算出对应的索引值(元素存储的位置)
      1. 判断当前索引位置的值是否为null:
      • 如果是null就直接存入
      • 如果不是null,表示该位置有元素,则调用equals与该位置的元素进行比较属性值:
        • 属性值一样:放弃添加数据
        • 属性值不一样,存入数组,形成链表,新元素直接挂载老元素的下面
      1. 扩容机制:
      • 第一次添加元素,数组table扩容到16,临界值=16*0.75=12,0.75是加载因子
      • 如果table数组使用到了临界值,数组的大小就会扩容到原来的两倍,新的临界值=16x2x0.75=24,依此类推。
      • 当一条链表的元素个数到达8时,且table数组的大小>=64,就会进行树化(链表直接变成红黑树),否则仍然采用数据的扩容机制。
  • 集合中存储的数组是自定义对象,必须重写hashCode方法和equals方法。

LinkedHashset集合

  1. 底层原理:底层数据结构也是使用哈希表(数组+双向链表),只是每个元素又额外多了一个双链表的机制记录存储的顺序。(底层维护的是一个LinkedHashMap(是HashMap的子类)
    在这里插入图片描述
  • 有序:LinkedHashSet内部使用了一个双向链表来维护元素的插入顺序,因此遍历LinkedHashSet时,元素的顺序将与它们的插入顺序一致。

TreeSet集合

  1. TreeSet的底层是基于红黑树的数据结构实现排序的,增删改查性能都很好
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");//如果是按照长度大小的规则来比较的,这个abc是加不进去的,因为上面的tom的长度也是3.(总的来说就是:return ((String) o1).length() - ((String) o2).length();如果return的值为0,那么这个key就加入不了。
        System.out.println("treeSet=" + treeSet);
    }
}

在这里插入图片描述

二、双列集合

  1. 双列集合的特点:用于保存具有映射关系的数据:key-value
      1. 双列集合一次需要存一对数据,分别是键和值(k,v)
      1. 键(key)不能重复值(value)可以重复
      1. 双列集合中的key可以为null,value也可以为null,key为null只有一个,value为null可以有多个。
      1. 键和值是一一对应的,每一个键只能找到自己对应的值
      1. 键+值这个整体我们称之为“键值对”或者“键值对对象“,在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:   数组默认长度16

DEFAULT_LOAD_FACTOR:        默认加载因子0.75



HashMap里面每一个对象包含以下内容:
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;
		
		//把哈希表中数组的地址值,赋值给局部变量tab
		tab = table;

        if (tab == null || (n = tab.length) == 0){
			//1.如果当前是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组
			//2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件
			//如果没有达到扩容条件,底层不会做任何操作
			//如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中
			tab = resize();
			//表示把当前数组的长度赋值给n
            n = 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
			//如果键一样,返回true
			boolean 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   555
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null){
					
					//等号的右边:当前要添加的值
					//等号的左边:0x0044的值
					e.value = value;
				}
                afterNodeAccess(e);
                return oldValue;
            }
        }
		
        //threshold:记录的就是数组的长度 * 0.75,哈希表的扩容时机  16 * 0.75 = 12
        if (++size > threshold){
			 resize();
		}
        
		//表示当前没有覆盖任何元素,返回null
        return 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) {
		//获取根节点的地址值,赋值给局部变量t
        Entry<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;
        else
            parent.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() {
            @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);

        
    }
}

四、集合总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值