概述
JDK1.2时候,Java的设计者们对Java存储数据的容器进行了大刀阔斧的改革。推出了庞大的Java集合框架体系。
Java集合框架拥有非常庞大的容器体系,通过不同的实现对不同场景的元素存取达到高性能。
Java集合拥有2大常用分支,一支是Collection体系的存储单个元素的集合,另一分支存储着Key——Value健值对形式的Map体系。
Collection体系中,又拥有3大常用分支分为List,Queue,Set。
而Map体系中,分为无序Key-Value和有序Key-Value形式2大类。
集合的子类非常多,本文将介绍常用的集合实现类。
下图展示了集合的继承关系,通过掌握继承关系图,可以更加容易学习集合框架。
Java常用集合类架构图
Java Map 集合类架构图
Collection 接口
整个Collection接口是集合的根接口,Collection用来描述一组对象,描述集合这个概念
同时Collection也提供了基本操作集合的方法
一些 Collection 允许有重复的元素,而另一些则不允许。一些 Collection 是有序的,而另一些则是无序的。
Collection子接口分为常用的三大分支:List(有序集合)Queue(队列)Set(无序不可重复)
Collection 规范了集合的常用方法,包含添加,删除,是否包含,迭代器等常用功能。
List 接口
List接口用来描述一组有序集合,集合记录元素插入顺序。
List接口允许用户通过索引对元素精准控制,包括插入,修改,访问元素。
List接口通常允许元素重复。
List 接口在Collection接口基础上,提供了更多通过索引操作元素
ArrayList
List 接口的可变数组的实现类,其拥有父接口List有序集合,索引控制,重复元素的等特点。
允许插入null所在内的元素。
此类还提供一些方法来操作内部用来存储列表的数组的大小。
在添加元素前,ArrayList使用 ensureCapacity 操作来增加容量。
// 1. 创建ArrayList对象
ArrayList list = new ArrayList<>();
//2. 添加设置元素的值
list.add("hello");
list.add(1, "world");
list.add("java");
//3. 删除元素
list.remove("world");
list.remove(0);
//4. 修改元素的值
list.set(0,"Hello");
//5. 查询相关
list.get(0);
list.forEach((String e) -> {
System.out.println("e = " + e);
});
Vector
Vector 是JDK1.0提供的集合类。
Vector 的底层实现与ArrayList相同,并提供了ArrayList相同的功能。
Vector 所有方法都通过增加synchronized来实现线程安全。
Vector 是线程安全的,但同时效率相比ArrayList较低,在JDK1.2中,ArrayList是Vector的替代方法,推荐使用ArrayList。
LinkedList
List接口的双向链表实现。
其拥有父接口List有序集合,索引控制,重复元素的等特点。
LinkedList 在操作列表,头元素,尾元素 (get、remove、insert)时,提供了统一的命名方式,所以LinekedList 这些操作允许将链表当作栈、队列或双端队列使用。
LinekedList所有操作都是按照双向链表方式执行的,当按索引获取/修改/插入元素时,将从开头或结尾遍历链表(从靠近指定索引的一端)。
LinkedList 由于双向链表的特性,以及实现了Queue,和Dequeue接口,将而外提供栈,和双端队列的特点。
6. 作为队列使用的LinkedList
LinkedList queue = new LinkedList<>();
// 向队列头插入元素
queue.offer(" world");
// 默认 linked last
queue.offer("hello");
queue.offerFirst("java");
//queue = [java, world, hello]
System.out.println("queue = " + queue);
// 获取 队列 头、尾元素,但不移除
String v = queue.peek();
v = queue.peekFirst();
v = queue.peekLast();
// 获取 队列 头、尾元素,并且移除出队列
v = queue.poll();
v = queue.pollLast();
作为栈使用的LinkedList
LinkedList stack = new LinkedList<>();
stack.push("hello");
stack.push("world");
stack.pop();
stack.pop();
Queue & Deque
Queue队列维护着一个先进先出(First In First Out)数据结构
Queue接口与List、Set同一级别,都是继承了Collection接口。
Deque一个线性 Collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写。
此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。
LinkedList使用双向链表实现了基本的队列操作
Set
Set集合在Collection集合基础上 提供 不包含重复元素的功能。
Set不包含满足 e1.equals(e2)或(el==e2) 的元素,并且最多包含一个 null 元素。
Set集合一般不维护着元素的插入顺序。
同时Set集合不提供对索引精准控制。
Set实现类底层采用与之名称对应Map实现类实现,通过将元素存储在Map的Key中,Value采用全局静态Object的实例。
Set接口提供着和Collection相同的方法列表规定。
HashSet
Set集合基于哈希表实现类---对应HashMap。
HashSet内部使用HashMap的key来存储元素,value使用静态的Object对象实例。
HashSet提供了Set接口的所有方法的实现,在判断是否包含某元素和删除插入元素上,拥有卓越的性能。
LinkedHashSet
LinkedHashSet 可预知迭代顺序Set接口的实现类,基于哈希表+双向链表实现---对应LinkedHashMap。
LinkedHashSet 继承自 HashSet。
通过调用父类接收3个参数构造方法,在内部初始化LinkedHashMap存储元素,所以其实现代码写在父类里,但父类HashMap此初始化方法是 默认 修饰符,只有包访问权限,所以只能子类初始化。
与 HashSet 的不同之外在于,后者在哈希表的基础上,增加了一个维护所有条目的双向链表,此链表定义了迭代顺序,即插入元素顺序。
TreeSet
TreeSet 可排序的Set接口的实现类---对应TreeMap。
TreeSet 内部使用了TreeMap(红黑树)实现排序功能,同样将元素作为TreeMap的Key。
TreeSet 默认使用元素的自然排序,元素需要实现Comparable接口。
TreeSet 同时允许,通过传递比较器(Comparator)实现类,自定义排序。
// TreeSet 自然排序
TreeSet set = new TreeSet<>();
set.add(10);
set.add(7);
set.add(2);
set.add(9);
// set = [2, 7, 9, 10]
System.out.println("set = " + set);
// 实现 Comparator 接口,自定义排序
TreeSet set = new TreeSet<>(new Comparator() {
@Override
public int compare(Student o1, Student o2) {
int num = o1.getAge() - o2.getAge();
num = num == 0 ? o1.getName().compareTo(o2.getName()) : num;
return num;
}
});
Map
Map接口是Map体系的根接口。
Map特点是将键映射到值的对象,每个键最多只能映射到一个值
Map中同样相同的健只能存储一个。
HashMap
HashMap是Map接口基于哈希表的实现类。
其拥有Map接口的所有实现方法。
HashMap不保证映射的顺序,即插入元素的顺序。
关于哈希表的实现,请参考。
LinkedHashMap
LinkedHashMap是Map接口 哈希表和链表的实现。
其拥有Map接口所有的方法,并具有可预知的迭代顺序。
后者维护着一个运行于所有条目的双重链表。此链表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序。
Hashtable
Hashtable 与 HashMap同样是基于哈希表实现类。
Hashtable 是JDK1.0推出的键值映射集合,在JDK1.2后此类就被改进以实现 Map 接口。
Hashtable不保证映射的顺序,即插入元素的顺序。
与HashMap不同的是,Hashtable所有方法都是线程同步的。
Properties
Properties 类是Hashtable的子类,其同样是键值映射集合。
不同的是 Properties类表示了一个持久的属性集。
Properties 可以配合IO流,从流中读取或将数据写入流中。
Properties 中每个键及其对应值都是一个字符串。
使用 Properties 时,不建议使用 put 和 putAll 方法,因为他们允许插入String之外的值。
Java开发中,常常将一些配置信息存放在.properties文件中,而使用Properties类的load方法进行加载。
7. 使用Properties存储键值对,并存储到文件中。
Properties p = new Properties();
p.setProperty("name","李洛克");
p.setProperty("age","18");
// 将 properties 保存到 classPath 路径下
String classPath = ClassLoader.getSystemResource("").getPath();
OutputStream os = new FileOutputStream(classPath + "test.properties");
p.store(os,"注释");
// 所谓classpath 就是指使用 javac -d classpath 的路径
// 在idea中就是 project compiler output path
// maven 工程会自动将 标记为 resources 文件夹下文件拷贝到classpath路径下
读取文件中properties的内容
// 从 classPath 下加载 properties文件
InputStream is = ClassLoader.getSystemResourceAsStream("test.properties");
Properties p = new Properties();
p.load(is);
System.out.println(p.getProperty("name"));
System.out.println(p.getProperty("age"));
TreeMap
可自然排序和自定义规则排序的Map集合。
TreeMap 是基于红黑树(Red-Black tree)的 NavigableMap 接口的实现。
使用TreeMap实现排序方法
// 自定义元素排序,按照key的长度 进行排序
Map map = new TreeMap<>(new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
map.put("a",1);
map.put("bbbb",1);
map.put("ccc",1);
// map = {a=1, ccc=1, bbbb=1}
System.out.println("map = " + map);