容器的深入研究

完整的容器分类图

在这里插入图片描述
上图介绍了java中容器类之间的关系。其中实线框的表示实体类,稀疏的虚线框即带有abstract的类是抽象类,交密的虚线框表示的是接口。

可以看出Collection和Map接口是Java容器的基础。

Collection & Iterator

  • Iterator(迭代器)
    Iterator作用是用来遍历集合,是一个接口。看看它的源码:
public interface Iterator<E> {
 // 判断是否还有元素 
  public boolean hasNext();
  // 获取元素
  public NullFromTypeParam E next();
  // 函数
  public default void remove() { 
  	throw new RuntimeException("Stub!");
  }
  // 遍历剩下元素
  public default void forEachRemaining(NonNull Consumer<? super NullFromTypeParam E> action) { 
  throw new RuntimeException("Stub!"); }
}

看上面的类关系图,Collection生产Iteartor,List生产ListIterator,而ListIterator继承Iterator接口,那么ListIterator和Iterator有什么区别?看看ListIterator的源码:

public interface ListIterator<E> extends Iterator<E> {
    /**
     * 判断是否还有元素
     */
    boolean hasNext();

    /**
     * 获取元素
     */
    E next();
    
    /**
     *移除元素(前面这三个方法是相同的)
     */
    void remove();

    /**
     * 判断是否有元素,向前遍历
     */
    boolean hasPrevious();

    /**
     * 向前遍历获取元素
     */
    E previous();

    /**
     * 获取下标,向后遍历
     */
    int nextIndex();

    /**
     * 获取下边,向前遍历
     */
    int previousIndex();

    /**
     * 修改元素。替换它访问过的最后一个元素.
     */
    void set(E e);

    /**
     * 添加。在next()方法返回的元素之前或previous()方法返回的元素之后插入一个元素.
     */
    void add(E e);
}

总结:
(1) ListIterator有add()方法,可以向List中添加对象,而Iterator不能
(2) ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
(3) ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
(4) 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
(5) ListIterator是一个功能更加强大的, 它继承于Iterator接口,只能用于各种List类型的访问,而Iterator还可以用与Set。

  • Collection

它是List和Set集合以及Queue队列的基础,是一个接口同时继承了Iterable接口。看看源码:

public interface Collection<E> extends Iterable<E> {
    /**
     *
     */
    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();

    /**
    *
     */
    @Override
    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);
    }
}

其中有一个返回Iterator的函数,Iterator iterator()。

Queue

队列是一个典型的先进先出的容器(FIFO)。

public interface Queue<E> extends Collection<E> {
	
    boolean add(E var1);//

    boolean offer(E var1);//在队尾添加以一个元素或返回false

    E remove();//移除并返回队头,在队列为空的情况下抛出异常NoSuchElementException

    E poll();//移除并返回队头,在队列为空的情况下返回null

    E element(); // 在不移除的情况下返回队头,在队列为空的情况下抛出异常NoSuchElementException

    E peek(); // 在不移除的情况下返回队头,在队列为空的情况下返回null
}

双向队列Deque

Deque也是一个接口,继承了Queue接口。即可以在任何一端添加和移除元素。

优先级队列PriorityQueue

当我们调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序。但我们可以通过实现Comparator接口而进行控制的。调用peek(),poll(),remove()方法时,获取的元素将是队列中优先级最高的元素。

public class PriorityQueue<E> extends AbstractQueue<E> implements Serializable {

	 private final Comparator<? super E> comparator;
	...
}

public abstract class AbstractQueue<E> extends AbstractCollection<E> implements Queue<E> {
	...
}

List

Java中List最主要的有两种。List可以添加重复的元素,元素的顺序是添加的顺序。

ArrayList

底层使用数组结构,所以随机访问很快,但向其中间插入和移除元素时较慢。

LinkedList

LinkedList继承AbstractSequentialList类,还实现了 Deque接口(Deque又继承Queue接口),所以可以使用LinkedList实现队列的功能。

底层基与链表结构,所以随机访问较慢,但向其中间插入和移除元素时较快。

Set

只要是加入Set容器中的元素都必须是唯一的,因为Set不保存重复元素。通过equals()方法以确保元素的唯一,相同的元素只保存一份。

