Collection接口
Collection接口
Map接口
Map接口

fast-fail

快速报错,是指当有其他线程对一个容器(如ArrayList,HashMap)进行了结构性修改,另外一个线程在使用iterator进行迭代,那么这个迭代线程会抛出并发修改的异常ConcurrentModificationException。
所谓结构性修改,是对原有容器的size造成影响的操作,如remove、add、clear操作等。
这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数

链表 LinkedList

方法的区分

方法名(添加,删除,取头) 特殊情况下的动作 相关接口
add, remove, element 抛异常 List
offer, poll, peek 返回空值 Queue

ListIterator接口

添加元素是,使用LinkedList.add将对象添加到链表的尾部,如果需要在指定位置添加,则需要使用迭代器。
但是对于自然有序的结合添加才有意义,因此类库提供了ListIterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface ListIterator<E> extends Iterator<E> {
//Iterator中的方法
boolean hasNext();
E next();
void remove();
//适用于自然有序的集合的方法
boolean hasPrevious();//反向遍历
E previous();//反向遍历
int nextIndex();
int previousIndex();
void set(E e);
void add(E e);
}

如果链表中有n个元素,则有n+1个位置可以添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void main(String[] args) {
List<String> a = new LinkedList<>();
a.add("Amy");
a.add("Carl");
a.add("Ercia");
List<String> b = new LinkedList<String>();
b.add("Bob");
b.add("Doug");
b.add("Frances");
b.add("Gloia");
//Merge the word from b to a
//listIterator和iterator是指向两个元素之间的,而不是某一个元素
ListIterator<String> aIterator = a.listIterator();
//bIterator是Iterator,因此无add方法
Iterator<String> bIterator = b.iterator();
while(bIterator.hasNext()){
if(aIterator.hasNext()){
aIterator.next();
}
//添加元素是在指针所指的中间添加元素
aIterator.add(bIterator.next());
}
System.out.println(a);
bIterator = b.iterator();
while(bIterator.hasNext()){
bIterator.next();
if(bIterator.hasNext()){
bIterator.next();
//删除指针左边的元素
bIterator.remove();
}
}
System.out.println(b);
//这里是List的方法,删除a中所有b的元素
a.removeAll(b);
System.out.println(a);
}

remove与next和previous

E previous()与next用法相似,也是返回刚刚经过的元素,但是方向是与next相反
因此调用next使用remove方法,删除左边元素
调用previous使用remove,删除右边元素

set与next和previous

set方法用一个新元素来取代调用next或previous返回的上一个元素

1
2
3
ListIterator<String> iterator = c.listIterator();
String oldValue = iterator.next();
iterator.set(newValue);

并发修改异常

1
2
3
4
5
6
7
8
Collection<String> a = new LinkedList<String>();
a.add("A");
a.add("B");
Iterator<String> it1 = a.iterator();
Iterator<String> it2 = a.iterator();
it1.next();
it1.remove();
it2.next();

由于并发修改,一个删除后,另一个访问,会抛出ConcurrentModificationException的错误
因为每个迭代器内部都会维护一个独立的计数值。在迭代器开始方法前,先检查自己的修改次数和集合的修改次数是否一致,如果不一致就会抛出ConcurrentModificationException

因此迭代器使用有个简单的原则:
每个集合可以有很多个迭代器,但是这些迭代器只能读
而专门只有一个迭代器既能读又能写

当前位置索引

由于迭代器指向两个元素的中间位置,因此可以产生两个索引
int nextIndex()返回下一次调用next返回的元素的整数索引
int previousIndex()返回下一次调用previous返回的元素的整数索引
list.listIterator(n);返回迭代器,其指向整数索引为n的前面的元素

实现

  • 内部为Node节点,包含了前驱、后继和值,故为双向链表
  • 有全局变量first和last
  • get方法
    由于链表支持随机查找,因此需要遍历,这里对遍历过程做了优化
    判断查找的位置是在前一半还是后一半。前一半就从第一个开始遍历,后一半就从最后一个倒着找。

数组列表 ArrayList, Vector

ArrayList和Vector实现了随机访问,其本质是动态再分配的对象数组
ArrayList是不安全
Vector是线程安全的

Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

