Java中的集合包最常用的有Collection和Map两个接口的实现类,Colleciton用于存放多个单对象,Map用于存放Key-Value形式的键值对。

   Collection中最常用的又分为两种类型的接口:List和Set,两者最明显的差别为List支持放入重复的元素,而Set不支持。

  List最常用的实现类有:ArrayList、LinkedList、Vector及Stack;Set接口常用的实现类有:HashSet、TreeSet。

由于HashSet的内部实现原理使用了HashMap,所以我们先来了解Map集合类。

1.HashMap、Hashtable和TreeMap

(1)java.lang.Object

继承者 java.util.AbstractMap<K,V>

继承者 java.util.HashMap<K,V>

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

基于数组+链表的结合体(链表散列)实现,将key-value看成一个整体,存放于Entity[]数组,put的时候根据key hash后的hashcode和数组length-1按位与的结果值判断放在数组的哪个位置,如果该数组位置上若已经存放其他元素,则在这个位置上的元素以链表的形式存放。如果该位置上没有元素则直接存放。

  当系统决定存储HashMap中的key-value对时,完全没有考虑Entry中的value,仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把Map集合中的value当成key的附属,当系统决定了key的存储位置之后,value随之保存在那里即可。get取值也是根据key的hashCode确定在数组中的位置,再根据key的equals确定在链表处的位置。  

  当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。

  负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。

  HashMap的实现中,通过threshold字段来判断HashMap的最大容量。threshold就是在此loadFactor和capacity对应下允许的最大元素数目,超过这个数目就重新resize,以降低实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。

  initialCapacity*2,成倍扩大容量,HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。不设定参数,则初始容量值为16,默认的负载因子为0.75,不宜过大也不宜过小,过大影响效率,过小浪费空间。扩容后需要重新计算每个元素在数组中的位置,是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

(2)java.lang.Object

继承者 java.util.Dictionary<K,V>

继承者 java.util.Hashtable<K,V>

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable

HashTable数据结构的原理大致一样,区别在于put、get时加了同步关键字,而且HashTable不可以存放null值。

  在高并发时可以使用ConcurrentHashMap,其内部使用锁分段技术,维持着锁Segment的数组,在数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中。

  总结:

 ①HashMap采用数组方式存储key、value构成的Entry对象,无容量限制;

 ②HashMap基于key hash寻找Entry对象存放到数组的位置,对于hash冲突采用链表的方式解决;

 ③HashMap在插入元素时可能会扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中;

 ④HashMap是非线程安全的。

(3)java.lang.Object

继承者 java.util.AbstractMap<K,V>

继承者 java.util.TreeMap<K,V>

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, Serializable

TreeMap基于红黑树的实现,因此它要求一定要有key比较的方法,要么传入Comparator实现,要么key对象实现Comparable借口。在put操作时,基于红黑树的方式遍历,基于comparator来比较key应放在树的左边还是右边,如找到相等的key,则直接替换掉value。

注:
红黑树(Red Black Tree),还被称为平衡二叉B树(symmetric binary B-trees), 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
它在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。其统计性能要好于平衡二叉树。

其中,HashMap不是线程安全的;HashTable是线程安全的,其线程安全是通过sychronized实现的。基于该原因,HashMap效率高于HashTable。

在多线程环境下,HashMap配合Collections工具类使用来实现线程安全;还可以选择ConcurrentHashMap,该类的线程安全是通过Lock的方式实现的,所以效率高于Hashtable。

数组,链表,哈希表,三者各有优劣。数组使用连续的内存空间,查找速度快,增删慢;链表充分利用了内存,存储空间是不连续的,首尾各存储上下一个节点的信息,所以寻址较慢,即查找速度慢,但是增删快;哈希表综合了前二者的优点,一个哈希表,由数组和链表组成,HashMap就采用了链表作为存储结构。假设一个链表有1000个节点,现在要查找最后一个节点,就得从第一个遍历到最后一个;如果用哈希表,将这条链表分为10组,用一个容量为10的数组来存储这10组链表的头结点(a[0] = 0 , a[1] = 100 , a[2] = 200 …以此类推),hashCode()相同的结点放在一个区域里,寻址时先比较hashCode()是否相同,以找到元素的存储块,再比较equals(),从而确定是否找到目标元素,从而提高了寻址效率。

