带你从头看完java集合框架源码之总体预览

带你从头看完java集合框架源码之总体预览

集合框架是java中十分重要的部分,几乎是我们java开发中一定会用到的东西,我们学习的时候一开始只是学会如何去使用它,但搞清楚它底层的实现,会对我们的编程能力有很大的提升,也可以更加深入的理解集合框架。我自己学习java也已经有一段时间了,一直没有对基础的原理和底层进行总结和提升,借此机会记录一下,也希望能帮到正在学习中的人。


目录

  1. java集合框架源码之总体预览
  2. java集合框架源码之List
  3. java集合框架源码之Queue
  4. java集合框架源码之Set

这篇文章将简单的介绍java Collection下的一些顶层接口,接口具体实现的介绍则在下一篇文章介绍

集合框架体系:

简单一点的图是这样的:

我们按照Iterator接口->ListIterator接口->Collection接口->AbstractCollection抽象类、List、Set、Queue接口的顺序一层一层的阅读源码

Iterator接口

一共有四个方法

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());
}

hasNext方法就是判断是否有下一个元素

next方法就是返回下一个元素

remove方法是移除当前集合元素返回的最后一个元素,接口中默认实现是直接抛出了UnsupportedOperationException异常

forEachRemaining方法,接受一个Consumer接口,对集合中的剩余每个元素都调用Consumer接口中的accpet方法进行处理

ListIterator接口

继承了Iterator接口,在Iterator接口的基础上,增加了如下几个方法:

boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void set(E e);
void add(E e);

hasPrevious方法,判断list当前位置下是否有上一个元素

previous方法,返回list当前位置下的上一个元素

nextIndex方法,返回下一个元素的下标

previousIndex方法,返回上一个元素的下标

set方法,将list中最近一个被ListIterator返回出去的值(最近一次调用previous或者next方法时的值)修改成指定值

add方法,在list当前的上一个元素和下一个元素之间插入一个元素

Collection接口

打开源码我们发现Collection接口继承了Iterable接口(不是Iterator接口),打开接口发现写着这样一段话:

Implementing this interface allows an object to be the target of the "for-each loop" statement. See For-each Loop
Since:1.5
Type parameters: – the type of elements returned by the iterator

翻译一下就是:实现这个接口,使得对象可以使用for-each-loop,也就是for-each循环

我们看看接口中有什么:

Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
}
default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
}

iterator方法返回一个Iterator对象

forEach方法,接受一个Consumer接口对象,对每一个元素t调用Consumer的accept方法

spliterator方法,返回一个Spliterator迭代器,就是并行迭代器,这个后面再说

看完Iterable接口,看看Collection接口有什么东西:

int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
}

ok现在看完Collection接口了,我们来看看实现Collection接口的抽象类AbstractCollection

AbstractCollection抽象类

这个类有这么一段话:This class provides a skeletal implementation of the Collection interface, to minimize the effort required to implement this interface.
To implement an unmodifiable collection, the programmer needs only to extend this class and provide implementations for the iterator and size methods. (The iterator returned by the iterator method must implement hasNext and next.)
To implement a modifiable collection, the programmer must additionally override this class's add method (which otherwise throws an UnsupportedOperationException), and the iterator returned by the iterator method must additionally implement its remove method.
The programmer should generally provide a void (no argument) and Collection constructor, as per the recommendation in the Collection interface specification.
The documentation for each non-abstract method in this class describes its implementation in detail. Each of these methods may be overridden if the collection being implemented admits a more efficient implementation

翻译成人话就是:

1、如果要实现一个不允许修改的集合类,则只需扩展这个类并提供迭代器和size方法的具体实现(迭代器Iterator必须实现hasNext和next方法)

2、如果要实现一个允许修改的集合类,除了1之外必须重写add方法,且迭代器还要实现remove方法

看看他的一些比较难理解的方法:

