集合的分类,ArrayList,LinkedList(单、双)仿写以及原理探究,HashMap的原理探究,队列(二十)

300 阅读14分钟

Java中集合接口和类的框架(部分常用的)

集合框架集.jpg

可变数组

vector数组源码分析(最古老的数组集合,线程安全)

1 addAll可能引起的扩容的分析

​ (1)数组长度扩为2倍或者 数组长度 = 数组长度 + 自定义扩容大大小;

​ (2)扩容后仍然放不下的情况

​ (3)扩容后超过数组最大长度的情况

ArrayList数组源码分析

1 在jdk1.7及其之后调用构造函数(无参的)时只创建了一个长度为0的数组,这样更加节省空间,在1.6以前是默认创建了一个长度为10的数组。

2 扩容的大小

​ 默认是扩大为原来的1.5倍

3 调用替换方法时,会返回被替换调的元素。

可变数组的实现(仿照ArrayList)

问题

1 为什么不使用E[]声明数组?

​ 使用E声明数组时,不能初始化数组,只能使用Object。

2 如何扩容?

​ all = Arrays.copyOf(all, all.length + (all.length >> 1));

​ 一定要赋值,否则相当于没有操作。

3 插入位置的合理范围?

​ [0,total]

4 如何移动元素?

​ System.arraycopy(all, index + 1, all, index, total - index - 1);

5 添加、删除元素后一定要修改total数量。

6 想要支持foreach循环?

​ 必须实现iterable接口,需要实现它的方法Iterator,这个方法返回迭代器,我们需要编写一个内部类Itr,来实现这个迭代器Iterator,实现它的方法hasnext和next。

这样做有什么好处?

(1)高内聚

(2)Itr时类的内部类才能访问这个类的私有成员

7 比较元素

​ (1)“==”和“equals”的区别?

​ ==比较的是两个元素的值和地址,equals是由元素的运行时类型的类规定的,更符合要求。

(2)需要注意的是?

​ .equals的点前边的对象不能为空,否则会报空指针异常。因此需要将数据分为两种情况来比较。

8 修改和删除的合理范围?

​ [0,total-1]

9 删除元素怎么做?这样做有什么好处?

​ all[--total] = null;

​ 不写的话,这个位置的地址指向元素,这个元素就迟迟得不到回收,就是说哪怕再数组中删除该元素了,只要不再添加新元素覆盖它,那么就会有地址一直指向它。

10 数组中存储的null是什么?

​ null表示没有任何对象,null默认是0.java执行程序判断all[i] = null;就不再到堆中去寻找对象。

package com.dyy.myarraylist;

import java.util.Arrays;
import java.util.Iterator;

public class MyArrayList<E> implements Iterable<E> {
    /*初始化时默认的数组*/
    private Object[] all = new Object[INIT_SIZE];
    /*可变数组中存储元素的个数*/
    private int total;
    /*初始化时数组默认的长度(自定义的)*/
    private static int INIT_SIZE = 5;


    /*判断是否需要扩容,如果需要扩容再进行扩容*/
    public void expandCapacity() {
        if (total + 1 > all.length) {
            all = Arrays.copyOf(all, all.length + (all.length >> 1));
        }
    }

    /**
     * @param value
     */
    public void add(E value) {
        /*判断是否扩容,如果需要扩容,尝试进行扩容*/
        expandCapacity();
        /*在最后一个元素的下标的后一个位置填入add的元素,元素个数total加一*/
        all[total++] = value;
    }

    /**
     * @param index 下标
     * @return 返回下标存储地址指向的元素
     */
    public E get(int index) {
        /*判断下标index是否在合理范围内,符合要求,返回元素*/
        if (index >= 0 && index < total) {
            return (E) all[index];
        } else {
            /*不合理,抛出异常*/
            throw new ArrayIndexOutOfBoundsException("数组下标不在合理范围");
        }
    }

