2、容器初探

简单容器的分类:

图1. 简单容器的分类

在“图1”中总结出了常用容器的简单关系。可以看到,只有4类容器:List、Set、Queue、Map。上图中虚线框表示一个接口,实线框表示一个具体的实现类,虚线箭头线表示一种“实现”关系,实线箭头线表示一种“继承”关系。红线箭头不表示实现与继承关系。

为了理清楚“图1”中简单容器的关系,首先从迭代器(Iterator)的作用开始讲解。

一、迭代器

迭代器是一个对象,它的工作是遍历并选择容器中的对象,而程序员不必要知道或关心容器的底层实现(无论List、Queue、Set都采用同样的方式来对待)。迭代器是一个轻量级的对象,创建它的代价很小,同样功能也简单(比如:只能单向移动)。只具备如下的功能:

①、使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回容器的第一个元素。

②、使用next()获得容器的下一个元素。

③、使用hasNext()检查容器中是否还有元素。

④、使用remove()将迭代器新近返回的元素删除。

迭代器真正的强大在于将遍历容器的操作与容器底层结构相分离。

例如:打印包含有Person对象的容器可以写下面一个方法,只需要向打印方法中传入一个迭代器即可,至于这个容器到底是List还是Set根本不需要关心。

1 void printPerson(Iterator<Person> itr){
2     if(itr == null)
3         return;
4     while(itr.hasNext()){
5         Person p = itr.next();
6         System.out.println(p.getName() + " " + p.getAge());
7     }
8 }

Collection接口中就定义有iterator()方法,所以如果容器是List、Set、Queue则只需要直接调用对象的iterator()方法便可以得到一个迭代器对象。

但是,Map容器并没有实现Collection接口,那么一个Map类型的容器该如何获得一个迭代器对象呢?Map定义了三个Collection视角的方法:

①、map.entrySet(); 返回一个Set<Entry<T, E>>对象,SetSet<Entry<T, E>>对象中有iterator()方法。

②、map.keySet();  返回一个Set<T>对象,Set<T>对象中有iterator()方法。

③、map.values(); 返回一个Collection<T>对象,Collection<T>对象中有iterator()方法。

下面是一个迭代器用于Map的例子:

 1 import java.util.*;
 2 import java.util.Map.Entry;
 3 public class Main{
 4     public static void main(String args[]){
 5
 6         Map<Integer, String> map = new HashMap<>();
 7         map.put(1, "aaa");
 8         map.put(2, "bbb");
 9         map.put(3, "ccc");
10         map.put(4, "ddd");
11
12         Set<Entry<Integer, String>> setEntry = map.entrySet();
13         Iterator<Entry<Integer, String>> itrEntry = setEntry.iterator();
14         System.out.println("--------map.entrySet()--------");
15         while(itrEntry.hasNext()){
16             Entry<Integer, String> entry = itrEntry.next();
17             Integer key = entry.getKey();
18             String val = entry.getValue();
19             System.out.print(key + ":" + val + " ");
20         }
21
22         Set<Integer> setKey = map.keySet();
23         Iterator<Integer> itrKey = setKey.iterator();
24         System.out.println("\n\n--------map.keySet()--------");
25         while(itrKey.hasNext()){
26             Integer key = itrKey.next();
27             System.out.print(key + " ");
28         }
29
30         Collection<String> values = map.values();
31         Iterator<String> itrValues = values.iterator();
32         System.out.println("\n\n--------map.values()--------");
33         while(itrValues.hasNext()){
34             String val = itrValues.next();
35             System.out.print(val + " ");
36         }
37     }

Map中使用迭代器

运行结果:

除此之外,ListIterator是一个更强大的Iterator的子类型,它只能够用于各种List类的访问。尽管Iterator只能够单向移动,但是ListIterator却能够双向移动。

二、List

public interface List<E> extends Collection<E> 可以看出,List接口直接继承Collection接口。

下面主要讲List接口的两个具体实现类ArrayList和LinkedList。

ArrayList底层的具体实现是一个Object[]对象。

在其调用add方法的时候会先检查底层Object[]对象是否还有足够的空间,如果空间足够则将元素添加到末尾;如果没有足够的空间则创建一个更大的Object[]数组,并将旧数组中的数据复制到新数组中间去。

扩容过程调用elementData = Arrays.copyOf(elementData, newCapacity) 来完成,elementData 就是ArrayList底层Object[]对象。copyOf方法最终会调用下面的方法来实现:

1  public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
2    @SuppressWarnings("unchecked")
3    T[] copy = ((Object)newType == (Object)Object[].class)
4             ? (T[]) new Object[newLength]
5             : (T[]) Array.newInstance(newType.getComponentType(), newLength);
6    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
7    return copy;
8  }