HashMap就是基于上述哈希表原理实现的寻址,Hashtable同理,只不过做了同步处理。

HashMap输出是无序的,这个无序不是说每次遍历的结果顺序不一样,而是说与插入顺序不一样

另外,TreeMap是按键排序的,默认升序,所以可以通过TreeMap来实现。TreeMap的查重与排序是在底层基于比较器(Comparator接口的int compare(T o1,T o2),Comaparable接口的int compareTo(T o))实现的。

HashMap实现线程同步的方式有两种,一种是:

       Map<Integer , String> hs = new HashMap<Integer , String>();
hs = Collections.synchronizedMap(hs);

  另一种是:

    ConcurrentHashMap<Integer , String> hs = new ConcurrentHashMap<Integer , String>();

  2.List接口及其子类ArrayList,LinkedList,Vector,Stack。

public interface List<E> extends Collection<E>

(1)java.lang.Object

继承者 java.util.AbstractCollection<E>

继承者 java.util.AbstractList<E>

继承者 java.util.ArrayList<E>

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable

ArrayList基于数组方式实现,默认构造器通过调用ArrayList(int)来完成创建,传入的值为10,实例化了一个Object数组,并将此数组赋给了当前实例的elementData属性,此Object数组的大小即为传入的initialCapacity,因此调用空构造器的情况下会创建一个大小为10的Object数组。

  插入对象:add(E)

 基于已有元素数量加1作为名叫minCapacity的变量,比较此值和Object数组的大小,若大于数组值,那么先将当前Object数组值赋给一个数组对象,接着产生一个新的数组容量值。此值的计算方法为当前数组值*1.5+1,如得出的容量值仍然小于minCapacity,那么就以minCapacity作为新的容量值,调用Arrays.copyOf来生成新的数组对象。

 还提供了add(int,E)这样的方法将元素直接插入指定的int位置上,将目前index及其后的数据都往后挪一位,然后才能将指定的index位置的赋值为传入的对象,这种方式要多付出一次复制数组的代价。还提供了addAll

 删除对象:remove(E)

 这里调用了faseRemove方法将index后的对象往前复制一位,并将数组中的最后一个元素的值设置为null,即释放了对此对象的引用。 还提供了remove(int)方法来删除指定位置的对象,remove(int)的实现比remove(E)多了一个数组范围的检测,但少了对象位置的查找,因此性能会更好。

  获取单个对象:get(int)

  遍历对象:iterator()

  判断对象是否存在:contains(E)

  总结:

 ①ArrayList基于数组方式实现,无容量的限制;

 ②ArrayList在执行插入元素时可能要扩容,在删除元素时并不会减小数组的容量(如希望相应的缩小数组容量,可以调用ArrayList的trimToSize()),在查找元素时要遍历数组,对于非null的元素采取equals的方式寻找;

 ③ArrayList是非线程安全的。

(2)java.lang.Object

继承者 java.util.AbstractCollection<E>

继承者 java.util.AbstractList<E>

继承者 java.util.AbstractSequentialList<E>

继承者 java.util.LinkedList<E>

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable

LinkedList基于双向链表机制,所谓双向链表机制,就是集合中的每个元素都知道其前一个元素及其后一个元素的位置。在LinkedList中,以一个内部的Entry类来代表集合中的元素,元素的值赋给element属性,Entry中的next属性指向元素的后一个元素,Entry中的previous属性指向元素的前一个元素,基于这样的机制可以快速实现集合中元素的移动。

  总结:

 ①LinkedList基于双向链表机制实现;

 ②LinkedList在插入元素时,须创建一个新的Entry对象,并切换相应元素的前后元素的引用;在查找元素时,须遍历链表;在删除元素时,要遍历链表,找到要删除的元素,然后从链表上将此元素删除即可,此时原有的前后元素改变引用连在一起;

 ③LinkedList是非线程安全的。

(3)java.lang.Object

继承者 java.util.AbstractCollection<E>

继承者 java.util.AbstractList<E>

继承者 java.util.Vector<E>

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable

其add、remove、get(int)方法都加了synchronized关键字,默认创建一个大小为10的Object数组,并将capacityIncrement设置为0。容量扩充策略:如果capacityIncrement大于0,则将Object数组的大小扩大为现有size加上capacityIncrement的值;如果capacity等于或小于0,则将Object数组的大小扩大为现有size的两倍,这种容量的控制策略比ArrayList更为可控。

 Vector是基于Synchronized实现的线程安全的ArrayList,但在插入元素时容量扩充的机制和ArrayList稍有不同,并可通过传入capacityIncrement来控制容量的扩充。

