【Java SE】十七、集合框架

在处理大数据时,数组就显得比较垃圾,这时候就需要使用 Java 提供的集合框架来处理数据。

Java 集合可分为 Collection 和 Map 两种体系:

  • Collection 接口:单列数据,定义了存取一组对象的方法的集合
    • List:元素有序、可重复的集合(ArrayList、LinkedList、Vector)
    • Set:元素无序、不重复的集合(HashSet、LinkedHashSet、TreeSet)
  • Map 接口:双列数据,保存具有映射关系“键值对”的集合(HashMap、LinkedHashMap、TreeMap、Hashtable、Properties)

Collection 接口

内置方法

因为 Collection 是接口,其实现类都具有一样的实现方法,所以这里用 ArrayList 演示。

下面的示例展示了实现类的实例化以及一些内置方法的使用:

import java.util.*;

public class Test {
    public static void main(String[] args) {
        Collection coll = new ArrayList();
        
        // add([int index,] 0bject e):将元素e添加到集合coll中的指定位置,默认为末尾
        coll.add("AA");
        coll.add(1, "BB");
        coll.add(123); // 自动装箱
        
        // size():获取添加的元素的个数
        System.out.println(coll.size()); // 3
        
        // addAll([int index,] Collection coll1):将coll1集合中的元素添加到集合coll中的指定位置,默认为末尾
        Collection coll1 = new ArrayList();

        coll1.add(123);
        coll1.add(456);
        coll1.add("CC");
        coll.addAll(coll1);
		System.out.println(coll.size()); // 5
        
        // clear():清空集合元素
		coll.clear();
        
        // isEmpty():判断当前集合是否为空
		System.out.println(coll.isEmpty()); // true
        
        // contains(Object obj):判断当前集合中是否包含obj(通过调用obj.equals()来判断)
		System.out.println(coll.contains("12")); // false
        
        // containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中
		Collection coll2 = new ArrayList(); // 返回一个ArrayList对象
        coll2.add(123);
        coll2.add(456);
		System.out.println(coll1.containsAll(coll2)); // true

		// remove(Object obj):从当前集合中移除obj
		coll2.remove(123); // 该方法调用时会先调用contains判断是否是指定元素
        System.out.println(coll2.contains(123)); // false
        
        // removeAll(Collection coll1):从当前集合中移除coll1中共有的所有的元素
		coll2.removeAll(coll1); // 移除456
        
        // retainAll(Collection coll1):获取当前集合和coll1集合的交集,井修改当前集合
		coll1.retainAll(Arrays.asList("AA", "BB", "CC"));
        System.out.println(coll1); // [CC] 只有CC
        
        // equals(Object obj):判断当前集合和obj集合的元素都是否相同,至于判断顺序相同则视集合类型而定
		coll1.add("DD");
        System.out.println(coll1.equals(Arrays.asList("CC", "DD"))); // true
        System.out.println(coll1.equals(Arrays.asList("DD", "CC"))); // false 有序集合需要判断顺序
        
        // hashCode():返回当前对象的哈希值
		System.out.println(coll1.hashCode()); // 69601
        
        // toArray():将集合转换为数组
        Object[] arr = coll.toArray();
        
        // iterator():返回Iterator接口的实例,用于遍历集合元素。这个我们单独来讲
    }
}

迭代器

上述代码展示了 Collection内置的 13 个常用方法,至于最后一个方法 iterator() ,我们首先要了解 Iterator 对象:

  • Iterator 对象称为迭代器(设计模式的一种),主要用于遍历集合中的元素
  • GOF 给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐 ”
  • Collection 接口继承了 java.lang.lterable 接口,该接口有一个 iterator() 方法,那么所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了 Iterator 接口的对象。
  • Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合
  • 集合对象每次调用 iterator() 方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
import java.util.*;

public class Test {
    public static void main(String[] args) {
        Collection coll = new ArrayList();
        coll.add("AA");
        coll.add("BB");
        coll.add(123);
        Iterator iterator = coll.iterator(), iterator1 = coll.iterator(), iterator2 = coll.iterator();
        Iterator iterator3 = coll.iterator();
        // next():返回下一个元素
        System.out.println(iterator.next()); // AA
        System.out.println(iterator.next()); // BB
        System.out.println(iterator.next()); // 123
        
        for (int i = 0; i < coll.size(); i++) { // 可以避免游标越界,但不常用
        	System.out.println(iterator1.next());
        }
        // hasNext():判断是否存在下一个元素
        while (iterator2.hasNext()) { // 常用方式,也常用ForEach循环
            System.out.println(iterator2.next());
        }
        
        // remove():从集合中删除当前元素
        while (iterator3.hasNext()) {
            if (iterator2.next().equals("BB")) {
                iterator3.remove(); // 从集合中删除BB
            }
        }
    }
}

注:Iterator 仅用于 Collection ,不用于 Map

List 接口

