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集合可分为Collection和Map两种体系 :

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

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

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

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

Collection接口继承树 :

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

 

Map接口继承树 :

|----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<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
    System.out.println(list);//[AA, BB, CC]
​
    List<int[]> list1 = Arrays.asList(new int[]{123, 456});//认为是一个参数(一维数组)
    System.out.println(list1);//[[I@300ffa5d]
    System.out.println(list1.size());//1 --> 一个元素
​
    List<Integer> 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接口中的方法

  
  @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> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> 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<Object>();
    
        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<Object>();
    
        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<Object>();
    
        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<Object>();
    
        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<Object> 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<Object>();
    
        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。

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<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);
    }
}

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

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]
}
  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

侯静川

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

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

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

打赏作者

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

抵扣说明:

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

余额充值