文章目录
一、Java集合概述
- 数组元素既可以是基本类型的值,也可以是对象;
- 集合里只能保存对象;
- Collection 集合体系的继承树:
Set:无序集合,元素不可重复;
List:有序集合,元素可以重复;
- Map 体系的继承树:
Map 保存的数据都是以 key-value 对的形式保存;
- Java 的集合可以分为三大类:
(1)Set 集合;只能根据元素本身来访问,元素不能重复;
(2)List 集合;可以根据索引来访问;
(3)Map 集合;可以根据 key 来访问 value;
二、Collection 和 Iterator 接口
- collection 集合的操作:
public class CollectionTest {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("zzz");
c.add(6);
c.remove(6);
System.out.println("c集合的元素个数为:" + c.size());
System.out.println("是否包含字符 zzz :" + c.contains("zzz"));
Collection books = new HashSet();
books.add("book1");
books.add("book2");
System.out.println("c集合是否完全包含books集合:" + c.containsAll(books));
}
}
1. 使用 Lambda 表达式遍历集合
2. 使用 java8 增强的 Iterator 遍历集合元素
- Iterator 接口是 Java 集合框架的成员,Iterator 主要用于遍历 Collection 中的元素,Iterator 对象也被称为迭代器;
- 注意:Iterator 仅用于遍历集合,本身并不提供盛装对象的能力;
public class IteratorTest {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("dd");
c.add("520");
c.add("zz");
c.add("725");
//获取集合对应的迭代器
Iterator it = c.iterator();
while (it.hasNext()){
//it.next() 返回值为为 Object 类型,需要强制类型转换
String s = (String) it.next();
System.out.println(s);
//删除 Iterator 中的元素
// if (s.equals("725")){
// it.remove();
// }
s = "haha";
}
System.out.println(c);
}
}
//输出结果为:
dd
520
zz
725
[dd, 520, zz, 725]
- 当使用 Iterator 对集合进行迭代时,Iterator 并不是把集合元素本身传给了迭代变量,而是把集合元素的值传递给了迭代变量,所以修改迭代变量的值对集合元素本身并没有任何影响;
- 当使用 it.remove(); 语句进行删除时,集合元素会发生改变;
3. 使用 Lambda 表达式遍历 Iterator
Iterator it = books.Iterator();
it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
4. 使用 foreach 循环遍历集合
for (Object obj : books){
String book = (String) obj;
System.out.println(book);
}
5. 使用 java8 新增的 Predicate 操作集合
6. 使用 java8 新增的 Stream 集合
- Stream 是一个通用流接口;IntStream、LongStream 、DoubleStream 则代表元素类型值为 int、long、double 的流;
- 对于大部分聚集方法而言,每个 Stream 只能执行一次;
public class IntStreamTest {
public static void main(String[] args) {
IntStream s = IntStream.builder()
.add(5)
.add(2)
.add(0)
.add(7)
.build();
//下面调用聚集方法的代码 每次只能执行一次
// System.out.println("max = " + s.max().getAsInt());
System.out.println("min = " + s.min().getAsInt());
// System.out.println("sum = " + s.sum());
// System.out.println("average = " + s.average());
}
}
- 上面的注释部分代码,每次只能调用一行;
三、Set 集合
- Set 集合通常不能记住元素的添加顺序;Set 集合与Collection 基本相同,没有提供任何额外的方法;
- Set 集合不允许包含相同的元素;
1. HashSet 类
-
HashSet 是 Set 接口的典型实现;HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能;
-
HashSet 具有以下特性:
①不能保证元素的排列顺序;(无序)
②HashSet 不是同步的,如果多个线程同时访问一个 HashSet,假设有两个或两个以上线程同时修改了 HashSet 集合,则必须通过代码来保证其同步;
③集合元素值可以是 null; -
HashSet 判断两个元素相等的标准:两个对象通过 equals() 方法比较相等,并且两个对象的 hashCode() 方法返回值也相等;
-
注意:当把一个对象放入 HashSet 时,如果需要重写该对象对应类的 equals() 方法,则也应该重写其 hashCode() 方法;规则是:如果两个对象 equals() 方法返回值为 true,这两个对象的 hashCode 值也应该相同;
-
重写 hashCode() 方法的基本规则:
①在程序运行过程中,同一个对象多次调用 hashCode() 方法应该返回相同的值;
②当两个对象通过 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法应返回相等的值;
③对象中用作 equals() 方法比较标准的实例变量,都应该用于计算 hashCode 值; -
重写 hashCode() 方法的一般步骤:
①
②
class R{
int count;
public R(int count){
this.count = count;
}
public String toString(){
return "R[count: " + count + "]";
}
public boolean equals(Object obj){
if (this == obj){
return true;
}
if (obj != null && obj.getClass() == R.class){
R r = (R) obj;
return this.count == r.count;
}
return false;
}
public int hashCode(){
return this.count;
}
}
public class HashCodeTest {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new R(5));
hs.add(new R(-3));
hs.add(new R(9));
hs.add(new R(-2));
System.out.println(hs);
//取出第一个元素
Iterator it = hs.iterator();
R first = (R) it.next();
//为第一个元素的 count 实例变量赋值
first.count = -3;
System.out.println(hs);
//删除 count 值为-3 的R对象
hs.remove(new R(-3));
System.out.println(hs);
//判断是否包含
System.out.println("是否包含-3:" + hs.contains(new R(-3)));
System.out.println("是否包含-2:" + hs.contains(new R(-2)));
System.out.println("是否包含5:" + hs.contains(new R(5)));
}
}
//输出结果:
[R[count: -2], R[count: -3], R[count: 5], R[count: 9]]
[R[count: -3], R[count: -3], R[count: 5], R[count: 9]]
[R[count: -3], R[count: 5], R[count: 9]]
是否包含-3:false
是否包含-2:false
是否包含5:true
- 由以上代码可以发现:当程序把可变对象添加到 HashSet 中之后,尽量不要去修改集合元素中参与计算 hashCode()、equals() 的实例变量,否则将会导致 HashSet 无法正确操作这些集合元素;
2. LinkedHashSet 类
- LinkedHashSet 是 HashSet 的子类,但它使用链表维护元素的次序;当遍历 LinkedHashSet 集合里的元素时,将会按照元素的添加顺序来访问集合里的元素(有序);
- 由于 LinkedHashSet 需要维护元素的顺序,所以性能略低于 HashSet;
- LinkedHashSet 具有唯一性和有序性,元素的顺序总是与添加顺序一致;
3. TreeSet 类
- TreeSet 是 SortSet 接口的实现类,可以确保元素处于排序状态;
- 元素的顺序不是根据添加顺序,而是根据元素实际值的大小进行排序的;
- TreeSet 采用红黑树的数据结构来存储集合元素;
- TreeSet 支持两种排序方法:自然排序和定制排序,默认情况下,用自然排序;
(1)自然排序
- TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序排列;
- compareTo(Object obj) 方法返回一个整数值,实现 Comparable 接口的类必须实现该方法;
- 向 TreeSet 中添加的应该是同一个类的对象,否则会引发 ClassCastException 异常;
- 对于 TreeSet 而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较是否返回0,为0则说明相等;
- 注意:当需要把一个对象放入 TreeSet 中,重写该对象对应类的 equals() 方法时,应遵循以下规则:如果两个对象通过 equals() 方法比较返回 true 时,这两个对象通过 compareTo(Object obj) 方法比较应返回 0;
- 一但改变了 TreeSet 集合里可变元素的实例变量,当再试图删除该对象时,TreeSet 也会删除失败;可以删除没有被修改的实例变量、且不与其他被修改的实例变量的对象重复的对象;
(2)定制排序
- 实现定制排序,则需要在创建 TreeSet 集合对象时,提供一个 Comparator 对象与该 TreeSet 集合关联;
4. EnumSet 类
- EnumSet 中所有元素都必须是指定枚举类型的枚举值,该枚举值在创建 EnumSet 时显式或隐式的指定;
- EnumSet 的集合元素是有序的,EnumSet 以枚举值在 Enum 类内的定义顺序来决定集合元素的顺序;
- EnumSet 在内部以位向量的形式存储,这种存储形式非常紧凑、高效,因此占内存很小,而且运行效率很好,执行批量操作速度非常快;
- EnumSet 集合不允许加入 null 元素;
5.各 Set 实现类的性能分析
- HashSet 和 TreeSet 是 Set 的两个典型实现,HashSet 的性能总是比 TreeSet 好(因为 TreeSet 需要红黑树算法来维护);只有当需要一个保持排序的 Set 时,才应使用 TreeSet ,否则都应使用 HashSet;
- HashSet 还有一个子类:LinkedHashSet ,对于普通插入删除,LinkedHashSet 比 HashSet 慢一点(需要维护链表),但遍历 LinkedHashSet 较快;
- EnumSet 是所有Set中性能最好的,但只能保存枚举值;
- HashSet 、TreeSet、EnumSet 都是线程不安全的;如果有多个线程同时访问同一个 Set 集合,并且有超过一个线程修改了该 Set 集合,则必须手动保证该 Set 集合的同步性;
四、List 集合
1. Java8 改进的 List 接口和 LIstIterator 接口
-
LIst 集合 代表一个元素有序、可重复的集合,集合中的每个元素都有其对应的顺序索引;
-
List 是 Collection 接口的子接口;
-
与 Set 集合相比,List 增加了根据索引来插入、替换和删除集合元素的方法;
-
List 判断两个对象相等,只要通过 equals() 方法比较返回 true 即可;
-
List 还额外提供了一个 listIterator() 方法,该方法返回一个 ListIterator 对象;ListIterator 接口继承了 Iterator 接口,提供了专门操作 List 的方法;
-
ListIterator 与普通的 Iterator 相比,ListIterator 增加了向前迭代的功能( Iterator 只有向后迭代);ListIterator 还可向 List 集合中添加元素( Iterator 只能删除元素);
2. ArrayList 和 Vector 实现类
- ArrayList 和 Vector 作为 List 类的两个典型实现,支持前面介绍的 List 接口的功能;
- ArrayList 和 Vector 类都是基于数组实现的 List 类,所以 ArrayList 和 Vector 类封装了一个动态的、允许再分配的 Object[ ] 数组;
- ArrayList 和 Vector 对象使用 initialCapacity 参数来设置数组的长度;
- 如果开始就知道 ArrayList 或 Vector 集合需要保存的元素个数,则可以在创建它们时就指定 initialCapacity 的大小;如果创建空的 ArrayList 或 Vector 集合时不指定 initialCapacity 参数,则 Object[] 的默认大小为 10;
- Vector 是一个比较老的集合,缺点也较多,尽量少使用 Vector 实现类;
- ArrayList 和 Vector 的区别:
①ArrayList 是线程不安全的;后面提供一个 Collections 工具类,可以将一个 ArrayList 变成线程安全的;
②Vector 是线程安全的;性能较低;不推荐使用;
注意:Vector 还提供了一个 Stack 子类,它用于模拟“栈”这种数据结构。但它也是比较早的集合类,线程安全、性能较差,不推荐使用;如果程序需要使用“栈”这种数据结构,则可以考虑使用 ArrayDeque;
3. 固定长度的 List
- Arrays 是一个操作数组的工具类,该工具类里提供了 asList(Object… a) 方法,该方法可以把一个数组或指定个数的对象转换成一个 List 集合;
- 这个集合是 Arrays 的内部类 ArrayList 的实例;
List li = Arrays.asList("dd", "zz");
//获取li的实现类,输出类型:Arrays$ArrayList
System.out.println(li.getClass());
//使用方法引用遍历集合元素
li.forEach(System.out::plintln);
五、Queue 集合
- Queue 用于模拟“队列”;
- Queue 有一个 PriorityQueue 实现类,还有一个 Deque 接口;
- Deque 代表一个“双端队列”,因此它的实现类既可以当成队列使用,也可以当成栈使用;
1. PriorityQueue 实现类
- PriorityQueue 是一个比较标准的队列实现类,但它保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序;
- PriorityQueue 不允许插入 null 值;PriorityQueue 的元素有两种排序方式:自然排序和定制排序;(PriorityQueue 队列对元素的要求与 TreeSet 对元素的要求基本一致);
2. Deque 接口与 ArrayDeque 实现类
- Deque 接口 是 Queue 接口的子接口,它代表一个双端队列,也可以当成栈来使用;
- Deque 接口提供了一个典型的实现类:ArrayDeque,它是一个基于数组实现的双端队列;创建 Deque 时可以指定一个 numElements ,用于指定数组长度,不指定则默认16;
3. LinkedList 实现类
- LinkedList 类是 List 接口的实现类,还实现了 Deque 接口;
- LinkedList 可以作为 List集合、双端队列、栈使用;
- LinkedList 与 ArrayList、ArrayQueue 的实现机制完全不同;
①ArrayList、ArrayQueue 内部以数组的形式来保存,随机访问集合元素时性能较好;
②LinkedList 内部以链表的形式来保存集合中的元素,随机访问性能较差,但插入、删除性能较好; - 对于所有的内部基于数组的集合实现,例如 ArrayList、ArrayQueue 等,使用随机访问的性能比使用 Iterator 的性能好;
4. 各种线性表的性能分析
- List 是线性表接口,
ArrayList 是基于数组的线性表,
LinkedList 是基于链的线性表,
Queue 代表队列,
Deque 代表了双端队列; - 总体来说,ArrayList 的性能比 LinkedList 性能好;
- 使用 List 集合:
①如果需要遍历 List 集合元素,
ArrayList、Vector 应采用随机访问方法(get)遍历集合;
LinkedList 应采用迭代器(Iterator)来遍历集合;
②如果需要经常执行插入、删除操作,可考虑使用 LinkedList;
③如果有多个线程需要同时访问 List 集合中的元素,可考虑使用 Collections 将集合包装成线程安全的集合;
六、Java8 增强的 Map 集合
- Map 用于保存具有映射关系的数据,key 和 value 之间存在一对一关系,key 不能重复;
- Map 里 key 集和 Set 集合里元素的存储形式很像,value 集和 List 集合很像;
- Map 有时也被称为字典,或关联数组;
- Map 中包括一个内部类 Entry,该类封装了一个 key-valuea 对;
1. Java8 为 Map 新增的方法
2. Java8 改进的 HashMap 和 Hashtable 实现类
- HashMap 和 Hashtable 都是 Map 接口的典型实现类,他们之间的关系完全类似于 ArrayList 和 Vector;Hashtable 是一个很早的 Map 实现类;
- HashMap 和 Hashtable 区别:
①HashMap 在存在 key 冲突时依然具有较好的性能;
②Hashtable 是一个线程安全的 Map 实现,HashMap 不安全,HashMap 性能较高;
③Hashtable 不允许使用 null 作为 key 和 value,HashMap 可以使用 null 作为 key 或 value; - HashMap 和 Hashtable 不能保证 key-value 对的顺序;
- HashMap 和 Hashtable 判断两个 value 相等的标准:两个对象通过 equals() 方法比较返回 true 即可;
3. LinkedHashMap 实现类
- HashSet 有一个 LinkedHashSet 子类,HashMap 也有一个 LinkedHashMap 子类;LinkedHashMap 使用双向链表来维护 key-value 对的次序,迭代顺序与 key-value 对的插入顺序一致;
4. 使用 Properties 读写属性文件
- Properties 类是 Hashtable 类的子类,Properties 类可以把 Map 对象和属性文件关联起来,从而将 Map 对象中的 key-value 对写入属性文件中;
- Properties 相当于一个 key 和 value 都是 String 类型的 Map;
- Properties 可以把 key-value 对以 XML 文件的形式保存起来,也可以从 XML 文件中加载 key-value 对;
5. SortedMap 接口和 TreeMap 实现类
- SortedMap 接口也有一个 TreeMap 实现类;
- TreeMap 就是一个红黑树数据结构,每个 key-value 对即作为红黑树的一个节点,存储时需要根据 key 对节点进行排序;
- TreeMap 中判断两个 key 相等的标准是:两个 key 通过 compareTo() 方法返回0;
6. WeakHashMap 实现类
7. IdentityHashMap 实现类
8. EnumMap 实现类
9. 各 Map 实现类的性能分析
- HashMap 通常比 Hashtable 快;
- TreeMap 通常比 HashMap、Hashtable 慢(尤其在插入、删除 key-value 对时更慢);
- TreeMap 中的 key-value 对总是处于有序状态;
- 对于一般的应用场景,应多考虑使用 HashMap,因为 HashMap 正是为快速查询设计的;
七、HashSet 和 HashMap 的性能选项
八、操作集合的工具类:Collections
- Java 提供了一个操作 Set、List、Map 集合的工具类:Collections;
1. 排序操作
2. 查找、替换操作
3. 同步控制
- Collections 类中提供了多个 synchronizedXxx() 方法,该方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题;