JavaJava 基础知识

Java 基础 之 Collection

2018-10-25  本文已影响0人  蓉漂里的小白

Java的Collection一直都是很经典的一个话题,花了些时间把常用的几个类做了一下整理。记录一下自己的学习并分享心得!

Java集合框架Collection是由一套设计优良的接口和类组成的,使我们操作成批的数据或对象元素极为方便,这些接口和类有很多对抽象数据类型操作的API。并且Java用面向对象的设计对这些数据结构和算法进行了封装,这就极大的减化了我们在编程上的工作量。

下面的图是一张Java Collection的UML, (从CSDN的一个博客上拿的,总结很详细,访问更清晰的图片:https://blog.csdn.net/u010887744/article/details/50575735)

我们常用的类有如下几个:Vector, ArrayList, LinkedList, HashSet,HashTable, HashMap

Vector, ArrayList, LinkedList都实现的List接口,继承的AbstractList抽象类

HashSet实现的是set接口,继承的AbstractSet抽象类

HashMap实现的是map接口,继承的AbstractMap抽象类,

HashTable实现的是map接口,继承的Dictionary类

一:List


    List存储一组不唯一,有序(按插入顺序存储)的对象,类似于Java数组,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素。

1:Vectory

    内部维护了一个Object的数组,在源码中Vectory定义了一个Object[]的成员变量

    protectedObject[] elementData;

    可以插入重复的元素,在源码中可以看到,只要数组有空间,就直接加到树组的下一个单元

    public synchronized voidadd (E obj) {

        modCount++;

        ensureCapacityHelper(elementCount + 1);

        elementData[elementCount++] = obj;

        }

    存储的顺序和插入的顺序一至,因为,Vectory的内部实现就是一个对象数组,如果我们不指定插入的位置,那默认就是将新元素插入到尾断

    查询速度快,可以通过索引快速随机读取

    添加,删除慢,需要移动数据

    线程安全,Vectory里面的所有函数都添加了synchronized关键字,Vectory是一个线程安全的collection

    Vectory的遍历方式:

2: ArrayList

    和Vectory一样,内部实现也是一个object数组,用来存储存储一组不唯一,有序(按插入顺序存储)的对象,不同的是,ArrayList是非同步的,所以在单线程环境中一般使用ArrayList,在多线程环境中使用Vectory

    内部维护了一个Object的数组,在源码中ArrayList定义了一个Object[]的成员变量

    private transientObject[] elementData;

    可以插入重复的元素,在源码中可以看到,只要数组有空间,就直接加到数组的下一个单元

    public booleanadd(E e) {

          ensureCapacity(size + 1);

        elementData[size++] = e;

          return true;

      }

    存储的顺序和插入的顺序一致

    查询速度快,可以通过索引快速随机读取

    添加,删除慢,需要移动数据

    线程不安全, ArrayList里面的所有函数都没有同步

  ArrayList的遍历方法:

3: LinkedList

    LinkedList和ArrayList,Vectory都不一样,它是一个双向链表。用来存储存储一组不唯一,有序(按插入顺序存储)的对象

    在LinkedList内部维护了2个Node结点,一个Prev,一个next.

    transient Node<E> first; //链表的头结点

    transient Node<E> last; //链表的尾节点

    可以插入重复的元素,而且存储的顺序和插入的顺序一至,在源码中每次添加新元素都是添加到末尾

public boolean add(E e) {

    final Node<E> l = last;

    final Node<E> newNode = new Node<>(l, e, null);

    last = newNode;

    if (l == null)

        first = newNode;//原来链表为空,这是插入的第一个元素

    else

        l.next = newNode;

    size++;

    return true;

}

    查询慢,需要移动指针来遍历

    添加,删除较快

    线程不安全,和LinkedList所有函数都没有同步

  LinkedList的遍历方法:


二:Set


    是一种无序,不包含重复元素的Collection, Set和 List虽然都是存储的对象但是实现方式却完全不一样,List是以树组为基础实现的而set是以hashMap为基础实现的

1: HashSet

    内部通过HashMap实现,从源码中可以看到,内部维护了一个HashMap的对象

    private transient HashMap map;

    不允许重复元素插入(当插入重复元素后,会将原有的元素的值都覆盖掉),从源码中可      以看到当有新元素添加到map中时,会加到map的key中,而value用object代替

    private static final Object PRESENT = new Object(); //hashMap中的value

    public boolean add(E e) {

        return map.put(e, PRESENT)==null;

    }

    元素插入的顺序和存储的顺序不一至

    允许null元素

    没有做线程安全,因为所有函数都没有同步

    HashSet的遍历方法:


三:Map


    Map是一种储存键值对 (key-value)对像的collection,在key-value的结果储存中,key不能重复,value可以重复。

1:hashMap

    是一个散列表,通过链地址法来解决冲突。

    内部实现是个Entry(Entry是一个可以构成单向链表的类)的数组,在源码中内部定义了一个Entry成员变量:

    private transient Entry[] table;

    使用链地址法来处理冲突,在源码中可以发现HashMap是先通过hash(key)生成hash值,然后通过hash值计算存储的位置

        int hash = hash(key);

        int index = hash &(table.length-1);

    HashMap在添加新元素时,找到了储存的数组位置 index后会遍历链表tab[index],如果在链表tab[index]中找到相同的key,替换该key对应的value,找不到新建一个Entry,并插入到tab[index]链表

    for (Entry e =table[index]; e != null; e = e.next) {

        Object k;

        if (e.hash == hash && ((k =e.key) == key || key.equals(k))) {

            V oldValue =e.value;

            e.value = value;

            e.recordAccess(this);

            return oldValue;

        }

    }

    modCount++;

    addEntry(hash, key, value, index);

    元素插入的顺序和存储的顺序不一至

    key允许为null(但只能有一条并且放在tbl[0],如果再写入会将第一次的value覆盖掉)

    非线程安全,因为所有函数都没有同步

  HashMap的遍历方法:

  2:HashTable

    是一个散列表,用来储存键值对(key-value)对像的collection。它是继承自Dictionary,实现的Map接口。

    内部实现是个Entry的数组和HashMap一样,在内部维护了一个Entry数组的成员变量:

    private transient Entry[] table;

    使用链地址法来处理冲突, 和HashMap一样也是先通过hash(key)生成hash值,然后通过hash值计算存储的位置。但是不一样的是他们的index计算方式,HashMap是用Hash值 “与”运算数组的长度-1,而HashTable是Hash值除数组的长度取余数。

    int hash = hash(key);

    int index = (hash &0x7FFFFFFF) % tab.length;

    添加元素和HashMap一样,找到了储存的数组位置 index后会遍历链表tab[index],如果在链表tab[index]中找到相同的key,替换该key对应的value,找不到新建一个Entry,并插入到tab[index]链表中

    Entry e = tab[index];

    tab[index] = newEntry<>(hash, key, value, e);

    元素插入的顺序和存储的顺序不一至

    key和value都不能插入null

    线程安全,因为HashTable的函数都是加了synchronized

    public synchronized V put(K key, V value){

    。。。。}

    HashTable的遍历方法:

    从上面的遍历方式我们可以看出来,HashTable比HashMap多了Elements的遍历,所以HashTable和HashMap继承类是不一样的。

3:ConcurrentHashMap

    和HashMap,HashTable一样都是储存键值对(key-value)对像的collection,HashMap是非线程安全,而HashTable是线程安全,所以在多线程中,我们一般使用HashTable,但HashTable是对整个hash表结构做锁定操作的,这样在锁表的期间,别的线程就需要等待了,性能不是很好。

    ConcurrentHashMap就解决HashTable的缺陷,因为在ConCurrentHashMap中,加锁是采用的分段加锁技术。

    ConcurrentHashMap内部是由一个Segment数组和一个HashEntry链表构成的,在每一个Segment段都设置了一把锁,所以对于不同Segment的数据进行操作是不用考虑锁竞争的

    final Segment[] segments;

segment类似于hashmap,内部实现是一个HashEntry数组

    transient volatileHashEntry[] table;(HashEntry是一个可以构成单向链表的类)

    ConcurrentHashMap的冲突解决方式和HashTable, HashMap都一样,先计算key的hash值,然后将该hash值右移segmentShift位(segmentShift  = 32 - segments.length 2次开方的值)然后 “与”运算segmentMask(segmentMask  = segments.length -1)

    int hash = hash(key);

    int j = (hash>>> segmentShift) & segmentMask;

    在ConcurrentHashMap中执行put操作时,ConcurrentHashMap就会调用Segment的put操作,但这个时候并不加锁,只是确定Segment的index位置。

    public V put(K key, V value) {

    Segment s;

    。。。。。。

    return s.put(key, hash,value, false);

    }

    在Segment中执行put操作时,1:加锁,2:添加到HashEntry的链表中,3:释放锁

    final V put(K key, int hash, V value, boolean onlyIfAbsent) {

    HashEntrynode = tryLock() ? null : scanAndLockForPut(key, hash, value);

    Try{

        。。。。       

    }finally {

        unlock();

    }

}

    key和value都不能插入null

    不允许重复的key插入(如果重复key对应的value会被覆盖),value可重复

ConcurrentHashMap的遍历方法:

上一篇 下一篇

猜你喜欢

热点阅读