Java集合部分学习+源码解析

Java集合

对象的容器,实现了对对象常用的操作,类似数组功能。

集合中的数据都是在内存中,当程序关闭或者重启后集合中的数据就会丢失,所以说是临时存储数据的容器

集合整体框架

  • Collection:单列集合,用来存储一个一个对象

    • List:存储有序可重复的数据 -->“动态”数组

      • ArrayList

      • LinkedList

      • Vector

    • Set:存储无序不可重复的数据 -->集合

      • HashSet

      • LinkedHashSet

      • TreeSet

    • Queue: -->队列

      • Deque

      • PriorityQueue

  • Map:双列集合,用来存储一对一对的数据(key-value) -->函数

    • HashMap

    • LinkedHashMap

    • TreeMap

    • Hashtable

    • Properties

迭代器

public interface Collection<E> extends Iterable<E> {
    //Map不能用迭代器
}

增强for循环的底层就是迭代器

迭代器的使用

public class TestIterator {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("bj");
        list.add("bj2");
        list.add("bj3");
        list.add("bj4");
​
        //使用迭代器进行集合的遍历 list和set
        Iterator<String> iterator = list.iterator();
        //判断当前是否有下一个元素
        while(iterator.hasNext()){
            String s = iterator.next();//直接获得下一个元素
            System.out.println(s);
        }
    }
}
​

注意,不能在迭代器循环或者增强for循环来移除元素,会报错ConcurrentModifactionException,原因看源码

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
private void fastRemove(int index) {
        modCount++;//注意这个变量,一执行删除操作这个值就会变化
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
//当调用下列函数进行检查时,如果变量值不同就会抛出异常
 final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

要遍历删除集合元素时,可以使用迭代器来删除

while(iterator.hasNext()) {
    iterator.remove();
}
//原因:内部类Itr中的remove方法不涉及到modCount这个值
private class Itr implements Iterator<E> {
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
​
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
}

源码分析

public Iterator<E> iterator() {
        return new Itr();
    }
//创建了一个内部类Itr
 private class Itr implements Iterator<E> {
        int cursor;       // 用来判断的
        int lastRet = -1; // 取值的
        int expectedModCount = modCount;
​
        Itr() {}
​
        public boolean hasNext() {
            return cursor != size;
        }
​
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            //先后移cursor
            int i = cursor;
            //这里也要判断,是防止直接调用next
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            //再后移lastRet
            return (E) elementData[lastRet = i];
        }
 }

ListIterator

public interface ListIterator<E> extends Iterator<E>{
    //继承于Iterator,但专门为list提供
}
​
ListIterator<String> stringListIterator = list.listIterator();
        while(stringListIterator.hasNext()){
            String next = stringListIterator.next();
            System.out.println(next);
        }
//注意,下面这个倒着遍历不能和上面的分开用,因为一开始cursor指针并没有指到末尾,而是在最前面
//只有先正着遍历,让cursor移到最后才能倒着遍历
        while(stringListIterator.hasPrevious()){
            String previous = stringListIterator.previous();
            System.out.println(previous);
        }

Collection父接口

List接口

public interface List<E> extends Collection<E>{
     //List是一个接口,继承于Collection父接口
}

ArrayList

常用方法

public class Demo1 {
    public static void main(String[] args) {
        //创建集合的对象,前面可以直接用实现类,也可以直接用接口
        //ArrayList<Integer> list=new ArrayList<>();
        List<Integer> list=new ArrayList<>();
        List<Integer> list2=new ArrayList<>();
        List<List<Integer>> list3=new ArrayList<>();//集合中也可以保存集合,即泛型为集合
        //添加实现
        list.add(123);//自动装箱
        list.add(124);//自动装箱
        list.add(1,5);//还有在指定位置添加
        list.addAll(list2);//把其他集合整体添加到list里面,还可以在前面加下标参数
        //修改
        list.set(0, 345);
        //获得元素
        Integer num = list.get(0);
        //删除实现
        list.remove(0);//根据下标进行移除
        list.remove(new Integer(123));//注意,根据对象删除,如果是整数的话,要转化一下
        list.clear();//把集合内容清空
        list.removeAll(list);//把集合的内容删除
        boolean listEmpty = list.isEmpty();//判断集合内容是否为空,如果初始化为null,不能用
        int size = list.size();
        int i = list.indexOf(123);//根据内容返回下标,找不到返回-1
        boolean contains = list.contains(123);//判断集合中是否包含指定的内容
​
    }
}

