0%

LinkedList介绍

LinkedList的类图如下

LinkedList

LinkedList是一个继承于AbstractSequentialList双向链表。他可以被当做栈、队列和双端队列来使用。

LinkedList实现了List接口,能对他进行队列操作。这个是因为。在LinkedList的默认实现时就是按照队列实现的。

LinkedList是实现Deque接口,能对他进行双端队列操作。
LinkedList不是线程安全的集合

LinkedList包含三个比较重要的成员:first、last和size

  • first指向双向链表的表头。

  • last指向双向链表的尾部。

  • size是双向链表中节点的个数。

阅读全文 »

本篇博客主要的内容是介绍ArrayList的使用和对其源码进行分析,并比较ArrayList不同迭代器之间的性能

1. ArrayList 简介

ArrayList是一个有序集合,底层是通过数组来实现的,可以动态的改变大小,相当于动态数组。与Java中的数组相比,它的容量能动态增长。他继承于AbstractList,实现了List,RandomAccess,Cloneable,java.io.Serializable接口。
类如如下:

list

ArrayList继承AbstractList,实现List接口。因此就具有相关的添加、删除、修改、遍历等功能。

ArrayList实现RandmoAccess接口,即提供随机访问功能,注意这个接口内部并没有任何方法,只是起到标记的作用。

ArrayList实现Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
注意的一点是:ArrayList不是线程安全的!所以不要再并发程序中使用

ArrayList源码分析

下面所有源码都是基于Java8来进行的分析:按照add,remove,get,Iterator,ListIterator的顺序来进行源码分析

首先看一下一些比较重要的属性,可以方便后面理解源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;

//如果是空实例,这个就会减少创建的开销,
private static final Object[] EMPTY_ELEMENTDATA = {};

//使用new ArrayList()创建时,elementData会指向下面这个数组,用于和上面进行区别对待
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//当开始添加任何元素时,此属性就会指向EMPTY_ELEMENTDATA
transient Object[] elementData;

//集合包含元素的数量
private int size;
  1. elementData 是”Object[]类型的数组”,它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们能通过构造函数ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;如果通过不含参数的构造函数来创建ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增长而动态的增长,具体的增长方式,请参考源码分析中的ensureCapacity()函数。
  2. 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
//设置ArrayList的初始容量,当时负数时抛出错误
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw
new IllegalArgumentException("Illegal Capacity: " +
initialCapacity);
}
}

//初始化ArrayList
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//添加指定的集合到此集合中并初始化
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//c.toArray 返回的数组类型有可能和此集合的类型不相同,
//则重新拷贝元素到本集合的数组中去
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size,
Object[].class);
} else {
// 利用空集合来初始化
this.elementData = EMPTY_ELEMENTDATA;
}
}

从上面可知,提供了三个默认构造函数,分别是默认构造函数,将会初始化一个空数组,指定长度的构造函数,根据指定的长度来初始化数组,添加一个集合来初始化数组,这个会初始化一个和集合长度相等的数组,并把数据添加进去。

这里补充一点,就是上面第三个构造函数,为什么还要在判断下数组元素的类型是否是Object类型的数组,主要的原因是因为,java标准库中提供了一个Arrays.asList的函数,这个函数会返回一个list,但是没有用ArrayList来实现,而是在其内部单独实现了一个ArrayList,其内部实现将底层数组定义成了T[] ,而不是Object[],所以如果传递的是这种list创建ArrayList,就会出现类型不一致的问题,这里判断的也是为了防止这一点,详细的内容可以参考利用Jdk 6260652 Bug解析Arrays.asList(学习笔记)

添加(add)

这里先总结下具体的添加过程

  1. 添加元素时,首先会检测数组是否已满,如果未满,则直接添加元素。否则进入下一步。
  2. 如果已经满了,则创建一个更长的数组,将原先数组的数据拷贝过来,然后在添加元素。

具体源码如下

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
 public boolean add(E e) {
// 确认数组是否已满
ensureCapacityInternal(size + 1);
// 在集合的末尾添加元素
elementData[size++] = e;
return true;
}

/**
* 确保数组有空间放置元素
*/
private void ensureCapacityInternal(int minCapacity) {
// 判断数组是否为空,如果为空,比较DEFAULT_CAPACITY
// 和minCapacity大小,使用较大的那个来初始化数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 初始化一个指定的长度
ensureExplicitCapacity(minCapacity);
}


