一般 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);
}
}
测试结果: