zoukankan      html  css  js  c++  java
  • Java容器

    Java容器

    容器主要包括Collection和Map两种,Collection存储着对象的集合,而Map存储着键值对(两个对象)的映射表。
    Java中常用的线程安全的集合类有Vector、Hashtable、ConcurrentHashMap、Stack
    

    Set

    --TreeSet(有序):基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如HashSet,HashSet查找的时间复杂度为O(1),TreeSet则为O(logN)。
    --HashSet(无序):基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用Iterator 遍历HashSet得到的结果是不确定的。
    --LinkedHashSet:具有HashSet的查找效率,且内部使用双向链表维护元素的插入顺序。
    

    List

    --ArrayList:基于动态数组实现,支持随机访问。(扩容1.5倍)
    --Vector:和 ArrayList 类似,但它是线程安全的。底层用Synchronize实现线程安全。(扩容2倍)
    --LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。
    

    Queue

    --LinkedList:可以用它来实现双向队列。
    --PriorityQueue:基于堆结构实现,可以用它来实现优先队列。
    

    Map

    --TreeMap(有序):基于红黑树实现。
    --HashMap(无序):由数组+链表+红黑树实现。
    --HashTable:和HashMap类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入HashTable并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用ConcurrentHashMap来支持线程安全,并且ConcurrentHashMap的效率会更高,因为ConcurrentHashMap引入了分段锁。
    --LinkedHashMap:继承自HashMap,底层仍是由数组和链表或红黑树组成。使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
    

    List、Set、Map的区别

    --List:存储的元素是有序的、可重复的。
    --Set:存储的元素是无序的、不可重复的。
    --Map:使用键值对(kye-value)存储,Key是无序、不可重复的,value是无序、可重复的,每个键最多映射到一个值。
    

    迭代器

    Iterator对象称为迭代器(设计模式的一种),迭代器可以对集合进行遍历,但每一个集合内部的数据结构可能是不尽相同的,所以每一个集合存和取都很可能是不一样的,虽然我们可以人为地在每一个类中定义 hasNext() 和 next() 方法,但这样做会让整个集合体系过于臃肿。于是就有了迭代器。
    
    迭代器是将这样的方法抽取出接口,然后在每个类的内部,定义自己迭代方式,这样做就规定了整个集合体系的遍历方式都是 hasNext()和next()方法,使用者不用管怎么实现的,会用即可。
    迭代器的定义为:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。
    
    Iterator主要是用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException异常。
    

    线程不安全的集合

    我们常用的Arraylist,LinkedList,Hashmap,HashSet,TreeSet,LinkedHashSet,TreeMap,PriorityQueue都不是线程安全的。解决办法很简单,可以使用线程安全的集合来代替。
    
    如果你要使用线程安全的集合的话, java.util.concurrent 包中提供了很多并发容器供你使用:
        --ConcurrentHashMap: 可以看作是线程安全的HashMap。
        --CopyOnWriteArrayList:可以看作是线程安全的ArrayList,在读多写少的场合性能非常好,远远好于Vector.
        --ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
        --BlockingQueue: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
        --ConcurrentSkipListMap :跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。
    

    ArrayList源码

     --ArrayList 是基于数组实现的,所以支持快速随机访问。
     --数组的默认大小为 10。
     --添加元素时使用ensureCapacityInternal()方法来保证容量足够,如果不够时,需要使用grow()方法进行扩容,新
    容量的大小为oldCapacity+(oldCapacity >> 1) ,也就是旧容量的1.5倍。
     --删除元素需要调用System.arraycopy()将index+1后面的元素都复制到index位置上,该操作的时间复杂度为 O(N)。
     --Fail-Fast:modCount用来记录ArrayList结构发生变化的次数。在进行序列化或者迭代等操作时,需要比较操作前后 modCount是否改变,如果改变了需要抛出ConcurrentModificationException。
    

    CopyOnWriteArrayList

    读写分离,但会导致内存占用和数据不一致
    写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
    写操作需要加锁,防止并发写入时导致写入数据丢失。
    写操作结束之后需要把原始数组指向新的复制数组。
    

    HashMap

    1、HashMap自Java1.8起,采用数组+链表+红黑树的数据结构
    		   Java1.8以前,采用数组+链表的结构
    
    2、HashMap初始容量为什么是2的n次幂 以及 扩容为什么是2倍的形式
    	HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,
    是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低!
    	hash = hashCode();
    	取模运算:hash % 16 = {0 - 15};
    	位运算:h & (length - 1);
    	如果要想位运算约等于取模运算,那length必须是2的n次幂。
    	
    3、Java7的链表插入,采用头插法。(头插法会造成死锁)
       Java8的链表插入,采用尾插法。
       
    4、HashMap的加载因子为什么是0.75
    	加载因子过高的话,例如1,则查询的时间复杂度增加。
    	而加载因子过低的话,例如0.5,空间利用率也会降低。
    	所以为了均衡时间和空间,采用0.75f
    
    5、链表的长度为8时,就马上转红黑树吗
    	当数组长度小于64时,优先扩容,当大于等于64时,才会转红黑树。
    	链表转红黑树,阈值等于8,但是链表的实际长度是9。
    	
    6、什么是哈希冲突,如何解决?
    	哈希函数处理后,存在不同的数据对应相同的哈希值,产生哈希冲突。
    	解决方法:开放地址法、链地址法、再哈希法、建立一个公共溢出区。
    	
    7、HashMap底层为什么使用红黑树不用AVL树?
    --红黑树牺牲了一些查找性能 但其本身并不是完全平衡的二叉树。因此插入删除操作效率略高于AVL树
    --AVL树用于自平衡的计算牺牲了插入删除性能,但是因为最多只有一层的高度差,查询效率会高一些。
    --在CurrentHashMap中是加锁了的,实际上是读写锁,如果写冲突就会等待,如果插入时间过长必然等待时间更长,而红黑树相对AVL树他的插入更快!
    
    8、JDK1.8以后的hashmap为什么在链表长度为8的时候变为红黑树
    因为树节点所占空间是普通节点的两倍,所以只有当节点足够多的时候,才会使用树节点。
    也就是说,节点少的时候,尽管时间复杂度上,红黑树比链表好一点,但是红黑树所占空间比较大,
    综合考虑,认为只能在节点太多的时候,红黑树占空间大这一劣势不太明显的时候,才会舍弃链表,使用红黑树。
    在理想状态下,受随机分布的hashCode影响,链表中的节点遵循泊松分布,而且根据统计,链表中节点数是8的概率
    已经接近千分之一,而且此时链表的性能已经很差了。所以在这种比较罕见和极端的情况下,才会把链表转变为红黑树。
    因为链表转换为红黑树也是需要消耗性能的,特殊情况特殊处理,为了挽回性能,权衡之下,才使用红黑树,提高性能。
    也就是大部分情况下,hashmap还是使用的链表,如果是理想的均匀分布,节点数不到8,hashmap就自动扩容了。
    
    

    ConcurrentHashMap

    --ConcurrentHashMap和HashMap实现上类似,最主要的差别是ConcurrentHashMap采用了分段锁(Segment),每个分段锁维护着几个桶(HashEntry),多个线程可以同时访问不同分段锁上的桶,从而使其并发度更高(并发度就是Segment的个数)。
    Segment继承自ReentrantLock。
    --默认的并发级别为 16,也就是说默认创建 16 个 Segment。
    --每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数。
    --JDK1.7使用分段锁机制来实现并发更新操作,核心类为Segment,它继承自重入锁ReentrantLock,并发度与Segment数量相等。JDK1.8使用了CAS操作来支持更高的并发度,在CAS操作失败时使用内置锁synchronized。并且JDK 1.8的实现也在链表过长时会转换为红黑树。
    
    
  • 相关阅读:
    The Future of Middleware and the BizTalk Roadmap
    FW: How to spawn a process that runs under the context of the impersonated user in Microsoft ASP.NET pages
    Strips illegal Xml characters
    luogu P2280 激光炸弹(二维前缀和)
    luogu P2704 炮兵阵地(经典状态压缩DP)
    SP1716 GSS3 Can you answer these queries III (线段树维护最大连续子段和)
    二分图判定、匹配问题
    C++语法综合 | 基于char*设计一个字符串类MyString
    luogu P1044 火车进出栈问题(Catalan数)
    C++设计模式 | 三种设计模式基础
  • 原文地址:https://www.cnblogs.com/yzhengy/p/13226886.html
Copyright © 2011-2022 走看看