可以看出,扩容原理就是新创建一个相同类型的新数组,同时将老数组中的数据复制到新数组中间去。

其实,继续追踪System.arraycopy的具体实现,可以引出另外一个问题:

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 

啊哈,JDK中根本看不见System.arraycopy的源代码。却发现了native这个关键字。


tips:

之前没去深入了解过底层的东西,在CSDN中找到一篇简要介绍native关键字的博客。http://blog.csdn.net/youjianbo_han_87/article/details/2586375

下面是博客的原文:

native关键字用法

native是与C++联合开发的时候用的!java自己开发不用的!

使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。 这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。

  1. native 是用做java 和其他语言(如c++)进行协作时用的也就是native 后的函数的实现不是用java写的。
  2. 既然都不是java,那就别管它的源代码了,呵呵。

native的意思就是通知操作系统, 这个函数你必须给我实现,因为我要使用。 所以native关键字的函数都是操作系统实现的, java只能调用。

java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了。

LinkedList底层的具体实现是一个链表结构。

直接看其add方法的源码:

 public boolean add(E e) {
        linkLast(e);
        return true;
  }

继续查看linkLast的源码:

void linkLast(E e) {
  final Node<E> l = last;
  final Node<E> newNode = new Node<>(l, e, null);
  last = newNode;
  if (l == null)
    first = newNode;
  else
    l.next = newNode;
  size++;
  modCount++;
}

恩,没错。就是在链表末尾添加一个新的节点。

对比ArrayList和LinkedList的区别:

①、底层实现不同,ArrayList底层是数组,LinkedList底层是一个单向链表。

②、从底层实现分析特性:ArrayList擅长于随机访问,但是对中间位置的插入和删除操作较慢;LinkedList则删除与插入和删除操作,随机访问速度较慢。

③、两个list都是通过位置索引编号来查询元素的。

三、Set

Set集合最大的特性是:集合中的元素不能重复。

Set最常被使用的一个功能就是测试归属性,可以很容易的查询某个元素是否在某个Set中。所以,查找就成了Set集合中最重要的操作。通常使用HashSet的实现,它对快速查找进行了优化。

HashSet使用了散列,元素的迭代输出没有任何规律性。

TreeSet 将元素存储在“红-黑树”数据结构中,元素的迭代输出按照递增的方式进行。

LinkedHashSet 使用了散列,看起来它使用了链表来维护元素的插入顺序。

其底层实现后面再专门研究。

四、Queue

队列是一种典型的先进先出FIFO容器。其在并发编程中特别重要。

可以看到Queue有很多的具体实现类。但是,现在只关注其中的一个实现类:LinkedList。没错,LinkedList实现了Queue接口,那么我们可以将一个LinkedList向上转型为一个Queue,从而实现基本的FIFO操作。

下面介绍几个和Queue相关的方法:

①、boolean offer(E e)方法在允许的情况下将一个元素插入到队尾,或者返回false。

②、peek()和element()方法将在不移除的情况下返回队头。队列为空,peek()返回null,element()抛出异常。

③、poll()和remove()方法将移除并返回队头。队列为空,poll()返回null,remove()抛出异常。

五、Map

Map具有将对象映射到对象的能力。

Map可以和其它的Collection一样,很容器将其扩展到多维。只需要将其值设置为Map(扩展Map的值可以是其它容器,甚至是其它Map)。所以,Map的组合可以快速的生成强大的数据结构,比如:随机数分布统计应用等。

HashMap使用散列,查找速度快。

TreeMap 可以自动的将key值进行排序。

LinkedHashMap 使用散列,同时貌似维持着存入顺序。

后面深入研究Map的具体实现。

六、Foreach与迭代器