实现要点

  • 内部使用数组实现
  • 数组的开始大小为10
  • 添加过程
    原内部数组中的对象数目加1,如果大于原内部数组长度,则以原长度的1.5倍新建一个对于原数组的拷贝(Arrays.copyOf),并修改原数组,指向这个新建数组。原数组自动抛弃,size则自增1,向数组中添加对象。
  • 删除
    根据给定索引值,判断合理性,之后取出对应这个索引位置的对象,之后将这个位置之后的所有对象向前移动一位即可

http://blog.csdn.net/zw0283/article/details/51122255

序列化与ArrayList

ArrayList中的数据是transient Object[] elementData;定义的,由于transient所以是无法序列化的,但是实际上ArrayList还是可以序列化的
因为重写了readObjectwriteObject,这么做的原因是为了防止一个包含大量空对象的数组被序列化
http://www.hollischuang.com/archives/1140

ArrayList的生成

在java中比较建议用List<Object> list = new ArrayList<Object>();而不是用ArrayList<Object> list = new ArrayList<Object>();
这样做的原因是解耦和封装
这是一种面向接口编程的思想,即以后做改变很容易,这样可以提高复用性
同时可以动态地使用,比如反射一个东西,只需要先设接口,new出的对象转化为该接口即可,而需要用if判断
http://stackoverflow.com/questions/9852831/polymorphism-why-use-list-list-new-arraylist-instead-of-arraylist-list-n?noredirect=1&lq=1

映射表HashMap

用来存放键值对,都实现map接口
HashMap对键进行散列,而TreeMap用键的整体顺序对元素进行排序,构成搜索树。
如果要求一定的顺序,使用TreeMap,否则使用HashMap(同HashSet和TreeSet相同)
往映射表添加内容必须提供一个键,这个键值要唯一,否则会被覆盖
http://www.importnew.com/20386.html
http://blog.csdn.net/zw0283/article/details/51177547

Entry数组

Entry是用于实现链表的一个节点、
里面有key,value用于存储自身节点数据, 哈希值,还有next用于下个节点的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class Entry<K, V> implements Map.Entry<K, V> {
// Entry继承了Map接口中的静态子类Entry;Entry<K,V>是槽中的元素,
// 用作链表来解决散列冲突
final K key;// 键
V value;// 值
Entry<K, V> next;// 用来实现链表结构, 同一链表中的key的hash是相同的
int hash;
protected Entry(int hash, K key, V value, Entry<K, V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}

HashMap的成员

1
2
3
4
5
6
7
8
9
public class HashMap<K,V>{
static final int DEFAULT_INITIAL_CAPACITY = 16 ; // 默认初始容量是16。 ( 必须是2的次方)
static final int MAXIMUM_CAPACITY = 1 << 30 ; // 即2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认装载因子
Entry[] table; // Entry表
int size; // Entry[]实际存储的Entry个数
int threshold; // reash的阈值, =capacity * loadfactor
final float loadFactor; //装填因子
}

  • 负载因子
    负载因子越大,填满的元素越多,空间利用率增加,hash冲突的机会增加,每个元素下挂载的链表会越来越长,同时会导致查找元素的效率变得低下。
    负载因子越小,填满的元素越少,空间利用率降低,hash冲突减少,但是数组中的元素过于稀疏,导致数组中很多空间还没有用就开始扩容,不过好处是查找的元素的效率相对高一些。
    所以,必然要在“查找效率”和“空间利用率”之中做一个折中,让它们处在一个相对的平衡状态。0.75就是这样的一个相对平衡的状态

  • 为什么容量是2的倍数?
    为了寻址的快速。 寻址是通过 hash(key) & (table.length-1)实现的
    若数组长度为2的N次方,则数组的长度必然为偶数,则偶数-1必然为奇数,在2进制的表示中,奇数的最后一位为1,所以,与奇数做“&”操作,最后的结果可能为奇数,也可能为偶数。
    其次,若数组长度不为偶数,则奇数-1为偶数,偶数在2进制中最后一位为0,那么与偶数做“&”操作,最后的结果只可能是偶数,不可能为奇数,所以在奇数位置的空间不会存储到元素,所以会有二分之一的空间被浪费掉。
    综上所述,数组长度取2的N次方,目的是为了能让元素均匀的分布在数组中,减小发生冲突的机会。

get方法

  1. 对nullkey做单独处理, 其实就是放在table[0]中
  2. 根据key的hash值, 决定entry在哪个桶
  3. 对桶内的entry链表进行遍历, 当查找到时返回value
  4. 找不到, 返回null
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public V get(Object key) {
    //对空值做处理
    if (key == null)
    return getForNullKey();
    //非空情况
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
    }
  • 空值处理getForNullKey
1
2
3
4
5
6
7
8
9
10
11
private V getForNullKey() {
if (size == 0) {
return null;
}
//对于空值table[0]中查找
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
  • 正常的处理getEntry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//二次hash,得到hash值
int hash = (key == null) ? 0 : hash(key);
//计算出在哪个桶indexFor(hash, table.length)
//遍历桶内元素
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}

put方法

使用key计算得到hashCode,之后得到该元素在数组中的存放位置,然后将其放入链表的头部,放入的过程中要对容量进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public V put(K key, V value) {
//判断是否空
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
//二次hash得到hash值
int hash = hash(key);
//得到该hash的桶号码
int i = indexFor(hash, table.length);
//寻找过程,确保唯一
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果存在,要覆盖
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//添加Entry
addEntry(hash, key, value, i);
return null;
}

put()方法中, 如果遇见的键值对是新的, 那么会调用addEntry()方法,
将这个键值对存储在hash值相同的槽位的头部( 链表的头插入)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
//容量扩大两倍
resize(2 * table.length);
//二次hash
hash = (null != key) ? hash(key) : 0;
//找到槽位
bucketIndex = indexFor(hash, table.length);
}
//添加
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//头插
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}

