以元素数量为依据的 LRUHashMap

一般 LRU(Least Recently Used) 是以容量大小为准,但有时我们需要以元素数量为准来做 ,实现方式如下:

import java.util.HashMap;
import java.util.LinkedHashMap;
import androidx.annotation.Nullable;

/**
 * 参考 LRU 算法实现的 LinkedHashMap,但是以 put 为准,内部维护一个双向链表,
 * 最新 put 的元素在队尾,最旧 put 的元素在队头,当 put 一个元素后超过最大元素数量 {@link #mMaxEntryCount} 时
 * 会将队头的一个元素移除掉(即最旧放入的元素),并将其放入 {@link #mRemovedEntryList}
 *
 * 应用场景:
 * 为优化 Android SoundPool 加载耗时问题,全局使用单例的 SoundPool,当要加载一批音频资源时,先看 LRUHashMap 是否已加载过
 * 如果存在则不需要再加载,如果不存在则加载,并将结果存入 LRUHashMap,当加载的资源超过最大数量时,将最旧加载的资源 unload 掉
 * 并从 LRUHashMap 中移除,可通过 {@link #getRemovedEntries()} 获取顶出队列的元素
 *
 * @param <K> key
 * @param <V> value
 * 
 * Author: AlanWang4523.
 * Date: 2020/11/23 12:19.
 * Mail: alanwang4523@gmail.com
 */
public class LRUHashMap<K, V> extends LinkedHashMap<K, V> {
    /**
     * map 中能容纳的最大元素数量
     */
    private int mMaxEntryCount;
    /**
     * 超过最大数量后继续 put 时被移除的元素集合
     */
    private HashMap<K, V> mRemovedEntryList;

    public LRUHashMap(int maxEntryCount) {
        mMaxEntryCount = maxEntryCount;
        mRemovedEntryList = new HashMap<>();
    }

    /**
     * 清空被移除的元素列表,可在 put 之前先清空
     */
    public void clearRemovedEntries() {
        mRemovedEntryList.clear();
    }

    /**
     * 获取被移除的元素列表,可在 put 之后获取
     * @return 被移除的元素列表
     */
    public HashMap<K, V> getRemovedEntries() {
        return mRemovedEntryList;
    }

    @Nullable
    @Override
    public V put(K key, V value) {
        if (containsKey(key)) {
            remove(key);
        }
        return super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Entry<K, V> eldest) {
        boolean needRemove = size() > mMaxEntryCount;
        if (needRemove) {
            mRemovedEntryList.put(eldest.getKey(), eldest.getValue());
        }
        return needRemove;
    }
}

测试代码如下:

import android.os.Bundle;
import android.util.Log;
import com.startap.avaudiorecord.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import androidx.appcompat.app.AppCompatActivity;

public class TestLRUHashMapActivity extends AppCompatActivity {
    private LRUHashMap<String, Integer> mLRUHashMap = new LRUHashMap<>(3);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_common_test);

        testLRUHashMap();
    }

    private void testLRUHashMap() {
        ArrayList<String> audioPathList = new ArrayList<>();
        audioPathList.add("1");
        audioPathList.add("2");
        audioPathList.add("3");
        List<Integer> streamIdList = loadList(audioPathList);

        showMap("After Put:[1, 2, 3]---->>");

        audioPathList.clear();
        audioPathList.add("5");
        audioPathList.add("1");
        streamIdList = loadList(audioPathList);

        showMap("After Put:[5, 2, 1]---->>");
    }

    public List<Integer> loadList(List<String> audioPathList) {
        List<Integer> streamIdList = new ArrayList<>(audioPathList.size());

        // 先查找已经 load 过的资源
        for (int i = 0; i < audioPathList.size(); i++) {
            String audioPath = audioPathList.get(i);
            int streamId = -1;
            Integer value = mLRUHashMap.get(audioPath);
            if (value != null) {
                streamId = value;
            }
            streamIdList.add(i, streamId);
        }

        // 将本次的 streamId 列表放入 LRU Map,没有加载过的则加载
        mLRUHashMap.clearRemovedEntries();
        for (int i = 0; i < streamIdList.size(); i++) {
            String audioPath = audioPathList.get(i);
            int streamId = streamIdList.get(i);
            if (streamId < 0) {
                streamId = load(audioPath);
                streamIdList.set(i, streamId);
            }
            mLRUHashMap.put(audioPath, streamId);
        }

        // 将最久不使用的并从 LRU Map 移除的资源释放掉
        HashMap<String, Integer> removedEntries = mLRUHashMap.getRemovedEntries();
        if (removedEntries.size() > 0) {
            for (HashMap.Entry <String, Integer> entry: removedEntries.entrySet()) {
                String audioPath = entry.getKey();
                int streamId = entry.getValue();
                if (!audioPathList.contains(audioPath)) {
                    unload(streamId);
                }
            }
        }
        return streamIdList;
    }

    private int load(String audioPath) {
        int streamId = Integer.parseInt(audioPath);
        loge("load()-->streamId = " + streamId +  ", audioPath = " + audioPath);
        return streamId;
    }

    private void unload(int streamId) {
        loge("unload()-->>streamId = " + streamId);
    }

    private void showMap(String msg) {
        StringBuilder stringBuilder = new StringBuilder(msg);
        stringBuilder.append("-->Map:[");
        for (HashMap.Entry < String, Integer > entry: mLRUHashMap.entrySet()) {
            stringBuilder.append(entry.getKey()).append(",");
        }
        stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), "]");
        loge("showMap()----->>>>>" + stringBuilder.toString());
    }
    
    private static void loge(String msg) {
        Log.e("TestLRUHashMapActivity", msg);
    }
}

测试结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值