java ArrayList、Vector和LinkedList

本文详细介绍了Java集合框架的各种实现,包括List、Set、Map的主要类别及其特性。对比了ArrayList、LinkedList、Vector在存储方式上的差异及适用场景,阐述了HashMap、TreeMap、HashSet等数据结构的原理。

大家都知道在JAVA中如果我们要存储和树立一组同类型的数据的时候,我们一般都采用数组来存储。但是大家知道数组一旦被创建,其长度就固定不变了,所以使用数组的时候需要知道或者说是我们要估算一下数据的规模,以方便我们创建长度适合的数组。如果我们估计的长度比实际需要的长度大,那则会浪费存储空间;若比实际长度小,则处理数据时会遇到麻烦,因此,用数据存储数目不确定的元素那样是一个不明智的选择。

ArrayList 和Vector是采取数组体式格式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,都容许直接序号索引元素,然则插入数据要设计到数组元素移动等内存操纵,所以索引数据快插入数据慢,Vector因为应用了synchronized办法(线程安然)所以机能上比ArrayList要差,LinkedList应用双向链表实现存储,按序号索引数据须要进行向前或向后遍历,然则插入数据时只须要记录本项的前后项即可,所以插入数度较快!Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack

├Set
│├HashSet
│├LinkedHashSet
│└TreeSet

Map
├Hashtable
├HashMap
├LinkedHashMap
├LinkedHashMap
└TreeMap

Collection接口
  Collection是最根蒂根基的凑集接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection容许雷同的元素而另一些不可。一些能排序而另一些不可。Java SDK不供给直接持续自Collection的类,Java SDK供给的类都是持续自Collection的“子接口”如List和Set。
  所有实现Collection接口的类都必须供给两个标准的机关函数:无参数的机关函数用于创建一个空的Collection,有一个Collection参数的机关函数用于创建一个新的Collection,这个新的Collection与传入的Collection有雷同的元素。后一个机关函数容许用户复制一个Collection。
  如何遍历Collection中的每一个元素?非论Collection的实际类型如何,它都支撑一个iterator()的办法,该办法返回一个迭代子,应用该迭代子即可一一接见Collection中每一个元素。典范的用法如下:
    Iterator it = collection.iterator(); // 获得一个迭代子
    while(it.hasNext()) {
      Object obj = it.next(); // 获得下一个元素
    }
  由Collection接口派生的两个接口是List和Set。

List接口
  List是有序的Collection,应用此接口可以或许正确的把握每个元素插入的地位。用户可以或许应用索引(元素在List中的地位,类似于数组下标)来接见List中的元素,这类似于Java的数组。
和下面要提到的Set不合,List容许有雷同的元素。
  除了具有Collection接口必备的iterator()办法外,List还供给一个listIterator()办法,返回一个ListIterator接口,和标准的Iterator接口比拟,ListIterator多了一些add()之类的办法,容许添加,删除,设定元素,还能向前或向后遍历。
  实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。

LinkedList类
  LinkedList实现了List接口,容许null元素,双向循环链表的数据结构,由于是基于链表的,所以无法法实现随机访问的,只能顺序访问,是不同步的,增删元素的速度很快。LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

ArrayList类
   内部是数组数据结构,是不同步的,查询的速度快。它容许所有元素,包含null。ArrayList没有同步。
  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小,默认是10。这个容量可跟着络续添加新元素而主动增长,容量不够时默认增加两倍。如果指定容量,容量必须大于当前容量的三倍时才生效。当须要插入多量元素时,在插入前可以调用ensureCapacity办法来增长ArrayList的容量以进步插入效力。

Vector类
  Vector很是类似ArrayList,然则Vector是同步的。默认容量是10,容量增加策略默认是增加一倍,如果指定容量则必须大于当前容量的两倍。

Stack 类
  Stack持续自Vector,Stack 类表示后进先出(LIFO)的对象堆栈,Stack刚创建后是空栈。

List:
     |--Vector:内部是数组数据结构,是同步的。增删,查询都很慢!
     |--ArrayList:内部是数组数据结构,是不同步的。替代了Vector。查询的速度快。
     |--LinkedList:内部是链表数据结构,是不同步的。增删元素的速度很快。
如果经常对数组做随机插入操作,特别是插入的比较靠前,那么LinkedList的性能优势就非常明显,而如果都只是末尾插入,则ArrayList更占据优势,如果需要线程安全,则使用Vector或者创建线程安全的ArrayList。在使用基于数组实现的ArrayList 和Vector 时我们要指定初始容量,因为我们在源码中也看到了,在添加时首先要进行容量的判断,如果容量不够则要创建新数组,还要将原来数组中的数据复制到新数组中,这个过程会减低效率并且会浪费资源。

Set接口
  Set是一种无序的不包含重复元素的Collection,即随便率性的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。

HashSet类