Map中有3个视图

1
2
3
Set<K> keySet(); //键集
Collection<V> values(); //值集合
Set<Map.Entry<K, V>> entrySet(); //键值集

多线程访问HashMap

多线程访问HashMap,会出现错误,可能有两种
1)hashmap放数据,扩容会导致内部一致性发生变化,出现越界等问题
2)两个线程hashmap同时放数据,两个节点可能会相互指向,出现死循环
http://coolshell.cn/articles/9606.html

Reference

http://www.ibm.com/developerworks/cn/java/j-lo-hash/
http://www.cnblogs.com/skywang12345/p/3310835.html#b1

散列集 HashSet

java中使用HashSet表示散列集

  • 实现Set接口
  • 实际是HashMap的一个实例
  • 允许空值
  • 元素无重复的无序集合
  • 使用链表数组实现
    每个列表被称为桶。
链表数组
链表数组

查找

要想查找对象的位置,计算其散列码,然后与总桶数取余,得到就是保存这个对象的索引。
如果桶中没有其他元素,就可以直接插入;
如果桶满了,这种现象被称作散列冲突,此时需要与这个桶中所有的元素进行比较。

装填因子和再散列

The HashMap instance has default initial capacity (16) and load factor (0.75).
如果需要更好的控制散列表的性能,需要制定一个初始的桶数,通常设置为元素个数的75%~150%。
当然并不是总能很好的估计桶数,如果散列表装的太慢,就需要再散列。
对散列表再散列,就需要一个桶数更多的表,将所有元素插入新表,将原表丢弃。
装填因子决定表是否需要再散列,如果表中元素占有率大于表的装填因子,表就会用双倍的桶数在散列。

不同步

本身不同步,可以下面的方法同步

1
Set s = Collections.synchronizedSet(new HashSet(...));

TreeMap 和 树集 TreeSet

树集和散列集相似,但是比散列集有所改进。
java用TreeSet表示树集

  • 树集是有序集合,实现了SortedSet接口
    Class TreeSet implements NavigableSet
    Interface NavigableSet extends SortedSet
    Interface SortedSet extends Set

  • TreeSet中元素实现了Comparable接口
    All elements inserted into a sorted set must implement the Comparable interface (or be accepted by the specified comparator)
    对于基本数据类型,已经实现了Comparable,但是对于自定义类,必须实现Comparable,否则会报错java.lang.ClassCastException

  • 树集使用红黑树实现的

  • 树集底层是调用TreeMap实现的

TreeSet 与 TreeMap

