集合

List、Set、Map

集合中的最上层接口只有2类:Map和Collection,List和Set是Collection的下一层。

三者名称 特性 安全类 安全原理
List 有序、可重复 CopyOnWriteArrayList 读写分离,写时复制
Set 无需、不可重复 CopyOnWriteArraySet 读写分离,写时复制
Map 键值对形式存储 ConcurrentMap 没哈希冲突就CAS,有哈希冲突就Syn加锁

在这里插入图片描述

LIst

LIst类 底层数据结构 特性 安全性
ArrayList 数据 增删慢,查询快, 不安全,安全需要CopyOnWriteArrayList
Vector 数组 增删慢,查询快 安全,底层是对每一个结点都加Syn
LinkedList 双向链表 增删快,查询慢 不安全,安全需要CopyOnWriteLinkedList

Queue

Queue 解释
ArrayBlockingQueue 数组结构组成的有界阻塞队列
LinkedBlockingQueue 由链表组成的有界阻塞队列(大小默认是21亿,不推荐默认使用)
LinkedBlockingDeque 由链表结构组成的双向阻塞队列
PriorityBlockingQueue 支持优先级排序的无界阻塞队列
DelayBlockingQueue 使用优先级队列实现的延迟无界队列
SynchronousQueue 不存储元素的阻塞队列=单个元素的队列
LinkedTransferQueue 由链表结构组成的无界阻塞队列

Set

Set 底层 特性
HashSet HashMap<key,PRESENT> 无序不重复
TreeSet 二叉树 排序不重复
LinkedHashSet LinkedHashMap<key,PRESENT> 可前后遍历,不重复

Map

Map 底层 安全性
HashMap 数组+链表/红黑树 Map不安全
ConcurrentMap 数组+链表/红黑树 安全,原理是冲突syn+不冲突CAS
TreeMap 二叉树 不安全
LinkedHashMap 双向链表 不安全

HashMap

HashMap 描述
底层数据结构 数组+链表/红黑树
存储数据流程 如下所示
长度 默认16,装载因子为0.75
阈值 扩容成红黑树的阈值为8

存储数据的流程

  1. 对key的hash后获得数组index;2.数组位置为空,初始化容量为16
  2. 数组位置为空,初试化容量为16
  3. hash后没有碰撞,就放入数组
  4. 有碰撞且节点已存在,则替换掉原来的对象
  5. 有碰撞且节点已经是树结构,则挂载到树上
  6. 有碰撞且节点已经是链表结构,则添加到链表末尾,并判断链表是否需要转换为树结构(链表结点大于8就转换)
  7. 完成put操作后,判断是否需要resize()操作

在这里插入图片描述

hashMap不安全原因

  1. 在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。源码是1.7时的 transfer函数,自己点进去看
  2. 在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。源码是1.8时的resize函数,自己点进去看

HashMap和Hashtable

特性 HashMap Hashtable
安全性 不安全,分为1.7和1.8 单个线程安全,原因是加了同步锁
hashcode 对key的hashcode重新计算 直接使用key的HashCode
key,value 都可以为null 都不能为null(注意,idea不会提示报错,但是运行出现空指针异常,源码有提示)
长度 默认16,扩容翻倍 默认11,扩容+1

key,value为空的问题:

   public static void main(String[] args) {
        HashMap<Integer, Integer> hashmap = new HashMap<>();
        hashmap.put(null, null);// hashmap两个都可以存null
        Hashtable<Integer, Integer> hashtable = new Hashtable<>();
        hashtable.put(null, null);//hashtable任一个都不能存null,但idea不会报错,运行会出现空指针异常
    }

HashMap的长度为什么是2的幂次方?

答:提高数组利用率,减少冲突(碰撞)的次数,提高HashMap查询效率

// 源码计算index的操作:n是table.length
 if ((p = tab[i = (n - 1) & hash]) == null)

ConcurrentHashMap

线程安全的底层原理:没有哈希冲突就大量CAS插入+如果有哈希冲突就Syn加锁

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

TreeMap

treeMap底层使用红黑树,会按照Key来排序

  • 如果是字符串,就会按照字典序来排序
  • 如果是自定义类,就要使用2种方法指定比较规则
    • 实现Compareable接口,但是需要重新定义比较规则就要修改源码,麻烦
    • 创建实例时候,传入一个比较器Comparator,重新定义规则不需要修改源码,推荐使用
public class TreeMapDemo {
    public static void main(String[] args) {
        // treeMap中自定义类需要指定比较器
        // 方式一:自定义类实现Comparable接口
        TreeMap<User, User> treeMap1 = new TreeMap<>();
        // 方式二:创建实例指定比较器Comparator
        TreeMap<User, User> treeMap2 = new TreeMap<>(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                // 定义比较规则
                return 0;
            }
        });
    }
}
public class User implements Comparable {
    private String id;
    private String username;

    @Override
    public int compareTo(Object obj) {
        // 这里定义比较规则
        return 0;
    }
}

ArrayList和LinkedList

特性 ArrayList LinkedList
底层 动态数组,查询快,插入慢 双向链表,查询慢,插入快
安全 不安全 不安全
接口 都是List接口的实现类,存储有序,可重复 都是List接口的实现类,存储有序,可重复

Vetor和CopyOnWriteList

list安全类是如下两个:Vetor、CopyOnWriteList; Collections.synchronizedLis是JDK包装实现线程安全的工具类

Vector Collections.synchronizedList CopyOnWriteList
线程安全原理 synchronized加载方法上 内部类封装SynchronizedList方法 写加锁,读不加锁,通过volatile保证可见性
    public synchronized int capacity() {
        return elementData.length;
    }

    // Vetor锁都加在方法上
    public synchronized int size() {
        return elementCount;
    }


    public synchronized boolean isEmpty() {
        return elementCount == 0;
    }
    ...
    }
static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
        private static final long serialVersionUID = -7754090372962971524L;

        final List<E> list;
        // Collections.synchronizedList:内部类SynchronizedList,锁加载内部类里面
        SynchronizedList(List<E> list) {
            super(list);
            this.list = list;
        }
        SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }
        ....
}
//  CopyOnWriteList 写加锁
public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // CopyOnWriteList是复制数组保证线程安全
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

// CopyOnWriteList 读不加锁,原数组通过 transient volatile保证不可系列化和可见性
private transient volatile Object[] array;

final Object[] getArray() {
    return array;
} 

public E get(int index) {
    return get(getArray(), index);
}

LinkedHashMap和LinkedHashSet

答:LinkedHashMap可以记录下元素的插入顺序和访问顺序

  • LinkedHashMap内部的Entry继承于HashMap.Node,这两个类都实现了Map.Entry<K,V>
  • 底层链表是双向链表,Node不光有value,next,还有before和after属性,保证了各个元素的插入顺序
  • 通过构造方法public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder), accessOrder传入true可以实现LRU缓存算法(访问顺序)

LRU算法

最近最少使用算法: 根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

public class LRUTest {
    // 0.指定map长度size=5
    private static final int size = 5;

    public static void main(String[] args) {
        // 1. LinkedHashMap三大参数,重写removeEldestEntry
        Map<String, String> map = new LinkedHashMap<String, String>(size, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {

                return size() > size;
            }
        };
        // 2.添加5个数,使得map满
        map.put("1", "1");
        map.put("2", "2");
        map.put("3", "3");
        map.put("4", "4");
        map.put("5", "5");
        System.out.println("map:" + map.toString());
        // 3.指定map满了,再put就会移除表头第一个元素:1=1
        map.put("6", "6");
        System.out.println("map:" + map.toString());
        // 4.get取出的元素,表示是常用的,放回到表尾
        map.get("3");
        System.out.println("map:" + map.toString());
    }
}

执行结果:

map:{1=1, 2=2, 3=3, 4=4, 5=5}
map:{2=2, 3=3, 4=4, 5=5, 6=6}
map:{2=2, 4=4, 5=5, 6=6, 3=3}

手写LRU算法

public class LRUCache {
    // 力扣146同一题
    class DoubleNode {
        int key;
        int value;
        DoubleNode pre;
        DoubleNode next;

        DoubleNode(int key, int value) {
            this.key = key;
            this.value = value;
        }

        DoubleNode() {

        }
    }

    private HashMap<Integer, DoubleNode> cache = new HashMap<>();
    private int size;
    private int capacity;
    private DoubleNode head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        this.head = new DoubleNode();
        this.tail = new DoubleNode();
        // 创建伪头部和伪尾部,减少添加和删除的逻辑
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        // 1.获取get元素
        DoubleNode node = cache.get(key);
        // 2.get元素不存就返回-1
        if (node == null) {
            return -1;
        }
        // 3.get元素就移动至头部,规定常用元素移动至头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        // 1.获取put元素
        DoubleNode node = cache.get(key);
        // 2.put元素不存在
        if (node == null) {
            // 生成它
            DoubleNode nowNode = new DoubleNode(key, value);
            // 放进cache
            cache.put(key, nowNode);
            // 添加进头部
            addToHead(nowNode);
            // 长度++
            size++;
            // 判断是否超过指定长度
            if (size > capacity) {
                DoubleNode tail = removeTail();
                cache.remove(tail.key);
                size--;
            }
        } else {
            // 3.node存在就更新value,然后移动至头部
            node.value = value;
            moveToHead(node);
        }
    }

    private void addToHead(DoubleNode node) {
        node.pre = head;
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
    }

    private DoubleNode removeTail() {
        DoubleNode del = tail.pre;
        removeNode(del);
        return del;
    }


    private void removeNode(DoubleNode node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    private void moveToHead(DoubleNode node) {
        removeNode(node);
        addToHead(node);
    }
}

Iterator和ListIterator

Iterator ListIterator
使用集合 List、set 只能是List
前后遍历 只能想后遍历 前后遍历都行
public class IteratorDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

快速失败和安全失败

fail-fast fail-safe
报错 失败/异常,会立刻报错 失败/异常,直接忽略,不会报错
异常 抛出CorrentModificationException 不会抛出异常
适用包 java.util下的集合类 java.util.concurrent下的集合类
原理 迭代器检查modCount和expectedModCount CopyOnWriteXX写加锁,读不加锁,两者不冲突

迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedModCount值,是的话就返回遍历;否则抛出异常,终止遍历

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    ...
    // modCount记录当前线程更改状态    
    ++modCount;
    ...
    return null;
}

数组和List和遍历转换

数组 List
遍历 Arrays.toString(arr) 直接遍历
相互转换 不推荐Arrays.asList(arr)。推荐Collections.singletonList(arr); arrayList.toArray(new Integer[3])
public class ArrayAndList {
    public static void main(String[] args) {
        // 1.数组遍历:Arrays.toString
        int[] arr = {1, 2, 3};
        System.out.println(Arrays.toString(arr));
        // 2.数组转成list,泛型说明不推荐使用,多此一举
        List<int[]> ints1 = Arrays.asList(arr);
        List<int[]> ints = Collections.singletonList(arr);
        for (int[] anInt : ints) {
            System.out.println(Arrays.toString(anInt));
        }
        System.out.println("------------");
        // 3.list遍历:直接遍历即可
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        System.out.println(arrayList);
        // 4.list转换成数组,list名.toArray(指定数组类型和长度)
        Integer[] integers = arrayList.toArray(new Integer[3]);
        System.out.println(Arrays.toString(integers));
    }
}