Java集合工具包位于 java.util 包下,包含了很多常用的数据结构,如数组、链表、栈、队列、集合、哈希表等。
学习Java集合框架下大致可以分为如下五个部分:List列表、Set集合、Map映射、迭代器(Iterator、Enumeration)、工具类(Arrays、Collections)。 使用的是JDK1.8
集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
接口:即表示集合的抽象数据类型。接口提供了让我们对集合中所表示的内容进行单独操作的可能。
实现:也就是集合框架中接口的具体实现。实际它们就是那些可复用的数据结构。
算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。-- 来自百度百科
简化图:
说明:对于以上的框架图有如下几点说明
1、所有集合类都位于 java.util 包下。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
2、集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。
3、抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。
4、实现类:8个实现类(实线表示),对接口的具体实现。
5、Collection 接口是一组允许重复的对象。
6、Set 接口继承 Collection,集合元素不重复。
7、List 接口继承 Collection,允许重复,维护元素插入顺序。
8、Map接口是键-值对象,与Collection接口没有什么关系。
9、Set、List和Map可以看做集合的三大类:
1)List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
2)Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
3)Map集合中保存Key-value对形式的元素,不允许key对象重复,value对象可以重复,访问时根据每项元素的key来访问其value。
List集合代表一个有序集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。
List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
集合的存储原理
(1)集合中只能存储对象,JDK5之后会自动装箱,底层还是对象
(2)集合中存储的是对象引用
1、Vector类的原理
(1)底层是数组的封装,初始容量默认为10。
(2)扩容机制:capacityIncrement,可在初始化时指定,若不指定默认扩容2倍。扩容实质上是通过私有的方法grow(int minCapacity)来实现的。
(3)Vector是线程安全的,Vector里每个存储元素的方法都是synchronized修饰的,但是由于锁的存在,效率会比较低。
截取的一段源码
2、Vector类的方法等(重写了toString()方法)
boolean |
add(E e) 将指定的元素追加到此Vector的末尾。 |
void |
add(int index, E element) 在此Vector中的指定位置插入指定的元素。 |
boolean |
addAll(Collection extends E> c) 将指定集合中的所有元素追加到该向量的末尾,按照它们由指定集合的迭代器返回的顺序。 |
void |
clear() 从此Vector中删除所有元素。 |
boolean |
equals(Object o) 将指定的对象与此向量进行比较以获得相等性。 |
E |
get(int index) 返回此向量中指定位置的元素。 |
boolean |
isEmpty() 测试此矢量是否没有组件。 |
E |
remove(int index) 删除此向量中指定位置的元素。 |
boolean |
remove(Object o) 删除此向量中指定元素的第一个出现如果Vector不包含元素,则它不会更改。 |
boolean |
removeAll(Collection> c) 从此Vector中删除指定集合中包含的所有元素。 |
E |
set(int index, E element) 用指定的元素替换此Vector中指定位置的元素。 |
Object[] |
toArray() 以正确的顺序返回一个包含此Vector中所有元素的数组。 |
String |
toString() 返回此Vector的字符串表示形式,其中包含每个元素的String表示形式。 |
测试demo
public static void main(String[] args) {
// 添加方法
Vector vector = new Vector();
vector.add("A");
vector.add("B");
vector.add(1); // 自动装箱
Vector vector2 = new Vector();
vector2.add(null);
vector2.add(0,"B"); // 自动装箱
vector2.add("C");
vector2.addAll(vector);
System.out.println(vector2); // [B, null, C, A, B, 1]
// 修改
vector2.set(vector2.size() - 1, 222);
System.out.println(vector2); // [B, null, C, A, B, 222]
//查询
System.out.println(vector2.isEmpty()); // false
for (int i = 0; i < vector2.size(); i++) {
System.out.println(vector2.get(i));
}
Object[] arr = vector2.toArray();
System.out.println(Arrays.toString(arr)); // [B, null, C, A, B, 222]
// 删除
vector2.remove("B");
System.out.println(vector2); // [null, C, A, B, 222]
vector2.removeAll(vector);
System.out.println(vector2); // [null, C, 222]
}
Stack是集合中对数据结构栈的一种实现,Stack是继承自Vector类的,意味着它也是由数组实现的线程安全的,不考虑线程安全的情况下完全可以用LinkedList当做栈来使用。
Stack栈的原则:表示对象的先进后出(first-in-last-out, FILO)栈。与队列相反先进先出(FILO)
Stack提供5个额外的方法使得Vector得以被当作堆栈使用。当第一次创建堆栈时,它不包含任何项,即空栈。
1、Stack类的原理
同Vector类相同,底层调用的是Vector类,将数组的最后位置索引作为栈顶
2、Stack类的常用方法
boolean |
empty() 测试此堆栈是否为空。 |
E |
peek() 查看此堆栈顶部的对象,而不从堆栈中删除它。 |
E |
pop() 删除此堆栈顶部的对象,并将该对象作为此函数的值返回。 |
E |
push(E item) 将项目推送到此堆栈的顶部。 |
int |
search(Object o) 返回一个对象在此堆栈上的基于1的位置。 |
测试demo
public static void main(String[] args) {
Stack stack = new Stack();
System.out.println(stack.isEmpty());// true
// 压栈,
stack.push("A");
stack.push(null);
stack.push(1);
stack.push("B");
System.out.println(stack);// [A, null, 1, B] 栈低
// 查看栈顶对象,不从删除它。
System.out.println(stack.peek()); //B
System.out.println(stack.size()); //4
// 出栈,删除此栈顶部的对象
stack.pop();
System.out.println(stack);// [A, null, 1]
}
文档建议使用上图操作,方法都一样,注意栈顶的位置
1、ArrayList类的原理
(1)底层是数组的封装,初始容量默认为10。
(2)扩容机制:每次添加新元素的时候,ArrayList内部都会将当前的容量+1,得到目标容量minCapacity大小,然后和当前的数组容量对比,如果目标容量大于当前容量,就会调用grow方法进行扩容操作,每次扩容,大小都会增加1.5倍,然后会调用Arrays.copyOf将旧数组中的元素复制到新数组中,最后移动新数组的下标,将新元素进行赋值。
(3)ArrayList是线程不安全的,效率较高。如果需要多线程访问,文档推荐如下:
2、ArrayList类的常用方法(重写了toString()方法)具体查看api
boolean |
add(E e) 将指定的元素追加到此列表的末尾。 |
boolean |
contains(Object o) 如果此列表包含指定的元素,则返回 true 。 |
E |
get(int index) 返回此列表中指定位置的元素。 |
boolean |
isEmpty() 如果此列表不包含元素,则返回 true 。 |
Iterator |
iterator() 以正确的顺序返回该列表中的元素的迭代器。 |
E |
remove(int index) 删除该列表中指定位置的元素。 |
boolean |
remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。 |
int |
size() 返回此列表中的元素数。 |
void |
sort(Comparator super E> c) 使用提供的 |
List |
subList(int fromIndex, int toIndex) 返回此列表中指定的 |
测试demo
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("A");
arrayList.add(null);
arrayList.add(1);
arrayList.add("B");
System.out.println(arrayList); //[A, null, 1, B]
arrayList.remove(1);
System.out.println(arrayList.size()); // 3
}
1、LinkedList类的原理
(1)底层是基于双向链表实现的,即同时保有前驱节点和后驱节点,支持正向遍历和反向遍历。
(2)由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作。
(3)LinkedList是线程不安全的,效率较高。如果需要多线程访问,文档推荐如下:
截取的一段源码:
其中Node是个静态内部类,它会维护一个前节点信息和后节点信息,以及当前的业务数据。
2、LinkedList类的常用方法(重写了toString()方法)具体查看api
LinkedList类是双向链表、单向队列、双向队列、栈的实现类,所以它里面的方法比较多。
void |
addFirst(E e) 在该列表开头插入指定的元素。 |
void |
addLast(E e) 将指定的元素追加到此列表的末尾。 |
E |
getFirst() 返回此列表中的第一个元素。 |
E |
getLast() 返回此列表中的最后一个元素。 |
int |
indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。 |
E |
removeFirst() 从此列表中删除并返回第一个元素。 |
测试demo
public static void main(String[] args) {
LinkedList list1 = new LinkedList();
list1.addLast("b");
list1.addLast("c");
list1.addLast("5");
System.out.println(list1); // [b, c, 5]
list1.addFirst("a");
System.out.println(list1); // [a, b, c, 5]
System.out.println(list1.indexOf("c"));//2
System.out.println(list1.pop());//a
Object[] arr = list1.toArray(); //返回列表中所有元素的数组
System.out.println(Arrays.toString(arr)); // [b, c, 5]
}
1、共同的特点
1)允许元素重复
2)记录元素的先后添加顺序
2、性能分析
Vector类:底层采用数组结构算法,方法都使用了synchronized修饰,线程安全,但是性能较ArrayList较低。(打死都不用)
Arraylist类:底层采用数组结构算法,方法都没使用synchronized修饰,线程不安全,但是性能较Vector较高。
LinkedList类:底层采用双向链表结构算法,方法都没使用synchronized修饰,线程不安全。
对于多线程访问,推荐使用文档的参考。
数组结构算法和双向链表结构算法的性能分析:
数组结构算法:插入和删除操作速度低,查询和修改较快。底层数组扩容是一个既消耗时间又消耗空间的操作。
双向链表结构算法:插入和删除速度较快,查询和修改慢。双向链表占用的内存较高。
1、集合的迭代操作:
把集合中的元素一个一个遍历出来。
2、迭代的对象:
Iterator:迭代器对象,只能从上往下迭代。
boolean |
hasNext() 如果迭代具有更多元素,则返回 |
E |
next() 返回迭代中的下一个元素。 |
default void |
remove() 从底层集合中删除此迭代器返回的最后一个元素(可选操作)。 |
ListIterator: 是Iterator接口的子接口,支持双向迭代(从上往下或从下往上)。不常用,具体查看api
3、迭代遍历:
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
// 方式1 ; for循环
System.out.println("======for循环 =====");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i));
}
// 方式2 for-each 增强for循环
/**
* for(类型 变量 : 数组/Iterable实例){}
*/
System.out.println("=====for-each 增强for循环======");
for (Object ele : list) {
System.out.print(ele);
}
// 方式3 使用while 循环操作迭代器Iterator
System.out.println("=====使用while 循环操作迭代器Iterator======");
Iterator it = list.iterator();// 返回按适当顺序在列表的元素上进行迭代的迭代器
while (it.hasNext()) {
System.out.print(it.next());
}
// 方式4 使用for循环来操作迭代器(推荐使用)
/**
* foreach 操作Iterable的实例底层就是采用该方式
*/
System.out.println("====使用for循环来操作迭代器======");
for(Iterator it2 = list.iterator();it2.hasNext();){
System.out.print(it2.next());
}
}
4、foreach和迭代的删除操作
当迭代的时候,在当前线程A中会单独创建一个新的线程B。A线程负责继续迭代,B线程负责删除。B线程每次都会检查和A线程中的元素个数是否相同,如果不相同,则报错并发修改异常:ConcurrentModificationException。
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
System.out.println("=====for-each 增强for循环======");
for (Object ele : list) {
System.out.print(ele);
/*if("a".equals(ele)){ 报错
list.remove(ele);
}*/
}
System.out.println("====使用for循环来操作迭代器======");
for(Iterator it2 = list.iterator();it2.hasNext();){
Object ele = it2.next();
System.out.print(ele);
if("a".equals(ele)){
list.remove(ele); // 报错
}
}
}
抛异常分析可参考文章:https://blog.csdn.net/izard999/article/details/6708738
解决方案:在迭代集合时进行边迭代边删除操作的时候
不要使用Conlection接口中存在的remove(...)方法,该方法只会删除集合中的元素,不会删除迭代器中的元素。
所以,我们使用 Iterator接口中的remove() 方法。
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
for(Iterator it2 = list.iterator();it2.hasNext();){
Object ele = it2.next();
System.out.print(ele);
if("a".equals(ele)){
it2.remove(); // 使用Iterator接口中的remove() 方法。
}
}
System.out.print(list); // [b, c]
}
写的有点杂,主要是看了下源码和底层的实现,简单的把比较了性能和出错的地方记录下来,具体查看API。
ends~