2、集合

继承树

Collection接口

说明: untitle.png

Map接口

说明: untitle.png

Collection 接口

Collection 接口方法

遍历Collection的两种方式

迭代器Iterator接口

Iterable:接口,规定实现类或子接口必须要有提供迭代器的能力

Iterator:迭代器的类型,迭代器使用Iterable接口中iterator方法返回的,通过Iterator<T> iterator();方法获取

java.util包下定义的迭代器接口:Iterator

不支持删除行为:Iterator迭代时,删除当前遍历到的元素可以使用迭代器对象.remove(),但是,切记!!!迭代器在迭代的时候不支持集合本身的修改行为add|remove,否则,会引发java.util.ConcurrentModificationException并发修改异常。

遍历代码实现

Iterator iterator = coll.iterator();
//hasNext():判断是否还下一个元素
while(iterator.hasNext()){
    //next():指针下移并将下移以后指向的元素返回
    System.out.println(iterator.next());
    //remove():删除指针当前指向的元素
    iterator.remove();
}

符合迭代器的设计模式

List接口

存储的数据特点:存储有序的、可重复的数据。

List接口常用方法

实现类1:ArrayList

底层:底层是一个Object类型的数组,初始的默认长度0(jdk1.6之前是10),在第一次add时,容量变为10,也可以指定长度,初始长度如果满了,底层进行自动扩容,扩容为原来的1.5倍oldCapacity + (oldCapacity >> 1)。如果对集合中的元素个数可以预估,那么建议预先指定一个合适的初始容量new ArrayList(20);

优点:查找效率高,向末尾添加元素也可以

缺点:增加 、删除牵扯到数组的扩容和移动,效率低;线程不安全

实现类2:LinkedList

底层:是一个链表(双向)结构,不是线性存储。

优点:增加、删除效率高

缺点:查找效率低;线程不安全

实现类3:Vector

底层:是一个Object类型的数组,初始的默认长度为10,扩容的时候扩容为原来的2倍,如果自己指定扩容的长度,那么就是旧容量加指定的,如果没有指定,就是旧容量的2倍

优点:线程安全,通过synchronized同步锁实现

缺点:效率低

// 初始容量1,每次扩容增加2
Vector v = new Vector(1,2);

存储的元素的要求

添加的对象,所在的类要重写equals()方法

三个实现类的异同

说明: untitle.png

Set接口

存储的数据特点:无序的、不可重复的元素(如果hashCode返回值一样和equalstrue,则认为是重复元素)

具体说明

以HashSet为例说明:

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

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

Set接口中没额外定义新的方法,使用的都是Collection中声明过的方法。

实现类1:HashSet

底层:HashSet的底层是一个HashMap,只是将HashMap中的值设置为一个常量,用所有的键组成了一个HashSet

优点**:可以存储null值**

缺点:线程不安全

实现类2:LinkedHashSet

LinkedHashSet 是 HashSet 的子类

底层:是一个LinkedHashMap,底层维护了一个数组 + 双向链表

存储对象所在类的要求:

向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()equals()

实现类3:TreeSet

TreeSet 是 SortedSet(继承于Set)接口的实现类,TreeSet 可以确保集合元素处于排序状态。

不可以放null对象

存储对象所在类的要求:

  1. 向TreeSet中添加的数据,要求是相同类的对象而且对象必须是可比较的。

  2. 两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator,创建TreeSet的时候可以作为构造方法参数传入!)

TreeSet<Integer> s2 = new TreeSet<Integer>(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        // 升序排序
        if (o1 > o2){
            return 1;
        }else {
            return -1;
        }
    }
});

Map接口

Map接口常用方法

存储结构的理解

实现类1:HashMap

底层:所有的键构成了一个HashSet,整体是数组+链表/红黑树

优点:效率高,可以存储null值、null键

缺点:线程不安全

初始长度16,每次扩容为原来的2倍,扩容因子(即当前元素个数 / 当前容量 > 扩容因子时就会进行扩容)0.75

HashMap的put过程

说明: HashMap的put().png

  1. 根据键的hash码(调用键的hashCode方法)进行哈希运算,得到一个整数哈希值
  2. 判断哈希表是否为空或者长度是否为0,如果是,要对数组进行初始化,如果否,进入3
  3. 根据1得到的哈希值计算数组索引(与运算),得到一个和数组存储位置匹配的索引i
  4. 判断i号位置是否为null,如果null,就将键和值封装为一个Entry类型的对象进行插入,如果不为null,进入5
  5. 判断key是否存在(使用equals进行判断),如果存在,覆盖原有的值,如果不存在,进入6
  6. 判断i号位置是否为一个树结构,如果是一个树结构,在树中进行插入,如果不是树结构,进入7
  7. 为链表结构,对链表进行遍历,判断key是否存在,存在就覆盖,不存在就在链表中插入新的节点
  8. 链表中插入新节点后,如果i号位置的元素个数大于等于8,i号位置的所有元素转换为树结构,如果不大于等于8,新节点正常插入结束
  9. size++
  10. 判断是否要进行扩容,如果需要扩容,就执行Resize()进行扩容
  11. 结束