HashSet

HashSet容器结构是散列结构,所以加入该容器的元素必须要有hashCode()方法产生散列码。由于hashCode()产生的散列码是可以重复的,但通过hashCode()和equals()必须能确定一个唯一的元素。容器中的元素是乱序的。

TreeSet

TreeSet容器结构是树结构,不基于散列结构,所以元素可以不用重写hashCode()。基于树结构,所以改容器的中的元素是排好顺序的,而顺序由元素实现Comparable接口决定。

LinkedHashSet

LinkedHashSet继承了HashSet类,所以该容器中的元素也需要重写hashCode()方法,这也LinkedHashSet的随机访问的速度仅次于HashSet容器的原因,使用了散列,但LinkedHashSet的底层结构是链表的,容器里面的元素的顺序是插入顺序,所以在迭代访问时,速度比HashSet的还快。

Map

HashMap

HashMap基于散列结构(它取代了Hashtable)。插入和查询的开销是固定的,可以通过构造器设置和负载因子,以调整容器的性能。容器中元素没有顺序,即是乱序的。

LinkedHashMap

LinkedHashMap继承了HashMap,所以该容器中的元素也需要重写hashCode()方法。LinkedHashMap的结构是链表结构,容器中元素的顺序是插入时的顺序的。所以在迭代时速度快于HashMap容器。

TreeMap

TreeMap容器结构是基于红黑树的,不基于散列结构,所以元素可以不用重写hashCode()。基于树结构,所以改容器的中的元素是排好顺序的,而顺序由元素实现Comparable或Comparator接口决定。TreeMap是唯一具有subMap()方法的Map,它可以返回一个字树。

散列和散列码

散列

散列的目的在于:想要使用一个对象来查找另一个对象。如果单单是为来达到此目的,可以使用TreeMap或者自己实现一个类似Map的功能也可以。但散列有另外的价值:散列使得查询得以快速进行。散列保存键信息,以便能够很快找到。存储一组元素最快的数据结构是数组,所以散列使用数组来表示键的信息(注意:说的是键的信息,而不是键本身)。但是如果键的数量被数组的容量限制了,该怎么办?

答案:数组并不保存键本身。而是通过对象生成一个数字,将其作为数组的下标,这个数字就是散列码,由定义在Object中的,且可以由你覆盖的hashCode()方法生成。

为解决数组容量被固定的问题,不同的键可以产生相同的下标,也就是说hashCode()产生的散列码可以重复。因此,数组多大就显得不那么重要了,任何键总能在数组中找到它的位置。

由于查询过程中首先是计算散列码,然后使用散列码查询数组,如果能够保证散列码没有冲突,那就有了一个完美的散列函数。但如果散列码冲突重复时,通常用外部链接处理:数组并不保存键本身,而是保存一个List,这个List保存的是键对应的值的元素。先通过散列码作为下表从数组中找出这个List,通过equaIs()方法线性遍历List,因此这个过程是比较慢的,但如果散列得好,这List的元素不多,也不需要多久的时间。

散列码

散列码是一个int值,散列码由定义在Object类中的hashCode()产生,Object类中默认产生的散列码是对象在内存中的地址。我们可以覆盖这个方法,重写hashCode()方法。

我们在重写hashCode()时需要遵守几点:

  1. hashCode()是为了反应快速,而不用过度强调唯一。
  2. 重写好后,检查 hashCode()最后生成的结果,确保相同的对象有相同的散列码。

equals()方法

Java容器中的元素的x.equals()方法显得格外的重要。List容器调用remove()等方法时,是基于元素的x.equals()方法,这些方法会由x.equals()方法的行为不通而有不同的结果。Set和Map容器通过equal()方法来确保元素和key的唯一。

所以我们在重写equal()方法时必须满足这5个条件:

  1. 自返性。对任意的x,x.equals(x)一定返回true;
  2. 对称性。对任意的x,y都有x.equals(y),y.equals(x);
  3. 传递性。对任意的x,y,z都有x.equals(y)为true,y.equals(z)为true,则有x.equals(z)为true;
  4. 一致性。对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么true,要么一直是false。
  5. 对任何不是null的x,x.equals(null);一直是false。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值