专栏笔记(二)集合框架

集合框架

1、List、Set、Map区别:

List:List接口存储不唯一有序的对象。

Set:不允许重复。

Map:键值对存储,两个Key可以引用相同的对象,但Key不能重复。

2、ArrayList和LinkedList区别:

a.保证线程安全。都是不同步的,即不保证县线程安全。

b.底层数据结构。ArrayList底层是Object数组;LinkedList底层是双向链表结构。

c.插入/删除和元素位置的关系。ArrayList数组存储,受元素位置影响。LinkedList链表存储,不受元素位置的影响。

d.快速随机访问。ArrayList支持,LinkedList不支持。快速随机访问是通过元素的序号快速获取元素对象。

e.内存空间占用。ArrayList体现在list列表的结尾会预留一定容量空间。LinkedList体现在其每一个元素都需要消耗更多空间(存放直接后继和直接前驱及数据)。

3、list遍历方式:

实现RandomAccess接口的list,优先选择普通for循环,其次foreach。

未实现RandomAccess接口的list,优先选择iterator遍历(foreach遍历的底层也是通过iterator实现的),大size的数据,不适用普通for循环。

4、双向链表和双向循环链表

双向链表。两个指针,一个prev指向前一个节点,一个next指向后一个节点。

双向循环链表。最后一个节点的next指向head,而head的prev指向最后一个节点,构成一个环。

5、ArrayList和Vector的区别:

Vector类的所有方法都是同步的,两个线程可以安全的访问一个Vector对象,但不适用于一个线程,一个线程访问Vector,代码在同步操作上耗费大量时间。

ArrayList不是同步的,用在不需要保证线程安全时。

6、HashMap和Hashtable的区别:

a.线程安全。HashMap非线程安全。hashTable线程安全,内部方法经synchronized修饰。

b.效率。HashMap效率比HashTable高。

c.Null key 和 Null value。HashMap中,null可作为键,只能有一个可以有一个/多个键对应的值为null。HashTable中put的键值不能为null,若有抛出NullPointerException。

d.初始容量、每次扩容大小。

①默认初始大小。HashMap为16,每次扩充,容量是原来的2倍。HashTable为11,每次扩容,容量是原来的2n+1倍。

②创建时给定容量初始值时,HashMap会将其扩充为2的幂次方。HahsTable直接使用给定的大小。

e.底层数据结构。HashMap在链表长度>阈值(默认8)时,将链表转化为红黑树,减少搜索时间。HashTable则没有该机制。