源码分析

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    private static final long serialVersionUID = 8683452581122892189L;
    //默认初始长度为10
    private static final int DEFAULT_CAPACITY = 10
    //默认数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //存放元素的数组
    transient Object[] elementData; 
    //实际元素的个数
    private int size;
    //无参构造
    public ArrayList() {
        //当没有往数组里添加东西的时候,集合的容量为0,创建了一个空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    //有参构造
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //如果是有参构造,那么就要创建一个大小为initialCapacity的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
    //add方法:在真正添加元素的时候才创建为容量10的数组
    //假如此时new了一个数组,并没有添加元素,那么size为0
    //此时调用add方法加入元素
    public boolean add(E e) {
        //进入到下面函数中,传递的参数为1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //扩容完之后,插入到数组中,也就是下标为size的地方
        elementData[size++] = e;
        return true;
    }
    //此时minCapacity参数为1,elementData为空
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    //此时minCapacity为1,elementData为空
     private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //满足这个默认条件,则返回默认初始化容量和1中最大的那个,也就是返回10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    //以上函数的返回值作为此函数的参数传递进来,也就是minCapacity为10
    private void ensureExplicitCapacity(int minCapacity) {
        //这是个管线程的值,先不管
        modCount++;
​
        // overflow-conscious code
        //因为此时elementData为空,所以说这个条件是成立的
        if (minCapacity - elementData.length > 0)
            //就扩容
            grow(minCapacity);
    }
    
    //此时minCapacity为1,
    private void grow(int minCapacity) {
        // overflow-conscious code
        //此时elementData为空,旧的值是0(oldCapacity)
        int oldCapacity = elementData.length;
        //扩为原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果原来就是个空数组,那么这个条件满足
        if (newCapacity - minCapacity < 0)
            //此时创建的数组容量为10
            newCapacity = minCapacity;
        //这个是判断扩容后长度是否超过了最大范围
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //然后就将之前的空数组放这,扩充为容量为10的
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    //此时如果扩容后长度超过了最大范围
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            //溢出,报异常
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
    
    //设置值set
    public E set(int index, E element) {
        //检查下标,逻辑就是index<0或者大于了数组长度就抛异常
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        //返回的是旧值
        return oldValue;
    }
    
    //获得元素get
    public E get(int index) {
        rangeCheck(index);
​
        return elementData(index);
    }
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }
    
    //移除元素remove
    public E remove(int index) {
        rangeCheck(index);
​
        modCount++;
        E oldValue = elementData(index);
​
        int numMoved = size - index - 1;
        //当删除的数是数组最后的一个时候,此值为0,那么不满足条件,也就直接删除就可以
        //如果不是最后一个,那么还需要进行index后面元素的前移复制操作
        if (numMoved > 0)
            /*
            public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
            */
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将size-1处的元素赋为null值
        elementData[--size] = null; // clear to let GC do its work
        //这个也是返回旧值
        return oldValue;
    }
    
}

LinkedList

LinkedList是java中对双向非循环链表的实现。额外还添加了头尾操作方法(这些在List接口里面没有,如果要使用,实例化的时候不用向上转型),实现了Deque接口

常用方法

public class Demo2 {
    public static void main(String[] args) {
        //父接口表示指向子类对象
        List<Integer> list=new LinkedList<>();
        LinkedList<Integer> list2=new LinkedList<>();
        list.add(1);
        list.set(1,555);
        list.get(1);
        list.remove(0);
        //特有的方法就不能用多态了
        list2.addFirst(1);//头插
        list2.addFirst(2);
        list2.addFirst(3);
        System.out.println(list);//3 2 1 这个是头插
​
        list2.addLast(1);//尾插,
​
        //获得头节点的值,如果头节点为空,直接抛异常NoSuchElementException
        Integer first = list2.getFirst();
        Integer last = list2.getLast();//尾节点
​
​
        //也是,获得头节点的值,这个不会报异常,如果为空则返回空
        Integer integer = list2.peekFirst();
        //移除头节点,没有则会抛异常
        Integer integer1 = list2.removeFirst();
        //也是移除,但不会报错
        Integer integer2 = list2.pollFirst();
​
​
    }
}