    /**
     * @param value 要寻找的元素
     * @return 返回找到第一个元素的下标
     */
    public int IndexOf(E value) {
        /*判断这个元素是不是空值,
        如果是null就不能使用.equals方法了
        要另外进行处理,此处不适用==,因为==比较的是地址值,
        对于一些对象来说,按照它的运行时类型中重写的equals方法更合适*/
        if (value == null) {
            for (int i = 0; i < total; i++) {
                if (all[i] == null) {
                    return i;
                }
            }
            /*元素非null时*/
        } else {
            for (int i = 0; i < total; i++) {
                /*找到了就返回它的下标*/
                if (all[i].equals(value)) {
                    return i;
                }
            }
        }
        /*如果遍历结束都没有找到这个元素,那么*/
        return -1;
    }

    /**
     * @return 返回数组中的元素个数
     */
    public int size() {
        return total;
    }

    /**
     * @param index 删掉下标为index的元素
     */
    public void remove(int index) {
        /*删除末尾元素,不必移动元素,直接最后一个元素置为空就可以,数组长度减一*/
        if (index == total - 1) {
            all[--total] = null;
            /*如果在合理范围内,index之后的元素整体向前移动一位,
            *我们使用的是System.arraycopy(all, index + 1, all, index, total - index - 1);
            * 它的意思是从index+1开始长度为total-index-1的长度的元素保持相对顺序移动到index上
            * 此时最后一位元素需要抛弃,我们将这个元素置为空*/
        } else if (index >= 0 && index < total - 1) {
            System.arraycopy(all, index + 1, all, index, total - index - 1);
            all[--total] = null;
        } else {
            throw new ArrayIndexOutOfBoundsException("数组下标不在合理范围");
        }

    }

    /**
     * @param data 删掉下标从小到大第一个数据值为data的一项
     */

    public void remove(E data) {
        /*判断元素是否在数组中*/
        if (IndexOf(data) != -1) {
            /*data是特殊值null的情况下*/
            if (data == null) {
                for (int i = 0; i < total; i++) {
                    if (all[i] == null) {
                        if (i != total - 1) {
                            System.arraycopy(all, i + 1, all, i, total - i - 1);
                        }
                        all[--total] = null;
                    }
                }
                /*非null*/
            } else {
                for (int i = 0; i < total; i++) {
                    if (data.equals(all[i])) {
                        if (i != total - 1) {
                            System.arraycopy(all, i + 1, all, i, total - i - 1);
                        }
                        all[--total] = null;
                    }
                }
            }
        }
    }

    /**
     * @param loc   替换位置的下标
     * @param value 替换的新值
     */
    public void set(int loc, E value) {
        if (loc >= 0 && loc < total) {
            all[loc] = value;
        } else {
            throw new ArrayIndexOutOfBoundsException("数组下标越界");
        }
    }

    public void set(E old, E NewValue) {

        int loc = IndexOf(old);
        if (loc != -1) {
            all[loc] = NewValue;
        }
    }



    @Override
    public Iterator<E> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator {
        int cur = 0;

        @Override
        public boolean hasNext() {
            return (cur < total);
        }

        @Override
        public Object next() {
            return (E) all[cur++];
        }
    }
}

可变数组的测试

package com.dyy.myarraylist;

