android轻量级开源缓存框架——ASimpleCache(ACache

ASimpleCache里只有一个JAVA文件——ACache.java

首先我用思维导图制作了ACache类的详细结构图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


二、官方demo分析

通过分析官方给的demo来驱动源码分析吧

以字符串存储为例(官方给的demo里给出了很多种数据读取的例子,其实方法相似),打开SaveStringActivity.java:


package com.yangfuhai.asimplecachedemo;



import org.afinal.simplecache.ACache;



import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.widget.EditText;

import android.widget.TextView;

import android.widget.Toast;



/** * * @ClassName: SaveStringActivity * @Description: 缓存string * @Author Yoson Hao * @WebSite www.haoyuexing.cn * @Email haoyuexing@gmail.com * @Date 2013-8-7 下午9:59:43 * */

public class SaveStringActivity extends Activity {



    private EditText mEt_string_input;

    private TextView mTv_string_res;



    private ACache mCache;



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_save_string);

        // 初始化控件

        initView();



        mCache = ACache.get(this);

    }



    /** * 初始化控件 */

    private void initView() {

        mEt_string_input = (EditText) findViewById(R.id.et_string_input);

        mTv_string_res = (TextView) findViewById(R.id.tv_string_res);

    }



    /** * 点击save事件 * * @param v */

    public void save(View v) {

        if (mEt_string_input.getText().toString().trim().length() == 0) {

            Toast.makeText(

                    this,

                    "Cuz u input is a nullcharacter ... So , when u press \"read\" , if do not show any result , plz don't be surprise",

                    Toast.LENGTH_SHORT).show();

        }

// mCache.put("testString", mEt_string_input.getText().toString());

        mCache.put("testString", mEt_string_input.getText().toString(),300);

    }



    /** * 点击read事件 * * @param v */

    public void read(View v) {

        String testString = mCache.getAsString("testString");

        if (testString == null) {

            Toast.makeText(this, "String cache is null ...", Toast.LENGTH_SHORT)

                    .show();

            mTv_string_res.setText(null);

            return;

        }

        mTv_string_res.setText(testString);

    }



    /** * 点击clear事件 * * @param v */

    public void clear(View v) {

        mCache.remove("testString");

    }



}




可以看到缓存字符串的读取方法很简单!!!

  1. 在onCreate里通过get方式获取缓存实例

    mCache = ACache.get(this);

  2. 在save按钮的点击事件里,通过put方法往缓存实例里保存字符串

    mCache.put(“testString”, mEt_string_input.getText().toString(),300);

  3. 在read按钮的点击事件里,通过getAsString方法从缓存实例里读取字符串

    mCache.getAsString(“testString”);

    其他数据读取,方法相似,也是这三个步骤。300为保存时间300秒。


三、ACache源码分析

1、获取缓存实例

那我们就从ACache.get()开始吧,其实查看上面的思维导图,ACache类的构造方法为private的,所以新建缓存实例只能通过ACache.get方式获取。


    //实例化应用程序场景缓存

    public static ACache get(Context ctx) {

        return get(ctx, "ACache");

    }

    //新建缓存目录

    public static ACache get(Context ctx, String cacheName) {

        //新建文件夹,文件路径为应用场景缓存路径目录,文件夹名为ACache(new File()也可新建文件,带上后缀即可)

        File f = new File(ctx.getCacheDir(), cacheName);    

        return get(f, MAX_SIZE, MAX_COUNT);

    }

    //新建缓存实例,存入实例map,key为缓存目录+每次应用开启的进程id

    public static ACache get(File cacheDir, long max_zise, int max_count) {

        //返回key为缓存目录+每次应用开启的进程id的map的value值,赋给缓存实例manager

        ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid());

        if (manager == null) { //缓存实例为空时,

            manager = new ACache(cacheDir, max_zise, max_count);

            mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager);//插入map

        }

        return manager;

    }



在调用ACache.get(Context)方法过程中,其实执行了三个get方法

(1)get(Context ctx)->(2)get(Context ctx, String cacheName)->(3)get(File cacheDir, long max_zise, int max_count)

在(2)中新建了缓存目录,路径为:

/data/data/app-package-name/cache/ACache

