第一章:集合概述

  • 集合是 Java 提供的一种容器,可以用来存储多个数据。
  • 集合的本质是用来 存储对象(有时,你会看到直接向集合中插入基本类型数据,会很疑惑,不是只能存储对象吗?其实不然,因为 JDK5 的新特性自动装箱和拆箱,所以在存储基本类型的数据的时候,会转换为对应的包装类型对象)。
  • 集合和数组既然都是容器,它们有啥区别?
    • ① 数组的长度是固定的,集合的长度是可变的。
    • ② 数组中可以存储基本类型数据,也可以存储对象;但是,集合中只能存储对象。
  • 集合主要分为两大类型:
    • ① Collection(单列集合):表示一组对象。
    • ② Map(双列集合):表示一组映射关系或键值对。

集合概述.png

第二章:Collection 接口

2.1 概述

  • Collection 接口是 List、Set 接口的父接口,该接口中定义的方法既可以用于操作 List 集合,也可以用于操作 Set 集合。
  • JDK 不提供此接口的任何直接实现,而是提供更具体的子接口(如:List 、Set 等)实现。
  • 在JDK 5 之前,Java 集合会丢失容器中所有的对象的数据类型,将所有对象都当成Object类型来处理;但是,从JDK 5 增加了 泛型 以后,Java 集合就可以记住容器中对象的数据类型。
  1. public interface Collection<E> extends Iterable<E> {
  2. ...
  3. }

2.2 常用方法

2.2.1 添加元素

  • 添加元素对象到当前集合中:
  1. boolean add(E e);
  • 添加另一个集合中的所有元素到当前集合中:
  1. boolean addAll(Collection<? extends E> c);
  • 示例:
  1. package com.github.demo1;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2021-09-27 14:41
  8. */
  9. public class Test {
  10. public static void main(String[] args) {
  11. Collection<String> collection = new ArrayList<>();
  12. collection.add("hello");
  13. collection.add("world");
  14. System.out.println("collection = " + collection); // collection = [hello, world]
  15. }
  16. }
  • 示例:
  1. package com.github.demo2;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2021-09-27 16:10
  8. */
  9. public class Test {
  10. public static void main(String[] args) {
  11. Collection<String> c1 = new ArrayList<>();
  12. c1.add("aa");
  13. c1.add("bb");
  14. c1.add("cc");
  15. Collection<String> c2 = new ArrayList<>();
  16. c2.add("ee");
  17. c2.add("ff");
  18. // 将c2中的所有元素添加到c1中
  19. c1.addAll(c2);
  20. System.out.println("c1 = " + c1); // c1 = [aa, bb, cc, ee, ff]
  21. }
  22. }
  • 示例:
  1. package com.github.demo3;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2021-09-27 16:13
  8. */
  9. public class Test {
  10. public static void main(String[] args) {
  11. Collection<Object> c1 = new ArrayList<>();
  12. c1.add("aa");
  13. c1.add("bb");
  14. c1.add("cc");
  15. Collection<Object> c2 = new ArrayList<>();
  16. c2.add("ee");
  17. c2.add("ff");
  18. c1.add(c2);
  19. System.out.println("c1 = " + c1); // c1 = [aa, bb, cc, [ee, ff]]
  20. }
  21. }

2.2.2 删除元素

  • 从当前集合中删除第一个找到的与 obj 对象 equals 返回 true 的元素:
  1. boolean remove(Object o);
  • 从当前集合中删除所有与 coll 集合中相同的元素:
  1. boolean removeAll(Collection<?> c)
  • 清空集合:
  1. void clear();
  • 示例:
  1. package com.github.demo4;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2021-09-27 16:35
  8. */
  9. public class Test {
  10. public static void main(String[] args) {
  11. Collection<String> c1 = new ArrayList<>();
  12. c1.add("aa");
  13. c1.add("bb");
  14. c1.add("cc");
  15. System.out.println("c1 = " + c1); // c1 = [aa, bb, cc]
  16. c1.remove("aa");
  17. System.out.println("c1 = " + c1); // c1 = [bb, cc]
  18. }
  19. }
  • 示例:
package com.github.demo5;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-27 16:38
 */
public class Test {
    public static void main(String[] args) {
        Collection<Object> c1 = new ArrayList<>();
        c1.add("aa");
        c1.add("bb");
        c1.add("cc");

        Collection<Object> c2 = new ArrayList<>();
        c2.add("ee");
        c2.add("ff");

        c1.add(c2);

        System.out.println("c1 = " + c1); // c1 = [aa, bb, cc]

        c1.remove(c2);

        System.out.println("c1 = " + c1); // c1 = [aa, bb, cc]

    }
}
  • 示例:
package com.github.demo6;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-27 16:41
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> c1 = new ArrayList<>();
        c1.add("aa");
        c1.add("bb");
        c1.add("cc");

        Collection<String> c2 = new ArrayList<>();
        c2.add("ee");
        c2.add("ff");

        c1.addAll(c2);

        System.out.println("c1 = " + c1); // c1 = [aa, bb, cc, ee, ff]

        c1.removeAll(c2);

        System.out.println("c1 = " + c1); // c1 = [aa, bb, cc]
    }
}

2.2.3 判断

  • 判断当前集合是否为空集合:
boolean isEmpty();
  • 判断当前集合中是否存在一个与 obj 对象 equals 返回 true 的元素:
boolean contains(Object o);
  • 判断 c 集合中的元素是否在当前集合中都存在,即 c 集合是否为当前集合的子集:
boolean containsAll(Collection<?> c)
  • 示例:
package com.github.demo7;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-27 16:43
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> c1 = new ArrayList<>();
        c1.add("aa");
        c1.add("bb");
        c1.add("cc");
        c1.add("dd");

        System.out.println("c1 = " + c1.isEmpty()); // c1 = false

        c1.clear();

        System.out.println("c1 = " + c1.isEmpty()); // c1 = true
    }
}
  • 示例:
package com.github.demo8;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-27 16:46
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> c1 = new ArrayList<>();
        c1.add("aa");
        c1.add("bb");
        c1.add("cc");
        c1.add("dd");

        System.out.println("c1 = " + c1.contains("aa")); // c1 = true
        System.out.println("c1 = " + c1.contains("aaa")); // c1 = false
    }
}
  • 示例:
