Java集合 : 底层原理剖析

目录

一、⭐️Java集合框架概述

二、⭐️Collection接口方法

三、⭐️Iterator迭代器接口

1. Iterator接口中的方法

2. foreach循环遍历集合元素

四、⭐️Collection子接口 : List

1. List的接口框架

2. ArrayList的源码分析

2.1 JDK 7中

2.2 JDK 8中

2.3 小结

3. LinkedList的源码分析

4. Vector的源码分析

5. List接口中的方法

五、⭐️Collection子接口 : Set

1. Set的接口框架

2. 如何理解Set接口特点

3. 添加元素的过程

4. hashCode()方法的重写

5. HashSet的使用

6. LinkedHashSet的使用

7. TreeSet的使用

7.1 TreeSet的自然排序

7.2 TreeSet的定制排序

六、⭐️Map接口

1. Map的接口框架

2. 关于Map结构的理解

3. HashMap的底层实现原理

3.1 JDK 7中

3.2 JDK 8中

4. LinkedHashMap的底层实现原理

5. Map接口中的方法

6. TreeMap的使用

6.1 TreeMap的自然排序

6.2 TreeMap的定制排序

7. Properties的使用

七、⭐️Collections工具类

1. 排序操作

2. 查找替换操作

3. 同步控制


一、Java集合框架概述

集合、数组都是对多个数据进行存储操作的结构,简称Java容器

说明 : 此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)

数组在存储多个数据方面的特点 :

  • 一旦初始化之后,其长度就确定了

  • 数组一旦定义好,其元素的类型也就确定了,只能操作指定类型的数据。比如 : String[] arr,int[] arr;

数组在存储多个数据方面的缺点 :

  • 一旦初始化之后,其长度就不可修改

  • 数组中提供的方法非常有限,对于 : 添加、删除、插入数据等操作,非常不方便,同时效率不高

  • 获取数组中实际元素的个数的需求,数组中没有现成的属性或方法可用

  • 数组存储数据的特点是有序的,可以存储重复的数据(有序,可重复),对于无序,不可重复的需求不能满足

关于数组的优缺点,集合都能很好的满足,其使用场景有 : 数据库返回的为一个List

Java集合 : 底层原理剖析_第1张图片 

Java集合可分为Collection和Map两种体系 :

  • Collection接口 : 单列数据,定义了存取一组对象的方法的集合

    • List : 元素有序、可重复的集合

    • Set : 元素无序、不可重复的集合

  • Map接口 : 双列数据,保存具有映射关系 "key - value 对" 的集合

Collection接口继承树 :

说明 : 实线为继承,虚线为实现

Java集合 : 底层原理剖析_第2张图片 

Map接口继承树 :

Java集合 : 底层原理剖析_第3张图片

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

二、Collection接口方法

最基本的15种方法 :

add(Object obj)、addAll(Collection coll)、size()、isEmpty()、clear();
​
contains(Object obj)、containsAll(Collection coll)、remove(Object obj)、removeAll(Collection voll)、retainsAll(Collection coll)、equals(Object obj);
​
hashCode()、toArray()、iterator();

1、添加

  • add(Object obj)

  • addAll(Collection coll)

2、获取有效元素的个数

  • int size()

3、清空集合

  • void clear()

4、是否是空集合

  • boolean isEmpty()

/*
Collection接口中声明的方法测试
*/
@SuppressWarnings("rawtypes")
@Test
public void test01(){
    Collection coll = new ArrayList();
​
    //add(Object e) : 将元素e添加到集合coll中
    coll.add("AA");
    coll.add(123);
    coll.add(new Object());
    coll.add("BB");
​
    //size() : 获取添加元素的个数
    System.out.println(coll.size());//4
​
    Collection coll1 = new ArrayList();
    coll1.add("AA");
    coll1.add(new Date());
​
    //addAll(Collection c) : 将c集合中的元素全部添加到当前集合中
    coll.addAll(coll1);
    System.out.println(coll.size());//6
​
    System.out.println(coll);
    //[AA, 123, java.lang.Object@76fb509a, BB, AA, Fri Aug 12 17:38:17 CST 2022]
​
    //clear() : 清空集合元素
    coll.clear();
​
    //isEmpty() : 判断当前集合是否为空
    System.out.println(coll.isEmpty());//true
}

5、是否包含某个元素

  • boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象

  • boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。

/*
Collection接口中声明的方法测试
向Collection接口的实现类中添加数据obj时,要求obj所在的类要重写equals()方法
 */
@SuppressWarnings("rawtypes")
@Test
public void test02(){
    Collection coll = new ArrayList();
​
    coll.add(123);
    coll.add(456);
    coll.add(new String("Tom"));
    coll.add(false);
    Person p = new Person("Jerry", 20);
    coll.add(p);
​
    //contains(Object obj) : 判断当前集合是否包含obj
    //我们在判断时会调用obj对象所在类的equals()方法
    boolean contains = coll.contains(123);
    System.out.println(contains);//true
    System.out.println(coll.contains(new String("Tom")));//true
                                        // --> 判断的内容(重写的equals方法)
    System.out.println(coll.contains(new Person("Jerry", 20)));//false
                                        // --> 由于equals方法未重写 所以就是判断的地址
​
    //containsAll(Collection coll) : 判断形参coll中的所有元素是否都存在于当前集合中
    List list = Arrays.asList(123, 456);//返回list类型 list是collection的子接口
    System.out.println(coll.containsAll(list));//true
}

6、删除

  • boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素

  • boolean removeAll(Collection coll):取当前集合的差集

/*
Collection接口中声明的方法测试
*/
@SuppressWarnings("rawtypes")
@Test
public void test03(){
    Collection coll = new ArrayList();
​
    coll.add(123);
    coll.add(456);
    coll.add(new String("Tom"));
    coll.add(false);
    coll.add(new Person("Jerry", 20));
​
    //remove(Object obj) : 从当前集合中移除
    System.out.println(coll.remove(123));//true
    System.out.println(coll);//[456, Tom, false, Person{name='Jerry', age=20}]
​
    System.out.println(coll.remove(new Person("Jerry", 20)));
    System.out.println(coll);
​
    //removeAll(Collection coll) : 从当前集合中移除coll当中的所有元素 (差集)
    List list = Arrays.asList(123, 456);
    System.out.println(coll.removeAll(list));//true
}

