collection
java.util
Collection 这个接口extends自 --java.lang.Iterable接口
+List 接口
-ArrayList 类
-LinkedList 类
-Vector 类 此类是实现同步的
+Queue 接口
+不常用,在此不表.
+Set 接口
+SortedSet 接口
-TreeSet 类
-HashSet
+Map 接口
-HashMap 类 (除了不同步和允许使用 null 键/值之外,与 Hashtable 大致相同.)
-Hashtable 类 此类是实现同步的,不允许使用 null 键值
+SortedMap 接口
-TreeMap 类
- 什么是java集合框架体系
(1)在java语言中,java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类),所有抽象出来的数据结构和操作(算法)统称为java集合框架(java Collection Framework)jcf。
(2)Java程序员在具体应用时,不必考虑数据结构和算法的实现细节,只需要用这些类创建出来一些对象,然后直接应用就可以了,这样就大大提高了编程效率
- 结构
- Collection接口
- List
该接口的特点:有序的,可重复的
实现类:
- List
(1)ArrayList
基于数组实现的,可自动改变大小
非线程安全类
增长方式:按原来的1.5倍进行扩容,加载因子为1
初始长度java6为10,java7为0,java8为10
遍历的三种方式:
for循环 提前把长度赋给一个变量。优化代码
Foreach
迭代器迭代
//iterator迭代器
Iterator i=list.iterator();
while(i.hasNext()){
System.out.prinln(i.next());
}
(2)LinkedList
基于双向链表实现的
非线程安全的
遍历方式
for循环 提前把长度赋给一个变量。优化代码
Foreach
迭代器迭代
两者的区别:
对于查询来说,ArrayList有下标,随机访问元素比LinkedList的效率高
对于添加,删除来说,操作add和remove,LinedList比较占优势,因为ArrayList要移动数据
优点:
: 对于查询来说,使用效率更高
对于添加、删除来说,使用LinkedList效率更高
List.removeAll()删除所有元素
指定添加add(下标,值);
添加add();
(3)Vector
底层是对象数组实现
线程安全的类
是同步的
初始容量(内部数组的大小)为10,两倍扩容,加载因子为1
vector声明如下:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Vector继承于AbstractList,实现了List、RandomAccess、Cloneable、 Serializable等接口。
ArrayList实现了List接口,可以对它进行队列操作;实现了RandmoAccess接口,即提供了随机访问功能;实现了Cloneable接口,能被克隆;实现了Serializable接口,因此它支持序列化,能够通过序列化传输。
Vector源码详解:
Vector内部通过一个Object数组来存储数据:
protected Object[] elementData;
Vector使用elementCount变量来表示实际存储的元素个数:
protected int elementCount;
Vector有四个构造方法:
// 创建一个空的Vector,并且指定了Vector的初始容量和扩容时的增长系数
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
// 创建一个空的Vector,并且指定了Vector的初始容量
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
// 创建一个空的Vector,并且指定了Vector的初始容量为10
public Vector() {
this(10);
}
// 根据其他集合来创建一个非空的Vector
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
add方法
Vector有两个重载的Add方法:
// 在数组elementData尾部添加一个元素
public synchronized boolean add(E e)
// 在数组elementData指定位置index处添加元素
public void add(int index, E element)
add(E e)方法
add(E e)方法源码如下:
// 在数组elementData尾部添加一个元素
public synchronized boolean add(E e) {
modCount++;
// 容量大小判断
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
该方法首先要判断elementData数组的容量是否能够容纳新的元素,若不能,则需要进行扩容操作,然后将元素e放置在数组的size位置。ensureCapacityHelper(int)方法源码如下:
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
// 增加元素后,ArrayList中要存储的元素个数为minCapacity
// 若此时minCapacity > elementData原始的容量,则要按照minCapacity进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
扩容的最终操作是通过grow(int)方法来实现的:
private void grow(int minCapacity) {
// overflow-conscious code
// 获取elementData的原始容量
int oldCapacity = elementData.length;
// 计算新的容量
// 如果在构造方法中设置了capacityIncrement > 0,那么新数组长度就是原数组长度 + capacityIncrement
// 否则,新数组长度就是原数组长度 * 2
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
// 若进行扩容后,capacity仍然比实际需要的小,则新容量更改为实际需要的大小,即minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新数组的长度比虚拟机能够提供给数组的最大存储空间大,则将新数组长度更改为最大正数:Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 按照新的容量newCapacity创建一个新数组,然后再将原数组中的内容copy到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
和ArrayList的扩容步骤很相似,这里不再介绍。
add(int index, E element)方法
add(int index, E element)方法源码如下:
public void add(int index, E element) {
insertElementAt(element, index);
}
该方式其实是调用了insertElementAt方法:
public synchronized void insertElementAt(E obj, int index) {
// fail-fast机制
modCount++;
// 判断index下标的合法性
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
// 判断容量大小
ensureCapacityHelper(elementCount + 1);
// 数组拷贝,将index到末尾的元素拷贝到index + 1到末尾的位置,将index的位置留出来
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
remove方法
remove方法在Vector中同样有两种实现方式:
// 根据元素删除
public boolean remove(Object o)
// 根据index下标删除元素
public synchronized E remove(int index)
我们先看remove(Object o)方法。
remove(Object o)方法
remove(Object o)方法源码如下:
public boolean remove(Object o) {
return removeElement(o);
}
其内部通过removeElement方法来删除元素:
public synchronized boolean removeElement(Object obj) {
// fail-fast机制
modCount++;
// 查找元素obj在数组中的下标
int i = indexOf(obj);
// 若下标 >= 0
if (i >= 0) {
// 调用removeElementAt(int)方法删除元素
removeElementAt(i);
return true;
}
return false;
}
我们先来看indexOf(Object)方法:
public int indexOf(Object o) {
return indexOf(o, 0);
}
public synchronized int indexOf(Object o, int index) {
// 若要查找的元素为null
if (o == null) {
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)
return i;
}
// 若要查找的元素不为null
else {
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
Vector查找元素时,是分为元素为null和不为null两种方式来判断的,这也说明Vector允许添加null元素;同时,如果这个元素在Vector中存在多个,则只会找出从index开始,最先出现的那个。
找到元素对应的下标,若下标 >= 0,则说明元素在数组中存在,然后通过removeElementAt(int)方法来删除元素,removeElementAt(int)方法源码如下:
public synchronized void removeElementAt(int index) {
modCount++;
// index下标合法性检验
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
// 要移动的元素个数
int j = elementCount - index - 1;
if (j > 0) {
// 将index之后的元素向前移动一位
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
remove(int index)方法
remove(int index)方法源码如下:
public synchronized E remove(int index) {
modCount++;
// index下标合法性检验
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 获取旧的元素值
E oldValue = elementData(index);
// 计算需要移动的元素个数
int numMoved = elementCount - index - 1;
// 将元素向前移动
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
Vector的相对线程安全
我们前面说过Vector是相对线程安全的,为什么这么说呢?
我们看下面一段代码:
public class VectorTest {
static class MyThread extends Thread {
private CountDownLatch countDownLatch;
private Vector<String> vector;
private String element;
public MyThread(CountDownLatch countDownLatch, Vector<String> vector, String element) {
this.countDownLatch = countDownLatch;
this.vector = vector;
this.element = element;
}
@Override
public void run() {
super.run();
try {
if (!vector.contains(element)) {
// 注意这里
Thread.sleep(1000);
vector.add(element);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Vector<String> vector = new Vector<>();
MyThread myThread1 = new MyThread(countDownLatch, vector, "abc");
MyThread myThread2 = new MyThread(countDownLatch, vector, "abc");
myThread1.start();
myThread2.start();
countDownLatch.await();
int vectorSize = vector.size();
System.out.println("vector size: " + vectorSize);
for (int i = 0; i < vectorSize; i++) {
System.out.println("index " + i + ": " + vector.get(i));
}
}
}
运行结果(不唯一):
vector size: 2
index 0: abc
index 1: abc
注意注释处的一段代码,该段代码是判断元素element是否存在,不存在的话,则将其添加到vector之中,如果线程1和线程2同时运行该段代码,设想一下如下情景:
线程1通过vector.contains(element)同步方法来判断元素是否存在,此时,该方法返回false,即表明线程1可以将element元素插入Vector中;但是运行完该方法之后,线程1开始sleep,那这时,线程2开始运行vector.contains(element)同步方法,该方法仍然返回了false,即线程2可以将element元素插入Vector中,然后线程2开始sleep,最终结果,就是线程1和线程2都将元素“abc”添加到了vector之中,这就是我们为什么说Vector是相对线程安全的了。
要解决该问题,需要我们在自己的业务代码代码中进行同步控制,比如将那一段代码修改为如下:
synchronized (vector) {
if (!vector.contains(element)) {
Thread.sleep(1000);
vector.add(element);
}
}
- Set
核心:集
特点:无序,去重,允许一个null;
常用的实现类:
(1)hashSet
底层为HashMap的Key,非线程安全类,是不同步的,默认容量大小为16,其加载因子为0.75;元素无下标没有顺序,不能用for循环,只能用foreach和迭代器遍历
API所提供的类,HashSet能自动去重,自定义类不能去重
HashSet的去重条件:两个对象的HashCode一致,两个对象的equals方法返回True;(重写这两个方法)
HashCode()和equals()方法
不管谁相同都能存储成功,只是存储的位置不同
(2)TreeSet
底层是TreeMap的实例(都是由二叉树实现的)(是一个有序的集),
是非线程安全类,是不同步的,
元素无下标
特点:去重,按照字典顺序排序
一般不用自定义类,用自定义类需要重写compareTo方法
// 对书的名称排序(依照unicode编码大小)
@Override
public int compareTo(Book o){
// return 0 返回0表示集合含有一个元素
// return 2 返回正数表示集合怎么存怎么取
// return -1 返回负数表示集合会倒叙存储
double num =this.bookName.compareTo(o.booName);
return (int) (num==0?this.price-o.price:num);
}
comparable和comparator区别
相同点:
1.comparable和comparator都是用来实现treeset等根据类的自然排序进行排序的集合容器中元素之间的比较,比较算法都是由比较器自己规定
不同点:
1.comparable是让集合元素自身具备比较性,让元素实现comparable接口,覆盖comparaeTo(T o)方法
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
2.comparator是让集合具备比较性,在集合初始化时就有了比较放松,定义一个类,实现compare(T o1,T o2)方法
比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
将此类的对象作为参数传给treeset等根据类的自然排序进行排序的集合容器的构造函数
举例子:让人根据年龄排序,如果年龄一样根据姓名的自然顺序排序
通过comparable方式
import java.util.Iterator;
import java.util.TreeSet;
/**
* @author WangShuang
*
*/
public class Demo {
public static void main(String[] args) {
Person p0 = new Person("张三",3);
Person p = new Person("张三",1);
Person p1 = new Person("张三",2);
Person p2 = new Person("张四",2);
Person p3 = new Person("张四",2);
TreeSet<Person> treeSet=new TreeSet<Person>();
treeSet.add(p0);
treeSet.add(p);
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
Iterator<Person> iterator = treeSet.iterator();
while(iterator.hasNext()){
Person next = iterator.next();
System.out.println(next);
}
}
}
class Person implements Comparable<Person>{//该接口强制让人具有比较性
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Person o) {
if(this.age>o.age){
return 1;
}else if(this.age<o.age){
return -1;
}else{
return this.name.compareTo(o.name);
}
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
通过comparator方式
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/**
*
* @author WangShuang
*
*/
public class Demo {
public static void main(String[] args) {
Person p0 = new Person("张三",3);
Person p = new Person("张三",1);
Person p1 = new Person("张三",2);
Person p2 = new Person("张四",2);
Person p3 = new Person("张四",2);
TreeSet<Person> treeSet=new TreeSet<>(new Com());
treeSet.add(p0);
treeSet.add(p);
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
Iterator<Person> iterator = treeSet.iterator();
while(iterator.hasNext()){
Person next = iterator.next();
System.out.println(next);
}
}
}
class Com implements Comparator<Person>{//该接口强制让集合具有比较性
@Override
public int compare(Person o1, Person o2) {
if(o1.getAge()>o2.getAge()){
return 1;
}else if(o1.getAge()<o2.getAge()){
return -1;
}else{
return o1.getName().compareTo(o2.getName());
}
}
}
class Person{
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
(3)LinkedHashSet:非线程安全类,
底层是哈希表和链接列表
初始值16,加载因子0.7
此实现与HashSet的不同在于,后者维护中一个运行于所有 条目的双重链表
无序,不重复(自定义类需要重写hashcode和equals方法)
(4)Queue
队列
HashSet和TreeSet的区别
- Map
键值对 键是set类型(无序,去重) 值是collection类型 (可重复,无序)
(1)HashMap
底层实现依赖于map.Entry接口
特点:
非线程安全类
允许使用一个空键和多个空值,键名不能重复
不保证顺序
默认大小为16,增长因子为0.75
实现原理
底层是通过数组和链表实现的,jdk1.8以后 链表长度大于8就开始红黑树化(查询速度更快,插入也更快),在存放k-v时,先计算key的哈希值,用哈希值去table里找有没有跟这个值相同的node,如果没有则直接放入table,有再比较key是否一样,一样就替换掉原来的value,否则将新的节点拼接到哈希值一样的那个节点后面。
加载因子为0.75 初始容量为16 扩容增量为原来的一倍
遍历:(1)不能直接遍历需要通过其所提供的Keyset
方法把键名映射到Set集合的视图上,然后在利用Set集合遍历 的方式取出键名,
取出所有值
(2)通过entrySet()方法也能取出所有键和值的形式
TreeMap
实现了SortedMap接口,底层是通过红黑树实现的
非线程安全类
按照键的类的自然顺序排序
hashmap什么时候用链表,什么时候用红黑树
在元素个数小于8时用单链表,大于8时用红黑树
因为红黑树需要进行左旋,右旋操作, 而单链表不需要,
以下都是单链表与红黑树结构对比。
如果元素小于8个,查询成本高,新增成本低
如果元素大于8个,查询成本低,新增成本高
HashMap之快速失败
为什么HashMap通过迭代器自身的remove或add方法就不会出现迭代器失败?
HashMap所有集合类视图所返回迭代器都是快速失败(fast-fail)的。
在HashMap中,有一个变量modCount来指示集合被修改的次数。在创建Iterator迭代器的时候,会给这个变量赋值给expectedModCount。当集合方法修改集合元素时,例如集合的remove()方法时,此时会修改modCount值,但不会同步修改expectedModCount值。当使用迭代器遍历元素操作时,会首先对比expectedModCount与modCount是否相等。如果不相等,则马上抛出java.util.ConcurrentModificationException异常。而通过Iterator的remove()方法移除元素时,会同时更新expectedModCount的值,将modCount的值重新赋值给expectedModCount,这样下一次遍历时,就不会发抛出ava.util.ConcurrentModificationException异常。
首先看hashmap的remove()方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**
* Implements Map.remove and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
//在真正移除节点时,修改modCount的值
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;//指示HashMap被修改的次数
当通过remove移除HashMap中的一个元素时,会修改modCount值,其他修改HashMap集合的方法也会修改modCount值。该值在创建迭代器的时候,会赋值给expectedModCount,在迭代器工作的时候,会判定检查modCount值是否修改了。如果该值被修改了,则抛出ConcurrentModificationException异常。HashMap的Value迭代器实现如下:
final class ValueIterator extends HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
//在构造迭代器的时候,将modCount值赋值给expectedModCount
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
//在获取下一个节点前,先判定modCount值是否修改,如果被修改了则抛出ConcurrentModificationException异常,从前面可以知道,当修改了HashMap的时候,都会修改modCount值。
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
//迭代器的删除操作,会重新给exceptedModCount赋值,因此不会导致fast-fail
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
//先判定modCount值是否被修改了
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
//将modCount值重新赋值给expectedModCount,这样下次迭代时,不会出现fast-fail
expectedModCount = modCount;
}
}
可以看到,通过迭代器remove一个元素,虽然会改变modCount值,但会将modCount值重新赋值为expectedModCount,这样下次再迭代时,不会出现fast-fail。而通过HashMap的remove方法会修改modCount值,但不会更新迭代器的expectedModCount值,所以迭代器在迭代操作时,会抛出ConcurrentModificationException异常。
如果从结构上对映射进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败。注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。
(2)Hashtable
该类实现了一个哈希表;
线程安全的类,同步的
不允许空键空值
初始值为11,加载因子0.75
遍历:不能直接遍历,
(1)可以通过Keyset方法把键名映射到Set集合的视图上,然 后在利 用 Set集合遍历 的方式取出键名,取出所有值
(2)通过entrySet()方法也能取出所有键和值的形式
HashTable键值都不能为null
键名不能重复,值可以重复
HashMap的键值允许为空
HashTable基本上和HashMap一模一样
如果涉及到多个线程操作同一个Map,要使用HashTable
如果只有一个线程会操作Map,可以用HashMap
(3)WeakHashMap
继承于AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。
和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap
既然有WeakHashMap,那么有WeakHashSet吗? java collections包是没有直接提供WeakHashSet的。
我们可以通过Collections.newSetFromMap(Map<E,Boolean> map)方法可以将任何 Map包装成一个Set。源码如下:
public static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
return new SetFromMap<>(map);
}
/**
* @serial include
*/
private static class SetFromMap<E> extends AbstractSet<E>
implements Set<E>, Serializable
{
private final Map<E, Boolean> m; // The backing map
private transient Set<E> s; // Its keySet
SetFromMap(Map<E, Boolean> map) {
if (!map.isEmpty())
throw new IllegalArgumentException("Map is non-empty");
m = map;
s = map.keySet();
}
public void clear() { m.clear(); }
public int size() { return m.size(); }
public boolean isEmpty() { return m.isEmpty(); }
public boolean contains(Object o) { return m.containsKey(o); }
public boolean remove(Object o) { return m.remove(o) != null; }
public boolean add(E e) { return m.put(e, Boolean.TRUE) == null; }
public Iterator<E> iterator() { return s.iterator(); }
public Object[] toArray() { return s.toArray(); }
public <T> T[] toArray(T[] a) { return s.toArray(a); }
public String toString() { return s.toString(); }
public int hashCode() { return s.hashCode(); }
public boolean equals(Object o) { return o == this || s.equals(o); }
public boolean containsAll(Collection<?> c) {return s.containsAll(c);}
public boolean removeAll(Collection<?> c) {return s.removeAll(c);}
public boolean retainAll(Collection<?> c) {return s.retainAll(c);}
就是对传入的map进行了简单的包装
collection常用方法摘要
boolean add(E e) 向集合中添加元素
void clear() 清空集合
boolean contains(Object o) 如果此 collection 包含指定的元素,则返回 true。
boolean containsAll(Collection<?> c)
如果此 collection 包含指定 collection 中的所有元素,则返回 true。
boolean equals(Object o)
比较此 collection 与指定对象是否相等。
int hashCode()
返回此 collection 的哈希码值。
boolean isEmpty()
如果此 collection 不包含元素,则返回 true。
Iterator\<E> iterator()
返回在此 collection 的元素上进行迭代的迭代器。
boolean remove(Object o)
从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
boolean removeAll(Collection<?> c)
移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。
boolean retainAll(Collection<?> c)
仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。
int size()
返回此 collection 中的元素数。
Object[] toArray()
返回包含此 collection 中所有元素的数组。
\<T> T[]
toArray(T[] a)
返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同
import java.util.*;
public class fuck{
public static void main(String[] args){
//1.创建集合
Collection c=new ArrayList();//多态,子类指向父类
//2.添加元素
//Collection只能存储引用类型,而且只能单个存储
c.add(1);
c.add(new Object());
//3。常用方法
System.out.println(c.size());//获取元素的个数,2
System.out.println(c.isEmpty());//是否为空集合,false
c.clear();
System.out.println(c.size());//0
System.out.println(c.isEmpty());//true
}
}
-
Arrays和collections的区别
(1)Arrays包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂.
(2)Collections是个java.util下的类,它包含有各种有关集合操作的静态方法。他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作(集合的帮助类)。 -
集合类没有实现Cloneable和Serializable接口?
没有
因为克隆(cloning)或者序列化(serialization)的语义和含义是跟具体的实现相关的。因此应该由集合类的具体实现类来决定如何被克隆或者序列化 -
迭代器
Iterator和ListIterator是Java三个游标中的两个,都是由Java.UTIL包中的集合框架定义的。
(1)遍历
使用Iterator,可以遍历所有集合,如Map,List,Set;但只能在向前方向上遍历集合中的元素。
使用ListIterator,只能遍历List实现的对象,但可以向前和向后遍历集合中的元素。
(2)添加元素
Iterator无法向集合中添加元素;而,ListIteror可以向集合添加元素。
(3)修改元素
Iterator无法修改集合中的元素;而,ListIterator可以使用set()修改集合中的元素。
(4)索引
Iterator无法获取集合中元素的索引;而,使用ListIterator,可以获取集合中元素的索引。 -
java基础–安全失败和快速失败
public static void main(String[] args) {
Hashtable<String, String> table = new Hashtable<String, String>();
table.put("a", "aa");
table.put("b", "bb");
table.put("c", "cc");
table.remove("c");
Iterator<Entry<String, String>> iterator = table.entrySet().iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next().getValue());
//采用iterator直接进行修改 程序正常
iterator.remove();
//直接从hashtable增删数据就会报错
table.put("d", "dd");
//直接从hashtable增删数据就会报错,hashtable,hashmap等非并发集合,如果在迭代过程中增减了数据,就是快速失败
table.remove("c");
}
System.out.println("-----------");
Lock lock = new ReentrantLock();
//即使加上lock,还是会跑出ConcurrentModificationException异常
lock.lock();
HashMap<String, String> hashmap = new HashMap<String, String>();
hashmap.put("a", "aa");
hashmap.put("b", "bb");
hashmap.put("c", "cc");
Iterator<Entry<String, String>> iterators = hashmap.entrySet().iterator();
while (iterators.hasNext()) {
System.out.println(iterators.next().getValue());
// 正常
iterators.remove();
//直接从hashtable增删数据就会报错。
//hashtable,hashmap等非并发集合,如果在迭代过程中增减了数据,会快速失败 (一检测到修改,马上抛异常)
//java.util.ConcurrentModificationException
hashmap.remove("c");
}
System.out.println("-----------");
lock.unlock();
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();
map.put("a", "aa");
map.put("b", "bb");
map.put("c", "cc");
Iterator<Entry<String, String>> mapiterator = map.entrySet().iterator();
while (mapiterator.hasNext()) {
System.out.println(mapiterator.next().getValue());
map.remove("c");// 正常 并发集合不存在快速失败问题
map.put("c", "cc");// 正常 并发集合不存在快速失败问题
}
System.out.println("-----------");
}
运行该段代码发现,在Hashtable和HashMap的循环迭代过程中在容器对象上做“修改”操作的话,是跑出java.util.ConcurrentModificationException异常,在Iterator上做操作不会异常。但是ConcurrentHashMap在容器对象和Iterator对象上都不会抛异常,这是为什么呢?
(1)首先来介绍两个概念,快速失败和安全失败。
Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,
而java.util.concurrent包下面的所有的类都是安全失败的。
快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
(2)我们查看Hashtable、HashMap、ConcurrentHashMap的在Java API底层的entrySet对象发现,三者都做了对当前对象的拷贝,三者的处理方式是一样的,那区别在哪里呢?看看获取下一个entrySet在逻辑上的区别
这是Hashtable、HashMap的
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
这是ConcurrentHashMap的
public final Map.Entry<K,V> next() {
Node<K,V> p;
if ((p = next) == null)
throw new NoSuchElementException();
K k = p.key;
V v = p.val;
lastReturned = p;
advance();
return new MapEntry<K,V>(k, v, map);
}
/**
* Advances if possible, returning next valid node, or null if none.
*/
final Node<K,V> advance() {
Node<K,V> e;
if ((e = next) != null)
e = e.next;
for (;;) {
Node<K,V>[] t; int i, n; // must use locals in checks
if (e != null)
return next = e;
if (baseIndex >= baseLimit || (t = tab) == null ||
(n = t.length) <= (i = index) || i < 0)
return next = null;
if ((e = tabAt(t, i)) != null && e.hash < 0) {
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable;
e = null;
pushState(t, i, n);
continue;
}
else if (e instanceof TreeBin)
e = ((TreeBin<K,V>)e).first;
else
e = null;
}
if (stack != null)
recoverState(n);
else if ((index = i + baseSize) >= n)
index = ++baseIndex; // visit upper slots if present
}
}
ConcurrentHashMap中的迭代器主要包括entrySet、keySet、values方法。它们大同小异,这里选择entrySet解释。当我们调用entrySet返回值的iterator方法时,返回的是EntryIterator,在EntryIterator上调用next方法时,最终实际调用到了HashIterator.advance()方法。这个方法在遍历底层数组。在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap迭代器弱一致的表现。ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable和同步的HashMap一样了。
最后我们看看JDK中对于快速失败的描述:
注意,此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下所示:
Map m = Collections.synchronizedMap(new HashMap(…));由所有此类的“collection 视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。