TreeSet源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
{
// 使用 NavigableMap 的 key 来保存 Set 集合的元素
private transient NavigableMap<E,Object> m;
// 使用一个 PRESENT 作为 Map 集合的所有 value。
private static final Object PRESENT = new Object();
// 包访问权限的构造器,以指定的 NavigableMap 对象创建 Set 集合
TreeSet(NavigableMap<E,Object> m)
{
this.m = m;
}
public TreeSet() // ①
{
// 以自然排序方式创建一个新的 TreeMap,
// 根据该 TreeSet 创建一个 TreeSet,
// 使用该 TreeMap 的 key 来保存 Set 集合的元素
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) // ②
{
// 以定制排序方式创建一个新的 TreeMap,
// 根据该 TreeSet 创建一个 TreeSet,
// 使用该 TreeMap 的 key 来保存 Set 集合的元素
this(new TreeMap<E,Object>(comparator));
}
public TreeSet(Collection<? extends E> c)
{
// 调用①号构造器创建一个 TreeSet,底层以 TreeMap 保存集合元素
this();
// 向 TreeSet 中添加 Collection 集合 c 里的所有元素
addAll(c);
}
public TreeSet(SortedSet<E> s)
{
// 调用②号构造器创建一个 TreeSet,底层以 TreeMap 保存集合元素
this(s.comparator());
// 向 TreeSet 中添加 SortedSet 集合 s 里的所有元素
addAll(s);
}
//TreeSet 的其他方法都只是直接调用 TreeMap 的方法来提供实现
...
public boolean addAll(Collection<? extends E> c)
{
if (m.size() == 0 && c.size() > 0 &&
c instanceof SortedSet &&
m instanceof TreeMap)
{
// 把 c 集合强制转换为 SortedSet 集合
SortedSet<? extends E> set = (SortedSet<? extends E>) c;
// 把 m 集合强制转换为 TreeMap 集合
TreeMap<E,Object> map = (TreeMap<E, Object>) m;
Comparator<? super E> cc = (Comparator<? super E>) set.comparator();
Comparator<? super E> mc = map.comparator();
// 如果 cc 和 mc 两个 Comparator 相等
if (cc == mc || (cc != null && cc.equals(mc)))
{
// 把 Collection 中所有元素添加成 TreeMap 集合的 key
map.addAllForTreeSet(set, PRESENT);
return true;
}
}
// 直接调用父类的 addAll() 方法来实现
return super.addAll(c);
}
...
}

从上面代码可以看出,TreeSet 的 ① 号、② 号构造器的都是新建一个 TreeMap 作为实际存储 Set 元素的容器,而另外 2 个构造器则分别依赖于 ① 号和 ② 号构造器,由此可见,TreeSet 底层实际使用的存储容器就是 TreeMap。

比较

如果TreeSet中不是基本数据类型,那么该类一定要实现comparable或者使用comparator比较器
具体请参照comparable与comparator

SortedSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public interface SortedSet<E> extends Set<E> {
Comparator<? super E> comparator();
/**
* Returns a view of the portion of this set whose elements range
* from <tt>fromElement</tt>, inclusive, to <tt>toElement</tt>,
* exclusive. (If <tt>fromElement</tt> and <tt>toElement</tt> are
* equal, the returned set is empty.)
*/
SortedSet<E> subSet(E fromElement, E toElement);
/**
* Returns a view of the portion of this set whose elements are
* strictly less than <tt>toElement</tt>.
*/
SortedSet<E> headSet(E toElement);
/**
* Returns a view of the portion of this set whose elements are
* greater than or equal to <tt>fromElement</tt>.
*/
SortedSet<E> tailSet(E fromElement);
/**
* Returns the first (lowest) element currently in this set.
*/
E first();
/**
* Returns the last (highest) element currently in this set.
*/
E last();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public interface NavigableSet<E> extends SortedSet<E> {
//不包括等于
/**
* Returns the greatest element in this set strictly less than the
* given element, or {@code null} if there is no such element.
*/
E lower(E e);
/**
* Returns the least element in this set strictly greater than the
* given element, or {@code null} if there is no such element.
*/
E higher(E e);
//包括等于
/**
* Returns the greatest element in this set less than or equal to
* the given element, or {@code null} if there is no such element.
*/
E floor(E e);
/**
* Returns the least element in this set greater than or equal to
* the given element, or {@code null} if there is no such element.
*/
E ceiling(E e);
/**
* Retrieves and removes the first (lowest) element,
* or returns {@code null} if this set is empty.
*/
E pollFirst();
/**
* Retrieves and removes the last (highest) element,
* or returns {@code null} if this set is empty.
*/
E pollLast();
/**
* Returns an iterator over the elements in this set, in ascending order.
*/
Iterator<E> iterator();
/**
* Returns a reverse order view of the elements contained in this set.
*/
NavigableSet<E> descendingSet();
/**
* Returns an iterator over the elements in this set, in descending order.
* Equivalent in effect to {@code descendingSet().iterator()}.
*/
Iterator<E> descendingIterator();
/**
* Returns a view of the portion of this set whose elements range from
* {@code fromElement} to {@code toElement}. If {@code fromElement} and
* {@code toElement} are equal, the returned set is empty unless {@code
* fromInclusive} and {@code toInclusive} are both true.
*/
NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
E toElement, boolean toInclusive);
/**
* Returns a view of the portion of this set whose elements are less than
* (or equal to, if {@code inclusive} is true) {@code toElement}.
*/
NavigableSet<E> headSet(E toElement, boolean inclusive);
/**
* Returns a view of the portion of this set whose elements are greater
* than (or equal to, if {@code inclusive} is true) {@code fromElement}.
*/
NavigableSet<E> tailSet(E fromElement, boolean inclusive);
}

