飞了起来 发布的文章

讲真,网上有很多讲关于这个的文章。我这篇只是梳理下,巩固下自己的知识。以前学习图片缓存框架的时候,接触过Lru,只知道这是一个帮助减少内存开销的方法。每次他确实是缓存的一种策略。在古老的手机只有512m的年代,手机内存是需要很节省点的(不像现在动辄就是6gb内存),虽说我看到这个策略出现的时间Android 3.1的年代,但是其中的原理是万变不离其宗的。

对了,本文基于老的LruCache研究的。新版的linkedHashMap方法有些不存在了,比如LinkedHashMap的eldest()方法。

LruCache需要分开理解。L-least r-recently u-used。结合起来就是 最近最少使用的缓存(算法)。
里面的原理主要就是靠一个linkedHashMap去维持一个栈。而这个栈不像Activity那种栈,先进后出这种。而是根据使用的情况和谁先进栈的情况综合考虑。文字描述可能有点头疼,一图流表示如下。
请输入图片描述

linkedHashMap是HashMap的子类,是一种双向链表,其构造方法的第二个参数是扩容用的(0.75标识当前容量快到0.75)第三个参数决定了是否是Lru顺序还是插入顺序。
这段不理解不要紧。你指看到创建linkedHashMap的时候,第三个参数是基于访问顺序,如果是true,就是Lru,false是不按照此顺序。

    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);
    }


     public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

但是使用LinkedHashMap的时候,使用方法是跟hashMap是没啥区别的。只是他内部方法,帮你完成了他的特定的顺序结构罢了。

  1. get方法:
    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++;
        }
        
        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;
        }
    }

hitCount代表命中次数 missCount代表丢失次数。
可以看到get()方法是有synchronized 锁住的,所以Lru是线程安全的,然后先从linkedHashMap中通过key去获取value,如果存在,命中次数+1;反之,丢失次数+1。并且create()一个新的null返回回去。

  1. put方法:
    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;
    }

这段代码意思是,如果不为null,put数加1,缓存空间数量+加上插入的缓存空间,并尝试往linkedHashMap插入数据,如果返回value不为null,则表示链表里面存在当前的key、value,缓存空间还原回未加之前。如果自己实现了entryRemoved方法,则会处理一遍。全部完成后,会去重新trimToSize()调整缓存大小。

有些人看源码会看到,哎哎哎,这个sizeOf返回的是1。怎么回事缓存空间大小呢?
因为safeSizeOf(key,value)-----> sizeOf(key,value),而sizeOf 是我们使用的时候会去重写的方法,所以会正确的返回value的大小。
  1. trimToSize方法:
    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();高版本找不到了,替换为下面的。
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
            entryRemoved(true, key, value, null);
        }
    }

这段表明trimTosize方法是个死循环,只有两个条件可以跳出。要么当前size比最大的缓存还大,要么就是链表的map已经没有存在任何键值对了。否则,就会去找链表中的最老的那条键值对,删除它,并且减去相应的空间缓存大小。
接着循环,看是否满足跳出的条件,直到满足条件。






- 阅读剩余部分 -

模板模式其实我们经常用,但是因为代码层面看起来很简单,我们不知道这是模板模式(是的,没读设计模式之前,我不知道-_-!)。

模板方法模式的定义:

定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义。

举个例子,笔记本蓝天模具场是个挺厉害的OEM厂。很多具体牌子的厂商会去买他的模具(自研开模比较贵),例如神舟,炫龙,外星人等等。但这些牌子有响当当的,也有传说中的小米都干不过的(神船)。所以他们需要在具体的笔记本配置上增删一些东西,增加自己的品牌和利润等等。蓝天就相当于模板,是抽象的;笔记本壳子,笔记本键盘,主板配置是各大厂商自己去配置的,是具体的笔记本了。

图例如下:
请输入图片描述

其中只是简单的运用了继承机制,但却也十分广泛运用。所以引申出来一个抽象模板。
其中分为两种方法:模板方法(一般final修饰,防止篡改) 和 基本方法(子类实现)。

用代码展现的话如下

/**
 * OEM工厂 这里工厂不是工厂模式的工厂只是厂商的意思,这里是OEM提供的模板
 */
public abstract class OEMFactory {
    protected abstract void addCPU();

    protected abstract void addKeyBoard();

    protected abstract void addShell();

    protected void makeLaplop(){
        this.addCPU();
        this.addKeyBoard();
        this.addShell();
    }


}

先写工厂模式之前,说说一个有趣的观点。曾经彩笔的我,刚踏上程序员之路的时候,只会单例、工厂模式。某次,在和某人讨论的时候,某人直接说工厂模式不需要,存在是没意义的,于是这段话就在我幼小的心灵中种下了一颗种子(主要是当时懒,觉得反驳不来,于是忘记了)。最近重新的梳理的时候,又想起了这个事,于是搜索了下,发现b乎上有大牛总结的很好,直接拖过来。

JAVA工厂方法的疑问(为何要创建工厂类或者工厂接口)? - 胖胖的回答 - 知乎