实现类2:LinkedHashMap

底层**:HashMap的子类**;在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。

优点:保证在遍历map元素时,可以照添加的顺序实现遍历。对于频繁的遍历操作,此类执行效率高HashMap。可以存储null值、null键

缺点:线程不安全

实现类3:TreeMap

底层**:红黑树**

优点:保证照添加的key-value对进行排序,实现按照键排序遍历,两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator

缺点**:键不可以为null,值可以为null**

实现类4:Hashtable

底层**:数组+链表/红黑树**

优点:线程安全(使用synchronized实现)

缺点:效率低;不能存储null的key和value

不可以放入null值、null键

初始长度11,每次扩容为原来的2倍加1,扩容因子(即当前元素个数 / 当前容量 > 扩容因子时就会进行扩容)0.75

实现类5:Properties

底层**:Hashtable的子类,数组+链表/红黑树**

优点:一般用来存储配置文件

缺点:key和value都是String类型

面试题

Collections工具类

作用:操作Collection和Map的工具类

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

Collections常用方法

说明: untitle.png

说明: untitle.png

集合的线程安全

List的线程安全

如果使用ArrayList多个线程同时添加和读取,可能会出现并发修改异常ConcurrentModificationException

Vector

使用synchronized关键字,对修改、添加、删除等方法进行修饰,实际开发中,我们很少使用,强同步性能低

Collections

image-20210916195857041

可以使用Collections中的静态方法synchronizedXXXXXX类型的集合转为线程安全版本

CopyOnWriteArrayList(推荐)

List list = new CopyOnWriteArrayList();

使用写时复制技术,也就是读的时候可以并发进行读,但是写,只允许一个线程写,复制一份和当前集合相同的集合,然后在写完以后,进行合并

使用Lock可重入锁进行实现

Set的线程安全

Collections

和List的使用方法一样

CopyOnWriteArraySet

和List的使用方法一样

Map的线程安全

HashTable

使用synchronizd强同步,效率低,不推荐

Collections

和List的使用方法一样

ConcurrentHashMap

Map map = new ConcurrentHashMap()

队列Queue

image-20230426172628576

任何无限容量的队列ho栈都是有容量的,这个容量就是Integer.MAX_VALUE

Queue常用方法

双端队列

deque是double ended queue的缩写。双端队列顾名思义就是队列的两个端口都能进出。

Deque的实现类中,LinkedList是最常用的。值得注意的是,LinkedList也实现了List接口。

大多数Deque既可以是有容量限制也可以是无固定容量限制。

头部 头部 尾部 尾部
失败响应 抛出异常 返回值 抛出异常 返回值
入队 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
出队 removeFirst() pollFirst() removeLast() pollLast()
查看 getFirst() peekFirst() getLast() peekLast()

双端队列的三种用法:

1、作为队列,先进先出

Queue<String> queue = new LinkedList();
// 队尾入队
queue.offer("El");
// 队首出队
String el = queue.poll();

Deque<String> deque = new LinkedList();
// 队尾入队
deque.offerLast("El");
// 队首出队
String el = deque.pollFirst();

2、作为堆栈,先进后出

注意:Java的堆栈类Stack已经过时,官方推荐Deque代替

Deque<String> deque = new LinkedList();
// 队尾入队
deque.offerLast("el");
// 队尾出队
String el = deque.pollLast();

3、作为双端队列,两端都可进出

Deque<String> deque = new LinkedList();
// 队尾入队
deque.offerLast("el");
// 队首入队
deque.offerFirst("el");
// 队尾出队
String el1 = deque.pollLast();
// 队首出队
String el2 = deque.pollFirst();

阻塞队列

阻塞队列是一个支持两个附加操作的队列,即在队列为满时,存储元素的线程会等待队列可用;当队列为空时,获取元素的线程会等待队列为非空。

阻塞队列所实现接口有BlockingQueueBlockingDeque,增加方法如下:

BlockingQueue

方法描述 一直阻塞 超时退出
入队 put(e) offer(e,time,unit)
出队 take() poll(time,unit)

BlockingDeque

头部 头部 尾部 尾部
方法描述 一直阻塞 超时退出 一直阻塞 超时退出
入队 putFirst(e) offerFirst(e,time,unit) putLast(e) offerLast(e,time,unit)
出队 takeFirst() pollFirst(time,unit) takeLast() pollLast(time,unit)

非阻塞队列

非阻塞队列不能阻塞,个人理解为普通队列,在多线程中,当队列满或空时,只能使用wait()notify()进行队列消息传送。

AbstractQueue是非阻塞队列所实现的接口,常见的非阻塞实现类有: