• LruCache的使用


    1、Lru算法

    LRU:Least Recently Used,即最近最少使用,意思是当缓存到达限制时候,优先淘汰近期内最少使用的缓存。

    1.1 Lru的一种简单实现

    使用Java容器LinkedHashMap,LinkedHashMap本身就具有LRU算法的特性:

        class LRUCache {
            private Map<Integer, Integer> cacheMap = null;
    
            public LRUCache(int capacity) {
                // 参数设置true,当removeEldestEntry()返回true,则删除最旧的数据
                cacheMap = new LinkedHashMap<Integer, Integer>(capacity,0.75F,true){
                    @Override
                    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
                        return size() > capacity;
                    }
                };
            }
    
            public int get(int key) {
                return cacheMap.getOrDefault(key, -1);
            }
    
            public void put(int key, int value) {
                cacheMap.put(key, value);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.2Lru算法的另一种实现

    HashMap+双向链表:

    • 维护一个双向链表,靠近链表尾部的结点是越早访问的,靠近头部的节点是最近访问的。
    • 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
    • 如果此数据没有在缓存链表中,又可以分为两种情况: 如果此时缓存未满,则将此结点直接插入到链表的头部;如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。
    class LRUCache2 {
            // 当前缓存容量
            private int size;
            // 限制最大缓存容量
            private int capacity;
            // 定义伪头尾节点
            private Node head;
            private Node tail;
            // 定义HashMap存储数据
            private Map<Integer, Node> cache = new HashMap();
    
            // 初始化操作
            public LRUCache2(int capacity) {
                size = 0;
                this.capacity = capacity;
                // 初始化头尾节点
                head = new Node();
                tail = new Node();
                // 让头尾节点相联
                head.next = tail;
                tail.pre = head;
            }
    
            public int get(int key) {
                Node node = cache.get(key);
                // 不存在返回-1
                if (null == node) {
                    return -1;
                }
                // 存在返回值,并且将当前节点移动到头
                moveNodeHead(node);
                return node.value;
            }
    
            public void put(int key, int value) {
                Node node = cache.get(key);
                // 不存在则插入,插入后判断当前容量是否大于限制最大容量
                if (null == node) {
                    Node newNode = new Node(key, value);
                    cache.put(key, newNode);
                    // 放入链表头部
                    addNodeHead(newNode);
                    size++;
                    if (size > capacity) {
                        // 删除尾结点
                        Node tail = removeNodeTail();
                        cache.remove(tail.key);
                    }
                } else {
                    // 存在则覆盖value,并且将当前节点移动到头
                    node.value = value;
                    moveNodeHead(node);
                }
            }
    
            // 放入链表的头部
            private void addNodeHead(Node node) {
                // 当前头节点的下一个节点的pre和当前节点连接
                head.next.pre = node;
                //
                node.next = head.next;
                //
                head.next = node;
                node.pre = head;
            }
    
            // 将节点移动到链表的头部
            private void moveNodeHead(Node node) {
                node.pre.next = node.next;
                node.next.pre = node.pre;
                addNodeHead(node);
            }
    
            // 删除链表的尾结点
            private Node removeNodeTail() {
                Node tailNode = tail.pre;
                tailNode.pre.next = tailNode.next;
                tailNode.next.pre = tailNode.pre;
                return tailNode;
            }
    
            // 定义一个双向链表,实际的缓存
            class Node {
                private int key;
                private int value;
                private Node pre;
                private Node next;
                public Node() {
                }
                public Node(int key, int value) {
                    this.key = key;
                    this.value = 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

    2、LruCache和DiskLruCahce在android中的使用

    在Android中Bitmap数据量比较大,可以考虑LruCache、DiskLruCache缓存做二级缓存,减少请求量;同时,在一些经常请求且重要的数据,也可以放在这两个缓存中,在请求失败或请求错误时直接使用缓存的值,颇有些redis的味道。

    2.1 LruCache的使用

    2.1.1 初始化

    	public LruCache<String, Bitmap> mMemoryCache;
    
        private void initMemoryCache() {
            ActivityManager am = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
            int availMenInBytes = am.getMemoryClass() * 1024 * 1024;
            
            this.mMemoryCache = new LruCache<String, Bitmap>(availMenInBytes / 8) {
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                    return getBitmapSize(bitmap);
                }
            };
        }
    	//根据android版本来计算bitmap的实际占用内存
        public int getBitmapSize(Bitmap bitmap) {
    
            if (bitmap == null) {
                return 0;
            }
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                //API 19
                return bitmap.getAllocationByteCount();
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
                //API 12
                return bitmap.getByteCount();
            }
            // 在低版本中用一行的字节x高度
            return bitmap.getRowBytes() * bitmap.getHeight();
        }
    
    • 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

    2.1.2 put/get操作

        private void addToMemoryCache(String key, Bitmap bitmap) {
            if (getFromMemCache(key) == null) {
                if ((key != null) && (bitmap != null)) {
                    try {
                        this.mMemoryCache.put(key, bitmap);
                    } catch (Exception e) {
                        LogUtils.e(e.toString());
                    }
                }
            }
        }
        private Bitmap getFromMemCache(String key) {
            Bitmap bm = mMemoryCache.get(key);
            return bm;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.2 DiskLruCache的使用

    2.2.1 创建 DiskLruCache 对象

    	private DiskLruCache mDiskCache;
    
        private void initDiskCache() {
            File localFile = FileUtils.getCacheDir(UtilServices.getContext().getApplicationContext(), DISK_CACHE_SUBDIR);
            new InitDiskCacheTask().execute(localFile);
        }
    
    	    private class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
            private InitDiskCacheTask() {}
    
            @Override
            protected Void doInBackground(File... params) {
                synchronized (mDiskCacheLock) {
                    try {
                        File cacheDir = params[0];
                        if (!cacheDir.exists()) {
                            cacheDir.mkdirs();
                        }
                        mDiskCache = DiskLruCache.open(cacheDir, getVersionCode(), 1, DISK_MAX_SIZE);
                        mDiskCacheStarting = false;
                        mDiskCacheLock.notifyAll();
                    } catch (IOException localIOException) {
                        LogUtils.e(localIOException.getMessage());
                    }
                }
                return null;
            }
        }
    
    • 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
    • 构造方法是 private 修饰的,无法使用。使用静态方法 static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 来创建DiskLruCache 对象。

    • 参数 directory 表示保存的目录,注意外部目录权限问题

    • 参数 appVersion 可以设置为版本号 version code。

    • 参数 valueCount 表示一个 key 可以关联几个文件,一般为 1(一般情况关联多个没有必要,而且会增加编码复杂度)

    • 参数 maxSize 缓存大小限制,单位 Byte

    • 需要调用close() 关闭

    2.2.2 写入DiskLruCache

        private DiskLruCache.Editor editor(String key) {
            if (mDiskCache != null) {
                try {
                    DiskLruCache.Editor edit = mDiskCache.edit(key);
                    return edit;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    
        public void put(String key, String value) {
            DiskLruCache.Editor editor = null;
            BufferedWriter writer = null;
            OutputStream os = null;
            try {
                editor = editor(key);
                if (editor == null) {
                    return;
                }
                os = editor.newOutputStream(0);
                writer = new BufferedWriter(new OutputStreamWriter(os));
                writer.write(value);
                writer.flush();
                editor.commit();
            } catch (IOException e) {
    			Log.e(e.toString());
    			try {
                    if (editor != null) {
                        editor.abort();
                    }
                } catch (Exception e1) {
                    Log.e(e1.toString());
                }
            } finally {
            	writer.close();
    			os.close();
            }
        }
    
    • 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
    • 先调用 DiskLruCache.Editor edit(String key) 方法获取一个 DiskLruCache.Editor对象,再通过这个对象进行写入操作。

    • 返回的类型是 DiskLruCache.Editor,其实就是将写入相关的一些操作抽象处理,对这个对象的操作都对应 key关联的缓存文件。

    • 如果同时有另一个 Editor 对象是通过 key 获取的,edit 方法将返回 null。保证同时只有一个 Editor 对象在对同一个 key 进行写入操作。因此调用之后需要判断一下。

    • 使用 OutputStream newOutputStream(int index) 创建输出流来写入数据,注意这个流不是缓存流,如果需要缓存流可以创建 BufferedOutputStream 包装一下 OutputStream。

    • 这个 OutputStream 需要手动关闭。

    • 除了关闭输出流,还还需对 Editor 设置结果。如果写入操作和相关业务成功了,缓存文件有效,则调用 Editor.commit()方法表示缓存写入成功。如果写入操作或相关业务失败了,缓存文件无效,则调用 Editor.abort() 来还原为未获取 Editor之前的状态。

    2.2.3 读取DiskLruCache

    	public final Object mDiskCacheLock = new Object();
        private boolean mDiskCacheStarting = true;
        
        private Bitmap getBitmapFromDiskCache(String key) {
    
            synchronized (mDiskCacheLock) {
                while (mDiskCacheStarting) {
                    try {
                        mDiskCacheLock.wait();
                        continue;
                    } catch (Exception e) {
                        Log.e(e.toString());
                    }
                }
                if (mDiskCache != null) {
                    try {
                        DiskLruCache.Snapshot snapShot = mDiskCache.get(key);
                        if (snapShot != null) {
                            InputStream is = snapShot.getInputStream(0);// 0表示第一个缓存文件,不能超过valueCount
                            return BitmapFactory.decodeStream(is);
                        }
                    } catch (Exception e) {
                        Log.e(e.toString());
                    }
    
                }
            }
            return null;
        }
    
    • 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
    • 先调用 DiskLruCache.Snapshot get(String key) 获取一个 DiskLruCache.Snapshot对象,再通过这个对象进行读取操作。

    • 返回的类型是 DiskLruCache.Snapshot,其实就是这个 key对应的相关数据,主要是多个文件的输入流和大小,多个文件的数量对应构造方法里的 valueCount。

    • Snapshot.getLength(int index) 获取文件大小,进行一些判断。

    • Snapshot.getInputStream(int index) 获取一个InputStream,可以用来读取文件内容,注意这个流不是缓存流,如果需要缓存流可以创建 BufferedInputStream包装一下 InputStream

    • 这个 InputStream 需要手动关闭,既可以直接关闭 InputStream,也可以调用 Snapshot.close()来关闭属于它的所有 InputStream。

    2.2.4 DiskLruCache原理

    DiskLruCache在本地有一个journal的日志文件:

    libcore.io.DiskLruCache
    1
    100
    2
    
    CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
    DIRTY 335c4c6028171cfddfbaae1a9c313c52
    CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
    REMOVE 335c4c6028171cfddfbaae1a9c313c52
    DIRTY 1ab96a171faeeee38496d8b330771a7a
    CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
    READ 335c4c6028171cfddfbaae1a9c313c52
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    其中1表示diskCache的版本,100表示应用的版本,2表示一个key对应多少个缓存文件。journal 文件还记录了每个 Entry 的数据,有四种状态:

    • DIRTY:表示正在写入
    • CLEAN:表示就绪,可以读取到最新的修改了
    • REMOVE:表示被删除了
    • READ:表示正在读取

    接下来每一行,如:
    CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 对应
    [状态] [key] [缓存文件1的size] [缓存文件2的size]
    Entry 数据并不是在记录的位置“原地”修改,而是不停地添加新的状态到文件末尾,只有读取到最新的一条 Entry 相关的记录才能知道它的最新状态。随着操作越来越多,DiskLruCache 也会执行压缩,删除之前的状态。

  • 相关阅读:
    50mm镜头对20m外标定板成像实验
    猿创征文|【C#编程指南】 文件系统和注册表编程指南与实战
    蓝桥杯每日一题2023.10.18
    为什么不建议使用Python自带的logging?
    Redis设置开机自启动
    Insanity:1靶机
    大数据毕设选题 - 生成对抗网络的照片上色动态算法设计与实现(深度学习 opencv python)
    视频编解码领域入门指南:小白必读!
    布隆过滤器 python3 pybloom_live使用例子 存储开销
    怎么看两张图片的相似度,测试两张照片相似度
  • 原文地址:https://blog.csdn.net/skysukai/article/details/127755674