源码分析

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;
    //静态内部类 节点Node
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
​
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
    
    //add方法
    public boolean add(E e) {
        //默认调用的是尾添加
        linkLast(e);
        return true;
    }
    //尾添加方法
     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++;
    }
    
    //头添加
    public void addFirst(E e) {
        linkFirst(e);
    }
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }
    
    //set修改,返回旧值
     public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);//获得元素
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
    Node<E> node(int index) {
        // assert isElementIndex(index);
​
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    
    //移除元素
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        //如果说当前要删除的元素在第一个
        //那么就将first指针指向当前元素的next
        if (prev == null) {
            first = next;
        } else {
            //否则就将当前元素的前一个指向当前元素的后一个
            prev.next = next;
            //前序节点置为null
            x.prev = null;
        }
        //如果要删除的元素是最后一个
        if (next == null) {
            //那么直接更新last指针为x的前一个
            last = prev;
        } else {
            //否则将x的后一个元素的prev指向x的prev
            next.prev = prev;
            //后序节点置为null
            x.next = null;
        }
        //清空值,长度减一
        x.item = null;
        size--;
        modCount++;
        return element;
    }
}

Stack

public class Stack<E> extends Vector<E> {
    //继承于Vector
    //底层也还是数组
}

Queue接口

//单端队列
public class Demo3 {
    public static void main(String[] args) {
        Queue<String> queue=new LinkedList<>();
        queue.offer("A");
        queue.offer("B");
        queue.offer("C");
        
        queue.poll();
​
    }
}

Deque(Queue的子接口)

//双端队列,如果从头进从尾处,那么就是栈结构
//从头出从尾进,则是单端队列
public class Demo3 {
    public static void main(String[] args) {
        Deque<String> deque=new LinkedList<>();
        deque.offerLast("A");
        deque.offerFirst("B");
        deque.pollFirst();
        deque.pollLast();
        System.out.println(deque);
​
    }
}

Set接口

唯一、无序、不可重复,放的位置顺序和放入顺序是不一样的,重复的元素不可以添加

public class Demo3 {
    public static void main(String[] args) {
        /*
        三种方式底层结构不同
        HashSet : 数组+链表+红黑树==》哈希表
        TreeSet : 红黑树
        LinkedHashSet : 链表+哈希表
        * */
        Set<String> set=new HashSet<>();//底层创建HashMap对象
        /*
        public HashSet() {
             map = new HashMap<>();Set的值底层就是存在map的key里面
        }*/
        Set<String> set1=new TreeSet<>();
        Set<String> set2=new LinkedHashSet<>();
        set.add("A");
        /*
        private static final Object PRESENT = new Object();//占位
        public boolean add(E e) {
              return map.put(e, PRESENT)==null;
         }
        */
        set.remove("A");
        //没有get方法,没有办法根据下标查找
        set.size();
        //遍历使用增强for循环
        for (String s:set) {
            System.out.println(s);
        }
    }
}

如何保证对象唯一?

HashSet

HashSet底层是哈希表,哈希表保证元素唯一的方法是equals()和hashcode()
hashcode()计算哈希码值,两个对象相同 那么hashcode()一定相同
equals()存储哈希表的时候判断当前值是否唯一
对于HashSet,如果Set<E> E是引用类,类实现中没有重写equals方法,那么调用的就是Object类中的equals方法,比较的是地址值,new两个相同值的对象地址不相同,那么就可以重复加入。同时也要重写HashCode方法,因为如果不重写,调用Object的HashCode方法,计算出的HashCode值是不一样的,相同对象得到的哈希值不同,放入哈希表中就是有问题的,所以要保证相同对象的哈希值相同。
public class Student {
   private int age;
   private String name;
​
    public Student() {
    }
​
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    @Override
    public boolean equals(Object obj) {
        if(this==obj) return true;
        if(obj==null||getClass()!=obj.getClass()) return false;
        Student student=(Student) obj;
        return age==student.age&&Objects.equals(name,student.name);
    }
​
    @Override
    public int hashCode() {
        return Objects.hash(name,age);
    }
​
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

TreeSet

//底层是红黑树,在进行存储的时候会进行值的比较,使用Comparable比较器(内部比较器)

public interface Comparable<T>{
    public int compareTo(T o);
}
​
//Integer的实现
public final class Integer extends Number implements Comparable<Integer> {
    //Integer类型本身实现了这个比较器接口
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
    public static int compare(int x, int y) {
        //比较大小
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
}
​
​
//String的实现
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
    public int compareTo(String anotherString) {
        //先获得长度
        int len1 = value.length;
        int len2 = anotherString.value.length;
        //取两个长度中最小值
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;
​
        int k = 0;
        //比两个长度相同的部分
        while (k < lim) {
            //一个字母一个字母比的
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                //不相等的话,直接返回差值
                return c1 - c2;
            }
            k++;
        }
        //要是长度相同的部分都相同,直接给长度作差
        return len1 - len2;
    }
}

