Java集合类详解

1 集合类概述

先附上《Java编程思想》中的集合类关系图(从百度百科下载):

1.1 为什么需要集合类?

通常,我们的程序需要根据程序运行时才知道创建多少个对象。但若非程序运行,程序开发阶段,我们根本不知道到底需要多少个数量的对象,甚至不知道它的准确类型。为了满足这些常规的编程需要,我们要求能在任何时候,任何地点创建任意数量的对象,而这些对象用什么来容纳呢?我们首先想到了数组,但是数组只能放统一类型的数据,而且其长度是固定的,那怎么办呢?集合便应运而生了!

引用来源于YSOcean:https://www.cnblogs.com/ysocean/p/6555373.html

再附上Z小繁的集合类关系图:https://img-blog.csdn.net/20160124221843905

1.2 容器

java.util 包中定义了一组类,这些类用来专门存放对象,被叫做容器(集合类)。

集合类存储的是对象(实际是对象的引用,基本数据类型为其包装类对象)。集合类可以存储不同类型的对象,同时集合类设计采用了泛型,如果指定了特定类型,则可以保证只存储同类对象。

1.3 打印

在用System.out.println();如果是对象则默认调用对象的toString()方法。集合类实现了Object的toString()方法,覆盖了Object根类的toString()方法:

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

如AbstractList中的实现:

 public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

该方法可以详细的打印集合类信息。

1.4 整体结构

集合类分为List、Queue、Set、Map四类。Collection是List、Queue、Set的顶层接口,定义了他们共有的方法(详细查看官方API)。Map是各种xxMap的顶层接口。在实现类和接口之间还定义了一些抽象类,这些抽象类是为了统一实现一些子类都需要用到的方法,从而减小子类设计复杂度。

2 List

List中的元素保持了插入的顺序,是有序的对象集合。List接口下有ArrayList、LinkedList、Vector、Stack等实现。其中常用的是ArrayList和LinkedList,Java编程思想中指出Vector和Stack都过时了。

  • ArrayList底层是Object数组(transient Object[] elementData;),所以擅长随机访问,插入和删除元素较慢,不是线程安全的。

  • LinkedList底层是带头指针和尾指针的链表transient Node<E> first; transient Node<E> last;),所以擅长插入和删除,提供了优化的顺序访问,随机访问较慢,不是线程安全的。

  • Vector底层是Object数组(protected Object[] elementData;),查看源码发现其方法均被synchronized修饰,是线程安全的。继承自AbstractList类,实现了List等接口,由于过时不在细说。

  • Stack直接继承自Vector,也是线程安全的。添加了push()、pop()、peek()、empty()和search()方法。Stack已经不推荐使用了,要用栈的行为应该使用LinkedList,或者继承LinkedList类创建一个自己的栈实现。

List接口定义了很多方法,这些方法被实现了List接口的类所实现来改变List的行为,详细查看官方API

通常除了需要使用子类特有的方法,通常我们都会将子类上转型成List接口使用,像这样:

List<String> l1 = new ArrayList<String>(); // 泛型就以String作为例子吧
List<String> l2 = new LinkedList<String>();
List<String> l3 = new Vector<>();       // new后面<>中可以不写类型
Stack<String> s = new Stack<>();       // Stack需要使用该类新添加的方法,保持Stack特点,不上转型

3 Queue

队列是一种重要的数据结构,应用也很广泛,队列在并发编程中特别重要。

  • Queue是一个接口继承自Collection接口,LinkedList实现了Queue接口定义的相关操作,所以我们需要一个Queue可以:
Queue<String> queue = new LinkedList<>();

Queue接口中新定义的方法很少,只有与队列相关的add(E)、offer(E)、remove()、poll()、element()和peek(),其余都是从Collection接口继承过来的。

  • Deque是双端队列接口他继承自Queue,新增了一些方法能够在队列两端进行添加、删除、查询的方法。

  • ArrayDeque是Deque的实现类,ArrayDeque底层是Object数组(transient Object[] elements;)

  • PriorityQueue底层是Object数组(transient Object[] queue;),不同的是PriorityQueue入队是按照优先级排序的,这样在出队的时候总是弹出优先级最高的对象。默认的排序将使用对象在队列中的自然顺序,自己也可以通过提供自己的Comparator来修改这个顺序。

4 Set

Set 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性,Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。Set常被用来测试归属性,查找是Set最重要的操作。


HashSet 为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()


TreeSet 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口


LinkedHashSet 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插人的次序显示。元素也必须定义hashCode()方法

