CallteFoot's blog

Victory belongs to the most persevering


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

LeastRecentlyUsed_最近最少使用

发表于 2017-04-26 | 分类于 Java

  LRU是Least Recently Used 近期最少使用算法,DiskLruCache是通过一个记录文件记录操作过程,从而保存各种文件,记录文件格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

  文件内容解析:

  1. 第一行固定字符串内容,没有多大意义;
  2. 第二行DiskLruCache的版本好,源码常量1;
  3. 第三行为app的版本好,这个可以自己传入;
  4. 第四行值得是每个key对应几个文件,一般为1;
  5. 空行做分割下面内容用;
  6. DIRTY 表示一个正在被写入(其实就是吧文件的outputstream交给你),写入分我两种情况:成功则写入一行CLEAN的记录;失败则写入一条REMOVE的记录,如果一条DIRTY没有一条CLEAN或REMOVE和它对应,那么这条记录就可以删除;
  7. REMOVE除了上述情况,当自己手动调用删除remover(key)方法也会写入一条记录;
  8. READ 说明有一次读取的记录;
  9. Clean的后面记录了文件的长度,和key关联了多少个文件;
      这里看出,只有CLEAN且没有REMOVE的记录,才是真正可用的Cache Entry记录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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) {
System.out
.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}

// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}

  验证记录文件是否存在,首先检查存不存在journal.bkp(journal的备份文件)如果存在:然后检查journal文件是否存在,如果正主在,bkp文件就可以删除了;如果不存在,将bkp文件重命名为journal文件。
接下里判断journal文件是否存在,

  • 如果不存在创建directory;重新构造disklrucache;调用rebuildJournal建立journal文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    /**
    * Creates a new journal that omits redundant information. This replaces the
    * current journal if it exists.
    */
    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));
    }

  可以看到首先构建一个journal.tmp文件,然后写入文件头(5行),然后遍历lruEntries(lruEntries = new LinkedHashMap(0, 0.75f, true);),当然我们这里没有任何数据。接下来将tmp文件重命名为journal文件。

  • 如果已经存在,那么调用readJournal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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);
}
}

  首先校验文件头,接下来调用readJournalLine按行读取内容。我们来看看readJournalLine中的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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);
}
}

  大家可以回忆下:每个记录至少有一个空格,有的包含两个空格。首先,拿到key,如果是REMOVE的记录呢,会调用lruEntries.remove(key);如果不是REMOVE记录,继续往下,如果该key没有加入到lruEntries,则创建并且加入。接下来,如果是CLEAN开头的合法记录,初始化entry,设置readable=true,currentEditor为null,初始化长度等。如果是DIRTY,设置currentEditor对象。如果是READ,那么直接不管。ok,经过上面这个过程,大家回忆下我们的记录格式,一般DIRTY不会单独出现,会和REMOVE、CLEAN成对出现(正常操作);也就是说,经过上面这个流程,基本上加入到lruEntries里面的只有CLEAN且没有被REMOVE的key。好了,回到readJournal方法,在我们按行读取的时候,会记录一下lineCount,然后最后给redundantOpCount赋值,这个变量记录的应该是没用的记录条数(文件的行数-真正可以的key的行数)。

最后,如果读取过程中发现journal文件有问题,则重建journal文件。没有问题的话,初始化下journalWriter,关闭reader。
readJournal完成了,会继续调用processJournal()这个方法内部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}

统计所有可用的cache占据的容量,赋值给size;对于所有非法DIRTY状态(就是DIRTY单独出现的)的entry,如果存在文件则删除,并且从lruEntries中移除。此时,剩的就真的只有CLEAN状态的key记录了。
  到此就初始化完毕了,太长了,根本记不住,我带大家总结下上面代码。根据我们传入的dir,去找journal文件,如果找不到,则创建个,只写入文件头(5行)。 如果找到,则遍历该文件,将里面所有的CLEAN记录的key,存到lruEntries中。这么长的代码,其实就两句话的意思。经过open以后,journal文件肯定存在了;lruEntries里面肯定有值了;size存储了当前所有的实体占据的容量;

存入缓存
存入操作:

1
2
3
4
5
String key = generateKey(url);  
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OuputStream os = editor.newOutputStream(0);
//...after op
editor.commit();

首先就是editor方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
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;
}

&nesp;&nesp;首先验证key,可以必须是字母、数字、下划线、横线(-)组成,且长度在1-120之间。然后通过key获取实体,因为我们是存,只要不是正在编辑这个实体,理论上都能返回一个合法的editor对象。所以接下来判断,如果不存在,则创建一个Entry加入到lruEntries中(如果存在,直接使用),然后为entry.currentEditor进行赋值为new Editor(entry);,最后在journal文件中写入一条DIRTY记录,代表这个文件正在被操作。

注意,如果entry.currentEditor != null不为null的时候,意味着该实体正在被编辑,会retrun null ;拿到editor对象以后,就是去调用newOutputStream去获得一个文件输入流了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
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);
}
}

首先校验index是否在valueCount范围内,一般我们使用都是一个key对应一个文件所以传入的基本都是0。接下来就是通过entry.getDirtyFile(index);拿到一个dirty File对象,为什么叫dirty file呢,其实就是个中转文件,文件格式为key.index.tmp。
将这个文件的FileOutputStream通过FaultHidingOutputStream封装下传给我们。
最后,别忘了我们通过os写入数据以后,需要调用commit方法。

1
2
3
4
5
6
7
8
9
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}

首先通过hasErrors判断,是否有错误发生,如果有调用completeEdit(this, false)且调用remove(entry.key);。如果没有就调用completeEdit(this, true);。

那么这里这个hasErrors哪来的呢?还记得上面newOutputStream的时候,返回了一个os,这个os是FileOutputStream,但是经过了FaultHidingOutputStream封装么,这个类实际上就是重写了FilterOutputStream的write相关方法,将所有的IOException给屏蔽了,如果发生IOException就将hasErrors赋值为true.

这样的设计还是很nice的,否则直接将OutputStream返回给用户,如果出错没法检测,还需要用户手动去调用一些操作。

接下来看completeEdit方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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);
}
}

  首先判断if (success && !entry.readable)是否成功,且是第一次写入(如果以前这个记录有值,则readable=true),内部的判断,我们都不会走,因为written[i]在newOutputStream的时候被写入true了。而且正常情况下,getDirtyFile是存在的。
  接下来,如果成功,将dirtyFile 进行重命名为 cleanFile,文件名为:key.index。然后刷新size的长度。如果失败,则删除dirtyFile.
  接下来,如果成功或者readable为true,将readable设置为true,写入一条CLEAN记录。如果第一次提交且失败,那么就会从lruEntries.remove(key),写入一条REMOVE记录。

写入缓存,肯定要控制下size。于是最后,判断是否超过了最大size,或者需要重建journal文件,什么时候需要重建呢?

1
2
3
4
5
private boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
return redundantOpCount >= redundantOpCompactThreshold //
&& redundantOpCount >= lruEntries.size();
}

  如果redundantOpCount达到2000,且超过了lruEntries.size()就重建,这里就可以看到redundantOpCount的作用了。防止journal文件过大。
  ok,到此我们的存入缓存就分析完成了。再次总结下,首先调用editor,拿到指定的dirtyFile的OutputStream,你可以尽情的进行写操作,写完以后呢,记得调用commit。
  commit中会检测是你是否发生IOException,如果没有发生,则将dirtyFile->cleanFile,将readable=true,写入CLEAN记录。如果发生错误,则删除dirtyFile,从lruEntries中移除,然后写入一条REMOVE记录。
读取缓存

1
2
3
4
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);  
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
}

那么首先看get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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);
}

get方法比较简单,如果取到的为null,或者readable=false,则返回null.否则将cleanFile的FileInputStream进行封装返回Snapshot,且写入一条READ语句。 然后getInputStream就是返回该FileInputStream了。
好了,到此,我们就分析完成了创建