所以如果TreeSet为引用类型,那么需要自己实现比较器

//比较规则可以自己任意定义
@Override
public int compareTo(Student o) {
    return this.age-o.age;
}

当不能修改某个类的源代码时,使用外部比较器

//自己定义一个外部比较器
public class Mycompare implements Comparator<Teacher> {
    //实现这个compare和直接写一个普通函数是有区别的
    //关键在于 定义的集合是否能知道定义的这个函数是比较器
    //涉及到有参构造,见下
    @Override
    public int compare(Teacher o1, Teacher o2) {
        return o1.getAge()-o2.getAge();
    }
}
​
public class Demo3 {
    public static void main(String[] args) {
        Teacher teacher=new Teacher("张三",12);
        Teacher teacher2=new Teacher("李四",14); 
        //调用这个外部比较器里的方法对对象进行比较
        Mycompare mycompare=new Mycompare();
        TreeSet<Teacher> set2=new TreeSet<>(mycompare);//将自己定义的外部比较器设置到set集合中
        int compare = mycompare.compare(teacher, teacher2);
​
    }
}
//TreeSet的有参构造之一,需要的就是Compartor类型的构造器
public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));//这里也可以看出,TreeSet的底层就是TreeMap
}
​

Map

常用方法

//Map是一个独立的接口,由key和value组成-->组成一个Entry
//并且key必须保证唯一
//HashMap:底层是哈希表
//TreeMap:底层是红黑树
//LinkedHashMap:哈希表+链表
public class TestMap {
    public static void main(String[] args) {
        Map<Integer,String> map=new HashMap<>();
        Map<Integer,String> map1=new TreeMap<>();
        Map<Integer,String> map2=new LinkedHashMap<>();
        //以下方法 上面三种结构是通用的
        //map集合添加元素
        map.put(1,"bj");
        String s = map.get(1);//返回value
        String remove= map.remove(1);//根据key进行元素移除,如果元素不存在返回是null,否则返回当前移除对象的value
        boolean ji = map.remove(1, "ji");//根据k 和v同时移除内容,返回值是布尔类型
        //清空集合内容,k和v都清空
        map.clear();
        map.replace(1,"pp");//替换
​
        //HashMap中如果key相同了,后者的就会把前者相同key的value覆盖了
        map.put(1,"bj");
        map.put(2,"bj2");
        map.put(1,"bj3");
        map.put(null,"bj5");//允许key为null的,空对象
        System.out.println(map);//这里输出的结果,key 1对应的value最终会被覆盖,也就是bj3
​
        //TreeMap
        map1.put(1,"bj1");
        map1.put(2,"bj2");
        map1.put(1,"bj3");//仍然是会覆盖的
        map1.put(null,"bj");//这里会报错,不能为空。Tree中不允许key出现空对象的情况
​
    }
}

Map集合的遍历

//遍历方式一
//获得map集合的所有key
Set<Integer> keySet = map.keySet();
for (Integer key : keySet) {
    System.out.println(map.get(key));
}
//遍历方式二:
//直接获得map集合的value,所有的当前value直接遍历
Collection<String> values = map.values();
for (String v : values) {
    System.out.println(v);
}
​
//遍历方式三:
//Map.Entry<Integer, String> entry也就是一组 例如1=bj、2=bj2
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
for (Map.Entry<Integer, String> entry : entrySet) {
    System.out.println(entry.getKey()+"---"+entry.getValue()+"---"+entry);
}

Hash表

HashCode源码

内容相等得到hashcode值一定相同,但反过来不一定,内容不同得到的结果也可能是一样的。

//int类型
Integer.hashCode(12);
public static int hashCode(int value) {
        return value;
    }
//byte类型
public static int hashCode(byte value) {
        return (int)value;
    }
//double类型
public static int hashCode(double value) {
        long bits = doubleToLongBits(value);
        return (int)(bits ^ (bits >>> 32));
    }
//String类型
 public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;//变为char数组
