Android DiskLruCache全然解析,硬盘缓存的最佳方案
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/28863651
概述
记得在非常早之前。我有写过一篇文章Android高效载入大图、多图解决方式,有效避免程序OOM,这篇文章是翻译自Android Doc的。其中防止多图OOM的核心解决思路就是使用LruCache技术。但LruCache仅仅是管理了内存中图片的存储与释放,假设图片从内存中被移除的话,那么又须要从网络上又一次载入一次图片,这显然非常耗时。对此,Google又提供了一套硬盘缓存的解决方式:DiskLruCache(非Google官方编写,但获得官方认证)。仅仅可惜。Android Doc中并没有对DiskLruCache的使用方法给出具体的说明。而网上关于DiskLruCache的资料也少之又少。因此今天我准备专门写一篇博客来具体解说DiskLruCache的使用方法,以及分析它的工作原理,这应该也是眼下网上关于DiskLruCache最具体的资料了。
那么我们先来看一下有哪些应用程序已经使用了DiskLruCache技术。在我所接触的应用范围里,Dropbox、Twitter、网易新闻等都是使用DiskLruCache来进行硬盘缓存的,其中Dropbox和Twitter大多数人应该都没用过,那么我们就从大家最熟悉的网易新闻開始着手分析,来对DiskLruCache有一个最初的认识吧。
初探
相信全部人都知道。网易新闻中的数据都是从网络上获取的,包括了非常多的新闻内容和新闻图片,例如以下图所看到的:
可是不知道大家有没有发现,这些内容和图片在从网络上获取到之后都会存入到本地缓存中,因此即使手机在没有网络的情况下依旧能够载入出曾经浏览过的新闻。
而使用的缓存技术不用多说。自然是DiskLruCache了,那么首先第一个问题,这些数据都被缓存在了手机的什么位置呢?
事实上DiskLruCache并没有限制数据的缓存位置。能够自由地进行设定。可是通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data/<application package>/cache 这个路径。选择在这个位置有两点优点:第一。这是存储在SD卡上的。因此即使缓存再多的数据也不会对手机的内置存储空间有不论什么影响,仅仅要SD卡空间足够就可以。第二,这个路径被Android系统认定为应用程序的缓存路径。当程序被卸载的时候。这里的数据也会一起被清除掉。这样就不会出现删除程序之后手机上还有非常多残留数据的问题。
那么这里还是以网易新闻为例,它的client的包名是com.netease.newsreader.activity。因此数据缓存地址就应该是 /sdcard/Android/data/com.netease.newsreader.activity/cache ,我们进入到这个目录中看一下,结果例如以下图所看到的:
能够看到有非常多个目录。由于网易新闻对多种类型的数据都进行了缓存,这里简单起见我们仅仅分析图片缓存就好。所以进入到bitmap目录其中。然后你将会看到一堆文件名称非常长的文件,这些文件命名没有不论什么规则,全然看不懂是什么意思,但假设你一直向下滚动。将会看到一个名为journal的文件。例如以下图所看到的:
那么这些文件究竟都是什么呢?看到这里相信有些朋友已经是一头雾水了,这里我简单解释一下。
上面那些文件名称非常长的文件就是一张张缓存的图片。每一个文件都相应着一张图片。而journal文件是DiskLruCache的一个日志文件。程序对每张图片的操作记录都存放在这个文件里。基本上看到journal这个文件就标志着该程序使用DiskLruCache技术了。
下载
好了,对DiskLruCache有了最初的认识之后,以下我们来学习一下DiskLruCache的使用方法吧。由于DiskLruCache并非由Google官方编写的,所以这个类并没有被包括在Android API其中。我们须要将这个类从网上下载下来,然后手动加入到项目其中。DiskLruCache的源代码在Google Source上,地址例如以下:
假设Google Source打不开的话,也能够点击这里下载DiskLruCache的源代码。
下载好了源代码之后,仅仅须要在项目中新建一个libcore.io包。然后将DiskLruCache.java文件拷贝到这个包中就可以。
打开缓存
这种话我们就把准备工作做好了,以下看一下DiskLruCache究竟该怎样使用。首先你要知道。DiskLruCache是不能new出实例的,假设我们要创建一个DiskLruCache的实例,则须要调用它的open()方法。接口例如以下所看到的:
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
open()方法接收四个參数。第一个參数指定的是数据的缓存地址。第二个參数指定当前应用程序的版本号号,第三个參数指定同一个key能够相应多少个缓存文件,基本都是传1,第四个參数指定最多能够缓存多少字节的数据。
其中缓存地址前面已经说过了,通常都会存放在 /sdcard/Android/data/<application package>/cache 这个路径以下,但同一时候我们又须要考虑假设这个手机没有SD卡,或者SD正好被移除了的情况,因此比較优秀的程序都会专门写一个方法来获取缓存地址,例如以下所看到的:
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
能够看到。当SD卡存在或者SD卡不可被移除的时候。就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir()方法来获取缓存路径。
前者获取到的就是 /sdcard/Android/data/<application package>/cache 这个路径,而后者获取到的是 /data/data/<application package>/cache 这个路径。
接着又将获取到的路径和一个uniqueName进行拼接。作为终于的缓存路径返回。那么这个uniqueName又是什么呢?事实上这就是为了对不同类型的数据进行区分而设定的一个唯一值,比方说在网易新闻缓存路径下看到的bitmap、object等目录。
接着是应用程序版本号号,我们能够使用例如以下代码简单地获取到当前应用程序的版本号号:
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
须要注意的是,每当版本号号改变,缓存路径下存储的全部数据都会被清除掉,由于DiskLruCache觉得当应用程序有版本号更新的时候。全部的数据都应该从网上又一次获取。
后面两个參数就没什么须要解释的了,第三个參数传1,第四个參数通常传入10M的大小就够了。这个能够依据自身的情况进行调节。
因此。一个非常标准的open()方法就能够这样写:
DiskLruCache mDiskLruCache = null;
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
首先调用getDiskCacheDir()方法获取到缓存地址的路径,然后推断一下该路径是否存在,假设不存在就创建一下。接着调用DiskLruCache的open()方法来创建实例,并把四个參数传入就可以。
有了DiskLruCache的实例之后,我们就能够对缓存的数据进行操作了,操作类型主要包括写入、訪问、移除等,我们一个个进行学习。
写入缓存
先来看写入,比方说如今有一张图片,地址是http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg。那么为了将这张图片下载下来,就能够这样写:
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
这段代码相当基础,相信大家都看得懂,就是訪问urlString中传入的网址,并通过outputStream写入到本地。
有了这种方法之后,以下我们就能够使用DiskLruCache来进行写入了,写入的操作是借助DiskLruCache.Editor这个类完毕的。相似地。这个类也是不能new的,须要调用DiskLruCache的edit()方法来获取实例,接口例如以下所看到的:
public Editor edit(String key) throws IOException
能够看到。edit()方法接收一个參数key,这个key将会成为缓存文件的文件名称,而且必须要和图片的URL是一一相应的。
那么怎样才干让key和图片的URL能够一一相应呢?直接使用URL来作为key?不太合适。由于图片URL中可能包括一些特殊字符,这些字符有可能在命名文件时是不合法的。事实上最简单的做法就是将图片的URL进行MD5编码。编码后的字符串肯定是唯一的,而且仅仅会包括0-F这种字符。全然符合文件的命名规则。
那么我们就写一个方法用来将字符串进行MD5编码,代码例如以下所看到的:
public String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
} private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
代码非常easy。如今我们仅仅须要调用一下hashKeyForDisk()方法。并把图片的URL传入到这种方法中,就能够得到相应的key了。
因此,如今就能够这样写来得到一个DiskLruCache.Editor的实例:
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
有了DiskLruCache.Editor的实例之后,我们能够调用它的newOutputStream()方法来创建一个输出流,然后把它传入到downloadUrlToStream()中就能实现下载并写入缓存的功能了。注意newOutputStream()方法接收一个index參数,由于前面在设置valueCount的时候指定的是1,所以这里index传0就能够了。在写入操作运行完之后。我们还须要调用一下commit()方法进行提交才干使写入生效,调用abort()方法的话则表示放弃此次写入。
因此。一次完整写入操作的代码例如以下所看到的:
new Thread(new Runnable() {
@Override
public void run() {
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
由于这里调用了downloadUrlToStream()方法来从网络上下载图片,所以一定要确保这段代码是在子线程其中运行的。注意在代码的最后我还调用了一下flush()方法,这种方法并非每次写入都必须要调用的,但在这里却必不可少。我会在后面说明它的作用。
如今的话缓存应该是已经成功写入了,我们进入到SD卡上的缓存目录里看一下,例如以下图所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3VvbGluX2Jsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
能够看到,这里有一个文件名称非常长的文件,和一个journal文件。那个文件名称非常长的文件自然就是缓存的图片了,由于是使用了MD5编码来进行命名的。
读取缓存
缓存已经写入成功之后,接下来我们就该学习一下怎样读取了。读取的方法要比写入简单一些。主要是借助DiskLruCache的get()方法实现的,接口例如以下所看到的:
public synchronized Snapshot get(String key) throws IOException
非常明显。get()方法要求传入一个key来获取到相应的缓存数据。而这个key毫无疑问就是将图片URL进行MD5编码后的值了,因此读取缓存数据的代码就能够这样写:
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
非常奇怪的是,这里获取到的是一个DiskLruCache.Snapshot对象。这个对象我们该怎么利用呢?非常easy。仅仅须要调用它的getInputStream()方法就能够得到缓存文件的输入流了。相同地,getInputStream()方法也须要传一个index參数,这里传入0就好。有了文件的输入流之后,想要把缓存图片显示到界面上就轻而易举了。
所以,一段完整的读取缓存。并将图片载入到界面上的代码例如以下所看到的:
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
mImage.setImageBitmap(bitmap);
}
} catch (IOException e) {
e.printStackTrace();
}
我们使用了BitmapFactory的decodeStream()方法将文件流解析成Bitmap对象,然后把它设置到ImageView其中。假设运行一下程序。将会看到例如以下效果:
OK。图片已经成功显示出来了。注意这是我们从本地缓存中载入的,而不是从网络上载入的,因此即使在你手机没有联网的情况下。这张图片仍然能够显示出来。
移除缓存
学习完了写入缓存和读取缓存的方法之后,最难的两个操作你就都已经掌握了。那么接下来要学习的移除缓存对你来说也一定非常轻松了。移除缓存主要是借助DiskLruCache的remove()方法实现的,接口例如以下所看到的:
public synchronized boolean remove(String key) throws IOException
相信你已经相当熟悉了,remove()方法中要求传入一个key。然后会删除这个key相应的缓存图片。演示样例代码例如以下:
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
mDiskLruCache.remove(key);
} catch (IOException e) {
e.printStackTrace();
}
使用方法尽管简单。可是你要知道。这种方法我们并不应该经常去调用它。由于你全然不须要操心缓存的数据过多从而占用SD卡太多空间的问题。DiskLruCache会依据我们在调用open()方法时设定的缓存最大值来自己主动删除多余的缓存。仅仅有你确定某个key相应的缓存内容已经过期,须要从网络获取最新数据的时候才应该调用remove()方法来移除缓存。
其他API
除了写入缓存、读取缓存、移除缓存之外,DiskLruCache还提供了另外一些比較经常使用的API。我们简单学习一下。
1. size()
这种方法会返回当前缓存路径下全部缓存数据的总字节数,以byte为单位,假设应用程序中须要在界面上显示当前缓存数据的总大小。就能够通过调用这种方法计算出来。
比方网易新闻中就有这样一个功能,例如以下图所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3VvbGluX2Jsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
2.flush()
这种方法用于将内存中的操作记录同步到日志文件(也就是journal文件)其中。这种方法非常重要。由于DiskLruCache能够正常工作的前提就是要依赖于journal文件里的内容。前面在解说写入缓存操作的时候我有调用过一次这种方法,但事实上并非每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来不论什么优点,仅仅会额外添加同步journal文件的时间。比較标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就能够了。
3.close()
这种方法用于将DiskLruCache关闭掉,是和open()方法相应的一个方法。关闭掉了之后就不能再调用DiskLruCache中不论什么操作缓存数据的方法。通常仅仅应该在Activity的onDestroy()方法中去调用close()方法。
4.delete()
这种方法用于将全部的缓存数据全部删除,比方说网易新闻中的那个手动清理缓存功能,事实上仅仅须要调用一下DiskLruCache的delete()方法就能够实现了。
解读journal
前面已经提到过。DiskLruCache能够正常工作的前提就是要依赖于journal文件里的内容,因此,能够读懂journal文件对于我们理解DiskLruCache的工作原理有着非常关键的数据。那么journal文件里的内容究竟是什么样的呢?我们来打开瞧一瞧吧,例如以下图所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3VvbGluX2Jsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
由于如今仅仅缓存了一张图片。所以journal中并没有几行日志,我们一行行进行分析。第一行是个固定的字符串“libcore.io.DiskLruCache”,标志着我们使用的是DiskLruCache技术。第二行是DiskLruCache的版本号号。这个值是恒为1的。第三行是应用程序的版本号号。我们在open()方法里传入的版本号号是什么这里就会显示什么。第四行是valueCount,这个值也是在open()方法中传入的,通常情况下都为1。第五行是一个空行。前五行也被称为journal文件的头,这部分内容还是比較好理解的,可是接下来的部分就要略微动点脑筋了。
第六行是以一个DIRTY前缀開始的,后面紧跟着缓存图片的key。通常我们看到DIRTY这个字样都不代表着什么好事情,意味着这是一条脏数据。
没错。每当我们调用一次DiskLruCache的edit()方法时,都会向journal文件里写入一条DIRTY记录,表示我们正准备写入一条缓存数据。但不知结果怎样。
然后调用commit()方法表示写入缓存成功。这时会向journal中写入一条CLEAN记录。意味着这条“脏”数据被“洗干净了”。调用abort()方法表示写入缓存失败。这时会向journal中写入一条REMOVE记录。也就是说,每一行DIRTY的key,后面都应该有一行相应的CLEAN或者REMOVE的记录,否则这条数据就是“脏”的,会被自己主动删除掉。
假设你足够细心的话应该还会注意到,第七行的那条记录。除了CLEAN前缀和key之外,后面另一个152313,这是什么意思呢?事实上,DiskLruCache会在每一行CLEAN记录的最后加上该条缓存数据的大小,以字节为单位。152313也就是我们缓存的那张图片的字节数了。换算出来大概是148.74K,和缓存图片刚刚好一样大,例如以下图所看到的:
前面我们所学的size()方法能够获取到当前缓存路径下全部缓存数据的总字节数,事实上它的工作原理就是把journal文件里全部CLEAN记录的字节数相加,求出的总合再把它返回而已。
除了DIRTY、CLEAN、REMOVE之外,另一种前缀是READ的记录,这个就非常easy了,每当我们调用get()方法去读取一条缓存数据时,就会向journal文件里写入一条READ记录。因此,像网易新闻这种图片和数据量都非常大的程序,journal文件里就可能会有大量的READ记录。
那么你可能会操心了。假设我不停频繁操作的话,就会不断地向journal文件里写入数据。那这样journal文件岂不是会越来越大?这倒不必操心。DiskLruCache中使用了一个redundantOpCount变量来记录用户操作的次数。每运行一次写入、读取或移除缓存的操作,这个变量值都会加1,当变量值达到2000的时候就会触发重构journal的事件,这时会自己主动把journal中一些多余的、不必要的记录全部清除掉。保证journal文件的大小始终保持在一个合理的范围内。
好了,这种话我们就算是把DiskLruCache的使用方法以及简要的工作原理分析完了。至于DiskLruCache的源代码还是比較简单的, 限于篇幅原因就不在这里展开了,感兴趣的朋友能够自己去摸索。下一篇文章中,我会带着大家通过一个项目实战的方式来更加深入地理解DiskLruCache的使用方法。
感兴趣的朋友请继续阅读 Android照片墙完整版,完美结合LruCache和DiskLruCache 。
blockquote{
border-left: 10px solid rgba(128,128,128,0.075);
background-color: rgba(128,128,128,0.05);
border-radius: 0 5px 5px 0;
padding: 15px 20px;
}
关注我的技术公众号,每天都有优质技术文章推送。
关注我的娱乐公众号。工作、学习累了的时候放松一下自己。
微信扫一扫下方二维码就可以关注:
Android DiskLruCache全然解析,硬盘缓存的最佳方案的更多相关文章
- Android DiskLruCache 源代码解析 硬盘缓存的绝佳方案
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47251585: 本文出自:[张鸿洋的博客] 一.概述 依然是整理东西.所以最近 ...
- Android DiskLruCache完全解析,硬盘缓存的最佳方案
Android DiskLruCache完全解析,硬盘缓存的最佳方案 概述 记得在很早之前,我有写过一篇文章Android高效加载大图.多图解决方案,有效避免程序OOM,这篇文章是翻译自Andro ...
- Android DiskLruCache完全解析,硬盘缓存的最佳方案(转)
概述 记得在很早之前,我有写过一篇文章<Android高效加载大图.多图解决方案,有效避免程序OOM>,这篇文章是翻译自Android Doc的,其中防止多图OOM的核心解决思路就是使用L ...
- (转)Android DiskLruCache完全解析,硬盘缓存的最佳方案
摘自:http://blog.csdn.net/guolin_blog/article/details/28863651 转载请注明出处: http://blog.csdn.net/guolin_bl ...
- Android DiskLruCache完全解析,硬盘缓存的最佳方案 --转载
概述 记得在很早之前,我有写过一篇文章 Android高效加载大图.多图解决方案,有效避免程序OOM,这篇文章是翻译自Android Doc的,其中防止多图OOM的核心解决思路就是使用LruCache ...
- 硬盘缓存的最佳方案,DiskLruCache完全解析
收藏自:http://blog.csdn.net/guolin_blog/article/details/28863651
- Android DiskLruCache 源码解析 硬盘缓存的绝佳方案
一.概述 依旧是整理东西,所以近期的博客涉及的东西可能会比较老一点,会分析一些经典的框架,我觉得可能也是每个优秀的开发者必须掌握的东西:那么对于Disk Cache,DiskLruCache可以算佼佼 ...
- Android ActionBar全然解析,使用官方推荐的最佳导航栏(上)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/18234477 本篇文章主要内容来自于Android Doc.我翻译之后又做了些加工 ...
- Android IntentService全然解析 当Service遇到Handler
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47143563: 本文出自:[张鸿洋的博客] 一 概述 大家都清楚.在Andro ...
随机推荐
- P3507 [POI2010]GRA-The Minima Game
题目描述 Alice and Bob learned the minima game, which they like very much, recently. The rules of the ga ...
- PHP高手进阶-LAMPer技能树
- 实现人脸识别性别之路---opencv
import cv2from make_imge import get_file_namefrom train_ph import Modelimport os IMAGE_SIZE = 128if ...
- 推荐《深入浅出深度学习原理剖析与python实践》PDF+代码
<深入浅出深度学习原理剖析与Python实践>介绍了深度学习相关的原理与应用,全书共分为三大部分,第一部分主要回顾了深度学习的发展历史,以及Theano的使用:第二部分详细讲解了与深度学习 ...
- 学习《TensorFlow实战Google深度学习框架 (第2版) 》中文PDF和代码
TensorFlow是谷歌2015年开源的主流深度学习框架,目前已得到广泛应用.<TensorFlow:实战Google深度学习框架(第2版)>为TensorFlow入门参考书,帮助快速. ...
- c++中sizeof()的用法介绍
1. 定义 sizeof是一个操作符(operator). 其作用是返回一个对象或类型所占的内存字节数. 2. 语法 sizeof有三种语法形式: 1) sizeof (obje ...
- CDQZ 0003:jubeeeeeat
0003:jubeeeeeat 查看 提交 统计 提问 总时间限制: 1000ms 内存限制: 256000kB 描述 众所周知,LZF很喜欢打一个叫Jubeat的游戏.这是个音乐游戏,游戏界面是 ...
- web api 特点
webapi有很多特点(我不想用优点这个词),比如说restful,支持路由,简单,类似mvc controller/action的代码编写方式,灵活的托管方式,和web的集成等等. Web API的 ...
- 运动识别之HOJ3D和HMM
http://cvrc.ece.utexas.edu/Publications/Xia_HAU3D12.pdf View Invariant Human Action Recognition Us ...
- Es5正则
##JSON(ES5) 前端后台都能识别的数据.(一种数据储存格式) XNL在JSON出来前 JSON不支持 undefinde和函数. 示列:let = '[{"useername&qu ...