一. ArrayList相关
构造方法源码
List list = new ArrayList<>();
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储具体数据的数组
transient Object[] elementData;
private static final Object[] EMPTY_ELEMENTDATA = {};
//无参构造器
public ArrayList() {
//无参构造器默认是一个空的数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//有参构造器
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果是构造参数设置为0,与使用无参构造器是一样的初始化一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 小于0抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
1.1 面试有可能会问的问题
ArrayList底层是用的什么结构存储数据,无参构造器初始化大小是多少?有参构造器呢?
- 1.底层是用的一个Object类型的数组。
- 2.当使用无参构造器底层的数组是一个空的,只用调用add()方法才会分配大小。
- 3.有参构造器是根据设置的大小给Object类型的数组分配一个固定的内存空间。
2.add()方法源码
list.add("测试添加数据");
//实际存储数据的Object类型数组
transient Object[] elementData;
//modCount是AbstractList类的属性
protected transient int modCount = 0;
private int size;
//调用的添加接口方法
boolean add(E e);
//接口方法的实现
public boolean add(E e) {
//每次调用加一
modCount++;
//e表示的需要添加的数据,elementData存储数据的数组,size实际存储的数据个数
add(e, elementData, size);
//只要是不抛出异常固定返回true
return true;
}
//实际的添加数据方法 e表示的需要添加的数据,elementData存储数据的数组,s实际存储的数据个数
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
// 如果是实际存储的数个数 == 数组最大长度 ,那么需要扩容
elementData = grow();
elementData[s] = e;
size = s + 1;
}
//扩容方法1
private Object[] grow() {
return grow(size + 1);
}
//扩容方法2
private Object[] grow(int minCapacity) {
// newCapacity(int i);
return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
}
//数组扩容具体方法
private int newCapacity(int minCapacity) {
// overflow-conscious code
//获取就数组的最大长度
int oldCapacity = elementData.length;
//新的数组长度 = 旧数组长度 + (旧数组长度 / 2)
//结论:新数组长度是就数组的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//新数组长度 - (实际存储的元素个数+1);实际存储的元素个数是指定在上一次添加成功后记录的数量,+1是本次需要的长度
if (newCapacity - minCapacity <= 0) {
//如果结果小于等于0
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//如果存储元素的数组是空的
// private static final int DEFAULT_CAPACITY = 10;
// 返回 10或者minCapacity中大的一个
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
// 这个为什么会小于0?如果int类型最大值就会出现负数
throw new OutOfMemoryError();
// 如果 newCapacity 的结果是负数,且minCapacity大于0。
// 为什么会有负数,如果int类型最大值就会出现负数
return minCapacity;
}
// int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity: hugeCapacity(minCapacity);
}
// 返回最大容量的逻辑
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
2.1 面试可能会遇到的问题
ArrayList的扩容机制以及什么时候触发
扩容机制:扩容的大小是原容量的1.5倍,公式:原数组长度+(原数组长度 / 2)= 新数组长度
触发时机:
- 使用无参构造器初始化化ArrayList,调用add()方法;
- 当Object存储的数据量等于数组的容量;
ArrayList的最大容量是多少?
- 理论的最大容量是int类型最大值减8
ArrayList是线程安全的吗?
- 非线程安全,整个源码完全没有出现锁相关代码。
为什么查询快,新增/删除慢?
- 底层是数组结构,且分配的是一块连续的内存空间。
- 因为存在索引,所以查询效率高。
- 新增删除慢是相对的,如果数据量大,那么向中间新增或者删除数据会出现数据移动的问题,所以效率低。
默认的数组大小是多少
- 如果初始化的ArrayList没有指定大小,那么在调用add()方法的时候设置默认大小是10。(懒加载)
如果需要在多线程的情况使用ArrayList,怎么办?
首先有vector,它的基本实现和ArryList基本一致,但是许多方法用了sync修饰。所以存在效率问题(基本不用)。
其次,List list = Collections.synchronizedList(list);将list转换为线程安全的在使用
其他Collections常用方法:
- Collections.sort(List list);对集合排序,从小到大。
- Collections.reverse(List list);集合内容反转,1234变为4321
- Collections.max(list);获取集合的最大值
二. LinkedList相关
1.构造方法源码
List<Integer> objects = new LinkedList<>();
// 无参构造器
public LinkedList() {}
// 有参构造器
public LinkedList(Collection<? extends E> c) {
//调用无参构造器
this();
//调用addAll()方法
addAll(c);
}
1.1 面试可能会问的问题
基本上没啥问的,如果聊到这就说一下有参构造器传入参数是集合类型,底层就是调用了addAll()方法。
2.add()方法源码
objects.add(1);
transient Node<E> last;
transient Node<E> first;
transient int size = 0;
//modCount属性在AbstractList类中
protected transient int modCount = 0;
//e是新添加的数据对象
boolean add(E e);
//添加方法
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);
// 新节点赋值给全局变量的末尾节点
// 注意:前面以已经把上次的末尾节点保存到局部变量中l
last = newNode;
if (l == null)
//如果上一次的末尾节点是null ,那么当前创建的节点不光是末尾节点,同时也是头节点
first = newNode;
else
// 如果上一次末尾节点不null,那就把上一次默认节点对象中的next属性指向新创建的节点对象
l.next = newNode;
size++;
modCount++;
}
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;
}
}
2.面试中可能出现的问题:
LinkedList中add()方法的执行流程?
- 首先关键对象是Node,关键属性是Node last和Node first.
- 如果是首次添加first和last都是null,所以第一次添加会把传入对象封装为一个Node对象,然后first和last都指向它。
- 如果是第二次以上添加,先找到原来链表的末尾节点,也就是last赋值给局部变量,然后把当前对象也封装唯一个Node对象,然后把新Node对象赋值全局last对象(这就形成了头节点不变,但尾节点是最新添加的数据)。接着把原last的局部对象中的next属性指向新创建对象(这样就把原末尾节点和当前新节点关联上)。
LinkedList和ArrayList的区别?
- 底层组成结构不同,LinkedList底层是Node对象组成双向链表,ArrayList底层是Object对象数组。
- 理论的最大容量不同:ArrayList的最大容量是int类型的最大值减8,LinkedList理论上是没有限制大小(但是具体使用收到内存和相同资源限制)。
- ArrayList底层是数组,有索引,查询和遍历效率高。删除和新增效率低(尾部删除和新增不存在),因为涉及到数据移动的问题。
- LinkedList新增删除效率高,因为新增和删除只要修改Node的结构指向,所以效率高。
3.addAll()方法源码
objects.addAll(list);
transient int size = 0;
// 参数类型必须是Collection的实现
boolean addAll(Collection<? extends E> c);
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
// index表示指定插入位置 c表示需要添加的集合
public boolean addAll(int index, Collection<? extends E> c) {
// 校验index,有异常会抛出数组下标越界
checkPositionIndex(index);
// 传入参数集合对象转为对象数组
Object[] a = c.toArray();
// 获取数组最大长度
int numNew = a.length;
if (numNew == 0)
//如果传入空数组直接返回false
return false;
//pred表示为当前需要传入数据的上一个节点(原来的末尾节点)
//succ表示下一个节点
Node<E> pred, succ;
if (index == size) {
//如果数据插入的位置 = 实际的数据量,表示是插在末尾
succ = null;
pred = last;
} else {
// 这个是在中间插入
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
// 循环操作,与add()方法基本一致
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}