集合(1)集合接口

目录

一、将集合的接口与实现分离

二、Java类库中的集合接口和迭代器接口

1、迭代器

2、删除元素

3、泛型实用方法

三、Java集合框架简介

1、Collection

2、Map


说明:这一系列博客内容摘自《Java核心技术 卷I》

一、将集合的接口与实现分离

与现代的数据结构类库的常见情况一样,Java集合类库也将接口与实现分离。首先,看一下人们熟悉的数据结构——队列(queue)是如何分离的。

 队列接口指出可以在队列的尾部添加元素,在队列的头部删除元素,并且可以査找队列中元素的个数。当需要收集对象,并按照“先进先出”的规则检索对象时就应该使用队列。

一个队列接口的最小形式可能类似下面这样:

interface Queue<E> // a simplified form of the interface in the standard library

{

    void add(E dement);

    E remove();

    int size();

}

 这个接口并没有说明队列是如何实现的。队列通常有两种实现方式:一种是使用循环数组;另一种是使用链表

 每一个实现都可以通过一个实现了Queue接口的类表示:

class CircularArrayQueue<E> implements Queue<E> // not an actual library class
{
    CircularArrayQueue(int capacity) { }
    public void add(E element) { }
    public E remove() { }
    public int size() { }
 
    private E[] elements;
    private int head;
    private int tail;
}

class LinkedListQueue<E> implements Queue<E> // not an actual library class
{
    LinkedListQueue() { }
    public void add(E element) { }
    public E remove() { }
    public int size() { }
 
    private Link head;
    private Link tail;
}

注释:实际上,Java类库没有名为CircularArrayQueue和LinkedListQueue的类。这里,只是以这些类作为示例,解释—下集合接口与实现在概念上的不同。如果需要一个循环数组队列,就可以使用ArrayDeque类。如果需要一个链表队列,就直接使用LinkedList类,这个类实现了Queue接口。

循环数组要比链表更高效,因此多数人优先选择循环数组。然而,通常这样做也需要付出一定的代价。

循环数组是一个有界集合,即容量有限。如果程序中要收集的对象数量没有上限,就最好使用链表来实现。

二、Java类库中的集合接口和迭代器接口

在Java中,集合类的基本接口是Collection接口。

这个接口有两个基本方法:

public interface Collection<E> {

    boolean add(E element);

    Iterator<E> iterator();
    
    ...

}

除了这两个方法之外,还有几个方法,将在稍后介绍。

add方法用于向集合中添加元素。如果添加元素确实改变了集合就返回true,如果集合没有发生变化就返回false。例如,如果试图向集中添加一个对象,而这个对象在集合中已经存在,这个添加请求就没有实效,因为集中不允许有重复的对象

iterator方法用于返回一个实现了Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素

1、迭代器

Iterator接口包含3个方法:

public interface Iterator<E> {

    E next();

    boolean hasNext();

    void remove();

}

通过反复调用next方法,可以逐个访问集合中的每个元素。但是,如果到达了集合的末尾,next方法将抛出一个NoSuchElementException。因此,需要在调用next之前调用hasNext方法。如果迭代器对象还有多个供访问的元素,这个方法就返回true。如果想要査看集合中的所有元素,就请求一个迭代器,并在hasNext返回true时反复地调用next方法。例如:

Collection<String> c = ...;

Iterator<String> iter = c.iterator();

while (iter.hasNext()) {

    String element = iter.next();

    doSomethingWith(element);

}

从Java SE 5.0起,这个循环可以采用一种更优雅的缩写方式。用“for each”循环可以更加简练地表示同样的循环操作:

for (String element : c) {

    doSomethingWith(element);

}

编译器简单地将“for each”循环翻译为带有迭代器的循环

“for each”循环可以与任何实现了Iterable接口的对象一起工作,这个接口只包含一个方法:

public interface Iterable<E> {

    Iterator<E> iterator();

}

Collection接口扩展了Iterable接口。因此,对于标准类库中的任何集合都可以使用“for each”循环。

元素被访问的顺序取决于集合类型。如果对ArrayList进行迭代,迭代器将从索引0开始,每迭代一次,索引值加1。然而,如果访问HashSet中的元素,每个元素将会按照某种随机的次序出现。虽然可以确定在迭代过程中能够遍历到集合中的所有元素,但却无法预知元素被访问的次序。这对于计算总和或统计符合某个条件的元素个数这类与顺序无关的操作来说.并不是什么问题。

Java集合类库中的迭代器与其他类库中的迭代器在概念上有着重要的区别。在传统的集合类库中,例如,C++的标准模版库,迭代器是根据数组索引建模的。如果给定这样一个迭代器,就可以査看指定位置上的元素,就像知道数组索引就可以査看数组元素a[i]一样。不需要查找元素,就可以将迭代器向前移动一个位置。这与不需要执行査找操作就可以通过i++将数组索引向前移动一样。但是,Java迭代器并不是这样操作的查找操作与位置变更是紧密相连的。査找一个元素的唯一方法是调用next,而在执行査找操作的同时,迭代器的位置随之向前移动。

因此,应该将Java迭代器认为是位于两个元素之间。当调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。

注释:这里还有一个有用的类推。可以将Iterator.next与InputStream.read看作为等效的。从数据流中读取一个字节,就会自动地“消耗掉”这个字节。下一次调用read将会消耗并返回输入的下一个字节。用同样的方式,反复地调用next就可以读取集合中所有元素。

2、删除元素

 Iterator接口的remove方法将会删除上次调用此以方法时返回的元素。下面是如何删除字符串集合中第一个元素的方法:

Iterator<String> it = c.iterator();

it.next(); // skip over the first element

it.remove(); // now remove it

 更重要的是,对next方法和remove方法的调用具有互相依赖性,如果调用remove之前没有调用next将是不合法的。如果这样做,将会抛出一个IllegalStateException异常。

3、泛型实用方法

由于Collection和Iterator都是泛型接口,可以编写操作任何集合类型的实用方法,例如,下面是一个检测任意集合是否包含指定元素的泛型方法:

public static <E> boolean contains(Collection<E> c, Object obj) {

        for (E elenent : c)

            if (elenent.equals(obj))

                return true;

        return false;

    }

Java类库的设计者认为:这些实用方法中的某些方法非常有用,应该将它们提供给用户使用。这样,类库的使用者就不必自己重新构建这些方法了。contains就是这样一个实用方法。

事实上,Collection接口声明了很多有用的方法,所有的实现类都必须提供这些方法。

当然,如果实现Collection接口的每一个类都要提供如此多的例行方法将是一件很烦人的事情。为了能够让实现者更容易地实现这个接口,Java类库提供了一个类AbstractCollection。它将基础方法size和iterator抽象化了,但是在此提供了例行方法。例如:

public abstract class AbstractCollection<E> implements Collection<E> {

    public abstract Iterator<E> iterator();

    public boolean contains(Object obj) {

        for (E elemeit : c)

            // calls iterator()

            if (element.equals(ob))

                return true;

        return false;

    }

}

此时,一个具体的集合类可以扩展AbstractCollection类了,现在要由具体的集合类提供iterator方法,而contains方法已由AbstractCollection超类提供了。然而,如果子类有更加有效的方式实现contains方法,也可以由子类提供,就这点而言,没有什么限制。

三、Java集合框架简介

Java集合框架如图:

在下表中,除了以Map结尾的类之外,其他类都实现了Collection接口。而以Map结尾的类实现了Map接口。

集合类型描述
ArrayList一种可以动态增长和缩减的索引序列
LinkedList一种可以在任何位置进行高效地插入和删除操作的有序序列
ArrayDeque一种用循环数组实现的双端队列
HashSet一种没有重复元素的无序集合
TreeSet一种有序集
EnumSet一种包含枚举类型值的集
LinkedHashSet一种可以记住元索插入次序的集
PriorityQueue一种允许高效删除最小元素的集合
HashMap一种存储键/值关联的数据结构
TreeMap一种键值有序排列的映射表
EnumMap一种键值槭卞枚举类型的映射表
LinkedHashMap一种可以记住键/值项添加次序的映射表
WeakHashMap一种其值无用武之地后可以被垃圾回收器回收的映射表
IdentityHashMap一种用==,而不是用equals比较键值的映射表

1、Collection

Collection是最基本的集合接口,包含了集合的基本操作和属性,主要分为List和Set。还有Queue。

(1)Queue

i)、队列与双端队列

        队列可以让人们有效地在尾部添加一个元素 , 在头部删除一个元素 。 有两个端头的队列 , 即双端队列 , 可以让人们有效地在头部和尾部同时添加或删除元素。 不支持在队列中间添加元素 。 在Java SE 6 中引入了 Deque 接口 , 并由 ArrayDeque 和
LinkedList 类实现。 这两个类都提供了双端队列 , 而且在必要时可以增加队列的长度。

        Queue的方法:add/offer;remove/poll;element/peek;

        Deque的方法:addFirst,addLast/offerFirst,offerLast;removeFirst,removeLast/pollFirst,pollLast;getFirst,getLast/peekFirst,peekLast

关于ArrayDeque和Linkedlist:ArrayDeque和LinkedList区别

ii)、优先队列

         优先级队列(priority queue) 中的元素可以按照任意的顺序插人,却总是按照排序的顺序进行检索。也就是说,无论何时调用 remove 方法,总会获得当前优先级队列中最小的元素。然而,优先级队列并没有对所有的元素进行排序。如果用迭代的方式处理这些元素,并不需要对它们进行排序。优先级队列使用了一个优雅且高效的数据结构,称为堆(heap)。堆是一个可以自我调整的二叉树,对树执行添加( add) 和删除(remore) 操作, 可以让最小的元素移动到根,而不必花费时间对元素进行排序

        与 TreeSet—样,一个优先级队列既可以保存实现了 Comparable 接口的类对象, 也可以保存在构造器中提供的 Comparator 对象。

        使用优先级队列的典型示例是任务调度。每一个任务有一个优先级,任务以随机顺序添加到队列中。每当启动一个新的任务时,都将优先级最高的任务从队列中删除。

(2)List

List相关内容

(3)Set

元素是无序不可重复的。(但是其中的TreeSet是特别的,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序)。

只允许一个 null 元素

Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。

Set和Map的底层联系密切,例如,HashSet依赖于HashMap,它实际上是通过HashMap实现的;TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。(Set说白了就是对Map的功能的限制)

HashSet:

  • HashSet底层实现其实是HashMap
  • HashSet实现了Set接口,它不允许集合中出现重复元素,无序。
  • 将对象存储在HashSet之前,要确保重写hashCode()方法和equals()方法,这样才能比较对象的值是否相等,确保集合中没有储存相同的对象。
  • 在HashSet中,元素都存到HashMap键值对的Key上面,而Value时有一个统一的值private static final Object PRESENT = new Object();
  • 当有新值加入时,底层的HashMap会判断Key值是否存在
  • 不是线程安全的

LinkedHashSet:

  • LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置
  • 它同时使用链表维护元素的次序
  • LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
  • LinkedHashSet取出元素的顺序和存入的顺序一致
  • LinkedHashSet的底层实现是继承了HashSet

TreeSet:

  • TreeSet类型唯一可实现自动排序的类型,即是取出的元素是经过排序后输出
  • TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。
  • TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
  • TreeSet的底层实现是TreeMap的变形

2、Map

Map是一个映射接口,即key-value键值对。Map中的每一个元素包含“一个key”和“key对应的value”。

不允许键重复(重复了覆盖了),但允许值重复

AbstractMap是个抽象类,它实现了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是继承于AbstractMap。Hashtable虽然继承于Dictionary,但它实现了Map接口。

HashTable和HashMap的对比:

  • HashTable的底层存储是使用了数组+链表,1.7及之前HashMap使用数组+链表,1.8使用了数组+红黑树
  • Hashtable是线程安全的,HashMap是线程不安全的,所以HashMap比Hashtable的性能高一点
  • Hashtable不允许使用null作为key和value;但HashMap可以使用null作为key或value。
  • 关于HashMap中,在jdk1.8之前是插入头部的,在jdk1.8中是插入尾部的

LinkedHashMap:

  • LinkedHashMap保存了记录的插入顺序、在用Iterator遍历LinkedHashMap时、先得到的记录肯定是先插入的.也可以在构造时用带参数、按照应用次数排序、在遍历的时候会比HashMap慢
  • LinkedHashMap的遍历速度只和实际数据有关、和容量无关、而HashMap的遍历速度和他的容量有关

TreeMap:

  • TreeMap实现SortMap接口、能够把它保存的记录根据键排序,默认是按键值的升序排序
  • 当用Iterator 遍历TreeMap时、得到的记录是排过序的
  • TreeMap取出来的是排序后的键值对、但如果您要按自然顺序或自定义顺序遍历键、那么TreeMap会更好
  • LinkedHashMap 是HashMap的一个子类、如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列
  • TreeMap的底层实现是红黑树

ps:Map和Collection是两个不同的接口,没什么关系,但可以通过Map生成Collection.比如:Map可以返回其所有键组成的Set和其所有值组成的Collection,或其键值对组成的Set.

参考:《Java核心技术 卷I》

https://www.cnblogs.com/skywang12345/p/3308498.html

https://www.cnblogs.com/dooor/p/5285487.html

https://www.jianshu.com/p/20a56b81157e

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值