【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> {
  *            E item;
  *            Node<E> next;
  *            Node<E> prev;
  *
  *            Node(Node<E> prev, E element, Node<E> 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<K,V> implements Map.Entry<K,V> {
 * 		   final K key;
 * 		   V value;
 * 		   Entry<K,V> 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<K,V> implements Map.Entry<K,V> {
 * 		   final int hash;
 * 		   final K key;
 * 		   V value;
 * 		   Node<K,V> 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
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: JavaSE项目列表是指在JavaSE平台上开发的各种项目的清单。JavaSE是指Java标准版(Java Standard Edition),它是一种完整的Java开发平台,提供了一系列基本的功能和API,用于开发桌面应用程序和小型服务器应用程序。 在JavaSE项目列表中,常见的项目包括: 1. 图形用户界面(GUI)应用程序:JavaSE提供了丰富的图形库,使开发者可以轻松地创建交互式窗口应用程序。比如JavaFX和Swing等库可以用来开发各种图形界面项目。 2. 命令行工具:使用JavaSE,可以开发各种命令行工具,用于完成各种任务,如文件处理、数据分析等。 3. 小型服务器应用程序:JavaSE提供了一些网络相关的API,如Socket编程,可以用于开发小型服务器应用程序,如聊天程序、文件传输程序等。 4. 数据库应用程序:JavaSE提供了一些用于数据库连接和操作的API,可以用于开发各种数据库应用程序。可以使用JDBC来连接各种不同的数据库,进行数据的增删改查等操作。 5. 网络应用程序:使用JavaSE,可以开发各种网络应用程序,如Web服务器、Web应用程序等。可以使用Java提供的HttpURLConnection类来发送HTTP请求和接收HTTP响应,同时也可以使用Java的Servlet API来开发服务器端的应用程序。 总之,JavaSE项目列表非常广泛,涵盖了各种类型的应用程序开发。JavaSE通过提供丰富的类库和API,为程序员提供了一种强大而灵活的开发平台,使得开发各种应用程序变得更加简便和高效。 ### 回答2: JavaSE项目列表是指一种使用Java标准版(JavaSE)开发的项目的列表。JavaSE是Java平台的一个基础版本,它提供了一系列的API和工具,用于开发应用程序。 JavaSE项目的列表可以包括各种类型的应用程序,例如桌面应用程序、命令行工具、小型的服务器端应用等等。以下是一些常见的JavaSE项目示例: 1. 图书管理系统:这是一个桌面应用程序,用于管理图书馆的图书和借阅记录。通过JavaSE提供的Swing或JavaFX图形界面库,可以实现用户界面和交互功能。 2. 邮件发送工具:这是一个命令行工具,用于通过SMTP协议发送电子邮件。JavaSE提供的JavaMail API可以用于构建和发送电子邮件。 3. 文件压缩工具:这是一个命令行工具,用于压缩和解压缩文件。JavaSE提供的java.util.zip包可以用于处理ZIP格式的文件压缩。 4. 网络聊天室:这是一个网络应用程序,用于实现即时的文本聊天功能。JavaSE提供的Socket和ServerSocket类可以用于实现网络通信。 5. 学生信息管理系统:这是一个桌面应用程序,用于管理学生的基本信息、课程选修和成绩录入。通过JavaSE提供的数据库访问API(如JDBC),可以实现与数据库的连接和数据持久化。 这些只是一些示例,实际上JavaSE可以用于开发各种各样的项目。通过使用JavaSE的API和工具,开发人员可以灵活地构建出符合需求的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愿你满腹经纶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值