7、取两个集合的交集

  • boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c

8、集合是否相等

  • boolean equals(Object obj)

 /*
    Collection接口中声明的方法测试
    */
    @SuppressWarnings("rawtypes")
    @Test
    public void test04(){
        Collection coll = new ArrayList();
​
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);
        coll.add(new Person("Jerry", 20));
​
        //retainAll(Collection coll) : 获取当前集合和coll集合之间的交集 修改当前集合的值为交集的值
//        List list = Arrays.asList(123, 456, 789);
//        System.out.println(coll.retainAll(list));//true
//        System.out.println(coll);//[123, 456]
​
        //equals(Object obj) : 判断当前集合中的每个元素和另一个集合是否相等(顺序元素都相等)
        Collection coll1 = new ArrayList();
​
        coll1.add(123);
        coll1.add(456);
        coll1.add(new String("Tom"));
        coll1.add(false);
        coll1.add(new Person("Jerry", 20));
​
        boolean equals = coll.equals(coll1);
        System.out.println(equals);//true
    }

9、转成对象数组

  • Object[] toArray()

10、获取集合对象的哈希值

  • hashCode()

11、遍历

  • iterator():返回迭代器对象,用于集合遍历

/*
Collection接口中声明的方法测试
*/
@SuppressWarnings("rawtypes")
@Test
public void test05() {
    Collection coll = new ArrayList();
​
    coll.add(123);
    coll.add(456);
    coll.add(new String("Tom"));
    coll.add(false);
    coll.add(new Person("Jerry", 20));
​
    //hashCode() : 返回当前对象的哈希值
    System.out.println(coll.hashCode());//-2061953047
​
    //toArray() : 集合 --> 数组
    Object[] arr = coll.toArray();//返回的是Object类型的数组
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
​
    //拓展 : 数组 --> 集合 Arrays.asList(T...t) 可变形参不推荐new,直接写数据即可
    List list = Arrays.asList(new String[]{"AA", "BB", "CC"});
    System.out.println(list);//[AA, BB, CC]
​
    List list1 = Arrays.asList(new int[]{123, 456});//认为是一个参数(一维数组)
    System.out.println(list1);//[[I@300ffa5d]
    System.out.println(list1.size());//1 --> 一个元素
​
    List list2 = Arrays.asList(new Integer[]{123, 456});
    System.out.println(list2);//[123, 456]
    System.out.println(list2.size());//2 --> 两个元素
    
    //iterator() : 返回Iterator接口的实例,用于遍历集合元素 放在IteratorTest.java中测试
}

三、Iterator迭代器接口

Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合(不用于Map集合)中的元素。

GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。

Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。

Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。

集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

1. Iterator接口中的方法

Java集合 : 底层原理剖析_第4张图片

  
  @SuppressWarnings("rawtypes")
    @Test
    public void test(){
        Collection coll = new ArrayList();
​
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);
        coll.add(new Person("Jerry", 20));
​
        Iterator iterator = coll.iterator();
​
        //next() : 游标下移 返回游标对应的元素
        //遍历方式一 :
        System.out.println(iterator.next());//123
        System.out.println(iterator.next());//456
        System.out.println(iterator.next());//Tom
        System.out.println(iterator.next());//false
        System.out.println(iterator.next());//Person{name='Jerry', age=20}
        //报异常 : NoSuchElementException
//        System.out.println(iterator.next());
​
        //遍历方式二 :
        //注意游标不会重置 这里重新获取一个迭代器对象
        Iterator iterator1 = coll.iterator();
        for (int i = 0; i < coll.size(); i++) {
            System.out.println(iterator1.next());
        }
​
        //hasNext() : 判断是否还有下一个元素
        //遍历方式三 : 推荐
        Iterator iterator2 = coll.iterator();
        while (iterator2.hasNext()){// 判断是否还有下一个元素
            //① 指针下移 ② 将下移以后集合位置上的元素返回
            System.out.println(iterator2.next());
        }
​
    }

测试Iterator中的remove() :

@SuppressWarnings("rawtypes")
@Test
public void test2(){
    Collection coll = new ArrayList();

    coll.add(123);
    coll.add(456);
    coll.add(new String("Tom"));
    coll.add(false);
    coll.add(new Person("Jerry", 20));

    Iterator iterator = coll.iterator();
    //remove() : 可以在遍历的时候 删除集合中的元素 (删除游标对应的元素)
    while (iterator.hasNext()){
        Object obj = iterator.next();
        if ("Tom".equals(obj)){
            iterator.remove();
            
            //不能连续调用两次 会报异常 IllegalStateException
//          iterator.remove();
            
        }
    }

    Iterator iterator1 = coll.iterator();
    while (iterator1.hasNext()){
        System.out.println(iterator1.next());
    }
}

注意 :

  • Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。

  • 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。

2. foreach循环遍历集合元素

Java 5.0 提供了 foreach 循环 (增强for循环) 迭代访问 Collection和数组。

  • 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。

  • 遍历集合的底层调用Iterator完成操作。

  • foreach还可以用来遍历数组。

for (集合元素的类型 局部变量 : 要遍历的集合对象){
	//内部仍然调用了迭代器实现
}

局部变量 : 盛装对应的元素 不是索引

遍历集合 :

public class ForTest {
    public static void main(String[] args) {
        Collection coll = new ArrayList();

        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);
        coll.add(new Person("Jerry", 20));

        //for(集合元素的类型 局部变量 : 集合对象)
        /*
        过程 : 内部用迭代器实现
        先取coll中第一个元素赋值给obj
        然后取第二个元素赋值给obj
         */
        for(Object obj : coll){
            System.out.println(obj);
        }
    }
}

