实用算法原理及简介

1、布隆过滤器

public class BloomFilter {

    /**
     * 1、首先需要初始化一个二进制的数组,长度设为 L,
     *    同时初始值全为 0 。
     *
     * 2、当写入一个 A1=1000 的数据时,需要进行 H 次 hash 函数的运算(这里为 2 次);
     *    与 HashMap 有点类似,通过算出的 HashCode 与 L 取模后定位到 0、2 处,将该处的值设为 1。
     *      A2=2000 也是同理计算后将 4、7 位置设为 1。
     *
     * 3、当有一个 B1=1000 需要判断是否存在时,也是做两次 Hash 运算,
     *    定位到 0、2 处,此时他们的值都为 1 ,所以认为 B1=1000 存在于集合中。
     *    当有一个 B2=3000 时,也是同理。第一次 Hash 定位到 index=4 时,数组中的值为 1,
     *    所以再进行第二次 Hash 运算,结果定位到 index=5 的值为 0,所以认为 B2=3000 不存在于集合中。
     */

    /**
     * 数组长度
     */
    private int arraySize;

    /**
     * 数组
     */
    private int[] array;

    public BloomFilter(int arraySize) {
        this.arraySize = arraySize;
        array = new int[arraySize];
    }

    public static void main(String[] args) {
        BloomFilter bloomFilter = new BloomFilter(10);
        bloomFilter.add("link");
        System.out.println(bloomFilter.check("link"));
    }

    /**
     * 写入数据
     * @param key
     */
    public void add(String key) {
        int first = hashcode_1(key);
        int second = hashcode_2(key);
        int third = hashcode_3(key);
        array[first % arraySize] = 1;
        array[second % arraySize] = 1;
        array[third % arraySize] = 1;
    }

    /**
     * 判断数据是否存在
     * @param key
     * @return
     */
    public boolean check(String key) {
        int first = hashcode_1(key);
        int second = hashcode_2(key);
        int third = hashcode_3(key);
        int firstIndex = array[first % arraySize];
        if (firstIndex == 0) {
            return false;
        }
        int secondIndex = array[second % arraySize];
        if (secondIndex == 0) {
            return false;
        }
        int thirdIndex = array[third % arraySize];
        if (thirdIndex == 0) {
            return false;
        }
        return true;
    }

    /**
     * hash 算法1
     * @param key
     * @return
     */
    private int hashcode_1(String key) {
        int hash = 0;
        int i;
        for (i = 0; i < key.length(); ++i) {
            hash = 33 * hash + key.charAt(i);
        }
        return Math.abs(hash);
    }

    /**
     * hash 算法2
     * @param data
     * @return
     */
    private int hashcode_2(String data) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < data.length(); i++) {
            hash = (hash ^ data.charAt(i)) * p;
        }
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        return Math.abs(hash);
    }

    /**
     *  hash 算法3
     * @param key
     * @return
     */
    private int hashcode_3(String key) {
        int hash, i;
        for (hash = 0, i = 0; i < key.length(); ++i) {
            hash += key.charAt(i);
            hash += (hash << 10);
            hash ^= (hash >> 6);
        }
        hash += (hash << 3);
        hash ^= (hash >> 11);
        hash += (hash << 15);
        return Math.abs(hash);
    }
}

2、一致性hash

一致性hash算法
1、初始化一个长度为 N 的数组。
2、将服务节点通过 hash 算法得到的正整数,同时将节点自身的数据(hashcode、ip、端口等)存放在这里。
3、完成节点存放后将整个数组进行排序(排序算法有多种)。
4、客户端获取路由节点时,将自身进行 hash 也得到一个正整数;
5、遍历这个数组直到找到一个数据大于等于当前客户端的 hash 值,就将当前节点作为该客户端所路由的节点。
6、如果没有发现比客户端大的数据就返回第一个节点(满足环的特性)。

  • TreeMap实现
public class ConsistentHashing {

