1. ArrayList概述:
ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。
2. ArrayList的实现:
对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析ArrayList的源代码:
1) 底层使用数组实现:
2) 构造方法:
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
3) 存储:
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。下面我们一一讲解:
4) 读取:
5) 删除:
ArrayList提供了根据下标或者指定对象两种方式的删除功能。如下:
注意:从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向左移动一个位置。
6) 调整数组容量:
从上面介绍的向ArrayList中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。代码如下:
7) Fail-Fast机制:
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。具体介绍请参考我之前的文章深入Java集合学习系列:HashMap的实现原理中的Fail-Fast机制。
8) 关于其他的一些方法的实现都很简单易懂,读者可参照API文档和源代码,一看便知,这里就不再多说。
/*****************************************************************************************************************************/
ArrayList是List里面使用率最高的。
package collection.lession7;
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.Collection;
import
java.util.Iterator;
import
java.util.List;
/**
* 老紫竹JAVA提高教程(7)-认识List列表之ArrayList<br>
*
* @author 老紫竹 JAVA世纪网(java2000.net)
*
*/
public
class
Lession7 {
public
static
void
main(String[] args) {
testNormal();
testSpecial();
// 一个最常见的错误
testForProblem();
}
public
static
void
testNormal() {
// -------------------------------------------------------
// 声明一个列表
// 允许放入任何数据
// -------------------------------------------------------
ArrayList list =
new
ArrayList();
// 放入整数
// 当然你用 new Integer(1)也可以
list.add(
1
);
// 放入字符串
list.add(
"abc"
);
// 放入浮点数
list.add(
new
Float(
1.11
));
// add会将数据保存到列表的尾部
showList(list);
// 1, abc, 1.11]
// 下面我们在列表的头部增加数据
list.add(
0
,
2
);
list.add(
0
,
"bcd"
);
list.add(
0
,
new
Double(
2.34
));
// 列表可以指定插入的位置
// 0 是头部第一个位置,所以数据都逐个放到最前面了
showList(list);
// [2.34, bcd, 2, 1, abc, 1.11]
// 下面我们插入到我们希望的任何位置
// 当然不能越界,(0 到 list.size()-1)范围内才可以
list.add(
1
,
3
);
list.add(
4
,
"xyz"
);
// 数据被放到了正确的位置
showList(list);
// [2.34, 3, bcd, 2, xyz, 1, abc, 1.11]
// -------------------------------------------------------
// 我们有了数据,我们来测试读取数据
// -------------------------------------------------------
// 我们可以通过指定索引的位置,来拿到我们希望的数据
System.out.println(list.get(
0
));
// 2.34
System.out.println(list.get(
4
));
// xyz
// -------------------------------------------------------
// 测试是否存在某个数据
// -------------------------------------------------------
System.out.println(list.contains(
"xyz"
));
// true
// 测试是否包含一组数据
Collection c =
new
ArrayList();
c.add(
1
);
c.add(
2
);
System.out.println(list.containsAll(c));
// true
c.add(
3
);
c.add(
4
);
// containsAll_1234=false
System.out.println(list.containsAll(c));
// false
// -------------------------------------------------------
// 查找某个数据所在的索引位置
// 如果不存在,返回-1
// -------------------------------------------------------
System.out.println(list.indexOf(
3
));
// 1
System.out.println(list.indexOf(
"xyz"
));
// 4
System.out.println(list.indexOf(
"abcd"
));
// -1
// -------------------------------------------------------
// 测试删除数据
// 请注意,
// 如果你使用整数(int)数字,则默认调用的是remove(int index);
// 如果你用 long,则会调用 remove(Object obj);
// 所以如果你要删除整数,请使用 remove(new Integer(int));
// -------------------------------------------------------
// 删除索引为1的数据
list.remove(
1
);
// 索引为1的数据被干掉了
showList(list);
// [2.34, bcd, 2, xyz, 1, abc, 1.11]
// 删除数字1 和字符串 abc
list.remove(
new
Integer(
1
));
list.remove(
"xyz"
);
showList(list);
// [2.34, bcd, 2, abc, 1.11]
// -------------------------------------------------------
// 迭代器的使用
// -------------------------------------------------------
Iterator it = list.iterator();
while
(it.hasNext()) {
System.out.print(it.next() +
" "
);
// 2.34 bcd 2 abc 1.11
}
System.out.println();
// -------------------------------------------------------
// 转化为数组
// -------------------------------------------------------
Object[] objs = list.toArray();
for
(Object obj : objs) {
System.out.print(obj +
" "
);
// 2.34 bcd 2 abc 1.11
}
System.out.println();
}
public
static
void
testSpecial() {
// -------------------------------------------------------
// 测试重复和null
// -------------------------------------------------------
//
List<Integer> list =
new
ArrayList<Integer>();
list.add(
123
);
list.add(
456
);
list.add(
123
);
list.add(
456
);
// 数据允许重复
showList(list);
// [123, 456, 123, 456]
list.add(
null
);
list.add(
789
);
list.add(
null
);
list.add(
999
);
// 允许放入多个null
showList(list);
// [123, 456, 123, 456, null, 789, null, 999]
// -------------------------------------------------------
// 测试一下查找最后一次出现的位置
// -------------------------------------------------------
System.out.println(list.indexOf(
123
));
// 0
System.out.println(list.lastIndexOf(
123
));
// 2
// -------------------------------------------------------
// 转化为数组
// 记得要转化为Inerger.
// -------------------------------------------------------
Integer[] nums = (Integer[]) list.toArray(
new
Integer[
0
]);
// 注意数据里面有null,所以循环变量不要用int 要用Integer
for
(Integer num : nums) {
System.out.print(num +
" "
);
// 123 456 123 456 null 789 null 999
}
System.out.println();
}
public
static
void
testForProblem() {
// 一些朋友在向循环里向列表增加对象的时候
// 经常忘记初始化,造成最终加入的都是同一个对象
List<MyObject> list =
new
ArrayList<MyObject>();
MyObject obj =
new
MyObject();
for
(
int
i =
1
; i <=
5
; i++) {
obj.setName(
"Name"
+ i);
list.add(obj);
}
// 里面的数据都是最后一个
showList(list);
// [Name5, Name5, Name5, Name5, Name5]
// 正确的做法
List<MyObject> list2 =
new
ArrayList<MyObject>();
MyObject obj2 =
null
;
for
(
int
i =
1
; i <=
5
; i++) {
obj2 =
new
MyObject();
obj2.setName(
"Name"
+ i);
list2.add(obj2);
}
// 里面的数据都是最后一个
showList(list2);
// [Name1, Name2, Name3, Name4, Name5]
}
/**
* 显示List里面的数据。
*
* @param list
*/
private
static
void
showList(List list) {
System.out.println(Arrays.toString(list.toArray()));
}
}
class
MyObject {
private
String name;
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
/**
* 重写toString方法,输出name
*/
public
String toString() {
return
name;
}
}
|
输出结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[
1
, abc,
1.11
]
[
2.34
, bcd,
2
,
1
, abc,
1.11
]
[
2.34
,
3
, bcd,
2
, xyz,
1
, abc,
1.11
]
2.34
xyz
true
true
false
1
4
-
1
[
2.34
, bcd,
2
, xyz,
1
, abc,
1.11
]
[
2.34
, bcd,
2
, abc,
1.11
]
2.34
bcd
2
abc
1.11
2.34
bcd
2
abc
1.11
[
123
,
456
,
123
,
456
]
[
123
,
456
,
123
,
456
,
null
,
789
,
null
,
999
]
0
2
123
456
123
456
null
789
null
999
[Name5, Name5, Name5, Name5, Name5]
[Name1, Name2, Name3, Name4, Name5]
|