***Java集合、多线程、反射和Spring框架总结,源码解析精华版***

Java集合、多线程、反射和Spring框架总结,源码解析

一、集合 - 通过不同的数据结构存储以及操作数据的工具

1.1 Collection

1.1.1 ArrayList、Vector

1.1.1.1 底层原理

ArrayList和Vector底层都是由动态数组实现的

1.1.1.2 ArrayList VS Vector

ArrayList是线程不安全的集合,而Vector是线程安全的集合。 Vector本质是JDK1.0的产物,但是集合体系是JDK1.2才推出的新特性。因此,JDK1.2之后sun公司强行的让Vector类实现了List接口,从而导致Vector之中有很多功能重复的方法。虽然现在为止Vector都没有过时,但是基本上已经不再使用Vector集合,哪怕是多线程环境,也不推荐直接使用Vector来保证线程安全。

1.1.1.3 什么是动态数组?

本质上数组是不能动态的,因为Java中数组一旦初始化好之后,就不能再改变数组长度。但是ArrayList和Vector底层是通过创建新的数组的方式,来达到数组动态扩展的目的。这种动态"改变"数组长度的方式,称之为动态数组

img

1.1.1.4 源码解析

ArrayList - 构造方法

构造方法中就是将一个长度为空的数组,赋值给一个Object数组的引用(elementData)。 JDK1.8之后,ArrayList初始化时,不再默认的初始化一个长度为10的数组(懒加载)。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
....
​
/**
 * 构造方法 - 初始化底层的数组
  */
public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
12345678910

add - 添加元素方法

public boolean add(E e) {
   //判断当前底层的数组是否需要扩容,如果需要扩容则调用grow方法,进行扩容
   ensureCapacityInternal(size + 1); 
   //将元素设置到数组size的位置,并且size自增
   elementData[size++] = e;
   return true;
}
​
//核心扩容方法,参数minCapacity表示当前最小需要扩容的容量
private void grow(int minCapacity) {
   //获得旧的数组容量
   int oldCapacity = elementData.length;
   //计算新的数组容量,根据旧的容量1.5倍扩容
   //右移一位相当于除以2,左移一位相当于乘以2
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   //如果新容量没有达到最小容量的要求,则直接用最小容量顶替新容量
   if (newCapacity - minCapacity < 0)
       newCapacity = minCapacity;
   if (newCapacity - MAX_ARRAY_SIZE > 0)
       newCapacity = hugeCapacity(minCapacity);
   //数组扩容
   //Arrays.copyOf表示将根据参数二(newCapacity)的大小,创建一个新的空数组,并且将参数一(elementData)中的元素,全部拷贝过去,并且返回新数组
   elementData = Arrays.copyOf(elementData, newCapacity);
}
123456789101112131415161718192021222324

add - 插入(往中间添加)元素的方法

//插入元素element到下标为index的位置
public void add(int index, E element) {
  //检测index下标是否越界  
  rangeCheckForAdd(index);
​
  //判断是否需要扩容  
  ensureCapacityInternal(size + 1);
  //做一个index位置的元素整体后移,空出index位置来  
  System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
  //将新的元素放到index位置处  
  elementData[index] = element;
  //元素数量加1  
  size++;
}
123456789101112131415

注意:在ArrayList中,记性数组扩容或者元素移位时,底层都是调用的native方法实现,native本身没有方法体,方法实现是由C/C++实现的,以此来提高扩容的效率。

1.1.2 LinkedList

1.1.2.1 底层原理

Linked底层是由双向链表实现

1.1.2.2 什么是链表?什么是双向链表?

链表是一种非常常见的线性数据结构(和数组类似),由一个一个的节点组成,每个节点都有一个指针,指向链表的下一个节点,因为有"指针"的存在,所以链表在内存空间上可以地址不连续,因此链表没有长度的限制(数组在内存空间上地址必须连续,长度有限)。

双向链表就是普通链表的节点多了一个指向上一个节点的指针

img

1.1.2.3 Java如何实现一个双向链表?

链表的关键其实就是节点,链表由一个一个节点组成,指向实现了节点的结构,那么链表就能实现。

链表节点的实现:

//LinkedList中底层节点的实现
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;
        }
}
​
12345678910111213141516

1.1.2.4 源码解析

LinkedList的一些基本属性

//first在LinkedList中永远指向链表的头节点,如果没有元素时,就为null
transient Node<E> first;
​
//last在LinkedList中永远指向链表的尾节点,如果没有元素时,就为null
transient Node<E> last;
12345

add添加元素的方法

