ArrayList
源码分析
前言:刚开始学习java基础时,我们一般采用数组来存放数据,随着数组的使用,渐渐发现,数组只能存放一种数据类型并且数组长度是定长的。实际开发过程中,我们可能想让对象,基本数据类型都存放为一个容器中,这时候,集合就出现了
ArrayList:它是实现List接口的子实现类,集合好比存放数据的容器,既然存放数据,简单的它会具有增,删,改,查,遍历等操作,此处我以jdk8分析底层源码走进ArrayList
@Test
public void test() { // 简单添加测试
List list = new ArrayList();
list.add(1);
list.add("hello");
list.add(new User("lisa", 20));
System.out.println(list);
}
–>开始走进源码
- List list = new ArrayList(); // 创建集合对象
- add()源码
- 扩容机制
综合分析:ArrayList底层采用的是动态数组,具有扩容机制(注:jdk7类似于单例模式的饿汉式,即一上来实例化就开始分配数组大小,为其分配空间,每次扩容为1.5倍(add()->grow()),即采用的是原数据+原数据右移一位(即位移运算),即缩小0.5(1+0.5=1.5);jdk8类似于单例模式的懒汉式,需要使用的时候才去分配数组大小,相对于jdk7节省内存资源),由于采用动态数组,它的优点为查询快(通过索引),删除和添加效率不高。(注:使用频率高)时间复杂度:增加和删除:O(n) 查找:O(1)
简单模拟
package com.dy.ArrayList;
public class MyArrayList {
// 解决数组定长和类型的问题
private Object[] elementData; // Object类型的数组
private int size; // 定义长度
// 创建对象初始化数组长度
public MyArrayList() {
elementData = new Object[10];
}
public void add(Object obj) {
// 数组扩容机制
// 数组超出长度需要为其扩容 即重新拷贝一个数组 使其容量加大 将原数据拷贝其新数组
if (size >= elementData.length) {
// 创建新数组 增加其容量
Object[] temp = new Object[elementData.length * 2];
// 原数组的值复制到新数组
System.arraycopy(elementData, 0, temp, 0, elementData.length);
elementData = temp;
}
elementData[size++] = obj;
}
public int size() {
return size;
}
}
// 测试类
public class MyArrayListTest {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(1);
myArrayList.add(2);
myArrayList.add(3);
myArrayList.add(4);
myArrayList.add(5);
myArrayList.add(6);
myArrayList.add(7);
myArrayList.add(8);
myArrayList.add(9);
myArrayList.add(10);
myArrayList.add(11);
System.out.println(myArrayList.size());
}
}
线程安全分析
观察底层代码发现不同于Vector(底层加入了synchronized线程安全,效率低),ArrayList类属于线程不安全的,效率较高,我们可以通过测试,观察下列测试代码
public class UnsafeList_1 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
// 线程不安全
// 解决:可以加锁,但是性能相应的降低了
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
/*
// 加同步锁解决线程不安全
for (int i = 0; i < 10000; i++) {
new Thread(()->{
// 加同步锁
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
*/
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
// 观察测试结果list.size()<10000
LinkedList
源码分析
- add(E e) // 添加数据
- Node:成员变量有prev(指向前一个节点的指针),element(数据域),next(指向后一个节点的指针),可以明白LinkedList底层是双向链表结构
综合分析:LinkedList底层采用的是数据结构中的双向链表(从prev和next可以看出),由于采用的是链表的数据结构,即它的优点删除和添加效率高(只需要移动指针),但是查询速度慢(类似于金链子)
时间复杂度:增加和删除:O(1) 查找:O(n)
简单模拟
public class Node {
public Object obj; // 保存当前节点的数据(数据域)
public Node prev; // 前驱节点
public Node next; // 后继节点
public Node() {
super();
}
public Node(Object obj, Node prev, Node next) {
super();
this.obj = obj;
this.prev = prev;
this.next = next;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
package com.dy.LinkedList;
public class MyLinkedList {
// 底层其实内部类的写法
private Node first;
private Node last;
private int size;
// 链表添加节点
public void add(Object obj) {
Node node = new Node();
if (first == null) {
// 第一个节点不存在
node.prev = null;
node.next = null;
node.setObj(obj);
first = node;
last = node;
} else {
// 第一个结点存在,为其添加后继节点
node.prev = last;
node.next = null;
node.setObj(obj);
last.next = node; // 类似于指针连接前后结点
last = node; // 此时的last节点为新添加的节点
}
size++;
}
public int size() {
return size;
}
}
// 测试类
public class MyLinkedListTest {
public static void main(String[] args) {
MyLinkedList list = new MyLinkedList();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
System.out.println(list.size());
}
}
线程安全分析
LinkedList同ArrayList一样是线程不安全的,代码测试和ArrayList大同小异,此时我就不演示了,怎样保证高并发情况下使用这两种集合安全也是后话了,这里不做讨论
TODO:思考
通过比较ArrayList和LinkedList的优缺点,我们可以发现ArrayList由于采用的数组,优点为查询快(通过数组索引),删除和添加效率不高
而LinkedList,它的删除和添加效率高(只需要移动指针),但是查询速度慢------->此时我们不妨想想是否有一种结构能综合两者的优点勒?数组+链表是否就可以勒?后续的HashMap就采用数组+链表(jdk7)+(jdk8+红黑树)…