-----《Thinking in Java》第17.6章节

HashSet底层是HashMap的key组成的,所以采用的是散列表结构存储, private transient HashMap<E,Object> map;其中HashMap的值(并无实际用处)是用一个叫PRESENT的Object对象填充的private static final Object PRESENT = new Object();

TreeSet底层是TreeMap的key组成的,所以是采用的红黑树存储,private transient NavigableMap<E,Object> m;NavigableMap是TreeMap的父类接口,实际构造方法返回的是TreeMap的实例对象,也就是TreeSet用了TreeMap的key, TreeSet中的Map的值也是被PRESENT的Object对象填充的。

LinkedHashSet底层与LinkedHashMap相同,具体咋来的这篇博文写的比较清楚了LinkedHashSet的实现原理

5 Map

HashMap* Map基于散列表的实现(它取代了Hashable)。插人和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能


LinkedHashMap 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢-点;而在迭代访问时反而更快、因为它使用链表维护内部次序


TreeMap 基于红黑树的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparable或Comparator决定)。TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树


WeakHashMap 弱键(weak key)映射,允许释放映射所指向的对象;这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个“键”, 则此“ 键”可以被垃圾收集器回收


ConcurrentHashMap 一种线程安全的Map,它不涉及同步加锁。


IdentityHashMap 使用==代替equals()对“ 键”进行比较的散列映射。专为解决特殊问题而设计的

-----《Thinking in Java》第17.8章节

6 Comparable和Comparator

专门用于比较的两个接口,自己搜了几篇博文

7 Arrays和Collections

这两个类是专门为数组和集合类的常用操作设计的工具类。这篇博文讲列举的很详细
集合系列18(Arrays和Collections工具类)

8 遍历与迭代器

对List集合类的遍历可以用:

// 1. 增强for循环
for(Object item : lists){}
// 2. 普通for循环
for(int i = 0; i < lists.size(); i++){}
// 3. 迭代器iterator
Iterator<Object> it = lists.iterator();
while(it.hasNext()){it.next()}

对Set集合类的遍历可以用:增强for循环、迭代器:

// 1. 增强for循环
for(Object item : set){}
// 2. 迭代器
Iterator<Object> it = set.iterator();
while(it.hasNext()){it.next();}

对Map类型集合类遍历:

// 1. 只遍历keySet
for (Object key : map.keySet()) {}
// 2. 只遍历values
for (Object value : map.values()) {}
// 3. 遍历entrySet
for (Map.Entry<Object, Object> entry : map.entrySet()){}
// 4. 通过entrySet().itertor()获取迭代器遍历
Iterator<Object, Object> it = map.entrySet().iterator(); 
while (it.hasNext()) {it.next();}
// 5. 遍历keySet再通过map.get(key)找值(该方法效率低每次都要调用map.get(key)搜索值)
for (Object key : map.keySet()) {value = map.get(key);}

上面三段代码中Object都可以根据实际的泛型类别替换,可以看见集合类型的遍历主要是通过迭代器来实现的,增强for循环实际上隐式的调用itertor()迭代器。我们也应当尽量使用迭代器或增强for循环的方式来遍历集合。

下面看一下迭代器相关接口Iterable和Iterator:

// 迭代器接口
public interface Iterable<T> {
    
    Iterator<T> iterator(); // 封装了Iterator接口

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) { // 实现了该接口的类,在使用增强for循环,this就是需要遍历的实现类
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

// 迭代器类
public interface Iterator<E> {  
    boolean hasNext();
    
    E next();
    
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
 
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

我们遍历一般是实现Iterable接口,从而获取到iterator(),为什么不直接实现Iterator()呢?网上都是复制的这个回答Iterator和Iterable接口的区别,自己还没完全理解。用Iterator遍历时,可以调用Iterator中的remove()方法删除一个元素。

ListIterator是专门用于遍历List的,Iterator只能向前移动,而ListIterator则可以双向移动,并且可以使用set()方法替换它访问过的最后一个元素。它添加了许多其他的方法:

package java.util;
public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext(); // 后面还有没
    
    E next(); // 下一个
    
    boolean hasPrevious(); // 前面还有没
    
    E previous(); // 上一个
   
    int nextIndex(); // 下一个的索引
   
    int previousIndex(); // 上一个的索引
    
    void remove(); // 删除刚访问过的那个元素
   
    void set(E e); // 修改刚访问过的那个元素

    void add(E e); // 将指定元素插入到当前位置之前
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值