• JavaSE——集合框架


    尚硅谷JavaSE笔记合集

    文章名链接
    【JavaSE】异常文章地址
    【JavaSE】常用类:String、LocalDateTime…文章地址
    【JavaSE】枚举文章地址
    【JavaSE】注解文章地址
    【JavaSE】集合框架文章地址 | HashMap源码解析 | List相关实现类源码解析
    【JavaSE】泛型文章地址
    【JavaSE】IO流文章地址 | 字符编码详解
    【JavaSE】网络编程,BIO需求演进文章地址
    【JavaSE】反射文章地址
    【JavaSE】jdk8新特性文章地址

    一、集合概述

    1.1 集合与数组

    /**
     * 概述:
     * 	 - 集合、数组都是对多个数据进行存储(内存层面的存储)操作的结构,简称Java容器。
     *
     * 数组的特点:
     *      》长度确定:初始化时,指定数组长度
     *      》数据类型确定:初始化时,指定数据类型。即操作时数据类型不可变
     *		》数据有序、可重复
     *      比如:String[] arr;int[] str;
     * 数组的缺点:
     *      》长度不可修改:初始化后,无法根据需要扩缩容
     *      》提供的方法非常有限:
     *			- 添加、删除、插入数据:非常不便,同时效率不高。
     *      	- 获取实际存储的元素个数:数组没有现成的属性或方法可用
     *      》数据有序、可重复:无法满足无序、不可重复的需求
     *
     */
    

    1.2 集合框架

    /**
     *
     *  Collection接口:单列集合,用来存储一个一个的对象
     *     - List接口:存储有序的、可重复的数据。  									 -->“动态”数组
     *     		ArrayList、LinkedList、Vector
     *
     *     - Set接口:存储无序的、不可重复的数据   									 -->高中讲的“集合”
     *			HashSet、LinkedHashSet、TreeSet
     *
     *  	Map接口:双列集合,用来存储key - value数据。元素无序、不可重复  			 -->高中函数:y = f(x)
     *       	HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
     *
     */
    

    在这里插入图片描述

    在这里插入图片描述

    二、Collection接口

    2.1 概述

    • 是List、Set 和Queue 的父接口
    • JDK只是提供此接口更具体的子接口实现:Set、List
    • JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。

    2.2 常用方法

    • 要求集合元素所在类要重写equals()
    • 数组转换成集合List:Arrays.asList(T…a)
    • 添加
      • add(Object):添加元素
      • addAll(Collection):添加Collection中所有元素
    • 删除
      • remove(Object):删除元素。只会删除找到的第一个元素
      • removeAll(Collection):删除存在于Collection中的所有元素
      • clear():清空所有元素
    • 获取交集
      • retainAll(Collection):取两个集合中相同的元素,存放于调用者中。即取交集
    • 判断
      • isEmpty():集合是否为空
      • contains(Object):是否包含某个元素。调用Object对象所在类的equals()
      • containsAll(Collection):是否包含Collection中所有元素。拿两个集合的元素的equals方法挨个比较。
      • equals(Object):判断两集合是否相同
    • 其他
      • toArray():转换成数组
    /**
     * Collection常用方法
     *  - 要求obj所在类要重写equals().
     *  - 数组转换成集合List:Arrays.asList(T...a)
     *      1.add(Object):添加元素
     *      2.addAll(Collection):添加Collection中所有元素
     *      3.clear():清空元素
     *      4.isEmpty():集合是否为空
     *      5.contains(Object):是否包含某个元素。调用Object对象所在类的equals()
     *      6.containsAll(Collection):是否包含Collection中所有元素。拿两个集合的元素的equals方法挨个比较。
     *      7.remove(Object):删除元素。只会删除找到的第一个元素
     *      8.removeAll(Collection):删除Collection中存在的元素。即取差集
     *      9.retainAll(Collection):取两个集合中相同的元素,存放于调用者中。即取交集
     *      10.equals(Object):判断两集合是否相同
     *      11.toArray():转换成数组
     *      12.hashCode():获取哈希值
     *      13.iterator():获取迭代器,用于当前集合的遍历
     */
    public class CollectionTest {
        @Test
        public void test1(){
            Collection coll1=new ArrayList();
            Collection coll2=new ArrayList();
            //1.add(Object):添加元素
            coll1.add(1);
            coll1.add(2);
            coll1.add("3");
            coll2.add("3");
            coll2.add(new Date());
            //2.addAll(Collection):添加集合的所有元素
            coll1.addAll(coll2);
            System.out.println(coll1); //[1, 2, 3, 3, Mon Sep 26 14:14:49 CST 2022]
            //3.clear():清空元素
            coll1.clear();
            //4.isEmpty():集合是否为空
            System.out.println("集合是否为空:"+coll1.isEmpty()); //true
            System.out.println(coll1); //[]
        }
        @Test
        public void test2(){
            Collection coll1=new ArrayList();
            Collection coll2=new ArrayList();
            coll1.add(1);
            coll1.add(2);
            coll1.add("3");
            coll2.add(2);
            coll2.add("3");
            //5.contains(Object):是否包含某个元素
            System.out.println("是否包含元素1:"+coll1.contains(1)); //是否包含元素1:true
            //6.containsAll(Collection):是否包含集合中所有元素
            System.out.println("是否包含集合2中所有元素:"+coll1.containsAll(coll2)); //是否包含集合2中所有元素:true
            //7.remove(Object):删除元素
            coll1.remove(1);
            System.out.println(coll1); //[2, 3]
            //8.removeAll(Collection):删除Collection中存在的元素。即取差集
            coll1.removeAll(coll2);
            System.out.println(coll1);//[]
        }
        @Test
        public void test3(){
            Collection coll1=new ArrayList();
            Collection coll2=new ArrayList();
            coll1.add(1);
            coll1.add(2);
            coll1.add("3");
            coll2.add(2);
            coll2.add("3");
            //9.retainAll(Collection):取两个集合中相同的元素,存放于调用者中。即取交集
            coll1.retainAll(coll2);
            System.out.println(coll1); //[2, 3]
            //10.equals(Object):判断两集合是否相同
            System.out.println("两个集合是否相同:"+coll1.equals(coll2)); //两个集合是否相同:true
            //11.toArray():转换成数组返回
            Object[] objects = coll1.toArray();
            for (Object object : objects) {
                System.out.println(object); //2 3
            }
            //12.hashCode():获取哈希值
            int i = coll1.hashCode();
            System.out.println(i); //1074
        }
        @Test
        public void test4(){
            Collection coll1=new ArrayList();
            coll1.add(1);
            coll1.add(2);
            coll1.add("3");
            //13.iterator():获取迭代器,用于当前集合的遍历
            Iterator iterator = coll1.iterator();
            while (iterator.hasNext()){
                System.out.println(iterator.next()); //1 2 3
            }
        }
    }
    

    三、Iterator接口

    3.1 概述

    • Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection 集合中的元素。
    • GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
    • Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
    • Iterator 仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
    • 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

    在这里插入图片描述

    3.2 执行原理

    在这里插入图片描述

    3.3 遍历集合

    /**
     * 集合元素的遍历:Iterator接口
     * 	 1.内部的方法:hasNext()和 next()
     * 	 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
     */
    public class IteratorTest {
        //正确写法
        @Test
        public void test1(){
            Collection coll=new ArrayList();
            coll.add(1);
            coll.add(2);
            coll.add("3");
            Iterator iterator = coll.iterator();
            while(iterator.hasNext()){
                System.out.println(iterator.next());
            }
        }
        //错误写法
        @Test
        public void test2(){
            Collection coll=new ArrayList();
            coll.add(1);
            coll.add(2);
            coll.add("3");
            Iterator iterator = coll.iterator();
            //1.错误写法一
            while (iterator.next()!=null){
                System.out.println(iterator.next());
            }
            //2.错误写法二:每次调用iterator()都得到一个全新的迭代器对象,默认游标指向集合的第一个元素。
            while (coll.iterator().hasNext()){
                System.out.println(coll.iterator().next());
            }
        }
    }
    

    3.4 注意remove()

    注意:IllegalStateException

    • remove()前需要有next():将指针移到指定位置再进行删除。
    • remove()不能连续调用
    /**
     * 集合元素的遍历:Iterator接口
     * 	 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
     *		- remove()前需要有next():将指针移到指定位置再进行删除。
     *		- remove()不能连续调用
     */
    public class IteratorTest {
        //iterator.remove():删除元素“3”
        @Test
        public void test3(){
            Collection coll=new ArrayList();
            coll.add(1);
            coll.add(2);
            coll.add("3");
            Iterator iterator = coll.iterator();
            while (iterator.hasNext()){
                Object next = iterator.next();
                if(next.equals("3")){
                    iterator.remove();
                }
            }
            System.out.println(coll); //[1, 2]
        }
    }
    

    3.5 新特性:增强for

    • Java 5.0 新特性:遍历 Collection、数组
    • 底层调用Iterator完成操作
    • 操作foreach出来的元素对 原来的Collection或数组 没有影响
    /**
     * jdk 5.0 新增了foreach循环,用于遍历集合、数组
     */
    public class ForEachTest {
        //集合遍历
        @Test
        public void test1(){
            Collection coll=new ArrayList();
            coll.add(1);
            coll.add(2);
            coll.add("3");
            for (Object o : coll) {
                System.out.println(o);
            }
        }
        //数组遍历
        @Test
        public void test2(){
            String[] strs=new String[]{"1","2","3"};
            for (String str : strs) {
                str=str+"h";
            }
            //注意:没有影响
            for (String str : strs) {
                System.out.println(str); //1 2 3
            }
        }
    }
    

    四、List接口

    4.1 概述

    /**
     * 数组的缺点:
     *      》长度不可修改:初始化后,无法根据需要扩缩容
     *      》提供的方法非常有限:
     *			- 添加、删除、插入数据:非常不便,同时效率不高。
     *      	- 获取实际存储的元素个数:数组没有现成的属性或方法可用
     *      》数据有序、可重复:无法满足无序、不可重复的需求(使用Set解决)
     */
    
    • 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
    • List集合类中元素有序、可重复,集合中的每个元素都有其对应的顺序索引。
    • 元素对于实现类建议重写equals(),用于方法调用判断元素是否相等。
      • list.contains(Object)
      • Collections.frequency(Collection,Object)

    4.2 常用实现类对比

    /**
     * 面试题:比较ArrayList、LinkedList、Vector三者的异同?建议说一说add的源码实现
     *
     *	相同点:都实现了List接口,存储有序的、可重复的数据
     *
     *	不同点:
     *  ArrayList:作为List的主要实现类,把ArrayList作为缺省选择
     *      - 线程不安全,效率高。
     *      - 作为List的主要实现类,ArrayList()懒汉式(jdk8)创建集合。默认容量为10
     *      - 底层使用数组存储
     *      - 扩容是原来的1.5倍
     *  Vector:
     *      - 线程安全,效率低总是比ArrayList低。尽量避免使用。
     *      - 作为古老的实现类,Vector()饿汉式创建集合。默认容量为10
     *      - 底层使用数组存储
     *      - 扩容是原来的2倍
     *  LinkedList:
     *      - 底层使用双向链表存储
     *		- 对于频繁的插入、删除操作,建议使用LinkedList,效率比ArrayList高。
     *
     */
    

    4.3 ArrayList 源码分析

    • 建议开发中使用带参的构造器,减少扩容次数
    • jdk7:ArrayList() --> this.elementData = new Object[initialCapacity]
    //==============================以jdk8为例===========================
    //1.实例化集合时:创建空数组 --> this.elementData={}
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    //2.添加元素时:
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
    	//2.1 根据需要的最小容量判断是否需要扩容
    	private void ensureCapacityInternal(int minCapacity) {
    	    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    	}
    		//2.1.1 作用:让空数组进入扩容逻辑
    		//如果当前数组为空,返回默认大小10。否则返回数组元素长度
    		private static int calculateCapacity(Object[] elementData, int minCapacity) {
    		    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    		        return Math.max(DEFAULT_CAPACITY, minCapacity);
    		    }
    		    return minCapacity;
    		}
    		//2.1.2 作用:扩容
    		//如果10-0>0,即还没创建数组则进行数组扩容。如果需要的最小容量-数组大小>0,则进行数组扩容
        	private void ensureExplicitCapacity(int minCapacity) {
        	    modCount++;
        	    if (minCapacity - elementData.length > 0)
        	        grow(minCapacity);
        	}
    			//扩容为原来的1.5倍
    			private void grow(int minCapacity) {
    			    int oldCapacity = elementData.length;
    			    int newCapacity = oldCapacity + (oldCapacity >> 1);
                    //数组为空时进入:将新容量设置为10
    			    if (newCapacity - minCapacity < 0)
    			        newCapacity = minCapacity;
                    //容量大于可接受范围时进入
    			    if (newCapacity - MAX_ARRAY_SIZE > 0)
    			        newCapacity = hugeCapacity(minCapacity);
                    //使用数组拷贝:将原数组拷贝到新容量的数组中
    			    elementData = Arrays.copyOf(elementData, newCapacity);
    			}
    	//2.2 进行赋值
    	elementData[size++] = e;
    

    4.4 LinkedList 源码分析

    /**
      * LinkedList的源码分析:
      *    1.内部声明了Node类型的first和last属性,默认值为null。用于记录首末元素
      *
      *    2.Node定义:作为LinkedList中保存数据的基本结构。体现了LinkedList的双向链表的说法
      *       private static class Node {
      *            E item;
      *            Node next;
      *            Node prev;
      *
      *            Node(Node prev, E element, Node next) {
      *            this.item = element;
      *            this.next = next;     //next变量记录下一个元素的位置
      *            this.prev = prev;     //prev变量记录前一个元素的位置
      *            }
      *        }
      */
    //1.实例化集合
    public LinkedList() {
    }
    //2.添加元素
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    	//2.1 链接到尾部
        void linkLast(E e) {
            //(1)保存尾节点
            final Node<E> l = last;
            //(2)创建新节点:传入尾节点
            //	 2.1 将新节点的prev指针指向尾节点
            final Node<E> newNode = new Node<>(l, e, null);
            //(3)将尾指针指向新节点
            last = newNode;
            //(4)如果一开始链表为空,则将头指针指向头节点
            if (l == null)
                first = newNode;
            //(5)如果链表不为空,将尾节点的next指针指向新节点。新节点成为尾节点
            else
                l.next = newNode;
            size++;
            modCount++;
        }
    

    4.5 Vector 源码分析

    //1.实例化集合:调用一个int型参数的构造器
    public Vector() {
        this(10);
    }
    //2.调用两个int型参数的构造器
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    //3.创建容量为10的Object数组
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
    //4.添加元素
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    	//4.1 判断是否需要扩容:如果需要的容量大于当前容量则扩容
    	private void ensureCapacityHelper(int minCapacity) {
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    		//扩容
    		private void grow(int minCapacity) {
            	int oldCapacity = elementData.length;
                //扩大为原来的两倍
            	int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
            	                                 capacityIncrement : oldCapacity);
            	if (newCapacity - minCapacity < 0)
            	    newCapacity = minCapacity;
            	if (newCapacity - MAX_ARRAY_SIZE > 0)
            	    newCapacity = hugeCapacity(minCapacity);
            	elementData = Arrays.copyOf(elementData, newCapacity);
        	}
    	//4.2 赋值
    	elementData[elementCount++] = e;
    

    4.6 常用方法

    List 集合里添加了一些根据索引来操作集合元素的方法。

    • 增删改:
      • addAll(int,Collection),从int位置开始将Collection中的所有元素添加进来
      • remove(int) 注意:参数为整型则认为是索引,移除并返回
      • set(int, Object)
    • 插入:add(int, Object)
    • 获取
      • get(int):获取索引位置的值
      • size():获取元素个数
      • indexOf(Object):获取元素首次出现的位置。如果不存在,返回-1
      • lastIndexOf(Object):获取元素末次出现的位置。如果不存在,返回-1
      • subList(int,int):返回[int,int)范围的子集合
    /**
     *  1.增:add(Object)
     *  2.删:remove(int) / remove(Object) 注意:参数为整型则认为是索引,移除并返回
     *  3.改:set(int, Object)
     *  4.查:get(int)
     *  5.插:add(int, Object)
     *  6.长度:size()
     *  7.添加:addAll(int,Collection),从int位置开始将Collection中的所有元素添加进来
     *  8.获取头部元素位置:indexOf(Object),返回集合中首次出现的位置。如果不存在,返回-1
     *  9.获取尾部元素位置:lastIndexOf(Object),返回集合中末次出现的位置。如果不存在,返回-1
     *  10.获取子集合:subList(int,int),返回[int,int)的子集合
     *  11.遍历:迭代器,增强for循环,普通for循环
     *
     */
    public class ListTest {
        @Test
        public void test1(){
            ArrayList list=new ArrayList();
            //1.增:add(Object)
            list.add(1);
            list.add(2);
            list.add(4);
            System.out.println(list); //[1, 2, 4]
            //2.删:remove(int) / remove(Object)
            list.remove(new Integer(4));
            System.out.println(list); //[1, 2]
            //3.改:set(int, Object)
            list.set(1,3);
            System.out.println(list);
            //4.查:get(int)
            Object o = list.get(0);
            System.out.println(o); //1
            //5.插:add(int, Object)
            list.add(1,2);
            System.out.println(list);
            //6.长度:size()
            System.out.println("长度为:"+list.size());
        }
        @Test
        public void test2(){
            ArrayList list1=new ArrayList();
            ArrayList list2=new ArrayList();
            list2.add(1);
            list2.add(2);
            list2.add(2);
            //7.添加:addAll(int,Collection),从int位置开始将Collection中的所有元素添加进来
            list1.addAll(0,list2);
            System.out.println(list1); //[1, 2, 2]
            //8.获取头部元素位置:indexOf(Object),返回Object在集合中首次出现的位置
            System.out.println("头部元素2的位置:"+list1.indexOf(2)); //头部元素2的位置:1
            //9.获取尾部元素位置:lastIndexOf(Object),返回Object在当前集合中末次出现的位置
            System.out.println("尾部元素2的位置:"+list1.lastIndexOf(2)); //尾部元素2的位置:2
            //10.获取子集合:subList(int,int),返回[int,int)的子集合
            List list = list1.subList(0, 2);
            System.out.println(list); //[1, 2]
        }
        //11.遍历
        @Test
        public void test3(){
            ArrayList list=new ArrayList();
            list.add(1);
            list.add(2);
            list.add(3);
            //迭代器
            Iterator iterator = list.iterator();
            while (iterator.hasNext()){
                System.out.println(iterator.next());
            }
            //增强for循环
            for (Object o : list) {
                System.out.println(o);
            }
            //普通for循环
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
    
        }
    }
    

    4.7 相关面试题

    • 请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别?

      相同点:都是Lsit的实现类,有序可重复
      不同点:
      	ArraysList VS Vector:ArraysList和Vector底层都是用数组存储的
      		- ArraysList线程不安全,Vector线程安全。ArraysList效率总是比Vector高。
      		- jdk8后ArraysList()是懒汉式创建容量为10的数组,Vector饿汉式创建容量为10的数组
      		- ArraysList扩容是原来的1.5倍,Vector扩容是原来的2倍
      	ArraysList VS LinkedList:
      		- ArraysList底层是用数组存储的,LinkedList底层是用双向链表存储的
      		- 如果是频繁的插入、删除操作,LinkedList的效率比较高
      
    • 如何区分ArrayList中remove(int index)和remove(Object obj)?

      //区分ArraysList中的remove(Object)和remove(int)
      @Test
      public void test4(){
          ArrayList list=new ArrayList();
          list.add(1);
          list.add(2);
          list.add(3);
          System.out.println(list);
          //实参为整型时,认为按索引删除
          list.remove(2);
          System.out.println(list);
          //实参为对象时,认为按元素删除
          list.remove(new Integer(2));
          System.out.println(list);
      }
      

    五、Set接口

    5.1 概述

    • Set容器中元素对应的类一定要重写equals()和hashCode()

      • 添加元素时计算索引、判断是否重复
      • 方法调用判断元素是否相同:set.contans(Object)、Collections.frequency(Collection,Object)等
    • 解决的问题

      /**
       * 数组的缺点:
       *      》长度不可修改:初始化后,无法根据需要扩缩容
       *      》提供的方法非常有限:
       *			- 添加、删除、插入数据:非常不便,同时效率不高。
       *      	- 获取实际存储的元素个数:数组没有现成的属性或方法可用
       *      》数据有序、可重复:无法满足无序、不可重复的需求(使用Set解决)
       */
      
    • Set集合类中元素无序、不可重复

      /**
       *	Set:存储无序的、不可重复的数据
       * 		- 无序性:存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。不等于随机性。
       *		- 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
       */
      

    5.2 常用实现类对比

    • Set容器中元素对应的类一定要重写equals()和hashCode()
    /**
     *	面试题:Set存储数据的特点是什么?常见的实现类有什么?说说彼此的特点
     *	
     *	Set存储特点:无序、不可重复
     *
     *	常见实现类:
     *  	HashSet:
     *			- 线程不安全的
     *			- 元素可以存储null值
     *			- jdk8底层存储使用数组+链表+红黑树。具有很好的存取、查找、删除性能。
     *			- HashSet()懒汉式创建长度为16的数组。当数组使用率超过0.75,就会扩大到原来的2倍
     *      LinkedHashSet:
     *			- 使用双向链表维护插入次序。插入性能略低于 HashSet,但在迭代访问时有很好的性能。
     *          - 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
     *  	TreeSet:
     *			- 按照指定方式排序,查询速度比List快
     *			- 底层使用红黑树存储
     */
    

    5.3 HashSet

    • 线程不安全的
    • 元素可以存储null值
    • jdk8底层存储使用数组+链表+红黑树。具有很好的存取、查找、删除性能。
    • HashSet()懒汉式创建长度为16的数组。当数组使用率超过0.75,就会扩大到原来的2倍

    元素添加源码分析

    /**
     *	1. 添加元素a,首先根据元素a所在类hashCode()的值计算元素a的hash值
     *  2. 将哈希值通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置)
     *  3. 判断数组此位置上是否已经有元素:
     *       3.1 如果没有,则元素a添加成功。 								--->情况1
     *       3.2 如果有元素b(或以链表形式存在的多个元素),比较元素a与b的hash值:
     *            3.2.1 如果不相同,则元素a添加成功。							--->情况2
     *            3.2.2 如果相同,调用元素a所在类的equals()方法:
     *                - 返回false,则元素a添加成功。							--->情况3
     *                - 返回true,元素a添加失败
     *
     *      对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
     *      jdk 7 :元素a放到数组中,作为头节点next指针指向原来的元素。
     *      jdk 8 :尾节点的next指针指向元素a,元素a作为新的尾节点
     *      总结:七上八下
     *
     *	HashSet底层:数组+链表(jdk7)、数组+链表+红黑树(jdk8)
     *
     */
    

    5.4 LinkedHashSet

    • 使用双向链表维护插入次序。插入性能略低于 HashSet,但在迭代访问时有很好的性能。
    • 对于频繁的遍历操作,效率高于HashSet

    在这里插入图片描述

    5.5 TreeSet

    • 按key的指定方式排序,元素必须指定排序方式:查询效率比List快

      • 自然排序:元素必须是相同类的对象,否则 ClasssCastException。不管 compareTo() 是怎么实现的。
      • 定制排序:TreeSet传入 Comparator对象
    • 底层使用红黑树存储:

      //头节点
      private transient NavigableMap<E,Object> m;
      
    • 判断两个key相等的标准:compareTo() 或者 compare() 返回0。

    注意:两个对象通过 equals() 比较返回true,则通过 compareTo(Object obj) 比较也应返回0。否则,让人难以理解。

    /**
     *  TreeSet:
     *      1.集合中对象对应的实现类必须指定排序方式,查询效率比List快。
     *          - 自然排序:实现Comparable接口。元素必须是相同类的对象,不管 compareTo() 是怎么实现的。
     *          - 定制排序:构造方法传入Comparator对象。
     *          - 除第一个元素外,后面添加的所有元素都会调用 compareTo() 进行比较
     *		2.判断两个对象是否相同:compareTo() 或者 compare() 返回0
     *      3.底层使用红黑树存储
     *      4.新增方法
     *          - first():返回最小元素
     *          - last():返回最大元素
     *          - lower(Object e):返回比 Object 低的最大元素
     *          - higher(Object):返回比 Object 高的最小元素
     *          - subSet(Object, Object):返回 [Object,Object)范围的Set集合
     *          - headSet(Object):返回 [head,Object)范围的Set集合
     *          - tailSet(Object):返回 [Object,tail] 范围的Set集合
     *
     *  注意:两个对象通过 equals() 比较返回true,则通过 compareTo(Object obj) 比较也应返回0。否则,让人难以理解。
     */
    public class TreeSetTest {
        @Test
        public void test1(){
            //2.定制排序:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
            TreeSet set=new TreeSet(new Comparator() {
                @Override
                public int compare(Object o1, Object o2) {
                    if (o1 instanceof Person && o2 instanceof Person){
                        Person p1= (Person) o1;
                        Person p2= (Person) o2;
                        int i = p1.name.compareTo(p2.name);
                        if(i==0){
                            return Integer.compare(p1.age,p2.age);
                        }
                        return i;
                    }
                    throw new RuntimeException("类型转换错误!");
                }
            });
            set.add(new Person("b",11));
            //set.add("111");   1.集合中元素必须都是相同类的对象:ClassCastException
            set.add(new Person("c",2));
            set.add(new Person("a",55));
            set.add(new Person("a",5));
            //4.新增方法
            //(1)first():返回最小元素
            System.out.println(set.first()); //Person{name='a', age=5}
            //(2)last():返回最大元素
            System.out.println(set.last()); //Person{name='c', age=2}
            //(3)lower(Object e):返回比 Object 低的最大元素
            System.out.println(set.lower(new Person("b",1))); //Person{name='a', age=55}
            //(4)higher(Object):返回比 Object 高的最小元素
            System.out.println(set.higher(new Person("a",5))); //Person{name='a', age=55}
            //(5)subSet(Object, Object):返回 [Object,Object)范围的Set集合
            System.out.println(set.subSet(new Person("a",55),new Person("b",11))); //[Person{name='a', age=55}]
            //(6)headSet(Object):返回 [head,Object)范围的Set集合
            System.out.println(set.headSet(new Person("a",55))); //[Person{name='a', age=5}]
            //(7)tailSet(Object):返回 [Object,tail] 范围的Set集合
            System.out.println(set.tailSet(new Person("c",2))); //[Person{name='c', age=2}]
        }
    }
    class Person implements Comparable{
        String name;
        Integer age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        //2.自然排序
        @Override
        public int compareTo(Object o) {
            if (o instanceof Person){
                Person p= (Person) o;
                return this.name.compareTo(((Person) o).name);
            }else{
                throw new RuntimeException("类型不匹配!");
            }
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Person person = (Person) o;
            return Objects.equals(name, person.name) && Objects.equals(age, person.age);
        }
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    5.6 重写 hashCode与equals

    5.6.1 hashCode()重写

    • 同一个对象多次调用应该返回相同的值
    • 两个对象 equals() 返回true 时,他们的 hashCode() 返回值也应相等
    public int hashCode() {
        return Objects.hash(name, age, sex);
    }
    public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }
    //31*[31*(31+name.hashcode)+age.hashcode]+sex
    public static int hashCode(Object a[]) {
         if (a == null)
             return 0;
         int result = 1;
         for (Object element : a)
             result = 31 * result + (element == null ? 0 : element.hashCode());
         return result;
     }
    

    问题:为什么重写的hashCode方法,有31这个数字?

    • 选择系数的时候要选择尽量大的系数。这样计算出来的hash值差别越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
    • 并且31只占用5bits,相乘造成数据溢出的概率较小。
    • 31可以由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)
    • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)

    5.6.2 equals() 重写

    • 当一个类根据自己特有的"逻辑相等”**重写equals()**的时候,一定要重写hashCode()
      • 因为改写equals()后,两个截然不同的实例有可能在逻辑上是相等的。
      • 根据Set的添加原理,逻辑上相等的两个实例需要放在数组的同一位置,才会去判断是否逻辑相等保证不可重复
      • 而放在数组的同一位置需要保证hash值相同,即hashCode()返回值相等。
    public boolean equals(Object o) {
        if (this == o) return true; //1.地址一样返回true
        if (o == null || getClass() != o.getClass()) return false; //2.不属于同一个类返回false
        Person person = (Person) o;
        //3.属于同一个类,属性一样返回true。否则返回false
        return age == person.age && Objects.equals(name, person.name) && Objects.equals(sex, person.sex);
    }
    

    5.7 面试题

    在List内去除重复数字值,要求尽量简单

    //1.在List内去除重复数字值,要求尽量简单
    @Test
    public void test1(){
        List list=new ArrayList();
        list.add(1);
        list.add(1);
        Set set=new HashSet();
        set.addAll(list);
        System.out.println(set); //1
    }
    

    回答代码中出现的问题

    public class SetTest {
        //2.回答代码中出现的问题
        @Test
        public void test2(){
            Set set=new HashSet();
            Student s1=new Student("a",1);
            Student s2=new Student("b",2);
            set.add(s1);
            set.add(s2);
            System.out.println(set);
            s1.name="c";
            set.remove(s1);
            System.out.println(set);    //问题1:现在set中有几个元素?:2个
            set.add(s1);
            System.out.println(set);    //问题2:现在set中有几个元素?:3个
            set.add(new Student("a",1));
            System.out.println(set);    //问题3:现在set中有几个元素?:4个
    
        }
    }
    class Student{
        String name;
        int age;
        public Student() {
        }
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return age == student.age && Objects.equals(name, student.name);
        }
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    六、Map接口

    6.1 概述

    • Map容器中元素的key对应的类一定要重写equals()和hashCode()
      • 添加元素时计算索引、判断是否重复
      • 方法调用判断值是否相同:map.containsKey(Object)等
    • Map容器中元素的value对应的类一定要重写equals()
      • 方法调用判断值是否相同:map.containsValue(Object)等
    • Map集合类中可元素无序、不可重复(覆盖)

    注意:添加到集合中的元素不要再进行key的修改

    6.2 常用实现类对比

    /**
     *  面试题:
     *  1. HashMap的底层实现原理?
     *  2. HashMap 和 Hashtable 的异同?
     *		- HashMap与Hashtable都实现了Map接口。由于HashMap的非线程安全性,效率上可能高于Hashtable。
     *			Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,
     *			不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。
     *      - HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
     *      - HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。
     *			因为contains方法容易让人引起误解。
     *      - Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。
     *      - Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。
     *  3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)
     *
     * Map:无序、不可重复(覆盖)
     *
     *   HashMap:
     *		- key和value可以为null
     *		- 底层使用数组+链表+红黑树存储(jdk8)
     *		- 扩容为原来的2倍
     *		- 默认容量16
     *   LinkedHashMap:作为HashMap的子类
     *		- 基于HashMap底层,增加before、after指针,记录元素添加的顺序。
     *      - 对于频繁的遍历操作,此类执行效率高于HashMap。
     *   TreeMap:
     *		- 底层使用红黑树存储,指定排序方式,查询效率比list快
     *   Hashtable:作为古老的实现类
     *		- 线程安全的,效率低;
     *		- key和value不能为null
     *   Properties:作为Hashtable的子类
     *		- key和value都是String类型
     *                    
     *
     */
    

    6.3 HashMap

    • 数组初始容量为16
    • 扩容为原来的2倍
    • 内部存储结构:数组+链表+红黑树(jdk8),查询速度快
    • 添加键值:7上8下
    • key和value可以为null

    注意:添加到集合中的元素不要再进行key的修改

    /* 总结:
     *   jdk8 相较于jdk7在底层实现方面的不同:
     *      1.new HashMap():数组创建为懒汉式,首次调用put()时才创建
     *      2.jdk 8底层的数组是:Node[],而非Entry[]
     *      3.在jdk8中底层存储结构为:数组+链表+红黑树。
     *         3.1 形成链表时,七上八下
     *         3.2 链表长度=8 且 数组容量>=64 时,链表转换为红黑树
     *		4.jdk8扩容判断在节点添加完成后进行。jdk7扩容判断是在节点插入之前进行的
     */
    

    6.3.1 JDK7实现原理

    在这里插入图片描述

    • 饿汉式创建数组
    /**
     *
     * 面试题:
     *	1.HashMap什么时候进行扩容呢? 元素个数大于阈值 且 bucket != null
     *	  - 扩容是非常消耗性能的,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
     *	2.添加元素的流程:
     *	  先判断bucket是否为空,为空就直接添加。
     *	  不为空就遍历链表,判断key是否存在
     *		key存在分为两种情况:
     *			第一种情况:数组下标为0的链表中存储key=null,覆盖
     *			第二种情况:对应链表中存在 hash值相同且equals为true的key,覆盖
     *		key不存在,添加成功
     *			
     *
     * Entry[]:数组
     * Capacity:数组容量
     * bucket:桶,数组中存放元素的位置。即Entry[i]
     * threshold:阈值
     * Entry:key-value 节点
     * 		static class Entry implements Map.Entry {
     * 		   final K key;
     * 		   V value;
     * 		   Entry next;
     * 		   int hash;
     * 		}
     *
     *  DEFAULT_INITIAL_CAPACITY : 数组默认容量,16
     *  DEFAULT_LOAD_FACTOR:默认加载因子:0.75
     *  threshold(扩容的临界值=容量*填充因子):16 * 0.75 => 12
     */
    //一.调用有参构造器
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    //二.创建容量16的数组,计算出阈值12
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)		//MAXIMUM_CAPACITY = 1 << 30
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +loadFactor);
        int capacity = 1;
        //容量总是2的倍数
        while (capacity < initialCapacity)
            capacity <<= 1;
        this.loadFactor = loadFactor;
        //计算阈值:16*0.75=12
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        init();
    }
    //三.存放key-value
    public V put(K key, V value) {
    	//3.1 判断:key为null
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key); //计算key的hash值
        int i = indexFor(hash, table.length);//通过hash值计算索引位置
        //3.2 判断:hash值相同 && ==或equals为true
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                //覆盖
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        //3.3 
        //情况一:bucket为null:添加成功
        //情况二:hash值不同:添加成功
        //情况三:hash值相同,==或equals为false:添加成功
        addEntry(hash, key, value, i);
        return null;
    }
    	//3.1 判断:key为null
        private V putForNullKey(V value) {
            //3.1.1 集合中存在:table[0]链表存在key为null的节点,覆盖
            for (Entry<K,V> e = table[0]; e != null; e = e.next) {
                if (e.key == null) {
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }
            modCount++;
            //3.1.2 集合中不存在:添加成功
            addEntry(0, null, value, 0);
            return null;
        }
    	    //3.1.2
    	    //3.3
    	    //情况一:bucket为null:添加成功
            //情况二:hash值不同:添加成功
            //情况三:hash值相同,==或equals为false:添加成功
            void addEntry(int hash, K key, V value, int bucketIndex) {
                //3.1.2.1 判断是否需要扩容:(元素个数>阈值 && table[0] != null )
                if ((size >= threshold) && (null != table[bucketIndex])) {
                    //扩容:原来的2倍
                    resize(2 * table.length);
                    hash = (null != key) ? hash(key) : 0;
                    //扩容后重写计算索引下标
                    bucketIndex = indexFor(hash, table.length);
                }
                //3.1.2.2 创建节点(key的hash值,key,value,数组索引)
                createEntry(hash, key, value, bucketIndex);
            }
    			//3.1.2.1 扩容(最消耗性能):原来的2倍
        		void resize(int newCapacity) {
                    //(1)保存旧的数组
        		    Entry[] oldTable = table;
                    //(2)保存旧的容量
        		    int oldCapacity = oldTable.length;
                    //(3)旧容量是否==1 << 30:如果是,将阈值设置为整型最大值。返回
        		    if (oldCapacity == MAXIMUM_CAPACITY) {
        		        threshold = Integer.MAX_VALUE;
        		        return;
        		    }
                    //(4)创建新容量的空数组
        		    Entry[] newTable = new Entry[newCapacity];
        		    boolean oldAltHashing = useAltHashing;
        		    useAltHashing |= sun.misc.VM.isBooted() &&
        		            (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        		    boolean rehash = oldAltHashing ^ useAltHashing;
                    //(5)将元素转移到新数组
        		    transfer(newTable, rehash);
                    //(6)保存新数组
        		    table = newTable;
                    //(7)计算新阈值
        		    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
        		}
    				//(5)将元素转移到新数组
        			void transfer(Entry[] newTable, boolean rehash) {
                        //获取新阈值
        			    int newCapacity = newTable.length;
                        //遍历旧数组
        			    for (Entry<K,V> e : table) {
        			        while(null != e) {
                                //保存next指针
        			            Entry<K,V> next = e.next;
                                //是否重写计算hash值
        			            if (rehash) {
        			                e.hash = null == e.key ? 0 : hash(e.key);
        			            }
                                //计算新数组中的索引位置
        			            int i = indexFor(e.hash, newCapacity);
                                //next指向新数组对应索引位置的链表
        			            e.next = newTable[i];
                                //将元素放在新数组的bucket位置
        			            newTable[i] = e;
                                //循环旧数组链表下一元素
        			            e = next;
        			        }
        			    }
        			}
    			//3.1.2.2 创建节点:新节点指向旧节点
        		void createEntry(int hash, K key, V value, int bucketIndex) {
        		    Entry<K,V> e = table[bucketIndex];
        		    table[bucketIndex] = new Entry<>(hash, key, value, e);
        		    size++;
        		}
    

    6.3.2 JDK8实现原理

    在这里插入图片描述

    • 懒汉式创建数组
    /**
     * 面试题:
     *	1.HashMap什么时候进行扩容和树形化呢? 
     *	  - 扩容:元素个数大于阈值(数组容量*0.75)
     *	  - 树形化:链表元素个数=8 且 数组容量>=64
     *	  - 扩容是非常消耗性能的,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
     *	2.添加元素的流程:
     *	  第一次调用先初始数组
     *	  先判断bucket是否为空,为空就直接添加。
     *	  不为空就遍历链表,判断key是否存在
     *		key已经存在:覆盖
     *		key不存在,添加成功
     *	  添加完成后:判断是否需要扩容
     *			
     *
     * Node[]:数组
     * Capacity:数组容量
     * bucket:桶,数组中存放元素的位置。即Entry[i]
     * threshold:阈值
     * Entry:key-value 节点
     * 		static class Node implements Map.Entry {
     * 		   final int hash;
     * 		   final K key;
     * 		   V value;
     * 		   Node next;
     * 		}
     *
     *  DEFAULT_INITIAL_CAPACITY : 数组默认容量,16
     *  DEFAULT_LOAD_FACTOR:默认加载因子:0.75
     *  threshold(扩容的临界值=容量*填充因子):16 * 0.75 => 12
     *  TREEIFY_THRESHOLD(链表长度大于该默认值,转化为红黑树):8
     *  MIN_TREEIFY_CAPACITY(数组长度大于该默认值,链表转化为红黑树):64
     *
     */
    //一.加载因子赋值为0.75
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
    }
    //二.存放key-value
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    //三.存放key-value
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //3.1 如果数组还未初始则进行默认初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //3.2 如果bucket为null则直接添加
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);	//添加成功
        //3.3 bucket不为null
        else {
            Node<K,V> e; K k;
            //3.3.1 如果bucket位存在key重复则进行覆盖 
            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);
            //3.3.2 bucket位不存在key重复
            else {
                for (int binCount = 0; ; ++binCount) {
                    //(1) 链表中不存在key重复:尾指针指向新节点
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果7>=8-1 且 数组容量>=64:将链表转换为红黑树,即添加节点后链表元素个数=8
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }
                    //(2) 链表中出现key重复
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //3.3.3 链表中出现key重复:覆盖
            if (e != null) { 
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //3.4 元素个数超过阈值
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    	//3.1 如果数组还未初始则进行默认初始化
    	//3.4 元素个数超过阈值:扩容
    	final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length; //保存旧容量
            int oldThr = threshold;	//保存旧阈值
            int newCap, newThr = 0; //定义容量和阈值
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                //3.4.1 容量和阈值都扩大为原来的2倍
                //newCap=32,newThr=24
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1;
            }
            else if (oldThr > 0)
                newCap = oldThr;
            else {
                newCap = DEFAULT_INITIAL_CAPACITY;	//3.1.1 获取默认容量16
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);	//3.1.2 获取默认阈值16*0.75=12
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            //3.4.2 设置阈值
            threshold = newThr;	//3.1.3 设置阈值
            @SuppressWarnings({"rawtypes","unchecked"})
            //根据新的容量创建新数组
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            //保存新数组
            table = newTab;
            //3.4.3 将元素转移到新数组
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            //3.1.1 返回容量为16的空数组,设置阈值12
            return newTab;
        }
    

    6.4 LinkedHashMap(了解)

    • 基于HashMap 底层,添加 before, after 指针,记录元素添加的顺序

    • 对于频繁的遍历操作,此类执行效率高于 HashMap

      //1.重写HashMap的newNode方法,创建的节点为重新定义的内部类
      Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
          LinkedHashMap.Entry<K,V> p =new LinkedHashMap.Entry<K,V>(hash, key, value, e);
          linkNodeLast(p);
          return p;
      }
      	//2.继承HashMap的节点,添加 before, after 前后两个指针
      	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);
              }
          }
      	//HashMap内部定义的Node类
      	static class Node<K,V> implements Map.Entry<K,V> {
              final int hash;
              final K key;
              V value;
              Node<K,V> next;
          }
      

    6.5 常用方法

    • 增删改
      • put(Object,Object):添加或修改 key-value
      • putAll(Map):将Map中所有元素进行添加
      • remove(Object):移除指定key-value,返回value、
      • clear():清空
    • 获取
      • get(Object):根据key获取value
      • size():获取元素个数
      • keySet():获取所有key构成的Set集合
      • values():获取所有value构成的Collection集合
      • entrySet():获取所有key-value对构成的Set集合
    • 判断
      • containsKey(Object):是否包含指定key
      • containsValue(Object):是否包含指定的value
      • isEmpty():判断集合是否为空
      • equals(Object):判断两个map是否相等
    /**
     * Map常用方法:
     *
     *  增删改
     *      - 1.put(Object,Object):添加或修改 key-value
     *      - 2.putAll(Map):将Map中所有元素进行添加
     *      - 3.remove(Object):移除指定key-value,返回value、
     *      - 4.clear():清空  与map=null不同
     *  获取
     *      - 5.get(Object):根据key获取value
     *      - 6.size():获取元素个数
     *      - 7.keySet():获取所有key构成的Set集合
     *      - 8.values():获取所有value构成的Collection集合
     *      - 9.entrySet():获取所有key-value对构成的Set集合
     *  判断
     *      - 10.containsKey(Object):是否包含指定key
     *      - 11.containsValue(Object):是否包含指定的value
     *      - 12.isEmpty():判断集合是否为空
     *      - 13.equals(Object):判断两个map是否相等
     */
    public class MapTest {
        //增删改
        @Test
        public void test1(){
            Map map1=new HashMap();
            Map map2=new HashMap();
            //1.put(Object,Object):添加或修改 key-value
            map2.put("1",new Student("小红",12));
            map2.put("2",new Student("小明",12));
            //2.putAll(Map):将Map中所有元素进行添加
            map1.putAll(map2);
            System.out.println(map1);// {1=Student{name='小红', age=12}, 2=Student{name='小明', age=12}}
            //3.remove(Object):移除指定key-value,返回value、
            map1.remove("2");// {1=Student{name='小红', age=12}}
            System.out.println(map1);
            //4.clear():清空
            map1.clear();
            System.out.println(map1);// {}
        }
        //获取
        @Test
        public void test2(){
            Map map=new HashMap();
            map.put("1",new Student("小红",12));
            map.put("2",new Student("小明",12));
            //5.get(Object):根据key获取value
            System.out.println(map.get("1")); //Student{name='小红', age=12}
            //6.size():获取元素个数
            System.out.println(map.size()); //2
            //7.keySet():获取所有key构成的Set集合
            Set set = map.keySet();
            for (Object o : set) {
                System.out.println(o); //1 2
            }
            //8.values():获取所有value构成的Collection集合
            Collection values = map.values();
            for (Object value : values) {
                System.out.println(value);//Student{name='小红', age=12} Student{name='小明', age=12}
            }
            //9.entrySet():获取所有key-value对构成的Set集合
            Set set1 = map.entrySet();
            for (Object o : set1) {
                if (o instanceof Map.Entry){
                    Map.Entry entry= (Map.Entry) o;
                    String key = (String) entry.getKey();
                    Student value = (Student) entry.getValue();
                    System.out.println(key+"="+value);//1=Student{name='小红', age=12} 2=Student{name='小明', age=12}
                }else {
                    throw new RuntimeException("类型转换错误!");
                }
            }
        }
        //判断
        @Test
        public void test3(){
            Map map1=new HashMap();
            Map map2=new HashMap();
            map2.put("1",new Student("小红",12));
            map2.put("2",new Student("小明",12));
            map1.putAll(map2);
            //10.containsKey(Object):是否包含指定key
            System.out.println(map1.containsKey("1")); //true
            //11.containsValue(Object):是否包含指定的value
            System.out.println(map1.containsValue(new Student("小红",12))); //true
            //12.isEmpty():判断集合是否为空
            System.out.println(map1.isEmpty()); //false
            //13.equals(Object):判断两个map是否相等
            System.out.println(map1.equals(map2)); //true
        }
    }
    class Student{
        String name;
        int age;
    
        public Student() {}
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return age == student.age && Objects.equals(name, student.name);
        }
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    6.6 TreeMap

    • 按key的指定方式排序,元素必须指定排序方式:查询效率比List快。

      • 自然排序:key必须为同一个类的实现,否则 ClasssCastException。不管 compareTo() 是怎么实现的。
        • Key 对应的类实现 Comparable 接口。
      • 定制排序:向 TreeMap 传入 Comparator 对象
    • 底层使用红黑树存储数据

      //头节点
      private transient Entry<K,V> root;
      
    • 判断两个key相等的标准:compareTo() 或者 compare() 返回0。

    注意:两个对象通过 equals() 比较返回true,则通过 compareTo(Object obj) 比较也应返回0。否则,让人难以理解。

    6.7 Hashtable

    • 线程安全
    • key和value 不可以为null

    6.8 Properties

    • 作为 Hashtable的子类
    • key、value 都是 String类型
    • 常用方法:
      • 保存:setProperty(String,String)
      • 获取:getProperty(String)
    /**
     * 注意:这里没有使用标准的流处理
     * 常用方法:
     *      1.setProperty(String,String)
     *      2.getProperty(String)
     */
    public class PropertiesTest {
        @Test
        public void test() throws IOException {
            //(1)不建议使用绝对路径
            //(2)使用FileInputStream 默认路径为项目根目录下,也即是JavaseTest下面。
            //fis =new FileInputStream("Collection_Demo/src/main/resources/jdbc.properties");
            //(3)使用Class.getResourceAsStream() 默认路径为类路径下当前类的位置
            //fis=  PropertiesTest.class.getResourceAsStream("../../../jdbc.properties");
            //(4)使用Class.getClassLoader().getResourceAsStream 默认路径为类路径
            InputStream fis=  PropertiesTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties properties=new Properties();
            properties.load(fis);
            //1.setProperty(String,String)
            properties.setProperty("password","123");
            //2.getProperty(String)
            String name = properties.getProperty("name");
            String password = properties.getProperty("password");
            System.out.println("name="+name);
            System.out.println("password="+password);
        }
    }
    

    七、Collections工具类

    提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,包括设置集合对象不可变、获取线程安全类等方法

    • 用于操作 Set、List、Map 等集合
    • 修改方法:
      • swap(List,int,int):交换List集合中元素
      • copy(List dest,List src):将src中的内容复制到dest中,注意dest内部数组容量必须足够大
      • replaceAll(List,Object oldVal,Object newVal):用newVal替换 List 中所有的oldVal
    • 排序方法:
      • reverse(List):反转List 中元素顺序
      • shuffle(List):随机排序List集合元素
      • sort(List):升序排序List 集合元素
      • sort(List,Comparator):升序排序List 集合元素
    • 获取:
      • max(Collection):返回Collection中的最大元素
      • max(Collection,Comparator):返回Collection中的最大元素
      • min(Collection):返回Collection中的最小元素
      • min(Collection,Comparator):返回Collection中的最小元素
      • frequency(Collection,Object):返回Collection中指定元素的个数
      • 线程安全类获取
    /**
     *  面试题:Collection和Collections的区别?
     *      - Collection是集合类的上级接口,子接口有List和Set
     *      - Collections是用于操作Collection、Map的工具类,提供一系列静态方法用于集合的排序、复制、修改、线程安全类获取等操作
     *
     *  Collections常用方法:
     *      - 修改:
     *          1. swap(List,int,int):交换List集合中元素
     *          2. copy(List dest,List src):将src中的内容复制到dest中,注意dest内部数组容量必须足够大
     *          3. replaceAll(List,Object oldVal,Object newVal):用newVal替换 List 中所有的oldVal
     *      - 排序:
     *          4. reverse(List):反转List 中元素顺序
     *          5. shuffle(List):随机排序List集合元素
     *          6. sort(List):自然排序List 集合元素
     *          7. sort(List,Comparator):定制排序List 集合元素
     *      - 获取:
     *          8. max(Collection):返回Collection中的最大元素
     *          9. max(Collection,Comparator):返回Collection中的最大元素
     *          10.min(Collection):返回Collection中的最小元素
     *          11.min(Collection,Comparator):返回Collection中的最小元素
     *          12.frequency(Collection,Object):返回Collection中指定元素的个数
     *			13.线程安全类获取
     */
    public class CollectionsTest {
        @Test
        public void test1(){
            List<String> list1=new ArrayList();
            list1.add("2");
            list1.add("1");
            list1.add("3");
            //1. swap(List,int,int):交换List集合中元素
            Collections.swap(list1,0,1);
            System.out.println(list1); //[1, 2, 3]
            //2. copy(List dest,List src):将src中的内容复制到dest中
            List<String> list2=  Arrays.asList(new String[list1.size()]);
            Collections.copy(list2,list1);
            System.out.println(list2); //[1, 2, 3]
            //3. replaceAll(List,Object oldVal,Object newVal):用newVal替换 List 中所有的oldVal
            Collections.replaceAll(list1,"1","newValue");
            System.out.println(list1); //[newValue, 2, 3]
        }
        @Test
        public void test2(){
            List<Student> list=new ArrayList();
            list.add(new Student("1",1));
            list.add(new Student("2",2));
            list.add(new Student("3",3));
            //4. reverse(List):反转List 中元素顺序
            Collections.reverse(list);
            System.out.println(list); //[Student{name='3', age=3}, Student{name='2', age=2}, Student{name='1', age=1}]
            //5. shuffle(List):随机排序List集合元素
            Collections.shuffle(list);
            System.out.println(list); //[Student{name='1', age=1}, Student{name='2', age=2}, Student{name='3', age=3}]
            //6. sort(List):自然排序排序List 集合元素
            Collections.sort(list);
            System.out.println(list); //[Student{name='1', age=1}, Student{name='2', age=2}, Student{name='3', age=3}]
            //7. sort(List,Comparator):定制排序List 集合元素
            Collections.sort(list, new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    if (o1 instanceof Student && o2 instanceof Student){
                        return -o1.name.compareTo(o2.name); //[Student{name='3', age=3}, Student{name='2', age=2}, Student{name='1', age=1}]
                    }else {
                        throw new RuntimeException("类型转换错误!");
                    }
                }
            });
            System.out.println(list);
        }
        @Test
        public void test3(){
            List<Student> list=new ArrayList();
            list.add(new Student("1",1));
            list.add(new Student("2",2));
            list.add(new Student("3",3));
            //8. max(Collection):返回Collection自然排序中的最大元素
            System.out.println(Collections.max(list)); //Student{name='3', age=3}
            //9. max(Collection,Comparator):返回Collection定制排序中的最大元素
            System.out.println(Collections.max(list, new Comparator<Student>() { //Student{name='1', age=1}
                @Override
                public int compare(Student o1, Student o2) {
                    if (o1 instanceof Student && o2 instanceof Student){
                        return -o1.name.compareTo(o2.name); //[Student{name='3', age=3}, Student{name='2', age=2}, Student{name='1', age=1}]
                    }else {
                        throw new RuntimeException("类型转换错误!");
                    }
                }
            }));
            //10.min(Collection):返回Collection自然排序中的最小元素
            System.out.println(Collections.min(list)); //Student{name='1', age=1}
            //11.min(Collection,Comparator):返回Collection定制排序中的最小元素
            System.out.println(Collections.min(list, new Comparator<Student>() { //Student{name='3', age=3}
                @Override
                public int compare(Student o1, Student o2) {
                    if (o1 instanceof Student && o2 instanceof Student){
                        return -o1.name.compareTo(o2.name); //[Student{name='3', age=3}, Student{name='2', age=2}, Student{name='1', age=1}]
                    }else {
                        throw new RuntimeException("类型转换错误!");
                    }
                }
            }));
            //12.frequency(Collection,Object):返回Collection中指定元素的个数
            System.out.println(Collections.frequency(list,new Student("1",1))); //1
        }
    }
    
  • 相关阅读:
    linux golang安装
    使用Spring Boot整合定时任务(Schedule)
    北游项目笔记
    产品经理常用软件汇总
    flutter vscode gradle 配置
    linux 运维 经常逛的几个官网文档
    PyTorch
    python安装selenium(Firefox和Chrome)+元素定位
    maven
    HTTPSConnectionPool
  • 原文地址:https://blog.csdn.net/weixin_43401592/article/details/127102092