HashSet中存放的元素是无序的,底层是用HashMap实现的,其中key是要放入的元素,value是一个Object类型的名为PRESENT的常量,由于用到了散列函数,因此其存取速度是非常快的,在地址空间很大的情况下它的存取速度可以达到O(1)级。

LinkedHashSet

LinkedHashSet低层是LinkedHashMap,将数据按存储顺序或访问顺序排序。

TreeSet

TreeSet低层是TreeMap,将数据自然排序或按key排序。

Map接口
  Map没有持续Collection接口,Map供给key到value的映射。一个Map中不能包含雷同的key,每个key只能映射一个value。Map接口供给3种凑集的视图,Map的内容可以被算作一组key凑集,一组value凑集,或者一组key-value映射。

HashMap

HashMap是一种基于哈希表(hash table)实现的map,哈希表(也叫关联数组)一种通用的数据结构,由数组加链表实现,其概念也比较简单:key经过hash函数作用后得到一个槽(buckets或slots)的索引(index),槽中保存着我们想要获取的值,如下图所示

线程非安全,并且允许key与value都为null值,不保证其内部元素的顺序,而且随着时间的推移,同一元素的位置也可能改变(resize的情况)
put、get操作的时间复杂度为O(1)。遍历其集合视角的时间复杂度与其容量(capacity,槽的个数)和现有元素的大小(entry的个数)成正比,所以如果遍历的性能要求很高,不要把capactiy设置的过高或把平衡因子(load factor,当entry数大于capacity*loadFactor时,会进行resize,reside会导致key进行rehash)设置的过低。
HashMap的桶数目,即Entry[] table数组的长度,由于数组是内存中连续的存储单元,它的空间代价是很大的,但是它的随机存取的速度是Java集合中最快的。我们增大桶的数量,而减少Entry<Key,Value>链表的长度,来提高从HashMap中读取数据的速度。这是典型的拿空间换时间的策略。但是我们不能刚开始就给HashMap分配过多的桶(即Entry[] table 数组起始不能太大),这是因为数组是连续的内存空间,它的创建代价很大,况且我们不能确定给HashMap分配这么大的空间,它实际到底能够用多少,为了解决这一个问题,HashMap采用了根据实际的情况,动态地分配桶的数量。
HashMap的权衡策略 
要动态分配桶的数量,这就要求要有一个权衡的策略了,HashMap的权衡策略是这样的:
如果HashMap的大小> HashMap的容量(即Entry[] table的大小)*加载因子(经验值0.75),则HashMap中的Entry[] table 的容量扩充为当前的一倍;然后重新将以前桶中的Entry<Key,Value>链表重新分配到各个桶中。上述的 HashMap的容量(即Entry[] table的大小) * 加载因子(经验值0.75)就是所谓的阀值(threshold):
阀值(threshold)=容量(capacity)*加载因子(load factor)
容量(capacity):是指HashMap内部Entry[] table线性数组的长度,默认是16
加载因子(load factor):默认为0.75
阀值(threshold):当HashMap大小超过了阀值,HashMap将扩充2倍,并且rehash。

put()方法-向HashMap存储键值对<Key,Value>
a.  获取这个Key的hashcode值,根据此值确定应该将这一对键值对存放在哪一个桶中,即确定要存放桶的索引;
b.  遍历所在桶中的Entry<Key,Value>链表,查找其中是否已经有了以Key值为Key存储的Entry<Key,Value>对象,
c1. 若已存在,定位到对应的Entry<Key,Value>,其中的Value值更新为新的Value值;返回旧值;
c2. 若不存在,则根据键值对<Key,Value> 创建一个新的Entry<Key,Value>对象,然后添加到这个桶的Entry<Key,Value>链表的头部。
d.  当前的HashMap的大小(即Entry<key,Value>节点的数目)是否超过了阀值,若超过了阀值(threshold),则增大HashMap的容量(即Entry[] table 的大小),并且重新组织内部各个Entry<Key,Value>排列。

HashMap在确定Key是否在HashMap中存在的要求有两个:
1. Key值是否相等;
2. hashcode是否相等;
所以我们在定义类时,如果重写了equals()方法,但是hashcode却没有保证相等,就会导致当使用该类实例作为Key值放入HashMap中,会出现HashMap“工作异常”的问题,会出现你不希望的情况。

LinkedHashMap

LinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。
LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。

其实LinkedHashMap几乎和HashMap一样,不同的是它定义了一个Entry<K,V> header,这个header不是放在Table里,它是额外独立出来的。LinkedHashMap通过继承hashMap中的Entry<K,V>,并添加两个属性Entry<K,V>  before,after,和header结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。

TreeMap

TreeMap的实现是红黑树算法的实现,TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap。

HashTable

Hashtable几乎可以等价于HashMap,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

CurrentHashMap

Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

 

 

 

转载于:https://my.oschina.net/u/2000675/blog/841186

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值