JAVA集合~

集合:

List:

List集合具有以下特点:

  1. 集合中的元素允许重复
  2. 集合中的元素是有顺序的,各元素插入的顺序就是各元素的顺序
  3. 集合中的元素可以通过索引来访问或者设置

List接口常用的实现类有:ArrayList、LinkedList、Vector。

Arraylist(线程不安全):

ArrayList 实现了 List 接口,继承了 AbstractList 抽象类,底层是基于数组实现的,并且实现了动态扩容。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final int DEFAULT_CAPACITY = 10;
    transient Object[] elementData;
    private int size;
}

RandomAccess 接口 : 随机访问不需要遍历,就可以通过下标(索引)直接访问到内存地址。

Cloneable 接口 : 这表明 ArrayList 是支持拷贝的。ArrayList 内部的确也重写了 Object 类的 clone() 方法

Serializable 接口 :序列化转二进制,内部提供了两个私有方法 writeObject 和 readObject 来完成序列化和反序列化

动态扩容: ArrayList ,

优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高

遍历ArrayList的元素主要有以下3种方式:

ArrayList<String> list = new ArrayList<>();
        list.add("我");
        list.add("adwj");
        list.add("我");
  1. 迭代器iterator遍历

            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()){
                System.out.println(iterator.next());
            }
    
    
  2. for循环

    for(int i=0;i<list.size();i++){
       System.out.println(list.get(i));
    }
           
    
  3. foreach循环

     for (String s : list) {
        System.out.println(s);
     }
    

LinkedList :

LinkedList 是一个继承自 AbstractSequentialList 的双向链表,因此它也可以被当作堆栈、队列或双端队列进行操作

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
}

优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高

vector(线程安全):

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
}

常问:

相同点

ArrayList、LinkedList、Vector都实现了List接口,所以使用方式很类似

不同点

但是ArrayList、LinkedList、Vector的内部实现方式不同,也就导致了它们之间是有区别的。

存储结构

ArrayList和Vector是基于数组实现的,LinkedList是基于双向链表实现的。

这也就导致ArrayList适合随机查找和遍历,而LinkedList适合动态插入和删除元素。

线程安全性

ArrayList和LinkedList是线程不安全的,Vector是线程安全的。

Vector可以看做是ArrayList在多线程环境下的另一种实现方式,这也导致了Vector的效率没有ArraykList和LinkedList高。

如果要在并发环境下使用ArrayList或者LinkedList,可以调用Collections类的synchronizedList()方法(分情况)

如果读大于写 那么可以使用CopyOnWriteArrayList<>() 不过会在写操作复制一个list进行写操作 会占用大量内存

CopyOnWriteArrayList 中, 定义了一个可重入锁:

final transient ReentrantLock lock = new ReentrantLock();

该锁用于对所有修改集合的方法 (add, remove 等) 进行同步, 在进行实际修改操作时, 会先复制原来的数组, 再进行修改, 最后替换原来的

但也会因此引入 "弱一致性" 问题; 所谓 "弱一致性" 是指当一个线程正在读取数据时, 若此时有另一个线程同时在修改该区域的数据, 读取的线程将无法读取最新的数据, 即该读取线程只能读取到它读取时刻以前的最新数据;

ArrayList初始容量是10,添加第11个元素会扩容到16,之后扩容因子是1.5+1

Vector初始容量是10,添加到第11个元素扩容为20,21个元素扩容为40,接着80.扩容因子2

Set:

Set集合包括Set接口以及Set接口的所有实现类。Set集合具有以下特点:

  1. 集合中不包含重复元素(你可以重复添加,但只会保留第1个)
  2. 集合中的元素不一定保证有序

Set接口常用的实现类有:HashSet、LinkedHashSet、TreeSet。

Hashset:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

底层数据结构是哈希表。(无序,唯一)如何来保证元素唯一性?
1.依赖两个方法:hashCode()和equals()

hashCode()判断哈希值是否相同,相同后调用equals() 判断是否是一个元素

遍历元素用迭代器或者foreach循环

TreeSet:

底层数据结构是红黑树。(唯一,有序)

  1. 如何保证元素排序的呢?
    自然排序 实现接口Comparable,重写该接口的方法compareTo()
    比较器排序
  2. 如何保证元素唯一性的呢?
    根据比较的返回值是否是0来决定 :

LinkedHashSet:

底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
1.由链表保证元素有序
2.由哈希表保证元素唯一

加载因子为0.75 , 即当 元素个数 超过 容量长度的0.75倍 时,进行扩容

扩容增量:原容量的 1 倍

HashSet的容量为16,一次扩容后是容量为32

Map:

HashMap(线程不安全)

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
	......
}

HashMap的父类是AbstractMap ,继承了Map<K,V>Cloneable, Serializable

使用entrySet()获得一个有Key+Value的Set集合 可以使用迭代器iterator()或者foreach()循环遍历

使用value()获得一个Value的值,可以使用迭代器iterator()或者foreach()循环遍历

JDK8中hashmap底层是通过数组+链表+红黑树来实现的

JDK8中的因为使用了红黑树保证了插入和查询了效率,所以实际上JDK8中的Hash算法实现的复杂度降低了

JDK8中数组扩容的条件也发了变化,只会判断是否当前元素个数是否超过了阈值,而不再判断当前put进来的元素对应的数组下标位置是否有值。

JDK7中是先扩容再添加新元素,JDK8中是先添加新元素然后再扩容

HashMap中PUT方法的流程?
  1. 通过key计算出个hashcode (key%table.length,提高效率key&table.length-1,前提数组长度是2的幂)
  2. 通过hashcode与数组表长与操作计算出一个数组下标
  3. 在把put进来的key,value封装为一个node对象
  4. 判断数组下标对应的位置,是不是空,如果是空则把node直接存在该数组位置
  5. 如果该下标对应的位置不为空,则需要把node插入到链表中
  6. 并且还需要判断该链表中是否存在相同的key,如果存在,则更新value
  7. 如果是JDK7,则使用头插法
  8. 如果是JDK8,则会遍历链表,并且在遍历链表的过程中,统计当前链表的元素个数,如果超过8个,则先把链表转变为红黑树,并且把元素插入到红黑树中
JDK8中链表转变为红黑树的条件?
  1. 链表中的元素的个数为8个或超过8个
  2. 同时,还要满足当前数组的长度大于或等于64才会把链表转变为红黑树。为什么?因为链表转变为红黑树的目的是为了解决链表过长,导致查询和插入效率慢的问题,而如果要解决这个问题,也可以通过数组扩容,把链表缩短也可以解决这个问题。所以在数组长度还不太长的情况,可以先通过数组扩容来解决链表过长的问题。
HashMap扩容流程是怎样的?
  1. HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新数组上来,这样才是数组的扩容
  2. 在HashMap中也是一样,先新建一个2被数组大小的数组
  3. 然后遍历老数组上的没一个位置,如果这个位置上是一个链表,就把这个链表上的元素转移到新数组上去
  4. 在这个过程中就需要遍历链表,当然jdk7,和jdk8在这个实
  5. 现时是有不一样的,jdk7就是简单的遍历链表上的没一个元素,然后按每个元素的hashcode结合新数组的长度重新计算得出一个下标,而重新得到的这个数组下标很可能和之前的数组下标是不一样的,这样子就达到了一种效果,就是扩容之后,某个链表会变短,这也就达到了扩容的目的,缩短链表长度,提高了查询效率
  6. 而在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。
  7. 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组会被回收到。
为什么HashMap的数组的大小是2的幂次方数?

JDK7的HashMap是数组+链表实现的

JDK8的HashMap是数组+链表+红黑树实现的

当某个key-value对需要存储到数组中时,需要先生成一个数组下标index,并且这个index不能越界。

在HashMap中,先得到key的hashcode,hashcode是一个数字,然后通过 hashcode & (table.length - 1) 运算得到一个数组下标index,是通过与运算计算出来一个数组下标的,而不是通过取余,与运算相比于取余运算速度更快,就是数组的长度得是一个2的幂次方数。2的幂次方数二进制值都是1,10,100,1000.......2的幂次方数-1的二进制值都是11,111,1111......&运算计算出来的值都会小于表长。不然会增加哈希碰撞概率,浪费内存

Hashtable:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
{
	......
}

HashMap的父类是 Dictionary,继承了Map<K,V>Cloneable, Serializable

HashTable类的使用方法和HashMap基本一样

LinkedHashMap:

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{
	......
}

inkedHashMap类继承了HashMap类。

LinkedHashMap类的使用方法和HashMap基本一样

TreeMap:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
	......
}

TreeMap中的元素是有序的,默认的排序规则是按照key的字典顺序升序排序

TreeMap类的使用方法和HashMap基本一样

4类区别:

相同点

1)HashMap、Hashtable、LinkedHashMap、TreeMap都实现了Map接口

2)四者都保证了Key的唯一性,即不允许Key重复

排序

HashMap不保证元素的顺序

Hashtable不保证元素的顺序

LinkHashMap保证FIFO即按插入顺序排序(链表)

TreeMap保证元素的顺序,支持自定义排序规则(树)

posted on 2020-12-05 11:34  Ruayo  阅读(47)  评论(0编辑  收藏  举报

1