/**
* 初始化一个指定长度的数组
*/
private void ensureExplicitCapacity(int minCapacity) {
// 修改modCount防止出现fail-fast
modCount++;

// 如果miniCapacity大于数组的长度,说明数组已满,
// 则创建一个更大的数组
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

// 初始化特定长度的数组,并将原来数组元素拷贝到当前数组中去
private void grow(int minCapacity) {

int oldCapacity = elementData.length;
// 设置新的数组长度为原来的三倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 检测新的数组长度是否大于需要的数组长度,如果小于,则使用minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 超过最大值,值抛出异常
newCapacity = hugeCapacity(minCapacity);
// 初始化newCapacity长度的数组,并拷贝旧数据
elementData = Arrays.copyOf(elementData, newCapacity);
}

这里需要注意的一点是,重新设置容量大小按照下面这个公式来设置的:新的容量=原始容量*3。并且如果数组长度超过整型的最大值,则会抛出异常。

指定下标添加元素
add(int index, E element) ,大体上和add差不多。在指定位置添加元素,需要将原先位置的元素以及后面的元素往后順移一位,大概过程如下

1
2
3
4
5
6
7
8
9
10
11
12
public void add(int index, E element) {
// 检查范围是否合适,如果小于0和大于size则抛出错误
rangeCheckForAdd(index);
// 判断数组是否已满
ensureCapacityInternal(size + 1); // Increments modCount!!
// 拷贝index下标之后的元素往后移以为
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//添加元素
elementData[index] = element;
size++;
}

删除(remove)

具体源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 public E remove(int index) {
// 判断下标是否合适
rangeCheck(index);

// 操作数加1,
modCount++;
E oldValue = elementData(index);

int numMoved = size - index - 1;
// 下标之后的数据向前移动一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
// 设置最后一个元素为空,方便GC
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}

remove(object)

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
public boolean remove(Object o) {
// 查找到素有和o相同的对象,并删除
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

// 这个相对于上面的remove(index),省略了异步索引的校验,因此比上面删除少一次检查,
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}

get

这个操作就比较简单,检查index范围是否正确,然后返回指定下标的元素

1
2
3
4
5
public E get(int index) {
rangeCheck(index);

return elementData(index);
}

Iterator和ListIterator

这俩个是通过内部维持一个当前操作的下标来实现的,当判断hashNext的时候判断当前下标是否超过size即可。

不过要注意的一点是,每一此next操作,都会进行如下操作

1
2
3
4
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

会检测modCounrt和迭代器保存的modCount是否相等,如果相同,则说明有其他线程修改了集合,会抛出并发修改异常。

ArrayList 遍历方式

(01) 第一种,通过迭代器遍历。即通过Iterator去遍历。

1
2
3
4
5
>Integer value = null;
>Iterator iter = list.iterator();
>while (iter.hasNext()) {
value = (Integer)iter.next();
>}

(02) 第二种,随机访问,通过索引值去遍历。
由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。

1
2
3
4
5
>Integer value = null;
>int size = list.size();
>for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
>}

(03) 第三种,for循环遍历。如下:

1
2
3
4
>Integer value = null;
>for (Integer integ:list) {
value = integ;
>}

下面通过一个实例来演示效率问题

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
>package Collections.cnblog.collection.list;

>import java.util.ArrayList;
>import java.util.Iterator;
>import java.util.List;

>/**************************************
* Author : zhangke
* Date : 2018/1/18 19:37
* Desc : 测出ArrayList三种哪一种遍历最快
***************************************/
>public class StudyArrayList {

public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
iteratorThroughFor(list);
iteratorThroughFor2(list);
iteratorThroughRandomAccess(list);
}

public static void iteratorThroughRandomAccess(List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
endTime = System.currentTimeMillis();
System.out.println("iteractorRandomAccess interval: " + (endTime - startTime));
}

public static void iteratorThroughFor2(List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for (Object object : list) {

}
endTime = System.currentTimeMillis();
System.out.println("iteractorfor interval: " + (endTime - startTime));
}

public static void iteratorThroughFor(List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
iterator.next();
}
endTime = System.currentTimeMillis();
System.out.println("iteractor interval: " + (endTime - startTime));
}
>}

结果如下

1
2
3
>iteractor interval: 7
>iteractorfor2 interval: 4
>iteractorRandomAccess interval: 4

我测试了几次,感觉java8里面随机访问和foreach访问比较快,不过相差也不是太大。不过数据量大的话,还是推介使用随机访问。