public class MyArrayListTest {
    public static void main(String[] args) {
        MyArrayList<String> my = new MyArrayList();

        my.add("hello");
        my.add("world");
        my.add("java");
        my.add("morning");
        my.add("evening");
        my.add("afternoon");
        my.add(null);
        my.add(null);
        System.out.println("添加元素");
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n----------------------------");
        System.out.println("数组长度:");
        System.out.println(my.size());


        System.out.println("------------------------------");
        my.remove(0);
        System.out.println("删除下标为0的元素");
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n------------------------------");
        my.remove("world");
        System.out.println("删除内容为world的元素");
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n------------------------------");
        my.remove(null);
        System.out.println("删除null的元素");
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n------------------------------");
        System.out.println("morning的下标:" + my.IndexOf("morning"));

        System.out.println("--------------------------------");
        my.set("java", "JAVA");
        System.out.println("替换java为JAVA");
        for (String s : my) {
            System.out.print(s + ",");
        }
    }
}
/*控制台输出结果
添加元素
        hello,world,java,morning,evening,afternoon,null,null,
        ----------------------------
        数组长度:
        8
        ------------------------------
        删除下标为0的元素
        world,java,morning,evening,afternoon,null,null,
        ------------------------------
        删除内容为world的元素
        java,morning,evening,afternoon,null,
        ------------------------------
        删除null的元素
        java,morning,evening,
        ------------------------------
        morning的下标:1
        --------------------------------
        替换java为JAVA
        JAVA,morning,evening,*/

链式结构

单链表的实现

问题

1 为什么使用内部类表示结点?

​ 单链表的结点类型,只符合该类的要求,而在别的地方用不到(高内聚思想)

2 每次发生删除和插入的时候,操作成功一定要更新total

3 使用内部类时有什么注意的?

​ 如果内部类加了static,那么外部类的<T><E>等就不能直接使用,必须要重新声明

package com.dyy.mylinkedlist;

import java.util.Iterator;

public class MyLinkedList<E> implements Iterable<E> {
    /*单链表头结点*/
    private Node head;
    /*单链表中结点的个数*/
    private int total;

    /**
     * 表示链表的结点
     * @param <E>
     */
    private class Node<E> {
        E data;
        Node next;

        public Node(E data, Node next) {
            this.data = data;
            this.next = next;
        }
    }

    /**
     * @param value 添加的元素的值,默认添加在链表末尾
     */
    public void add(E value) {
        Node node = head;
        Node newNode = new Node(value, null);
        /*链表为空时,将新结点作为头结点*/
        if (head == null) {
            head = newNode;
        } else {
            /*找到最后一个元素,设置它的后继指向新结点*/
            while (node.next != null) {
                node = node.next;
            }
            node.next = newNode;
        }
        /*总个数加一*/
        total++;
    }

    /**
     * @return 返回链表中元素的个数
     */
    public int size() {
        return total;
    }

    /**
     * @param value 要寻找的结点的值
     * @return 返回这个结点在链表中的虚拟地址(下标从0开始),如果找不到返回-1
     */
    public int IndexOf(E value) {
        Node node = head;
        int cur = 0;
        if (node != null) {
            /*如果要找的值恰好为null*/
            if (value == null) {
                while (node != null) {
                    /*找到这个元素就退出,因为cur每循环一次就+1,所以就是这个元素的下标*/
                    if (node.data == null) {
                        return cur;
                    }
                    node = node.next;
                    cur++;
                }
            /*如果是普通值*/
            } else {
                while (node != null) {
                    if (value.equals(node.data)) {
                        return cur;
                    }
                    node = node.next;
                    cur++;
                }
            }
        }
        /*找不到的话,我们使它返回-1*/
        return -1;
    }

    /**
     * @param value 要删除的结点的值,只会删除第一个遍历到符合要求的结点,没有的话就不进行任何操作
     */
    public void remove(E value) {
        /*遍历结点*/
        Node node = head;
        /*待删除结点的前一个结点*/
        Node prev = head;
        /*待删除结点*/
        Node del = null;
        /*判断这个结点在不在链表中*/
        if (IndexOf(value) != -1) {
            /*遍历查找删除结点和它的前一个结点*/
            while (node != null) {
                if (node.data == value) {
                    del = node;
                    break;
                }
                prev = node;
                node = node.next;
            }
            /*如果是头结点的话*/
            if (node == head) {
                head = head.next;
            }
            /*如果找到了被删除的结点*/
            if (node != null) {
                prev.next = del.next;
                /*将被删除的结点所有成员变量置为空,使虚拟机回收资源*/
                del.data = null;
                del.next = null;

            }
            total--;
        }
    }

