转载请保留原文链接: http://dashidan.com/article/java/basic/15.html
Java集合类是java提供的工具包,包含了常用的数据结构:集合、链表、队列、栈、映射等.Java集合类主要可以划分为4个部分:List列表、Set集合、Map映射、工具类(Iterator迭代器、Enumeration枚举类、Arrays和Collections).
① 常用集合类接口
常用集合类继承关系如下所示:
Collection
|-List
|-LinkedList
|-ArrayList
|-Vector
|-Set
|-HashSet
|-Queue
Map
|-Hashtable
|-HashMap
1.Collection
接口
Collection
是最基本的集合接口,一个Collection
代表一组Object,即Collection
的元素(Elements).由Collection
接口派生的两个接口是List
和Set
.List
允许有相同元素, Set
不允许有相同元素.
实现Collection接口的类都必须提供两个标准的构造函数:
* 无参数的构造函数用于创建一个空的Collection.
* 有一个参数的构造函数用于创建一个新的Collection
对象,这个对象传入的Collection
有相同的元素. 允许用户复制一个Collection
.
2.List
接口
List
是有序
的Collection
. 使用此接口能够精确的控制每个元素插入的位置.能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组.
除了具有Collection
接口必备的iterator()
方法外, List
还提供一个listIterator()
方法, 返回一个ListIterator
接口,和标准的Iterator
接口相比,ListIterator
多了一些add()
之类的方法,允许添加,删除,设定元素,还能向前或向后遍历.
实现List接口的常用类有LinkedList
,ArrayList
,Vector
和Stack
.
和Set
不同, List
允许有相同的元素.
必须小心操作可变对象(Mutable Object)
如果一个`Set`中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题.
4.Queue
接口
可以实现先进先出的队列.LinkedList
同样实现了Queue接口.PriorityQueue
用来创建自然排序的优先级队列.
5.Map
接口
Map
提供key
到value
的映射. 一个Map
中不能包含相同的key
,每个key
只能映射一个value
.Map
的内容可以被当作一组key
集合,一组value
集合,或者一组key-value
映射.
Map
没有继承Collection
接口.
② 常用集合类
1.LinkedList
类
LinkedList
实现了List
接口,允许null元素.提供额外的get
,remove
,insert
方法在LinkedList
的首部或尾部.这些操作使LinkedList
可被用作栈
(stack),队列
(queue)或双向队列
(deque).
LinkedList
没有同步方法:
如果多个线程同时访问一个List, 则必须自己实现访问同步.一种解决方法是在创建List时构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList(...));.
2.ArrayList
类
ArrayList
实现了变长数组.它允许所有元素,包括null.每个ArrayList
实例都有一个容量(Capacity
), 即用于存储元素的数组的大小.这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义.
当需要插入大量元素时,在插入前可以调用ensureCapacity
方法来增加ArrayList
的容量以提高插入效率.
和LinkedList一样,ArrayList也是非同步的(unsynchronized).
ArrayList
添加删除对象:
/** ArrayList添加对象*/
ArrayList<Integer> intList = new ArrayList<>();
System.out.println("ArrayList 长度: " + intList.size());
for (int i = 0; i < 10; i++) {
intList.add(i);
}
System.out.println("ArrayList 长度: " + intList.size());
/** ArrayList删除对象*/
intList.remove(5);
System.out.println("ArrayList 长度: " + intList.size());
`ArrayList`遍历: * ArrayList 遍历方式 1 增强for循环, 获得值, 无法获得索引
for (Integer intNum : arrayList) {
System.out.println(intNum);
}
* ArrayList 遍历方式 2普通for循环, 索引和值都能获得
for (int i = 0; i < arrayList.size(); i++) {
int num = arrayList.get(i);
System.out.println(num);
}
* ArrayList 遍历方式 3 迭代器 Itertor, 无法获得索引
Iterator<Integer> it = arrayList.iterator();
while (it.hasNext()) {
int num = it.next();
System.out.println(num);
}
3.Vector
类
Vector
非常类似ArrayList
,但Vector
是线程同步的.
由Vector
创建的Iterator
,虽然和ArrayList
创建的Iterator
是同一接口,但由于Vector
是线程同步的,当一个Iterator
被创建而且正在被使用,另一个线程改变了Vector
的状态(例如,添加或删除了一些元素),这时调用Iterator
的方法时将抛出ConcurrentModificationException
,因此必须捕获该异常.
代码示例:
/** Vector*/
Vector<Integer> vector = new Vector<>();
System.out.println("Vector 长度: " + vector.size());
for (int i = 0; i < 5; i++) {
vector.add(i);
}
System.out.println("Vector 长度: " + vector.size());
/** Vectort 删除对象*/
vector.remove(3);
System.out.println("Vector 长度: " + vector.size());
/** Vectort 遍历方式 1 增强for循环*/
for (Integer intNum : vector) {
System.out.println(intNum);
}
Vector添加删除对象和遍历方式和ArrayList类似, 参考ArrayList的代码实现.
###4.`Stack`类 `Stack`继承自`Vector`,实现一个`后进先出`的堆栈. `stack`的常用方法: * `push`: 放入栈 * `pop`: 出栈 * `peek`:得到栈顶的元素 * `empty`:测试堆栈是否为空 * `search`:检测一个元素在堆栈中的位置 代码示例:/** Stack*/
Stack<Integer> stack = new Stack<>();
System.out.println("Stack 长度: " + stack.size());
for (int i = 0; i < 5; i++) {
/** 入栈*/
stack.push(i);
}
System.out.println("Stack 长度: " + stack.size());
/** Stack pop 出栈*/
stack.pop();
System.out.println("Stack 长度: " + stack.size());
/** Stack peek 得到栈顶的元素*/
int peekNum = stack.peek();
System.out.println("Stack peekNum: " + peekNum);
/** Stack search 检测一个元素在堆栈中的位置*/
int pos = stack.search(3);
System.out.println("Stack search pos: " + pos);
/** Stack 遍历方式 1 增强for循环*/
for (Integer anIntList : stack) {
int num = anIntList;
System.out.println(num);
}
Stack
遍历方式和ArrayList
类似, 参考ArrayList
的代码实现.
5.HashMap类
HashMap
是最常用的Map
,不是线程安全的.它根据键的HashCode
值存储数据,根据键可以直接获取它的值,具有很快的访问速度.遍历时,取得数据的顺序是完全随机
的.
由于键对象不可以重复, 所以HashMap
最多只允许一条记录的键为空(null),允许多条记录的值为空(null).
由于作为key的对象将通过计算其散列函数
来确定与之对应的value
的位置, 因此任何作为key
的对象都必须实现hashCode
和equals
方法.
hashCode
和equals
方法继承自根类Object
.
用自定义的类当作key
按照散列函数
的定义,如果两个对象相同,即obj1.equals(obj2)=true
,则它们的hashCode
必须相同,但如果两个对象不同,则它们的hashCode``不一定不同
.如果两个不同对象的hashCode相同,这种现象称为冲突
. 冲突会导致操作哈希表的时间开销增大.意想不到的结果(期待的get方法返回null
).要避免这种问题,只需要牢记一条:要同时覆写equals
方法和hashCode
方法, 而不要只写其中一个.
代码示例:
HashMap
添加和删除对象:
HashMap<Integer, Integer> hashMap = new HashMap<>();
System.out.println("HashMap 长度: " + hashMap.size());
for (int i = 0; i < 5; i++) {
/** HashMap 添加对象*/
hashMap.put(i, 100 + i);
}
System.out.println("HashMap 长度: " + hashMap.size());
/** HashMap 删除对象*/
hashMap.remove(2);
System.out.println("HashMap 长度: " + hashMap.size());
HashMap
遍历:
* HashMap 遍历方式 1 增强for循环遍历 Entry
推荐使用
直接拿到了key和value的对象, 省去了`get`数据的操作,效率最高.
System.out.println("HashMap 遍历方式 1 增强for循环遍历 Entry ");
for (Map.Entry<Integer, Integer> entry : hashMap.entrySet()) {
System.out.println("hashMap key: " + entry.getKey() + " hashMap value: " + entry.getValue());
}
- HashMap 遍历方式 2 通过迭代器遍历 key
Iterator<Integer> it0 = hashMap.keySet().iterator();
while (it0.hasNext()) {
int key = it0.next();
int value = hashMap.get(key);
System.out.println("key: " + key + " value: " + value);
}
- HashMap 遍历方式 3 通过 增强for循环遍历 key
for (Integer key : hashMap.keySet()) {
int value = hashMap.get(key);
System.out.println("key: " + key + " value: " + value);
}
- HashMap 遍历方式 4 通过迭代器遍历 values, 无法得到 key值
Iterator<Integer> it1 = hashMap.values().iterator();
while (it1.hasNext()) {
int value = it1.next();
System.out.println("value: " + value);
}
- HashMap 遍历方式 5 通过 增强for循环遍历 values, 无法得到 key值
for (Integer value : hashMap.values()) {
System.out.println("value: " + value);
}
6.Hashtable
类
Hashtable
继承Map
接口,实现一个key-value
映射的哈希表. 任何非空
(non-null)的对象都可作为key
或者value
. Hashtable
与HashMap
类似,是HashMap
的线程安全版,是线程同步的,即任一时刻只有一个线程能写Hashtable
,因此也导致了Hashtale在写入时会比较慢.
它继承自Dictionary
类,不同的是它不允许记录的键或者值为null
,同时效率较低.
建议使用ConcurrentHashMap替代
如果需要线程同步的map, 建议使用线程同步的ConcurrentHashMap类. 不建议使用这个类, 并没有示例代码.
7.WeakHashMap
类
WeakHashMap
是一种改进的HashMap
. 它对key
实行弱引用
,如果一个key
不再被外部所引用,那么该key
可以被Java的垃圾回收器
(GC)回收.
使用方式同HashMap
.
8. HashSet
类
HashSet
中元素是无序的(这个无序指的是数据的添加顺序和后来的排列顺序不同),而且元素不可重复.HashSet
的底层是数组,在增加和删除的时候由于运用的hashCode
的比较来确定添加元素的位置,不存在元素的偏移, 因此查询和删除和增加元素的效率都非常高.
但是HashSet
增删的高效率是通过花费大量的空间换来的, 因为空间越大,取余数相同的情况就越小.HashSet
这种算法会建立许多无用的空间.
使用HashSet类时如果发生冲突,就会出现遍历整个数组的情况,这样就使得效率非常的低.
/** 循环两次,放入重复的1-5*/
for (int i = 0; i < 5; i++) {
hashSet.add(i);
}
for (int i = 0; i < 5; i++) {
hashSet.add(i);
}
System.out.println("HashSet 长度: " + hashSet.size());
/** HashSet 遍历方式 1 增强for循环*/
for (Integer intNum : hashSet) {
System.out.println(intNum);
}
由于HashSet去重功能, 第二次循环插入重复数据时, HashSet中并没有加入新的数据, 长度依然是5. HashSet的遍历方式同ArrayList,参考ArrayList的遍历.
9.ConcurrentHashMap
类
线程同步的HashMap
,线程安全并且锁分离. ConcurrentHashMap
内部使用段(Segment
)来表示这些不同的部分,每个段其实就是一个小的hash table
,它们有自己的锁. 只要多个修改操作发生在不同的段上,它们就可以并发进行.
使用方式同HashMap
.
10.LinkedHashMap
类
有序的HashMap
, 非线程安全. LinkedHashMap
保存了记录的插入顺序,在用Iteraor
遍历LinkedHashMap
时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap
慢,有HashMap的全部特性.
使用方式同HashMap
.
11.TreeMap
类
TreeMap
实现SortMap
接口,能够把它保存的记录根据键排序,默认是按键值的升序排序(自然顺序),也可以指定排序的比较器. 当用Iterator
遍历TreeMap
时, 得到的记录是排过序的.
TreeMap
不允许key
值为空
,非线程同步.
使用方式同HashMap
.
11.总结
- 如果涉及到堆栈,队列等操作,应该考虑用
List
. 对于需要快速插入,删除元素,应该使用LinkedList
, 如果需要快速随机访问元素,应该使用ArrayList
. - 如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑
非同步
的类,其效率较高. - 如果多个线程可能同时操作一个类,应该使用同步的类.
- 要特别注意对哈希表的操作,作为
key
的对象要正确覆写equals
和hashCode
方法. - 尽量返回接口而非实际的类型, 如返回
List
而非ArrayList
, 这样如果以后需要将ArrayList
换成LinkedList
时, 代码接口不用改变.扩展性强.
③ 常用遍历集合方式
1. 迭代器Iterator
不论Collection
的实际类型如何,它都支持一个iterator()
的方法,该方法返回一个迭代器,使用该迭代器即可逐一访问Collection
中每一个元素.典型的用法如下:
// 获得一个迭代器
Iterator it = collection.iterator();
while(it.hasNext()) {
//获取下一个元素
Object obj = it.next();
}
2. foreach
增强for循环, JDK1.5之后提供的新功能, 可以输出数组或集合.
3. for循环
普通for循环遍历示例代码:
package com.dashidan.lesson13;
import java.util.*;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 13.Java集合类
*/
public class Demo1 {
public static void main(String[] args) {
testArrayList();
testVector();
testStack();
testHashMap();
testHashSet();
}
/**
* ArrayList基本操作
*/
public static void testArrayList() {
System.out.println("---ArrayList---");
/** ArrayList添加对象*/
ArrayList<Integer> arrayList = new ArrayList<>();
System.out.println("ArrayList 长度: " + arrayList.size());
for (int i = 0; i < 5; i++) {
arrayList.add(i);
}
System.out.println("ArrayList 长度: " + arrayList.size());
/** ArrayList删除对象*/
arrayList.remove(3);
System.out.println("ArrayList 长度: " + arrayList.size());
/** ArrayList 遍历方式 1 增强for循环, 获得值, 无法获得索引*/
for (Integer intNum : arrayList) {
System.out.println(intNum);
}
/** ArrayList 遍历方式 2普通for循环, 值和索引都能得到*/
for (int i = 0; i < arrayList.size(); i++) {
int num = arrayList.get(i);
System.out.println(num);
}
/** ArrayList 遍历方式 3 迭代器 Itertor, 获得值, 无法获得索引*/
Iterator<Integer> it = arrayList.iterator();
while (it.hasNext()) {
int num = it.next();
System.out.println(num);
}
}
/**
* Vector基本操作
*/
public static void testVector() {
System.out.println("---Vector---");
/** Vector*/
Vector<Integer> vector = new Vector<>();
System.out.println("Vector 长度: " + vector.size());
for (int i = 0; i < 5; i++) {
vector.add(i);
}
System.out.println("Vector 长度: " + vector.size());
/** Vectort 删除对象*/
vector.remove(3);
System.out.println("Vector 长度: " + vector.size());
/** Vectort 遍历方式 1 增强for循环, 获得值, 无法获得索引*/
for (Integer intNum : vector) {
System.out.println(intNum);
}
/** Vectort 遍历方式 2普通for循环, 值和索引都能得到*/
for (int i = 0; i < vector.size(); i++) {
int num = vector.get(i);
System.out.println(num);
}
/** Vectort 遍历方式 3 迭代器 Itertor, 获得值, 无法获得索引*/
Iterator<Integer> it = vector.iterator();
while (it.hasNext()) {
int num = it.next();
System.out.println(num);
}
}
/**
* Stack基本操作
*/
public static void testStack() {
System.out.println("---Stack---");
/** Stack*/
Stack<Integer> stack = new Stack<>();
System.out.println("Stack 长度: " + stack.size());
for (int i = 0; i < 5; i++) {
/** 入栈*/
stack.push(i);
}
System.out.println("Stack 长度: " + stack.size());
/** Stack pop 出栈*/
stack.pop();
System.out.println("Stack 长度: " + stack.size());
/** Stack peek 得到栈顶的元素*/
int peekNum = stack.peek();
System.out.println("Stack peekNum: " + peekNum);
/** Stack search 检测一个元素在堆栈中的位置*/
int pos = stack.search(3);
System.out.println("Stack search pos: " + pos);
/** Stack 遍历方式 1 增强for循环*/
for (Integer intNum : stack) {
System.out.println(intNum);
}
}
/**
* HashMap基本操作
*/
public static void testHashMap() {
System.out.println("---HashMap---");
HashMap<Integer, Integer> hashMap = new HashMap<>();
System.out.println("HashMap 长度: " + hashMap.size());
for (int i = 0; i < 5; i++) {
/** HashMap 添加对象*/
hashMap.put(i, 100 + i);
}
System.out.println("HashMap 长度: " + hashMap.size());
/** HashMap 删除对象*/
hashMap.remove(2);
System.out.println("HashMap 长度: " + hashMap.size());
/** HashMap 遍历方式 1 增强for循环遍历 Entry */
System.out.println("HashMap 遍历方式 1 增强for循环遍历 Entry ");
for (Map.Entry<Integer, Integer> entry : hashMap.entrySet()) {
System.out.println("hashMap key: " + entry.getKey() + " hashMap value: " + entry.getValue());
}
/** HashMap 遍历方式 2 通过迭代器遍历 key */
System.out.println("HashMap 遍历方式 2 通过迭代器遍历 key *");
Iterator<Integer> it0 = hashMap.keySet().iterator();
while (it0.hasNext()) {
int key = it0.next();
int value = hashMap.get(key);
System.out.println("key: " + key + " value: " + value);
}
/** HashMap 遍历方式 3 通过 增强for循环遍历 key*/
System.out.println("HashMap 遍历方式 3 通过 增强for循环遍历 key");
for (Integer key : hashMap.keySet()) {
int value = hashMap.get(key);
System.out.println("key: " + key + " value: " + value);
}
/** HashMap 遍历方式 4 通过迭代器遍历 values, 无法得到 key值 */
System.out.println("HashMap 遍历方式 4 通过迭代器遍历 values, 无法得到 key值 ");
Iterator<Integer> it1 = hashMap.values().iterator();
while (it1.hasNext()) {
int value = it1.next();
System.out.println("value: " + value);
}
/** HashMap 遍历方式 5 通过 增强for循环遍历 values, 无法得到 key值 */
System.out.println("HashMap 遍历方式 5 通过 增强for循环遍历 values, 无法得到 key值 ");
for (Integer value : hashMap.values()) {
System.out.println("value: " + value);
}
}
/**
* HashSet基本操作
*/
public static void testHashSet() {
System.out.println("---HashSet---");
HashSet<Integer> hashSet = new HashSet<>();
System.out.println("HashSet 长度: " + hashSet.size());
/** 循环两次,放入重复的1-5*/
for (int i = 0; i < 5; i++) {
hashSet.add(i);
}
for (int i = 0; i < 5; i++) {
hashSet.add(i);
}
System.out.println("HashSet 长度: " + hashSet.size());
/** HashSet 遍历方式 1 增强for循环*/
for (Integer intNum : hashSet) {
System.out.println(intNum);
}
}
}
输出:
---ArrayList---
ArrayList 长度: 0
ArrayList 长度: 5
ArrayList 长度: 4
0
1
2
4
0
1
2
4
0
1
2
4
---Vector---
Vector 长度: 0
Vector 长度: 5
Vector 长度: 4
0
1
2
4
0
1
2
4
0
1
2
4
---Stack---
Stack 长度: 0
Stack 长度: 5
Stack 长度: 4
Stack peekNum: 3
Stack search pos: 1
0
1
2
3
---HashMap---
HashMap 长度: 0
HashMap 长度: 5
HashMap 长度: 4
HashMap 遍历方式 1 增强for循环遍历 Entry
hashMap key: 0 hashMap value: 100
hashMap key: 1 hashMap value: 101
hashMap key: 3 hashMap value: 103
hashMap key: 4 hashMap value: 104
HashMap 遍历方式 2 通过迭代器遍历 key *
key: 0 value: 100
key: 1 value: 101
key: 3 value: 103
key: 4 value: 104
HashMap 遍历方式 3 通过 增强for循环遍历 key
key: 0 value: 100
key: 1 value: 101
key: 3 value: 103
key: 4 value: 104
HashMap 遍历方式 4 通过迭代器遍历 values, 无法得到 key值
value: 100
value: 101
value: 103
value: 104
HashMap 遍历方式 5 通过 增强for循环遍历 values, 无法得到 key值
value: 100
value: 101
value: 103
value: 104
---HashSet---
HashSet 长度: 0
HashSet 长度: 5
0
1
2
3
4
④ FAQ
集合类相关常见问题
1.集合类和数组的区别
- 数组(可以存储基本数据类型)是用来存现对象的一种容器, 数组的长度固定, 适合在对象数量固定时使用.
- 集合(只能存储对象,对象类型可以不一样)的长度可变,可在对象数量不固定时使用.
2.ArrayList
和LinkedList
区别
ArrayList
和LinkedList
在用法上没有区别,但是在功能上还是有区别的.LinkedList
经常用在增删操作较多而查询操作很少的情况下, 比如队列和堆栈.ArrayList
则相反.- ArrayList底层是Object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点.
而在LinkedList的底层是一种双向循环链表.在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置).最后一个节点的后指针指向第一个节点的前指针,形成一个循环.双向循环链表的查询效率低但是增删效率高. - 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针.
- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据.
ArrayList和LinkedList应用场景
若只对单条数据插入或删除,ArrayList的速度优于LinkedList. 但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据.
队列和栈
队列:先进先出的数据结构.栈:后进先出的数据结构.使用栈的时候, 不能提供非末尾元素出栈的方法.
3.HashTable
与HashMap
区别
Hashtable
是基于陈旧的Dictionary
类的,HashMap
是Java 1.2引入的Map
接口的一个实现Hashtable
是线程安全的,也就是说是同步的, 而HashMap
是线程序不安全的,不是同步的HashMap·允许存在一个为空(null)的
key,多个为空(null)的
value`.Hashtable
的key
和value
都不允许为空(null).
4.ArrayList
和Vector
区别
Vector
是线程同步的,所以它也是线程安全的. 而Arraylist
不是线程同步的. 如果不考虑到线程的安全因素,一般用Arraylist
效率比较高.- 如果集合中的元素的数目大于目前集合数组的长度时,
Vector
增长率为目前数组长度的100%
, 而Arraylist
增长率为目前数组长度的50%
. 在集合中使用数据量比较大的数据,用Vector
有一定的优势. - 如果查找一个指定位置的数据,
Vector
和Arraylist
使用的时间是相同的,都是O(1)
, 这个时候使用Vector
和Arraylist
都可以.
5.ArrayList
和Linklist
区别
ArrayList
和Vector
是采用数组方式存储数据, 此数组元素数大于实际存储的数据以便增加和插入元素, 都允许直接序号索引元素, 但是插入数据要设计到数组元素移动等内存操作, 所以索引数据快插入数据慢.Vector
由于使用了synchronized
方法(线程安全)所以性能上比ArrayList
要差.LinkedList
使用双向链表实现存储, 按序号索引数据需要进行向前或向后遍历, 但是插入数据时只需要记录本项的前后项即可,所以插入数度较快!- 如果移动一个指定位置的数据花费的时间为
O(n-i)
,n
为总长度,
这个时候就应该考虑到使用Linklist
, 因为它移动一个指定位置的数据所花费的时间为O(1)
, 而查询一个指定位置的数据时花费的时间为O(i)
.
6.HashMap
与TreeMap
区别
TreeMap
实现SortedMap
, 元素顺序固定.HashMap
没有实现该接口.HashMap
通过hashcode
对其内容进行快速查找,HashMap
中元素的排列顺序是不固定的, 而TreeMap
中所有的元素都保持着某种固定的顺序. 如果需要得到一个有序的结果应该使用TreeMap
.- 在
Map
中插入、删除和定位元素,HashMap
是最好的选择. 但如果要按自然顺序或自定义顺序遍历键, 那么TreeMap
会更好. - 使用
HashMap
要求添加的键类明确定义了hashCode()
和equals()
的实现. 这个TreeMap
没有调优选项, 因为该树总处于平衡状态.