遍历数组 :

@Test
public void test(){
    int[] arr = new int[]{1, 2, 3, 4, 5, 6};
    //for(数组元素的类型 局部变量 : 数组对象)
    for (int i : arr){
        System.out.println(i);//这里的 i 就是对应的元素 不是索引
    }
}

练习题 :

    @Test
    public void test01() {
        String[] arr = new String[]{"MM", "MM", "MM"};

        //方式一 : 普通for赋值
//        for (int i = 0; i < arr.length; i++) {
//            arr[i] = "GG";
//        }

        //方式二 : 增强for赋值
        for (String str : arr){
            str = "GG";//此时的赋值不是修改的数组本身 而是修改的局部变量
        }

        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

四、Collection子接口 : List

鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组,称作动态数组。

List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。

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

JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。

1. List的接口框架

|----Collection接口 : 单列集合,用来存储一个一个的对象
    |----List接口 : 存储有序、可重复的数据 --> "动态"数组,替换原有的数组
        |----ArrayList
        |----LinkedList
        |----Vector

比较ArrayList、LinkedList、Vector三者的异同 ?

相同点 : 三个类都是实现了List接口,存储数据的特点相同 : 存储有序的、可重复的数据

不同点 :

  • ArrayList : 作为List接口的主要实现类,线程不安全的,效率高,底层使用Object[] elementData存储

  • Linkedlist : 对于频繁的增加和删除操作,使用此类效率比ArrayList高,底层使用双向链表存储

  • Vector : 作为List接口的古老实现类,线程安全的,效率低,底层使用Object[] elementData存储

2. ArrayList的源码分析

2.1 JDK 7中

ArrayList list = new ArrayList();

底层创建了长度是10的 Object[] 数组 elementData

list.add(123);//elementData[0] = new Integer(123);
...
list.add(456);//如果此次的添加导致底层elementData数组容量不够,则需要扩容

默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中 ( elementData = Arrays.copyOf(elementData, newCapacity) )。

结论 : 建议开发中使用带参的构造器 :

ArrayList list = new ArrayList(int capacity);

2.2 JDK 8中

ArrayList list = new ArrayList();

底层Object[] elementData初始化为 {},并没有创建长度为10的数组

list.add(123);

当第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]

后续的添加与扩容操作与JDK7相同。

2.3 小结

JDK 7中的ArrayList的对象的创建类似于单例模式的饿汉式

JDK 8中的ArrayList的对象的创建类似于单例模式的懒汉式,延迟了数组的创建,节省内存。

3. LinkedList的源码分析

LinkedList list = new LinkedList();

内部声明了Node类型的first和last属性,默认值为null

list.add(123);

将123封装到Node中,创建了Node对象,其中Node的定义为如下,体现了LinkedList双向链表的实现原理。

private static class Node {
    E item;
    Node next;
    Node prev;

    Node(Node prev, E element, Node next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

4. Vector的源码分析

由于Vector容器是在JDK 1.0时就存在,而Collection接口框架在JDK 1.2时才有,是后才将Vector并入接口框架中的,所以对Vector的使用不多,Vector的特点是线程安全的。其底层实现与ArrayList的原理一样(Object[] 数组),在创建对象的时候默认也是创建一个容量为10的Object数组,但在扩容的时候与ArrayList不同的是默认扩容2倍而非1.5倍。

5. List接口中的方法

List除了从Collection集合继承的方法外,List集合里添加了一些根据索引来操作集合元素的方法 :

  • void add(int index, Object ele) : 在index位置插入ele元素

  • boolean addAll(int index, Collection eles) : 从index位置开始将eles中的所有元素添加进来

    @SuppressWarnings("rawtypes")
    @Test
    public void test01(){
        List list = new ArrayList();
    
        list.add(123);
        list.add(456);
        list.add(new String("Tom"));
        list.add(false);
        list.add(new Person("Jerry", 20));
    
        //void add(int index, Object ele) : 在index位置插入ele元素
        list.add(1, new String("insert"));
    
        System.out.println(list);//[123, insert, 456, Tom, false, Person{name='Jerry', age=20}]
    
        //boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
        List list1 = Arrays.asList(1, 2, 3);
        list.addAll(list1);
        System.out.println(list.size());
    } 
    
  • Object get(int index) : 获取指定index位置的元素

  • int indexOf(Object obj) : 返回obj在集合中首次出现的位置

  • int lastIndexOf(Object obj) : 返回obj在当前集合中末次出现的位置

    @SuppressWarnings("rawtypes")
    @Test
    public void test02(){
        List list = new ArrayList();
    
        list.add(123);
        list.add(456);
        list.add(new String("Tom"));
        list.add(false);
        list.add(456);
        list.add(new Person("Jerry", 20));
    
        //Object get(int index):获取指定index位置的元素
        Object obj = list.get(1);
        System.out.println(obj);//456
    
        //int indexOf(Object obj):返回obj在集合中首次出现的位置 如果不存在返回-1
        int index = list.indexOf(456);
        System.out.println(index);//1
    
        //int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置 如果不存在返回-1
        int lastIndex = list.lastIndexOf(456);
        System.out.println(lastIndex);//4
    } 
    
  • Object remove(int index) : 移除指定index位置的元素,并返回此元素

  • Object set(int index, Object ele):设置指定index位置的元素为ele,并返回被修改的元素

    @SuppressWarnings("rawtypes")
    @Test
    public void test03(){
        List list = new ArrayList();
    
        list.add(123);
        list.add(456);
        list.add(new String("Tom"));
        list.add(false);
        list.add(456);
        list.add(new Person("Jerry", 20));
    
        //Object remove(int index):移除指定index位置的元素,并返回此元素
        Object removeObj = list.remove(4);
        System.out.println(removeObj);
    
        System.out.println(list);//[123, 456, Tom, false, Person{name='Jerry', age=20}]
    
        //Object set(int index, Object ele):设置指定index位置的元素为ele 并返回被修改的元素
        Object obj = list.set(0, 789);
        System.out.println(obj);//123
        System.out.println(list);//[789, 456, Tom, false, Person{name='Jerry', age=20}]
    } 
    
  • List subList(int fromIndex, int toIndex) : 返回从fromIndex到toIndex位置的子集合

    @SuppressWarnings("rawtypes")
    @Test
    public void test04(){
        List list = new ArrayList();
    
        list.add(123);
        list.add(456);
        list.add(new String("Tom"));
        list.add(false);
        list.add(456);
        list.add(new Person("Jerry", 20));
        System.out.println(list);//[123, 456, Tom, false, 456, Person{name='Jerry', age=20}]
    
        //List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
        List subList = list.subList(2, 4);
        System.out.println(subList);//[Tom, false]
        System.out.println(list);//[123, 456, Tom, false, 456, Person{name='Jerry', age=20}]
    } 
    
  • 遍历 : ①迭代器 ②增强for循环 ③普通for循环

    @SuppressWarnings("rawtypes")
    @Test
    public void test05(){
        List list = new ArrayList();
    
        list.add(123);
        list.add(456);
        list.add(new String("Tom"));
        list.add(false);
        list.add(456);
        list.add(new Person("Jerry", 20));
    
        //方式一 : 迭代器
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.print(iterator.next() + " ");
        }
        System.out.println();
    
        //方式二 : 增强for循环
        for (Object obj : list){
            System.out.print(obj + " ");
        }
        System.out.println();
    
        //方式三 : 普通for循环
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
    } 
    
    