    /**
     * @param old      要被替换掉的结点的值
     * @param NewValue 即将置入的新值
     *                 找到第一个data==old的结点,将它的值置为NewValue,如果找不到就不进行操作。
     */
    public void set(E old, E NewValue) {
        /*判断是否存在*/
        if (IndexOf(old) != -1) {
            Node node = head;
            /*这个被替换结点数据项是null时*/
            if(old==null){
                while (node != null) {
                    if (node.data == null) {
                        node.data = NewValue;
                        return;
                    }
                    node = node.next;
                }
                /*这个被替换结点数据项非空时*/
            }else{
                while (node != null) {
                    if (old.equals(node.data)) {
                        node.data = NewValue;
                        return;
                    }
                    node = node.next;
                }
            }
        }
    }

    /**
     * @param loc 被删除的结点在链表中的位置(从0开始)
     */
    public void remove(int loc) {
        /*判断位置是否合理*/
        if (loc < total && loc >= 0) {
            /*删除的是头结点*/
            if (loc == 0) {
                head = head.next;
                total--;
                return;
            }
            Node node = head;
            Node prev = null;
            /*遍历,找目标结点和它的前一个结点*/
            while (loc > 0) {
                prev = node;
                node = node.next;
                loc--;
            }
            /*进行删除*/
            prev.next = node.next;
            node.data = null;
            node.next = null;
            total--;
            /*不合理抛出异常*/
        } else {
            throw new ArrayIndexOutOfBoundsException("下标越界,超过合理范围");
        }
    }


    /**
     * 实现Iterable接口中的iterator接口,使这个链表能够使用foreach循环
     *
     * @return
     */
    @Override
    public Iterator iterator() {
        return new Itr();
    }

    /**
     * 实现Iterator,供iterator方法调用
     */

    private class Itr implements Iterator {
        Node node = head;

        @Override
        public boolean hasNext() {
            return node != null;
        }

        @Override
        public Object next() {
            E data = (E) node.data;
            node = node.next;
            return data;
        }
    }


}

单链表的测试

package com.dyy.mylinkedlist;

public class MyLinkedTest {
    public static void main(String[] args) {
        MyLinkedList<String> my = new MyLinkedList<>();

        my.add("hello");
        my.add("world");
        my.add("2021");
        my.add("7/19");
        my.add("18:52");
        my.add("java");
        System.out.println("添加完成!");
        System.out.println("遍历输出链表中的数据:");
        for (String s : my) {
            System.out.print(s + ",");
        }
        System.out.println();
        System.out.println("----------------------------------------------");
        System.out.println("java这个字符串的下标:" + my.IndexOf("java"));

        System.out.println("----------------------------------------------");
        System.out.println("链表的长度:" + my.size());


        System.out.println("----------------------------------------------");
        my.set("7/19", "七月十九号");
        my.set("java", "JAVA");
        System.out.println("替换7/19为七月十九号");
        System.out.println("替换java为JAVA");
        System.out.println("替换后的结果是:");
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n----------------------------------------------");
        System.out.println("删除2021");
        my.remove("2021");
        System.out.println("删除hello");
        my.remove("hello");
        System.out.println("删除结果展示:");
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n----------------------------------------------");
        System.out.println("删除某个位置上的结点");
        System.out.println("当前链表的长度" + my.size() + "\n");
        System.out.println("删除位置0上的元素:");
        my.remove(0);
        System.out.println("删除结点后的链表" + "长度" + my.size());
        for (String s : my) {
            System.out.print(s + ",");
        }
        System.out.println("\n\n删除位置1上的元素:");
        my.remove(1);
        System.out.println("删除结点后的链表" + "长度" + my.size());
        for (String s : my) {
            System.out.print(s + ",");
        }
//        System.out.println("\n删除位置7上的元素:");
//        my.remove(7);抛出下标越界异常
        System.out.println("删除结点后的链表");
        for (String s : my) {
            System.out.print(s + ",");
        }


        System.out.println("\n---------------------------------------------");
        System.out.println("添加元素null");
        my.add(null);
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n-----------------------------------------------");
        System.out.println("删除null结点");
        my.remove(null);
        System.out.println("展示");
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n-----------------------------------------------");
        System.out.println("添加元素null");
        my.add(null);
        for (String s : my) {
            System.out.print(s + ",");
        }

        System.out.println("\n----------------------------------------------");
        my.set(null,"NULL");
        System.out.println("将null替换为字符串");
        System.out.println("展示");
        for (String s : my) {
            System.out.print(s + ",");
        }
    }
}
/* 控制台输出结果
添加完成!
遍历输出链表中的数据:
hello,world,2021,7/19,18:52,java,
----------------------------------------------
java这个字符串的下标:5
----------------------------------------------
链表的长度:6
----------------------------------------------
替换7/19为七月十九号
替换java为JAVA
替换后的结果是:
hello,world,2021,七月十九号,18:52,JAVA,
----------------------------------------------
删除2021
删除hello
删除结果展示:
world,七月十九号,18:52,JAVA,
----------------------------------------------
删除某个位置上的结点
当前链表的长度4

删除位置0上的元素:
删除结点后的链表长度3
七月十九号,18:52,JAVA,

删除位置1上的元素:
删除结点后的链表长度2
七月十九号,JAVA,删除结点后的链表
七月十九号,JAVA,
---------------------------------------------
添加元素null
七月十九号,JAVA,null,
-----------------------------------------------
删除null结点
展示
七月十九号,JAVA,
-----------------------------------------------
添加元素null
七月十九号,JAVA,null,
----------------------------------------------
将null替换为字符串
展示
七月十九号,JAVA,NULL,
Process finished with exit code 0
*/

