一、Queue
1.特点:
和数据结构中的队列类似,只允许在队头删除元素,队尾插入元素。队列中允许有重复的元素。
2.Queue中的方法
2.1 插入元素
boolean add(E element)
boolean offer(E element)
说明:二者都是向队列中添加元素,如果成功,返回true。区别在于当队列已满时,add()抛出IllegalStateException,而返回false。
2.2 删除元素
E remove()
E poll()
说明:二者都是删除队列中的元素,删除成功,返回删除的元素。区别在于当队列为空时,remove()抛出NoSuchElementException异常,而offer()返回false。
2.3 获取元素
E element()
E peek()
说明:二者都会返回队头元素,但并不删除它。区别在于如果队头为空,element()会抛出NoSuchElementException异常,而peek()会返回null。
3.Queue的子类
3.1 Deque接口(双向队列)
3.1.1 特点:可以在队头和队尾插入或删除元素。
3.1.2 Deque中的方法(抽象,需要被覆写):
(1)插入元素
void addFirst(E element);
void addLast(E element);
boolean offerFirst(E element);
boolean offerLast(E element);
说明:这四个方法都会在队头或队尾插入元素。区别是如果队列已满,前两个方法会抛出IllegalStateException异常;而后两个方法会返回false。
(2)删除元素
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
区别:如果队列为空,前两个方法会抛出NoSuchElementException异常,而后两个方法返回null。
(3)获取队头或队尾元素(不会删除)
E getFirst();
E getLast();
E peekFirst();
E peekLast();
区别:如果队列为空,前两个方法会抛出NoSuchElementException异常,而后两个方法返回null。
3.1.3 Deque的实现子类
(1)LinkedList和ArrayDeque都实现了Deque接口。LinkedList之前已经介绍过,这里不再做介绍。下面是ArrayDeque的用法
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
public class TestCollection {
public static void main(String[] args) {
Queue<Integer> queue=new ArrayDeque<Integer>();
queue.add(1);
queue.offer(2);
queue.add(3);
queue.add(4);//1->2->3->4
//打印队列中的元素
for(Integer i:queue){
System.out.print(i+" ");
}//1 2 3 4
System.out.println();
((ArrayDeque<Integer>) queue).removeLast();//删除最后一个元素,队列变为:1->2->3
System.out.println(((ArrayDeque<Integer>) queue).getLast());//3
((ArrayDeque<Integer>) queue).pollLast();//1->2
System.out.println(((ArrayDeque<Integer>) queue).peekLast());//2
}
}
(2)PriorityQueue(优先级队列)
PriorityQueue(优先级队列)会按照排序的方式对队列中的元素进行排序和检索,因此PriorityQueue的对象必须实现Comparable接口,提供对元素排序时两个元素之间的比较规则。
但要注意:当通过foreach语句遍历优先队列时,获得的元素并没有进行排序,而在通过remove()方法删除元素时,该方法总是会删除当前队列中的最小元素。
import java.util.PriorityQueue;
import java.util.Queue;
public class TestCollection {
public static void main(String[] args) {
Queue<Integer> queue=new PriorityQueue<Integer>();
queue.add(13);
queue.add(56);
queue.add(26);
System.out.println("队列中的元素依次为:");
for(Integer i:queue){
System.out.print(i+" ");
}
System.out.println();
System.out.println("依次删除优先级队列中的元素:");
while(!queue.isEmpty()){
int c=queue.remove();
System.out.print(c+" ");
}
}
}
结果为:
队列中的元素依次为:
13 56 26
依次删除优先级队列中的元素:
13 26 56
二、Map
1.Collection集合的特点是每次进行单个对象的保存,而Map可以进行一对对象的保存,Map集合一次性会保存两个对象,且这两个对象的关系:key=value结构。这种结构最大的特点是可以通过key找到对应的value内容。
2.特点:集合中的key不能重复,而value可以重复。
3.Map接口中的方法:
public V put(K key,V value); 向Map中追加数据。
public V get(Object key); 通过key找到对应的value信息。
public Set<K> keySet(); 取得所有key信息,key不能重复。
public Collection<V> values(); 取得所有value信息,可以重复。
public Set<Map.Entry<K,V>> entrySet(); 将Map集合变为Set集合。
4.子类
4.1 HashMap--是使用Map集合中最常用的子类。
4.1.1 特点:线程不安全。不能在循环遍历Map的时候修改值,否则会产生ConcurrentModificationsException并发修改异常。
4.1.2 HashMap的实现原理:在数据量小(JDK8设置阈值为8)的时候,HashMap按照链表形式存储;当数据量变大之后,为了进行快速查找,会将链表变为红黑树(均衡二叉树)来进行数据保存,用hash码作为数据定位。
4.1.3 HashMap的存储结构:在JDK1.8之前,以链表形式存储。在JDK1.8之后,可以看作是桶数组和链表结合组成的复合结构。数组被分为一个个桶,通过哈希值决定了键值对在这个数组上的寻址。哈希值相同的键值对,会以链表形式存储。当链表的长度超过8时,则链表自动转换为红黑树。
4.1.4 HashMap的一些属性
public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable {
private static final long serialVersionUID = 362498820763181265L;
// 默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量:2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的填充因子(加载因子):0.75f
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 阈值1:8。含义:当桶(bucket)上的链表数大于这个值时会转成红黑树,put方法的代码里有用到。
static final int TREEIFY_THRESHOLD = 8;
// 阈值2:6。含义:同上一个相反,当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;
// 源码注释里说是:树的最小的容量,至少是 4 x TREEIFY_THRESHOLD = 32 然后为了避免(resizing 和 treeification thresholds) 设置成64
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的倍数
transient Node<k,v>[] table;
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器
transient int modCount;
// 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
int threshold;
// 填充因子
final float loadFactor;
说明:
1.容量:哈希表中桶的数量
2.初始容量:创建对象时桶的数量。
3.大小:元素的数量
4.负载因子:等于大小/容量,即元素的数量/桶的数量。HashMap中默认是0.75.
由此可见容量和负载系数决定了可用的桶的数量。
对于负载因子,建议:如果没有特别需求,不要轻易修改。如果确实需要修改,建议不要设置超过0.75的数值,因为这样会显著增加冲突,降低HashMap的性能。
5.树化
下面是精简过的treeifyBin方法
final void treeifyBin(Node<K,V>[] tab,int hash){
int n,index;
Node<K,V> e;
if(tab==null || (n=tab.length)<MIN_TREEIFY_CAPACITY)
resize();
else if((e=tab[index=(n-1)&hash])!=null){
//树化改造逻辑
}
因此树化改造的逻辑可以理解为,当bin的数量大于TREEIFY_THRESHOLD时:
如果容量(哈系桶的数量)<MIN_TREEIFY_CAPACITY,只会进行简单的扩容;
如果容量(哈系桶的数量)>MIN_TREEIFY_CAPACITY,则会进行树化改造。
树化的目的:确保安全。因为在元素放置的过程中,如果元素发生哈希碰撞,就会放到同一个哈希桶里,形成一个链表。而链表是线性的,会严重影响存取的性能。而在现实世界中,构造哈希冲突的数据非常容易,恶意代码就可以利用这些数据大量与服务器端交互,导致服务器端CPU被大量占用,这就构成了哈希碰撞拒绝服务攻击。
4.1.5 测试HashMap
import java.util.*;
public class TestMap{
public static void main(String[] args) {
Map<Integer,String> map=new HashMap<>();
map.put(1,"Hello");
map.put(3,"Java");
map.put(2,"Bit");
map.put(null,"null");//可以设置k,v为null.
//获取所有的key信息
Set<Integer> set=map.keySet();
Iterator<Integer> iterator=set.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next()+" ");
}
//获取所有的value
Collection<String> values=map.values();
for(String value:values){
System.out.print(value+",");
}
System.out.println();
//打印map
//方式一
System.out.println(map);
//通过key遍历value
//方式二
System.out.println("通过key遍历value") ;
for(Integer key:map.keySet()){
System.out.print(key+"="+map.get(key)+" ");
}
System.out.println();
//方式三
//将Map集合变为Set集合
Set<Map.Entry<Integer,String>> entries=map.entrySet();
for(Map.Entry<Integer,String> entry:entries){
System.out.print(entry.getKey()+"="+entry.getValue()+" ");
}
}
}
结果:
null 1 2 3 null,Hello,Bit,Java,
{null=null, 1=Hello, 2=Bit, 3=Java}
通过key遍历value
null=null 1=Hello 2=Bit 3=Java
null=null 1=Hello 2=Bit 3=Java
4.2 Hashtable(注意t小写)
4.2.1 特点:线程安全。k和v不能设置为null。
4.2.2 Hashtable与HashMap的区别:
(1)出现版本不同:Hashtable在JDK1.0出现,HashMap在JDK1.2出现。
(2)性能:Hashtable同步处理,性能低;HashMap异步处理,性能高。
(3)安全性:Hashtable线程安全;而HashMap线程不安全。
(4)null的操作:Hashtable中的key和value都不能为null,而HashMap中都可以为null。
因此,在单线程的时候,考虑使用Hashtable。
4.2.3 Hashtable的操作:
public class TestCollection {
public static void main(String[] args) {
Map<String,String> map=new Hashtable<>();
map.put("First","Monday");
map.put("Second","Tuesday");
map.put("Third","Wednesday");
// map.put("A",null);//抛出java.lang.NullPointerException异常
System.out.println(map);
}
}
结果:
{Second=Tuesday, First=Monday, Third=Wednesday}
4.3ConcurrentHashMap
4.3.1 特点:线程安全,性能高。ConcurrentHashMap的特点=HashMap的性能+Hashtable的线程安全。因此,在多线程需要进行异步处理时,考虑使用ConcurrentHashMap。
4.4TreeMap
4.4.1 特点:可排序的Map子类,按照key的内容进行排序。