(4)java.lang.Object

继承者 java.util.AbstractCollection<E>

继承者 java.util.AbstractList<E>

继承者 java.util.Vector<E>

继承者 java.util.Stack<E>

public class Stack<E> extends Vector<E>

Stack继承于Vector,在其基础上实现了Stack所要求的后进先出(LIFO)的弹出与压入操作,其提供了push、pop、peek三个主要的方法:

 push操作通过调用Vector中的addElement来完成;

 pop操作通过调用peek来获取元素,并同时删除数组中的最后一个元素;

 peek操作通过获取当前Object数组的大小,并获取数组上的最后一个元素。

其中,ArrayList和Vector本质都是用数组实现的,而LinkList是用双链表实现的。所以,Arraylist和Vector在查找效率上比较高,增删效率比较低;LinkedList则正好相反。当然,如果ArrayList和Vector是在末尾增删元素,而且集合空间够用的话,因为不必涉及集合的另一部分移位,所以操作效率也会并不慢。

ArrayList是线程不安全的,Vector是线程安全的,因此,Vector在效率上没有ArrayList高。实际使用中,一般也不怎么用Vector,可以自己做线程同步,也可以用Collections配合ArrayList实现线程同步。

在排序上,List是使用Collections类的sort方法,构造Comparator或者让List中的对象实现Comparable都可以。

Stack继承自Vector,在用法、线程安全方面跟Vector都差不多,有几个地方需要注意,一是add()和push(),stack是将最后一个element作为栈顶的,add()返回boolean,就是添加成功了没有,push()返回的是添加的元素,一般推荐使用push;二是peek()和pop(),这两个方法都能得到栈顶元素,区别是peek()只是读取,原栈的元素数量没有改变,pop()从字面上就能理解为“出栈”,所以原栈的栈顶元素没了,原栈的元素数量会减1。

3.Set接口及其子类HashSet,TreeSet。

public interface Set<E> extends Collection<E>

(1)java.lang.Object

继承者 java.util.AbstractCollection<E>

继承者 java.util.AbstractSet<E>

继承者 java.util.HashSet<E>

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable

HashSet默认构造创建一个HashMap对象:

  add(E):调用HashMap的put方法来完成此操作,将需要增加的元素作为Map中的key,value则传入一个之前已创建的Object对象。

  remove(E):调用HashMap的remove(E)方法完成此操作。

  contains(E):HashMap的containsKey

  iterator():调用HashMap的keySet的iterator方法。

  HashSet不支持通过get(int)获取指定位置的元素,只能自行通过iterator方法来获取。

  总结:

 ①HashSet基于HashMap实现,无容量限制;

 ②HashSet是非线程安全的。

(2)java.lang.Object

继承者 java.util.AbstractCollection<E>

继承者 java.util.AbstractSet<E>

继承者 java.util.TreeSet<E>

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable

TreeSet和HashSet的主要不同在于TreeSet对于排序的支持,TreeSet基于TreeMap实现。

总的来说,Set集合类的特点是可以去重,它们的内部实现都是基于Map的key是不可以重复的。Set集合类既然要去重,就需要比较,而比较是基于hascode()方法和equals()方法的。Set集合在对元素查重时,会先比较hashCode(),在hashCode()相等时再比较equals()是否相等,如果此时equals()又相等,意味着是同一个元素。