该接口用于存储有序的、可重复的数据。 相当于“动态”数组,会替换原有的数组,其实现类有如下三个:

  • ArrayList:作为 List 接口的主要实现类;线程不安全的,效率高;底层使用 Object[] elementData 存储
    • 使用空参构造器时,底层会在调用 add() 时才创建一个 Object[10] elementData 来储存
    • 当添加的元素数量大于数组容量,则会将容量扩大 1.5 倍
    • 开发中使用,建议提前指定容量,避免扩容
  • LinkedList:对于频繁的插入、删除操作,使用此类效率比 ArrayList 高;底层使用双向链表存储
    • 内部声明了 Node 类型的 firstlast 属性,默认值为 null
    • 每次 add() 都会把数据封装到节点中并连接到链表里
  • Vector:作为 List 接口的古老实现类;线程安全的,效率低;底层使用 Object[] elementData 存储
    • 使用空参构造器时,就创建一个 Object[10] elementData 来储存
    • 相比于 ArrayList ,扩容时会扩大 2 倍
    • 添加了 synchronized 的 ArrayList ,几乎没人用了......

List 除了从 Collection 集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。

方法 说明
void add(int index, Object ele) 在 index 位置插入 ele 元素
boolean addAll(int index, Collection eles) 从 index 位置开始将 eles 中
Object get(int index) 获取指定 index 位置的元素
int indexOf(Object obj) 返回 obj 在集合中首次出现的位置
int lastlndexOf(Object obj) 返回 obj 在当前集合中末次出现的位置
Object remove(int index) 移除指定 index 位置的元素,并返回此元素
Object set(int index, Object ele) 设置指定 index 位置的元素为 ele
List subList(int fromIndex, int tolndex) 返回从 fromIndex 到 tolndex 位置的子集合
注:使用 List 类型创建引用,即可调用这些方法

Set 接口

该接口用的比较少,用于存储无序的、无重复的数据。Set 接口判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法。

  • 无序性:等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值排序
  • 不可重复性:保证添加的元素按照 equaLs() 判断时,不能返回 true ,即相同的元素只能添加一个

其实现类有如下三个:

  • HashSet:作为 Set 接口的主要实现类;线程不安全的;可以存储 null 值
    • 内部使用 HashMap 实现的
  • LinkedHashSet:作为 HashSet 的子类;遍历其内部数据时,可以按照添加的顺序遍历
    • 在添加数据的同时,每个数据还维护了两个引用(双向链表),记录此数据前一个数据和后一个数据
    • 对于频繁的遍历操作,LinkedHashSet 效率高 HashSet
  • TreeSet:可以按照添加对象的指定属性自动进行排序,遍历结果默认为升序
    • 因为涉及排序,所以要求添加的数据是相同类的对象,且所属类必须实现两个排序接口
    • 自然排序中,比较两个对象是否相同的标准为 compareTo() ,而不再是 equals()
    • 定制排序中,可以将 Comparator 对象作为构造器参数传入,从而改变比较标准为 compare()

Set 接口添加元素的过程:

  1. 向 HashSet 中添加元素 a
  2. 获取元素 a 的 hash 值
  3. 此哈希值通过某种算法计算出底层数组中的存放位置
  4. 判断数组此位置上是否已经有元素:
    1. 如果此位置上没有其他元素,则元素 a 添加成功
    2. 如果此位置上有其他元素 b (或以链表形式存在的多个元素),则比较元素 a 与元素 b 的 hash 值:
      1. 如果 hash 值不相同,则元素 a 添加成功
      2. 如果 hash 值相同,进而需要调用元素 a 所在类的 equlas 方法:
        1. equals() 返回 true ,元素 a 添加失败
        2. equals() 返回 false ,则元素 a 添加成功

综上所述,Set 接口添加元素需要用到 hashCode()equals() ,因此向 Set 中添加的数据,其所在的类一 定要重写这两个方法,且尽可能保证一致性。

注:Set 接口没有添加额外的方法,只有继承来的方法

Map 接口

该接口由于存储双列数据,也就是 key-value 对的数据,相当于 Python 的字典

  • Map 中的 key:无序的、不可重复的,使用 Set 存储所有的 key(记得重写 equals 和 hashCode)
  • Map 中的 value:无序的、可重复的,使用 List 存储所有 的 value(记得重写 equals)
  • Map 中的 entry:无序的、不可重复的,使用 Set 存储所有的 entry(一个键值对构成了一个 Entry 对象 )

其实现类有如下五个:

  • HashMap:作为Map的主要实现类;线程不安全的,效率高能存储 null 的 key-value 对;底层用数组+链表+红黑树存储
    • 使用空参构造器时,底层会在调用 put() 时才创建一个 Node[16] table 来储存
    • 在添加键值对时,会先按照 Set 接口的方式添加 key,再按 List 接口的方式添加 value;若添加相同的 key ,则会覆盖原有的值
    • 当添加的元素数量大于数组容量,则会将容量扩大 2 倍
    • 当数组的某一索引位置上用链表存储 8 个以上的数据且数组长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储
  • LinkedHashMap:作为 HashMap 的子类;保证在遍历元素时,可以按照添加的顺序实现遍历
    • 底层用了双向链表,对于频繁的遍历操作,此类执行效率高 HashMap
  • TreeMap:保证按照添加的 key-value 对进行排序,实现排序遍历。
    • 因为涉及排序,所以要求添加的 key 是相同类的对象,且所属类必须实现两个排序接口
    • 至于如何排序,则与 TreeSet 相同
    • 与 TreeSet 一样,底层使用红黑树,要考虑自然排序和定制排序
  • Hashtable:作为古老的实现类;线程安全的,效率低不能存储 null 的 key-value 对
    • 添加了 synchronized 的 HashMap ,不咋用...
  • Properties:作为 Hashtable 的子类;常用来处理配置文件。key 和 value 都是 String 类型

内置方法

因为 Map 是接口,其实现类都具有一样的实现方法,所以这里用 HashMap 演示。

下面的示例展示了实现类的实例化以及一些不同于 Collection 的内置方法的使用:

import java.util.*;

public class Test {
    public static void main(String[] args) {
        Map map = new HashMap();
        
        // put(0bject key, 0bject value):将键值对(key,value)添加到集合coll中的指定位置
        map.put("name", 123);
        map.put(456, "age");
        map.put("name", 789); // 修改,覆盖原有的值
        
        // size():获取添加的元素的个数
        System.out.println(map.size()); // 2
        
        // putAll(Map map1):将map1集合中的元素添加到集合map中的指定位置
        Map map1 = new HashMap();
        map1.put(1, "Oh");
        map1.put("shit", 2);
        map.putAll(map1);
		System.out.println(map.size()); // 4
        
        // get(Object key):获取指定key对应的value
		System.out.println(map.get("shit")); // 2
        
        // containsKey(Object key):判断当前集合中是否包含指定的key
		System.out.println(map.containsKey("12")); // false
        
        // containsValue(Object value):判断当前集合中是否包含指定的value
		System.out.println(map.containsValue(789)); // true

		// remove(Object key):从当前集合中移除key所属的键值对并返回其value;若集合中没有key,则返回null
        System.out.println(map.remove(456)); // age
        System.out.println(map); // 移除了456=age
        
        // keySet():返回由所有key组成的Set对象
        Set set = map.keySet();
        Iterator iterator = set.iterator();
        while(iterator.hasNext()) {
        	System.out.println(iterator.next()); // 遍历所有的key集
        }
        
        // values():返回所有value组成的Collection对象
        Collection values = map.values();
        for(Object obj : values) {
        	System.out.println(obj); // 遍历所有的value集
        }
        
        //entryset():返回由所有key-value组成的Set对象
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()) {
            Map.Entry entry = (Map.Entry)iterator1.next(); // entrySet集合中的元素都是entry
            System.out.println(entry.getKey() + "-" + entry.getValue());  // 遍历所有的key-value集
        }
        
        // hashCode():返回当前对象的哈希值
        
        // clear():清空集合元素
        
        // isEmpty():判断当前集合是否为空
        
        // equals(Object obj):判断当前集合和obj是否相同
        
    }
}

注:以上只展示不同方法,相同方法仅标明

Properties 的使用

Properties 类是 Hashtable 的子类,该对象用于处理属性文件;由于属性文件里的 key 、value 都是字符串类犁,所以 Properties 里的key 和 value 都是字符串类型;存取数据时,建议使用 setProperty(String key,String value)getProperty(String key)

假设该 java 文件同目录下存在一个配置文件 jdbc.properties ,内容如下:

// 注意不要有多余空格!
name=shit
age=18

下面的代码简单地展示了如何读取配置文件:

Properties pros = new Properties();
fis = new FileInputStream("jdbc.properties"); // 文件操作,注意捕获异常
pros.load(fis); // 加载流对应的文件
String name = pros.getProperty("name"), age = pros.getProperty("age");
System.out.println("name = " + name + ", age = " + age); // name = shit, age = 18

Collections 工具类

Collections 是一个操作 CollectionMap 等集合的工具类,它提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

常用方法如下:

方法 说明
reverse(List list) 反转 list 中元素的顺序
shuffLe(List list) 对 list 中元素的进行随机排序
sort(List list[, Comparator com]) 根据元素的自然顺序或定制排序对指定 list 中的元素进行排序
swap(List list, int i, int j) 将指定 list 中的 i 处元素和 j 处元素进行交换
Object max(Collection coll[, Comparator com]) 根据元素的自然顺序或定制排序,返回给定集合中的最大元素
Object min(Collection coll[, Comparator com]) 根据元素的自然顺序或定制排序,返回给定集合中的最小元素
int frequency(Collection coll, Object obj) 返回指定集合中指定元素的出现次数
copy(List dest, List src) 将 src 中的内容复制到 dest 中
boolean replaceAll(List list, Object oldVal, Object newVal) 使用 newVal 替换 list 中所有的 oldVal 元素

Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题,如下:

List list1 = Collections.synchronizedList(list); // 返回的list1即为线程安全的List对象

当然,也有 Set ,Map 的方法,这里就不多赘述