韩奇峰高级讲师
多年实战工作经验曾参与制作宝马Usage Training项目、DMS项目,奥迪全
息投影项目,奔驰等多家汽车门户行业大型项目,负责UI设计、界面设计、3D模型制作、前端开发等职务。
从事设计行业多年,精通PhotoShop、UI设计、AfterEffects、Flash、
Actionscript、HTML、CSS、JavaScript、jQuery、资深动画设计师,设计作品曾获得全国动画设计三等奖。
课程讲解注重实战应用,对讲述知识点穿插案例制作,使课程内容更加接近
工作中实际的项目。授课风格注重实战经验分析,深受学生喜欢。
培训Java与自学Java的差距
我以前也是自学Java,在一家公司跟着别人学,以前是别人眼中的菜鸟,现
在是别人眼中的大神,Java很简单的,贵在坚持和多练,没必要花那培训钱。如果真的要去学的话,
选择Java培训机构要注意这两点基本上就能避免一些坑:
1. 老师没有正经公司工作经历,或者没有已经在线上正常运转的产品。一
些所谓培训班的老师水平往往比较一般,甚至还有培训出来后又接着培训别人的。
2、是不是会承诺帮你找到工作,要找到好的工作,不是靠别人给你保证的
,还是要靠自己提升能力。
建议多自己学习加上找些好的代码主动学习。例如github,多练习网上很多
网站里真正好的代码。作为Java工程师,可以多看看spring文档,看看很多已经成熟的框架,深入去体会。另外,学软件等等**好还是自己多学,找点
视频教程之类,也省点钱。
Java开发体系结构介绍 :
1、类加载器:为程序的执行加载所需要的全部类。类加载器将本地文件系
统的类名空间与来自远程网络源的类名空间相分离,本地类总是首先被加载,以增加安全性。当全部类被加载后,可执行文件的存储器格式被确定。这
时,特定的存储器地址被分配给符号引用并创建检索表格。由于存储器格式在运行时出现,因而Java解释器增加了保护以防止对限制代码区的非法进入
。
2、字节代码校验器:基于代码的规范包括语法语义的检查以及如上所述的
安全性检查。
3、Java运行时解释器:它是JVM的核心内容,实现把抽象的字节码指令映射
到本地系统平台下的库引用或指令。
4、API类库:实现标准Java平台API的一系列可执行代码。
5、硬件本地平台接口:提供对底层系统平台资源库调用的接口。
DiskLruCache源码分析
>
简介
上篇文章介绍了LRUCache它的思想是把一部分常用的对象存在内存里,以便下次使用的时候快速提取。
但是内存容量也就三G两G的,早期的或者低端一点的也就几百M,能分给自己的APP用来缓存数据的空间实在不多。
但是别忘了,我们还有Disk这个后花园。磁盘缓存的速度虽然不然不及内存缓存,但是容量很大,是典型的用时间换空间思想。
简单使用
DiskLruCache mDiskLruCache = DiskLruCache.open(directory, appVersion, valueCount, maxSize);
String key = generateKey(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OuputStream os = editor.newOutputStream(0);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
}
valueCount 是每一个key对应的value文件有几个
Journal文件
Journal是一个日志文件,一个典型的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
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
前五行组成了日志文件的文件头
**行 固定字符串libcore.io.DiskLruCache,也就是文件的魔数。
第二行 是DiskLruCache的版本号,源码中为常量1
第三行 是app的版本号,自己在open方法里传入
第四行 valueCount,每个key对应几个缓存文件
第五行 空行
接下来看起来乱乱的就是一条条操作记录了,
每一行由状态、key、如果key对应多个缓存文件且它是CLEAN的会依次列出缓存文件的大小。
DIRTY表示这个entry正在被写入,如果写入成功后边会跟一条CLEAN记录。
如果写入失败,后边会跟一条REMOVE记录。
CLEAN 表示缓存写好了,后边还会跟多个缓存文件的长度
READ get方法get一次,也就是读取一次,就写一个READ
REMOVE 删除之后写
数据结构
lruEntries里存储了key和Entry,Entry里存储了关于文件的信息。
key和文件的状态存储在日志文件里,和日志文件同级的的真正缓存的文件。
这样lruEntries放在内存里,存储所有关于缓存的信息,同LRUCache一样,它也是LinkHashMap
所以也实现了LRU算法,在达到内存上限之后,删除掉近期**少使用的文件。
open
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// PRefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
return cache;
} catch (IOException journalIsCorrupt) {
cache.delete();
}
}
// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
如果journal的备份文件已经存在,就去new一个journal文件,如果journal文件也已经存在,那么就删除备份,
如果journal文件不存在,那么就将备份文件变成journal文件,也就是重命名。
如果journal的备份文件不存在,那么说明该路径没有任何缓存,那么就创建一个空的cache,并调用rebuildJournal方法
rebuildJournal方法
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
Writer writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
try {
writer.write(MAGIC);
writer.write("\n");
writer.write(VERSION_1);
writer.write("\n");
writer.write(Integer.toString(appVersion));
writer.write("\n");
writer.write(Integer.toString(valueCount));
writer.write("\n");
writer.write("\n");
for (Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.write(DIRTY entry.key \n );
} else {
writer.write(CLEAN entry.key entry.getLengths() \n );
}
}
} finally {
writer.close();
}
if (journalFile.exists()) {
renameTo(journalFile, journalFileBackup, true);
}
renameTo(journalFileTmp, journalFile, false);
journalFileBackup.delete();
journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
}
创建journalFileTmp文件,并将其前5行写好。遍历lruEntries并将Entry当前的状态写入日志文件。
如果journalFile已经存在,那么就做个备份。
并将刚才的临时日志文件journalFileTmp重命名为日志文件journalFile。
然后删除备份日志文件。
为什么这么做呢?
readJournal
如果journalFile存在,那么首先调用了readJournal方法
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" magic ", " version ", "
valueCountString ", " blank "]");
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount ;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
// If we ended on a truncated line, rebuild the journal before appending to it.
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), Util.US_ASCII));
}
} finally {
Util.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf( );
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " line);
}
int keyBegin = firstSpace 1;
int secondSpace = line.indexOf( , keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " line);
}
}
进来之后首先校验文件格式对不对,也就是验证是不是标准的日志文件。
然后调用了readJournalLine方法,我们分析过日志文件它确实是每个记录单独一行。
首先从日志文件中把key读出来。
如果当前key对应的记录是REMOVE,那么就从lruEntries中remove掉。
如果lruEntries中还没有此条记录,那么就new一个Entry放进去。
如果是CLEAN状态,说明没有对象在操作这条记录。
如果是DIRTY状态,说明正在被编辑,那么给它赋值一个编辑器。
如果是READ状态,那么什么都不操作。
这样内存中的lruEntries就整体把控了当前缓存的情况。
**后,读取过程中如果发现journal文件有问题,则重建journal文件。
没有问题的话,初始化journalWriter,并关闭reader。
然后在open方法中又调用了processJournal方法
经过以上调用,之后journal文件、lruEntries、以及size就都初始化好了。
写入缓存
一开始的时候写缓存是这样调用的
String key = generateKey(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OuputStream os = editor.newOutputStream(0);
也就是先调用了edit方法
edit
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Snapshot is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.write(DIRTY key \n );
journalWriter.flush();
return editor;
}
首先检查journalWriter有没有关闭以及key字符的合法性
然后去lruEntries里查找key对应的Entry,如果有就直接使用,如果没有就new一个。
然后new一个Editor给这个Entry
并在日志文件中写入DIRTY标志,标识此Entry正在被编辑。
**后返回这个Editor
然后调用了Editor的newOutputStream方法,拿到一个输出流。
newOutputStream
public OutputStream newOutputStream(int index) throws IOException {
if (index < 0 || index >= valueCount) {
throw new IllegalArgumentException("Expected index " index " to "
"be greater than 0 and less than the maximum value count "
"of " valueCount);
}
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.getDirtyFile(index);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e) {
// Attempt to recreate the cache directory.
directory.mkdirs();
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e2) {
// We are unable to recover. Silently eat the writes.
return NULL_OUTPUT_STREAM;
}
}
return new FaultHidingOutputStream(outputStream);
}
}
创建一个输出流,并将流写入dirtyFile这个临时文件里。
dirtyFile**Entry的getDirtyFile创建,它的命名规则是key.index.tmp
**输出流写完文件之后,调用commit方法。
commit
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
completeEdit
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}
// If this edit is creating the entry for the first time, every index must have a value.
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i ) {
if (!editor.written[i]) {
editor.abort();
throw new IllegalStateException("Newly created entry didn t create value for index " i);
}
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
return;
}
}
}
for (int i = 0; i < valueCount; i ) {
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength newLength;
}
} else {
deleteIfExists(dirty);
}
}
redundantOpCount ;
entry.currentEditor = null;
if (entry.readable | success) {
entry.readable = true;
journalWriter.write(CLEAN entry.key entry.getLengths() \n );
if (success) {
entry.sequenceNumber = nextSequenceNumber ;
}
} else {
lruEntries.remove(entry.key);
journalWriter.write(REMOVE entry.key \n );
}
journalWriter.flush();
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
如果成功
把dirtyFile变成cleanFile,并重新计算size。
然后把这个entry设置为可读,并写入一条CLEAN的操作日志。
如果失败
就把dirtyFile删除,然后把它从lruEntries中移除,并写入一条REMOVE的操作日志。
这样entry文件写好了,日志也写好了,这次缓存也就做好了。
读取
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
}
那么首先看get方法
public synchronized Snapshot get(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
InputStream[] ins = new InputStream[valueCount];
try {
for (int i = 0; i < valueCount; i ) {
ins[i] = new FileInputStream(entry.getCleanFile(i));
}
} catch (FileNotFoundException e) {
// A file must have been deleted manually!
for (int i = 0; i < valueCount; i ) {
if (ins[i] != null) {
Util.closeQuietly(ins[i]);
} else {
break;
}
}
return null;
}
redundantOpCount ;
journalWriter.append(READ key \n );
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
}
同样是检查journalWriter有没有关闭,以及key的合法性。
然后new出valueCount个输入流。
向日志文件中写入一条READ日志。
**后封装一个Snapshot返回
拿到输入流,就可以读入文件了。
相关推荐:
苏州JAVA培训 苏州JAVA培训班 苏州JAVA培训机构