1. Set和Map

  Set代表一种集合元素无序、集合元素不可重复的集合,Map则代表一种由多个key-value对组成的集合,Map集合类似于传统的关联数组。表面上看它们之间相似性很少,但实际上Map和Set之间有莫大的关联。

1.1 Set和Map的关系

  在看Set和Map之间的关系之前,先来看看Set集合的继承体系。

  再看Map集合的类继承关系:

  仔细观察图3.2中的Map集合的继承体系里被灰色覆盖的区域,可以发现,这些Map接口、实现类和Set集合的接口、实现类的类名完全类似,这绝不是偶然的现象,肯定有必然的原因。

  考虑Map集合的key,不难发现,这些Map集合的key具有一个特征:所有的key不能重复,key之间没有顺序,也就是说,如果把Map集合的key集中起来,那这些key就组成了一个Set集合。所以,Map集合提供了如下方法来返回所有key组成的Set集合。

Set<K> keySet()

  换一个思维来理解Map集合,如果把Map集合的value当成key的“附属物”(实际上也是,对于一个Map集合而言,只要给出指定的key,Map总是可以根据该key快速查询到对应的value),那么Map集合在保存key-value对时只需考虑key即可。

1.2 HashMap和HashSet

  下面是HashMap类的put()方法源代码如下:

  上面的程序用到了一个重要的内部接口Map.Entry,每个Map.Entry其实就是一个key-value对。从上面的程序可以看出:当系统决定存储HashMap中的key-value对时,完全没有考虑Entry的value,而仅仅只是根据key来计算并决定每个Entry的存储位置,这也说明了前面的结论:完全可以把Map集合中的value当成是key的附属,当系统决定了key的存储位置之后,value随之保存在那里即可。

  从上面的put方法的源代码可以看出,当程序试图将一个key-value对放入HashMap中时,首先根据该key的hashCode()返回值决定该Entry的存储位置:如果两个Entry的key的hashCode()返回值相同,那他们的存储位置相同;如果这两个Entry的key通过equals比较返回true,新添加Entry的value将覆盖集合中原有的Entry的value,但key不会覆盖;如果这两个Entry的key通过equals比较返回false,新添加的Entry将与集合中原有的Entry形成Entry链,而且心田家的Entry位于Entry位于Entry链的头部。下面来看addEntry方法:

  代码相对来说比较简单,系统总是将新添加的Entry对象放入table数组的bucketIndex索引处。如果bucketIndex索引处已经有了一个Entry对象,新添加的Entry对象指向原有的Entry对象(产生一个Entry链);如果bucketIndex索引处没有Entry对象,也就是上面程序1行代码的e变量是null,即新放入的Entry对象指向null,就没有产生Entry链。

  下面我们来看看HashSet:

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
.....
}

  HashSet的实现非常简单,它只是封装了一个HashMap对象来存储所有的集合元素。所有放入HashSet的集合元素实际上由HashMap的key来保存,而HashMap的value则存储一个PRESENT,它是一个静态的Object对象。HashSET的绝大部分方法都是通过调用HashMap方法来实现的。

1.3 TreeMap和TreeSet

  类似于HashMap和HashSet之间的关系,HashSet底层依赖于HashMap实现,TreeSet底层则采用一个NavigableMap来保存TreeSet集合的元素。但实际上,由于NavigableMap只是一个接口,因此底层依然是是使用TreeMap来包含Set集合中的所有元素。

  对于TreeMap而言,底层采用“红黑树”的排序二叉树来保存Map中的每个Entry。

2.ArrayList与LinkedList

  在List集合的实现类中,主要有3个实现类:ArrayList、Vector、LinkedList。其中Vector还有一个Stack(栈)子类,这个Stack子类仅在Vector父类的基础上增加了5个方法。