DiskLruCache,存入缓存和取出缓存的源码。
除此以外,还有一些别的方法我们需要了解的。

其他方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}

for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}

redundantOpCount++;
journalWriter.append(REMOVE + ' ' + key + '\n');
lruEntries.remove(key);

if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}

return true;
}

如果实体存在且不在被编辑,就可以直接进行删除,然后写入一条REMOVE记录。

与open对应还有个remove方法,大家在使用完成cache后可以手动关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** Closes this cache. Stored values will remain on the filesystem. */
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}

关闭前,会判断所有正在编辑的实体,调用abort方法,最后关闭journalWriter。至于abort方法,其实我们分析过了,就是存储失败的时候的逻辑:

1
2
3
public void abort() throws IOException {
completeEdit(this, false);
}

到此,我们的整个源码分析就结束了。可以看到DiskLruCache,利用一个journal文件,保证了保证了cache实体的可用性(只有CLEAN的可用),且获取文件的长度的时候可以通过在该文件的记录中读取。利用FaultHidingOutputStream对FileOutPutStream很好的对写入文件过程中是否发生错误进行捕获,而不是让用户手动去调用出错后的处理方法。其内部的很多细节都很值得推敲。

不过也可以看到,存取的操作不是特别的容易使用,需要大家自己去操作文件流,但在存储比较小的数据的时候(不存在内存问题),很多时候还是希望有类似put(key,value),getAsT(key)等方法直接使用。我看了ASimpleCache 提供的API属于比较好用的了。于是萌生想法,对DiskLruCache公开的API进行扩展,对外除了原有的存取方式以外,提供类似ASimpleCache那样比较简单的API用于存储,而内部的核心实现,依然是DiskLruCache原本的。

考资料:

  • http://blog.csdn.net/lmj623565791/article/details/47251585

关于react native页面全屏设置

发表于 2017-04-22 | 分类于 React Native

  在React Native中,页面使用的是flexbox规则来指定某个组件的子元素布局,因此要实现页面的全拼设置,是需要对view的flex相关属性进行设置。
  通常情况下我们考虑的是设置宽高来制定页面为全屏,那系统也没有提供全屏的变量之给我们,我们只能通过系统属性实例const {width, height} = Dimensions.get(‘window’);获取窗口的宽高,在指定view的宽高,这个是一般情况的想法。
  正如开头所说,React Native是以flexbox的规则来制定某个组件的子元素布局,因此需要考虑从flex的属性着手,flex属性代表了试图占取窗口比例,默认情况为0,即按照子元素大小绘制内容,如果指定了非0值x,则代表占取总flex的x份,如果其他子元素没有制定flex值,则在绘制完其他子元素后剩余的空间都是该制定flex值的元素的。

linux系统磁盘挂载

发表于 2017-04-22 | 分类于 Linux

linux系统挂载知识

  • linux的硬盘识别
    2.6 kernel以后,linux会将识别到的硬件设备在/dev下建立相应的设备文件,如:
设备前缀 描述
sda 表示SCSI类硬盘
hda 表示IDE类硬盘
scd 表示USB类设备

我们可以通过fdisk -l命令来查看设备信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xxxxxx:~ $ sudo fdisk -l
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 137215 129024 63M c W95 FAT32 (LBA)
/dev/mmcblk0p2 137216 30367743 30230528 14.4G 83 Linux

Disk /dev/sda: 74.5 GiB, 80026361856 bytes, 156301488 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x7780b3c2

Device Boot Start End Sectors Size Id Type
/dev/sda1 2048 156301487 156299440 74.5G 83 Linux

通过命令我们可以看到/dev/sda1设备信息,这个设备标识已经分过区并且格式化过了,现在就可以挂载了,但在挂在前我们得设置一个挂载点啊,要不凭空怎么挂呢,是吧,一般可以选择/mnt目录下获知/media目录下,先在选定目录下创建一个文件夹,然后就可以挂载在文件夹上了。