//添加元素
public boolean add(E e) {
    //调用该方法添加元素到链表的尾部
    linkLast(e);
    return true;
}
​
//添加元素e到链表的尾部
void linkLast(E e) {
    //让l指向尾节点,第一次添加时,因为没有任何节点,所以l和last都是null
    final Node<E> l = last;
    //将新元素封装成新节点,并且新节点的头指针指向l
    final Node<E> newNode = new Node<>(l, e, null);
    //再将尾指针指向新节点
    last = newNode;
    //判断l是否为null,如果为null,表示新节点就是第一个节点
    if (l == null)
      //first再指向新节点   
      first = newNode;
    else
      //l节点的尾指针指向新节点  
      l.next = newNode;
    size++;
    modCount++;
}
12345678910111213141516171819202122232425

get获取元素的方法

//获取下标index除外的元素
public E get(int index) {
    //检查下标越界
    checkElementIndex(index);
    //找到index位置的节点,获得节点的数据部分
    return node(index).item;
}
​
//node方法,返回index处的节点对象
Node<E> node(int 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;
        }
}
12345678910111213141516171819202122232425

add插入(从中间添加)元素的方法

//插入元素到index下标的位置
public void add(int index, E element) {
        //检查下标是否越界
        checkPositionIndex(index);
​
        if (index == size)
            //说明当前的插入位置为末尾,直接调用追加元素的方法
            linkLast(element);
        else
            //插入元素到指定节点之前
            linkBefore(element, node(index));
}
​
....
//插入数据e到succ节点的前面    
void linkBefore(E e, Node<E> succ) {
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
}    
1234567891011121314151617181920212223242526

1.1.4 ArrayList VS LinkedList

插入性能对比: 尾部:ArrayList和LinkedList性能相近 中间:ArrayList的速度 >> LinkedList的速度,ArrayList虽然存在移位,但是底层通过C++直接操作内存的方式进行了优化,而LinkedList每次插入都需要通过遍历的方式找到元素,所以性能有所下降。 头部:LinkedList的速度 >> ArrayList的速度,ArrayList往头部插入元素,也就意味着每次都需要位移整个数组,虽然有优化,但是也不等于没有耗时,但是LinkedList查找靠前/后的元素速度很快,同时插入性能也很快,所以相对ArrayList性能更优

查询性能对比: 尾部:ArrayList和LinkedList性能相近 中间:ArrayList >>>>>>>> LinkedList 头部:ArrayList和LinkedList性能相近

1.1.3 HashSet、LinkedHashSet、TreeSet

Set各个实现类的特点: HashSet:无序、不可重复 LinkedHashSet:不可重复,但是元素有序(插入顺序) TreeSet:不可重复,但是元素有序(字典顺序)

底层实现:

底层都是通过对应的Map集合实现

1.2 Map

1.2.1 HashMap、Hashtable

1.2.1.1 底层原理

HashMap和Hashtable底层都是由哈希表实现的,Hashtable和HashMap的关系与Vector和ArrayList之间的关系类似,Hashtable线程安全HashMap线程不安全

1.2.1.2 哈希表(重要)

什么是哈希表?

哈希表是一种用于快速搜索数据结构,精准查询(通过key查询value)效率极高,和元素的数量(理想型,实际过程中,多少还是有点关系)无关,所以时间复杂度为O(1)

img

什么是哈希碰撞(哈希冲突)?

哈希碰撞不是一件好事,但是不可避免,所以任何一张好的哈希表必须对哈希碰撞有良好的解决方案。

所谓的哈希碰撞就是指,不同的key,通过哈希函数,计算出的下标相同了。

哈希碰撞的解决方式(HashMap的解决方案)

链地址法:*将发生碰撞的元素通过链表连接起来 (**JDK1.8**之后,是通过*链表 + 红黑树的方式解决的哈希碰撞)

哈希表的扩容(底层数组的扩容)

一个哈希表,哈希碰撞是不可避免的,如果频繁的发生哈希碰撞,那么会导致大量的链表生成,对查询性能影响很严重。因此哈希表必须通过适当的扩容,来降低哈希碰撞发生的概率,以及优化查询性能。

扩容的好处: 1、降低后续发生哈希碰撞的概率 2、打散现有的碰撞的链表

什么时候扩容?

哈希表有一个参数,称之为填充因子(加载因子,HashMap默认为0.75),当添加的元素数量/数组长度时,一旦达到了填充因子的比例,就会触发一次扩容。 数组长度(100) * 填充因子(0.75) = 扩容阈值(75)

如果碰到一些精准定位、去重、判断是否存在等诸如此类的问题,都可以先考虑一下哈希表或者哈希表的一些变种结构(布隆过滤器)

1.2.1.3 红黑树(简单介绍)

什么是红黑树?

红黑树本身是一种特殊的二叉树,是用于快速查询数据结构

什么是二叉搜索树?

在一颗二叉树中,任意节点的左子树节点都比当前节点要小,右子树节点都比当前节点要大,那么这颗二叉树就称之为二叉搜索树

二叉搜索树的缺点:如果插入的元素有一定的顺序,那么就可以导致树的失衡,从而严重降低树的查询能力

红黑树就是为了解决二叉搜索树,失衡问题而设计的一颗二叉搜索平衡树

img

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值