LruCache缓存使用

作者: 金馆长说 | 来源:发表于2018-09-03 21:47 被阅读19次

    1.为什么要缓存?

    在android项目中经常需要请求网络图片,这个时候如果每张网络图片显示都要去获取一次的话,是非常耗费性能的。从网络中成功获取一张图片需要发起请求、等待响应、接收数据、解析数据、在做显示。这一系列操作我们都需要放到子线程中去执行,确认程序不会出现ANR问题。

    对于缓存来说,无法就是添加缓存、删除缓存、获取缓存这三种操作。先判断是否有缓存,没有就进行缓存操作,当缓存满了之后我们就可以把最久没使用的缓存给清除掉,已达到不超出缓存边界的问题。

    2.二种缓存方式

    为了应对这种性能问题,我们有可以选择对图片进行缓存,在android中主要有二种方式来缓存,一种是内存缓存、磁盘缓存。内存缓存是最快的因为它是直接从内存中读取数据,磁盘缓存稍慢因为它要做IO流操作,但是它可以持久化数据,各有各的好处,所以我们可以结合情况二种方式都用上。

    3.LruCache

    结合以上出现的问题android在3.1的时候推出了一个LRU(Least Recently Used)最近期最少使用算法,它的核心思想就是,优先清除那些最近最少使用的对象。LRU体现有二种一个是内存中的LruCache和一个针对磁盘的DisLruCache。它们的核心都是LRU思想。

    4.使用

    这里初始化了一个LruCache类,构造函数我们传入了一个Int类型的 cacheSize参数,这个参数代表缓存的最大边界是当前显示的1/8的容量。

    sizeOf
    这个方法是计算每个缓存对象大小的,内部通过这个方法叠加大小看是否超过了,我们初始化构造函数中的大小。

      private LruCache<String, Bitmap> mCache;
      private Bitmap bitmap;
      private LruCache<String, Bitmap> mCache;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.layout_glide);
            img = findViewById(R.id.iamge);
            initLRUCache();
            findViewById(R.id.but).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Bitmap bitmap = getCacheImage("bitmap");
                    if (bitmap == null) {
                        requestImage();
                    } else {
                        Log.i("jinwei", "获取缓存");
                        img.setImageBitmap(bitmap);
                    }
                }
            });
    
        private void initLRUCache() {
            long maxMemory = Runtime.getRuntime().maxMemory();
            int cacheSize = (int) (maxMemory / 8);
            mCache = new LruCache<String, Bitmap>(cacheSize) {
    
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getByteCount();
                }
            };
        }
    
      
    
        android.os.Handler handler = new android.os.Handler() {
            @Override
            public void dispatchMessage(Message msg) {
                super.dispatchMessage(msg);
                img.setImageBitmap(bitmap);
            }
        };
    
        private void httpUrlConnectionImage() throws MalformedURLException {
            Log.i("jinwei", "request Image");
            URL url = new URL("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1535715922665&di=b706c8b7a4f907a07b1f5062073a4e2a&imgtype=0&src=http%3A%2F%2Fwww.baimg.com%2Fuploadfile%2F2015%2F0413%2FBTW_2015041347455.jpg");
            try {
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setReadTimeout(5000);
                connection.setConnectTimeout(5000);
                InputStream inputStream = connection.getInputStream();
                bitmap = BitmapFactory.decodeStream(inputStream);
                addCache("bitmap", bitmap);
                handler.sendEmptyMessage(2);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private void initLRUCache() {
            long maxMemory = Runtime.getRuntime().maxMemory();
            int cacheSize = (int) (maxMemory / 8);
            mCache = new LruCache<String, Bitmap>(cacheSize) {
    
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getByteCount();
                }
            };
        }
    
        /**
         * 缓存取bitmap
         *
         * @param key key
         * @return 缓存bm
         */
        private Bitmap getCacheImage(String key) {
            Bitmap mb = mCache.get(key);
            return mb;
        }
    
        /**
         * 移除缓存
         *
         * @param key
         */
        private void removeCache(String key) {
            mCache.remove(key);
        }
    
        /**
         * 添加一个缓存
         *
         * @param key
         * @param bitmap
         */
        private void addCache(String key, Bitmap bitmap) {
            if (getCacheImage(key) == null) {
                mCache.put(key, bitmap);
            }
        }
    
        private void requestImage() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        httpUrlConnectionImage();
                    } catch (MalformedURLException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    

    原理分析

    可以看到构造里面做的操作,初始化可一个LinkedHashMap,所有LRU的核心就是通过hasmap来存储缓存对象的,maxSize保存了传入预设的大小值。

        private final LinkedHashMap<K, V> map;
    
        /** Size of this cache in units. Not necessarily the number of elements. */
        private int size;
        private int maxSize;
    
        private int putCount;
        private int createCount;
        private int evictionCount;
        private int hitCount;
        private int missCount;
    
     public LruCache(int maxSize) {
            if (maxSize <= 0) {
                throw new IllegalArgumentException("maxSize <= 0");
            }
            this.maxSize = maxSize;
            this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
        }
    

    put方法

    首先判断了key-value是否为null,为Null会抛出一个异常,所以LRU不支持key-value为null值。Lru的put方法是线程安全的,safeSizeOf就是会调用我们之前实现的sizeOf方法。

    /**
         * Caches {@code value} for {@code key}. The value is moved to the head of
         * the queue.
         *
         * @return the previous value mapped by {@code key}.
         */
        public final V put(K key, V value) {
            if (key == null || value == null) {
                throw new NullPointerException("key == null || value == null");
            }
    
            V previous;
            synchronized (this) {
                putCount++;
                size += safeSizeOf(key, value);
                previous = map.put(key, value);
                if (previous != null) {
                    size -= safeSizeOf(key, previous);
                }
            }
    
            if (previous != null) {
                entryRemoved(false, key, previous, value);
            }
    
            trimToSize(maxSize);
            return previous;
        }
    
         private int safeSizeOf(K key, V value) {
            int result = sizeOf(key, value);
            if (result < 0) {
                throw new IllegalStateException("Negative size: " + key + "=" + value);
            }
            return result;
        }
    

    trimToSize

    注释说明的检查最近最少使用的的对象进行清除,当然是要缓存超出边界了才会进行清除。size <= maxSize这个判断可以看出来。这个方法在put方法中调用,意思就是每次Put的时候都会检查当然的缓存容量,如果超出就会清除最近最少使用的对象。

     /**
         * Remove the eldest entries until the total of remaining entries is at or
         * below the requested size.
         *
         * @param maxSize the maximum size of the cache before returning. May be -1
         *            to evict even 0-sized elements.
         */
    public void trimToSize(int maxSize) {
            while (true) {
                K key;
                V value;
                synchronized (this) {
                    if (size < 0 || (map.isEmpty() && size != 0)) {
                        throw new IllegalStateException(getClass().getName()
                                + ".sizeOf() is reporting inconsistent results!");
                    }
    
                    if (size <= maxSize) {
                        break;
                    }
    
                    Map.Entry<K, V> toEvict = map.eldest();
                    if (toEvict == null) {
                        break;
                    }
    
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    map.remove(key);
                    size -= safeSizeOf(key, value);
                    evictionCount++;
                }
    
                entryRemoved(true, key, value, null);
            }
        }
    

    get

    这个方法比较容易理解,mapValue = map.get(key);如果mapValue不为NUll就直接返回我们缓存的对象。

    /**
         * Returns the value for {@code key} if it exists in the cache or can be
         * created by {@code #create}. If a value was returned, it is moved to the
         * head of the queue. This returns null if a value is not cached and cannot
         * be created.
         */
        public final V get(K key) {
            if (key == null) {
                throw new NullPointerException("key == null");
            }
    
            V mapValue;
            synchronized (this) {
                mapValue = map.get(key);
                if (mapValue != null) {
                    hitCount++;
                    return mapValue;
                }
                missCount++;
            }
    
            /*
             * Attempt to create a value. This may take a long time, and the map
             * may be different when create() returns. If a conflicting value was
             * added to the map while create() was working, we leave that value in
             * the map and release the created value.
             */
    
            V createdValue = create(key);
            if (createdValue == null) {
                return null;
            }
    
            synchronized (this) {
                createCount++;
                mapValue = map.put(key, createdValue);
    
                if (mapValue != null) {
                    // There was a conflict so undo that last put
                    map.put(key, mapValue);
                } else {
                    size += safeSizeOf(key, createdValue);
                }
            }
    
            if (mapValue != null) {
                entryRemoved(false, key, createdValue, mapValue);
                return mapValue;
            } else {
                trimToSize(maxSize);
                return createdValue;
            }
        }
    

    remove

    remove直接会key 对应的对象清除掉,响应缓存容量也会减少。

      /**
         * Removes the entry for {@code key} if it exists.
         *
         * @return the previous value mapped by {@code key}.
         */
        public final V remove(K key) {
            if (key == null) {
                throw new NullPointerException("key == null");
            }
    
            V previous;
            synchronized (this) {
                previous = map.remove(key);
                if (previous != null) {
                    size -= safeSizeOf(key, previous);
                }
            }
    
            if (previous != null) {
                entryRemoved(false, key, previous, null);
            }
    
            return previous;
        }
    

    相关文章

      网友评论

      本文标题:LruCache缓存使用

      本文链接:https://www.haomeiwen.com/subject/msniwftx.html