​
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];//相当于是拿的Ascii码
            }
            hash = h;
        }
        return h;
    }

处理哈希碰撞的方法:链地址法再散列法(重新计算值);建立一个公共溢出区(冲突的值再找一部分公共区域存起来);

装填因子=哈希表中个数/哈希表长度。装填因子越小,发生冲突的几率越小,装填因子达到最优的时候,Hash性能能达到最优。HashMap里面的装填因子是0.75

HashMap源码(1.7)

JDK1.7及其之前,HashMap底层是一个table数组+链表实现的哈希表存储结构

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable{
    //初始化的默认容量 16,也就是哈希表主数组的长度
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
    //最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //装填因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //主数组
    static final Entry<?,?>[] EMPTY_TABLE = {};
    
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    //长度
    transient int size;
}

链表的每个节点就是一个Entry,其中包括:键key、值value、键的哈希码hash、执行下一个节点的引用next四部分.

//HashMap中的内部类 
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;//指向下一个节点的指针
        int hash;//哈希码
  }

put()方法

调用put方法添加键值对。哈希表三步添加数据原理的具体实现;是计算key的哈希码,和value无关。特别注意:

  1. 第一步计算哈希码时,不仅调用了key的hashCode(),还进行了更复杂处理,目的是尽量保证不同的key尽量得到不同的哈希码

  2. 第二步根据哈希码计算存储位置时,使用了位运算提高效率。同时也要求主数组长度必须是2的幂)

  3. 第三步添加Entry时添加到链表的第一个位置,而不是链表末尾

  4. 第三步添加Entry是发现了相同的key已经存在,就使用新的value替代旧的value,并且返回旧的value

public class HashMap {
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            //如果自定义大小M,那么定义的table大小是离这个数最近的2次幂数,例如30-->32
            inflateTable(threshold);
        }
       //如果key是null,特殊处理
        if (key == null) return putForNullKey(value);
        //1.计算key的哈希码hash 
        int hash = hash(key);
        //2.将哈希码代入函数,计算出存储位置  y= x%16;
        int i = indexFor(hash, table.length);
        //如果已经存在链表,判断是否存在该key,需要用到equals()
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如找到了,使用新value覆盖旧的value,返回旧value
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
                V oldValue = e.value;// the United States
                e.value = value;//America
                e.recordAccess(this);
                return oldValue;
            }
        }
        //添加一个结点
        addEntry(hash, key, value, i);
        return null;
    }
private V putForNullKey(V value) {
    //当key为null的执行这个函数,
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                //如果旧值key为null,新值key也为null
                //将旧值替换成新值,并返回旧值
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        //如果对应下标处没有元素,直接增加一个节点,hash码为0(因为key为空)
        addEntry(0, null, value, 0);
        return null;
}
final int hash(Object k) {
    int h = 0;
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
    //按位相与,也就是计算索引的
//作用就相当于y = x%16,采用了位运算,效率更高
    return h & (length-1);
 }
}

addEntry()方法

bucketIndex就是当前要放入的下标

void addEntry(int hash, K key, V value, int bucketIndex) {
    //如果达到了门槛值,就扩容,容量为原来容量的2位 16---32
    //如果要放入的这个下标对应为null,也不会扩容,直接放不影响性能
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    //添加节点
    createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
    //链表头插,是插到前面,然后将新插入的值的next指向一开始位于链表头的e
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
}
Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;//将next指向n
            key = k;
            hash = h;
}
void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
​
        Entry[] newTable = new Entry[newCapacity];
        //将原来的值往新的数组里面放
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                //重新计算下标
                int i = indexFor(e.hash, newCapacity);
                //仍然是头插 e表示的是当前要移的那个
                //这样的话,如果原本在链表相邻两个数移动之后还是在同一个位置,那么顺序会变化
                e.next = newTable[i];
                newTable[i] = e;
                //后移e
                e = next;
            }
        }
    }

数组的长度必须是2的几次幂?为了提高存储key-value的数组下标位置的随机性和分布均匀性,尽量避免出现hash值冲突

jdk7中头插法在多线程情况下可能会产生问题,也就是在多线程情况下进行数组的扩容可能产生数据的闭环。数据迁移的时候指针的指向可能会出现闭环。jdk8改成了尾插,但是在多线程情况下仍然会发生数据的覆盖/丢失。

get()方法

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        //如果entry== null,返回null,否则返回value
        return null == entry ? null : entry.getValue();
    }