ArrayDeque 和 Stack

ArrayDeque

@since 1.6

  • 变长数组,长度无限增长
    Resizable-array implementation of the Deque interface.
    Array deques have no capacity restrictions; they grow as necessary to support
    usage.
  • 线程不安全
    They are not thread-safe; in the absence of external
    synchronization, they do not support concurrent access by multiple threads.
  • 非空
    Null elements are prohibited.

This class is likely to be faster than Stack when used as a stack,
and faster than LinkedList when used as a queue.

Stack

栈,先进后出
线程安全,继承Vector

优先级队PriorityQueue

元素按照任意顺序插入,但是按照一定顺序排序检索。也就是说无论何时调用remove方法,都会删除最小的元素(The elements of the priority queue are ordered according to their natural ordering, or by a Comparator provided at queue construction time, depending on which constructor is used. )。

在优先队列内部并不会对所有的元素进行排序
使用堆Heap的数据结构,只会保证最小或者最大的元素在第一个位置
The head of this queue is the least element with respect to the specified ordering.
If multiple elements are tied for least value, the head is one of those elements – ties are broken arbitrarily.

The queue retrieval operations poll, remove, peek, and element access the element at the head of the queue.

PriorityQueue is not thread safe, java provides PriorityBlockingQueue class that implements the BlockingQueue interface to use in java multi-threading environment.

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
Queue<Integer> prq = new PriorityQueue();
Random r = new Random();
System.out.println("--------------add----------------");
for(int i = 0; i < 5; i++){
int addNum = r.nextInt(100);
prq.add(addNum);
System.out.println("add num" + addNum);
//通过打印的队列可以看出来,队列内部不一定有序,但是最小元素一定在队头
//添加的变化过程就是将新元素放在最后,然后调整堆
System.out.println("prq" + prq);
}
System.out.println("----------------remove-----------------------");
for(int i = 0; i < 5; i++){
int x = prq.peek();
System.out.println("remove num" + x);
prq.remove();
//通过打印的队列可以看出来,队列内部不一定有序,但是最小元素一定在队头
//删除的变化过程就是将最后一个元素,放在堆顶,然后在进行调整
System.out.println("prq" + prq);
}
}

>
————–add—————-
add num33
prq[33]
add num36
prq[33, 36]
add num45
prq[33, 36, 45]
add num38
prq[33, 36, 45, 38]
add num4
prq[4, 33, 45, 38, 36]
—————-remove———————–
remove num4
prq[33, 36, 45, 38]
remove num33
prq[36, 38, 45]
remove num36
prq[38, 45]
remove num38
prq[45]
remove num45
prq[]

堆的建立过程参考循环建立递归建立

WeakHashMap

WeakHashMap 继承于AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。

不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。
更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。

这个“弱键”的原理呢?大致上就是,通过WeakReferenceReferenceQueue实现的。
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) throws Exception {
String a = new String("a");
String b = new String("b");
Map weakmap = new WeakHashMap();
Map map = new HashMap();
map.put(a, "aaa");
map.put(b, "bbb");
weakmap.put(a, "aaa");
weakmap.put(b, "bbb");
map.remove(a);
a = null;
b = null;
System.gc();
Iterator i = map.entrySet().iterator();
while (i.hasNext()) {
Map.Entry en = (Map.Entry) i.next();
System.out.println("map:" + en.getKey() + ":" + en.getValue());
}
Iterator j = weakmap.entrySet().iterator();
while (j.hasNext()) {
Map.Entry en = (Map.Entry) j.next();
System.out.println("weakmap:" + en.getKey() + ":" + en.getValue());
}
}