public
class Stack<E> extends Vector<E> {
/**
* Creates an empty Stack.
*/无参构造器
public Stack() {
} /**
* 进栈*/
public E push(E item) {
addElement(item); return item;
} /**
* 出栈*/
public synchronized E pop() {
E obj;
int len = size(); obj = peek();
removeElementAt(len - 1); return obj;
} /**
* peek瞟一眼,取出最后一个元素,但是并不出栈*/
public synchronized E peek() {
int len = size(); if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
} /**
* 判断是否为空*/
public boolean empty() {
return size() == 0;
} /**
* 元素的到栈顶的距离*/
public synchronized int search(Object o) {
     //获取o在集合中的位置
int i = lastIndexOf(o); if (i >= 0) {
       //集合长度减去在集合中的位置,就得到元素的到栈顶的距离
return size() - i;
}
return -1;
} /** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = 1224463164541339165L;
}

  可以看到,Stack新增的5个方法中有3个使用了synchronized修饰(那些需要操作集合元素的方法都添加了synchronized修饰),也就是说Stack是一个线程安全的类,这也是为了让Stack与Vector保持一致,Vector也是一个线程安全的类。

  实际上即使当程序需要栈这种数据结构时,Java也不在推荐Stack类,而是推荐使用Deque实现类。从JDK1.6开始,Java提供了一个Deque接口,并为接口提供了一个ArrayDeque实现类。在无需保证线程安全的情况下,程序完全可以使用ArrayDeque代替Stack类。

  Deque接口代表双端队列这种数据结构。双端队列已经不再是简单的队列了,它既具有队列的性质先进先出(FIFO),也具有栈的性质(FILO),也就是说双端队列既是队列,也是栈。ArrayList和ArrayDeque底层都是基于Java数组来实现的。

2.1 Vector和ArrayList的区别

  Vector和ArrayList这两个集合类的本质并没有太大不同,都实现了List接口,底层都是基于Java数组来存储集合元素。

  ArrayList的部分源码:

    /**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access

Vector的部分源码:

    /**
* The array buffer into which the components of the vector are
* stored. The capacity of the vector is the length of this array buffer,
* and is at least large enough to contain all the vector's elements.
*
* <p>Any array elements following the last element in the Vector are null.
*
* @serial
*/
protected Object[] elementData;

  可以看到,ArrayList使用transient修饰了elementData数组。这保证了系统序列化ArrayList对象时不会直接序列化elementData数组,而是通过ArrayList提供的writeObject、readObject来实现定制序列化。除此之外,Vector其实就是ArrayList的线程安全版本。即使需要在多线程环境下使用List集合,依然可以避免使用Vector。Java提供了一个Collections工具类,通过该工具类synchronizedList方法就可将普通ArrayList包装成线程安全的ArrayList。

2.2 ArrayList和LinkedList的实现差异

  ArrayList是一种顺序存储的线性表,底层使用数组来保存集合元素;LinkedList则是一种链式存储的线性表,其本质上是一个双向链表,而它不仅实现了List接口,还实现了Deque接口,也就是说LinkedList既可以当成双向链表使用,也可以当成队列使用,还可以当成栈来使用。两者的优缺点就是数组和链表的优缺点,就经验来说,ArrayList性能总体上优于LinkedList。

  当程序需要以get(int index)方法获取List集合指定索引处的元素时,ArrayList性能大大优于LinkedList,LinkedList必须一个个地搜索过去。当程序调用add(int index,Object obj)向List集合中添加元素时,ArrayList必须对底层数组元素进行“整体搬家”,如果添加元素导致集合长度将要超过数组长度,ArrayList必须创建一个为原来长度1.5倍的数组,再由垃圾回收机制回收原有数组,因此开销比较大。

3. Interator迭代器

  对于Iterator迭代器而言,它只是一个接口,Java要求各种集合都提供一个iterator()方法,该方法可以返回一个Iterator用于遍历该集合中元素,至于返回的Iterator到底是哪种实现类,程序并不关心,这就是典型的“迭代器模式”。

  迭代时删除指定元素:由于迭代器只负责对各种集合所包含的元素进行迭代,它自己并没有保留集合元素,因此迭代时,通常不应该删除集合元素,否则将引发ConcurrentModificationException异常。当然,Java里面集合是允许使用Iterator的remove()方法删除刚刚迭代过的元素。

 public class Test {  

     public static void main(String[] args) {  

         List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String temp = it.next();
it.remove();
}
}
}

  此程序无问题,但是注意第十三行删除时,必须执行了十二行。