private V getForNullKey() {
        if (size == 0) {
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            //因为key为null的值只会有一个,直接返回
            if (e.key == null)
                return e.value;
        }
        return null;
    }
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }
​
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

HashMap源码(1.8)

在JDK1.8中有一些变化,当链表的存储数据个数大于等于8且数组的长度大于等于64的时候,不再采用链表存储,而采用红黑树存储结构。这么做主要是查询的时间复杂度上,链表为O(n),而红黑树一直是O(logn)。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
  //序列化和反序列化时使用相同的id
  private static final long serialVersionUID = 362498820763181265L;
  //初始化容量
  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  //最大容量
  static final int MAXIMUM_CAPACITY = 1 << 30;
  //默认负载因子
  static final float DEFAULT_LOAD_FACTOR = 0.75f;
  //树形阈值,当链表长度大于等于8 就变成红黑树
  static final int TREEIFY_THRESHOLD = 8;
  //取消阈值,当链表长度小于6的时候就转回成链表
  static final int UNTREEIFY_THRESHOLD = 6;
  //最小树形容量,如果链表长度大于等于8了但总节点数没有超过64也不会转红黑树,而是扩容
  static final int MIN_TREEIFY_CAPACITY = 64;
  //节点
  transient Node<K,V>[] table;
  //存储键值对的个数
  transient int size;
  //散列表被修改的次数
  transient int modCount; 
  //扩容临界值
  int threshold;
  //负载因子
  final float loadFactor;
}

Node节点

1.7 是 Entry 结点,1.8 则是 Node 结点,都实现了 Map.Entry (Map 接口中的 Entry 接口)接口,即,实现了 getKey() , getValue() , equals(Object o )和 hashCode() 等方法;

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;//这里这个hash是key调用hashCode方法后的值用hash方法计算出的值
    final K key;
    V value;
    Node<K,V> next;
}