    总结,常用方法 :

    增 : add(Object obj)
    删 : remove(int index) / remove(Object obj)
    改 : set(int index, Object ele)
    查 : get(int index)
    插 : add(int index, Object obj)
    长度 : size()
    遍历 : ①迭代器 ②增强for循环 ③普通for循环

    区分remove中remove(int index) ,remove(Object obj)方法,例子 :

    public class ListExer {
        public static void main(String[] args) {
            List list = new ArrayList();
            list.add(1);
            list.add(2);
            list.add(3);
    
            updateList(list);
    
            System.out.println(list);//[1, 2]
        }
    
        public static void updateList(List list){
            list.remove(2);
            
            //list.remove(new Integer(2))
        }
    }

    remove方法有 : remove(int index) ,remove(Object obj)两个,在方法外调用remove方法时首先默认调用的是不需要自动装箱的方法,如果想要调用remove(Object obj),则需要手动装箱。

    五、Collection子接口 : Set

    Set接口是Collection的子接口,set接口没有提供额外的方法

    Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。

    Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法

    1. Set的接口框架

    |----Collection接口 : 单列集合,用来存储一个一个的对象
        |----Set接口 : 存储无序的、不可重复的数据 --> 高中讲的 "集合"
        	|----HashSet
        		|----LinkedHashSet
        	|----TreeSet

    比较HashSet、LinkedHashSet、TreeSet三者的异同 ?

    相同点 : 三个类都是实现了Set接口,存储数据的特点相同 : 存储无序的、不可重复的数据

    不同点 :

    • HashSet : 作为Set接口的主要实现类,线程不安全的,可以存储null值

    • LinkedHashSet : 作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历

    • TreeSet : 可以按照添加对象的指定属性,进行排序

    2. 如何理解Set接口特点

    以HashSet为例说明 :

    (1) 无序性 : 不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。无序只是指在存储的时候不按索引顺序添加。

    (2) 不可重复性 : 保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个

    @SuppressWarnings("rawtypes")
    @Test
    public void test01(){
        Set set = new HashSet();
        set.add(456);
        set.add(123);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(new User("Tom", 12));
        set.add(new User("Tom", 12));
        set.add(129);
    
        System.out.println(set);
        //如果未重写equals方法 判断的地址值 所以不同
        //[AA, CC, User{name='Tom', age=12}, 129, User{name='Tom', age=12}, 456, 123]
        System.out.println(set);//重写equals方法之后 还是有两个
        //[AA, CC, User{name='Tom', age=12}, 129, User{name='Tom', age=12}, 456, 123]
        System.out.println(set);//重写了equals方法和hashCode方法之后 只有一个了
        //[AA, CC, 129, 456, 123, User{name='Tom', age=12}]
        
    }

    3. 添加元素的过程

    以HashSet为例 :

    我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法 (& (size - 1) 等操作) 计算出在HashSet底层数组中的存放位置 (即 : 索引位置) ,判断数组此位置上是否已经有元素 :

    如果此位置上没有其他元素,则元素a添加成功 (情况1)。如果此位置上有其他元素b (或以链表形式存在的多个元素),则比较元素a与元素b的hash值 :

    如果hash值不相同,则元素a添加成功 (情况2)。如果hash值相同,则进而需要调用元素a所在类的equals()方法 :

    如果equals()返回false,则元素a添加成功 (情况3)。如果equals()返回true,则元素a添加失败。

    对于添加成功的情况2和情况3而言 : 元素a与已经存在指定索引位置上数据以链表的方式存储 :

    • jdk 7 : 元素a放到数组中,指向原来的元素。

    • jdk 8 : 原来的元素在数组中,指向元素a。

    HashSet底层 : 数组 + 链表的结构(前提JDK 7)

    实际上HashSet的实现原理就是HashMap,HashSet的数据实际上就是HashMap中的key,而value存放的是一个静态Object类对象,没有意义。

    4. hashCode()方法的重写

    当向Set集合中添加数据时,在重写equals()方法时,建议同时重写hashCode(),并且保证两个方法的一致性 (相同的属性参与运算) ,这是因为在当对象放到HashSet集合或HashMap集合时,保证去重后的正确性。

    了解hashCode方法和equals方法的两个重要的规范 :

    规范一 : 两个对象相等,其hashCode一定相等

    规范二 : 两个对象不等(equals方法返回false),并不要求这两个对象的hashCode得到不同的数

    则得到如下推论 :