看一个foreach很简单的例子:

void foreachTest(){
    int[] arr = {1,2,3,4,5,6,7,8};
    for(int i : arr){
        System.out.print(i + " ");
    }
}

很简洁的就输出了数组中的所有元素,根本不需要关心数组中元素的个数。

foreach能够用于数组,那么,能够用于上面介绍的容器吗?如果我们自己定义一个奇葩容器,它能用foreach输出吗?

原理:foreach之所以能够工作,是因为java SE5引入了新的被称为Iterable的接口,该接口包含了一个能够产生Iterator对象的iterator()方法,并且Iterable接口被foreach用来在序列中移动。所以,不难理解,如果你创建了任何实现Iterable接口的类都能够将它用于foreach语句中。

为了印证上面对于foreach的原理,下面定义一个类,它实现了Iterable接口,那么这个类的对象就可以应用于foreach语句中。

 1 import java.util.*;
 2 public class IterableTest implements Iterable<String>{
 3     private String[] str = {"aaa","bbb","ccc","ddd","eee"};
 4
 5     //重写iterator()方法,使得其支持foreach
 6     public Iterator<String> iterator() {
 7         return new Iterator<String>(){//匿名内部类
 8             private int index = 0;
 9             public boolean hasNext() {
10                 if(index < str.length){
11                     return true;
12                 }
13                 return false;
14             }
15             public String next() {
16                 return str[index++];
17             }
18         };
19     }
20
21     //测试看IterableTest对象是否能够运用于foreach中
22     public static void main(String[] args){
23         IterableTest itrTst = new IterableTest();
24         for(String str : itrTst){//IterableTest 对象用于foreach语句中
25             System.out.print(str + " ");
26         }
27     }
28 }

现在提出一个新的问题:对于IterableTest 类,我们希望在默认前向迭代器的基础上,添加产生反向迭代器的能力。因此,我们不能够使用覆盖,而是添加一个能够产生Iterable对象的方法,该方法可以用于foreach语句。

改造代码如下:

 1 import java.util.*;
 2 public class IterableTest implements Iterable<String>{
 3
 4     private String[] str = {"aaa","bbb","ccc","ddd","eee"};
 5
 6     //重写iterator()方法,使得其支持foreach
 7     public Iterator<String> iterator() {
 8         return new Iterator<String>(){//匿名内部类
 9             private int index = 0;
10
11             public boolean hasNext() {
12                 if(index < str.length){
13                     return true;
14                 }
15                 return false;
16             }
17             public String next() {
18                 return str[index++];
19             }
20         };
21     }
22     /*
23      * 通过reversed()方法得到一个Iterable对象,该对象重写了
24      * iterator()方法实现了反向迭代器的功能
25      */
26     public Iterable<String> reversed(){
27         return new Iterable<String>(){
28             public Iterator<String> iterator() {
29                 return new Iterator<String>(){
30                     private int current = str.length - 1;
31
32                     public boolean hasNext() {
33                         if(current >= 0){
34                             return true;
35                         }
36                         return false;
37                     }
38
39                     public String next() {
40                         return str[current--];
41                     }
42                 };
43             }
44         };
45     }
46
47     //测试看IterableTest对象是否能够运用于foreach中
48     public static void main(String[] args){
49         IterableTest itrTst = new IterableTest();
50         for(String str : itrTst){//正向迭代器
51             System.out.print(str + " ");
52         }
53
54         System.out.println();
55         for(String str : itrTst.reversed()){//反向迭代器
56             System.out.print(str + " ");
57         }
58     }
59 }
时间: 2024-11-05 09:04:49

2、容器初探的相关文章

c++ STL容器初探

什么是容器 首先,我们必须理解一下什么是容器,在C++ 中容器被定义为:在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器.很简单,容器就是保存其它对象的对象,当然这是一个朴素的理解,这种"对象"还包含了一系列处理"其它对象"的方法,因为这些方法在程序的设计上会经常被用到,所以容器也体现了一个好处,就是"容器类是一种对特定代码重用问题的良好的解决方案". 容器还有另一个特点是容器可以自行扩展.在解决问题时

JAVA 持有对象——容器初探