public Object[] toArray() {
        // Estimate size of array; be prepared to see more or fewer elements
        Object[] r = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) // fewer elements than expected
                return Arrays.copyOf(r, i);
            r[i] = it.next();
        }
        return it.hasNext() ? finishToArray(r, it) : r;
}

这个方法是将一个集合转换为一个对象数组,首先创建一个数组,大小是size方法返回的大小,然后获取集合的迭代器。第一个奇怪的点是这个循环:

for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) // fewer elements than expected
                return Arrays.copyOf(r, i);
            r[i] = it.next();
}

这里考虑了并发修改集合的情况,在获取size之后,很有可能其他线程对这个集合进行了操作,删除了一些元素,使得实际元素比之前size返回的少,所以调用copyOf方法,把数组裁剪返回。

第二个点就是最后一句返回语句,为什么循环结束后还需要判断迭代器有没有下一个对象。原因还是并发修改,很有可能其他线程对集合增加了元素,所以迭代器中还有元素,就需要把剩余元素也一并添加进数组,这里调用的是finishToArray方法

private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        int i = r.length;
        while (it.hasNext()) {
            int cap = r.length;
            if (i == cap) {
                int newCap = cap + (cap >> 1) + 1;
                // overflow-conscious code
                if (newCap - MAX_ARRAY_SIZE > 0)
                    newCap = hugeCapacity(cap + 1);
                r = Arrays.copyOf(r, newCap);
            }
            r[i++] = (T)it.next();
        }
        // trim if overallocated
        return (i == r.length) ? r : Arrays.copyOf(r, i);
}

finishToArray方法逻辑:首先获取数组的大小,如果数组大小不够,迭代器还有元素,就扩充数组,最后如果数组大小和元素个数相同则直接返回,否则就裁剪后返回。

扩充数组的逻辑:首先,让新数组大小=原数组大小+原数组大小一半+1,如果新数组大小>MAX_ARRAY_SIZE(一个静态变量,大小是Integer.MAX_VALUE - 8),就调用hugeCapacity方法获取新数组大小。然后调用copyOf方法扩充数组。

接下来是继承了Collection的三大接口,List,Queue和Set

List接口:

特点如下:有序集合,允许重复元素,可以添加null(根据具体实现)

对比Collection接口,多了以下方法:

//重载的addAll方法
boolean addAll(int index, Collection<? extends E> c);

default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }

default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
E get(int index);
E set(int index, E element);

//重载的add方法
void add(int index, E element);
//重载的remove方法
E remove(int index);

int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);

//重写了spliterator方法
default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.ORDERED);
    }

可以看出list在collection接口的基础上,增加了一些和list有关的操作,如重载的add方法允许在任意位置添加元素,可以移除指定下标的元素,提供了更合适的迭代器listIterator(可以双向访问),可以获取指定位置的元素

Set接口

特点:不允许添加重复元素,可以添加null(取决于具体实现,最多只能添加一个)

对比Collection接口,多了一个重写的spliterator方法

default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.DISTINCT);
    }

Queue接口

特点:Queue是队列,通常是FIFO的(有些不是),通常不允许插入null(LinkQueue可以,但是不提倡)

对比Collection接口,多了一些队列的特殊操作

//插入元素
boolean offer(E e);
//返回并删除队列头元素
E poll();
//返回队列头元素但不删除,失败返回异常
E element();
//返回队列头元素但不删除,失败不返回异常
E peek();

到此,顶层的一些接口与抽象类我们已经全部看完了。

总结

本文从整体上简单的介绍了java集合框架。集合框架需要迭代能力,因此所以所有的集合框架接口都继承或实现了迭代器接口,不同类型的接口所依赖的数据结构也是不同的,也拥有了不同的特点。后续将介绍不同接口的实现类
能力有限,如果有什么错误还希望大家指正。

posted @ 2021-03-07 14:30  ForYou丶  阅读(190)  评论(0编辑  收藏  举报