package com.github.demo9;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-27 16:47
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> c1 = new ArrayList<>();
        c1.add("aa");
        c1.add("bb");
        c1.add("cc");
        c1.add("dd");

        Collection<String> c2 = new ArrayList<>();
        c2.add("aa");
        c2.add("bb");
        c2.add("ee");

        System.out.println("c1.containsAll(c2) = " + c1.containsAll(c2)); // c1.containsAll(c2) = false

        Collection<String> c3 = new ArrayList<>();
        c2.add("aa");
        c2.add("bb");

        System.out.println("c1.containsAll(c3) = " + c1.containsAll(c3)); // c1.containsAll(c3) = true

    }
}

2.2.4 获取集合中元素的个数

  • 获取当前集合中实际存储的元素个数:
int size();
  • 示例:
package com.github.demo10;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-27 16:50
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> c1 = new ArrayList<>();
        c1.add("aa");
        c1.add("bb");
        c1.add("cc");
        c1.add("dd");

        System.out.println("c1.size() = " + c1.size()); // c1.size() = 4

        c1.clear();

        System.out.println("c1.size() = " + c1.size()); // c1.size() = 0
    }
}

2.2.5 交集

  • 当前集合仅保留与 c 集合中的元素相同的元素,即当前集合中仅保留两个集合的交集:
boolean retainAll(Collection<?> c);
  • 示例:
package com.github.demo11;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-27 16:53
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> c1 = new ArrayList<>();
        c1.add("aa");
        c1.add("bb");
        c1.add("cc");
        c1.add("dd");

        Collection<String> c2 = new ArrayList<>();
        c2.add("bb");

        c1.retainAll(c2);

        System.out.println("c1 = " + c1); // c1 = [bb]
    }
}

2.2.6 转数组

  • 返回包含当前集合中所有元素的数组:
Object[] toArray();
<T> T[] toArray(T[] a);
  • 示例:
package com.github.demo12;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-27 16:55
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<>();
        c.add("aa");
        c.add("bb");
        c.add("cc");
        c.add("dd");

        Object[] objects = c.toArray();
        System.out.println("objects = " + Arrays.toString(objects)); // objects = [aa, bb, cc, dd]

        String[] strings = c.toArray(new String[c.size()]); 
        System.out.println("strings = " + Arrays.toString(strings)); // strings = [aa, bb, cc, dd]
    }
}

第三章:Iterator 迭代器

3.1 Iterator 接口

  • 在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK 专门提供了一个接口 java.util.Iterator
  • Iterator 接口也是 Java 集合中的医院,但是它和 Collection 、Map 接口有所不同,Collection 接口和 Map 接口主要用来存储元素,而 Iterator 接口主要用于迭代访问(即遍历)Collection 中的元素,因此 Iterator 对象也称为迭代器。
  • 获取迭代器的方法(Collection 接口中提供的方法):
Iterator<E> iterator();
  • Iterator 接口中的常用方法:

    • ① 判断是否有元素可以迭代,如果有,返回 true ;否则,返回 false :

      boolean hasNext();
      
    • ② 返回迭代的下一个元素:

      E next();
      

注意:在使用迭代器进行遍历集合的时候,如果集合中已经没有元素了,还使用迭代器的 next 方法,将会抛出 java.util.NoSuchElementException 异常。

  • 示例:
package com.github.collection1.demo1;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 08:18
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> collection = new ArrayList<>();
        collection.add("aa");
        collection.add("bb");
        collection.add("cc");
        collection.add("dd");

        // 获取迭代器
        Iterator<String> iterator = collection.iterator();
        // 判断集合中是否有元素
        while (iterator.hasNext()) {
            // 取出集合中的元素
            String next = iterator.next();
            System.out.println(next);
        }

    }
}
  • 示例:
package com.github.collection1.demo2;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 08:31
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> collection = new ArrayList<>();
        collection.add("aa");
        collection.add("bb");
        collection.add("cc");
        collection.add("dd");

        for (Iterator<String> iterator = collection.iterator(); iterator.hasNext();) {
            String next = iterator.next();
            System.out.println(next);
        }

    }
}

3.2 迭代器实现原理

  • 每个集合容器的内部结构都是不同的,但是迭代器都可以进行统一的遍历是实现,是怎么做到的?
  • Collection 接口提供了获取 Iterator 的方法:
Iterator<E> iterator();
  • 那么,Collection 接口的每个子类都必须重写这个方法。
  • 以 ArrayList 为例:
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    // 重写了iterator方法
    public Iterator<E> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator<E> {
        ...
    }

    ...
}

3.3 使用 Iterator 迭代器删除元素

  • Iterator 接口中有一个删除的方法:
default void remove() {
    throw new UnsupportedOperationException("remove");
}
  • 既然,Collection 接口中已经有了 remove(xxx) 的方法,为什么 Iterator 迭代器中还要提供 remove() 方法?
  • 因为Collection 接口的 remove(xxx) 方法无法根据指定条件删除。

注意:不要在使用 Iterator 迭代器迭代元素的时候,调用 Collection 的 remove(xxx) 方法,否则会报 java.util.ConcurrentModificationException 异常或出现其他不确定的行为。

  • 示例:删除集合中的偶数元素
package com.github.collection1.demo3;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 09:35
 */
public class Test {
    public static void main(String[] args) {
        Collection<Integer> collection = new ArrayList<>();
        collection.add(1);
        collection.add(2);
        collection.add(3);
        collection.add(4);

        System.out.println("原来集合中的元素 = " + collection); // 原来集合中的元素 = [1, 2, 3, 4]

        for (Iterator<Integer> iterator = collection.iterator(); iterator.hasNext();) {
            Integer ele = iterator.next();
            if (ele % 2 == 0) {
                iterator.remove();
            }
        }

        System.out.println("后来集合中的元素 = " + collection); // 后来集合中的元素 = [1, 3]
    }
}

3.4 并发修改异常( ConcurrentModificationException )

  • 异常的产生原因:在迭代器遍历集合的过程中,调用集合自身的功能(例如:调用集合的 add 和 remove 方法),改变集合的长度。

  • 示例:并发修改异常

package com.github.collection1.demo4;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 09:50
 */
public class Test {
    public static void main(String[] args) {
        Collection<String> collection = new ArrayList<>();
        collection.add("aa");
        collection.add("bb");
        collection.add("cc");
        collection.add("dd");

        for (Iterator<String> iterator = collection.iterator(); iterator.hasNext();) {
            String ele = iterator.next();
            System.out.println("ele = " + ele);
            collection.add("ee");
        }
    }
}

并发修改异常.png

  • 如果在 Iterator、ListIterator 迭代器创建后的任意时间从结构上修改了集合(通过迭代器自身的remove或add方法之外的任何其他方式),则迭代器会抛出 ConcurrentModificationException 异常。因此,面对并发的修改,迭代器很快会失败,而不是冒着在将来不确定的时间发生不确定行为的风险。
  • 这样设计时因此,迭代器代表集合中的某个元素的位置,内部会存储某些能够代表该位置的信息。当集合发生改变的时候,该信息的含义可能会发生变化,这个时候操作迭代器就有可能造成不可预料的后果。因此,果断抛出异常阻止,是最好的方法,者就是 Iterator 迭代器的快速失败机制。
  • 需要注意的是,迭代器的快速失败机制行文不能得到保证,一般来说,存在不同步的并发修改时,不可能做出任何坚决的保证。快速失败机制尽最大努力抛出 ConcurrentModificationException 。因此,迭代器的快速失败行文应该仅仅用于检测bug
  • 快速失败机制的实现原理:
    • ① 在 ArrayList 等集合类中都有一个 modCount 变量,它用来记录集合的结构被修改的次数。
    • ② 当我们给集合添加或删除元素的时候,会导致 modCount++ 。
    • ③ 当我们使用 Iterator 迭代器遍历集合的时候,会使用一个变量记录当前集合的 modCount 。例如:int expectedModCount = modCount;,并且,在迭代器每次 next() 迭代元素的时候,都要检查 expectedModCount != modCount ,如果不相等,则说明调用了 Iterator 迭代器以外的 Collection 的 add 、remove 等方法,修改了集合的结构,使得 modCount++ ,值变了,就会抛出 ConcurrentModificationException 。

3.5 集合存储自定义对象并迭代

  • 示例:
package com.github.collection1.demo5;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 10:07
 */
public class Person {
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + '}';
    }
}
package com.github.collection1.demo5;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 10:07
 */
public class Test {
    public static void main(String[] args) {
        Collection<Person> collection = new ArrayList<>();

        collection.add(new Person("张三", 11));
        collection.add(new Person("李四", 59));
        collection.add(new Person("王五", 19));
        collection.add(new Person("赵六", 42));
        collection.add(new Person("田七", 8));
        collection.add(new Person("王八", 2));

        for (Iterator<Person> iterator = collection.iterator(); iterator.hasNext();) {
            Person next = iterator.next();
            System.out.println(next);
        }
    }
}

3.6 增强 for 循环

  • 增强 for 循环(也称为 for each 循环)是 JDK 5 之后出来的一个高级的 for 循环,专门用来遍历数组和集合。
  • 语法:
for(元素的数据类型 变量: 数组 或 Collection集合){
    // 写代码
}
  • 示例:遍历数组(不要在遍历数组的时候对数组的元素进行修改)
package com.github.collection1.demo6;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 10:41
 */
public class Test {
    public static void main(String[] args) {
        // 创建数组
        int[] arr = {1, 2, 3, 4, 5};
        // 遍历数组
        for (int i : arr) {
            System.out.println("i = " + i);
        }
    }
}
  • 示例:遍历集合(不要在遍历集合的时候对集合中的元素进行增加、删除、替换等操作)
package com.github.collection1.demo7;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 10:45
 */
public class Test {
    public static void main(String[] args) {
        // 创建集合
        Collection<Integer> collection = new ArrayList<>();
        // 向集合中添加元素
        collection.add(1);
        collection.add(2);
        collection.add(3);
        collection.add(4);
        // 遍历集合
        for (Integer i : collection) {
            System.out.println("i = " + i);
        }
    }
}

3.7 java.lang.Iterable 接口

  • java.lang.Iterable 接口,实现这个接口,允许对象称为 "for each" 语句的目标。
  • JDK 5 的时候,Collection 接口继承了 java.lang.Iterable 接口,因此 Collection 系列的集合就可以直接使用 foreach 循环。
  • java.lang.Iterable 接口的抽象方法:
// 获取对应的迭代器,用来遍历数组或集合中的元素的。
Iterator<T> iterator();
  • foreach 循环遍历集合的本质就是使用 Iterator 迭代器进行遍历的。

注意:不要在使用 foreach 循环遍历集合的时候,使用 Collection 的 remove() 等方法。否则,要么报 java.util.ConcurrentModificationException 异常,要么行为不确定。

第四章:List 接口

4.1 概述

  • 鉴于 Java 中数组用来存储数据的局限性,我们通常使用 List 来代替数组。
  • List 集合类中 元素有序(元素存储和取出的顺序一致)、且可重复 ,集合中的每个元素都有其对应的顺序索引。
  • List 容器中的元素都对应一个整数类型的序号记载其在容器中的位置,根据根据序号存取容器中的元素。
  • List 接口的常用类:
    • ArrayList 。
    • LinkedList 。
    • Vector(已过时)。

4.2 常用方法

4.2.1 添加元素

  • 在指定的索引位置上添加元素:
void add(int index, E element);
  • 在指定的索引位置上添加另一个集合中的所有元素:
boolean addAll(int index, Collection<? extends E> c);
  • 示例:
package com.github.list.demo1;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:18
 */
public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        // 向集合的尾部添加
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);

        System.out.println("list = " + list); // list = [1, 2, 3, 4]

        // 在指定的索引上添加元素
        list.add(1, 10);

        System.out.println("list = " + list); // list = [1, 10, 2, 3, 4]
    }
}
  • 示例:
package com.github.list.demo2;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:25
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");

        System.out.println("list = " + list); // list = [aa, bb, cc, dd]

        List<String> list2 = new ArrayList<>();

        list2.add("ee");
        list2.add("ff");

        list.addAll(1, list2);

        System.out.println("list = " + list); // list = [aa, ee, ff, bb, cc, dd]
    }
}

4.2.2 获取元素

  • 根据索引获取元素:
E get(int index);
  • 根据开始索引和结束索引获取子 List 集合:
List<E> subList(int fromIndex, int toIndex);
  • 示例:
package com.github.list.demo3;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:28
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");

        String s = list.get(1);
        System.out.println("s = " + s); // s = bb
    }
}
  • 示例:
package com.github.list.demo4;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:31
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");

        System.out.println("list = " + list); // list = [aa, bb, cc, dd]

        List<String> list1 = list.subList(1, 3);

        System.out.println("list1 = " + list1); // list1 = [bb, cc]
    }
}
  • 示例:
package com.github.list.demo5;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:33
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");

        // 遍历List集合
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

4.2.3 获取元素索引

  • 从前往后根据元素查找其索引,如果找到,返回该元素的索引;否则,返回 -1 :
int indexOf(Object o);
  • 从后往前根据元素查找其索引,如果找到,返回该元素的索引;否则,返回 -1 :
int lastIndexOf(Object o);
  • 示例:
package com.github.list.demo6;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:35
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("aa");

        int index = list.indexOf("aa");
        System.out.println("index = " + index); // index = 0

        int index1 = list.indexOf("ee");
        System.out.println("index1 = " + index1); // index1 = -1
    }
}
  • 示例:
package com.github.list.demo7;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:38
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("aa");

        int index = list.lastIndexOf("aa");
        System.out.println("index = " + index); // index = 4

        int index1 = list.lastIndexOf("ff");
        System.out.println("index1 = " + index1); // index1 = -1
    }
}

4.2.4 删除元素

  • 根据索引删除元素,返回删除的元素:
E remove(int index);
  • 示例:
package com.github.list.demo8;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:40
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("aa");

        System.out.println("list = " + list); // list = [aa, bb, cc, dd, aa]

        String remove = list.remove(2);
        System.out.println("remove = " + remove); // remove = cc

        System.out.println("list = " + list); // list = [aa, bb, dd, aa]
    }
}

4.2.5 替换元素

  • 替换指定索引上的元素:
E set(int index, E element);
  • 示例:
package com.github.list.demo9;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:43
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("aa");

        System.out.println("list = " + list); // list = [aa, bb, cc, dd, aa]

        list.set(4, "java");

        System.out.println("list = " + list); // list = [aa, bb, cc, dd, java]
    }
}

4.3 List 特有的迭代器 ListIterator(了解)

  • List 接口提供了获取 ListIterator 的方法,该方法返回一个 ListIterator 对象:
ListIterator<E> listIterator();
// 如果从后向前遍历,index就是最后一个元素的索引,即list的size()
ListIterator<E> listIterator(int index);
  • ListIterator 接口继承了 Iterator 接口,提供了专门操作 List 的方法:

    • 是否有下一个元素:

      boolean hasNext();
      
    • 返回下一个元素:

      E next();
      
    • 返回后一个元素的索引:

      int nextIndex();
      
    • 返回前一个元素的索引:

      int previousIndex();
      
    • 逆向遍历集合,向前是否还有元素:

      boolean hasPrevious();
      
    • 获取前一个元素:

      E previous();
      
    • 通过迭代器添加元素到集合中:

      void add(E e);
      
    • 通过迭代器替换元素:

      void set(E e);
      
  • 示例:

package com.github.list.demo10;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 13:59
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("aa");

        for (ListIterator<String> iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
            String previous = iterator.previous();
            System.out.println(previous);
        }
    }
}

4.4 List接口的实现类之一:ArrayList

4.4.1 概述

  • ArrayList 具备了List接口的特性(有序、重复、索引)。
  • ArrayList 集合底层的实现原理是数组,大小可变。
  • ArrayList 的特点:查询速度快、增删慢。
  • ArrayList 在 JDK8 前后的实现区别:
    • JDK7 :ArrayList 像饿汉式,直接创建了一个初始容量为 10 的数组,每次扩容是原来长度的 1.5 倍。
    • JDK8 :ArrayList 像懒汉式,一开始创建一个长度为 0 的数组,当添加第一个元素的时候再创建一个容器为 10 的数组,每次扩容是原来长度的 1.5 倍。
  • ArrayList 是线程不安全的集合,运行速度快。

4.4.2 ArrayList的类成员变量

  • 默认容量:
private static final int DEFAULT_CAPACITY = 10;
  • 空数组:
private static final Object[] EMPTY_ELEMENTDATA = {};
  • 默认容量的空数组:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  • ArrayList 集合中的核心数组:
transient Object[] elementData;
  • 记录数组中存储个数:
private int size;
  • 数组扩容的最大值:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

4.4.3 ArrayList 类的构造方法

  • 无参构造方法:
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  • 传入指定容量的构造方法:
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

4.4.4 ArrayList 类的 add 方法

public boolean add(E e) {
    // 检查容量 size初始化的时候是0,那么size+1就是1
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将元素添加到数组中,size计数器++
    elementData[size++] = e;
    return true;
}

// minCapacity此时是1
private void ensureCapacityInternal(int minCapacity) {
    // 如果存储元素的数据 == 默认的空的数组,无参构造器中赋值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 返回DEFAULT_CAPACITY=10和minCapacity=1的最大值,即10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 扩容
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    // 10- 数组的长度0 > 0
    if (minCapacity - elementData.length > 0)
        // 传入10
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // overflow-conscious code
    // 数组的原来长度 0 
    int oldCapacity = elementData.length;
    // 新容量 = 数组原来的长度 + 数组原来的长度 / 2
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 0
    if (newCapacity - minCapacity < 0) // 0 -10 < 0
        newCapacity = minCapacity; // 新容量 = 10
    if (newCapacity - MAX_ARRAY_SIZE > 0) // 判断是否超过最大容量
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity); // 数组的复制
}

4.4.5 ArrayList 类的 get 方法

public E get(int index) {
    // 检查索引
    rangeCheck(index);
    // 返回数组中指定索引处的元素
    return elementData(index);
}

4.4.6 ArrayList 类的 set 方法

public E set(int index, E element) {
    // 检查索引
    rangeCheck(index);
    // 获取旧的元素
    E oldValue = elementData(index);
    // 将新的元素设置到指定的索引处
    elementData[index] = element;
    // 返回旧的元素
    return oldValue;
}

4.4.7 ArrayList 类的 remove 方法

public E remove(int index) {
    // 检查索引
    rangeCheck(index);

    modCount++;
    // 获取旧的元素
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 将index后面的元素依次复制到前面
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 最后一个元素设置为null
    elementData[--size] = null; // clear to let GC do its work
    // 返回旧的元素
    return oldValue;
}

4.5 List 接口的实现类之二:LinkedList

4.5.1 概述

  • LinkedList 具备了 List 接口的特性(有序、重复、索引)。
  • LinkedList 地城实现原理是双向链表。
  • LinkedList 的增删速度快、查询慢。
  • LinkedList 是线程不安全的集合,运行速度快。

4.5.2 LinkedList 集合特有方法

  • 元素插入到链表开头:
public void addFirst(E e) {}
  • 元素插入到列表结尾:
public void addLast(E e) {}
  • 获取链表开头的元素:
public E getFirst() {}
  • 获取链表结尾的元素:
public E getLast() {}
  • 移除链表开头的元素:
public E removeFirst() {}
  • 移除链表结尾的元素:
public E removeLast() {}
  • 示例:
package com.github.linkedlist.demo1;

import java.util.LinkedList;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-28 14:22
 */
public class Test {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();

        linkedList.add("aa");
        linkedList.add("bb");
        linkedList.add("cc");
        linkedList.add("dd");

        System.out.println("linkedList = " + linkedList); // linkedList = [aa, bb, cc, dd]

        linkedList.addFirst("你好啊");

        System.out.println("linkedList = " + linkedList); // linkedList = [你好啊, aa, bb, cc, dd]

        linkedList.addLast("你好呢");

        System.out.println("linkedList = " + linkedList); // linkedList = [你好啊, aa, bb, cc, dd, 你好呢]

        System.out.println(linkedList.getFirst()); // 你好啊

        System.out.println(linkedList.getLast()); // 你好呢

        linkedList.removeFirst();

        System.out.println("linkedList = " + linkedList); // linkedList = [aa, bb, cc, dd, 你好呢]

        linkedList.removeLast();

        System.out.println("linkedList = " + linkedList); // linkedList = [aa, bb, cc, dd]
    }
}

4.5.3 LinkedList 的类成员变量

  • 集合中存储元素个数的计数器:
transient int size = 0;
  • 第一个元素:
transient Node<E> first;
  • 最后一个元素:
transient Node<E> last;

4.5.4 LinkedList 的成员内部类 Node

// 节点
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.5.5 LinkedList 类的 add 方法

public boolean add(E e) {
    linkLast(e);
    return true;
}

void linkLast(E e) {
    // 声明新的节点对象 = last
    final Node<E> l = last; // l = null
    // 创建新的节点对象
    final Node<E> newNode = new Node<>(l, e, null);
    // 新的节点赋值给最后一个节点
    last = newNode;
    if (l == null)
        // 将新的节点赋值给first
        first = newNode;
    else
        // 将新的节点赋值给最后一个节点的最后
        l.next = newNode;
    size++;
    modCount++;
}

4.5.6 LinkedList 类的 get 方法

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
Node<E> node(int index) {
    // assert isElementIndex(index);
    // 索引是否小于长度的一半,二分查找
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

第五章:Set 接口

5.1 概述

  • Set 接口是 Collection 接口的子接口,Set 接口没有提供额外的方法。
  • Set 集合不允许包含相同的元素,如果试着将两个相同的元素加入同一个 Set 集合中,则会添加失败(不会抛出异常)。
  • Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals 方法。
  • Set 集合支持的遍历方式和 Collection 集合一样:foreach 和 Iterator。
  • Set 的常用实现类:HashSet 、TreeSet 、LinkedHashSet 。

  • 示例:

package com.github.set.demo1;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 08:29
 */
public class Test {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();

        set.add("aa");
        set.add("bb");
        set.add("cc");
        set.add("aa");

        System.out.println("set = " + set); // set = [aa, bb, cc]

        System.out.println("----------------");

        // foreach
        for (String s : set) {
            System.out.println(s);
        }

        System.out.println("----------------");

        // iterator
        for (Iterator<String> iterator = set.iterator(); iterator.hasNext();) {
            String next = iterator.next();
            System.out.println(next);
        }
    }
}

5.2 Set 的实现类之一:HashSet

  • HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合的时候都使用这个实现类。
  • HashSet 按照 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
  • HashSet 的底层实现原理是哈希表,在内部使用的是 HashMap 来实现的。
  • HashSet 具有以下的特点:
    • ① 不能保证元素的顺序(元素存储顺序和取出顺序不一定相同)。
    • ② HashSet 不是线程安全的。
    • ③ 集合元素可以为 null
  • HashSet集合判断两个元素相等的标准 :两个对象的 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。

  • 示例:

package com.github.set.demo2;

import java.util.Objects;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 08:58
 */
public class Person {

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;

    public Person() {}

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return this.age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        Person person = (Person)o;
        return Objects.equals(this.name, person.name) && Objects.equals(this.age, person.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.name, this.age);
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + '}';
    }
}
package com.github.set.demo2;

import java.util.HashSet;
import java.util.Set;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 08:59
 */
public class Test {
    public static void main(String[] args) {
        Set<Person> set = new HashSet<>();

        set.add(new Person("张三", 18));
        set.add(new Person("张三", 18));
        set.add(new Person("张三", 21));
        set.add(new Person("李四", 20));
        set.add(new Person("李四", 18));

        System.out.println("set = " + set); // set = [Person{name='张三', age=21}, Person{name='张三', age=18}, Person{name='李四', age=20}, Person{name='李四', age=18}]
    }
}