    推论一 : hashCode相同的两个对象,不一定相等

    推论二 : hashCode不同的两个对象,一定不相等

    hashCode方法在Object类中定义,Object类中定义的hashCode方法调用的底层的C语言来计算出来一个随机数,这个随机数基本保证了不同的对象有不同的hashCode。如果我们不重写hashCode的话,在往HashSet集合中存放对象时,如果存放内容相等的两个对象,其hashCode不同,那两个对象都会存放成功,达不到去重效果。

    重写hashCode()方法的基本原则 :

    • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。

    • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。

    • 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

    @Override
    public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + age;
    return result;
    }

    为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?

    • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

    • 并且31只占用5bits,相乘造成数据溢出的概率较小。

    • 31可以 由i * 31 == ( i << 5 ) - 1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)

    • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)

    5. HashSet的使用

    关于HashSet的使用,有关remove等方法的说明 : 先调用hashCode再用equals

    public class hashSetTest {
        public static void main(String[] args) {
            HashSet set = new HashSet();
    
            Person p1 = new Person(1001, "AA");
            Person p2 = new Person(1002, "BB");
    
            set.add(p1);
            set.add(p2);
            System.out.println(set);
            //[Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
            p1.name = "CC";
            set.remove(p1);
            System.out.println(set);
            //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
            set.add(new Person(1001, "CC"));
            System.out.println(set);
            //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
            set.add(new Person(1001, "AA"));
            System.out.println(set);
            //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'},
            // Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
    
        }
    }

    以上测试体现了HashSet的特点,当我们向HashSet中添加数据时,按照hashCode计算索引位置的规则进行添加的。当我们将添加以后的数据内容更改以后,其计算的hashCode将会改变。当我们remove改数据时,通过计算hashCode来寻找改元素计算得到的索引位置大概率不会是原来的位置,所以remove操作将会失败。当我们再向HashSet中添加一个更新后的数据时,由于原来更改后的数据没有占着索引位置,所以将会添加成功。当我们向HashSet中添加一个更新前的数据时,这时由于原来的数据占着其索引位置,那么就会比较hashCode,两个数据内容不同,所以hashCode不同,则添加成功。

    6. LinkedHashSet的使用

    LinkedHashSet是HashSet的子类,特点是在遍历的时候,可以按照添加的顺序遍历出来。原理是,在添加元素的同时还记录下了上一个添加的元素的地址,上一个添加的元素同时也记录了本次添加元素的地址。相当于形成了一个双向链表。

    优点 : 对于频繁的遍历操作,LinkedHashSet效率高于HashSet。

    @SuppressWarnings("rawtypes")
    @Test
    public void test02(){
        Set set = new LinkedHashSet();
        set.add(456);
        set.add(123);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(new User("Tom", 12));
        set.add(new User("Tom", 12));
        set.add(129);
    
        System.out.println(set);
        //[456, 123, AA, CC, User{name='Tom', age=12}, 129]
    }

    7. TreeSet的使用

    TreeSet : 可以按照添加对象的指定属性,进行排序

    TreeSet底层使用的红黑树实现的,利用二叉树存储数据。调用compareTo()或compare()来判断元素是否相同。

    在后续的调用contains方法、remove方法等,都是调用的compareTo或者compare方法来判断的。

    向TreeSet中添加的数据,要求是同一个类的对象,不能添加不同类的对象,否则报ClassCastException异常。

    向TreeSet中添加Integer对象 :

    @SuppressWarnings("rawtypes")
    @Test
    public void test01(){
        TreeSet set = new TreeSet();
    
        set.add(34);
        set.add(-34);
        set.add(43);
        set.add(11);
        set.add(8);
    
        System.out.println(set);//[-34, 8, 11, 34, 43]
    
        Iterator it = set.iterator();
        while(it.hasNext()){
            Object obj = it.next();
            System.out.print(obj + " ");
        }//-34 8 11 34 43 从小到大的顺序
    }

    7.1 TreeSet的自然排序

    在创建TreeSet对象时,调用空参构造器,那么就会按照对象的自然排序方式进行操作。

    向空参构造的TreeSet中添加自定义类User对象,其自定义的对象必须实现Comparable接口,否则报异常

    ClassCastException: com.atguigu.study.User cannot be cast to java.lang.Comparable

    自然排序中,比较两个对象是否相同的标准为 : compareTo()返回 0,不再是equals方法。两个对象是否相同的标准,是一个属性相同就认为两个对象相同,还是所有属性相同才认为两个对象相同,就要看自己在compareTo()方法中实现的逻辑了。

    @SuppressWarnings("rawtypes")
    @Test
    public void test02(){
        TreeSet set = new TreeSet();
    
        set.add(new User("Tom", 12));
        set.add(new User("Jerry", 32));
        set.add(new User("Jim", 2));
        set.add(new User("Mike", 65));
        set.add(new User("Jack", 33));
    
        Iterator it = set.iterator();
        while(it.hasNext()){
            Object obj = it.next();
            System.out.print(obj + " ");
        }
        //User{name='Jack', age=33} User{name='Jerry', age=32} User{name='Jim', age=2}
        // User{name='Mike', age=65} User{name='Tom', age=12}
    }
    
    
    public class User implements Comparable{
        private String name;
        private int age;
    
        public User(){}
        public User(String name, int age){
            this.age = age;
            this.name = name;
        }
    
        //按照名字从小到大排
        @Override
        public int compareTo(Object o) {
    
            if (o instanceof User) {
                User user = (User) o;
    
                return this.name.compareTo(user.name);
    
            } else throw new RuntimeException("传入数据类型不一致");
    
        }
    }

    7.2 TreeSet的定制排序

    在创建TreeSet对象时,调用传入比较器的构造器,那么就会按照对象的定制排序方式进行操作。

    定制排序中,比较两个对象是否相同的标准为 : compare()返回 0,不再是equals方法。

    @SuppressWarnings("rawtypes")
    @Test
    public void test03(){
        //按照年龄从小到大排 生成一个比较器
        Comparator com = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof User && o2 instanceof User) {
                    User user1 = (User) o1;
                    User user2 = (User) o2;
    
                    return Integer.compare(user1.getAge(), user2.getAge());
    
                } else throw new RuntimeException("传入数据类型不一致");
            }
        };
    	//构造器中传入一个比较器 按照定制排序方式操作
        TreeSet set = new TreeSet(com);
    
        set.add(new User("Tom", 12));
        set.add(new User("Jerry", 32));
        set.add(new User("Jim", 2));
        set.add(new User("Mike", 65));
        set.add(new User("Jack", 33));
    
        Iterator it = set.iterator();
        while(it.hasNext()){
            Object obj = it.next();
            System.out.print(obj + " ");
        }
        //User{name='Jim', age=2} User{name='Tom', age=12} User{name='Jerry', age=32}
        // User{name='Jack', age=33} User{name='Mike', age=65}
    }

    六、Map接口

    1. Map的接口框架

     |----Map : 双列数据,存储key-value对的数据 ---->类似于高中的函数 : y = f(x)
          |----HashMap : 作为Map的主要实现类,线程不安全,效率高,可以存储null的key和value
              |----LinkedHashMap : 保证在遍历map元素时,可以按照添加的顺序实现遍历
                  原理 : 在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素
                  对于频繁的遍历操作,此类执行效率高于HashMap。
          |----Hashtable : 作为古老的实现类,线程安全,效率低,不可以存储null的key和value
              |----Properties : 常用来处理配置文件。key-value都是String类型
          |----TreeMap : 保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
              			 底层使用红黑树实现
    
     HashMap的底层 : 数组 + 链表 (JDK 7及之前)
                    数组 + 链表 + 红黑树(JDK 8)

    2. 关于Map结构的理解

    key-value拆分理解 :

    Map中的key : 无序的、不可重复的,使用Set存储所有的key (以HashMap为例,key所在的类要重写equals()和hashCode())。

    Map中的value : 无序的、可重复的,使用Collection存储所有的value(value所在的类要重写equals()方法)。

    一个键值对 : key-value构成了一个Entry对象。

    Map中的Entry : 无序的、不可重复的,使用Set存储所有的Entry。

    Java集合 : 底层原理剖析_第5张图片

    3. HashMap的底层实现原理

    3.1 JDK 7中

    HashMap map = new HashMap();

    在实例化之后,底层创建了长度是16的一维数组 Entry[] table。

    map.put()...
    map.put(key1, value1);

    已经执行多次put以后的put(一个普通的put) 的执行过程 :

    首先,调用key1所在类的hashCode()方法计算key1的哈希值,此哈希值经过某种算法(&)计算以后,得到在Entry数组中的存放位置。

    如果此位置上的数据为空,此时的key1-value1(Entry)添加成功 (情况1) 。如果此位置上的数据不为空(意味着此位置上存在一个或多个数据以链表形式存在),比较当前key1和已经存在的一个或多个数据的哈希值。

    如果key1的哈希值与已经存在的数据都不相同,此时key1-value1添加成功 (情况2) 。如果key1的哈希值与已经存在的某个数据 (key2-value2) 的哈希值相同,继续比较,调用key1所在类的equals()方法比较。

    如果equals()返回false,此时key1-value1添加成功 (情况3) 。如果equals()返回true,则将使用value1替换value2。

    @SuppressWarnings("rawtypes")
    @Test
    public void test02(){
        HashMap map = new HashMap();
    
        //put具有修改功能
        map.put("Tom", 12);
        System.out.println(map);//{Tom=12}
        map.put("Tom", 45);
        System.out.println(map);//{Tom=45}
    
    }

    补充 : 关于情况2和情况3,此时key1-value1和原来的数据以链表的方式 (JDK 7新元素在上) 存储。

    在添加过程中,涉及到扩容问题,当超出临界值时 (且要存放的位置非空) 默认的扩容方式 : 扩容为原来数组的二倍,并将原有的数据复制过来。在复制的时候会重新计算各个元素的位置,重新存放。

    3.2 JDK 8中

    JDK 8相较于JDK 7在底层实现方面的不同 :

    • new HashMap() 时底层没有创建一个长度为16的数组

    • JDK 8底层的数组是Node[],而非Entry[]

    • 首次调用put()方法时,底层创建一个长度为16的数组

    • JDK 7底层结构只有 : 数组 + 链表,JDK 8底层结构 : 数组 + 链表 + 红黑树。当数组的某一个索引位置上的以链表形式存在的数据个数大于8,且当前数组的长度大于64时,此时,此索引位置上的所有数据改为使用红黑树存储。

    HashMap源码中的重要常量 :

    • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16

    • DEFAULT_LOAD_FACTOR:HashMap的默认加载因子 (可以控制链表的多少),0.75 (比较稳定的数值)

    • threshold:扩容的临界值,容量 x 填充因子 = 0.75 x 16 = 12

    • TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树 ,8

    • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,64

    负载因子值的大小对HashMap有什么影响 ?

    • 负载因子的大小决定了HashMap的数据密度。

    • 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。

    • 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。

    • 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数。

    4. LinkedHashMap的底层实现原理

    能够按照添加的顺序进行遍历 :

    @SuppressWarnings("rawtypes")
    @Test
    public void test01(){
        Map map1 = new HashMap();
    
        map1.put(123, "AA");
        map1.put(456, "BB");
        map1.put(789, "CC");
    
        System.out.println(map1);//{789=CC, 456=BB, 123=AA}
    
        Map map2 = new LinkedHashMap();
    
        map2.put(123, "AA");
        map2.put(456, "BB");
        map2.put(789, "CC");
    
        System.out.println(map2);//{123=AA, 456=BB, 789=CC}
    }

    在源码中,内部类Entry :

    static class Entry extends HashMap.Node {
        Entry before, after;
        Entry(int hash, K key, V value, Node next) {
            super(hash, key, value, next);
        }
    }

    增加了两个指针来记录上一个添加的元素和下一个添加的元素地址,遍历的时候通过该指针实现按照添加顺序遍历。

    5. Map接口中的方法

    添加、删除、修改操作:

    Object put(Object key,Object value) : 将指定key-value添加到(或修改)当前map对象中

    void putAll(Map m) : 将m中的所有key-value对存放到当前map中

    Object remove(Object key) : 移除指定key的key-value对,并返回value

    void clear() : 清空当前map中的所有数据

    /*
    Map接口中的常用方法测试
     */
    @SuppressWarnings("rawtypes")
    @Test
    public void test03(){
        Map map = new HashMap();
    
        //1. Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
        //添加
        map.put("AA", 123);
        map.put(45, 123);
        map.put("BB", 56);
        //修改
        map.put("AA", 87);
    
        System.out.println(map);//{AA=87, BB=56, 45=123}
    
        Map map1 = new HashMap();
        map1.put("CC", 123);
        map1.put("DD", 123);
    
        //2. void putAll(Map m):将m中的所有key-value对存放到当前map中
        map.putAll(map1);
        System.out.println(map);
        //{AA=87, BB=56, CC=123, DD=123, 45=123}
    
        //3. Object remove(Object key):移除指定key的key-value对,并返回value
        Object value = map.remove("CC");
        Object ee = map.remove("EE");
        System.out.println(value);//123
        System.out.println(ee);//null remove的key不存在将返回null
        System.out.println(map);//{AA=87, BB=56, DD=123, 45=123}
    
        //4. void clear():清空当前map中的所有数据
        map.clear();//与map = null操作不同
        System.out.println(map.size());//0
        System.out.println(map);//{}
    }

    元素查询的操作:

    Object get(Object key) : 获取指定key对应的value

    boolean containsKey(Object key) : 是否包含指定的key

    boolean containsValue(Object value) : 是否包含指定的value

    int size() : 返回map中key-value对的个数

    boolean isEmpty() : 判断当前map是否为空

    boolean equals(Object obj) : 判断当前map和参数对象obj是否相等

    /*
    Map接口的常用方法测试
     */
    @SuppressWarnings("rawtypes")
    @Test
    public void test04(){
        Map map = new HashMap();
    
        map.put("AA", 123);
        map.put(45, 123);
        map.put("BB", 56);
    
        //1. Object get(Object key):获取指定key对应的value
        System.out.println(map.get("AA"));//123
        System.out.println(map.get("CC"));//null
    
        //2. boolean containsKey(Object key):是否包含指定的key
        boolean isExistKey = map.containsKey("AA");
        System.out.println(isExistKey);//true
    
        //3. boolean containsValue(Object value):是否包含指定的value
        boolean isExistVal = map.containsValue(123);
        System.out.println(isExistVal);//true
    
        //4. int size():返回map中key-value对的个数
        System.out.println(map.size());//3
    
        //5. boolean isEmpty():判断当前map是否为空
        boolean isEmpty = map.isEmpty();
        System.out.println(isEmpty);//false
    
        //6. boolean equals(Object obj):判断当前map和参数对象obj是否相等
        //要想为true则必须传入一个map且数据相同
    }

    元视图操作的方法:

    Set keySet() : 返回所有key构成的Set集合

    Collection values() : 返回所有value构成的Collection集合

    Set entrySet() : 返回所有key-value对构成的Set集合

    /*
    Map接口中的常用方法测试
     */
    @SuppressWarnings("rawtypes")
    @Test
    public void test05(){
        Map map = new HashMap();
    
        map.put("AA", 123);
        map.put(45, 123);
        map.put("BB", 56);
    
        //1. 遍历所有的Key集合 :
        Set keySet = map.keySet();
        for (Object o : keySet) {
            System.out.print(o + " ");
        }//AA BB 45
    
        System.out.println();
    
        //2. 遍历所有的values集 :
        Collection values = map.values();
        Iterator it = values.iterator();
        while(it.hasNext()){
            Object obj = it.next();
            System.out.print(obj + " ");
        }//123 56 123
    
        System.out.println();
    
        //3. 遍历所有的Entry集 :
        //方式一 :
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Object obj = iterator.next();
            //entrySet集合中的元素都是entry
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + " ---> " + entry.getValue());
        }
        /*
        AA ---> 123
        BB ---> 56
        45 ---> 123
         */
    
        System.out.println();
    
        //方式二 :
        Iterator iterator1 = keySet.iterator();
        while(iterator1.hasNext()){
            Object key = iterator1.next();
            Object value = map.get(key);
    
            System.out.println(key + " ---> " + value);
        }
        /*
        AA ---> 123
        BB ---> 56
        45 ---> 123
         */
    }

    总结 : 常用方法

    增 : put(Object key, Object value)
    删 : remove(Object key)
    改 : put(Object key, Object value)
    查 : get(Object key)
    长度 : size()
    遍历 : keySet()/values()/entrySet()

    6. TreeMap的使用

    向TreeMap中添加key-value,要求key必须是由同一个类创建的对象。

    只能按照key来排,不能按照value排序。

    6.1 TreeMap的自然排序

    @SuppressWarnings("rawtypes")
    @Test
    public void test01(){
        TreeMap map = new TreeMap();
    
        User u1 = new User("Tom", 23);
        User u2 = new User("Jerry", 32);
        User u3 = new User("Jack", 20);
        User u4 = new User("Rose", 18);
    
        map.put(u1, 98);
        map.put(u2, 89);
        map.put(u3, 76);
        map.put(u4, 100);
    
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Object obj = iterator.next();
            //entrySet集合中的元素都是entry
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + " ---> " + entry.getValue());
        }
        /*
        User{name='Jack', age=20} ---> 76
        User{name='Jerry', age=32} ---> 89
        User{name='Rose', age=18} ---> 100
        User{name='Tom', age=23} ---> 98
         */
    }

    6.2 TreeMap的定制排序

    @SuppressWarnings("rawtypes")
    @Test
    public void test02(){
        TreeMap map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof User && o2 instanceof User) {
                    User u1 = (User) o1;
                    User u2 = (User) o2;
                    //按照年龄排
                    return Integer.compare(u1.getAge(), u2.getAge());
    
                } else throw new RuntimeException("传入的类型不一致");
            }
        });
    
        User u1 = new User("Tom", 23);
        User u2 = new User("Jerry", 32);
        User u3 = new User("Jack", 20);
        User u4 = new User("Rose", 18);
    
        map.put(u1, 98);
        map.put(u2, 89);
        map.put(u3, 76);
        map.put(u4, 100);
    
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Object obj = iterator.next();
            //entrySet集合中的元素都是entry
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + " ---> " + entry.getValue());
        }
        /*
        User{name='Rose', age=18} ---> 100
        User{name='Jack', age=20} ---> 76
        User{name='Tom', age=23} ---> 98
        User{name='Jerry', age=32} ---> 89
         */
    }

    7. Properties的使用

    Properties 类是 Hashtable 的子类,该对象用于处理属性文件

    由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型

    存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法

    public class PropertiesTest {
        public static void main(String[] args) {
            FileInputStream fis = null;
    
            try {
                Properties pros = new Properties();
                fis = new FileInputStream("jdbc.properties");
    
                pros.load(fis);//加载流对应的文件
    
                String name = pros.getProperty("name");//获取value
                String password = pros.getProperty("password");
    
                System.out.println("name = " + name + " password = " + password);
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fis != null)
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
            }
        }
    }

    七、Collections工具类

    Collections 是一个操作 Set、List 和 Map 等集合的工具类

    Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。

    1. 排序操作

    关于集合的排序操作,均为static方法 :

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

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

    • sort(List) : 根据元素的自然顺序对指定List集合元素按升序排序

    • sort(List,Comparator) : 根据指定的Comparator产生的顺序对List 集合元素进行排序

    • swap(List,int,int) : 将指定list 集合中的 i 处元素和 j 处元素进行交换

      @SuppressWarnings("rawtypes")
      @Test
      public void test01(){
          List list = new ArrayList();
      
          list.add(123);
          list.add(43);
          list.add(765);
          list.add(-97);
          list.add(0);
      
          System.out.println(list);//[123, 43, 765, -97, 0]
          //1. reverse(List) : 反转List中元素的顺序
          Collections.reverse(list);
          System.out.println(list);//[0, -97, 765, 43, 123]
      
          //2. shuffle(List) : 对List集合元素进行随机排序 (随机化)
          Collections.shuffle(list);
          System.out.println(list);//[-97, 0, 43, 123, 765]
      
          //3. sort(List) : 根据元素的自然顺序对指定List集合元素按升序排序
          Collections.sort(list);
          System.out.println(list);//[-97, 0, 43, 123, 765]
      
          //4. swap(List,int,int) : 将指定list集合中的i处元素和j处元素进行交换
          Collections.swap(list, 0, 1);
          System.out.println(list);//[0, -97, 43, 123, 765]
      }

    2. 查找替换操作

    • Object max(Collection) : 根据元素的自然顺序,返回给定集合中的最大元素

    • Object max(Collection,Comparator) : 根据Comparator 指定的顺序,返回给定集合中的最大元素

    • Object min(Collection)

    • Object min(Collection,Comparator)

    • int frequency(Collection,Object) : 返回指定集合中指定元素的出现次数

      @SuppressWarnings("rawtypes")
      @Test
      public void test02(){
          List list = new ArrayList();
      
          list.add(123);
          list.add(43);
          list.add(765);
          list.add(765);
          list.add(765);
          list.add(-97);
          list.add(0);
          System.out.println(list);
          //1. Object max(Collection) : 根据元素的自然顺序,返回给定集合中的最大元素
          Comparable max = Collections.max(list);
          System.out.println(max);//765
      
          //2. Object min(Collection)
          Comparable min = Collections.min(list);
          System.out.println(min);//-97
      
          //3. int frequency(Collection,Object) : 返回指定集合中指定元素的出现次数
          int frequency = Collections.frequency(list, 765);
          System.out.println(frequency);//3
      
      }
    • void copy(List dest,List src) : 将src中的内容复制到dest中

    • boolean replaceAll(List list,Object oldVal,Object newVal) : 使用新值替换List对象的所有旧值

          @SuppressWarnings("rawtypes")
          @Test
          public void test03(){
              List list = new ArrayList();
      ​
              list.add(123);
              list.add(43);
              list.add(765);
              list.add(-97);
              list.add(0);
      ​
              System.out.println(list);
      ​
              //1. void copy(List dest,List src) : 将src中的内容复制到dest中
              //错误 报异常 底层要判断 src.size() > dest.size()
              //java.lang.IndexOutOfBoundsException: Source does not fit in dest
      //        List dest = new ArrayList();
      //        Collections.copy(dest, list);
              //利用Object数组把dest的size撑开
              List dest = Arrays.asList(new Object[list.size()]);
              Collections.copy(dest, list);
              System.out.println(dest);//[123, 43, 765, -97, 0]
      ​
              //2. boolean replaceAll(List list,Object oldVal,Object newVal) : 使用新值替换List对象的所有旧值
              Collections.replaceAll(list, 123, 0);
              System.out.println(list);//[0, 43, 765, -97, 0]
          }

    3. 同步控制

    Collections 类中提供了多个synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。

    @SuppressWarnings("rawtypes")
    @Test
    public void test04(){
        List list = new ArrayList();
    ​
        list.add(123);
        list.add(43);
        list.add(765);
        list.add(-97);
        list.add(0);
    ​
        //synchronizedXxx() : 该方法可使将指定集合包装成线程同步的集合
        //返回的synchronizedList即为线程安全的List
        List synchronizedList = Collections.synchronizedList(list);
        System.out.println(synchronizedList);//[123, 43, 765, -97, 0]
    }

    你可能感兴趣的:(Java高级编程,java,jvm,算法,面试,数据结构)