一个文件级循环队列的Journal实现

来源:互联网 发布:手机自动安装软件 编辑:程序博客网 时间:2024/05/29 10:09

准备做一个比较靠谱的下载组件,这是第一步,Journal。
最开始想的比较多,希望新增和更新单个消息都尽量少的进行磁盘操作(后来想想,特别是看了Android官方的下载之后,觉得完全没必要,数据库足矣),不希望用数据库(理论上,没有index,会读全部数据)。
想来想去,做了一个循环队列的方法,尽量减少文件操作,当然肯定还是不如数据库+index来的快。是个思路,也是个教训。

思路

基本与最最简单的磁盘块管理相同,因为没有文件名、文件夹导航之类的问题,就是一个数据块的表格,读、写基本都可以保证很少次数的操作。
- 想要减少磁盘IO操作,肯定是要有index的,而手写又很麻烦。有一个简单的办法,就是把每块数据的大小固定,构成一个定长队列,使用定距的RandomAccessFile.seek来在不同的记录中切换。这样就保证了,不想读的数据,完全不用磁盘IO,seek就能搞定
- 给每个数据块做标记(一个char),在seek到当前位置时,只要读一个字符就能判断当前块是否可用,在delete、add时很快

代码

  • 数据块
    封装了很多文件类的操作,主要是要定长
package gt.research.losf.journal.file;import android.text.TextUtils;import java.io.IOException;import java.io.RandomAccessFile;import java.util.Arrays;import gt.research.losf.journal.IBlockInfo;import gt.research.losf.util.TypeUtils;/** * Created by GT on 2016/3/16. */public class FileBlockInfo implements IBlockInfo {    public static final int STATE_LENGTH = 1;    public static final int URI_LENGTH = 256;    public static final int BLOCK_LENGTH = 10;    public static final int OFFSET_LENGTH = 10;    public static final char STATE_PROGRESS = 'p';    public static final char STATE_DELETE = 'd';    public static final char STATE_NEW = 'n';    public static final int LENGTH = STATE_LENGTH + URI_LENGTH + BLOCK_LENGTH + OFFSET_LENGTH;    private char mState;    private String mUri;    private int mBlockId;    private int mOffset;//offset of this block in file (next reading start)    public FileBlockInfo() {    }    public FileBlockInfo(String uri, int blockId, int offset) {        mState = STATE_NEW;        mUri = uri;        mBlockId = blockId;        mOffset = offset;    }    public FileBlockInfo(char[] raw) {        fromChars(raw);    }    public FileBlockInfo(RandomAccessFile file) throws Exception {        byte[] bytes = new byte[LENGTH];        int read = file.read(bytes, 0, LENGTH);        if (LENGTH != read) {            throw new Exception("Illegal Length " + read);        }        fromChars(TypeUtils.bytesToChars(bytes));    }    @Override    public char getState() {        return mState;    }    @Override    public String getUri() {        return mUri;    }    @Override    public int getBlockId() {        return mBlockId;    }    @Override    public int getOffset() {        return mOffset;    }    @Override    public boolean isLegal() {        boolean result = STATE_PROGRESS == mState ||                STATE_DELETE == mState || STATE_NEW == mState;        result &= !TextUtils.isEmpty(mUri);        result &= mBlockId > 0;        result &= mOffset > 0;        return result;    }    public void fromChars(char[] raw) {        int offset = STATE_LENGTH;        mState = raw[0];        mUri = readValue(raw, offset, totalLength(STATE_LENGTH, URI_LENGTH));        offset += URI_LENGTH;        mBlockId = Integer.valueOf(readValue(raw, offset,                totalLength(STATE_LENGTH, URI_LENGTH, BLOCK_LENGTH)));        offset += BLOCK_LENGTH;        mOffset = Integer.valueOf(readValue(raw, offset, totalLength(LENGTH)));    }    public char[] toRaw() {        int offset = STATE_LENGTH;        char[] raw = new char[LENGTH];        raw[0] = mState;        offset = fillSpace(raw, mUri, offset, totalLength(STATE_LENGTH, URI_LENGTH));        offset = fillSpace(raw, String.valueOf(mBlockId), offset,                totalLength(STATE_LENGTH, URI_LENGTH, BLOCK_LENGTH));        fillSpace(raw, String.valueOf(mOffset), offset, totalLength(LENGTH));        return raw;    }    public void writeToFile(RandomAccessFile file) throws IOException {        byte[] bytes = TypeUtils.charsToBytes(toRaw());        file.write(bytes);    }    public void setStateProgressAndFile(RandomAccessFile file) throws IOException {        setStateAndFile(file, STATE_PROGRESS);    }    public void setStateDeleteAndFile(RandomAccessFile file) throws IOException {        setStateAndFile(file, STATE_DELETE);    }    public void setStateNewAndFile(RandomAccessFile file) throws IOException {        setStateAndFile(file, STATE_NEW);    }    private void setStateAndFile(RandomAccessFile file, char state) throws IOException {        mState = state;        file.writeChar(mState);    }    private int fillSpace(char[] raw, String value, int offset, int end) {        System.arraycopy(value.toCharArray(), 0, raw, offset, value.length());        offset += value.length();        if (offset >= end) {            return end;        }        Arrays.fill(raw, offset, Math.min(end + 1, raw.length), ' ');        return end;    }    private String readValue(char[] raw, int offset, int end) {        int i = end - 1;        for (; i >= offset; --i) {            if (' ' != raw[i]) {                break;            }        }        return String.valueOf(raw, offset, i - offset + 1);    }    private int totalLength(int... lengths) {        int sum = 0;        for (int length : lengths) {            sum += length;        }        return sum;    }}
  • 单个循环队列文件
    维护一个文件级的循环队列。最重要的是做到遍历足够快、尽量减少IO,还要保证状态与文件同步。
package gt.research.losf.journal.file;import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;import java.util.LinkedList;import java.util.List;import gt.research.losf.common.Reference;import gt.research.losf.journal.IBlockInfo;import gt.research.losf.journal.IJournal;import gt.research.losf.journal.util.BlockUtils;import gt.research.losf.util.LogUtils;/** * Created by GT on 2016/3/16. */public class FileJournal implements IJournal {    private static final int sCount = 10;//100 lines of journal    private static final int sLength = FileBlockInfo.LENGTH * sCount;    private RandomAccessFile mFile;    // the position after this insertion, may be occupied.    private int mLastIndex = -1;    // block info count    private int mSize = 0;    public FileJournal(String name) throws IOException {        this(new File(name));    }    public FileJournal(File file) throws IOException {        if (!file.exists()) {            try {                file.createNewFile();            } catch (IOException e) {                LogUtils.exception(this, e);            }        }        mFile = new RandomAccessFile(file, "rwd");        mFile.setLength(sLength);        readStateFromFile();    }    private void readStateFromFile() {        iterateOverBlocks(new BlockIterateCallback() {            @Override            public boolean onBlock(RandomAccessFile file, int index) throws Exception {                if (!BlockUtils.isCurrentInfoVailable(file)) {                    ++mSize;                }                return false;            }        }, false);    }    @Override    public int addBlock(FileBlockInfo info) {        moveToNextEmptyIndex();        if (mLastIndex >= 0) {            try {                info.writeToFile(mFile);                ++mSize;                return RESULT_SUCCESS;            } catch (IOException e) {                LogUtils.exception(this, e);            }        }        return RESULT_FAIL;    }    @Override    public int addBlock(int id, String uri, int offset) {        return addBlock(new FileBlockInfo(uri, id, offset));    }    @Override    public int deleteBlock(final int id) {        int lastSize = mSize;        iterateOverBlocks(new BlockIterateCallback() {            @Override            public boolean onBlock(RandomAccessFile file, int index) throws Exception {                if (BlockUtils.isCurrentInfoVailable(file)) {                    return false;                }                FileBlockInfo blockInfo = new FileBlockInfo(file);                if (id == blockInfo.getBlockId()) {                    blockInfo.setStateDeleteAndFile(file);                    --mSize;                    return true;                }                return false;            }        }, false);        return lastSize == mSize ? RESULT_FAIL : RESULT_SUCCESS;    }    @Override    public int deleteBlock(final String uri) {        int lastSize = mSize;        iterateOverBlocks(new BlockIterateCallback() {            @Override            public boolean onBlock(RandomAccessFile file, int index) throws Exception {                if (BlockUtils.isCurrentInfoVailable(file)) {                    return false;                }                FileBlockInfo blockInfo = new FileBlockInfo(file);                if (uri.equals(blockInfo.getUri())) {                    blockInfo.setStateDeleteAndFile(file);                    --mSize;                }                return false;            }        }, false);        return lastSize == mSize ? RESULT_FAIL : RESULT_SUCCESS;    }    @Override    public boolean isFull() {        return mSize == sCount;    }    @Override    public int getSize() {        return mSize;    }    @Override    public IBlockInfo getBlock(final int id) {        final Reference<FileBlockInfo> ref = new Reference<>();        iterateOverBlocks(new BlockIterateCallback() {            @Override            public boolean onBlock(RandomAccessFile file, int index) throws Exception {                if (BlockUtils.isCurrentInfoVailable(file)) {                    return false;                }                FileBlockInfo blockInfo = new FileBlockInfo(file);                if (id == blockInfo.getBlockId()) {                    ref.ref = blockInfo;                    return true;                }                return false;            }        }, true);        return ref.ref;    }    @Override    public List<IBlockInfo> getBlocks(final String uri) {        final LinkedList<IBlockInfo> list = new LinkedList<>();        iterateOverBlocks(new BlockIterateCallback() {            @Override            public boolean onBlock(RandomAccessFile file, int index) throws Exception {                if (BlockUtils.isCurrentInfoVailable(file)) {                    return false;                }                FileBlockInfo blockInfo = new FileBlockInfo(file);                if (uri.equals(blockInfo.getUri())) {                    list.add(blockInfo);                }                return false;            }        }, true);        return list.isEmpty() ? null : list;    }    private void moveToNextEmptyIndex() {        iterateOverBlocks(new BlockIterateCallback() {            @Override            public boolean onBlock(RandomAccessFile file, int index) throws IOException {                return BlockUtils.isCurrentInfoVailable(file);            }        }, false);    }    private void iterateOverBlocks(BlockIterateCallback callback, boolean restoreFileSeeker) {        if (null == callback) {            return;        }        int startIndex = mLastIndex;        try {            int count = 0;            do {                mLastIndex = ++mLastIndex % sCount;                long offset = mLastIndex * FileBlockInfo.LENGTH;                mFile.seek(offset);                ++count;            } while (!callback.onBlock(mFile, mLastIndex) && count < sCount);        } catch (Exception e) {            LogUtils.exception(this, e);            mLastIndex = RESULT_INDEX_EXCEPTION;        }        if (startIndex == mLastIndex) {            mLastIndex = RESULT_INDEX_FULL;        }        if (restoreFileSeeker) {            mLastIndex = startIndex;            try {                long pointer = mLastIndex * FileBlockInfo.LENGTH;                if (pointer < 0) {                    pointer = 0;                }                mFile.seek(pointer);            } catch (IOException e) {                LogUtils.exception(this, e);            }        }    }    private interface BlockIterateCallback {        boolean onBlock(RandomAccessFile file, int index) throws Exception;    }}
  • 总管理器
    因为单个Journal队列的大小是固定的,可能要维护多个Journal队列。当然如果能做的一致性Hash,还可以进一步减少文件量,不过太麻烦了。不是关注点,有时间再试吧。
    因为这段代码是不使用的,所以没有测试,只是记个思路。
package gt.research.losf.journal.file;import android.util.ArrayMap;import java.io.File;import java.io.FilenameFilter;import java.io.IOException;import java.util.LinkedList;import java.util.List;import java.util.Random;import gt.research.losf.journal.IBlockInfo;import gt.research.losf.journal.IJournal;import gt.research.losf.util.LogUtils;/** * Created by GT on 2016/3/16. * manages a particular type of journal */public class FileJournalManager implements IJournal {    public static class Factory {        private static final ArrayMap<String, FileJournalManager> sInstances = new ArrayMap<>();        private static final Random mRandom = new Random(System.currentTimeMillis());        public static FileJournalManager getJournal(String name) {            if (null == sInstances.get(name)) {                synchronized (sInstances) {                    if (null == sInstances.get(name)) {                        sInstances.put(name, new FileJournalManager(name));                    }                }            }            return sInstances.get(name);        }        public static int generateId() {            return mRandom.nextInt();        }    }    private LinkedList<IJournal> mJournals = new LinkedList<>();    private int mLastSuffix = 0;    private String mParentPath;    private String mNamePrefix;    private FileJournalManager(String name) {        final File file = new File(name);        ensurePath(file);        loadExistingJournals(file);    }    @Override    public int addBlock(FileBlockInfo info) {        for (IJournal journal : mJournals) {            if (!journal.isFull()) {                return journal.addBlock(info);            }        }        IJournal journal = expandJournals();        if (null != journal) {            journal.addBlock(info);        }        return RESULT_FAIL;    }    @Override    public int addBlock(int id, String uri, int offset) {        return addBlock(new FileBlockInfo(uri, id, offset));    }    @Override    public int deleteBlock(int id) {        for (IJournal journal : mJournals) {            if (0 != journal.getSize()) {                int result = journal.deleteBlock(id);                if (RESULT_SUCCESS == result) {                    return RESULT_SUCCESS;                }            }        }        return RESULT_FAIL;    }    @Override    public int deleteBlock(String uri) {        boolean deleted = false;        for (IJournal journal : mJournals) {            if (0 != journal.getSize()) {                int result = journal.deleteBlock(uri);                if (RESULT_SUCCESS == result) {                    deleted = true;                }            }        }        return deleted ? RESULT_SUCCESS : RESULT_FAIL;    }    @Override    public boolean isFull() {        return false;    }    @Override    public int getSize() {        int size = 0;        for (IJournal journal : mJournals) {            size += journal.getSize();        }        return size;    }    @Override    public IBlockInfo getBlock(int id) {        IBlockInfo blockInfo = null;        for (IJournal journal : mJournals) {            blockInfo = journal.getBlock(id);            if (null != blockInfo) {                return blockInfo;            }        }        return null;    }    @Override    public List<IBlockInfo> getBlocks(String uri) {        List<IBlockInfo> resultList = new LinkedList<>();        for (IJournal journal : mJournals) {            List<IBlockInfo> list = journal.getBlocks(uri);            if (null == list) {                continue;            }            resultList.addAll(list);        }        return resultList.isEmpty() ? null : resultList;    }    private void ensurePath(File file) {        File parent = file.getParentFile();        if (null == parent) {            return;        }        parent.mkdirs();        mParentPath = parent.getAbsolutePath();    }    private void loadExistingJournals(File file) {        File parent = file.getParentFile();        mNamePrefix = file.getName();        File[] journals = parent.listFiles(new FilenameFilter() {            @Override            public boolean accept(File dir, String filename) {                return filename.startsWith(mNamePrefix);            }        });        if (null == journals || 0 == journals.length) {            journals = new File[]{new File(file.getParent(), file.getName() + 0)};        }        for (File journalFile : journals) {            try {                IJournal journal = new FileJournal(journalFile);                if (0 == journal.getSize()) {                    journalFile.delete();                    continue;                }                mJournals.add(new FileJournal(journalFile));                int count = Integer.valueOf(journalFile.getName().substring(mNamePrefix.length()));                if (count > mLastSuffix) {                    mLastSuffix = count;                }            } catch (IOException e) {                LogUtils.exception(this, e);            }        }    }    private IJournal expandJournals() {        ++mLastSuffix;        try {            IJournal journal = new FileJournal(new File(mParentPath, mNamePrefix + mLastSuffix));            mJournals.add(journal);            return journal;        } catch (IOException e) {            LogUtils.exception(this, e);        }        return null;    }}
0 0
原创粉丝点击