引言 如果一个程序只包含固定数量的且其生命周期都是已知对象,那么这是一个非常简单的程序--<think in java> 了解容器前,先提出一个问题,ArrayList和LinkedList谁的处理速度更快呢? 一 持有对象的方式 在Java中,我们可以使用数组来保存一组对象.但是,数组是固定大小的,在一般情况下,我们写程序时并不知道将需要多少个对象,因此数组固定大小对于编程有些受限. java类库中提供了一套相当完整的容器类来解决这个问题,其中基本类型有List,Queue,Set,Map,

【Java心得总结五】Java容器上——容器初探

在数学中我们有集合的概念,所谓的一个集合,就是将数个对象归类而分成为一个或数个形态各异的大小整体. 一般来讲,集合是具有某种特性的事物的整体,或是一些确认对象的汇集.构成集合的事物或对象称作元素或是成员.集合具有:无序性.互异性.确定性. 而在我们计算机科学种集合的定义是:集合是一组可变数量的数据项(也可能是0个)的组合,这些数据项可能共享某些特征,需要以某种操作方式一起进行操作.一般来讲,这些数据项的类型是相同的,或基类相同(若使用的语言支持继承).列表(或数组)通常不被认为是集合,因为其大小

【Java心得总结五】Java容器中——Collection

在[Java心得总结五]Java容器上——容器初探这篇博文中,我对Java容器类库从一个整体的偏向于宏观的角度初步认识了Java容器类库.而在这篇博文中,我想着重对容器类库中的Collection容器做一个着重的探索与总结. Collection:一个独立元素的序列,这些元素都服从一条或多条规则.(注:Collection其实就是将一组数据对象按照一维线性的方式组织起来)List必须按照插入的顺序保存元素,而set不能有重复元素.Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序

【Java心得总结七】Java容器下——Map

我将容器类库自己平时编程及看书的感受总结成了三篇博文,前两篇分别是:[Java心得总结五]Java容器上——容器初探和[Java心得总结六]Java容器中——Collection,第一篇从宏观整体的角度对Java中强大的容器类库做了一个简单总结而第二篇专门针对容器类库中的Collection部分进行了总结.这篇博文将对容器类库中的Map部分进行一个整理总结. 一.初识Map Map:一组成对的“键值对”对象,允许你使用键来查找值.(注:Map其实是将键与值形成的二元组按照一维线性的方式组织起来,

把《c++ primer》读薄(3-2 标准库vector容器+迭代器初探)

督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. 标准库vector类型初探,同一种类型的对象的集合(类似数组),是一个类模版而不是数据类型,学名容器,负责管理 和 存储的元素 相关的内存,因为vetcor是类模版,对应多个不同类型,比如int,string,或者自己定义的数据类型等. 程序开头应如下声明 #include <iostream> #include <vector> #include <string> using std::strin

初探STL之关联容器

关联容器 分类:set, multiset, map, multimap 特点:内部元素有序排列,新元素插入的位置取决于它的值,查找速度快. 常用函数: find: 查找等于某个值 的元素(x小于y和y小于x同时不成立即为相等) lower_bound : 查找某个下界 upper_bound : 查找某个上界 equal_range : 同时查找上界和下界 count :计算等于某个值的元素个数(x小于y和y小于x同时不成立即为相等) insert: 用以插入一个元素或一个区间 set 特点:

Java 容器:Collection 初探之 List

1 1 ///: JavaBasic//com.cnblogs.pattywgm.day1//CollectionTest.java 2 2 3 3 package com.cnblogs.pattywgm.day1; 4 4 5 5 import java.io.BufferedReader; 6 6 import java.io.IOException; 7 7 import java.io.InputStreamReader; 8 8 import java.util.ArrayList;

初探STL之容器适配器

容器适配器 特点 用某种顺序容器来实现(让已有的顺序容器以栈/队列的方式工作) 分类 1) stack: 头文件 <stack> ? 栈 -- 后进先出 2) queue: 头文件 <queue> ? 队列 -- 先进先出 3) priority_queue: 头文件 <queue> ? 优先级队列 -- 最高优先级元素总是第一个出列 注: 容器适配器上没有迭代器 STL中各种排序, 查找, 变序等算法都不适合容器适配器 Stack 特点 1.stack 是后进先出的数