常见Java集合的实现细节的更多相关文章

  1. Java总结——常见Java集合实现细节(1)

    Java提高——常见Java集合实现细节(1) 2018年04月18日 15:07:35 阅读数:25 集合关系图 Set和Map set代表一种集合元素无序.集合元素不可重复的集合 map代表一种由 ...

  2. Java集合的实现细节—Set集合和Map集合

    Set:代表无序.不可重复的集合 Map:代表key-value对集合,也称为关联数组 从表面上看,Set和Map相似性很少,但实际上可以说Map集合时Set集合的扩展. 1.Set集合和Map集合的 ...

  3. 程序员的基本功之Java集合的实现细节

    1.Set集合与Map 仔细对比观察上面Set下和Map下的接口名,不难发现它们如此的相似,必有原因 如果只考察Map的Key会发现,它们不可以重复,没有顺序,也就是说把Map的所有的Key集中起来就 ...

  4. Java集合中的细节

    integer数据对比 对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值 ...

  5. java集合的实现细节--ArrayList和LinkedList

     ArrayList和LinkedList的实现差异 List代表一种线性表的数据结构,ArrayList则是一种顺序存储的线性表,ArrayList底层采用动态数组的形式保存每一个集合元素,Link ...

  6. Java集合中的细节问题

    1)集合不保存基本数据类型,而是会把基本数据类型装箱后保存. 2)Empty和null的区别:null是不存在,Empty已经初始化了,只不过里面是空的. 3)判断集合有效性: 先判断空,再判断emp ...

  7. (Set, Map, Collections工具类)JAVA集合框架二

    Java集合框架部分细节总结二 Set 实现类:HashSet,TreeSet HashSet 基于HashCode计算元素存放位置,当计算得出哈希码相同时,会调用equals判断是否相同,相同则拒绝 ...

  8. (Collection, List, 泛型)JAVA集合框架一

    Java集合框架部分细节总结一 Collection List 有序,有下标,元素可重复 Set 无序,无下标,元素不可重复 以上为Collection接口 以ArrayList为实现类实现遍历:增强 ...

  9. Java提高篇(三五)-----Java集合细节(一):请为集合指定初始容量

    集合是我们在Java编程中使用非常广泛的,它就像大海,海纳百川,像万能容器,盛装万物,而且这个大海,万能容器还可以无限变大(如果条件允许).当这个海.容器的量变得非常大的时候,它的初始容量就会显得很重 ...

随机推荐

  1. Android studio 添加引用新建类库

    1.新建一个工程包 2.修改AndroidManifest.xml 将AndroidManifest.xml 修改为 <manifest xmlns:android="http://s ...

  2. 九九乘法表---for循环的嵌套

    package com.zuoye.test;//控制台输出九九乘法表public class Jiujiu { public static void main(String[] args) { in ...

  3. 【Oracle】解锁用户

    登录oracle数据库时有时会显示ERROR: ORA-28000: the account is locked,这是因为所登录的账号被锁定了. 解决办法: sqlplus / as sysdba; ...

  4. more-less-cat-tail-head 命令简单分析

    区别:cat一次性把文件内容全部显示出来,管你看不看得清,显示完了cat命令就返回了,不能进行交互式 操作,适合察看内容短小.不超过一屏的文件:more比cat强大一点,支持分页显示,你可以ctrl+ ...

  5. javascript 基础知识点

    NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示 Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时 ...

  6. C# tostring("0000000")

    public string ConverNo(string str) { string result = ""; ]; ; i < chars.Length; i++) ch ...

  7. drf05 路由Routers

    对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息. REST framework提供了两个router ...

  8. Java同步的三种实现方式

    1.使用synchronized关键字修饰类或者代码块: 2.使用Volatile关键字修饰变量: 3.在类中加入重入锁 举例子:多个线程在处理一个共享变量的时候,就会出现线程安全问题.(相当于多个窗 ...

  9. vmware Horizon 7 与远程桌面(mstsc)兼容性问题解决办法

    关于Horizon 7 Agent与远程桌面(mstsc)兼容性问题解决办法 在Horizon 7环境中,在桌面模板安装了Horizon Agent后,就无法直接通过微软的远程桌面(mstsc)工具连 ...

  10. python 从Excel中取值

    import openpyxl from openpyxl import load_workbook def open_file(file_path): workbook = load_workboo ...