缓存大小MAX_SIZE和数量MAX_COUNT均由final变量控制。

其实最终调用(3)获取实例:

mInstanceMap的key为缓存目录+每次应用开启的进程id,value为ACache.

初次运行,mInstanceMap没有任何键值对,所以manager == null。故通过ACache构造方法构造新实例,最后将该实例引用存入mInstanceMap。

接下来我们来看看ACache构造方法:


    //ACache构造函数 为private私有(所以在其他类里获得实例只能通过get()方法)

    private ACache(File cacheDir, long max_size, int max_count) {

        if (!cacheDir.exists() && !cacheDir.mkdirs()) {     //缓存目录不存在并且无法创建时,抛出异常

            throw new RuntimeException("can't make dirs in " + cacheDir.getAbsolutePath());

        }

        mCache = new ACacheManager(cacheDir, max_size, max_count);//实例化ACacheManager内部类实例

    }



缓存目录不存在并且无法创建时,抛出异常,否则实例化ACacheManager内部类实例(缓存管理器)。ACacheManager内部类的构造函数如下:


        //内部类ACacheManager的构造函数

        private ACacheManager(File cacheDir, long sizeLimit, int countLimit) {

            this.cacheDir = cacheDir;

            this.sizeLimit = sizeLimit;

            this.countLimit = countLimit;

            cacheSize = new AtomicLong();       //原子类实例cacheSize,不用加锁保证线程安全

            cacheCount = new AtomicInteger();   //原子类实例cacheCount,不用加锁保证线程安全

            calculateCacheSizeAndCacheCount();

        }

构造函数得到原子类实例cacheSize和cacheCount,通过calculateCacheSizeAndCacheCount();方法计算cacheSize和cacheCount如下:


        /** * 计算 cacheSize和cacheCount */

        private void calculateCacheSizeAndCacheCount() {

            new Thread(new Runnable() {

                @Override

                public void run() {

                    //int size = 0;

                    long size = 0;  //这里long类型才对——by牧之丶

                    int count = 0;

                    File[] cachedFiles = cacheDir.listFiles();  //返回缓存目录cacheDir下的文件数组

                    if (cachedFiles != null) {

                        for (File cachedFile : cachedFiles) {   //对文件数组遍历

                            size += calculateSize(cachedFile);  

                            count += 1;

                            lastUsageDates.put(cachedFile, cachedFile.lastModified());  //将缓存文件和最后修改时间插入map

                        }

                        cacheSize.set(size);        //设置为给定值

                        cacheCount.set(count);      //设置为给定值

                    }

                }

            }).start();

        }

calculateCacheSizeAndCacheCount方法中开启线程进行大小和数量的计算。计算完后存入cacheSize和cacheCount,cacheSize和cacheCount在内部类中定义为AtomicLong和AtomicInteger量子类,也就是线程安全的。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入。


到这里获取缓存实例工作完成,主要完成了如下工作:

  1. 新建了缓存目录

  2. 通过ACache构造方法构造新实例,并且将该实例引用插入mInstanceMap

  3. 实例化ACacheManager,计算cacheSize和cacheCount


接下来就是数据存取操作。

2、往缓存实例存入数据

从上面的思维导图public method(各种数据的读写方法)中,有各种public的put和get等方法来缓存各种数据类型的数据。由上面的demo的put方法

mCache.put(“testString”, mEt_string_input.getText().toString(),300);我们找到原形:


    /** * 保存 String数据 到 缓存中 * * @param key * 保存的key * @param value * 保存的String数据 * @param saveTime * 保存的时间,单位:秒 */

    public void put(String key, String value, int saveTime) {

        put(key, Utils.newStringWithDateInfo(saveTime, value));

    }



这里的put方法可以指定缓存时间。调用他自身的另一个put方法:


/** * 保存 String数据 到 缓存中 * * @param key * 保存的key * @param value * 保存的String数据 */

    public void put(String key, String value) {

        File file = mCache.newFile(key);    //新建文件

        BufferedWriter out = null;          //缓冲字符输出流,作用是为其他字符输出流添加一些缓冲功能

        try {

            out = new BufferedWriter(new FileWriter(file), 1024);   //获取file字符流

            out.write(value);       // 把value写入

        } catch (IOException e) {

            e.printStackTrace();

        } finally {

            if (out != null) {

                try {

                    out.flush();        

                    out.close();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

            mCache.put(file);   //更新cacheCount和cacheSize lastUsageDates插入新建文件和时间的键值对

        }

    }

在put(String key, String value)方法中首先调用mCache.newFile(key)新建一个文件:


        //新建文件

        private File newFile(String key) {

            return new File(cacheDir, key.hashCode() + "");     //新建文件,文件名为key的整型哈希码

        }

新建的文件名为key的整型哈希码。回到put(String key, String value)中,然后通过out.write(value);将数据存入文件。最后调用mCache.put(file);进行ACacheManager实例的更新操作:


    //更新cacheCount和cacheSize lastUsageDates插入新建文件和时间的键值对

    //文件放入程序缓存后,统计缓存总量,总数,文件存放到文件map中(value值为文件最后修改时间,便于根据设置的销毁时间进行销毁)

    //缓存没有超过限制,则增加缓存总量,总数的数值

    //缓存超过限制,则减少缓存总量,总数的数值

    //通过removeNext方法找到最老文件的大小

        private void put(File file) {

            int curCacheCount = cacheCount.get();   //获取数量 

            while (curCacheCount + 1 > countLimit) {    //大于上限

                long freedSize = removeNext();          //移除旧的文件,返回文件大小

                cacheSize.addAndGet(-freedSize);        //更新cacheSize



                curCacheCount = cacheCount.addAndGet(-1);//更新cacheCount

            }

            cacheCount.addAndGet(1);//更新cacheCount



            long valueSize = calculateSize(file);       //计算文件大小

            long curCacheSize = cacheSize.get();        //获取当前缓存大小

            while (curCacheSize + valueSize > sizeLimit) {  //大于上限

                long freedSize = removeNext();              //移除旧的文件,返回文件大小

                curCacheSize = cacheSize.addAndGet(-freedSize);     //更新curCacheSize

            }

            cacheSize.addAndGet(valueSize);                         //更新cacheSize



            Long currentTime = System.currentTimeMillis();

            file.setLastModified(currentTime);          //设置文件最后修改时间

            lastUsageDates.put(file, currentTime);      //插入map

        }

分析完ACacheManager的put()后,我们回到put(key, Utils.newStringWithDateInfo(saveTime, value))

其中第二个参数value传入的是Utils.newStringWithDateInfo(saveTime, value),而newStringWithDateInfo是ACache的内部工具类的一个方法,在value内容前面加上了时间信息:


        //返回时间信息+value

        private static String newStringWithDateInfo(int second, String strInfo) {

            return createDateInfo(second) + strInfo;

        }

返回拼接createDateInfo(second)和value的字符串。createDateInfo()如下:


        //时间信息

        private static String createDateInfo(int second) {

            String currentTime = System.currentTimeMillis() + "";

            while (currentTime.length() < 13) {     //小于13,前面补0

                currentTime = "0" + currentTime;



**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/7c063c73b5e12ba6963e7927ef6dea15.png)
![img](https://img-blog.csdnimg.cn/img_convert/7cc662a483e41036073d23a5b2f76879.png)
![img](https://img-blog.csdnimg.cn/img_convert/76591d8c28b7e14f639cc8d183589e2e.png)
![img](https://img-blog.csdnimg.cn/img_convert/f428277bc57b799b2610fe45d4991326.png)
![img](https://img-blog.csdnimg.cn/img_convert/83dd09502c60462974b8560ddf9c264a.png)
![img](https://img-blog.csdnimg.cn/img_convert/e43f4a874c349c0151e5aa5704a3b2bf.png)
![img](https://img-blog.csdnimg.cn/13f2cb2e05a14868a3f0fd6ac81d625c.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)**
![img](https://img-blog.csdnimg.cn/img_convert/66c8683e586c61428c28915bfa38ab1f.png)



### 最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
![](https://img-blog.csdnimg.cn/img_convert/b03348b1e4a81ed5605770cf0b244942.webp?x-oss-process=image/format,png)

除此之外,前文所提及的Alibaba珍藏版 **Android框架体系架构** 手写文档以及一本 **《大话数据结构》** 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!



**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
![img](https://img-blog.csdnimg.cn/img_convert/05b786c67fc22695f55b424b115445ea.png)
uNh-1712779665288)]



### 最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
[外链图片转存中...(img-1JQP5KXy-1712779665288)]

除此之外,前文所提及的Alibaba珍藏版 **Android框架体系架构** 手写文档以及一本 **《大话数据结构》** 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!



**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
[外链图片转存中...(img-4rWtMXmf-1712779665289)]
  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Avro是一个轻量级的数据序列化框架,同时也提供了RPC功能。Avro提供了一个基于JSON的schema定义文件来描述数据结构,使得Avro能够支持动态的数据类型。Avro还提供了一个名为avro-rpc的模块,用于实现基于Avro的RPC。 下面我们来对avro-rpc进行性能测试。我们将使用Python 3.7作为客户端和服务端编程语言,并使用Apache Bench来进行压力测试。 首先,我们需要安装avro和avro-rpc模块: ``` pip install avro pip install avro-rpc ``` 接下来,我们编写一个简单的RPC服务端程序: ```python import avro.protocol import avro.ipc import socket PROTOCOL = avro.protocol.parse(open("test.avpr").read()) class RpcServer(object): def __init__(self, host, port): self.server = avro.ipc.HTTPServer(self.handle_request) self.server.add_listener((host, port)) def handle_request(self, request, protocol): message_name = request.message_name request_params = request.request_params print("Received request: {} {}".format(message_name, request_params)) if message_name == "ping": return "pong" elif message_name == "echo": return request_params else: raise avro.AvroRemoteException("Unknown message: {}".format(message_name)) def serve_forever(self): self.server.start() self.server.join() if __name__ == "__main__": server = RpcServer("localhost", 8080) server.serve_forever() ``` 这个RPC服务端程序会监听localhost的8080端口,并实现了两个RPC方法:ping和echo。当客户端调用ping方法时,服务端会返回字符串“pong”;当客户端调用echo方法时,服务端会返回客户端传递的参数。 接下来,我们编写一个简单的RPC客户端程序: ```python import avro.protocol import avro.ipc import socket PROTOCOL = avro.protocol.parse(open("test.avpr").read()) class RpcClient(object): def __init__(self, host, port): self.transceiver = avro.ipc.HTTPTransceiver((host, port)) self.requestor = avro.ipc.Requestor(PROTOCOL, self.transceiver) def ping(self): return self.requestor.request("ping", []) def echo(self, message): return self.requestor.request("echo", [message]) if __name__ == "__main__": client = RpcClient("localhost", 8080) print(client.ping()) print(client.echo("Hello, world!")) ``` 这个RPC客户端程序会连接到localhost的8080端口,并调用服务端实现的ping和echo方法。 接下来,我们使用Apache Bench来进行压力测试: ``` ab -n 10000 -c 10 http://localhost:8080/ ``` 这个命令会模拟10个并发连接,总共发送10000个请求。我们可以通过修改-n和-c参数来改变测试规模。 测试结果如下: ``` Server Software: Server Hostname: localhost Server Port: 8080 Document Path: / Document Length: 4 bytes Concurrency Level: 10 Time taken for tests: 7.194 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1830000 bytes HTML transferred: 40000 bytes Requests per second: 1390.36 [#/sec] (mean) Time per request: 7.194 [ms] (mean) Time per request: 0.719 [ms] (mean, across all concurrent requests) Transfer rate: 248.18 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.3 0 14 Processing: 1 7 2.3 7 24 Waiting: 1 7 2.3 7 24 Total: 1 7 2.3 7 24 Percentage of the requests served within a certain time (ms) 50% 7 66% 8 75% 8 80% 8 90% 10 95% 12 98% 15 99% 17 100% 24 (longest request) ``` 从测试结果中可以看出,avro-rpc在处理10000个请求时,平均每个请求处理时间为7.194毫秒。每秒处理请求数为1390.36,处理速度较快,而且没有出现失败的请求。因此,我们可以认为avro-rpc是一个性能良好的轻量级RPC框架

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值