5.2 Set 的实现类之二:LinkedHashSet

  • LinkedHashSe t是 HashSet 的子类。
  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但是它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
  • LinkedHashSet 的插入性能略低于 HashSet ,但是在迭代访问Set里面的全部元素的时候有很好的性能。
  • LinkedHashSet 不允许集合元素重复。

  • 示例:

package com.github.set.demo3;

import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 10:06
 */
public class Test {
    public static void main(String[] args) {
        Set<String> set = new LinkedHashSet<>();

        set.add("aa");
        set.add("cc");
        set.add("bb");
        set.add("aa");

        System.out.println("set = " + set); // [aa, cc, bb]
    }
}

5.3 Set 的实现类之三:TreeSet

5.3.1 概述

  • TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
  • TreeSet 底层使用红黑树结构存储数据,在内部使用的是 TreeMap 来实现的。

TreeSet类图.png

  • TreeSet 有两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。

5.3.2 自然排序

  • 自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
  • 如果视图将一个对象添加到 TreeSet 中,则该对象所属的类必须实现 Comparable 接口。
  • Comparable 的实现:

    • BigDecimal 、BigInteger 以及所有的数值型对应的包装类:按照它们对应的数值大小进行比较。
    • Character :按照字符的 Unicode 值进行比较。
    • Boolean :true 对应的包装类实例大于 false 对应的包装类实例。
    • String :按照字符串中字符的 Unicode 值进行比较。
    • Date 、Time :后面的时间、日期比前面的时间、日期大。
  • 示例:

package com.github.set.demo4;

import java.util.Set;
import java.util.TreeSet;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 14:19
 */
public class Test {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();

        set.add("g");
        set.add("b");
        set.add("d");
        set.add("c");
        set.add("f");

        System.out.println("set = " + set); // set = [b, c, d, f, g]
    }
}
  • 示例:
package com.github.set.demo5;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 14:30
 */
public class Person implements Comparable<Person> {

    private String name;

    private int age;

    public Person() {}

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person o) {
        return Integer.compare(this.getAge(), o.getAge());
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age='" + this.age + '\'' + '}';
    }
}
package com.github.set.demo5;

import java.util.Set;
import java.util.TreeSet;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 14:30
 */
public class Test {
    public static void main(String[] args) {
        Set<Person> set = new TreeSet<>();

        set.add(new Person("张三", 15));
        set.add(new Person("李四", 99));
        set.add(new Person("王五", 58));
        set.add(new Person("赵六", 9));
        set.add(new Person("田七", 1));
        set.add(new Person("王八", 44));

        // set = [Person{name='田七', age='1'}, Person{name='赵六', age='9'}, Person{name='张三', age='15'}, Person{name='王八',
        // age='44'}, Person{name='王五', age='58'}, Person{name='李四', age='99'}]
        System.out.println("set = " + set);
    }
}

5.3.3 定制排序

  • 定制排序,通过 Comparator 接口来实现。需要重写 compare(T o1,T o2) 方法。
  • 利用 int compare(T o1,T o2) 方法,比较 o1 和 o2 的大小:如果方法返回正整数,则表示 o1 大于 o2 ;如果返回 0 ,表示相等;返回负整数,表示 o1 小于 o2 。
  • 要实现定制排序,需要将实现 Comparator 接口的实例作为形参传递给 TreeSet 的构造器。
  • 使用定制排序判断两个元素相等的标准是:通过 Comparator 比较两个元素返回了 0 。
  • 示例:
package com.github.set.demo6;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 14:30
 */
public class Person {

    private String name;

    private int age;

    public Person() {}

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age='" + this.age + '\'' + '}';
    }
}
package com.github.set.demo6;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 14:38
 */
public class Test {
    public static void main(String[] args) {
        Set<Person> set = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return Integer.compare(o2.getAge(), o1.getAge());
            }
        });

        set.add(new Person("张三", 15));
        set.add(new Person("李四", 99));
        set.add(new Person("王五", 58));
        set.add(new Person("赵六", 9));
        set.add(new Person("田七", 1));
        set.add(new Person("王八", 44));

        // set = [Person{name='李四', age='99'}, Person{name='王五', age='58'}, Person{name='王八', age='44'}, Person{name='张三', age='15'}, Person{name='赵六', age='9'}, Person{name='田七', age='1'}]
        System.out.println("set = " + set);
    }
}

第六章:Collections 工具类

6.1 概述

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

6.2 常用方法

  • 反转 List 中元素的顺序:
public static void reverse(List<?> list) {}
  • 对 List 集合中的元素进行随机排序:
public static void shuffle(List<?> list) {}
  • 根据元素的自然顺序对 List 集合中的元素进行排序:
public static <T extends Comparable<? super T>> void sort(List<T> list) {}
  • 根据指定的 Comparator 对 List 集合元素进行排序:
public static <T> void sort(List<T> list, Comparator<? super T> c) {}
  • 根据元素的自然顺序,返回给定集合中的最大元素:
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {}
  • 根据指定的 Comparator,返回给定集合中的最大元素:
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {}
  • 根据元素的自然顺序,返回给定集合中的最小元素:
public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) {}
  • 根据指定的 Comparator,返回给定集合中的最小元素:
public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp) {}
  • 返回指定集合中指定元素出现的次数:
public static int frequency(Collection<?> c, Object o) {}
  • 集合元素的复制:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {}
  • 使用新值替换 List 集合中的所有旧值:
public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal) {}
  • 示例:
package com.github.collections.demo1;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 15:24
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aa");
        list.add("dd");
        list.add("hh");
        list.add("bb");

        System.out.println("list = " + list); // list = [aa, dd, hh, bb]

        Collections.reverse(list);

        System.out.println("list = " + list); // list = [bb, hh, dd, aa]

        String max = Collections.max(list);
        System.out.println("max = " + max); // max = hh

        int gg = Collections.binarySearch(list, "gg");
        System.out.println("gg = " + gg); // gg = -2

    }
}