    public static void main(String[] args) {
    	// 基于红黑树
        TreeMap<Long, String> map = new TreeMap<>();

        map.put(1L, "127.0.0.1");
        map.put(11L, "127.0.0.11");
        map.put(6L, "127.0.0.6");
        map.put(21L, "127.0.0.21");

        // tailMap 可以获取比当前 key 大的部分数据
        SortedMap<Long, String> last = map.tailMap(7L);
        if (!last.isEmpty()) {
            System.out.println(last.get(last.firstKey()));
        } else {
            System.out.println(map.firstEntry().getValue());
        }
    }

}
  • 自定义实现
    扩展性是对路由算法来说的,比如它需要支持轮询、
    hash、一致性hash、随机、LRU等
// AbstractConsistentHash 一致性抽象
public abstract class AbstractConsistentHash {

    /**
     * 添加节点
     *
     * @param key
     * @param value
     */
    public abstract void add(long key, String value);


    /**
     * 节点排序,默认自带排序时不用排序
     */
    public void sort() {
    }

    /**
     * 根据key获取第一个值
     *
     * @param value
     * @return
     */
    public abstract String getFirstNodeValue(String value);


    /**
     * 传入节点列表,返回一个节点
     *
     * @param values
     * @param key
     * @return
     */
    public String process(List<String> values, String key) {
        for (String value : values) {
            add(hash(key), value);
        }
        sort();
        return getFirstNodeValue(key);
    }

    /**
     * hash函数
     * @param key
     * @return
     */
    public Long hash(String key) {
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("md5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("md5 not supported", e);
        }
        md5.reset();
        byte[] keyBytes = null;
        keyBytes = key.getBytes(StandardCharsets.UTF_8);
        md5.update(keyBytes);
        byte[] digest = md5.digest();

        long hashCode = ((long) (digest[3] & 0xFF) << 24)
                | ((long) (digest[2] & 0xFF) << 16)
                | ((long) (digest[3] & 0xFF) << 8)
                | (digest[0] & 0xFF);

        long truncateHashCode = hashCode & 0xffffffffL;
        return truncateHashCode;
    }

}

public class SortArrayMap {

    private Node[] buckets;

    private static final int DEFAULT_SIZE = 10;

    private int size = 0;

    public SortArrayMap() {
        this.buckets = new Node[DEFAULT_SIZE];
    }

    private class Node {
        public Long key;
        public String value;

        public Node(Long key, String value) {
            this.key = key;
            this.value = value;
        }
    }

    public void add(Long key, String value) {
        checkSize(size + 1);
        Node node = new Node(key, value);
        buckets[size++] = node;
    }

    public void sort() {
        Arrays.sort(buckets, 0, size, new Comparator<Node>() {
            @Override
            public int compare(Node o1, Node o2) {
                return o1.key > o2.key ? 1 : -1;
            }
        });
    }

    public String firstNodeValue(Long key) {
        if (size == 0) {
            return null;
        }
        for (Node bucket : buckets) {
            if (bucket == null){
               continue;
            }
            if (bucket.key >= key){
                return bucket.value;
            }
        }
        return null;
    }

    private void checkSize(int size) {
        if (size >= buckets.length) {
            int oldLen = buckets.length;
            int newLen = oldLen + (oldLen >> 1);
            buckets = Arrays.copyOf(buckets, newLen);
        }
    }


}

public class SortArrayMapConsistentHash extends AbstractConsistentHash {

    private SortArrayMap sortArrayMap = new SortArrayMap();

    /**
     * 每个实际节点映射的虚拟节点数
     */
    private static final int VIRTUAL_NODE_SIZE = 2;

    @Override
    public void add(long key, String value) {
        for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) {
            Long hash = super.hash("vir" + key + i);
            sortArrayMap.add(hash, value);
        }
        sortArrayMap.add(key, value);
    }

    @Override
    public void sort() {
        sortArrayMap.sort();
    }

    @Override
    public String getFirstNodeValue(String value) {
        long hash = super.hash(value);
        return sortArrayMap.firstNodeValue(hash);
    }


    public static void main(String[] args) {
        AbstractConsistentHash map = new SortArrayMapConsistentHash();

        List<String> ips = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            ips.add("127.0.0." + i);
        }

        String process = map.process(ips, "link");
        System.out.println(process);
    }
}

  • 实现2