1
2
mount -t ext4 /dev/sda1 /media/disk1
umount /dev/sda1

其中第一句中的ext4 表示文件系统类型(可以通过parted程序查看);iocharset=utf8文件读取格式,可选参数;umask表示让挂载的分区允许所有用户读取和修改,可选参数。第二句为卸载命令。
这个只是临时的挂在,如果要在开机的时候就挂载的话则需要修改/etc/fstab。

下面详细来说下mount 命令和mount相关参数设定:
标准模式的mount命令使用是如下的形式:

1
mount -t type device dir

  其中type代表的是挂在文件的文件系统,device代表fdisk查找的块设备,dir代表的是需要挂在的路径;
文件系统独立挂载选项
  这些参数只在/etc/fstab文件中用到,有些参数在系统内核已经给定了默认值来启用或关闭某些特性,如果要查看这些参数,可以查看/proc/mounts文件。
  下面这些参数可以用在一些文件系统挂载时,但是不是每个文件系统都可以使用这些参数,比如sync目前只使用于 ext2、ext3、fat、vfat和ufs文件系统:

命令 描述
async 所有的i/o都要满足异步输入输出,同样sync亦是如此
atime 不要使用noatime特性,那么对inode的访问将使用内核默认的
noatime 不更新文件的访问时间
auto !自动挂载,可以在mount时候使用-a参数启用
noauto 只能显示挂载

/etc/fstab
  该文件包含了不同的文件系统的描述信息,是静态的文件系统信息。它由系统管理员修改和管理的,只能通过系统程序读取,对其他用户是不可写的。
  文件中每一行代表了一个文件系统,以#开头的是解释性内容,空白行会被忽略。在文件中,每条记录的顺序也是很重要的,因为fsck、mount和umount命令都是顺序迭代fstab来做它们自身的业务。
  该文件中每行由六个字段属性组合而成,各字段功能解析:

  1. (文件块)该字段描述了需要挂载的块设备和远程文件系统。对于普通的挂在,其会保持一个块设备节点(由mknod创建),如‘/dev/cdrom’、‘/dev/sdb7/’,而对于NFS格式文件系统会以:dir形式挂载,如‘knuth.aeb.nl:/’。
      除了明确指定块设备路径,我们还可以通过标签或者UUID来挂载块设备,如‘LABEL=Boot’,‘UUID=3e6be9de-8139-11d1-
    -9106-a43f08d823a6’。
    
      另外还可以通过PARTUUID= and PARTLABEL=z来挂载快设备,但是这两个特性只支持GUID分区表(GPT)和MAC分区表。
  2. 文件挂载点,对于swap分区,该字段需要指定为‘none’,如果文件挂载点包含了空格字符,需要以‘\040’替换。
  3. 文件系统格式,说明挂载的块设备是什么类型的文件格式,目前linux支持文件系统有:adfs, affs, autofs, coda,
    coherent, cramfs, devpts, efs, ext2, ext3, hfs,  hpfs,  iso9660,
    jfs,  minix,  msdos,  ncpfs,  nfs,  ntfs,     proc, qnx4, reiserfs,
    romfs, smbfs, sysv, tmpfs, udf, ufs, umsdos, vfat,  xenix,  xfs和其他,如果要更详细的可以通过mount查看。
    
  4. 声明与文件系统相关的选项,选项列表是以逗号分割开来的,包含挂载类型以及任何适合于文件系统类型的附加选项。有关可用安装选项的文档,请参考mount(8)),在可用的swap选项文件,请参考wapon(8))。
    基础的一些参数选项有:
参数 描述
defaults 使用默认参数:rw,suid,dev,exec,auto,nouser and async
noauto 在‘mount -a’命令下,不挂载文件系统
user 允许一个用户挂载
owner 允许块设备拥有者挂载
comment 或 x- 使用fstab维护程序
nofail 如果快设备不存在,不要报告错误
  1. 指定该文件系统是否需要备份,如果该字段没有指定,默认则是为0,也即该文件系统不需要备份,如果需要查看备份选项可以看dump(8))。
  2. 指定文件系统是否需要检查和检查顺序,具体选项意义可以查看fsck(8)),对于root文件系统需要指定为1,其他文件系统指定为2,如果字段没有指定,默认值为0,也即表明文件系统不需要检查。

参考资料:

  • http://man.yolinux.com/cgi-bin/man2html?cgi_command=mount
  • http://blog.chinaunix.net/uid-7411781-id-2056205.html
  • http://www.jb51.net/os/RedHat/1117.html
  • http://qq85609655.iteye.com/blog/1449899
  • http://freearth.blog.chinaunix.net/uid-20769015-id-3478861.html
  • http://blog.csdn.net/jiyuanyi1992/article/details/43866273
  • /etc/fstab文件解析:http://blog.csdn.net/richerg85/article/details/17917129
  • http://blog.163.com/fan_yishan/blog/static/47692213200951343813425/

hexo写blog

发表于 2017-04-22 | 分类于 Hexo

  上一篇我们讲了怎么使用hexo向guthub发布自己的blog,这篇我们讲讲怎么通过hexo写blog。
  在我们开始写blog之前,我们先了解下hexo的blog目录结构:
hexo的blog目录

  1. deploy_git一看名称我们就知道大概作何用的,是的,最终发布到github的所有东西都在这个目录下,hexo自动生成的;
  2. node_modules是node执行的资源文件;
  3. scaffolds模板文件存放的地方;
  4. source文章资源存放的地方;
  5. themes主题存放的地方;
  6. _config.yml我们的blog配置文件,在上篇中也提到过的;
    暂时理解到这里,以后发现不对会及时补充。

生成一篇新博客
  好了现在可以开始准备写自己的blog啦
hexo通过下面的命令来创建一片新的文章:

1
hexo new [layout] <title>

其中[]表示可选填的参数,<>表示必填的参数,(layout)为制定了文章的布局,默认为post,这个默认值可以通过 _config.yml 中的 default_layout 参数来修改,其他还有page和draft两种布局,它们也代表了文章最终放在哪里:

布局 路径
post source/_posts
page source
draft source/_drafts

!注意! 如果我们自定义了布局,并且和post相同,也是会放到source/_posts下。
  有生成bolg那也有移除(删除)blog,只要删除需要删除的blog的.md文件并运行以下命令即可:

1
hexo clean

   该命令会删除node的数据库并重新生成。

文件名称
  Hexo默认是以标题作为文件名称,可以通过new_post_name参数修改默认的文件名称,如:year-:month-:day-:title.md 可让您更方便的通过日期来管理文章,且最终文件的后缀是.md。

变量 描述
:title 标题(小写,空格将会被替换为短杠)
:year 建立的年份,比如, 2015
:month 建立的月份(有前导零),比如, 04
:i_month 建立的月份(无前导零),比如, 4
:day 建立的日期(有前导零),比如, 07
:i_day 建立的日期(无前导零),比如, 7

草稿
  如上提到的三种布局中的draft,其代表为文件为草稿,存放在source/_drafts 文件夹,在默认发布blog时是不会将草稿发布到github,需要通过publish命令将草稿移动到source/_posts 文件夹:

1
hexo publish [layout] <title>

   草稿默认不会显示在页面中,可在执行时加上 –draft 参数,或是把 render_drafts 参数设为 true 来预览草稿。
模板
  在新建文章时,Hexo 会根据 scaffolds 文件夹内相对应的文件来建立文件,例如:

1
hexo new photo "My Gallery"

  在执行这行指令时,Hexo 会尝试在 scaffolds 文件夹中寻找 photo.md,并根据其内容建立文章,以下是您可以在模版中使用的变量:

变量 描述
layout 布局
title 标题
draft 文件建立日期

注意在做表格的时候,一定要回车换行、回车换行、回车换行后做表格,要不不会被解析。