下面方法保证了 HashMap 总是使用2的幂作为哈希表的大小。
    /**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

7、HashMap 和 HashSet 区别:

HashSet底层基于HashMap实现,其方法(除了clone()、writeObject()、readObject()外)是直接调用Hashmap的方法。

HahMap HashSet
实现Map接口 实现Set接口
存储键值对 存储对象(仅)
put()方法添加元素 add()方法添加元素
使用键(key)计算HashCode 使用成员对象计算HashCode值,两个对象的hashcode可能相同,故用equals()方法判断对象相等性。

HashSet检查重复:把对象加入HashSet时,先计算对象的和hashcode值判断对象加入的位置,并与其它加入的对象hashcode值比较,若不相等,即认为没有重复出现。若存在相同hashcode值的对象,会调用equals()方法检查是否真的相同。两者还相同,HashSet不会让对象加入成功。

hashCode() 和 equals():

a.两个对象相等,则hashCode一定相同。

b.两个对象相等,两个对象的equals方法返回true。

c.两个对象有相同的hashcode值,但不一定相等。

d.equals方法若被覆盖,则hashCode方法必须被覆盖。

e.hashCode()默认是对堆上的对象产生独特值。若没有重写hashCode(),则该class的两个对象一定不会相等(即使指向相同的数据)。

8、== 和 equals()的区别:

a.==:判断两个变量/实例是否指向同一个内存空间。equals:判断两个变量/实例指向的内存空间的值是否相同。

b.==:对内存地址进行比较。equals:对字符串内容进行比较。

c.==:引用是否相同。equals:值是否相同。

9、HashMap底层实现

JDK1.8之前:

HashMap底层是数组+链表的链表散列。通过key的hashCode经扰动函数处理得到hash值,再通过(n-1)&hash判断当前元素存放的位置(n是数组长度),若当前位置存在元素,则判断两个元素(该元素和要存入的元素)的hash值、key是否相同。若相同,直接覆盖;不同就通过拉链法解决冲突。

注:扰动函数:HashMap的hash方法,减少碰撞。使用hash方法是为了防止一些实现比较差的hashClde()方法。

JDK1.8:hash方法更简化,原理不变。

拉链法:将链表和数组相结合,即创建一个链表数组,数组中每一格是一个链表。遇到哈希冲突,就将冲突的值加到链表中。

jdk1.8之前的内部结构

JDK1.8之后:解决哈希冲突有较大变化,当链表长度>阈值(默认8)时,链表转化为红黑树,减少搜索时间。

JDK1.8之后的HashMap底层数据结构

TreeMap、TreeSet和HashMap(JDK1.8之后)底层都用红黑树。红黑树是为了解决二叉查找树的缺陷,因为二叉查找树某些情况会退化成一个线性结构。

10、HashMap长度是2的幂次方:

数组下标计算方法:(n-1)&hash,n是数组长度。

取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。

11、ConcurrentHashMap 和 HashTable 的区别:狐妖体现在实现线程安全的方式上。

a.底层数据结构。ConcurrentHashMap在1.7采用分段数组+链表;1.8的和HashMap一样是数组+链表/红黑二叉树。hashTable和1.8之前的HashMap采用数组+链表形式。|| 数组是HashMap的主体,链表主要为了解决哈希冲突而存在。

b.实现线程安全的方式(重要)。①JDK1.7之前,ConcurrentHashMap(分段锁)对整个桶分割分段(Segment),每把锁只锁容器中一部分数据,多线程访问容器里不同数据段的数据,就不会产生锁竞争,提高并发访问率。

JDK1.8 直接用Node 数组+链表+红黑树的结构,并发控制用synchronized和CAS操作,摒弃Segment概念。

②HashTable(同一把锁):使用synchronized保证线程安全,效率低。在一个线程访问同步方法时,其他线程也访问同步方法就可能进入阻塞或轮询状态。如用put添加元素,另一个线程不能使用put添加元素,也不能用get,竞争越来越激烈、效率越来越低。

imgimg

ConcurrentHashMap线程安全的实现

JDK1.7:将数据分成一段一段来存储,再给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其它段的数据也能被其他线程访问。

ConcurrentHashMap是由Segment数组结构和HashEntry数据结构组成。Segment实现ReentrantLock,是一种可重入锁,扮演锁的角色。HashEntry用于存储键值对数据。

static class Segment<K,V> extends ReentrantLock implements Serializable {

}

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

JDK1.8:

ConcurrentHashMap采用CAS、synchronized保证并发安全,取消了Segment分段锁。数据结构和HashMap1.8类似为数组+链表/红黑二叉树。Java 8在链表长度>阈值(8)时,将链表(寻址时间复杂度O(N))转换为红黑树(寻址时间复杂度0(log(N))。

synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发,效率又提升N倍。

12、comparable和Comparator的区别

comparable接口出自java.lang包,排序方法compareTo(Object obj)

comparator接口出自java.util包,排序方法compare(Object obj1,Object obj2)。

13、集合框架底层数据结构总结

Collection

------List

       -----Arraylist:Object数组

       -----Vector:Object数组

       -----LinkedList:双向链表(JDK1.6之前循环链表,JDK1.7取消循环)

------Set

       -----HashSet(无序、唯一/不重复):基于HashMap,底层采用HashMap保存元素。

       -----LinkedHashSet:LinkedHashSet继承HashMap,其内部通过LinkedHashMap实现。

       -----TreeSet(有序、唯一/不重复):红黑树(自平衡排序二叉树)

Map

------HahsMap:JDK1.8之前HashMap由数组+链表组成,数组是主题,链表为了解决哈希冲突(拉链法解决冲突)。

                          JDK1.8之后在解决哈希冲突上变化较大,当链表长度>阈值(默认8)时,链表转化为红黑树,减少搜索时间。

------LinkedHashMap:继承自HahsMap,底层仍是拉链式散列结构即数组+链表/红黑树,另外LinkedHahsMap在此基础上增加了一条双向链表,使得保证键值对的插入顺序等。

------HashTable:数组+链表组成,数组是主体,链表为了解决哈希冲突。

------TreeMap:红黑树(自平衡排序二叉树)。

14、集合的选用:根据各自特点选用。

根据键值获取元素值,选Map接口的集合。

------需要排序时,选TreeMap;

------不需要排序时,选HashMap;

------需要保证线程安全时,选ConcurrentHashMap;

只需要存放元素时,选实现Collention接口的集合;

需要保证元素唯一时,选实现Set接口的集合,如TreeSet、HashSet;

不需要保证元素唯一时,选实现List接口的集合,如ArrayList、LinkedList;

再根据实现这些接口的集合的特点选用。

猜你喜欢

转载自blog.csdn.net/cheng59241/article/details/89787045