public class TreeMapConsistentHash  extends AbstractConsistentHash{

    private TreeMap<Long, String> treeMap = new TreeMap<>();

    /**
     * 虚拟节点数
     */
    private static final int VIRTUAL_NODE_SIZE = 2;

    @Override
    public void add(long key, String value) {
        for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) {
            Long hash = super.hash("vir" + key + i);
            treeMap.put(hash, value);
        }
        treeMap.put(key, value);
    }

    @Override
    public String getFirstNodeValue(String value) {
        long hash = super.hash(value);
        SortedMap<Long, String> last = treeMap.tailMap(hash);
        if (!last.isEmpty()){
            return last.get(last.firstKey());
        }
        return treeMap.firstEntry().getValue();
    }

    public static void main(String[] args) {
        AbstractConsistentHash map = new TreeMapConsistentHash();

        List<String> ips = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            ips.add("127.0.0." + i);
        }

        String process = map.process(ips, "link");
        System.out.println(process);
    }

}

3、微信抢红包算法

一般情况:
1.所有人抢到金额之和等于红包金额
2.每个人抢到不能少于最小值
3.要保证所有人抢到金额的几率相等
4.完全分配所有金额
 
特殊情况:
带有上下边界的分配
例如:总金额:100,个数:10,最小面值:1,最大面值:20
 
 假如前面九个均抢到1,那剩下最后一个为91
 没有最大的范围限制
 
方法1:二倍均值法
 *      M: 剩余红包金额
 *      N: 剩余人数
 *      每次抢到的金额 = 随机区间 (0, M / N X 2)
 *    例:
 *      假设有10个人,红包总额100元。
 *      100/10X2 = 20, 所以第一个人的随机范围是(0,20 ),平均可以抢到10元。
 *
 *      假设第一个人随机到10元,那么剩余金额是100-10 = 90 元。
 *      90/9X2 = 20, 所以第二个人的随机范围同样是(0,20 ),平均可以抢到10元。
 *
 *    缺点:
 *      每次抢到金额小于的平均的2倍
 
 
 方法2:线段切割法
 *      当N个人一起抢总金额为M的红包时,我们需要做N-1次随机运算,
 *      以此确定N-1个切割点。随机的范围区间是(1, M)
 *
 *    注意:
 *      1.当随机切割点出现重复,如何处理。
 *      2.如何尽可能降低时间复杂度和空间复杂度。
 
 方法3: 带上下限非对称平均值偏移修正随机
public class RedPacketTools {

    private static final BigDecimal TIMES_100 = new BigDecimal(100);
    private static BigDecimal MIN_MONEY = new BigDecimal("0.01");
    private static BigDecimal MAX_MONEY = new BigDecimal(50);
    private static final Random random = new Random();


    public static List<BigDecimal> createBonusList(BigDecimal totalMoney, int totalCount) {
        List<BigDecimal> moneys = new ArrayList<>(totalCount);

        int rdMoney = totalMoney.multiply(TIMES_100).intValue();
        int rdMin = MIN_MONEY.multiply(TIMES_100).intValue();
        int rdMax = MAX_MONEY.multiply(TIMES_100).intValue();

        List<Integer> _moneys = createBonusList(rdMoney, totalCount, rdMin, rdMax, 0.9);
        for (int m : _moneys) {
            moneys.add(new BigDecimal(m).divide(TIMES_100));
        }
        return moneys;
    }

    private static List<Integer> createBonusList(int totalBonus, int totalNum, int rdMin, int rdMax, double bigRate) {
        int sendedBonus = 0;
        int sendedNum = 0;
        List<Integer> bonusList = new ArrayList<>();
        while (sendedNum < totalNum) {
            int bonus = randomBonusWithSpecifyBound(totalBonus, totalNum, sendedBonus, sendedNum, rdMin, rdMax, bigRate);
            bonusList.add(bonus);
            sendedNum++;
            sendedBonus += bonus;
        }
        return bonusList;
    }