put()方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
//hash方法
static final int hash(Object key) {
  int h;
  //hashCode和h移位右移16位进行按位异或运算
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
​
//putVal()方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
    //申明tab 和 p 用于操作原数组和结点
    //用于记录旧节点信息的
    Node<K,V>[] tab; Node<K,V> p;
    int n, i;
    //如果原数组是空或者原数组的长度等于0,那么通过resize()方法进行创建初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        //获取到创建后数组的长度n
        n = (tab = resize()).length;
​
    //通过key的hash值和 数组长度-1 计算出存储元素结点的数组中位置(和1.7一样)
    //并且,如果该位置为空时,则直接创建元素结点赋值给该位置,后继元素结点为null
    //如果该位置不为空,那么此时p也指向的对应位置上的元素,进入到else
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        //否则,说明该位置存在元素
        Node<K,V> e; K k;
        //判断table[i]的元素的key是否与添加的key相同,若相同则直接用新value覆盖旧value(这个逻辑在下面的if执行)
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
            //判断是否是红黑树的结点,如果是,那么就直接在树中添加或者更新键值对
        else if (p instanceof TreeNode)
            //如果树中没有这个元素,返回的是空值
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //否则,就是链表,则在链表中添加或替换
        else {
            //链表转红黑树的过程
            //遍历table[i],并判断添加的key是否已经存在,和之前判断一样,hash和equals
            //遍历完毕后仍无发现上述情况,则直接在链表尾部插入数据
            //binCount也就是链表长度
            for (int binCount = 0; ; ++binCount) {
                //如果遍历的下一个结点为空,那么直接插入
                //该方法是尾插法(与1.7不同)
                //将p的next赋值给e进行以下判断,也就是p的下一个节点如果为空,那么就在后面新建节点
                if ((e = p.next) == null) {
                    //直接创建新结点连接在上一个结点的后继上
                    p.next = newNode(hash, key, value, null);
                //如果插入结点后,链表的结点数大于等7(8-1,即大于8)时,则进行红黑树的转换
                //注意:不仅仅是链表大于8,并且会在treeifyBin方法中判断数组是否为空或数组长度是否小于64
                //如果小于64则进行扩容,并且不是直接转换为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    //完成后直接退出循环
                    break;
                }
                //不退出循环时,则判断两个元素的key是否相同
                //若相同,则直接退出循环,进行下面替换的操作
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                //否则,让p指向下一个元素结点
                p = e;
            }
        }
        //接着上面的第二个break,如果e不为空,直接用新value覆盖旧value并且返回旧value
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    //添加成功后,判断实际存在的键值对数量size是否大于扩容阈值threshold(第一次时为12)
    if (++size > threshold)
        //若大于,扩容
        resize();
    //添加成功时会调用的方法(默认实现为空)
    afterNodeInsertion(evict);
    return null;
}
​
//resize()方法
 //该函数有两种使用情况:初始化哈希表或前数组容量过小,需要扩容
        final Node<K,V>[] resize() {
            //获取原数组
            Node<K,V>[] oldTab = table;
            //获取到原数组的容量oldCap
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            //获取原扩容阈值
            int oldThr = threshold;
            //新的容量和阈值目前都为0
            int newCap, newThr = 0;
            if (oldCap > 0) {
                //如果原数组容量大于等于最大容量,那么不再扩容
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                //而没有超过最大容量,那么扩容为原来的2倍
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                        oldCap >= DEFAULT_INITIAL_CAPACITY)
                    //扩容为原2倍
                    newThr = oldThr << 1; // double threshold
            }
            //经过上面的if,那么这步为初始化容量(使用有参构造器的初始化)
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            else {               // zero initial threshold signifies using defaults
                //否则,使用的无参构造器
                //那么,容量为16,阈值为12(0.75*16)
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            //计算新的resize的上限
            if (newThr == 0) {
     float ft = (float)newCap * loadFactor;
 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            @SuppressWarnings({"rawtypes","unchecked"})
            //使用新的容量床架一个新的数组
             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            //将新的数组引用赋值给table
            table = newTab;
            //如果原数组不为空,那么就进行元素的移动
            if (oldTab != null) {
                //遍历原数组中的每个位置的元素
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        //如果该位置元素不为空,那么上一步获取元素接着置为空
                        oldTab[j] = null;
                        //判断该元素上是否有链表
                        if (e.next == null)
        //如果无链表,确定元素存放位置,
//扩容前的元素位置为 (oldCap - 1) & e.hash ,所以这里的新的位置只有两种可能:1.位置不变,
//2.变为 原来的位置+oldCap,下面会详细介绍
                            newTab[e.hash & (newCap - 1)] = e;
                            //判断是否是树结点,如果是则执行树的操作
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            //否则,说明该元素上存在链表,那么进行元素的移动
                            //根据变化的最高位的不同,也就是0或者1,将链表拆分开
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                //最高位为0时,则将节点加入 loTail.next
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                //最高位为1,则将节点加入 hiTail.next
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
//通过loHead和hiHead来保存链表的头结点,然后将两个头结点放到newTab[j]与newTab[j+oldCap]上面去
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }

第一次初始化调用resize():第一次创建这个HashMap时,根据默认大小或者指定大小来初始化创建数组(严格来讲不算是扩容,是初始化)

两次扩容:

  • 当链表长度大于8且数组长度小于64的时候,选择扩容

  • 实际存在的键值对数量如果大于了阈值threshold,也会扩容

get()方法

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                //如果第一个节点满足,那么直接返回
                return first;
            if ((e = first.next) != null) {
                //判断是否是红黑树里的结构
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    //在链表中查找
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

问题

为什么链表长度为8的时候选择转换?

* Because TreeNodes are about twice the size of regular nodes, we
     * use them only when bins contain enough nodes to warrant use
     * (see TREEIFY_THRESHOLD). And when they become too small (due to
     * removal or resizing) they are converted back to plain bins.  In
     * usages with well-distributed user hashCodes, tree bins are
     * rarely used.  Ideally, under random hashCodes, the frequency of
     * nodes in bins follows a Poisson distribution
     * (http://en.wikipedia.org/wiki/Poisson_distribution) with a
     * parameter of about 0.5 on average for the default resizing
     * threshold of 0.75, although with a large variance because of
     * resizing granularity. Ignoring variance, the expected
     * occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
     * factorial(k)). The first values are:
     *
     * 0:    0.60653066
     * 1:    0.30326533
     * 2:    0.07581633
     * 3:    0.01263606
     * 4:    0.00157952
     * 5:    0.00015795
     * 6:    0.00001316
     * 7:    0.00000094
     * 8:    0.00000006
     * more: less than 1 in ten million
可以看到,当链表长度达到8的时候,效率就很低了,在这种比较极端的情况下,才会把链表转变为红黑树,因为红黑树底层旋转等操作也要耗费一定性能,所以要综合考虑

TreeMap底层

基本属性

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
  //比较器,是自然排序,还是定制排序 ,使用final修饰,表明一旦赋值便不允许改变
  private final Comparator<? super K> comparator;
  //红黑树的根节点
  private transient Entry<K,V> root;
  //TreeMap中存放的键值对的数量
  private transient int size = 0;
  //修改的次数
  private transient int modCount = 0;
  //构造方法,comparator比较器
  public TreeMap() {
     comparator = null;
  }
  //构造方法,提供比较器,用指定比较器排序
  public TreeMap(Comparator<? super K> comparator) {
     this.comparator = comparator;
  }
}

节点

static final class Entry<K,V> implements Map.Entry<K,V> {
  K key; //键
  V value; //值
  Entry<K, V> left = null; //左孩子节点
  Entry<K, V> right = null;//右孩子节点
  Entry<K, V> parent; //父节点
  boolean color = BLACK; //节点的颜色,在红黑树中,只有两种颜色,红色和黑色
  //省略 有参构造 无参构造 equals()和hashCode() getter和setter
}

put()方法

public V put(K key, V value) {
  //红黑树的根节点
  Entry<K,V> t = root; 
  //红黑树是否为空
  if (t == null) {
    //检查比较器
    compare(key, key); // type (and possibly null) check
        //创建根节点,因为根节点没有父节点,传入null值。 
    root = new Entry<>(key, value, null);
    //size值=1
    size = 1;
    //改变修改的次数
    modCount++;
    //返回null 
    return null;
  }
  int cmp;
  //声明节点
  Entry<K,V> parent;
  // split comparator and comparable paths
  //获取比较器
  Comparator<? super K> cpr = comparator;
  //如果定义了比较器,采用自定义比较器进行比较
  if (cpr != null) {
    do {
      //将红黑树根节点赋值给parent
      parent = t;
      //添加的key与根节点的值比较大小
      cmp = cpr.compare(key, t.key);
      //如果key < t.key , 指向左子树
      if (cmp < 0)
        t = t.left;
      //如果key > t.key , 指向右子树
      else if (cmp > 0)
        t = t.right;
      //如果它们相等
      else
        //新值替换旧值
        return t.setValue(value);
    } while (t != null);
  }
  //自然排序方式,没有指定比较器
  else {
    //key不能为null
    if (key == null)
      throw new NullPointerException();
    @SuppressWarnings("unchecked")
    //类型转换
    Comparable<? super K> k = (Comparable<? super K>) key;
    do {
      parent = t;
      //添加的key与根节点的值比较大小
      cmp = k.compareTo(t.key);
      // key < t.key
      if (cmp < 0)
        t = t.left;//左孩子
      // key > t.key 
      else if (cmp > 0)
        t = t.right;//右孩子
      else//如果它们相等
        //新值替换旧值
        return t.setValue(value);
    } while (t != null);
  }
  //创建新节点,并指定父节点
  Entry<K,V> e = new Entry<>(key, value, parent);
  //根据比较结果,决定新节点作为父节点的左孩子或右孩子
  if (cmp < 0)
    parent.left = e;
  else
    parent.right = e;
  //新插入节点后重新调整红黑树 
  fixAfterInsertion(e);
  size++;
  modCount++;
  return null;
}

get()方法

public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }
 final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

集合工具类Collections

public class TestCollections {
    public static void main(String[] args) {
        List<Integer> list=new ArrayList<>();
        //快速在集合中添加元素
        Collections.addAll(list,1,200,3,400,100);
        //排序,默认是使用内部比较器,也可以使用外部的
        Collections.sort(list);
        //二分查找,返回是下标(前提是数组是有序的),如果不存在,返回的是一个负数
        int i = Collections.binarySearch(list, 400);
        //返回最大值最小值,这个不需要排序
        Integer min = Collections.min(list);
        Integer max = Collections.max(list);
        //用于清空元素内容但是不改变长度
        Collections.fill(list,null);
        //复制集合
        List<Integer> list2=new ArrayList<>();
        //可以比原数组短,不能比它长,相当于从前往后覆盖了部分
        Collections.addAll(list2,6,7,9,9,10);
        Collections.copy(list,list2);
        //同步集合:线程安全问题
        Collections.synchronizedList(list);//把集合存放以后,后续的操作这个list集合就安全了
        
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值