6.3 Collections 的同步控制方法

  • Collections 类提供了多个 synchronizedXxx() 方法,该方法可以将指定集合包装成线程安全的集合,从而可以解决多线程并发访问集合时的线程安全问题。
  • 将 Collection 集合转换为线程安全的集合:
public static <T> Collection<T> synchronizedCollection(Collection<T> c) {}
  • 将 Set 集合转换为线程安全的集合:
public static <T> Set<T> synchronizedSet(Set<T> s) {}
  • 将 List 集合转换为线程安全的集合:
public static <T> List<T> synchronizedList(List<T> list) {}
  • 将 Map 集合转换为线程安全的集合:
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {}
  • 示例:
package com.github.collections.demo2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 15:34
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());

        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");

        System.out.println("list = " + list);
    }
}

第七章:Map 接口

7.1 概述

  • Map 和 Collection 并列存在。用于保存具有映射关系的数据:key - value 。
  • Map 中的 key 和 value 可以是任何引用类型的数据。
  • Map 中的 key不允许重复,即同一个 Map 对象所对应的类,必须重写 equals() 和 hashCode() 方法。
  • 通常情况下,使用 String 作为 Map 的 key 。
  • Map 中的 key 和 value 之间存在单向一对一的关系,即通过指定的 key 能找到唯一的、确定的 value 。
  • Map 接口的常用实现类:HashMap 、TreeMap 、LinkedHashMap 和 Properties 。其中,HashMap 是 Map 接口使用频率最高的实现类。

Map接口概述.png

7.2 Map 接口常用的方法

  • 将指定的 key - value 添加(修改)当前 Map 对象中:
V put(K key, V value);
  • 将 m 中所有的 key - value 存放到当前的 Map 对象中:
void putAll(Map<? extends K, ? extends V> m);
  • 根据指定的 key 从当前 Map 对象中移除 key - value,并返回 value :
V remove(Object key);
  • 清空当前 Map 对象中的所有 key - value :
void clear();
  • 根据指定的 key 获取对应的 value :
V get(Object key);
  • 是否包含指定的 key :
boolean containsKey(Object key);
  • 是否包含指定的 value :
boolean containsValue(Object value);
  • 返回 Map 对象中的 key - value 的个数:
int size();
  • 判断当前 Map 是否为空(没有元素):
boolean isEmpty();
  • 判断当前 Map 和参数对象 obj 是否相等:
boolean equals(Object o);
  • 返回所有 key 构成的 Set 集合:
Set<K> keySet();
  • 返回所有 value 构成的 Collection 集合:
Collection<V> values();
  • 返回所有 key - value 构成的 Set 集合:
Set<Map.Entry<K, V>> entrySet();
  • 示例:
package com.github.map.demo1;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 17:07
 */
public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();

        map.put("张三", 18);
        map.put("李四", 28);
        map.put("王五", 45);

        System.out.println("map = " + map); // map = {李四=28, 张三=18, 王五=45}

        Map<String, Integer> map2 = new HashMap<>();
        map2.put("赵六", 9);
        map2.put("田七", 30);

        System.out.println("map2 = " + map2); // map2 = {赵六=9, 田七=30}

        map.putAll(map2);

        System.out.println("map = " + map); // map = {李四=28, 张三=18, 王五=45, 赵六=9, 田七=30}

        Integer age = map.get("张三");
        System.out.println("age = " + age); // age = 18

        age = map.get("王八");
        System.out.println("age = " + age); // age = null

        System.out.println("map.isEmpty() = " + map.isEmpty()); // map.isEmpty() = false

        int size = map.size();
        System.out.println("size = " + size); // size = 5

        System.out.println("map.equals(map2) = " + map.equals(map2)); // map.equals(map2) = false

        age = map.remove("张三");
        System.out.println("age = " + age); // age = 18

        Set<String> keySet = map.keySet();
        System.out.println("keySet = " + keySet); // keySet = [李四, 王五, 赵六, 田七]

        Collection<Integer> values = map.values();
        System.out.println("values = " + values); // values = [28, 45, 9, 30]

    }
}
  • 示例:
package com.github.map.demo2;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Map的遍历
 * 
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 17:14
 */
public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();

        map.put("张三", 18);
        map.put("李四", 28);
        map.put("王五", 45);

        for (Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext();) {
            String key = iterator.next();
            Integer value = map.get(key);
            System.out.println("key:" + key + ",value:" + value);
        }
    }
}
  • 示例:
package com.github.map.demo3;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 17:16
 */
public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();

        map.put("张三", 18);
        map.put("李四", 28);
        map.put("王五", 45);

        for (String key : map.keySet()) {
            Integer value = map.get(key);
            System.out.println("key:" + key + ",value:" + value);
        }
    }
}
  • 示例:
package com.github.map.demo4;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 17:17
 */
public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();

        map.put("张三", 18);
        map.put("李四", 28);
        map.put("王五", 45);

        for (Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
            Map.Entry<String, Integer> entry = iterator.next();
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("key:" + key + ",value:" + value);
        }
    }
}
  • 示例:
package com.github.map.demo5;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-29 17:19
 */
public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();

        map.put("张三", 18);
        map.put("李四", 28);
        map.put("王五", 45);

        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("key:" + key + ",value:" + value);
        }
    }
}

7.2 Map 的实现类之一:HashMap

7.2.1 概述

  • HashMap 是 Map 接口使用频率较高的实现类。
  • 允许使用 null 键和 null 值,和 HashSet 一样,不能保证映射的顺序。
  • 所有的 key 构成的集合是 Set:不重复的。所以,key 所在的类需要重写 equals() 和 hashCode() 方法。
  • 所有的 value 构成的集合是 Collection:可以重复的。所以,value 所在的类需要重写 equals() 方法。
  • 一个 key - value 构成一个 entry 。
  • HashMap 判断两个 key 相等的标准:两个 key 的 hashCode() 和 equals() 分别都相等。
  • HashMap 判断两个 value 相等的标准:两个 value 的 equals() 相等。

72.2 HashMap 的成员变量

  • 哈希表数组的初始化容量:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 哈希表数组的最大容量:
static final int MAXIMUM_CAPACITY = 1 << 30;
  • 默认的加载因子:
static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • 阈值(转红黑树):
static final int TREEIFY_THRESHOLD = 8;
  • 阈值(解除红黑树):
static final int UNTREEIFY_THRESHOLD = 6;
  • 阈值(转红黑树):
static final int MIN_TREEIFY_CAPACITY = 64;

7.2.3 HashMap 的内部类Node

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

    // Node节点
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash; // 对象的hash值
        final K key; // 存储对象
        V value; // 使用Set的时候,是Object的对象
        Node<K,V> next; // 链表的下一个节点

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

    ...

}

7.2.4 HashMap 的 put 方法

// HashMap存储对象的方法
public V put(K key, V value) {
    // 存储值 参数:传递新计算的hash值,要存储的元素
    return putVal(hash(key), key, value, false, true);
}
/**
* 存储值
* @param hash 重新计算的hash值
* @param key 键
* @param value 值
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    // Node类型的数组;Node类型的节点;n,i
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // tab = Node[] = null
    if ((tab = table) == null || (n = tab.length) == 0)
        // n = (tab数组 = resize()扩容后返回的数组)的长度,默认为16
        n = (tab = resize()).length;
    // (n - 1) & hash:数组的长度-1 & hash,确定存储的位置
    // 判断数组的索引上是不是null,并赋值给p
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 数组的索引 = 赋值新的节点对象
        tab[i] = newNode(hash, key, value, null);
    else { // 数组的索引不为null,说明,要存储的对象已经有了
        Node<K,V> e; K k;
        // 判断已经存在的对象和要存储对象的hash值、equals方法
        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);
        else { // 遍历该索引下的链表,和每个元素比较hashCode和equals,替换
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
// 将存储的对象,再次计算哈希值
// 尽量降低哈希值的碰撞
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 数组的扩容
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;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    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;
                    }
                }
            }
        }
    }
    return newTab;
}

7.2.5 JDK 7 和 JDK 8 的 HashMap 的区别

  • JDK7 及以前版本:HashMap 是 数组+链表结构。

JDK7的HashMap结构.png

  • JDK8 :HashMap 是 数组+链表+红黑树。

JDK8的HashMap结构.png

链表转红黑树的条件:

  • ① 链表长度 > 8 。
  • ② 数组长度 > 64 。

红黑树转链表的条件:链表的长度 < 6 。

7.3 Map 的实现类之二:LinkedHashMap

  • LinkedHashMap 是 HashMap 的子类。
  • 在 HashMap 存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。
  • 和 LinkedHashSet 类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序和 key - value 对的插入顺序一致。

  • 示例:

package com.github.map.demo6;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 08:35
 */
public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map = new LinkedHashMap<>();

        map.put("张三", 18);
        map.put("李四", 19);
        map.put("王五", 20);
        map.put("赵六", 21);

        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("key:" + key + ",value:" + value);
        }
    }
}

7.4 Map 的实现类之三:Hashtable

  • Hashtable 是个古老的 Map 实现类,JDK1.0 就提供了。不同于 HashMap ,Hashtable 是线程安全的。
  • Hashtable 的实现原理和 HashMap 相同,功能相同。底层都是使用哈希表结构,查询速度快,很多情况下可以互用。
  • 和 HashMap 不同的是,Hashtable 不允许使用 null 作为 key 和 value 。
  • 和 HashMap 相同的是,Hashtable 不能保证 key - value 对的顺序。
  • Hashtable 判断两个 key 相等、两个 value 相等的标准,与 HashMap 一致。

7.5 Map 的实现类之四:TreeMap

  • TreeMap 存储 Key - Value 对时,需要根据 key - value 对进行排序。
  • TreeMap 可以保证所有的 key - value 对处于有序状态。
  • TreeMap 底层使用红黑树结构存储数据。
  • TreeMap 的 key 的排序:
    • 自然排序:TreeMap 的所有的 key 必须实现 Comparable 接口,而且所有的 key 应该是同一个类的对象,否则将会抛出 ClasssCastException 。
    • 定制排序:创建 TreeMap 时,传入一个Comparator 对象,该对象负责对TreeMap中的所有key进行排序。此时不需要 Map 的 key 实现 Comparable 接口。
  • TreeMap 判断两个 key 相等的标准:两个 key 通过 compareTo() 方法或者 compare() 方法返回 0 。

  • 示例:

package com.github.map.demo7;

import java.util.Map;
import java.util.TreeMap;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 09:04
 */
public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map = new TreeMap<>();

        map.put("c", 25);
        map.put("h", 18);
        map.put("a", 21);
        map.put("b", 98);

        System.out.println("map = " + map);
    }
}
  • 示例:
package com.github.map.demo8;

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 09:06
 */
public class Test {
    public static void main(String[] args) {
        Map<Person, String> map = new TreeMap<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        map.put(new Person("张三", 18), "大一");
        map.put(new Person("李四", 20), "大一");
        map.put(new Person("王五", 19), "大二");
        map.put(new Person("赵六", 23), "大四");
        map.put(new Person("田七", 17), "大一");

        System.out.println("map = " + map);
    }
}

7.6 Map 的实现类之五:Properties

  • Properties 类是 Hashtable 的子类,该对象是用来处理属性文件的。
  • 由于属性文件的 key 和 value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型。
  • 存取数据的时候,建议使用 setPropert y和 getProperty 方法。

  • 示例:

package com.github.map.demo9;

import java.util.Enumeration;
import java.util.Properties;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 09:16
 */
public class Test {
    public static void main(String[] args) {
        Properties properties = new Properties();

        properties.setProperty("hello", "你好");
        properties.setProperty("world", "世界");

        String hello = properties.getProperty("hello");
        System.out.println("hello = " + hello);

        String world = properties.getProperty("world");
        System.out.println("world = " + world);

        Enumeration<?> enumeration = properties.propertyNames();
        while (enumeration.hasMoreElements()) {
            Object key = enumeration.nextElement();
            Object value = properties.get(key);
            System.out.println("key = " + key + ",value = " + value);
        }

        for (String name : properties.stringPropertyNames()) {
            String property = properties.getProperty(name);
            System.out.println("key = " + name + ",value = " + property);
        }
    }
}