    /**
     * 带上下线的随机生成:
     * 总金币数量为S1,已发币量为S2
     * 总份数为P1,已发份数为P2
     * 平均值为S1/P1,则随机下限SMIN=(S1/P1)*0.1,随机上限SMAX=(S1/P1)*1.9
     * 设生成的第n个随机币值为X,剩余获取总值为Y,则剩余币S1-S2=X+Y
     * 同时X应处于限定随机上下限(SMIN~SMAX)之间,X的随机范围为:
     * <p>
     * MAX(S1–S2-(P1–P2-1)*SMAX,SMIN)<=X<=MIN(S1–S2–(P1–P2-1)*SMIN,SMAX)
     * <p>
     * bigRate:波动范围大小
     *
     * @return
     */
    private static int randomBonusWithSpecifyBound(int totalBonus, int totalNum, int sendedBonus,
                                                   int sendedNum, int rdMin, int rdMax, double bigRate) {
        double avg = totalBonus / (double) totalNum;
        int leftLen = (int) Math.ceil(avg - rdMin);
        int rightLen = (int) Math.floor(rdMax - avg);
        int boundMin = 0;
        int boundMax = 0;

        // 大范围设置小概率
        if (leftLen == rightLen) {
            boundMin = Math.max((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMax), rdMin);
            boundMax = Math.min((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMin), rdMax);
        } else if (rightLen - leftLen > 0) {
            // 上限偏离
            int standardRdMax = (int) Math.ceil(avg + leftLen);
            int _rdMax = canReward(bigRate) ? rdMax : standardRdMax;
            boundMin = Math.max((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * standardRdMax), rdMin);
            boundMax = Math.min((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMin), _rdMax);
        } else {
            // 下限偏离
            int standardRdMin = (int) Math.floor(avg - rightLen);
            int _rdMin = canReward(bigRate) ? rdMin : standardRdMin;
            boundMin = Math.max((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMax), _rdMin);
            boundMax = Math.min((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * standardRdMin), rdMax);
        }

        // 已发平均值偏移修正-动态比例
        if (boundMin == boundMax) {
            return getRandomVal(boundMin, boundMax);
        }
        double currAvg = sendedNum == 0 ? (double) avg : (sendedBonus / (double) sendedNum);
        double middle = (boundMin + boundMax) / 2.0;
        int subMin = boundMin, subMax = boundMax;
        // 期望值
        double exp = avg - (currAvg - avg) * sendedNum / (double) (totalNum - sendedNum);
        if (middle > exp) {
            subMax = (int) Math.round((boundMin + exp) / 2.0);
        } else {
            subMin = (int) Math.round((exp + boundMax) / 2.0);
        }
        int expBound = (boundMin + boundMax) / 2;
        int expSub = (subMin + subMax) / 2;
        double subRate = (exp - expBound) / (double) (expSub - expBound);
        return getRandomValWithSpecifySubRate(boundMin, boundMax, subMin, subMax, subRate);
    }

    /**
     * 返回一次抽奖在指定中奖概率下是否中奖
     */
    private static boolean canReward(double rate) {
        return Math.random() <= rate;
    }

    /**
     * 返回min~max区间内随机数,含min和max
     */
    private static int getRandomVal(int min, int max) {
        return random.nextInt(max - min + 1) + min;
    }

    /**
     * 带概率偏向的随机算法,概率偏向subMin~subMax区间
     * 返回boundMin~boundMax区间内随机数(含boundMin和boundMax),同时可以指定子区间subMin~subMax的优先概率
     * 例:传入参数(10, 50, 20, 30, 0.8),则随机结果有80%概率从20~30中随机返回,有20%概率从10~50中随机返回
     */
    private static int getRandomValWithSpecifySubRate(int boundMin, int boundMax, int subMin, int subMax, double subRate) {
        if (canReward(subRate)) {
            return getRandomVal(subMin, subMax);
        }
        return getRandomVal(boundMin, boundMax);
    }