使用hexo向github发布自己的blog

发表于 2017-04-22 | 分类于 Hexo
  • 准备:使用hexo在github上搭建博客需要以下东西:
  1. git,下载安装即可

  2. node,下载安装即可,如果自己用压缩包安装,注意配置好环境变量。

  3. 通过node安装好hexo。

  • 安装hexo:
    在装好node才可以安装hexo
    1
    npm install -g hexo

创建hexo文件夹即自己的blog项目文件夹,切换到目录下,按住shirt键并右击,可以看到《在此处打开命令窗口》,点击进入输入下面命令:

1
hexo init

运行完后,可以看到建立网站所需要的所有文件,至此,全部安装工作已经完成!blog就是你的博客根目录,所有的操作都在里面进行。
生成静态页面

1
hexo generate

本地启动:
启动本地服务,进行文章预览调试,命令:

1
hexo server

浏览器输入http://localhost:4000,这样本地博客已经建立好了,如何才能发布到github上呢,首先我们的配置下我们githu的信息,hexo配置github的信息在文件夹的_config.yml这个文件里面,打开文件我们可以看到以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# Hexo Configuration
## Docs: https://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/
# Site

title: CallteFoot's blog

subtitle:

description:

author: John Doe

language:

timezone:

# URL

## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'

url: https://cattlefoot.github.io/

root: /

permalink: :year/:month/:day/:title/

permalink_defaults:

# Directory

source_dir: source

public_dir: public

tag_dir: tags

archive_dir: archives

category_dir: categories

code_dir: downloads/code

i18n_dir: :lang

skip_render:

# Writing

new_post_name: :title.md # File name of new posts

default_layout: post

titlecase: false # Transform title into titlecase

external_link: true # Open external links in new tab

filename_case: 0

render_drafts: false

post_asset_folder: false

relative_link: false

future: true

highlight:

enable: true

line_number: true

auto_detect: false

tab_replace:

# Category & Tag

default_category: uncategorized

category_map:

tag_map:

# Date / Time format

## Hexo uses Moment.js to parse and display date

## You can customize the date format as defined in

## http://momentjs.com/docs/#/displaying/format/

date_format: YYYY-MM-DD

time_format: HH:mm:ss

# Pagination

## Set per_page to 0 to disable pagination

per_page: 10

pagination_dir: page

theme: landscape

deploy:

type: git

#我的github项目地址:https://github.com/CattleFoot/CattleFoot.github.io.git

repo: https://{username}:{password}@github.com/CattleFoot/CattleFoot.github.io.git

#注意上面这个,要不发布到github时会要输账号、密码的,反正我是一直输错的,哭死。

branch: master

message: hexo build blog

接下来就的配置hexo发布github服务依赖

1
npm install hexo-deployer-git --save

这个安装好了,基本就可以发布自己的博客到github上了,但是还缺少了ssh登陆认证。

生成ssh密码,主要目的是使github和发布机器建立信任关系,在生产密钥前先运行下ssh-keygen是否全局命令,如果不是需要在环境变量中配置下,其所在的目录是Git\usr\bin,即git安装目录下的usr下的bin文件夹下。

ssh-keygen -t rsa -C “你的邮箱地址”,按3个回车,密码为空。

在C:\Users\Administrator.ssh下,得到两个文件id_rsa和id_rsa.pub。

生成好了之后到https://github.com/settings/ssh点击Add SSH key,并将id_rsa.pub内容粘贴进去。

  • 发布博客
    如果上面一切都搞定,接着就可以发布自己的博客到github啦:
    1
    hexo deploy

最后到github就可以看到自己的博客文件资源啦,然后可以进入自己的blog看看,如我的blog地址:https://cattlefoot.github.io/。

参考文献:

  • https://hexo.io/zh-cn/docs/server.html 官方文档,hexo是台湾人写的,文档也是中文。

  • http://www.jianshu.com/p/701b1095da11

  • http://www.jianshu.com/p/465830080ea9

