zoukankan      html  css  js  c++  java
  • 集合框架

    基于jdk1.7

    ArrayList和LinkedList的区别
        ArrayList底层实现是可变大小的数组,在内存是一片连续的空间,支持快速随机访问,时间复杂度O(1),插入和删除一个元素的时间复杂度是O(n)。ArrayList默认初始容量为10,每次添加元素前会检查容量,如果容量不足,则扩容至1.5倍,并将原有元素复制到新数组,并指向新数组。

        LinkedList底层实现是双向链表,并且实现了Deque(双端队列)接口,在内存中不是连续的空间,插入和删除元素的时间复杂度为O(1)。
        LinkedList中定义了first和last两个指针,链表中节点包含数据项item,next和prev两个指针。add remove操作本质上是对链表的添加、删除元素操作。

    Vector的实现原理


    HashMap的实现原理
        HashMap中定义了一个数组,数组中的每一项是一个链表(Entry),Entry中包含key,value和一个指向next Entry的指针。
        HashMap中允许存放NULL key和NULL value,NULL key的键值对存放在数组下标为0的链表中。
        put操作:
        每次添加元素(put)的时候,先计算出key的hash,根据hash找到key在数组中的索引,如果对应索引的链表的节点key的hash值与要放置元素的key的hash值相同,并且它们key的equals方法返回true,就用待放置的元素的vlaue替换链表对应节点的value;如果不存在,在数组对应的索引下创建一个链表节点,将key,value,hash放入节点,并将next指针指向此链表的头结点(即采用头插法)。 在添加元素时,如果发现HaspMap的容量达到临界值(threshold,值为容量*负载因子)并且要放置的位置不为空时,将HashMap的容量扩至两倍(默认初试容量为16,负载因子为0.75),并重新计算hash和要放置的位置(原来的数据都要重新计算放置的位置并重新放入,这是个非常耗时的操作,如果知道要放置元素的个数,在初始化的时候就指定容量,可以避免扩容操作)。hashmap的容量设置为2^n,在根据hash计算key在数组中的索引的时候可以减少hash冲突,计算hash在数组中索引的方法为h & (length-1),由于length为2^n,length-1相当于把最高位1变成0,剩下位0变成1,在和hash做&运算,相当hash对length取模,即hash%length,由于低位全是1,所以计算之后得到的索引低位与hash低位相同,这样就减少了得到相同索引的几率,减少处理冲突的次数,查找元素时,也减少了遍历数组元素链表的几率,提高查找效率。
        get操作:
        查找元素时,根据key计算出hash,再根据hash找到key在数组中的索引,遍历对应索引下的链表,如果链表中节点的hash和待查找key的hash相等,且它们key的equals方法返回true,则返回节点的value,如果未查找到,返回null。
        remove操作:
        删除元素时,根据key计算出hash,再根据hash找到key在数组中的索引,遍历对应索引下的链表,如果链表中节点的hash和待查找key的hash相等,且它们key的equals方法返回true,则删除节点,并返回被删除元素的value。
        HashMap不是线程安全的,在多线程情况下会出现死循环,在扩容后reshah时可能会出现环形链表,导致在get操作时进入死循环。

    LinkedHashMap实现原理
        LinkedHashMap继承自HashMap,可以按照元素插入顺序或者访问顺序迭代输出元素,默认是按照元素插入顺序。
        LinkedHashMap中定义了一个final修饰的boolean类型变量accessOrder,在构造函数中通过初始化此变量来指定迭代顺序,默认是false,即按照插入顺序。LinkedHashMap中定义了静态内部类Entry,继承自HashMap的Entry,但是多了before和after两个指针,即此Entry为双向链表,通过操作两个指针来保证迭代的顺序。
        LinkedHashMap有个recordAccess()函数,当map中的元素被访问或者被修改时都会调用此方法。此方法中会判断accessOrder是否为true,是则将被访问的元素从链表节点中删除,并添加到链表头。、
        

    TreeMap的实现原理

    如何实现线程安全的HashMap?
        使用Collections.synchronizedMap(hashMap)方法包装hashMap,构造一个SynchronizedMap类,其中包含被封装的map对象和一个final域的Object类型变量mutex,在put等操作中使用synchronized锁住mutex,同一时刻只允许一个线程操作hashMap。

    fail-fast机制
        fail-fast 机制是 java 集合(Collection)中的一种错误机制。 当多个线程对同一个集合的内容进行操作时,或者使用for循环迭集合的同时增加或删除集合中的元素时,就可能会产生 fail-fast 事件,抛出 ConcurrentModificationException。(通过迭代器进行remove元素不会抛出异常)。集合有个modCount变量,增加或删除集合的元素时,modCount会自增, 在初始化迭代器的时候,会将modCount变量赋值给迭代器的expectedModCount变量,在每次调用迭代器的next方法时,会检查modCount和expectedModCount变量是否相等,不相等直接抛出异常。
        
    ConcurrentHashMap的实现原理
        ConcurrentHashMap中定义了一个Segment[]数组segments,大小为16,Segment继承自ReentrantLock(可重入锁),可充当锁的角色,包含一个HashEntry[]数组table,并且包含一个count计数器,记录每个segment中包含的hashEntry的总数(不在ConcurrentHashMap中定义此计数器是为了“避免热点域”,这样在更新计数器时不用锁住整个map)。HashEntry中定义了hash(final),key(final),value(volatile),以及一个next(volatile)指针,HashEntry存放键值对,可构成链表。
        ConcurrentHashMap不允许NULL key和NULL value。
        ConcurrentHashMap采用锁分段技术,最多允许16个线程并发操作map(put、remove操作等结构性的修改操作),允许完全并发地读取map。
        put操作:
        计算出key的hash,根据hash找到对应的segment,调用segment的put方法。segment的put方法锁住此分段,通过hash对hashEntry table数组长度取模计算出key在table中的下标,找到此下标下的HashEntry链表头节点,遍历此链表,如果key与链表节点key相等并且它们的equals方法返回true,说明key已存在,则替换value,否则构造一个新的链表节点存放key、value,并插入链表表头。最后在finally块中调用unlock方法释放锁。
        remove操作:
        根据key的hash找到segment,调用segment的remove方法。segment的remove方法锁住此分段,根据hash计算出存放数据的HashEntry,遍历此链表,如果key与链表节点key相等并且它们的equals方法返回true,则删除此链表节点。
        get操作:
        ConcurrentHashMap的get操作不需要加锁,除非得到的值是空的才需要重新加锁,因为HashEntry中的value定义为volatile类型,根据java内存模型的happens-before原则,对volatile变量的写操作happens-before于对其的读操作,即写对读可见,所以即使另一个线程修改了value的值,读线程也能获取到最新的值。值为空的时候需要加锁,原因是ConcurrentHashMap中不允许存储NULL value,value为空说明写线程构造HashEntry过程未完成(赋值操作发生了重排序),则加锁重新获取,等待put操作释放锁之后获取最新的value。 
        size操作:
        计算size需要统计所有Segment的元素之和,Segment的count变量定义为volatile,可以保证在读取每个Segment中元素个数的时候读取到最新的值,但是累加之后可能count又发生了变化,所以size计算就不准确了。安全的做法是计算size得时候把segment都锁起来,这样比较低效。ConcurrentHashMap的做法是先尝试两次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小。

    HashTable实现原理


    HashSet的实现原理
        HashSet底层实现是HashMap,定义了一个Object类型的类变量PRESENT,作为HashMap的vlaue,添加的新元素作为HashMap的key,所以保证了HashSet中不会有重复的元素。 
        add(e)方法调用map.put(e, PRESENT)方法,如果添加的元素不存在,则返回true;否则返回false。
        remove(e)方法调用map.remove(e)方法 ,如果删除的元素存在,则返回true,否则返回false。

    LinkedHashSet实现原理


    TreeSet的实现原理
  • 相关阅读:
    MySQL for OPS 02:SQL 基础
    Samba:基于公网 IP 的服务访问
    MySQL for OPS 01:简介 / 安装初始化 / 用户授权管理
    Samba:打造企业级授权文件共享服务器
    嵌入式web server——Goahead移植要点
    libConfuse的使用
    【工具篇】notepad++
    使用sprintf打印float并控制小数位数时引起的问题
    【工具篇】source Insight
    【工具篇】xshell
  • 原文地址:https://www.cnblogs.com/lolau/p/7144997.html
Copyright © 2011-2022 走看看