    /**
     *  双边界法
     */
    public static void doubleBoundMethod() {
        BigDecimal totalMoney = new BigDecimal("100");
        int totalAmount = 10;
        List<BigDecimal> bonusList = createBonusList(totalMoney, totalAmount);
        System.out.println(Arrays.toString(bonusList.toArray()));
        BigDecimal sum = new BigDecimal(0);
        for (BigDecimal m : bonusList) {
            sum = sum.add(m);
        }
        System.out.println("sum:" + sum + ",人数:" + totalAmount);
    }

    /**
     * 2倍均值法
     * 特点:带下线的分割法
     * 缺点:分配不均匀,
     */
    public static List<Integer> redPacketDoubleMethod(int total, int count) {
        List<Integer> list = new ArrayList<>();
        int minLimit = 80 / 9;
        int maxLimit = 20;
        if (total < minLimit * count) {
            throw new RuntimeException("金额过小、最小金额过大!");
        }
        while (count > 0) {
            if (count == 0) {
                list.add(total);
                break;
            }
            int max = total / count * 2;
            int money = (int) (Math.random() * max);
            money = money < minLimit ? minLimit : money;
            money = money > maxLimit ? maxLimit : money;
            // 最大值限制
            total -= money;
            count--;
            list.add(money);
        }
        return list;
    }


    public static void main(String[] args) {
        redPacketDoubleMethod(100, 10);
        doubleBoundMethod();
    }
}

4、LRU缓存设计

  • 方法1
实现一个 LRU 缓存:
 * 1、当缓存数据达到 N 之后需要淘汰掉最近最少使用的数据。
 * 2、N 小时之内没有被访问的数据也需要淘汰掉。
public class LRUAbstractMap extends java.util.AbstractMap {

    /**
     * 检查是否超期线程
     */
    private ExecutorService checkTimePool;
    /**
     * map 最大size
     */
    private final static int MAX_SIZE = 1024;
    /**
     * 缓存节点数据
     */
    private final static ArrayBlockingQueue<Node> QUEUE = new ArrayBlockingQueue<>(MAX_SIZE);


    /**
     * 默认大小
     */
    private final static int DEFAULT_ARRAY_SIZE = 1024;

    /**
     * 数组长度
     */
    private int arraySize;

    /**
     * 数组
     */
    private Object[] arrays;

    /**
     * 判断是否停止 flag
     */
    private volatile boolean flag = true;

    /**
     * 超时时间
     */
    private final static Long EXPIRE_TIME = 60 * 60 * 1000L;

    /**
     * 整个 Map 的大小
     */
    private volatile AtomicInteger size;


    public LRUAbstractMap() {
        arraySize = DEFAULT_ARRAY_SIZE;
        arrays = new Object[arraySize];

        //开启一个线程检查最先放入队列的值是否超期
        executeCheckTime();
    }

    @Override
    public Set<Entry> entrySet() {
        return super.keySet();
    }

    @Override
    public int size() {
        return size.get();
    }

    public int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    @Override
    public Object put(Object key, Object value) {
        int hash = hash(key);
        int index = hash % arraySize ;
        Node currentNode = (Node) arrays[index] ;
        if (currentNode == null){
            arrays[index] = new Node(null,null, key, value);
            //写入队列
            QUEUE.offer((Node) arrays[index]) ;
            sizeUp();
        }else {
            Node cNode = currentNode ;
            Node nNode = cNode ;
            //存在就覆盖
            if (nNode.key == key){
                cNode.val = value ;
            }
            while (nNode.next != null){
                //key 存在 就覆盖 简单判断
                if (nNode.key == key){
                    nNode.val = value ;
                    break ;
                }else {
                    //不存在就新增链表
                    sizeUp();
                    Node node = new Node(nNode,null,key,value) ;
                    //写入队列
                    QUEUE.offer(currentNode) ;
                    cNode.next = node ;
                }
                nNode = nNode.next ;
            }
        }
        return null ;
    }