双向链表的实现

问题:

删除的结点是头结点或尾结点时,应该如何处理?如何判断删除的结点是头/尾结点?

​ 判断结点的前驱,前驱是空的话,那么这个结点是头结点。如果是头结点,那么就不再设置前驱的后继,然后将这个结点的下一个结点设置为头节点。

​ 判断结点的后继,后继是空的话,那么这个结点是尾结点。如果是尾结点,那么就不再设置后继的前驱,然后将这个结点的上一个结点设置为尾节点。

package com.dyy.doublelinkedlist;

import java.util.Iterator;

public class DoubleLinkedList<E> implements Iterable<E> {
    /*双向链表头结点*/
    private Node first;
    /*双向链表尾结点*/
    private Node last;
    /*链表中元素的个数*/
    private int total;

    /*双向链表的结点内部类*/
    private class Node {
        E data;
        Node next;
        Node prev;

        public Node(E data, Node next, Node prev) {
            this.data = data;
            this.next = next;
            this.prev = prev;
        }
    }

    /*支持iterator*/
    @Override
    public Iterator iterator() {
        return new Itr();
    }


    private class Itr implements Iterator {
        Node node = first;

        @Override
        public boolean hasNext() {
            return node != null;
        }

        @Override
        public Object next() {
            E value = node.data;
            node = node.next;
            return value;
        }
    }

    /**
     * @param value 目标结点的data值
     * @return 返回第一个值是value的虚拟下标(默认从0开始)
     */
    public int IndexOf(E value) {
        int cur = 0;
        Node node = first;
        /*如果查找的值是null特殊值*/
        if (value == null) {
            while (node != null) {
                if (node.data == null) {
                    return cur;
                }
                node = node.next;
                cur++;
            }
            /*正常值调用equals比较*/
        } else {
            while (node != null) {
                if (value.equals(node.data)) {
                    return cur;
                }
                node = node.next;
                cur++;
            }
        }
        return -1;
    }
    /**
     * @param value 添加一个结点,这个结点的值是value,默认添加在末端
     */
    public void add(E value) {
        Node node = new Node(value, null, null);
        /*判断链表是否为空,如果为空,将这个新结点作为头尾结点*/
        if (first == null) {
            first = node;
            last = node;
        } else {
            last.next = node;
            node.prev = last;
            last = node;
        }
        total++;
    }



