LinkedList源码解析及LRU算法解析
LinkedList源码解析
简介
LinkedList 类
优点:尾插效率高,插入删除时间一致的,数据移动快
缺点:随机访问效率慢
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;
}
依赖关系
LinkedList依赖关系如下图
常用方法
1:add方法
add方法有很四个,咱们一个一个来分析,第一个尾插add方法
add(E e):
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//取出最后的节点数据
final Node<E> l = last;
//新节点在last节点后面
final Node<E> newNode = new Node<>(l, e, null);
//newNode变为最后节点
last = newNode;
//如果没有最后的节点(没有最后一个肯定也没有第一个),第一个节点变为newNode
if (l == null)
first = newNode;
else
//有最后一个节点的话,就把newNode变为最后一个节点,绑定在之前last的后面
l.next = newNode;
//长度+1
size++;
//修改次数+1
modCount++;
}
指定位置插入,主要有三个方法,linkLast()尾插方法,node(),linkBefore()。
add(int index, E element):
public void add(int index, E element) {
//检验index是否越界
checkPositionIndex(index);
//如果index与size相同,代表要在最后一位插入,代表则用尾插
if (index == size)
//与上一个一致,见上面解析
linkLast(element);
else
linkBefore(element, node(index));
}
//查询指定位置的node节点元素
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;
}
}
//主要进行节点替换操作
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//取出下标对应的节点的上一个节点对象
final Node<E> pred = succ.prev;
//把下标对应节点设置为newNode,更新上一个与下一个
final Node<E> newNode = new Node<>(pred, e, succ);
//上个节点的数据设置为newNode
succ.prev = newNode;
//下标对应的节点对象的上一个对象,如果不存在,则上一个设置为newNode;
//不存在则为第一个节点,所以设置第一个节点为newNode
if (pred == null)
first = newNode;
else
//下标对应的节点的下一个节点对象设置为newNode
pred.next = newNode;
//长度+1
size++;
//修改次数+1
modCount++;
}
addFirst:头部插入
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);
//第一个节点设置为newNode
first = newNode;
//如果此前没有第一个节点,则最后一个节点设置为newNode;因为只有
if (f == null)
last = newNode;
else
//如果此前有第一个节点,则上一个节点设置为newNode;因为链表每个数据要插入头部
f.prev = newNode;
//长度+1
size++;
//修改次数+1
modCount++;
}
addLast方法:
public void addLast(E e) {
//上面已经描述过了
linkLast(e);
}
2:remove方法
remove方法有五种,removeFirst,removeLast,remove(Object o),remove(int index),remove()
remove(int index):删除指定位置数据,返回删除数据
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;
//上一个元素是null,则first设置为指定元素的下一个,因为指定元素要删除,上一个也没,自己也没,只能下一个变为第一。
if (prev == null) {
first = next;
} else {
//上一个元素的下一个变为自己的下一个,因为自己要被删除
prev.next = next;
//自己制为null
x.prev = null;
}
//如果指定对象的下一个为null,说明是最后一个元素,自己被删除,last变为自己的上一个元素
if (next == null) {
last = prev;
} else {
//下一个元素的上一个指向自己的上一个,因为本来指向自己的,自己没了
next.prev = prev;
//自己的下一个设为null,自己都没了,自己的上一个下一个都没意义了
x.next = null;
}
//自己设为null
x.item = null;
//长度-1
size--;
//变更次数+1
modCount++;
return element;
}
remove(Object o):删除指定对象,只删除一个
public boolean remove(Object o) {
//循环找出要删除的对象所处的节点,然后删除,只删除第一个
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
remove():删除链表头部数据,并返回该数据
public E remove() {
return removeFirst();
}
removeFirst():删除链表头部数据,并返回该数据(小问号,你是否有很多的朋友)
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
removeLast():删除并返回最后一个元素
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
//最后一个元素的数据
final E element = l.item;
//最后一个元素的数上一个节点
final Node<E> prev = l.prev;
//清除自己个自己的上一个
l.item = null;
l.prev = null; // help GC
//最后一个变为自己的上一个
last = prev;
//没有上一个,则last=first,第一个为null
if (prev == null)
first = null;
else
//上一个节点的下一个制为null
prev.next = null;
//长度-1
size--;
//更改次数+1
modCount++;
return element;
}
3:set方法
set(int index, E element):替换指定节点的数据
public E set(int index, E element) {
//校验下标是否越界
checkElementIndex(index);
//查询出指定节点的数据
Node<E> x = node(index);
//老节点数据返回使用
E oldVal = x.item;
//新节点赋值
x.item = element;
return oldVal;
}
3:get方法
get方法有三个,getFirst(),getLast(),get()
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
有个小疑惑,为什么为null的时候不能返回null,而抛异常。
我理解就是为了node循环查询时,不在额外判断是否为null,增加效率,不知到对不对。
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
LRU算法解析
介绍
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
- 新数据插入到链表头部;
- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
- 当链表满的时候,将链表尾部的数据丢弃。
基于LinkedList实现LRU算法
既然是有插入淘汰的算法,肯定考虑空间大小,所以设置链表长度。
public class LruLinkedList {
int memory_size;//用于限定内存空间大小,也就是缓存大小
static final int DEFAULT_CAP = 5;//默认空间为5个节点
public LruLinkedList(int default_memory_size) {
memory_size = default_memory_size;
}
}
新数据从头部增加
public void lruAdd(E data) {
//如果链表长度大于等于设置的缓存大小,移除队尾数据
if (size >= memory_size) {
removeLast();
addFirst(data);
} else {
addFirst(data);
}
}
访问的节点移到头部,更改链表顺序,删除新增效率高
public E LruGet(int index) {
checkPositionIndex(index);
Node<E> resultDate = node(index);
//删除访问的节点,访问的节点移到头部
unlink(resultDate);
//因为使用的已有方法,每个方法都会modCount++,从而减少一次。
modCount--;
linkFirst(resultDate.item);
return resultDate.item;
}
测试代码与结果
public static void main(String[] args) {
LinkedList <String> linkedList=new LinkedList<>();
for(int i=0;i<10;i++){
linkedList.add(i+"");
}
System.out.println("默认的linked数据:"+linkedList);
System.out.println("查询后linked数据:"+lruGet(linkedList,2));
System.out.println("新增的linked数据:"+lruAdd(linkedList,10+""));
System.out.println("新增的linked数据:"+lruAdd(linkedList,11+""));
}
以上就是全部内容,有任何疑问可以留言,大家一起讨论。