    @Override
    public Object get(Object key) {
        int hash = hash(key);
        int index = hash % arraySize;
        Node currentNode = (Node) arrays[index];
        if (currentNode == null) {
            return null;
        }
        if (currentNode.next == null) {
            //更新时间
            currentNode.setUpdateTime(System.currentTimeMillis());
            //没有冲突
            return currentNode;
        }
        Node nNode = currentNode;
        while (nNode.next != null) {
            if (nNode.key == key) {
                //更新时间
                currentNode.setUpdateTime(System.currentTimeMillis());
                return nNode;
            }
            nNode = nNode.next;
        }
        return super.get(key);
    }


    /**
     * 增加size
     */
    private void sizeUp() {
        //在put值时候认为里边已经有数据了
        flag = true;
        if (size == null) {
            size = new AtomicInteger();
        }
        int size = this.size.incrementAndGet();
        if (size >= MAX_SIZE) {
            //找到队列头的数据
            Node node = QUEUE.poll();
            if (node == null) {
                throw new RuntimeException("data error");
            }
            //移除该 key
            Object key = node.key;
            remove(key);
            lruCallback();
        }

    }

    @Override
    public Object remove(Object key) {
        int hash = hash(key);
        int index = hash % arraySize;
        Node currentNode = (Node) arrays[index];
        if (currentNode == null) {
            return null;
        }
        if (currentNode.key == key) {
            sizeDown();
            arrays[index] = null;
            //移除队列
            QUEUE.poll();
            return currentNode;
        }
        Node nNode = currentNode;
        while (nNode.next != null) {
            if (nNode.key == key) {
                sizeDown();
                //在链表中找到了 把上一个节点的 next 指向当前节点的下一个节点
                nNode.pre.next = nNode.next;
                nNode = null;
                //移除队列
                QUEUE.poll();
                return nNode;
            }
            nNode = nNode.next;
        }
        return super.remove(key);
    }

    private void lruCallback() {
        System.out.println("lruCallback");
    }

    /**
     * 数量减小
     */
    private void sizeDown() {
        if (QUEUE.size() == 0) {
            flag = false;
        }
        this.size.decrementAndGet();
    }


    // *******************************************************//

    /**
     * 开启一个线程检查最先放入队列的值是否超期
     * 设置为守护线程
     */
    private void executeCheckTime() {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("check-thread-%d")
                .setDaemon(true)
                .build();
        checkTimePool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
        checkTimePool.execute(new CheckTimeThread());

    }

    private class CheckTimeThread implements Runnable {
        @Override
        public void run() {
            while (flag) {
                try {
                    Node node = QUEUE.poll();
                    if (node == null) {
                        continue;
                    }
                    Long updateTime = node.getUpdateTime();
                    if ((updateTime - System.currentTimeMillis()) >= EXPIRE_TIME) {
                        remove(node.key);
                    }
                } catch (Exception e) {
                    System.out.println("InterruptedException");
                }
            }
        }
    }


    /**
     * 链表节点
     */
    private class Node {
        private Node next;
        private Node pre;
        private Object key;
        private Object val;
        private Long updateTime;

        public Node(Node pre, Node next, Object key, Object val) {
            this.pre = pre;
            this.next = next;
            this.key = key;
            this.val = val;
            this.updateTime = System.currentTimeMillis();
        }

        public void setUpdateTime(Long updateTime) {
            this.updateTime = updateTime;
        }

        public Long getUpdateTime() {
            return updateTime;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "key=" + key +
                    ", val=" + val +
                    '}';
        }
    }
}

  • 方法2
要求:
 *      要记录最近最少使用,那至少需要一个有序的集合来保证写入的顺序。
 *      在使用了数据之后能够更新它的顺序。
 思路:
 *      1、每次写入数据时将数据放入链表头结点。
 *      2、使用数据时候将数据移动到头结点。
 *      3、缓存数量超过阈值时移除链表尾部数据。
public class LRUMap<K,V> {

    private final Map<K, V> cacheMap = new HashMap<>();

    /**
     * 最大缓存大小
     */
    private int cacheSize;

    /**
     * 节点大小
     */
    private int nodeCount;

    /**
     * 头结点
     */
    private Node<K, V> header;

    /**
     * 尾结点
     */
    private Node<K, V> tailer;