    /**
     * @return 返回数组长度
     */
    public int size() {
        return total;
    }

    /**
     * @param value 被删除结点的值
     *              从头到尾进行遍历,遇到第一个值为value的结点就删掉,如果没有找到,那就不进行任何操作
     */
    public void remove(E value) {
        int loc = IndexOf(value);
        /*判断是否存在,如果存在那么find一定不为null(一定能找到这个元素),不用再进行判断*/
        if (loc != -1) {
            Node find = null;
            /*如果目标结点在前半段,我们从头结点开始遍历,提高效率*/
            if (loc <= (size() >> 1)) {
                Node node = first;
                /*值是null,使用==null比较*/
                if (value != null) {
                    while (node != null) {
                        if (value.equals(node.data)) {
                            find = node;
                            break;
                        }
                        node = node.next;
                    }
                    /*值非null,使用equals*/
                } else {
                    while (node != null) {
                        if (node.data == null) {
                            find = node;
                            break;
                        }
                        node = node.next;
                    }
                }
                /*如果在后半段,我们使用尾结点进行遍历*/
            } else {
                Node node = last;
                /*值非空*/
                if (value != null) {
                    while (node != null) {
                        if (value.equals(node.data)) {
                            find = node;
                            break;
                        }
                        node = node.prev;
                    }
                    /*值为空*/
                } else {
                    while (node != null) {
                        if (node.data == null) {
                            find = node;
                            break;
                        }
                        node = node.prev;
                    }
                }
            }

                Node p = find.prev;
                Node n = find.next;
                /*如果目标结点是头结点*/
                if (p == null) {
                    first = n;
                    /*非头结点*/
                } else {
                    p.next = n;
                }
                /*是尾结点*/
                if (n == null) {
                    last = p;
                    /*非尾结点*/
                } else {
                    n.prev = p;
                }
                /*将删除的结点成员变量置为空,使JVM进行回收*/
                find.data = null;
                find.prev = null;
                find.next = null;
                /*删除结束,元素个数-1*/
                total--;
        }
    }

    /**
     * @param loc 双向链表的虚拟下标,从0开始
     *            loc不在合理范围就抛出异常,在正常范围就进行删除
     */
    public void remove(int loc) {
        Node node = first;
        /*是否合理范围*/
        if (loc >= 0 && loc < total) {
            /*如果是头结点*/
            if (loc == 0) {
                first = first.next;
                first.prev = null;
                return;
                /*如果是尾结点*/
            } else if (loc == total - 1) {
                last = last.prev;
                last.next = null;
                /*啥也不是*/
            } else {
                while (loc > 0) {
                    node = node.next;
                    loc--;
                }
                Node p = node.prev;
                Node n = node.next;
                p.next = n;
                n.prev = p;
                node.prev = null;
                node.next = null;
                node.data = null;
            }
            /*删除后长度-1*/
            total--;
        } else {
            throw new ArrayIndexOutOfBoundsException("下标越界异常");
        }
    }

    /**
     * @param old      被替换的元素原来的值
     * @param NewValue 被替换的元素的新的值
     */
    public void set(E old, E NewValue) {
        Node node = first;
        /*目标值为null*/
        if (old == null) {
            while (node != null) {
                if (node.data == null) {
                    node.data = NewValue;
                }
                node = node.next;
            }
            /*非null*/
        } else {
            while (node != null) {
                if (old.equals(node.data)) {
                    node.data = NewValue;
                }
                node = node.next;
            }
        }
    }
}

双向链表的测试

package com.dyy.doublelinkedlist;

public class DLLTest {
    public static void main(String[] args) {
        DoubleLinkedList<String> dll = new DoubleLinkedList<>();
        dll.add("hello");
        dll.add("world");
        dll.add("test");
        dll.add(null);
        dll.add("method");
        dll.add("unit");
        dll.add("one");
        dll.add("DDL");
        dll.add(null);
        dll.add(null);
        for (String s : dll) {
            System.out.print(s + ",");
        }

        System.out.println("\n------------------------------------------------");
        System.out.println("此时双向链表长度为"+dll.size());

        System.out.println("------------------------------------------------");
        System.out.println("<<根据元素内容删除>>");
        System.out.println("删除元素world,它的下标为:"+dll.IndexOf("world"));
        dll.remove("world");

        for (String s : dll) {
            System.out.print(s + ",");
        }
        dll.remove(null);
        System.out.println("\n删除元素null");
        for (String s : dll) {
            System.out.print(s + ",");
        }

        System.out.println("\n-------------------------------------------------");
        System.out.println("<<根据下标删除>>");
        dll.remove(0);
        System.out.println("删除头结点");
        for (String s : dll) {
            System.out.print(s + ",");
        }
        System.out.println();
        dll.remove(dll.size()-1);
        System.out.println("删除尾结点");
        for (String s : dll) {
            System.out.print(s + ",");
        }

        System.out.println();
        dll.remove(2);
        System.out.println("删除中间结点下标为2的元素");
        for (String s : dll) {
            System.out.print(s + ",");
        }
        System.out.println("\n-------------------------------------------------");
        dll.set(null,"空");
        System.out.println("<<替换>>null为空");
        for (String s : dll) {
            System.out.print(s + ",");
        }

    }
}
/*控制台输出结果
hello,world,test,null,method,unit,one,DDL,null,null,
        ------------------------------------------------
        此时双向链表长度为10
        ------------------------------------------------
        <<根据元素内容删除>>
        删除元素world,它的下标为:1
        hello,test,null,method,unit,one,DDL,null,null,
        删除元素null
        hello,test,method,unit,one,DDL,null,null,
        -------------------------------------------------
        <<根据下标删除>>
        删除头结点
        test,method,unit,one,DDL,null,null,
        删除尾结点
        test,method,unit,one,DDL,null,
        删除中间结点下标为2的元素
        test,method,one,DDL,null,
        -------------------------------------------------
        <<替换>>null为空
        test,method,one,DDL,空,
*/

二叉树

在满树的情况下,每层的结点个数为2的(n-1)次方;从根结点到第n层的总结点数是2的n次方-1

哈希表

hashtable---最古老的哈希表,是线程安全的,但是效率比较低

hashmap---最常用的哈希表

jdk 1.7 是数组+链表结构

jdk 1.8 是数组+链表/红黑树结构

1 hashmap是如何解决哈希碰撞的?

​ (1)对象的hashcode % 数组长度

​ (2)对象的hashcode & (2的n次方-1)

hashmap底层是使用的第二种,他是位运算,效率比较高,由于如果hashmap的表是无规则长度时,比如10,我们使用与运算和7进行运算时结果种类变少,使碰撞概率大大增加,因此为了能够取到刚好的一个特殊值,hashmap的逻辑会使它的长度强制等于相近大小的2的幂次方(如果不指定的话,它的长度默认是16)。

在jdk1.7以前是默认转为最近的2的幂次方。

在jdk1.8中调用了Integer.highestOneBit(),向上取2的n次方。

2 一个位置上有多个元素存储时,hashmap是使用链表进行存储的,当链表长度达到8且满足一定条件时,会转化为红黑树。

​ 这是为了提高查找的效率,但是因为红黑树要始终保持平衡,所以导致了它添加或者删除的效率很低。

3 那么是不是链表长度一达到8就立刻转化为红黑树?

​ 不是的。

​ hashmap的数组有初始化长度,默认是16,数组越短,hash冲突的概率越高,那么HashMap的数组长度未达到临界值(64)时,一旦链表长度达到8,那么他是先进行扩容解决的,扩容后[index]中的(key,value)就要重新计算地址了,那么此时这8个结点可能也就分散了。