Java中的集合概述的更多相关文章

  1. Java中的集合框架(上)

    Java中的集合框架概述 集合的概念: Java中的集合类:是一种工具类,就像是容器,存储任意数量的具有共同属性的对象. 集合的作用: 1.在类的内部,对数据进行组织: 2.简单的快速的搜索大数据量的 ...

  2. Java 中的集合接口——List、Set、Map

    Java 中的集合接口——List.Set.Map 什么叫集合:集合就是Java API所提供的一系列类的实例,可以用于动态存放多个对象.这跟我们学过的数组差不多,那为什么我们还要学集合,我们看看数组 ...

  3. 实现java 中 list集合中有几十万条数据,每100条为一组取出

    解决"java 中 list集合中有几十万条数据,每100条为一组取出来如何实现,求代码!!!"的问题. 具体解决方案如下: /** * 实现java 中 list集合中有几十万条 ...

  4. java中对集合对象list的几种循环访问

    java中对集合对象list的几种循环访问的总结如下 1 经典的for循环 public static void main(String[] args) { List<String> li ...

  5. 菜鸟日记之 java中的集合框架

    java中的集合框架图 如图所示:java中的集合分为两种Collection和Map两种接口 可分为Collection是单列集合和Map的双列集合 Collection单列集合:继承了Iterat ...

  6. C#与java中的集合区别

    集合一般的操作       插入: add       删除: remove       查找: contains,remove java中的集合 注意哪些是接口,哪些是实现类 使用集合的时候 1. ...

  7. java中list集合的内容,如何使用像数据库中group by形式那样排序

    java中list集合的内容,如何使用像数据库中group by形式那样排序,比如:有一个 List<JavaBean> 他中包含了一些如下的内容JavaBean:name    mone ...

  8. Java中的集合类型的继承关系图

    Java中的集合类型的继承关系图

  9. (转)java中对集合对象list的几种循环访问总结

    Java集合的Stack.Queue.Map的遍历   在集合操作中,常常离不开对集合的遍历,对集合遍历一般来说一个foreach就搞定了,但是,对于Stack.Queue.Map类型的遍历,还是有一 ...

随机推荐

  1. Java学习笔记13---如何理解“子类重写父类方法时,返回值若为类类型,则必须与父类返回值类型相同或为其子类”

    子类重新实现父类的方法称重写:重写时可以修改访问权限修饰符和返回值,方法名和参数类型及个数都不可以修改:仅当返回值为类类型时,重写的方法才可以修改返回值类型,且必须是父类方法返回值的子类:要么就不修改 ...

  2. CCLuaObjcBridge - Lua 与 Objective-C 互操作的简单解决方案

    http://dualface.github.io/blog/2013/01/27/call-objectivec-from-lua/ 月初的时候,发了一篇关于 Lua 与 Java 互操作的文章,里 ...

  3. centos7下部署Django(nginx+uwsgi+python3+django)

    系统版本 centos7 python版本 使用官方python3.6.3正式版 django版本 使用本文发布时最新的1.11.7 uwsgi版本 使用本文发布时最新的2.0.15 nginx版本 ...

  4. 查看Page结构

    SQL Server存储数据的基本单元是Page,每一个Page的大小是8KB,数据文件是由Page构成的.在同一个数据库上,每一个Page都有一个唯一的资源标识,标识符由三部分组成:db_id,fi ...

  5. 2735:八进制到十进制-poj

    总时间限制:  1000ms 内存限制:  65536kB 描述 把一个八进制正整数转化成十进制. 输入 一行,仅含一个八进制表示的正整数a,a的十进制表示的范围是(0, 65536). 输出 一行, ...

  6. JavaSE学习入门

    Java基础: 1.安装JDK1.7(JDK 包括JRE,Java工具包,Java的类库) 2.编写Hello,world 程序 public class Hello{ public static v ...

  7. 【MySQL疑难杂症】如何将树形结构存储在数据库中(方案一、Adjacency List)

    今天来看看一个比较头疼的问题,如何在数据库中存储树形结构呢? 像mysql这样的关系型数据库,比较适合存储一些类似表格的扁平化数据,但是遇到像树形结构这样有深度的人,就很难驾驭了. 举个栗子:现在有一 ...

  8. NOIP2016提高组初赛(2)四、读程序写结果3、求最长回文子序列

    #include <iostream> using namespace std; int lps(string seq, int i, int j) { int len1, len2; i ...

  9. c++对象在lua层的生命周期与内容扩展

    前言 上一篇博客记录了 tolua++ 将 c++类型,变量,函数,以及对象导出到 lua 的过程,这篇博客就接着记录一下 c++对象的内存回收以及c++对象数据和方法在lua中的扩展. 首先 tol ...

  10. vue页面切换效果(slide效果切换)

    最近碰到一个需求,单页应用里面页面切换的效果需要做成跟轮播图滑动slide一样,让这个页面在切换时感觉是一个页面.反复琢磨的vue里面的transition,最终将实现的核心代码贴出来.这里实现的是上 ...