代码声明了两个 Map 对象,一个是 HashMap,一个是 WeakHashMap,同时向两个 map 中放入 A、B 两个对象.
当 HashMap 删除 A,并且 A、B 都指向 Null 时,WeakHashMap 中的 A 将自动被回收掉。
出现这个状况的原因是,对于 A 对象而言,当 HashMap 删除并且将 A 指向 Null 后,除了 WeakHashMap 中还保存 A 外已经没有指向 A 的指针了,所以 WeakHashMap 会自动舍弃掉 a,
而对于 B 对象虽然指向了 null,但 HashMap 中还有指向 B 的指针,所以 WeakHashMap 将会保留 B 对象。

WeakHashMap 主要通过 expungeStaleEntries这个函数来实现移除其内部不用的条目,从而达到自动释放内存的目的。
基本上只要对 WeakHashMap 的内容进行访问就会调用这个函数,从而达到清除其内部不再为外部引用的条目。
但是如果预先生成了 WeakHashMap,而在 GC 以前又不曾访问该 WeakHashMap, 那不是就不能释放内存了吗?

1
2
3
4
5
6
7
8
9
10
11
List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();
for (int i = 0; i < 1000; i++) {
WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
d.put(new byte[1000][1000], new byte[1000][1000]);
maps.add(d);
System.gc();
System.err.println(i);
// for (int j = 0; j < i; j++) {
// System.err.println(j + " size" + maps.get(j).size());
// }
}

果不其然,WeakHashMap 这个时候并没有自动帮我们释放不用的内存
总的来说,WeakHashMap并不是你什么也干它就能自动释放内部不用的对象的,而是在你访问它的内容的时候释放内部不用的对象

WeakHashMap 实现弱引用,是因为它的 Entry是继承自 WeakReference

1
2
3
4
5
6
7
private static class Entry<K,V> extends WeakReference<K>
implements Map.Entry<K,V> Entry(K key, V value, ReferenceQueue<K> queue,int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}

请注意它构造父类的语句super(key, queue);,传入的是 Key,因此 Key 才是进行弱引用的,Value 是直接强引用关联在 this.value 之中。
在 System.gc() 时,Key 中的 Byte 数组进行了回收,而 Value 依然保持 (Value 被强关联到 Entry 上,Entry 又关联在 Map 中,Map 关联在 ArrayList 中)。

For 循环中每次都 New 一个新的 WeakHashMap,在 Put 操作后,虽然 GC 将 WeakReference 的 Key 中的 Byte 数组回收了,并将事件通知到了 ReferenceQueue,但后续却没有相应的动作去触发 WeakHashMap 去处理 ReferenceQueue,所以 WeakReference 包装 Key 依然存在于 WeakHashMap 中,其对应的 value 也当然存在。

那 value 是何时被清除的呢?
上面的注释代码中的 maps.get(j).size() 触发了 Value 的回收,那又如何触发的呢?
查看 WeakHashMap 源码可知,Size 方法调用了 expungeStaleEntries 方法,该方法对 JVM 要回收的的 Entry(Quene 中) 进行遍历,并将 Entry 的 Value 置空,回收了内存。
所以效果是 Key 在 GC 的时候被清除,Value 在 Key 清除后访问 WeakHashMap 被清除

WeakHashMap 类是线程不同步的,可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap, 每个键对象间接地存储为一个弱引用的指示对象。
因此,不管是在映射内还是在映射之外,只有在垃圾回收器清除某个键的弱引用之后,该键才会自动移除。

内容参考WeakHashMap

LinkedHashMap 和 LinkedHashSet

HashSet是调用HashMap实现的,同理适用于LinkedHashSet,因此下面主要讲LInkedHashMap
LinkedHashMap是按照插入顺序和按访问顺序(构造时accessOrder设为true)的链表
其中按访问顺序可以构建 LRU (Least Recently Used)缓存
实现了记录顺序的LinkedHashMap,通过数组链表(HashMap)和循环双向链表(记录顺序)实现
在链表中有head指针,head.before 最新,head.after 最老

http://blog.csdn.net/zw0283/article/details/51258683