toArray 异常

有俩种方法可以将list转换成数组,具体的方法如下

1
2
3
4

Object[] toArray()

<T> T[] toArray(T[] arr)

有时调用toArray()函数会抛出“java.lang.ClassCastException”异常,这时就需要调用toArray(T[] contents)来解决这中异常。

toArray() 会抛出异常是因为toArray()返回的是Object[]数组,将 Object[] 转换为其它类型(类如,将Object[]转换为的Integer [])则会抛出“java.lang.ClassCastException”异常,因为Java不支持向下转型。具体的可以参考前面ArrayList.java的源码介绍部分的toArray()。
解决该问题的办法是调用 <T> T[] toArray(T[] contents)

ArrayList基本使用

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
>import java.util.*;

>public class ArrayListTest {

public static void main(String[] args) {

// 创建ArrayList
ArrayList list = new ArrayList();

// 将“”
list.add("1");
list.add("2");
list.add("3");
list.add("4");
// 将下面的元素添加到第1个位置
list.add(0, "5");

// 获取第1个元素
System.out.println("the first element is: "+ list.get(0));
// 删除“3”
list.remove("3");
// 获取ArrayList的大小
System.out.println("Arraylist size=: "+ list.size());
// 判断list中是否包含"3"
System.out.println("ArrayList contains 3 is: "+ list.contains(3));
// 设置第2个元素为10
list.set(1, "10");

// 通过Iterator遍历ArrayList
for(Iterator iter = list.iterator(); iter.hasNext(); ) {
System.out.println("next is: "+ iter.next());
}

// 将ArrayList转换为数组
String[] arr = (String[])list.toArray(new String[0]);
for (String str:arr)
System.out.println("str: "+ str);

// 清空ArrayList
list.clear();
// 判断ArrayList是否为空
System.out.println("ArrayList is empty: "+ list.isEmpty());
}
>}

前面已经讲解了Arraylist,但是留下了一个知识点没有说明,Iterator的fail-fast机制。本篇博客主要讲解一下这个机制。

Fail-fast简介

下面是Java官方在Arraylist的介绍中一段描述fail-fast的话

1
if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

大概的意思就是说:当通过list返回一个Iterator或者ListIterator对象之后,除了Iterator和ListIterator对象自己对集合的修改之外的其他任何修改集合的行为,都会报ConcurrentModificationException。因此在面对并发修改时,迭代器会快速而干净的失败,也就是不对List对象的状态有任何的修改,同时不会在未来某个不确定的时间产生任意的风险和不确定的行为。

用下面通俗的话来说就是:

fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

