基于双向链表和哈希表(开放地址)实现LRU缓存

blueice
发布于 2020-9-25 10:35
浏览
0收藏

一、设计思路


1. 数据存储

 

数据存储使用开放地址哈希表,而不是使用链表哈希的方式,从而保证存放最多指定容量的数据。如果发生冲突,则往下查找直到找到一个空位置。

 

对应的数据结构层面,则是使用数组,更确切地说是环形数组,因为当发生冲突时,需要查找除了直接通过 key % 容量capacity 取模之外的所有其他位置一遍,直到找到一个可以存放该键值对的位置。对于数据获取,也是类似的思路。

 

2. LRU特性实现

 

仿照Java的linkedhashmap来维护一个双向链表,每次访问一个节点或者新增一个节点则放到链表尾部,删除节点,则删除链表首部节点,实现O(1)复杂度。

 

二、编码实现
leetcode已通过: LRU缓存

class LRUCache {
    // 解题思路:
    // 仿照Java的linkedhashmap来维护一个双向链表,每次访问一个节点或者新增一个节点则放到链表尾部
    // 删除节点,则删除链表首部节点
    // 数据存储使用开放地址哈希表,就冲突节点放在下一个空位置,而不是使用链表哈希的方式

    private Node head;
    private Node tail;

    private int capacity;
    private int size;
    private Node[] table;


    public LRUCache(int capacity) {
        if (capacity > 0) {
            this.capacity = capacity;
            this.table = new Node[capacity];
        }
    }
    
    public int get(int key) {
        if (capacity <= 0 || key < 0 || size == 0) {
            return -1;
        }

        // 目标位置
        int arrayIndex = key % capacity;
        Node target = table[arrayIndex];

        // 存在冲突不在目标位置
        if (target == null || target.key != key) {
            int nextLocation = (arrayIndex + 1) % capacity;
            while (nextLocation != arrayIndex) {
                Node node = table[nextLocation];
                if (node != null && node.key == key) {
                    // 找到
                    target = node;
                    break;
                } else {
                    nextLocation = (nextLocation + 1) % capacity;
                }
            }
        }

        if (target != null && target.key == key) {
            // 维护双向链表,放到链表尾部
            adjustNodeAccessLocation(target);
            
            return target.value;
        } else {
            // 未找到
            return -1;
        }
    }
    
    public void put(int key, int value) {
        if (capacity <= 0 || key < 0) {
            return;
        }

        Node target = null;
        // 查找当前节点是否存在,存在则更新,否则插入
        for (int i = 0; i < table.length; i++) {
            Node node = table[i];
            if (node != null && node.key == key) {
                target = node;
                break;
            }
        }

        if (target != null) {
            // 更新
            target.value = value;
            adjustNodeAccessLocation(target);

        } else {
            // 新增
            // 容量满了,删除最近最少访问的元素,即head节点
            if (size == capacity) {
                Node toDeletedNode = head;
                head = head.next;
                if (head == null) {
                    tail = null;
                } else {
                    head.pre = null;
                }

                table[toDeletedNode.arrayIndex] = null;
                toDeletedNode = null;
                size--;
            }

            // 取模获取数组下标,如果已经存在元素,则往下查找直到查找到一个空闲位置,
            // 注意是环形数组,直到当前数组下标的前一个位置
            int arrayIndex = key % capacity;
            Node node = table[arrayIndex];
            
            Node newNode = new Node(key, value);

            if (node == null) {
                newNode.arrayIndex = arrayIndex;
                table[arrayIndex] = newNode;
            } else {
                // 存在冲突,遍历直到找到一个空位置,环形数组故与capacity取模,避免越界问题
                int nextLocation = (arrayIndex + 1) % capacity;

                // 查找直到回到原位置
                while (nextLocation != arrayIndex) {
                    if (table[nextLocation] == null) {
                        // 找到
                        newNode.arrayIndex = nextLocation;
                        table[nextLocation] = newNode;
                        break;
                    } else {
                        // 往下查找
                        nextLocation = (nextLocation + 1) % capacity;
                    }
                }
            }
            size++;
            target = newNode;
            // 新节点追加到双向链表尾部
            addNewNodeToTail(target);
        }
    }

    private void adjustNodeAccessLocation(Node target) {
        // 存在两个以上节点且不是访问尾结点
        if (head != tail && target != tail) {
            // 访问头结点
            if (head == target) {
                head = head.next;
                head.pre = null;

                tail.next = target;
                target.pre = tail;
                tail = target;
            } else {
                // 访问中间节点
                // 调整当前节点的双向链表的前后节点
                target.pre.next = target.next;
                target.next.pre = target.pre;
                
                // 当前节点放到链表尾部
                tail.next = target;
                target.pre = tail;
                tail = target;
            }
        }
    }

    private void addNewNodeToTail(Node target) {
        if (head == null && tail == null) {
            head = tail = target;
        } else {
            tail.next = target;
            target.pre = tail;
            tail = target;
        }
    }

    private class Node {
        public int key;
        public int value;
        // 优化性能:当前在哈希表数组的下标,O(1)时间复杂度删除
        public int arrayIndex;
        public Node pre;
        public Node next;

        public Node() {}

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
            this.pre = null;
            this.next = null;
        }
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */```

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.

 

作者:服务端开发

来源:CSDN

分类
收藏
回复
举报


回复
    相关推荐