结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
{
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*/
private final boolean accessOrder;
/**
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the specified initial capacity and load factor.
*/
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
//初始化不记录访问顺序
accessOrder = false;
}
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
/**
* Called by superclass constructors and pseudoconstructors (clone,
* readObject) before any entries are inserted into the map. Initializes
* the chain.
*/
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
}
private static class Entry<K,V> extends HashMap.Entry<K,V> {
//双向循环链表
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
//put方法会调用该方法
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//按照访问顺序记录
if (lm.accessOrder) {
lm.modCount++;
//删除该节点
remove();
//由于被访问过,变为最新节点,放在head前面
addBefore(lm.header);
}
}
void recordRemoval(HashMap<K,V> m) {
remove();
}
}

put

LinkedHashMap 并未重写父类 HashMap 的 put 方法,而是重写了父类 HashMap 的 put 方法调用的子方法void recordAccess(HashMap m)void addEntry(int hash, K key, V value, int bucketIndex)void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//添加了删除队头的操作
addEntry(hash, key, value, i);
return null;
}
//#####################重写方法#####################
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
//最老的在队头
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//按照访问顺序记录
if (lm.accessOrder) {
lm.modCount++;
//删除该节点
remove();
//由于被访问过,变为最新节点,放在head前面
addBefore(lm.header);
}
}
//在一个节点前面添加节点
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}

get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//按照访问顺序记录
if (lm.accessOrder) {
lm.modCount++;
//删除该节点
remove();
//由于被访问过,变为最新节点,放在head前面
addBefore(lm.header);
}
}

References

http://wiki.jikexueyuan.com/project/java-collection/linkedhashmap.html
http://wiki.jikexueyuan.com/project/java-collection/linkedhashmap-lrucache.html
http://zhouyunan2010.iteye.com/blog/1236220

EnumSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private enum Color {
RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255);
private int r;
private int g;
private int b;
Color(int r, int g, int b) {
this.r = r;
this.g = g;
this.b = b;
}
}
public static void drawLine(Set<Color> colors) {
System.out.println("Requested Colors to draw lines : " + colors);
for (Color c : colors) {
System.out.println("drawing line in color : " + c);
}
}
public static void main(String[] args) {
// this will draw line in yellow color
EnumSet<Color> yellow = EnumSet.of(Color.RED, Color.GREEN);
drawLine(yellow);
// RED + GREEN + BLUE = WHITE
EnumSet<Color> white = EnumSet.of(Color.RED, Color.GREEN, Color.BLUE);
drawLine(white);
// RED + BLUE = PINK
EnumSet<Color> pink = EnumSet.of(Color.RED, Color.BLUE);
drawLine(pink);
}

BitSet

方法

  • void set(i) i位置置1
  • boolean get(i) 返回i的布尔值
  • void clear(i) 位置置0
  • int lenght() 逻辑值
  • int size() 长度

例子

  • 素数筛法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    //bitset 被置1说明是素数
    public static void count(BitSet set){
    //length() -> Returns the "logical size" of this BitSet
    for(int i = 2; i < set.size(); i++){
    //初始化,全部置1
    set.set(i);
    }
    for(int i = 2; i < Math.sqrt(set.size()) + 1; i++){
    if(set.get(i)){
    for(int j = 2 * i; j < set.length(); j += i){
    //素数的倍数全部标记为非素数
    set.clear(j);
    }
    }
    }
    }
    public static void main(String[] args) {
    BitSet b = new BitSet(2000000);
    count(b);
    int count = 0;
    for(int i = 2; i < b.length(); i++){
    if(b.get(i)){
    count ++;
    if(i < 100){
    System.out.println(i);
    }
    }
    }
    System.out.println("count" + count);
    }
  • 逻辑运算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public static void main(String args[]) {
    BitSet bits1 = new BitSet(16);
    BitSet bits2 = new BitSet(16);
    // set some bits
    for (int i = 0; i < 16; i++) {
    //偶数都会被置1
    if ((i % 2) == 0)
    bits1.set(i);
    //5的倍数都会被置1
    if ((i % 5) != 0)
    bits2.set(i);
    }
    System.out.println("Initial pattern in bits1: ");
    System.out.println(bits1);
    System.out.println("\nInitial pattern in bits2: ");
    System.out.println(bits2);
    // AND bits
    bits2.and(bits1);
    System.out.println("\nbits2 AND bits1: ");
    System.out.println(bits2);
    // OR bits
    bits2.or(bits1);
    System.out.println("\nbits2 OR bits1: ");
    System.out.println(bits2);
    // XOR bits
    bits2.xor(bits1);
    System.out.println("\nbits2 XOR bits1: ");
    System.out.println(bits2);
    }

Properties

Properties Tutorial