集合简介
数组长度不能变,保存的元素得是同种类型,扩容麻烦,由此引出集合概念
- 可以动态保存任意多个对象,使用比较方便
- 提供了一系列方便的操作对象的方法:add,remove,set,get...,代码简洁
集合框架
-
Collection(单列集合)
-
List
-
ArrayList
-
LinkedList
-
Vector
-
-
Set
-
HashSet
- LinkedHashSet
-
TreeSet
-
-
-
Map(双列集合,存键值对)
-
HashMap
- LinkedHashMap
-
Hashtable
- Properties
-
TreeMap
-
Collection接口和常用方法
List和Map都能用
- Collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的实现类,有些是有序的(List),有些不是有序的(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
- add() 添加单个元素
- remove() 删除指定元素,可以是索引也可以是对象,索引返回对象,对象返回布尔值
- contains() 查找元素是否存在
- size() 返回元素个数
- isEmpty() 判断集合是否为空
- clear() 清空集合
- addAll() 传入一个实现了Collection接口的集合,将其中所有元素都添加进去
- containsAll() 传入一个实现了Collection接口的集合,判断其中所有元素是否都存在
- removeAll() 传入一个实现了Collection接口的集合,将该集合中有的所有元素删除
遍历方法
- Iterator迭代器
Collection<Integer> col = new ArrayList<>();
col.add(1);
col.add(2);
col.add(3);
// 先得到集合对应的的iterator迭代器
Iterator<Integer> iterator = col.iterator();
// 使用while进行遍历
while (iterator.hasNext()) {// 判断是否还有数据
Integer next = iterator.next();// 返回下一个元素
System.out.println(next);
}
// 如果想再次遍历则需要重置迭代器,Iterator<Integer> iterator = col.iterator();再写一遍
- 增强for遍历
Collection<Integer> col = new ArrayList<>();
col.add(1);
col.add(2);
col.add(3);
for (Integer integer : col) {
System.out.println(integer);
}
// 增强for底层其实也是迭代器
List接口和常用方法
- List集合类中元素有序(即添加顺序和取出顺序一致),且可重复
- List集合中的每个元素都有其对应的顺序索引,即支特索引
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
- List可以加入多个null,ArrayList和Vector底层是数组实现的,LinkedList底层实现了双向链表和双端队列,LinkedList线程不安全,它每个节点(Node对象)有prev,next,item三个属性,prev指向前一个,next指向后一个,实现双向链表,正因此它的添加和删除效率高
- List的实现子类中ArrayList基本等同于Vector,区别是ArrayList线程不安全,但效率高,Vector线程安全,ArrayList若是无参构造器则初始大小为0,加元素时扩容,第一次从0变成10,往后依次增加1.5倍,若是指定大小的构造器,则初始大小为指定,下一次为1.5倍,Vector若是无参构造器则初始大小为10,往后依次增加2倍,若是指定大小的构造器,则初始大小为指定,下一次为2倍
-
add(index,e) 在index位置插入元素e
-
addAll(index,list) 在index位置开始将list集合中所有元素全部插入进来
-
get(index) 返回此集合中指定位置的元素
-
indexOf(e) 返回此集合中指定元素e的第一次出现的索引
-
lastIndexOf(e) 返回此集合中指定元素e的最后一次出现的索引
-
remove(index) 删除指定位置的元素,并返回该元素
-
set(index,e) 设置index位置为元素e,相当于替换已有的
-
subList(start,end) 截取并返回一段子集合[start,end)
List除了迭代器和增强for,还可以用普通for循环遍历
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
ArrayList和LinkedList的比较
| 底层结构 | 增删效率 | 改查效率 | |
|---|---|---|---|
| ArrayList | 可变数组 | 较低,数组扩容 | 较高 |
| LinkedList | 双向列表 | 较高,通过链表追加 | 较低 |
如何选择ArrayList和LinkedList
- 如果我们改查的操作多,选择ArrayList
- 如果我们增删的操作多,选择LinkedList
- 一般来说,在程序中80%-90%都是查询,因此大部分情况下会选择ArrayList
- 在一个项目中,根据业务灵活选择,也可能是一个模块使用的是ArrayList,另外一个模块是LinkedList
Set接口和常用方法
-
无序,添加和取出的顺序不一致,没有索引,虽然乱序,但是固定
-
不允许重复元素,所以最多包含一个null
-
它的实现类HashSet底层实际上是HashMap,而HashMap底层是数组+链表+红黑树,HashSet里的元素的存放位置取决于hash值,所以乱序,HashSet的添加元素的过程是先得到元素的hash值,根据hash值得到其在数组中的存放位置,假如其他元素的hash值和该元素的一样,就意味着要放到数组的同一个位置上,先用equals方法(可重写)进行比较,若他俩相同,就会放弃加入,若他俩不同,这时就会将该元素连接到已有元素后面,如果这条链表的长度大于8,且数组的长度大于64,就会树化,变成红黑树
-
它的实现类LinkedHashSet继承了HashSet,底层实际上是LinkedHashMap,底层维护了一个数组+双向链表LinkedHashMap第一次添加时直接将数组扩容到16,放的节点类型是LinkedHashMap$Entry,LinkedHashSet里的元素的存放位置取决于hash值,存放过程和HashSet一样,但是它用链表将乱序的元素连接起来,使得元素看起来像是顺序插入的一样
-
可以重写要添加元素类上的equals和HashCode使得Set不能加入相同属性值的元素(默认可以,因为虽然属性值相同,但实际上是不同对象)
-
TreeSet最大特点是可以排序,它的底层是TreeMap,当用默认构造器创建TreeSet时仍然是无序的,但可以往构造器里传入一个比较器,并指定排序规则
TreeSet<Integer> treeSet = new TreeSet<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); treeSet.add(2); treeSet.add(3); treeSet.add(1); for (Integer integer : treeSet) { System.out.println(integer); }
add() 添加一个元素
remove() 删除一个元素
遍历只能用迭代器或者增强for,因为没有索引,所以没有get()方法,所以不能用普通for循环遍历
Map接口和常用方法
HashMap,Hashtable和TreeMap都能用
- Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,原因和HashSet一样,value可以重复
- Map的key可以为null,value也可以为null,但是注意key为null,只能有一个而value为null,可以多个
- 常用String类作为Map的key
- key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
- 一对key-value实际放在了一个HashMap$Node对象中,但也可以简单理解为一对key-value就是一个Entry,为了方便程序员的遍历,还会创建entrySet集合,该集合存放的元素的类型为Entry,而每个Entry对象就有k和v,当把HashMap$Node对象存放到entrySet,就方便我们遍历,因为Map.Entry提供了重要方法getKey()和getValue()
- HashMap是Map实现类中使用频率最高的,线程不安全,扩容机制见上面的HashSet,和它一样,创建对象时加载因子默认为0.75,一对k-v新加进来具体放哪要看key的值,key重复直接替换新旧value,第1次添加,则需要扩容table数组的容量为16,临界值为12,以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推,在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
- Hashtable的键值都不能为null,线程安全,使用方法和HashMap类似
- Properties是Hashtable的子类,使用方法和Hashtable类似,Properties还可以用于从xxx.properties配置文件中,加载数据到Properties类对象,并进行读取和修改
- TreeMap,用默认构造器创建的TreeMap是无序的,但可以往构造器里传入一个比较器,并指定排序规则,和上面的TreeSet用法一样
put(key,value) 添加键值对
remove(key) 根据键删除键值对
get(key) 根据键获取值
size() 获取map元素个数
isEmpty() 判断map是否为空
clear() 清空map
containsKey() 查找键是否存在,返回布尔类型
keySet() 获取所有的键
entrySet() 获取所有的键值对关系
values() 获取所有的值
遍历方法
-
先取出所有的key,再通过key取出对应的value
HashMap<Integer, Book> hashMap = new HashMap<>(); hashMap.put(1, new Book(1001, "nb")); hashMap.put(2, new Book(1002, "sb")); hashMap.put(3, new Book(1003, "db")); // 取出所有的key Set keySet = hashMap.keySet(); // 1. 增强for for (Object key : keySet) { System.out.println(hashMap.get(key)); } // 2. 迭代器 Iterator iterator = keySet.iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println(hashMap.get(key)); } -
不关心key,直接取出values
HashMap<Integer, Book> hashMap = new HashMap<>(); hashMap.put(1, new Book(1001, "nb")); hashMap.put(2, new Book(1002, "sb")); hashMap.put(3, new Book(1003, "db")); // 直接取出values Collection<Book> values = hashMap.values(); // 1. 增强for for (Object value : values) { System.out.println(value); } // 2. 迭代器 Iterator<Book> iterator = values.iterator(); while (iterator.hasNext()) { Object value = iterator.next(); System.out.println(value); } -
通过EntrySet来获取k-v对
HashMap<Integer, Book> hashMap = new HashMap<>(); hashMap.put(1, new Book(1001, "nb")); hashMap.put(2, new Book(1002, "sb")); hashMap.put(3, new Book(1003, "db")); Set<Map.Entry<Integer, Book>> entries = hashMap.entrySet(); // 1. 增强for for (Object entry : entries) { Map.Entry m = (Map.Entry) entry; System.out.println(m.getValue()); } // 2. 迭代器 Iterator<Map.Entry<Integer, Book>> iterator = entries.iterator(); while (iterator.hasNext()) { Object entry = iterator.next(); Map.Entry m = (Map.Entry) entry; System.out.println(m.getValue()); }
HashMap对比Hashtable
| 版本 | 线程安全 | 效率 | 允许null键null值 | |
|---|---|---|---|---|
| HashMap | 1.2 | 不安全 | 高 | 可以 |
| Hashtable | 1.0 | 安全 | 较低 | 不可以 |
Collections工具类
Collections是一个操作Set,List和Map等集合的工具类,其中提供了一系列静态的方法对集合元素进行排序,查询,修改等操作
reverse(List) 反转List集合中的顺序
shuffle(List) 对List集合中的元素进行随机排序
sort(List) 根据元素的自然顺序对指定List集合元素按升序排序
sort(List,Comparator) 根据指定的Comparator产生的顺序对List集合元素进行排序
swap(List, i, j) 将指定List集合中的i处元素和j处元素进行交换
max(List) 根据元素的自然顺序,返回给定集合中的最大元素
max(List,Comparator) 根据Comparator指定的顺序,返回给定集合中的最大元素
min(List) 根据元素的自然顺序,返回给定集合中的最小元素
min(List,Comparator) 根据Comparator指定的顺序,返回给定集合中的最小元素
frequency(List, Object) 返回指定集合中指定元素的出现次数
copy(a, b) 将b集合中的内容复制到a集合中,a和b的集合大小要一样
replace(List, a, b) 将集合List中的a全替换为b,返回布尔
举例
// 静态方法直接调用即可
Collections.reverse(list);
怎样选合适的集合
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下
- 先判断存储的类型(一组对象或一组键值对)
- 一组对象:Collection接口
- 允许重复:List 增删多:LinkedList[底层维护了一个双向链表] 改查多:ArrayList[底层维护Object类型的可变数组]
- 不允许重复:Set 无序:HashSet[底层是HashMap,维护了一个哈希表即数组+链表+红黑树] 排序:TreeSet 插入和取出顺序一致:LinkedHashSet 维护数组+双向链表
- 一组键值对:Map
- 键无序:HashMap[底层是哈希表,jdk7:数组+链表,jdk8:数组+链表+红黑树]
- 键排序:TreeMap
- 键插入和取出顺序一致:LinkedHashMap
- 读取文件Properties