常见Java集合的实现细节
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集合的实现细节的更多相关文章
- Java总结——常见Java集合实现细节(1)
Java提高——常见Java集合实现细节(1) 2018年04月18日 15:07:35 阅读数:25 集合关系图 Set和Map set代表一种集合元素无序.集合元素不可重复的集合 map代表一种由 ...
- Java集合的实现细节—Set集合和Map集合
Set:代表无序.不可重复的集合 Map:代表key-value对集合,也称为关联数组 从表面上看,Set和Map相似性很少,但实际上可以说Map集合时Set集合的扩展. 1.Set集合和Map集合的 ...
- 程序员的基本功之Java集合的实现细节
1.Set集合与Map 仔细对比观察上面Set下和Map下的接口名,不难发现它们如此的相似,必有原因 如果只考察Map的Key会发现,它们不可以重复,没有顺序,也就是说把Map的所有的Key集中起来就 ...
- Java集合中的细节
integer数据对比 对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值 ...
- java集合的实现细节--ArrayList和LinkedList
ArrayList和LinkedList的实现差异 List代表一种线性表的数据结构,ArrayList则是一种顺序存储的线性表,ArrayList底层采用动态数组的形式保存每一个集合元素,Link ...
- Java集合中的细节问题
1)集合不保存基本数据类型,而是会把基本数据类型装箱后保存. 2)Empty和null的区别:null是不存在,Empty已经初始化了,只不过里面是空的. 3)判断集合有效性: 先判断空,再判断emp ...
- (Set, Map, Collections工具类)JAVA集合框架二
Java集合框架部分细节总结二 Set 实现类:HashSet,TreeSet HashSet 基于HashCode计算元素存放位置,当计算得出哈希码相同时,会调用equals判断是否相同,相同则拒绝 ...
- (Collection, List, 泛型)JAVA集合框架一
Java集合框架部分细节总结一 Collection List 有序,有下标,元素可重复 Set 无序,无下标,元素不可重复 以上为Collection接口 以ArrayList为实现类实现遍历:增强 ...
- Java提高篇(三五)-----Java集合细节(一):请为集合指定初始容量
集合是我们在Java编程中使用非常广泛的,它就像大海,海纳百川,像万能容器,盛装万物,而且这个大海,万能容器还可以无限变大(如果条件允许).当这个海.容器的量变得非常大的时候,它的初始容量就会显得很重 ...
随机推荐
- Windows下错误码全解析
windows系统下,调用函数出错时.可以调用GetLastError函数返回错误码.但是GetLastError函数返回值是DWORD类型,是一个整数.如果想要知道函数调用的真正错误原因,就需要对这 ...
- hibernate_08_关联映射_一对多
hibernate的映射关系 一对多.多对一.一对一.多对多. 常用的是一对多和多对一. 在数据库中可以通过添加主外键的关联,表现一对多的关系:在hibernate中通过在一方持有多方的集合实现,即在 ...
- 能够附加图片的标签控件iOS项目源码
这个源码案例是能够附加图片的标签控件,源码JTImageLabel,JTImageLabel能够附加图片的标签Label控件,图片可以随意更换.位置也能够很好的控制.效果图: <ignore_j ...
- MatLab之HDL coder
1 Workflow The workflow for applying HDL code generation to the hardware design process requires the ...
- CVPR2015深度学习回顾
原文链接:http://www.csdn.net/article/2015-08-06/2825395 本文做了少量修改,仅作转载存贮,如有疑问或版权问题,请访问原作者或告知本人. CVPR可谓计算机 ...
- Data mapping-数据映射
数据映射:根据数据的结构信息建立数据间的映射操作机制. 数据映射的要素: 一.数据 1.源数据: 2.目标数据: 3.数据间关系: 4.数据的元数据(结构信息). 5.元素类型的对应关系. 二.元数据 ...
- java操作Excel的poi的导出Excel表格
页面布局 点击导出用户:触发函数,直接访问后台 后台方法如下: public String export()throws Exception{ Connection con=null; try { c ...
- MQTTnet 的Asp.Net Core 认证事件的扩展
MQTTnet 的数据接收 连接 等事件都很丰富, 唯独客户端连接验证不能依赖注入也不能很舒服的使用事件的方式, 因此MQTTnet.AspNetCoreEx 就出现了. 示例如下:在 public ...
- 一次由于 MTU 设置不当导致的网络访问超时
转自:http://weibo.com/ttarticle/p/show?id=2309404140904511340923 API 服务正常,但是调用总是超时.api端日志显示,响应速度很快. ...
- nyoj23-取石子(一)
取石子(一) 时间限制:3000 ms | 内存限制:65535 KB 难度:2 描述 一天,TT在寝室闲着无聊,和同寝的人玩起了取石子游戏,而由于条件有限,他/她们是用旺仔小馒头当作石子.游戏的 ...