做的LC一道题,时间复杂度为O(1)的CRUD、数据结构,里面就想到了JAVA中的LinkedHashMap ,编码逻辑十分清楚。文字讲解放在了开头。
/**
* @description: Leetcode_146 LRU缓存
* 最近最少使用元素,查找插入修改时间复杂度都为O(1)。使用HashMap加上有序双向链表(即队列)的方式
* 1、创建Node {key,value,prev,next,有参构造,无参构造}
* 2、补充LRUCache构造方法,就是 初始化链表(head.next = tail; tail.prev = head;)
* 3、补充put get方法,其中用到moveToHead,addToHead,removeTail等方法
* 4、put: hashMap.get(key),如果没有就添加,创建node对象,addToHead方法添加,并hashmap.put(key),并判断添加完后链表大小是否大于容量,大于则removeTail
* 5、get: hashMap.get(key),有就return得到的value,没有就是return -1
* 6 7 8 朋友们自己多写几下,不要复制粘贴,链表的插入删除移动节点指针得动手练习(我就是动手写的,加强熟练度朋友们)。
* 6、moveToHead: node.prev.next = node.next; node.next.prev = node.prev; node.prev = head; node.next = head.next; head.next.prev = node; head.next = node;
* 7、addToHead: node.prev = head; node.next = head.next; head.next.prev = node; head.next = node;
* 8、removeTail: tail.prev.prev.next = tail; tail.prev = tail.prev.prev;
* @author: HammerRay
* @date: 2024/3/22 下午8:35
*/
import java.util.HashMap;
import java.util.Map;
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int _key, int _value) {
key = _key;
value = _value;
}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
// hashMap + 队列(双向链表) 一层hash包一层双向链表
this.size = 0;
this.capacity = capacity;
// 伪头部和伪尾部
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果key存在,想通过hashmap定位,再移动到头部 hashmap与hashtable的关系是 前者是线程不安全的,后者安全的
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
DLinkedNode newNode = new DLinkedNode(key, value);
cache.put(key, newNode);
addToHead(newNode);
++size;
if (size > capacity) {
DLinkedNode tail = removeTail();
cache.remove(tail.key);
--size;
}
} else {
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void moveToHead(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = head;
node.next = head.next;
head.next = node;
node.next.prev = node;
}
private DLinkedNode removeTail() {
DLinkedNode result = tail.prev;
tail.prev.prev.next = tail;
tail.prev = tail.prev.prev;
return result;
}
}
其中回想起了一些知识点:
1、HashTable和HashMap的区别,前者是线程安全的,后者线程不安全,那些性能什么差异就是线程安不安全的差异,还有代码上的什么HashTable是实现了Map接口又继承了Dictionary类,HashMap只实现了Map接口,还有具备方法上的差异就不说了,因为个人认为不需要记住这些,感兴趣的朋友自行搜索一哈。
2、HashTable为什么能实现CRUD为零呢?主要是hash(key)函数,这个函数中,将key值转换为具体的数组上存储的位置index,并且函数中计算出来的数据十分散列,让每一个元素都随机分配到一个数组上。等到CRUD时候,比如get(key),通过hash(key)立马获得index,查找到元素。
3、HashTable中总会存在冲突有那些解决办法?
链地址法:hashtable的那个数组元素后,接有链表,发生冲突的元素往链表中添加
线性探测:从探测产生冲突位置向下一个一个位置地探测是否为空,有空位就向空位中插入。
二次探测: 通过计算一个二次方程来确定下一个探测的位置 ,有空就插
二重散列: 使用另一个hash()函数进行偏移量测量,寻找空位。
4、redis的哨兵模式是什么?
它是为了保证redis集群的高可用HA而产生的,通过配置配置文件调整哨兵的个数。哨兵节点会向主节点,发送ping,如果足够的哨兵发现,ping主节点异常,则集群开启调整,选取下一个主节点进行故障转移操作。
哇越往下想越多,redis的选主操作是如何进行的? 常见的选主算法有哪些?raft和paxos有什么异同?redis常用来做什么?为什么? etc.. 路漫漫其修远兮哟。