Fail-fast示例

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
/**
* java集合中Fast-Fail的测试程序。
*
* fast-fail事件产生的条件:
* 当多个线程对Collection进行操作时,若其中某一个线程通过iterator去遍历集合时,
* 该集合的内容被其他线程所改变;则会抛出ConcurrentModificationException异常。
* fast-fail解决办法:通过util.concurrent集合包下的相应类去处理,则不会产生fast-fail事件。
*
* 本例中,分别测试ArrayList和CopyOnWriteArrayList这两种情况。
* ArrayList会产生fast-fail事件,而CopyOnWriteArrayList不会产生fast-fail事件。
*
* 1. 使用ArrayList时,会产生fast-fail事件,抛出ConcurrentModificationException异常;
* 定义如下:
* private static List<String list = new ArrayList<String();
* 2. 使用时CopyOnWriteArrayList,不会产生fast-fail事件;
* 定义如下:
* private static List<String list = new CopyOnWriteArrayList<String();
*/
public class FailFastTest {

private static List<String list = new ArrayList<();

public static void main(String[] args) {

// 启动两个线程对list进行操作!
new ThreadOne().start();
new ThreadTwo().start();
}

public static void printAll() {
System.out.println(Thread.currentThread().getName());
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}

/**
* 向list中依次添加0,1,2,3,4,5,每添加一个数之后,就通过printAll()遍历整个list
*/
private static class ThreadOne extends Thread {
public void run() {
int i = 0;
while (i < 6) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
}

/**
* 向list中依次添加10,11,12,13,14,15,每添加一个数之后,就通过printAll()遍历整个list
*/
private static class ThreadTwo extends Thread {
public void run() {
int i = 10;
while (i < 16) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
}
}

运行上面代码,会抛出异常:java.util.ConcurrentModificationException!即,产生fail-fast事件!

结果说明
FastFailTest中通过 new ThreadOne().start() 和 new ThreadTwo().start() 同时启动两个线程去操作list。
ThreadOne线程:向list中依次添加0,1,2,3,4,5。每添加一个数之后,就通过printAll()遍历整个list。
ThreadTwo线程:向list中依次添加10,11,12,13,14,15。每添加一个数之后,就通过printAll()遍历整个list。

当某一个线程遍历list的过程中,list的内容被另外一个线程所改变了;就会抛出ConcurrentModificationException异常,产生fail-fast事件。

Fail-fast原理

产生fail-fast事件,是通过抛出ConcurrentModificationException异常来触发的。那么,ArrayList是如何抛出ConcurrentModificationException异常的呢?

在前一篇博客里面我们已经分析了ArrayList的源码。下面是摘录

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
//实现list通用的迭代器
private class Itr implements Iterator<E {
int cursor; //当前迭代器指向的位置
int lastRet; //指向next操作的前一个元素
int expectedModCount; //当前list的大小

private Itr() {
this.cursor = 0; //开始的位置
this.lastRet = -1; //没有开始进行迭代操作
this.expectedModCount = AbstractList.this.modCount; //外部类设置list的长度
}

//利用当前指向的游标和外部类list的大小来判断是否还有下一个元素
public boolean hasNext() {
return this.cursor != AbstractList.this.size();
}

//返回元素
public E next() {
//首先利用fail-fast来检测list是否改变,
this.checkForComodification();

try {
int var1 = this.cursor;
Object var2 = AbstractList.this.get(var1);
this.lastRet = var1;
this.cursor = var1 + 1;
return var2;
} catch (IndexOutOfBoundsException var3) { //捕获超出边界异常
//判断是否是当前list被并发修改,如果是,则抛出此异常
this.checkForComodification();

throw new NoSuchElementException(); //正常抛出异常
}
}

//实现Iterator 接口 的remove方法
public void remove() {
if (this.lastRet < 0) {
throw new IllegalStateException();
} else {
this.checkForComodification();

try {
AbstractList.this.remove(this.lastRet);
if (this.lastRet < this.cursor) { //当期那游标减1
--this.cursor;
}

this.lastRet = -1;
//重新设置list的大小
this.expectedModCount = AbstractList.this.modCount;
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}

// 判断list是否已经修改,
// 如果修改则抛出并发修改错误
final void checkForComodification() {
if (AbstractList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
}

从中,我们可以发现在调用 next() 和 remove()时,都会执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。

要搞明白 fail-fast机制,我们就要需要理解什么时候“modCount 不等于 expectedModCount”!从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,需要考证的就是modCount何时会被修改。

上一篇博客里面分析ArrayList源码时,里面只要涉及到修改ArrayList的地点,都会对modCount加1,这些修改包括add(),remove(),clear()等等。

接下来,我们再系统的梳理一下fail-fast是怎么产生的。步骤如下:

  1. 新建了一个ArrayList,名称为arrayList
  2. 向arrayList中添加内容。
  3. 新建一个“线程a,并在线程a中通过Iterator反复的读取arrayList的值
  4. 新建一个线程b,在线程b中删除arrayList中的一个节点A
  5. 这时,就会产生有趣的事件了。在某一时刻,线程a创建了arrayList的Iterator。此时节点A仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。在线程a在遍历arrayList过程中的某一时刻,线程b执行了,并且线程b删除了arrayList中的节点A。线程b执行remove()进行删除操作时,在remove()中执行了modCount++,此时modCount变成了N+1!线程a接着遍历,当它执行到next()函数时,调用checkForComodification()比较expectedModCount和modCount的大小;而expectedModCount=NmodCount=N+1,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。

至此,我们就完全了解了fail-fast是如何产生的!
即,当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

解决fail-fast方式

抛出这种错误的原因是因为出现并发操作,主要由以下俩种方式来解决

  1. 我们可以使用Collections.synchronizedList包装一下Arraylist,对每个方法都是用Synchronize进行同步
  2. 使用JUC中线程安全的集合类,类如CopyOnWriteList来替代ArrayList

Fail-fast存在的问题

从前面可以看到,Fail-fast产生的原因是expectedModCount和modCount不相等,就会抛出异常,什么时间会修改modCount,在删除或者新增的时候会修改,如果是更新呢,根据ArrayList的源码可以看出,是不会对modCount进行修改,因此如果遍历数据的时候,只是修改数据,则不会出现Fail-fast的问题,因此会带来数据不一致的问题,但是ArrayList本身也没有保证一致性。

概要

首先,对Collection进行说明。下面是Collection的继承关系的主要类图,(这里只列举了抽象类和接口,来说明Collection的整体结构)

collection 整体架构

Collection是一个接口,它主要的俩个分支是:ListSet

ListSet都是接口,他们继承于Collection。

List是有序队列,这里所说的有序队列是指,按照什么顺序添加,可以以相同的顺序取出来,List中可以有相同的元素。

Set可以和数学概念中的集合类比,Set中不允许有重复的元素。

由上面的类图可以看出,首先抽象出了一个AbstractCollection抽象类,实现了Collection接口中的大部分方法,方便代码的编写。接着AbstractList和AbstractSet继承了AbstractCollection。其中AbstraList实现了List中特有的一写方法,AbstractSet实现了对于Set来说通用的一些方法。这样做可以方便子类的编写。这很好的体现了面向对象的思想。

另外要说明的一点是,Collection继承了Iterable接口,所以每一个实现了Collection接口的类中,都可以使用迭代器遍历。List系列的集合实现了一个特有的ListIterator接口,在这个接口中增加了一些添加,删除等方法。

通过上面的介绍可以发现,Collection体系中的集合并不是特别的复杂,所以只要细心的理一下,还是很容易理解和使用的。java8新增加了default方法,还有流(Stream),我们关注的是集合的使用,因此在这里就省略这部分内容。

阅读全文 »

集合框架是Java提供的工具包,包含了一些常用的数据结构:集合、链表、队列、栈、数组、映射等,方便我们日常开发。Java集合工具包位置是java.util.*。

Java集合主要可以划分为4个部分:列表(List)、集合(Set)、映射(Map)、工具类。

下图是java集合的整体框架图:

collection

集合

java集合具体的官方描述在这

阅读全文 »

本篇文章将要介绍的是ConcurrentHashMap,你可以将这个理解为线程安全的HashMap,但是他不是想HashTable一样对所有的方法都是用Synchronize来保证线程安全。至于是如何保证线程安全的,下文会对此进行详细的介绍,也是我们研究的主要点之一。

下文将按照下面几个方面来进行介绍

  1. 重要成员属性的介绍
  2. put 方法实现并发添加
  3. remove方法法实现并发删除
  4. get方法的实现
  5. 其他的一些方法的简单介绍
  6. 使用注意点
阅读全文 »

Inherited

前面也介绍过,这个注解作用是注解其他注解,让注解具有继承的特性。但是继承是有条件的。
下面是摘自javaDOC上的一段话

1
Note that this meta-annotation type has no effect if the annotated type is used to annotate anything other than a class. Note also that this meta-annotation only causes annotations to be inherited from superclasses; annotations on implemented interfaces have no effect.

中文意思如下

1
请注意,如果带有继承特性的注解使用在其他元素而不是类上,则注解的继承特性是没有效果的。还要注意,这个元注解只会从父类继承注解;实现接口上的注解没有作用。

具体示例

标记在类上的继承情况

自定义一个带有继承特性的注解

1
2
3
4
5
@Inherited // 可以被继承
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) // 可以通过反射读取注解
public @interface BatchExec {
String value();
}
阅读全文 »

什么是注解

注解的定义:一种元数据形式,可以添加到Java代码中。因此从定义可以看出注解仅仅是在源码中添加的一些标记,即元数据,对代码的运行没有任何影响。那注解能用来干嘛呢?一种技术的出现肯定是有其存在意义。注解本身是表示元数据,但是java为注解提供了单独的注解处理器,因此我们可以通过注解处理器来实现一些有意义的功能,比如编译检查,如@Override,生成文档@Document当然不止这些简单的功能,Spring中利用注解实现了AOP,事务等功能。

注解分类

我按照使用方式,将注解分成三类,元注解(meta-annoation),系统注解,自定义注解。
下面先介绍元注解和系统注解。

阅读全文 »

Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本比较高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为重量级锁。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了一系列的锁优化措施。本篇将最浙西优化措施进行介绍。

如果对Synchronized底层原理不清楚的可以参考Synchronize实现原理,对于Mutex Lock不太清楚的可以参考Mutex lock for Linux Thread Synchronization,同时这篇浅谈Mutex (Lock)文章详细介绍了mutex时间消耗。

优化主要分为俩大类,一类是编译优化,一类是运行时优化。

阅读全文 »