0
点赞
收藏
分享

微信扫一扫

java容器精讲(Java容器基本说全了),带源码讲解,集合,同步类容器,并发容器,队列

扶摇_hyber 2022-01-13 阅读 31
集合 & 容器
源码(码云):https://gitee.com/yin_zhipeng/to_study_the_collection.git
看一下最基本的集合
  1. 简易的:文件位置simple_collection/uml/collection-simple.puml
    在这里插入图片描述
声明:看源码时经常看见这个变量modCount,就是标记容器被操作的次数的,在这里统一说一下,否则遇见一次说一次,想一头撞死

文章目录

一、Collection接口

Collectiono 是顶级接口,我们对其常用Api进行介绍

1. 常用Api解析

描述API
添加单个元素add(E e)
添加一个集合addAll(Collection<? extends E> c)
清除集合clear()
移除指定元素remove(Object o)
迭代器iterator()
集合元素个数size()
判断指定元素是否在集合中contains(Object o)
比较两个集合的元素是否相等equals(Object o)
集合是否为空isEmpty()
测试API:simple_collection/CollectionAPITest.java
import java.util.*;

public class CollectionAPITest {

    public static void main(String[] args) {
        //Collection是接口,使用需要实现类
        Collection col = new ArrayList();
        //add()添加一个元素,添加基本数据类型,会自动装箱,int--->Integer
        col.add(1);
        col.add(2);
        System.out.println("使用add方法添加1和2:"+col);
        //addALl()添加一个集合的所有元素
        List<Integer> integers = Arrays.asList(new Integer[]{3, 4, 5, 6, 7, 8});
        col.addAll(integers);
        System.out.println("使用addAll方法添加{3, 4, 5, 6, 7, 8}:"+col);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //remove()方法移除指定元素
        System.out.println("使用remove()方法移除元素1:"+col.remove(1)+"(true成功/false失败)移除后集合"+col);
        System.out.println("再次使用remove()方法移除元素1:"+col.remove(1)+"(true成功/false失败)移除后集合"+col);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //clear()方法清除集合
        col.clear();
        System.out.println("使用clear方法清除集合"+col);
        //isEmpty()方法查看集合是否为空
        System.out.println("使用isEmpty方法查看集合是否为空"+col.isEmpty());
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //equals()方法比较两个集合是否相等
        ArrayList<Object> objects = new ArrayList<>();
        System.out.println("集合一:"+col+"集合二:"+objects+"使用equals()比较两个集合元素是否相等"+col.equals(objects));
        objects.add(1);
        System.out.println("集合一:"+col+"集合二:"+objects+"使用equals()比较两个集合元素是否相等"+col.equals(objects));
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //contains()方法判断集合是否包含指定元素
        System.out.println("集合:"+objects+"使用contains()方法判断集合是否包含1这个元素"+objects.contains(1));
        System.out.println("集合:"+objects+"使用contains()方法判断集合是否包含2这个元素"+objects.contains(2));
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //size()方法获取集合长度
        System.out.println("集合:"+col+"使用size()方法获取集合长度"+col.size());
        System.out.println("集合:"+objects+"使用size()方法获取集合长度"+objects.size());
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //使用iterator()迭代器遍历集合
        col.addAll( Arrays.asList(new Integer[]{3, 4, 5, 6, 7, 8}));
        System.out.println("使用iterator()遍历集合"+col);

        Iterator iterator = col.iterator();//获取Iterator对象
        int i = 1;
        while(iterator.hasNext()){//Iterator.hasNext()方法,判断是否有个下一个可迭代对象
            Object next = iterator.next();//返回当前元素,并指针后移,指针指向下一个元素,若没有,下一次hasNext方法会做处理
            System.out.println("第"+i+"个元素:"+next);
            i++;
        }

    }
}

2. Iterator接口

首先Collection extends Iterable,而Iterable接口中定义了Iterator iterator();,返回Iterator接口对象,我们操作迭代的方法,都在Iterator接口
接口是不可用的,如果想用需要实现类,我们从ArrayList源码中,可以看到,实现类就是Itr类,Itr类是内部类

3. ArrayList的iterator()方法源码(JDK1.8)

ArrayList的iterator迭代器解析

4. 集合之间如何比较

1. Comparable 和 Comparator

  1. Comparabel接口(内部比较器),实现更简单, Comparator接口(外部比较器)更灵活
import java.util.*;

public class Test implements Comparable<Test>{
    private Integer age;

    public Test(Integer age) {
        this.age = age;
    }

    /**
     * 返回一个int值,正数表示自己(this)比o大,0表示this=o2,负数表示this小于o2
     */
    @Override
    public int compareTo(Test o) {
        //根据年龄决定谁大
        return this.age-o.age;
    }

    @Override
    public String toString() {
        return "Test{" +
                "age=" + age +
                '}';
    }

    public static void main(String[] args) {
        Test test = new Test(1);
        Test test1 = new Test(2);
        ArrayList<Test> tests = new ArrayList<>();
        tests.add(test1);
        tests.add(test);
        System.out.println("list集合排序前"+Arrays.toString(tests.toArray())+"\n\n");
        System.out.println("==========Comparable排序===========");

        int i = test.compareTo(test1);
        System.out.println("Comparable:test和test1谁大(正数表示test大,0表示相等,负数表示test1大)"+i);
        Collections.sort(tests);
        System.out.println("list集合排序后"+Arrays.toString(tests.toArray())+"\n\n");


        System.out.println("==========Comparator排序===========");
        Comparator<Test> comparator = new Comparator<Test>() {

            /**
             * 返回一个int值,正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
             */
            @Override
            public int compare(Test o1, Test o2) {
                //根据年龄决定谁大
                return o2.age-o1.age;
            }
        };
        int compare = comparator.compare(test, test1);
        System.out.println("Comparator:test和test1谁大(正数表示test1大,0表示相等,负数表示test大)"+compare);
        Collections.sort(tests, new Comparator<Test>() {
            /**
             * 返回一个int值,正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
             */
            @Override
            public int compare(Test o1, Test o2) {
                return o2.age-o1.age;
            }
        });
        System.out.println("list集合排序后"+Arrays.toString(tests.toArray()));
    }
}

二、List接口

List也是一个接口,继承于Collection,Collection有的他都有,所以我们介绍它特有的常用Api进行介绍

1. 常用Api解析

listIterator()和listInterator(int index)等方法,放在后面统一介绍,放在这里有的突兀
描述API
添加单个元素到指定下标add(int index,E e)
设置(替换)单个元素到指定下标set(int index,E e)
获取指定下标元素get(int index)
移除指定下标元素remove(int index)
测试API:simple_collection/ListApiTest.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ListApiTest {
    public static void main(String[] args) {
        //List是接口,使用需要子类
        List list = new ArrayList();
        list.addAll(Arrays.asList(new Integer[]{4,7,8,-1}));
        System.out.println("List init ====>>> "+list);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //add(int index,E e)添加单个元素到指定下标
        list.add(1,5);
        System.out.println("List add(1,5):在下标为1的位置添加元素5"+list);
        //set(int index,E e)设置单个元素到指定下标
        list.set(1, 6);
        System.out.println("List set(1,6):在下标为1的位置设置元素6"+list);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //get(int index)获取指定下标元素
        System.out.println("List get(1):获取下标为1的元素:"+list.get(1));
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //remove(int index)移除指定下标元素
        list.remove(1);
        System.out.println("List remove(1):移除下标为1的元素:"+list);
    }
}

2. ArrayList源码(JDK1.8)

ArrayList优缺点
简单说一下JDK1.7版本的细节,具体源码看JDK1.8的
JDK1.8 ArrayList源码

3. Vector源码(JDK1.8)

一个被淘汰的容器,但是面试常问

4. iterator()方法的Itr迭代器并发修改异常

ArrayList是线程不安全的,使用迭代器的时候,不可以同时对集合进行其它操作,比如用迭代器添加数据,一个指针迭代,另一个指针操作集合,就会报错
那么我们可以让一个人做这件事,解决并发修改异常,使用ListIterator接口

5. ListIterator接口,ArrayList的ListItr实现类

ListIterator接口继承于Iterator接口,同时ArrayList也使用内部类ListItr实现了此接口,ListItr内部类还继承了Itr内部类
遍历和添加,都使用ListIterator对象,不报并发修改异常
遍历时还可以判断上一个元素,以完成逆向遍历,前提是,当前ListIterator指针已经在靠后的位置(不等于0)

三、LinkedList实现类

1. 常用Api解析

描述API
插入指定元素到列表开头addFirst(E e)
插入指定元素到列表结尾addLast(E e)
获取但不移除此列表的头E element()
获取但不移除此列表的头E peek()
获取但不移除此列表的第一个元素,列表为空,返回nullE peekFirst()
获取但不移除此列表的最后一个元素,列表为空,返回nullE peekLast()
返回此列表第一个元素getFirst()
返回此列表最后一个元素getLast()
返回此列表首次出现的指定元素的索引,无元素返回-1indexOf(Object o)
返回此列表最后出现的指定元素的索引,无元素返回-1lastIndexOf(Object o)
获取并移除此列表的头poll()
获取并移除此列表的第一个元素,列表为空,返回nullpollFirst()
获取并移除此列表的最后一个元素,列表为空,返回nullpollLast()
从此列表所表示的堆栈处弹出一个元素pop()
将元素推入此列表所表示的堆栈push(E e)
将指定元素添加到此列表的末尾offer(E e)
将指定元素插入到此列表的开头offerFirst(E e)
将指定元素插入到此列表的末尾offerLast(E e)
移除并返回此列表的第一个元素,列表为空,报错E removeFirst()
移除并返回此列表的最后一个元素E removeLast()
测试API:simple_collection/LinkedListApiTest.java
import java.util.LinkedList;

public class LinkedListApiTest {
    public static void main(String[] args) {
        //创建LinkedList
        LinkedList linkedList = new LinkedList();
        //特有的添加方法
        System.out.println("addFirst()插入指定元素到列表开头");linkedList.addFirst("addFirst()");
        System.out.println("addLast()插入指定元素到列表结尾");linkedList.addLast("addLast()");
        System.out.println("push()入栈");linkedList.push("push()");
        System.out.println("offer()元素添加到末尾");linkedList.offer("offer()");
        System.out.println("offerFirst()元素插入到列表开头");linkedList.offerFirst("offerFirst()");
        System.out.println("offerLast()元素插入到列表末尾");linkedList.offerLast("offerLast()");
        linkedList.push("push()");//多添加一个用于测试lastIndexOf()
        System.out.println("添加元素完成:"+linkedList);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //特有获取元素方法
        System.out.println("element():获取但不移除列表头"+linkedList.element());
        System.out.println("peek():获取但不移除列表头"+linkedList.peek());
        System.out.println("peekFirst():获取但不移除列表头,列表空,返回null:"+linkedList.peekFirst());
        System.out.println("peekLast():获取但不移除列表尾,列表空,返回null:"+linkedList.peekLast());
        System.out.println("getFirst():列表第一个元素"+linkedList.getFirst());
        System.out.println("getLast():列表最后一个元素"+linkedList.getLast());
        System.out.println("linkedList.indexOf(push()):获取列表第一个'push()'的下标"+linkedList.indexOf("push()"));
        System.out.println("lastIndexOf(push()):获取列表最后一个'push()'的下标"+linkedList.lastIndexOf("push()"));
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //特有移除元素方法
        System.out.println("pop(),出栈弹出一个元素=="+linkedList.pop()+"==移除后列表:"+linkedList);
        System.out.println("poll(),获取并移除此列表的头=="+linkedList.poll()+"==移除后列表:"+linkedList);
        System.out.println("pollFirst(),获取并移除列表首元素,列表空,返回null:=="+linkedList.pollFirst()+"==移除后列表:"+linkedList);
        System.out.println("pollLast(),获取并移除列表尾元素,列表空,返回null:=="+linkedList.pollLast()+"==移除后列表:"+linkedList);
        System.out.println("removeFirst(),移除并返回此列表第一个元素=="+linkedList.removeFirst()+"==移除后列表:"+linkedList);
        System.out.println("removeLast(),移除并返回此列表最后一个元素=="+linkedList.removeLast()+"==移除后列表:"+linkedList);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //遍历
        System.out.println("遍历======");
        for(Iterator iterator =linkedList.iterator();iterator.hasNext();){
            System.out.println(iterator.next());
        }
    }
}

2. 两种迭代器使用方法比较

//不推荐,iterator 用完,不会立即被回收
Iterator iterator = col.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
//更优,iterator 为局部变量,for循环结束,迭代器直接回收
for(Iterator iterator =linkedList.iterator();iterator.hasNext();){
    System.out.println(iterator.next());
}

3. LinkedList源码(JDK1.8)

根据索引查找

四、Set接口

Set接口继承于Collection接口,Collection有的他都有,特性如下
Set接口的API和List接口大致相同,但是少了和索引相关的API,Set中没有索引,所以我们不介绍特有API
Set底层都是用Map实现,所以我们研究源码时,只看怎么用的Map,具体原理请看后面Map的源码

1. HashSet实现类和源码(JDK1.8)

基本特性测试:simple_collection/HashSetApiTest.java
import java.util.HashSet;
import java.util.Set;

public class SetApiTest {
    public static void main(String[] args) {
        Set set = new HashSet();
        //去重,根据Set特性,数据唯一
        set.add(1);set.add(1);set.add(1);set.add(1);set.add(1);set.add(1);
        System.out.println("向set集合中添加6个元素1,根据数据唯一特性,集合中将只有1个1:"+set);
        //无序测试
        set.add("a");set.add("b");set.add(2);set.add(3);set.add(4);
        set.add(5);set.add(6);set.add(7);set.add("asdfsdf");set.add("asdasdffsdf");set.add("asdfasdfasdfsdf");
        System.out.println("Set集合无序测试,查看是否和插入顺序一至:" +set);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //add方法,添加元素成功,返回true,添加失败,返回false,表示重复了,集合中有了
        System.out.println("测试添加集合中没有的值,添加元素10:"+set.add(10));
        System.out.println("测试添加集合中已经有值,添加元素1:"+set.add(1));
    }
}
源码解析:看不懂就先往下看或者先看Map

2. LinkedHashSet实现类和源码(JDK1.8)

LinkedHashSet 和 HashSet的区别
测试,发现有序了:simple_collection/LinkedHashSetTest.java
import java.util.LinkedHashSet;

public class LinkedHashSetTest {
    public static void main(String[] args) {
        LinkedHashSet set = new LinkedHashSet();
        //去重,根据Set特性,数据唯一
        set.add(1);set.add(1);set.add(1);set.add(1);set.add(1);set.add(1);
        System.out.println("向set集合中添加6个元素1,根据数据唯一特性,集合中将只有1个1:"+set);
        //无序测试
        set.add("a");set.add("b");set.add(2);set.add(3);set.add(4);
        set.add(5);set.add(6);set.add(7);set.add("asdfsdf");set.add("asdasdffsdf");set.add("asdfasdfasdfsdf");
        System.out.println("Set集合无序测试,查看是否和插入顺序一至:" +set);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //add方法,添加元素成功,返回true,添加失败,返回false,表示重复了,集合中有了
        System.out.println("测试添加集合中没有的值,添加元素10:"+set.add(10));
        System.out.println("测试添加集合中已经有值,添加元素1:"+set.add(1));
    }
}
源码分析:看不懂先去看map

3. TreeSet实现类和源码(JDK1.8)

和HashSet基本一样,只是底层是用二叉树实现的(遍历方式为中序遍历),一样的不按插入顺序排序,一样的不允许重复,但是会排序:simple_collection/TreeSetTest.java
import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetTest {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet();//使用默认比较器
        //去重,根据Set特性,数据唯一
        set.add(1);set.add(1);set.add(1);set.add(1);set.add(1);set.add(1);
        System.out.println("向set集合中添加6个元素1,根据数据唯一特性,集合中将只有1个1:"+set);
        //无序测试
        set.add(9);set.add(13);set.add(7);set.add(16);set.add(17);set.add(15);

        System.out.println("TreeSet集合使用:内部比较器排序:" +set);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        //add方法,添加元素成功,返回true,添加失败,返回false,表示重复了,集合中有了
        System.out.println("测试添加集合中没有的值,添加元素10:"+set.add(10));
        System.out.println("测试添加集合中已经有值,添加元素1:"+set.add(1));
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        
        TreeSet<Integer> integers = new TreeSet<>(new Comparator<Integer>() {//使用外部比较器
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        integers.add(9);integers.add(13);integers.add(7);integers.add(16);integers.add(17);integers.add(15);

        System.out.println("TreeSet集合使用:外部比较器排序:" +integers);
    }
}
源码:主要理解为什么可以排序
HashMap是没有比较的

五、Map接口

Map是一个顶级接口,主要规定采用键值对的形式存储数据,多采用Hash表和二叉树两种数据结构实现,接口不能new对象,我们用HashMap介绍Map的Api

1. 常用Api解析

描述API
从此映射中移除所有隐射关系clear()
如果此映射包含指定键的映射关系,返回trueboolean containsKey(Object key)
如果此映射将一个或多个键映射到指定值,返回trueboolean containsValue(Object value)
返回此映射中包含的映射关系的Set视图Set<Map.Entry<K,V>> entrySet()
比较指定的对象与此映射是否相等boolean equals(Object o)
返回指定键所映射的值;如果此映射不包含该键的映射关系,返回nullV get(Object key)
返回此映射的哈希码值int hashCode()
如果此映射未包含键-值映射关系,返回trueboolean isEmpty()
返回此映射包含的键的Set视图Set< K > keySet()
将指定的值与此映射中的指定键关联(可选操作)V put(K key,V value)
从指定映射中将所有映射关系复制到此映射中(可选操作)void putAll(Map<? extends K,? extends V> m)
如果存在一个键的映射关系,则将其从此映射中移除(可选操作)V remove(Object key)
返回此映射中的键-值映射关系数int size()
返回此映射中包含的值的Collection视图Collection< V > values()
测试API:simple_collection/MapApiTest.java
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapApiTest {
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        System.out.println("测试一下对同一个key赋值,put()方法的返回值");
        System.out.println("put(\"one\", 3)返回值:"+map.put("one", 3));
        System.out.println("put(\"one\", 2)返回值:"+map.put("one", 2));
        System.out.println("put(\"one\", 1)返回值:"+map.put("one", 1));
        System.out.println("对同一个key,one设置3个值,只会保留最后一个,一个键映射一个值,重复赋值会替换:"+map);
        map.put("two",2);
        map.put("three",3);
        map.put("four",4);
        System.out.println("map集合初始化完成(会按照key的hash排序):"+map);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        System.out.println("通过get('one')获取one对应的值:"+map.get("one"));
        System.out.println("当前map的大小:"+map.size());
        System.out.println("keySet()获取所有key,Set<K>对象:"+map.keySet());
        System.out.println("values()获取所有映射值Collection对象:"+map.values());
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        System.out.println("map1.equals(map2):判断两个map的映射关系,是否一致:"+map.equals(map));
        System.out.println("isEmpty()判断集合是否为空:"+map.isEmpty());
        System.out.println("map.containsKey(\"one\"):判断集合中是否包含key值one的映射:"+map.containsKey("one"));
        System.out.println("map.containsValue(1):判断1这个值,是否和map中某个或某些key映射:"+map.containsValue(1));
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));
        System.out.println("remove(\"one\"):删除指定key,one,返回它映射的值(key丢失,1这个值没有引用指向,也会被GC回收):"+map.remove("one"));
        System.out.println("remove(\"two\", 3):删除指定key-value对,不满足映射关系,不删除:"+map.remove("two", 3));
        System.out.println("删除后map:"+map);
        map.clear();
        System.out.println("map.clear()清空map:"+map);
        System.out.println(Integer.toBinaryString(2)+" "+Integer.toBinaryString(-2)+" "+Integer.toHexString(-2>>>2)+" "+Integer.toBinaryString(-2>>>2));

        map.put("one", 1);map.put("two",2);map.put("three",3);map.put("four",4);



        System.out.print("配合keySet()方法遍历key值:===>>[");
        Set<String> keySet = map.keySet();
        for(String s:keySet){
            System.out.print(s+", ");
        }
        System.out.println("]");

        System.out.print("配合keySet()和get()方法遍历key和value:===>>{");
        for(String s:keySet){
            System.out.print(s+"="+map.get(s)+", ");
        }
        System.out.println("}");

        System.out.print("配合values()方法遍历value值:===>>[");
        Collection<Integer> values = map.values();
        for(Integer i : values){
            System.out.print(i+", ");
        }
        System.out.println("]");

        System.out.print("配合entrySet()方法,同时遍历key和vlaue值:===>>{");
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
            System.out.print(entry.getKey()+"="+entry.getValue()+", ");
        }
        System.out.println("}");
    }
}

2. HashMap源码(JDK1.8)

HashMap底层是由哈希表数据结构实现(数组+链表+红黑树JDK1.8)
hash怎么算,一定要搞懂,否则后面源码看不懂
hash不一样,但是下标算成一样的怎么办(哈希碰撞),一定要搞懂,否则后面源码看不懂
        //获取key的hashCode值h,然后保存h无符号右位移16位的二进制i,将h ^ i 得到我们需要的hashCode码
        //11010111001100110 ^ 异或表示 值一样取0,值相异为1
        //00000000000000001
        //11010111001100111
        int h;
        String key = "one";
        h = key.hashCode();//获取hashCode值
        System.out.println("key通过hashCode()方法获取的hashcode值"+Integer.toBinaryString(h));
        int i = h >>> 16;//右位移16位,
        System.out.println("将hashcode值,右位移16位,用来减少碰撞"+Integer.toBinaryString(i));
        int hashCode = (h) ^ (i);//异或
        System.out.println("右位移16位异或减少碰撞,还能保留高位和低位的特性:"+hashCode);
        System.out.println("hashCode的二进制"+Integer.toBinaryString(hashCode));


        //计算下标,n - 1 n是散列表长度,让高位参与运算,
        int n = 16;
        int i1 = 16 - 1;
        System.out.println(Integer.toBinaryString(i1));
        System.out.println("最终下标位置:"+(i1&hashCode));
分析源码1:算hash
源码分析2:如何在用户指定大小时(用户可能指定偶数也可能指定负数,不可以指定就不用这么麻烦了,每次都<<1就可以了),依旧保证大小为2的幂次方,tableSizeFor 表示生成一个大于等于 cap 且为 2 的 N 次方的最小整数
        System.out.println("=======================测试当我们向要将散列表长度扩充为8,我们如何确保长度为2的幂次方的基础上,还能不浪费空间呢===============================");
        int n = 8;//
        int n2 = n-1;//-1可以解决空间浪费
        System.out.println(n+"==================="+n2);
        System.out.println(Integer.toBinaryString(n)+"=============="+Integer.toBinaryString(n2));
//        n = n - 1;//7
        System.out.println(Integer.toBinaryString(n |= n >>> 1)+"=============="+Integer.toBinaryString(n2|= n2 >>> 1));
        System.out.println(Integer.toBinaryString(n |= n >>> 2)+"=============="+Integer.toBinaryString(n2|= n2 >>> 2));
        System.out.println(Integer.toBinaryString(n |= n >>> 4)+"=============="+Integer.toBinaryString(n2|= n2 >>> 4));
        System.out.println(Integer.toBinaryString(n |= n >>> 8)+"=============="+Integer.toBinaryString(n2|= n2 >>> 8));
        System.out.println(Integer.toBinaryString(n |= n >>> 16)+"=============="+Integer.toBinaryString(n2|= n2 >>> 16));

        System.out.println((n + 1)+"=============="+(n2+1));

分析源码3:重要属性
源码解析4:创建散列表
源码分析5:添加
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //声明了一个局部变量 tab,局部变量 Node 类型的数据 p,int 类型 n,i
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //首先将当前 hashmap 中的 table(哈希表)赋值给当前的局部变量 tab,然后判断tab 是不是空或者长度是不是 0,实际上就是判断当前 hashmap 中的哈希表是不是空或者长度等于 0
        if ((tab = table) == null || (n = tab.length) == 0)
        //如果是空的或者长度等于0,代表现在还没哈希表,所以需要创建新的哈希表,默认就是创建了一个长度为 16 的哈希表
            n = (tab = resize()).length;
        //将当前哈希表中与要插入的数据位置对应的数据取出来,(n - 1) & hash])就是找当前要插入的数据应该在哈希表中的位置,如果没找到,代表哈希表中当前的位置是空的,否则就代表找到数据了, 并赋值给变量 p
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);//创建一个新的数据,这个数据没有下一条,并将数据放到当前这个位置
        else {//代表要插入的数据所在的位置是有内容的
        //声明了一个节点 e, 一个 key k
            Node<K,V> e; K k;
            if (p.hash == hash && //如果当前位置上的那个数据的 hash 和我们要插入的 hash 是一样,代表没有放错位置
            //如果当前这个数据的 key 和我们要放的 key 是一样的,实际操作应该是就替换值
                ((k = p.key) == key || (key != null && key.equals(k))))
                //将当前的节点赋值给局部变量 e
                e = p;
            else if (p instanceof TreeNode)//如果当前节点的 key 和要插入的 key 不一样,然后要判断当前节点是不是一个红黑色类型的节点
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//如果是就创建一个新的树节点,并把数据放进去
            else {
                //如果不是树节点,代表当前是一个链表,那么就遍历链表
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {//如果当前节点的下一个是空的,就代表没有后面的数据了
                        p.next = newNode(hash, key, value, null);//创建一个新的节点数据并放到当前遍历的节点的后面
                        if (binCount >= TREEIFY_THRESHOLD - 1) // 重新计算当前链表的长度是不是超出了限制
                            treeifyBin(tab, hash);//超出了之后就将当前链表转换为树,注意转换树的时候,如果当前数组的长度小于MIN_TREEIFY_CAPACITY(默认 64),会触发扩容,我个人感觉可能是因为觉得一个节点下面的数据都超过8 了,说明 hash寻址重复的厉害(比如数组长度为 16 ,hash 值刚好是 0或者 16 的倍数,导致都去同一个位置),需要重新扩容重新 hash
                        break;
                    }
                    //如果当前遍历到的数据和要插入的数据的 key 是一样,和上面之前的一样,赋值给变量 e,下面替换内容
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { //如果当前的节点不等于空,
                V oldValue = e.value;//将当前节点的值赋值给 oldvalue
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value; //将当前要插入的 value 替换当前的节点里面值
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//增加长度
        if (++size > threshold)
            resize();//如果当前的 hash表的长度已经超过了当前 hash 需要扩容的长度, 重新扩容,条件是 haspmap 中存放的数据超过了临界值(经过测试),而不是数组中被使用的下标
        afterNodeInsertion(evict);
        return null;
    }

源码分析6:底层数组扩容

3. TreeMap源码(JDK1.8)

TreeMap底层就是一棵二叉排序树,学会二叉排序树就行了,没啥好讲的,就走一遍源码流程得了

六、同步类容器

1. Collections工具类转换

首先,集合有一个工具类,Collections,提供一些api,方便我们操作集合,但是不常用。然而我们可以用它将线程不安全的容器,转换成线程安全的。
ArrayList<Integer> arrayList = new ArrayList<>();
List<Integer> synchronizedList = Collections.synchronizedList(arrayList);//变成线程安全
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CollectionsTest {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        HashMap<String, Integer> hashMap = new HashMap<>();

        List<Integer> synchronizedList = Collections.synchronizedList(arrayList);
        Map<String, Integer> synchronizedMap = Collections.synchronizedMap(hashMap);

        //搞个线程池测试
        ExecutorService es = Executors.newFixedThreadPool(100);

        //向集合中插入10000个值
        for(Integer i = 0;i<10000;i++){
            Integer finalI = i;
            es.execute(new Runnable() {
                @Override
                public void run() {
                    //arrayList.add(1);//线程不安全,直接报错Exception in thread "main" java.util.ConcurrentModificationException
                    synchronizedList.add(finalI);
                }
            });
        }
        //System.out.println(arrayList);
        System.out.println(synchronizedList);
        //关闭线程池
        es.shutdown();
        //监控线程是否执行完毕
        while(true){
            System.out.println("正在监控线程是否都执行完毕");
            //线程都执行完后返回true
            if(es.isTerminated()){
                System.out.println("所有子线程都执行完毕了!");
                //输出集合
                System.out.println(synchronizedList);
                //执行完毕后看一下集合中元素数量
                System.out.println(synchronizedList.size());
                if(synchronizedList.size() == 10000){
                    System.out.println("线程安全!");
                }else{
                    System.out.println("线程不安全!!!");
                }
                break;
            }
        }
    }
}

2. Collections源码(JDK1.8)

可以发现,他对整个集合加了锁,这是很影响性能的

七、并发类容器

JDK1.5之后,提供多种并发类容器,可以代替同步类容器,提升性能、吞吐量

1. ConcurrentMap

HashTable为什么被淘汰?为什么效率低
ConcurrentMap为什么效率高
测试一下,ConcurrentMap最快,HashTable慢了4倍,synchronizedMap最慢:simple_collection/ConcurrentMapApiTest.java
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentMapApiTest {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> cHMap = new ConcurrentHashMap<>();
        //线程池
        ExecutorService es = Executors.newFixedThreadPool(10);

        for (int i = 0;i<10;i++){
            es.execute(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    for (int j = 0;j<1000000;j++){
                        cHMap.put("test"+j,j);
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println(Thread.currentThread()+"==ConcurrentHashMap==>"+"执行完成共需要"+(endTime - startTime));

                }
            });
        }
        es.shutdown();
        System.out.println("========================HashTable=============================");
        //线程池
        ExecutorService es2 = Executors.newFixedThreadPool(10);
        Hashtable<String, Integer> hashtable = new Hashtable<>();
        for (int i = 0;i<10;i++){
            es2.execute(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    for (int j = 0;j<1000000;j++){
                        hashtable.put("test"+j,j);
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println(Thread.currentThread()+"==HashTable==>"+"执行完成共需要"+(endTime - startTime));

                }
            });
        }
        es2.shutdown();
        System.out.println("========================synchronizedMap=============================");
        //线程池
        ExecutorService es3 = Executors.newFixedThreadPool(10);
        Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<>());

        for (int i = 0;i<10;i++){
            es3.execute(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    for (int j = 0;j<1000000;j++){//太慢了,我给他剪了个0
                        synchronizedMap.put("test"+j,j);
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println(Thread.currentThread()+"==synchronizedMap==>"+"执行完成共需要"+(endTime - startTime));
                }
            });
        }
        es3.shutdown();

    }
}

2. COW(Copy On Write)并发容器,写时复制容器

原理
两种COW容器

1. CopyOnWriteArrayList实现类

简单使用:simple_collection/COWApiTest.java
import java.util.concurrent.CopyOnWriteArrayList;

public class COWApiTest {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> cOWList = new CopyOnWriteArrayList<>();
        //添加元素,可添加重复元素
        cOWList.add(1);
        cOWList.add(1);
        cOWList.add(1);
        System.out.println("使用add添加,元素可以重复:"+cOWList);
        cOWList.addIfAbsent(1);
        System.out.println("使用addIfAbsent添加,如果集合中已有相同的,不可以添加:"+cOWList);
    }
}

2. CopyOnWriteArrayList源码(JDK1.8)

可重入锁
就是一个线程执行一个加锁的代码,此时这个线程,执行另一个加锁的代码,两个锁发现是同一个线程,就让线程可以进入,这就是可重入锁
基本源码
addIfAbsent()方法效率会比add()低很多,因为每次都需要比较

3. CopyOnWriteArraySet源码(JDK1.8)

使用和普通set没有区别,不做api介绍,直接上源码分析,其实就是用CopyOnWriteArrayList实现而已,感觉完全没必要看

八、队列

简单类图(烦的不行,东西怎么越写越多?):simple_collection/uml/Queue.puml
为什么不用非阻塞队列
  1. 当从队列取数据时,队列为空

1. BlockingQueue接口常用API解析

BlockingQueue是个接口,需要具体实现类,给出APi,不做测试了
描述(🍅:不阻塞/🏆:阻塞)API
🍅将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true,如果没有可用空间,抛出IllegalStateException异常boolean add(E e)
🍅将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true,如果没有可用空间,返回falseboolean offer(E e)
🏆将指定元素插入此队列中,将等待可用的空间(如果有必要)void put(E e)
🏆获取并移除此队列头部,在元素变得可用之前一直等待(如果有必要)E take()
🏆获取并移除此队列的头部,在指定的等待时间前等待可用元素(如果有必要)E poll(Long timeout,TimeUnit unit)
🍅从此队列中移除指定元素的单个实例(如果存在)boolean remove(Object o)
🍅无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的附加元素数量;如果没有内部限制,返回Integer.MAX_VALUEint remainingCapacity()

2. ArrayBlockingQueue实现类和源码(JDK1.8)

ArrayBlockingQueue
基本使用:其实就是put和take这两个阻塞方法
import java.util.concurrent.ArrayBlockingQueue;

public class QueueTest {
    public static void main(String[] args) throws InterruptedException {
        //ArrayBlockingQueue,初始化3个空间
        ArrayBlockingQueue<Integer> arrayBlockingQueue = new ArrayBlockingQueue<Integer>(3);

        //添加数据
        arrayBlockingQueue.offer(1);
        arrayBlockingQueue.offer(2);
        arrayBlockingQueue.offer(3);
//        arrayBlockingQueue.put(4);//如果没有兄弟往外取,就会一直阻塞
        System.out.println("==================="+arrayBlockingQueue+"===================================");
        System.out.println("peek()方法,ArrayBlockingQueue特有,不移除,只是返回值"+arrayBlockingQueue.peek());
        System.out.println("take()方法,获取并移除队列头"+arrayBlockingQueue.take());
        System.out.println("remove()方法,移除3这个元素"+arrayBlockingQueue.remove(3));
        System.out.println("==================="+arrayBlockingQueue+"===================================");

    }
}
源码分析
  1. dequeue():拿到数组,根据取元素下标获取数据,然后下标位置,置为null,如果下标++超出队列大小,重新置为0;队列元素个数–;迭代器如果不为空,通知迭代器执行逻辑(当队列为空时调用。通知所有活动迭代器队列为空,清除所有弱引用,并解除itrs数据结构的链接。当takeIndex绕到0时调用。通知所有迭代器,并删除任何过时的迭代器)。通知队列满放元素等待池,可以继续放元素了。
    在这里插入图片描述
  2. 阻塞
上面的put和take()方法,while是必须的(不能是if),因为如果池子中线程被激活瞬间,其它线程又放入/取出数据,让队列又满了/空了,那么还沿着await后面执行就会出错了

3. LinkedBlockingQueue实现类和源码(JDK1.8)

LinkedBlockingQueue
源码

4. SynchronousQueue队列

SynchronousQueue队列:一种很特殊的队列,方便线程间,高效数据传输
此队列容量为0,当插入元素时,必须同时有个线程往外取
就是说,当你往这个队列里面插入一个元素,它就拿着这个元素站着(阻塞),直到有个取元素的线程来,它就把元素交给它
就是用来同步数据的,也就是线程间交互数据用的一个特殊队列
package com.mashibing.juc.c_025;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

public class T08_SynchronusQueue { //容量为0
	public static void main(String[] args) throws InterruptedException {
		BlockingQueue<String> strs = new SynchronousQueue<>();
		
		new Thread(()->{//这个线程就是消费者,来取值
			try {
				System.out.println(strs.take());//和同步队列要值
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();

		strs.put("aaa"); //阻塞等待消费者消费,就拿着aaa站着,等线程来取
		//strs.put("bbb");
		//strs.add("aaa");//因为容量为0,添加的话会直接报错
		System.out.println(strs.size());
	}
}

5. PriorityQueque队列

优先队列(二叉树算法,就是排序);队列取的时候有先后顺序,数据有不同权重
import java.util.PriorityQueue;

public class T07_01_PriorityQueque {
    public static void main(String[] args) {
        PriorityQueue<String> q = new PriorityQueue<>();

        q.add("c");
        q.add("e");
        q.add("a");
        q.add("d");
        q.add("z");

        for (int i = 0; i < 5; i++) {
            System.out.println(q.poll());
        }

    }
}

6. DelayQueue队列

无界阻塞队列:只能存放Delayed对象的队列,所以,当我们想要使用这个队列,我们的类必须实现Delayed接口,重写getDelay和compareTo两个方法
业务场景
使用步骤

7. Deque双端队列

前面都是一端放,一端取;这个是两端都可以放,也都可以取
举报

相关推荐

0 条评论