Android之UncaughtExceptionHandler异常捕获

发表于 2017-02-12 | 分类于 Android

概要

在Android开发中,目前没有工业级的bug收集工具,不比java后台服务有各式各样的收集工具,Android用的更多一些
第三方的如bugly或者友盟等。那如何自己去收集呢?其实安卓原生提供了异常收集类UncaughtExceptionHandler。

UncaughtExceptionHandler正如其名称所示,用于收集未捕获的异常(简单的理解可以认为是崩溃异常)。
具体使用分为两步:

  1. 实现自定义的异常处理类:
    1
    2
    3
    4
    5
    6
    public class CrashHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
    //异常具体的处理,记录亦或上传到服务器
    }
    }

实现Thread.UncaughtExceptionHandler接口当有未捕获的异常的时候会回调uncaughtException(Thread thread, Throwable ex)方法

  1. 设置自定的异常处理类为系统默认的
    1
    Thread.setDefaultUncaughtExceptionHandler(crashHandler);

以上就是大致的使用步骤

具体代码逻辑

  1. 在application中初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class AppApplication extends Application {


    private static Context mContext;

    @Override
    public void onCreate() {
    super.onCreate();
    this.mContext = this;
    CrashHandler.getInstance().init(this);
    }

    public static Context getContext(){
    return mContext;
    }
    }
  2. CrashHandler 实现类如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    public class CrashHandler implements Thread.UncaughtExceptionHandler {

    /**
    * 系统默认UncaughtExceptionHandler
    */
    private Thread.UncaughtExceptionHandler mDefaultHandler;

    /**
    * context
    */
    private Context mContext;

    /**
    * 存储异常和参数信息
    */
    private Map paramsMap = new HashMap<>();

    /**
    * 格式化时间
    */
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

    private String TAG = this.getClass().getSimpleName();

    private static CrashHandler mInstance;

    private CrashHandler() {

    }

    /**
    * 获取CrashHandler实例
    */
    public static synchronized CrashHandler getInstance(){
    if(null == mInstance){
    mInstance = new CrashHandler();
    }
    return mInstance;
    }

    public void init(Context context){
    mContext = context;
    mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();

    Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
    * uncaughtException 回调函数
    */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
    if(!handleException(ex) && mDefaultHandler != null){
    mDefaultHandler.uncaughtException(thread,ex);
    }else{
    try {
    Thread.sleep(3000);
    } catch (InterruptedException e) {
    Log.e(TAG, "error : ", e);
    }

    AppManager.getAppManager().AppExit(mContext);
    }

    }

    /**
    * 收集错误信息.发送到服务器
    * @return 处理了该异常返回true,否则false
    */
    private boolean handleException(Throwable ex) {
    if (ex == null) {
    return false;
    }

    collectDeviceInfo(mContext);

    new Thread() {
    @Override
    public void run() {
    Looper.prepare();
    Toast.makeText(mContext, "程序开小差了呢..", Toast.LENGTH_SHORT).show();
    Looper.loop();
    }
    }.start();

    saveCrashInfo2File(ex);
    return true;
    }


    /**
    * 收集设备参数信息
    * @param ctx
    */
    public void collectDeviceInfo(Context ctx) {

    try {
    PackageManager pm = ctx.getPackageManager();
    PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
    if (pi != null) {
    String versionName = pi.versionName == null ? "null" : pi.versionName;
    String versionCode = pi.versionCode + "";
    paramsMap.put("versionName", versionName);
    paramsMap.put("versionCode", versionCode);
    }
    } catch (PackageManager.NameNotFoundException e) {
    Log.e(TAG, "an error occured when collect package info", e);
    }

    Field[] fields = Build.class.getDeclaredFields();
    for (Field field : fields) {
    try {
    field.setAccessible(true);
    paramsMap.put(field.getName(), field.get(null).toString());
    } catch (Exception e) {
    Log.e(TAG, "an error occured when collect crash info", e);
    }
    }
    }

    /**
    * 保存错误信息到文件中
    *
    * @param ex
    * @return 返回文件名称,便于将文件传送到服务器
    */
    private String saveCrashInfo2File(Throwable ex) {

    StringBuffer sb = new StringBuffer();
    for (Map.Entry entry : paramsMap.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
    sb.append(key + "=" + value + "\n");
    }

    Writer writer = new StringWriter();
    PrintWriter printWriter = new PrintWriter(writer);
    ex.printStackTrace(printWriter);
    Throwable cause = ex.getCause();
    while (cause != null) {
    cause.printStackTrace(printWriter);
    cause = cause.getCause();
    }
    printWriter.close();
    String result = writer.toString();
    sb.append(result);
    try {
    long timestamp = System.currentTimeMillis();
    String time = format.format(new Date());
    String fileName = "crash-" + time + "-" + timestamp + ".log";
    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
    String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/crash/";
    File dir = new File(path);
    if (!dir.exists()) {
    dir.mkdirs();
    }
    FileOutputStream fos = new FileOutputStream(path + fileName);
    fos.write(sb.toString().getBytes());
    fos.close();
    }
    return fileName;
    } catch (Exception e) {
    Log.e(TAG, "an error occured while writing file...", e);
    }
    return null;
    }
    }

