java.lang.Iterable
):单列集合,用来存储一个一个的对象
List接口:extends Collection
,存储有序的、可重复的数据。“动态”数组
Set接口:extends Collection
,存储无序的、不可重复的数据。高中讲的“集合”
add(Object obj)
,addAll(Collection coll)
int size()
void clear()
boolean isEmpty()
boolean contains(Object obj)
,boolean containsAll(Collection c)
boolean remove(Object obj)
,boolean removeAll(Collection c)
boolean retainAll(Collection c)
boolean equals(Object obj)
Object[] toArray()
iterator()
使用迭代器Iterator
foreach循环(或增强for循环):内部仍然调用的是迭代器
Iterable:接口,规定实现类或子接口必须要有提供迭代器的能力
Iterator:迭代器的类型,迭代器使用Iterable接口中iterator方法返回的,通过Iterator<T> iterator();
方法获取
java.util
包下定义的迭代器接口:Iterator
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
不支持删除行为:Iterator迭代时,删除当前遍历到的元素可以使用迭代器对象.remove()
,但是,切记!!!迭代器在迭代的时候不支持集合本身的修改行为add|remove
,否则,会引发java.util.ConcurrentModificationException
并发修改异常。
Iterator iterator = coll.iterator();
//hasNext():判断是否还下一个元素
while(iterator.hasNext()){
//next():指针下移并将下移以后指向的元素返回
System.out.println(iterator.next());
//remove():删除指针当前指向的元素
iterator.remove();
}
存储的数据特点:存储有序的、可重复的数据。
add(Object obj)
remove(int index) / remove(Object obj)
set(int index, Object ele)
get(int index)
add(int index, Object ele)
size()
底层:底层是一个Object类型的数组,初始的默认长度0(jdk1.6之前是10),在第一次add时,容量变为10,也可以指定长度,初始长度如果满了,底层进行自动扩容,扩容为原来的1.5倍oldCapacity + (oldCapacity >> 1)
。如果对集合中的元素个数可以预估,那么建议预先指定一个合适的初始容量new ArrayList(20);
优点:查找效率高,向末尾添加元素也可以
缺点:增加 、删除牵扯到数组的扩容和移动,效率低;线程不安全
底层:是一个链表(双向)结构,不是线性存储。
优点:增加、删除效率高
缺点:查找效率低;线程不安全
底层:是一个Object类型的数组,初始的默认长度为10,扩容的时候扩容为原来的2倍,如果自己指定扩容的长度,那么就是旧容量加指定的,如果没有指定,就是旧容量的2倍。
优点:线程安全,通过synchronized
同步锁实现
缺点:效率低
// 初始容量1,每次扩容增加2
Vector v = new Vector(1,2);
添加的对象,所在的类要重写equals()
方法
存储的数据特点:无序的、不可重复的元素(如果hashCode
返回值一样和equals
为true
,则认为是重复元素)
以HashSet为例说明:
无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。
不可重复性:保证添加的元素照equals()
判断时,不能返回true,hashCode不可以一样,即:相同的元素只能添加一个。
Set接口中没额外定义新的方法,使用的都是Collection中声明过的方法。
底层:HashSet的底层是一个HashMap,只是将HashMap中的值设置为一个常量,用所有的键组成了一个HashSet
优点**:可以存储null值**
缺点:线程不安全
LinkedHashSet 是 HashSet 的子类
底层:是一个LinkedHashMap
,底层维护了一个数组 + 双向链表
存储对象所在类的要求:
向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()
和equals()
TreeSet 是 SortedSet(继承于Set)接口的实现类,TreeSet 可以确保集合元素处于排序状态。
不可以放null对象
存储对象所在类的要求:
向TreeSet中添加的数据,要求是相同类的对象而且对象必须是可比较的。
两种排序方式:自然排序(实现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;
}
}
});
key-value
对的数据,类似于高中的函数:y = f(x)
Object put(Object key,Object value)
,void putAll(Map m)
Object remove(Object key)
void clear()
Object get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
Set keySet()
Collection values()
Set entrySet()
equals()
和hashCode()
(以HashMap为例)equals()
,一个键值对:key-value构成了一个Entry对象。底层:所有的键构成了一个HashSet,整体是数组+链表/红黑树
优点:效率高,可以存储null值、null键
缺点:线程不安全
equals()
方法返回 true, hashCode 值也相等。equals()
方法返回 true。初始长度16
,每次扩容为原来的2倍,扩容因子(即当前元素个数 / 当前容量 > 扩容因子
时就会进行扩容)0.75
hashCode
方法)进行哈希运算,得到一个整数哈希值i
equals
进行判断),如果存在,覆盖原有的值,如果不存在,进入6size++
Resize()
进行扩容底层**:HashMap的子类**;在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
优点:保证在遍历map元素时,可以照添加的顺序实现遍历。对于频繁的遍历操作,此类执行效率高HashMap。可以存储null值、null键
缺点:线程不安全
底层**:红黑树**
优点:保证照添加的key-value对进行排序,实现按照键排序遍历,两种排序方式:自然排序(实现Comparable
接口) 和 定制排序(Comparator
)
缺点**:键不可以为null,值可以为null**
底层**:数组+链表/红黑树**
优点:线程安全(使用synchronized
实现)
缺点:效率低;不能存储null的key和value
不可以放入null值、null键
初始长度11
,每次扩容为原来的2倍加1,扩容因子(即当前元素个数 / 当前容量 > 扩容因子
时就会进行扩容)0.75
底层**:Hashtable的子类,数组+链表/红黑树**
优点:一般用来存储配置文件
缺点:key和value都是String类型
为什么HashMap的长度为什么要设计成2的n次方?
为什么设计扩容因子
根据key怎么找到值的get(key)
?
为什么使用hash码相关的集合的时候,重写equals方法的时候建议也重写hashCode方法
HashMap和Hashtable的区别:
int newCapacity = (oldCapacity << 1) + 1
)HashMap在jdk8中相较于jdk7在底层实现方面的不同:
jdk7:底层初始创建一个长度为16的数组,jdk8:初始化没有指定HashMap数组大小,而是在添加第一个元素时,进行扩容操作
jdk8底层的数组是:Node[]
,而非Entry[]
(jdk7)
首次调用put()
方法时,底层创建长度为16的数组
jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8
且当前数组的长度 > 64
时,此时此索引位置上的所有数据改为使用红黑树存储。
作用:操作Collection和Map的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
如果使用ArrayList多个线程同时添加和读取,可能会出现并发修改异常ConcurrentModificationException
使用synchronized关键字,对修改、添加、删除等方法进行修饰,实际开发中,我们很少使用,强同步性能低
可以使用Collections中的静态方法synchronizedXXX
将XXX
类型的集合转为线程安全版本
List list = new CopyOnWriteArrayList();
使用写时复制技术,也就是读的时候可以并发进行读,但是写,只允许一个线程写,复制一份和当前集合相同的集合,然后在写完以后,进行合并
使用Lock可重入锁进行实现
和List的使用方法一样
和List的使用方法一样
使用synchronizd强同步,效率低,不推荐
和List的使用方法一样
Map map = new ConcurrentHashMap()
任何无限容量的队列ho栈都是有容量的,这个容量就是
Integer.MAX_VALUE
void add(Object e)
:将指定元素加入此队列的尾部,当超过容量,抛出异常boolean offer(Object e)
:将指定元素加入该队列的尾部,当超过容量,返回falseObject poll()
:获取队列头部的元素,并删除该元素,队列为空则返回nullObject remove()
:获取队列头部的元素,并删除该元素,队列为空时抛出异常NoSuchElementException
Object element()
:获取列队头部的元素,但是不删除该元素,队列为空抛出异常Object peek()
:获取队列头部的元素,但是不删除该元素,队列为空,则返回nulldeque是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();
阻塞队列是一个支持两个附加操作的队列,即在队列为满时,存储元素的线程会等待队列可用;当队列为空时,获取元素的线程会等待队列为非空。
阻塞队列所实现接口有BlockingQueue
和BlockingDeque
,增加方法如下:
方法描述 | 一直阻塞 | 超时退出 |
---|---|---|
入队 | put(e) | offer(e,time,unit) |
出队 | take() | poll(time,unit) |
头部 | 头部 | 尾部 | 尾部 | |
---|---|---|---|---|
方法描述 | 一直阻塞 | 超时退出 | 一直阻塞 | 超时退出 |
入队 | putFirst(e) | offerFirst(e,time,unit) | putLast(e) | offerLast(e,time,unit) |
出队 | takeFirst() | pollFirst(time,unit) | takeLast() | pollLast(time,unit) |
非阻塞队列不能阻塞,个人理解为普通队列,在多线程中,当队列满或空时,只能使用wait()
和notify()
进行队列消息传送。
AbstractQueue是非阻塞队列所实现的接口,常见的非阻塞实现类有: