Bootstrap

韩顺平Java学习笔记_集合底层源码解读

目录

一集合概述

 1. 集合的理解和好处

 2. 集合框架体系

​编辑

集合主要是两组(单列集合,双列集合), Collection接口有两个重要的子接口List Set,他们的实现子类都是单列集合.Map接口的实现子类是双列集合,存放KV对.

2.1. 单列集合

​编辑

2.2 双列集合

二 Collection接口和常用方法

1. Collection接口实现类的特点

​编辑 2.Arraylist的常见使用方法

3.Collection接口的遍历方式 

3.1 使用Iterator(迭代器)

3.2 增强for循环 

3.3 使用普通for

4. List接口和常用方法

1.特点

2.常用方法

3.练习题 

5. ArrayList底层结构和源码分析

1.注意事项

2.底层操作机制源码分析(重难点) 

6.Vector底层结构和源码分析

6.1 Vector和ArrayList的比较 

6.2源码分析 

7.LinkedList底层结构

​编辑 7.1 理解双向链表

7.2 LinkedList常用方法

7.3 LinkedList底层结构与源码分析

8.List 集合选择

8.1 ArrayList和LinkedList比较

5.Set接口和常用方法

1. Set特点:

2. Set接口实现类-HashSet

(1)特点:

 (2)HashSet底层机制

练习:

3 Set接口实现类-LinkedHashSet

6. Map接口和常用方法

6.1 Map接口实现类的特点

6.2 Map接口的常用方法

6.3 Map接口的六大遍历方式

6.4 Map接口课堂练习

6.5 HashMap小结

6.6 HashMap底层机制及源码解读

6.7 Map接口实现类-Hashtable

6.8 Map接口实现类-Properties

七 总结-开发中如何选择集合实现类 

八 TreeSet源码解读

 九 TreeMap源码解读

十 Collections工具类 

十一 练习题


一集合概述

 1. 集合的理解和好处

 2. 集合框架体系

集合主要是两组(单列集合,双列集合), Collection接口有两个重要的子接口List Set,他们的实现子类都是单列集合.Map接口的实现子类是双列集合,存放KV对.

2.1. 单列集合

2.2 双列集合


二 Collection接口和常用方法

1. Collection接口实现类的特点

 2.Arraylist的常见使用方法

        //add 添加单个元素
        ArrayList arrayList = new ArrayList();
        arrayList.add("jack");
        arrayList.add(10);
        arrayList.add(true);
        System.out.println(arrayList);
        //delete删除元素
        arrayList.remove(0); //删除第一个元素
        arrayList.remove("jack"); //指定删除某个元素
        System.out.println(arrayList.contains("jack")); //contains查找元素是否存在
        arrayList.size(); //size:获取元素个数
        arrayList.isEmpty(); //判断是否为空
        arrayList.clear(); //清空,慎重使用!
        ArrayList<Object> list = new ArrayList<>();
        arrayList.addAll(new ArrayList<>());  //addAll(Collection)可以添加新集合
        arrayList.containsAll(list); //查找多个元素是否都存在
        list.removeAll(arrayList); // 删除多个元素

3.Collection接口的遍历方式 

3.1 使用Iterator(迭代器)

迭代器的执行原理:

1. Iterator iterator = coll.iterator();可以得到一个集合的迭代器

2. hasNext()方法可以判断是否还有下一个元素

3. next()方法将指针下移并且将下移后集合位置上的元素返回

4. remove()方法将移除返回的元素

Note: 调用next()方法前必须调用hasNext()检测是否还有元素,否则有可能抛出异3

Iterator iterator = col.iterator();
        while (iterator.hasNext()) {  //快捷建 itit 一键生成
            Object obj =  iterator.next();
            System.out.println("obj=" + obj);
        }

3.2 增强for循环 

for(Object book : col){
    System.out.println("book=" + book);
}

Note: 增强for底层仍然是迭代器,可以理解为简化版的迭代器遍历;

          也可以直接在数组中使用;

          快捷键 I

3.3 使用普通for

for(int i = 0; i < list.size(); i++){
        Object obj = list.get(i);
        System.out.println(obj);
}

4. List接口和常用方法

1.特点

(1) List集合类中元素有序,且可重复;

(2) List集合类中的每个元素都有其对应的顺序索引,即支持索引索引从0开始;

(3) List集合类的实现类有很多,常用的有ArrayList, LinkedList和Vector.

2.常用方法

3.练习题 

        ArrayList<Object> list = new ArrayList<>();
        list.addAll(Arrays.asList(0,1,2,3,4,5,6,7,8,9));
        list.add(1,"韩顺平教育");
        list.get(4);
        list.remove(list.get(5));
        list.set(6,"修改元素7");
        Iterator<Object> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
        }

排序练习


5. ArrayList底层结构和源码分析

1.注意事项

2.底层操作机制源码分析(重难点) 

2.1结论

transisent 表示瞬间,短暂的 ,如果被transisent修饰,表示该属性不会被序列化

2.2源码分析

debugCode(强烈建议自己debug一下!!!)

        ArrayList list = new ArrayList();
//      ArrayList list2 = new ArrayList(8);
        for (int i = 1; i <=10; i++){
            list.add(i);
        }
        for (int i = 11; i <= 15; i++){
            list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);

sourcecode分析:

(1) 无参构造

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
//创建了一个空的elementData数组

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
//执行list.add (1)先确定是否要扩容 (2) 然后再执行赋值操作

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
//该方法确定minCapacity (1)第一次扩容为10 

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
//(1)modCount记录集合被修改的次数,防止多线程操作 (2)如果elementData大小不够,就调用grow()扩容

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
//(1)真的扩容 (2)使用扩容机制来确定要扩大到多大 (3)第一次newCapacity=10 (4)以后按1.5倍扩容
//(5)扩容使用Arrays.copyOf()

(2)指定大小构造器

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
//创建了一个指定大小的elementData数组 this.elementData = new Object[capacity]

//如果是有参构造器,第一次扩容,就按照elementData的1.5倍扩容

6.Vector底层结构和源码分析

6.1 Vector和ArrayList的比较 

6.2源码分析 

        Vector vector = new Vector();
      //Vector vector = new Vector(8);
        for (int i = 0; i < 10;i++){
            vector.add(i);
        }
        vector.add(100);
        System.out.println(vector);
        //1.new Vector()底层
//        public Vector() {
//            this(10);
//        }
        //补充:如果是有参构造 如new Vector(8) ,走的方法就是
//    public Vector(int initialCapacity) {
//            this(initialCapacity, 0);
//        }

        //2.vector.add(i)
        //2.1下面这个方法就是添加数据到集合
//        public synchronized boolean add(E e) {
//            modCount++;
//            ensureCapacityHelper(elementCount + 1);
//            elementData[elementCount++] = e;
//            return true;
//        }
        //2.2 确定是否需要扩容
//        private void ensureCapacityHelper(int minCapacity) {
//            // overflow-conscious code
//            if (minCapacity - elementData.length > 0)
//                grow(minCapacity);
//        }
        //2.3如果需要的数组大小不够用,就扩容,扩容的算法
//        private void grow(int minCapacity) {
//            // overflow-conscious code
//            int oldCapacity = elementData.length;
//            int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
//                    capacityIncrement : oldCapacity);
//            if (newCapacity - minCapacity < 0)
//                newCapacity = minCapacity;
//            if (newCapacity - MAX_ARRAY_SIZE > 0)
//                newCapacity = hugeCapacity(minCapacity);
//            elementData = Arrays.copyOf(elementData, newCapacity);
//        }

7.LinkedList底层结构

 7.1 理解双向链表


       //模拟一个双向链表(这里省略了Node类的定义)
        Node jack = new Node("jack");
        Node tom = new Node("tom");
        Node hsp = new Node("老韩");

        //连接三个结点,形成双向链表
        jack.next = tom;
        tom.next = hsp;
        hsp.pre = tom;
        tom.pre = jack;

        Node first = jack;//让first引用指向jack,就是双向链表的头结点
        Node last = hsp; //让last引用指向hsp,就是双向链表的尾结点



        //演示链表的添加对象/数据 是多么方便
        //要求,是在tom和hsp之间插入一个对象 zhangfei
        Node zhangfei = new Node("张飞");
        tom.next = zhangfei;
        hsp.pre = zhangfei;
        zhangfei.next = hsp;
        zhangfei.pre = tom;

        //演示,从头到尾进行遍历
        while (true) {
            if (first == null){
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;

        }

7.2 LinkedList常用方法

        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println(linkedList);
        linkedList.remove();//共三种方法可以remove: remove() 默认删除第一个;remove(Object)删除指定对象;remove(index)删除索引所在位置的对象
        System.out.println(linkedList);
        //修改某个结点对象
        linkedList.set(1,99);
        //得到某个结点对象
        //get(1)是得到双向链表的第二个对象
        Object o = linkedList.getFirst();
        //因为linkedlist实现了list接口,遍历方式
        Iterator iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }
        for(Object i : linkedList){
            System.out.println(i);
        }
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.println(linkedList.get(i));
        }

7.3 LinkedList底层结构与源码分析


        //源码阅读:添加
        /*
        1. LinkedList linkedList = new LinkedList();
        public LinkedList() {}
        2. 这时linkedlist的属性first = null  last = null
        3. 执行
            public boolean add(E e) {
            linkLast(e);
            return true;
        }
        4.将新的结点加入到双向链表的最后
        void linkLast(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++;
        modCount++;
    }
         */
        /*
        源码阅读:remove()
        1.执行 removeFirst(
        public E remove() {
        return removeFirst();
    }
        2.执行
        public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
        3.执行 unlinkFirst(f),将f指向的双向链表的第一个结点拿掉
            private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
         */

8.List 集合选择

8.1 ArrayList和LinkedList比较


5.Set接口和常用方法

1. Set特点:

(1) Set下有两个实现类,分别为HashSet和TreeSet 

(2) 无序(添加的顺序和取出的顺序不一致,但取出的顺序是固定的)

(3) 无索引,所以无法用索引方式遍历

(4) 不允许重复元素,所以最多包含一个null

(5) 常用方法和遍历方法和Collection集合一样(无法用普通索引方法遍历)

2. Set接口实现类-HashSet

(1)特点:

HashSet.add(Object)方法会返回一个boolean值,成功返回true,失败返回false

HashSet.remove(Object)可以指定删除的元素 

思考: 这里为什么两个new String 只能加入一个?-->看HashSet底层机制

 (2)HashSet底层机制

底层机制说明:

这里的12不仅指12个数组元素,也可以表示单条链表上的元素个数到达12

练习:

 Note: 重写hashcode()和equals()

Note: 重写MyDate类的hashcode()和equals()以及Employee类name的hashcode()和equals() 


3 Set接口实现类-LinkedHashSet

(1) LinkedHashSet是HashSet的子类;

(2) LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表;

(3) LinkedHashSet根据元素的hashcode值来决定元素的存储位置,同时使用链表维护元素的次序,是元素看起来以插入顺序保存;

(4) LinkedHashSet不允许添加重复元素.

 练习:

 Note: 重写Car类的hashcode()和equals()即可


6. Map接口和常用方法

6.1 Map接口实现类的特点

Node<k,v>  --> entry<k,v> --> entrySet<entry>

6.2 Map接口的常用方法

(1) put: 添加

(2) remove: 根据键删除映射关系

(3) get: 根据键获取值

(4) size: 获取元素个数

(5) isEmpty: 判断个数是否为0

(6) clear: 清除

(7) containsKey: 查找键是否存在

6.3 Map接口的六大遍历方式

//1.keySet
        // (1)增强for
        for (Object key : map.keySet()){
            System.out.println(key + "-" + map.get(key));
        }
        // (2)迭代器
        Iterator iterator = map.keySet().iterator();
        while (iterator.hasNext()) {//快捷键itit
            Object key =  iterator.next();
            System.out.println(key + "-" + map.get(key));
        }
        //2.把所有的values取出
        Collection values = map.values();
        //这里可以使用所有collections使用的遍历方法
         //(1)增强for
        for (Object value:
             values) {
            System.out.println(value);
        }
         //(2)迭代器
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()) {
            Object next =  iterator1.next();
            System.out.println(next);
        }
        //3.通过entrySet来获取k-v
        //(1)增强for
        Set entrySet = map.entrySet();
        for (Object o:
             entrySet) {
           //将entry转成Map.Entry
            Map.Entry entry = (Map.Entry) o;
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }
        //(2)迭代器
        Iterator iterator2 = entrySet.iterator();
        while (iterator2.hasNext()) {
            Object next =  iterator1.next();
            Map.Entry entry = (Map.Entry) next;
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }

6.4 Map接口课堂练习


        HashMap<Object, Object> map = new HashMap<>();
        Employee1 tom = new Employee1("TOM", 24000, 234);
        Employee1 kate = new Employee1("KATE", 23000, 357);
        Employee1 chris = new Employee1("CHRIS", 10000, 123);
        map.put(tom.getId(),tom);
        map.put(kate.getId(),kate);
        map.put(chris.getId(),chris);
        //1.keySet
        for (Object o : map.keySet()) {
            Employee1 e = (Employee1) map.get(o);
            if (e.getSal() > 18000){
                System.out.println(e.getName());
            }
        }
        //2.entrySet
        for(Object o : map.entrySet()){
            Map.Entry entry = (Map.Entry) o;
            Employee1 e1 = (Employee1) entry.getValue();
            if (e1.getSal() > 18000){
                System.out.println(e1.getName());
            }
        }

6.5 HashMap小结

6.6 HashMap底层机制及源码解读

 


6.7 Map接口实现类-Hashtable

(1)存放的元素是键值对;

(2)键和值都不能为null;

(3)使用方法基本上和HashMap一样;

(4)Hashtable线程安全,但hashMap是线程不安全的;

底层分析:

1.底层有数组 Hashtable$Entry[] 初始化大小为11

2.threshold 8 = 11 * 0.75

3.扩容:按照自己的扩容机制进行 ,执行addEntry(...)方法

4.当count >= threshold时,进行扩容, 新容量为原先容量*2 +1


6.8 Map接口实现类-Properties

结构图:

 

分析:

1.Properties继承Hashtable

2.可通过kv存放数据,k和v都不能为null

3.方法:

put(k,v) 增加

get(k) 通过k获取v

remove(k) 通过k删除对应v

put(k,v) 修改kv

Note:对于用Properties去读取配置的详细内容可参照IO流笔记


七 总结-开发中如何选择集合实现类 


八 TreeSet源码解读

结构图:

构造器:

1.当我们使用无参构造器创建TreeSet时,仍然是无序的

2.如果想要添加的元素按照一定规则排序,可以使用有参构造,传入比较器(匿名内部类)

  

源码解读: 


 九 TreeMap源码解读

结构图:

源码大致同TreeSet


十 Collections工具类 

        ArrayList list = new ArrayList();
        list.add("tom");
        list.add("smith");
        list.add("king");
        list.add("milan");
        //reverse(list) 反转
        Collections.reverse(list);
        System.out.println(list);
        //shuffle(list)对list元素进行随机排序
        Collections.shuffle(list);
        System.out.println(list);
        //sort(list)按照元素的自然顺序对list元素按升序排序
        Collections.sort(list);
        //sort(list,comparator)希望按照指定的Comparator对list的元素进行排序
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //可以加入校验代码
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        //
        System.out.println(list);
        //swap(list,int,int) 将指定list集合中的i处元素和j处元素进行交换
        Collections.swap(list,0,1);
        System.out.println(list);

//Object max(Collection) 根据元素的自然顺序,返回给定集合中的最大元素
        System.out.println("自然顺序最大元素:" + Collections.max(list));
        //Object max(Collection,Comparator) 根据自定义的比较器,返回给定集合中的最大元素
        System.out.println("指定比较器最大元素:" + Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();
            }
        }));
        //Object min(Collection)
        //Object min(Collection,Comparator)  这两个方法参照max即可

        //int frequency(Collection,Object) 返回指定集合中指定元素出现的次数
        System.out.println("tom出现的次数:" + Collections.frequency(list,"tom"));
        //Void copy(list dest, list src)将src的内容复制到dest中 //为了完整拷贝,需要先给dest赋值,大小和list.size()一样

        //boolean replaceAll(List list, Object oldVal, Object newVal) 使用新值替换list对象的所有旧值
        System.out.println(Collections.replaceAll(list,"tom","汤姆"));
        System.out.println(list);

十一 练习题

ArrayList list = new ArrayList();
        News news1 = new News("新冠确诊病例超千万,数百万印度教信徒赴恒河\"圣浴\"引民众担忧");
        News news2 = new News("男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生");
        list.add(news1);
        list.add(news2);
        //倒序遍历
        for (int i = list.size() - 1; i >= 0; i--){
            News news = (News) list.get(i);
            news.setTitle(news.getTitle().substring(0, 15) + "...");
            System.out.println(list.get(i));
        }

HashMap m = new HashMap();
        m.put("jack", 650);
        m.put("tom", 1200);
        m.put("smith", 2900);
        m.put("jack", 2600);
        for (Object obj : m.entrySet()){
            Map.Entry entry = (Map.Entry) obj;
            Object value = entry.getValue();
            Integer value2 = (Integer)value + 100;
            entry.setValue(value2);
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }

(1)HashSet的去重机制: hashCode()+equals(),底层先通过存入对象,进行运算得到hash值,通过hash值得到对应的索引,如果发现table索引所在的位置没有数据,就直接存放,如果有数据,就进行equals比较[遍历比较],如果比较后不相同就加入,否则不加入

(2)TreeSet的去重机制: 如果传入了一个Comparator匿名对象,就使用实现的compare去重,如果方法返回0,就认为是相同的元素/数据,就不添加,如果没有传入Comparator,则以你添加的对象实现的Comparator对象的compareto方法实现去重

 

 由于p1.name的值更改,所以无法定义索引位置去remove,则remove不成功,仍旧输出两个元素; 而因为索引不同,则(1001,"cc")可以添加成功,此时输出三个对象;(1001,"AA")会挂在原先索引对象的后面,形成链表

  

;