大意就是工厂模式的关键是模式,属于创建者模式。而创造者模式,最大的特征就是将“定义过程”和“实例化过程”分离。如果只是简单的用传参来重载方法完成,一旦功能变得复杂,代码重复率就会越来越多,代码也会庞大。但是换成工厂模式就不会。

参考设计模式之禅的工厂模式,女娲造人造黑种人,白种人,黄种人。(ps:原书作者写的很有意思,烤焦了变黑人,烤白了,经验不足,黄种人,正好完美,2333333)。用八卦炉去造人,下达的命令不是我要去造一个具体细节的人种(例如跑得飞快,头发颜色焦炭的黑人),而是直接说命令早黑种人,就会生成一个黑种人。
定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

通用工厂类图

工厂模式的优点:

  • 良好的封装性,代码结构清晰。一个对象的创建是有条件约束的,比如一个调用者需要一个小白车,只需要知道小白车的图纸的名称,不用知道小白车是如何将轮子,将油漆等等创建出来的,降低模块的耦合性。
  • 扩展性非常优秀,需要需要小黄车,只需要继承汽车产品,再去再添加一个小黄车的具体产品的实现方案就行了。
  • 屏蔽产品类。这一特点非常重要。比如android中下载一张图片,我不管你是用httpUrlConnection还是okhttp去实现。这些细节对我不重要,我只需要知道你能给我下载图片就行了。具体用哪个看情况就是了,而且切换无需修改实现代码。
  • 工厂模式是典型的解耦框架,高层模块只需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要就不用交流,也符合依赖倒置原则,只依赖产品类的抽象;当然符合里氏替换原则原则,使用产品子类替换父类。

工厂模式的扩展:

  1. 简单工厂模式
    一个模块只需要一个工厂类,直接去除抽象的工厂,并且不需要产生,直接静态的方法就可以实现了。

优点,比标准版调用简单。
缺点,工厂类的扩展就比较困难了。

  1. 多个工厂模式
    一个产品对应一个工厂类,每个工厂类就负责创建对应的产品对象,非常符合单一职责原则。

优点,创建类职责清晰,结构简单。
缺点,可扩展性和可维护性破坏了,每次扩展都需要创建一个产品类,就需要一个工厂类,增加扩展的复杂度。


扩展

  1. 替代单例模式
    通过工厂模式在内存中生产一个对象。通过反射获取当前不允许new 对象的类的对象,获取其中的方法。然后设置访问权限,但是要注意保存内存中的对象唯一,不能多次反射获取。
  2. 延迟初始化
    一个对象消费完毕后,并不立刻释放,工厂类保持初始状态,等待再此使用。用map缓存创建好的工厂,如果下次再调用,查询map中是否拥有,有就直接取出生产产品。

多用于限制一个产品类的最大实例化数量,或者对象初始化比较复杂的情况下,通过延迟加载降低对象的产生和销毁带来的复杂性。

参考书籍
设计模式之禅

单例模式

一般分为 饿汉式 懒汉式 线程不安全的 线程安全的 以及内部类形式的单例。

为什么使用单例?好处呢?

因为单例在内存中只有一个实例,减少内存的开销。主要一般适用于一个对象需要频繁的创建、销毁,而且无法优化创建和销毁时的性能。避免对资源的多重占用。可以全局调用。

缺点?

  • 单例模式一般没有接口,扩展很困难,基本都是修改代码实现。
  • 与单一指责原则冲突。
  • 对测试不利
  1. 饿汉式 一步到位 类初始化时,就实例化instance

    private static SingletonDesign instance = new SingletonDesign();

    private SingletonDesign(){

    }
    public static SingletonDesign getInstance(){
        return instance;
    }
  1. 懒汉式 (线程不安全的)为什么不安全,是因为如果多个线程同时创建单例,则会失效,可能创建多个instance。
private static SingletonDesign instance = null;

    private SingletonDesign(){

    }
    public static SingletonDesign getInstance(){
        if (instance == null){
            instance = new SingletonDesign();
        }
        return instance;
    }

3 懒汉式(线程安全的)但是因为加了synchronized 所以效率低。

private static SingletonDesign instance = null;

    private SingletonDesign(){

    }

    public synchronized static SingletonDesign getInstance(){
        if (instance == null){
            instance = new SingletonDesign();
        }
        return instance;
    }

4 静态内部类 比饿汉式效率稍微高点,因为这种开始并不一定会获取instance,而是去调用getInstance时候才去实例化instance

private static class Singletonholder{
        private static final SingletonDesign instance= new SingletonDesign();


    }
    private SingletonDesign(){

    }
    public static final SingletonDesign getInstance(){
        return Singletonholder.instance;
    }

5 双重锁单例 懒汉线程安全版的升级

private volatile static SingletonDesign instance = null;

    private SingletonDesign(){

    }

    public  static SingletonDesign getInstance(){
        if (instance == null){
            synchronized (SingletonDesign.class){
                if (instance == null){
                    instance = new SingletonDesign();
                }
            }

        }
        return instance;
    }

设计模式之禅

常见的几种单例模式