4 介绍一下这个临界值。

​ 这个值在HashMap源码中声明为

​ MIN_TREEIFY_CAPACITY = 64;

也就是说hashmap长度小于64之前,某个地址上的链表长度超过8是优先进行扩容的,然后对这些元素的地址重新计算,(这个时候这些元素可能分散),直到长度为64时,它才会使用红黑树。

5 remove删除后的红黑树?

​ 当map中(key,value)移除一些后,原来[index]下面的结点可能会变少,在结点减少至6个时,会考虑是否转换为链表。

6 什么时候红黑树转换为链表?

​ 并不会在remove的时候,发现少于6个立刻进行转换。它的转换场景有:

​ (1)下次在添加时,发现某个红黑树结点很少(大概四个左右),才转为链表

​ (2)remove到红黑树的结点降至3个左右时,转为链表

满足上面两个条件之一,会进行转换。

7 (key,value)中的key的值会决定它的最终位置,单并不是直接使用key&(长度-1)而是要经过再次运算

​ 比如说:(1)null值本来是没有hashcode的,但是hashMap把他处理为0,所以key==null的值一定在table[0]中。

(2)key的hashcode分布很广,但table.length是比较局限的,大多数情况是:计算后都会聚集在低位,高位的空间就浪费掉了,所以hashmap使用hashcode的高位二进制和地位二进制进行^运算。(依靠的是离散数学的知识)

8 添加到Map中的key值是不能再进行修改的。特别是跟index计算有关的都不能修改。

​ 因为无论在jdk1.7或者是jdk1.8中,map除了存储各个位置上元素的(key,value)还存储key的(处理过的)hashcode,因为我们每次添加新(key,value)时,会和存储的值进行比较,如果key重复,只会覆盖(key,value),所以我们新的key与原来的key做比较的话,会产生两种情况之一:

​ (1)我们新的(key,value)会先计算出一个它即将存储的table[index],如果table[index]为null,那么直接放进去就可以;

​ (2)如果table[index]非空,会比较原来的key和新的key,比较它们的hashcode,如果一样,再比较equals方法;如果不一样,就不用equals了。

为什么不直接使用equals?

这是因为数值运算非常快,equals比较慢,所以先使用hashcode比较。

​ 那么存储了hashcode的值,就不用每次比较的时候再获取key 的hashcode值,然后再次处理了,可以提高效率

concurrentHashMap---是线程安全的哈希表,效率比hashtable高

Map.Entry

​ 我们向哈希表中put的(key,value)键值对,它其实就是Map.Entry类型,他是一个接口,是Map的内部接口,所以他是无法被创建对象的。

​ 在JDK1.7时,HashMap中一个内部类Entry实现了Map.Entry

​ 在JDK1.8时,HashMap中一个内部类Node实现了Map.Entry,还有另外一个内部类TreeNode继承了LinkedHashMap的Entry类型,而这个LinkedHashMap又继承了HashMap的Node,相当于内部类TreeNode继承了HashMap的Node。

栈和队列

Stack栈

java.util.Stack<E>是Vector<E>集合的子类。

比Vector多了几个方法

  • (1)peek:查看栈顶元素,不弹出
  • (2)pop:弹出栈
  • (3)push:压入栈 即添加到链表的头

Queue和Deque

Queue 方法等效 Deque 方法
add(e)addLast(e)
offer(e)offerLast(e)
remove()removeFirst()
poll()pollFirst()
element()getFirst()
peek()peekFirst()

双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留stack类。

堆栈方法等效 Deque 方法
push(e)addFirst(e)
pop()removeFirst()
peek()peekFirst()

结论:Deque接口的实现类既可以用作FILO堆栈使用,又可以用作FIFO队列使用。

Deque接口的实现类有ArrayDeque和LinkedList,它们一个底层是使用数组实现,一个使用双向链表实现。