LinkedList源码分析和效率分析
1. 存储结构
LinkedList底层使用双向链表作为数据存储结构
链表的概念:是一种线性存储结构,需要存储的数据放在一个存储单元里边,而这个存储单元中除了数据,还存放有其前一个存储单元以及后一个存储单元的地址。每当需要查询数据时,通过某一个存储单元中的下一个存储单元地址寻找其后边的存储单元,完成遍历。删除指定元素时,需要将指定元素存储单元中的数据删除以外,同时清楚前一个后一个存储单元的地址,同时将前一个后一个存储单元的地址进行双向绑定。如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rEV0ZeQj-1613641433747)(C:\Users\baby\AppData\Roaming\Typora\typora-user-images\1613051661458.png)]
2. 成员属性
//存储数据的个数
transient int size = 0;
//链表第一个元素
transient Node<E> first;
//链表最后一个元素
transient Node<E> last;
/**
* 私有内部类 LinkedList存储数据单元
*
* @param <E> 泛型 通过LinedList进行约束
*/
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.prev = prev;
this.next = next;
}
}
3. 构造方法
//无参构造
public LinkedList() {
}
//有参构造 Collection集合转化为LinkedList集合
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
4. API实现
4.1添加接口
向集合中添加元素时,需要将需要插入位置的链断开后,将插入元素的单元分别与前一个单元后一个单元进行双向绑定,如图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RsvMvk1v-1613641433752)(C:\Users\baby\AppData\Roaming\Typora\typora-user-images\1613052078742.png)]
/**
* 在链表末尾添加元素
*
* @param e 要添加的元素
* @return 返回插入状态
*/
public boolean add(E e) {
return addLast(e);
}
/**
* 指定下标位置插入元素
* @param index int 指定下标位置
* @param e 要插入的元素
* @return
*/
public boolean add(int index, E e) {
checkIndexAdd(index);
if (index ==size){
addLast(e);
return true;
}else if (index == 0){
addFirst(e);
}else {
unlinkAdd(e,node(index));
}
return true;
}
/**
* 向除头尾插入元素的实际方法
* @param e 要插入的元素
* @param node 要更新链的元素
*/
private void unlinkAdd(E e, Node<E> node) {
Node<E> prev = node.prev;
Node<E> newNode = new Node<>(prev, e, node);
prev.next = newNode;
}
/**
* 向链表头部添加元素的方法
*
* @param e 要插入的元素
* @return 返回插入状态
*/
public boolean addFirst(E e) {
final Node<E> f = first;//使用临时变量存储插入前的第一个元素
final Node<E> newNode = new Node<E>(null, e, f); //创建新元素对象,将first引用地址给新元素对象的next属性
first = newNode;
if (f == null) {
last = newNode; //如果链表在插入前为空,则last最后一个元素也是第一个元素
} else {
f.prev = newNode;//将插入前的元素prev属性赋值新插入元素的引用,完成双向引用
}
size++;//计数+1
return true;
}
/**
* 向链表末尾插入元素
*
* @param e 要插入的元素
* @return 返回插入状态 true 插入成功
*/
public boolean addLast(E e) {
final Node<E> l = last;//使用临时变量存储插入前的最后一个元素
final Node<E> newNode = new Node<E>(l, e, null);//创建新元素对象,将last引用地址给新元素对象的prev属性
last = newNode;
if (l == null) {
first = newNode;//如果链表在插入前为空,则first第一个元素也是最后一个元素
} else {
l.next = newNode;//将插入前的元素prev属性赋值新插入元素的引用,完成双向引用
}
size++;
return true;
}
4.2删除接口
删除指定元素时,需要将指定元素存储单元中的数据删除以外,同时清空前一个后一个存储单元的地址,同时将前一个后一个存储单元的地址进行双向绑定。如图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U3VV0Efa-1613641433754)(C:\Users\baby\AppData\Roaming\Typora\typora-user-images\1613052048966.png)]
/**
* 删除指定下标元素
*
* @param index int 指定下标
* @return 被删除的元素
*/
public E remove(int index) {
checkIndex(index);
return unlink(node(index));
}
/**
* 删除第一个元素
*
* @return 返回被删除的元素
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlink(node(0));
}
/**
* 删除最后一个元素
*
* @return 返回被删除的元素
*/
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlink(node(size - 1));
}
/**
* 删除指定元素
*
* @param obj Object 指定元素
*/
public boolean remove(Object obj) {
if (obj == null) {
for (Node<E> i = first; i != null; i = i.next) {
if (i.item == null) {
unlink(i);
return true;
}
}
}else {
for (Node<E> i = first; i != null; i = i.next) {
if (obj.equals(i.item)) {
unlink(i);
return true;
}
}
}
return false;
}
4.3更新接口
通过指定下标获取指定元素后,对单元中的item属性进行修改
/**
* 更新元素中指定下标元素
* @param index int 类型 元素下标
* @param e 修改的元素
* @return 修改前的数据
*/
public E set(int index, E e) {
checkIndex(index);
Node<E> x = node(index);
E oldValue = x.item;
x.item = e;
return oldValue;
}
4.4查询接口
从第一个元素或者最后一个元素开始,通过next或prev的地址找到下一个或上一个单元的方式进行遍历,进行查询操作
/**
* 获取指定下标位置元素
* @param index int类型 指定下标位置
* @return 返回元素中存储的数据
*/
public E get(int index) {
checkIndex(index);
return node(index).item;
}
/**
* 获取第一个元素
* @return 返回元素中的item
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
/**
* 获取最后一个元素
* @return 返回元素中的item
*/
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
/**
* 获取指定元素的第一个下标位置
* @param obj Object 指定元素
* @return int 返回元素的下标位置
*/
public int indexOf(Object obj) {
int index = 0;
if (obj == null){
for(Node<E> x = first; x != null; x = x.next){
if (x.item == null){
return index;
}
index++;
}
}else {
for(Node<E> x = first; x != null; x = x.next){
if (obj.equals(x.item)){
return index;
}
index++;
}
}
return -1;
}
/**
* 返回指定元素的下标位置 最后一次出现
* @param obj Object 指定元素
* @return int 指定元素最后一次出现的下标
*/
public int lastIndexOf(Object obj) {
int index = size;
if (obj == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (obj.equals(x.item))
return index;
}
}
return -1;
}
/**
* 判断元素是否在集合中
* @param obj Object 需要判断的元素
* @return boolean类型 返回true代表存在
*/
public boolean contains(Object obj) {
return indexOf(obj) != -1;
}
/**
* LinkedList集合转换成Object数组
* @return Object[] 数组
*/
public Object[] toArray() {
Object[] ret = new Object[size];
int i = 0;
for (Node<E> x = first; x!= null;x=x.next){
ret[i++] = x.item;
}
return ret;
}
/**
* 返回LinkedList集合从指定下标截取到指定下标的新的LinkedList集合
* @param start int 开始下标
* @param end int 结束下标
* @return LinkedList 新的集合
*/
public List<E> subList(int start, int end) {
checkSubIndex(start,end);
LinkedList<E> newLinkedList = new LinkedList<>();
for (Node<E> x = node(start);x != node(end); x=x.next){
newLinkedList.add(x.item);
}
return (List<E>) newLinkedList;
}
4.5 内部私有方法封装
对用户增删改查时输入的下标进行检查的方法进行封装,对指定下标单元获取的方法进行封装,对取消链表中某一单元的链接形成新的连接的方法进行封装
/**
* 检查下标是否正常
*
* @param index
*/
private void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("下标异常" + index);
}
}
/**
* 检查下标是否合法 用于添加元素
* @param index
*/
private void checkIndexAdd(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("下标异常" + index);
}
}
/**
* 检查截取新集合下标是否正常
* @param start 开始下标
* @param end 终止下标
*/
private void checkSubIndex(int start, int end) {
if (start >= end) {
throw new IndexOutOfBoundsException("下标异常,正确下标 start < end");
}
checkIndex(start);
checkIndex(end);
}
/**
* 取消链表中某一单元的prev next引用 将指定元素t出链表结构中 将该元素的前后连接元素连接到一起
*
* @param x 要取消的元素
* @return 返回被取消的元素
*/
E unlink(Node<E> x) {
final E element = x.item;//用来保存要取消连接的数据
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//判断prev是否为空来确定是否是第一个元素
if (prev == null) {
first = next;
} else {
prev.next = next;//被取消元素前一个元素的next引用重新指向
x.prev = null;//清除被取消元素的prev引用
}
//判断next是否为空来确定是否是最后一个元素
if (next == null) {
last = prev;
} else {
next.prev = prev;//被取消元素的后一个元素的prev引用重新指向
x.next = null; // 清空被取消元素的next引用
}
x.item = null;//清空数据
size--;
return element;
}
/**
* 获取指定下标元素
*
* @param index 指定下标
* @return Node<E>元素
*/
Node<E> node(int index) {
if (index < size / 2) {
//当下标小于1/2size时,从first遍历
Node<E> temp = first;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp;
} else {
//当下标大于1/2size时,从last遍历
Node<E> temp = last;
for (int i = size - 1; i > index; i++) {
temp = temp.prev;
}
return temp;
}
}
4.5 tips
源码对其中一些操作进行了细致封装,在这里我只是做简单的源码实现,只对部分功能进行私有化方法封装,详细可以参照源码
5. 效率分析
LinkedList的增删直接使用断链生成新链的方式,相对来说消耗的时间较短。
LinkedList的查询操作需要从第一个单元或者最后一个单元进行遍历链表的操作,通过上一个单元才能获取下一个单元的数据,因此,无论查询哪一个元素,都要对链表进行遍历操作,相对来说消耗时间较长。
因此,采用LinkedList集合存储的数据在增删上效率较高,但是在查询的效率上相对低一些。
不足之处欢迎各位大大提意见!