⚠️此处并未使用常用的退出应用代码

1
2
3
4
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
//区别在于:system.exit(0):正常退出,程序正常执行结束退出,system.exit(1):是非正常退出,就是说无论程序正在执行与否,都退出,
//System.exit(status)不管status为何值都会退出程序。和return 相比有以下不同点:return是回到上一层,而System.exit(status)是回到最上层 */

具体原因为:在4.4即以上系统使用该方法杀进程的时候app会重启,类似内存不足app挂了,系统会发广播拉起app的那种情况,那么就有可能会导致一个异常多次上报的情况,所以这里实现的activity栈来管理退出.

  1. activity管理类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    public class AppManager {  


    private static Stack activityStack;

    private static AppManager instance;

    private AppManager() {
    }

    /**
    * 单一实例
    */
    public static AppManager getAppManager() {
    if (instance == null) {
    instance = new AppManager();
    }
    return instance;
    }

    /**
    * 添加Activity到堆栈
    */
    public void addActivity(Activity activity) {
    if (activityStack == null) {
    activityStack = new Stack();
    }
    activityStack.add(activity);
    }

    /**
    * 获取当前Activity(堆栈中最后一个压入的)
    */
    public Activity currentActivity() {
    Activity activity = activityStack.lastElement();
    return activity;
    }

    /**
    * 结束当前Activity(堆栈中最后一个压入的)
    */
    public void finishActivity() {
    Activity activity = activityStack.lastElement();
    finishActivity(activity);
    }

    /**
    * 结束指定的Activity
    */
    public void finishActivity(Activity activity) {
    if (activity != null) {
    activityStack.remove(activity);
    activity.finish();
    activity = null;
    }
    }

    /**
    * 结束指定类名的Activity
    */
    public void finishActivity(Class cls) {
    for (Activity activity : activityStack) {
    if (activity.getClass().equals(cls)) {
    finishActivity(activity);
    break;
    }
    }
    }

    /**
    * 结束所有Activity
    */
    public void finishAllActivity() {
    for (int i = 0; i < activityStack.size(); i++) {
    if (null != activityStack.get(i)) {
    activityStack.get(i).finish();
    }
    }
    activityStack.clear();
    }

    /**
    * 退出应用程序
    */
    public void AppExit(Context context) {
    try {
    finishAllActivity();
    ActivityManager activityMgr = (ActivityManager) context
    .getSystemService(Context.ACTIVITY_SERVICE);
    activityMgr.killBackgroundProcesses(context.getPackageName());
    System.exit(0);
    } catch (Exception e) {
    }
    }
    }
1…56

CallteFoot

The blog from a Android coder

56 日志
23 分类
71 标签
© 2020 CallteFoot
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4