    public LRUMap(int cacheSize) {
        this.cacheSize = cacheSize;
        header = new Node<>();
        header.pre = null;
        tailer = new Node<>();
        tailer.next = null;
        // 双向链表
        header.next = tailer;
        tailer.pre = header;
    }

    public void put(K key, V value) {
        cacheMap.put(key, value);
        addNode(key, value);
    }
    private void addNode(K key, V value) {
        Node<K, V> node = new Node<>(key, value);
        if (cacheSize == nodeCount) {
            delTail();
        }
        addHead(node);
    }
    private void delTail() {
        cacheMap.remove(tailer.getKey());
        tailer.pre.next = null;
        tailer = tailer.pre;
        nodeCount--;
    }
    private void addHead(Node<K, V> node) {
        //写入头结点
        header.pre = node;
        node.next = header;
        header = node;
        nodeCount++;
        //如果写入的数据大于2个 就将初始化的头尾结点删除
        if (nodeCount == 2) {
            tailer.pre.pre.next = null;
            tailer = tailer.pre.pre;
        }
    }



    public V get(K key){
        Node<K, V> node = getNode(key);
        moveToHead(node) ;
        return cacheMap.get(key);
    }
    private void moveToHead(Node<K,V> node){
        //如果是最后的一个节点
        if (node.next == null){
            node.pre.next = null ;
            tailer = node.pre ;
            nodeCount -- ;
        }
        //如果是本来就是头节点 不作处理
        if (node.pre == null){
            return ;
        }
        //如果处于中间节点
        if (node.next != null && node.pre != null){
            //它的上一节点指向它的下一节点 也就删除当前节点
            node.next.pre = node.pre ;
            nodeCount -- ;
        }
        //最后在头部增加当前节点
        //注意这里需要重新 new 一个对象,不然原本的node 还有着下面的引用,会造成内存溢出。
        node = new Node<>(node.getKey(),node.getValue()) ;
        addHead(node) ;
    }

    /**
     * 链表查询 效率较低
     * @param key
     * @return
     */
    private Node<K,V> getNode(K key){
        Node<K,V> node = tailer ;
        while (node != null){
            if (node.getKey().equals(key)){
                return node ;
            }
            node = node.pre ;
        }
        return null ;
    }

    private class Node<K, V> {
        private K key;
        private V value;
        Node<K, V> next;
        Node<K, V> pre;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public Node() {
        }

        public K getKey() {
            return key;
        }

        public void setKey(K key) {
            this.key = key;
        }

        public V getValue() {
            return value;
        }

        public void setValue(V value) {
            this.value = value;
        }

    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder() ;
        Node<K,V> node = tailer ;
        while (node != null){
            sb.append(node.getKey()).append(":")
                    .append(node.getValue())
                    .append("-->") ;

            node = node.pre ;
        }


        return sb.toString();
    }
}

  • 设计3:LinkedHashMap实现

LRU 缓存机制 设计和实现一个 LRU(最近最少使用)缓存数据结构,使它应该支持一下操作:get 和 put。 get(key) - 如果 key 存在于缓存中,则获取 key 的 value(总是正数),否则返回 -1。 put(key,value) - 如果 key 不存在,请设置或插入 value。当缓存达到其容量时,它应该在插入新项目之前使最近最少使用的项目作废。

public class LRULinkedMap<K,V> {

    /**
     * 最大缓存大小
     */
    private int cacheSize;

    private LinkedHashMap<K,V> cacheMap ;

    public LRULinkedMap(int cacheSize) {
        this.cacheSize = cacheSize;
        cacheMap = new LinkedHashMap(16,0.75F,true){
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                if (cacheSize + 1 == cacheMap.size()){
                    return true ;
                }else {
                    return false ;
                }
            }
        };
    }

    public void put(K key,V value){
        cacheMap.put(key,value) ;
    }

    public V get(K key){
        return cacheMap.get(key) ;
    }


    public Collection<Map.Entry<K, V>> getAll() {
        return new ArrayList<Map.Entry<K, V>>(cacheMap.entrySet());
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值