AtomicFile简介
AtomicFile是Android API17中引入的对文件进行原子操作的帮助类,所谓原子性,是指在对整个文件操作时,要么不操作,要么操作成功。如果操作失败,不会影响文件内容。
实现原理
在获取该实例时,会在内部创建两个File对象,一个代表原文件,一个代表备份文件,通过这两个文件保证原文件的原子性,在对文件进行写操作时,步骤及其结果如下:
1.开始写入时:
- 如果原文件存在、备份文件不存在,则直接将原文件重命名为备份文件;
- 如果原文件存在、备份文件也存在,则直接删除原文件;
2.写入完成时:
删除备份文件
3.写入失败时:
删除原文件,将备份文件重命名为原文件。
特点
- 1.AtomicFile读取文件时实际上读的是备份文件,写文件时操作的是原文件。当文件操作失败时会将备份文件变为原文件,实现”回滚”,成功时删除备份文件;
- 2.只要备份文件存在,原始文件就被认为是无效的;
- 3.AtomicFile内部没有任何锁定义,因此不适合多线程或进程的访问。
AtomicFile的操作原理如图所示:
API方法分析
构造方法AtomicFile(File baseName)
public AtomicFile(File baseName) {
mBaseName = baseName;
mBackupName = new File(baseName.getPath() + ".bak");
}
在创建AtomicFile实例时,会创建两个File对象,代表原文件个备份文件。
FileOutputStream startWrite()
该方法在写文件时调用,返回一个文件输出流,通过该流进行写文件操作,在这个方法中会保证备份文件的存在
public FileOutputStream startWrite() throws IOException {
// Rename the current file so it may be used as a backup during the next read
if (mBaseName.exists()) {
if (!mBackupName.exists()) {
if (!mBaseName.renameTo(mBackupName)) {
Log.w("AtomicFile", "Couldn't rename file " + mBaseName
+ " to backup file " + mBackupName);
}
} else {
mBaseName.delete();
}
}
FileOutputStream str = null;
try {
str = new FileOutputStream(mBaseName);
} catch (FileNotFoundException e) {
File parent = mBaseName.getParentFile();
if (!parent.mkdirs()) {
throw new IOException("Couldn't create directory " + mBaseName);
}
FileUtils.setPermissions(
parent.getPath(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
try {
str = new FileOutputStream(mBaseName);
} catch (FileNotFoundException e2) {
throw new IOException("Couldn't create " + mBaseName);
}
}
return str;
}
finishWrite(FileOutputStream str)
当通过startWrite()获取的流写入完成时调用该方法,表示写入成功,在这个方法中删除备份文件:
public void finishWrite(FileOutputStream str) {
if (str != null) {
FileUtils.sync(str);
try {
str.close();
mBackupName.delete();
} catch (IOException e) {
Log.w("AtomicFile", "finishWrite: Got exception:", e);
}
}
}
这个方法中会关闭FileOutputStream,因此不需要我们去关闭。
failWrite(FileOutputStream str)
如果startWrite()中写入失败时,调用该方法恢复原文件,这个方法中会删除原文件,并将备份文件变为原文件:
public void failWrite(FileOutputStream str) {
if (str != null) {
FileUtils.sync(str);
try {
str.close();
mBaseName.delete();
mBackupName.renameTo(mBaseName);
} catch (IOException e) {
Log.w("AtomicFile", "failWrite: Got exception:", e);
}
}
}
FileInputStream openRead():
用于读取文件,读取操作后应该关闭文件流:
public FileInputStream openRead() throws FileNotFoundException {
if (mBackupName.exists()) {
mBaseName.delete();
mBackupName.renameTo(mBaseName);
}
return new FileInputStream(mBaseName);
}
byte[] readFully()
内部调用openRead()方法,返回一个byte[]:
public byte[] readFully() throws IOException {
FileInputStream stream = openRead();
try {
int pos = 0;
int avail = stream.available();
byte[] data = new byte[avail];
while (true) {
int amt = stream.read(data, pos, data.length-pos);
if (amt <= 0) {
return data;
}
pos += amt;
avail = stream.available();
if (avail > data.length-pos) {
byte[] newData = new byte[pos+avail];
System.arraycopy(data, 0, newData, 0, pos);
data = newData;
}
}
} finally {
stream.close();
}
}
delete():
删除AtomicFile,会同时删除原文件和备份文件:
public void delete() {
mBaseName.delete();
mBackupName.delete();
}
File getBaseFile():
得到原文件对象
public File getBaseFile() {
return mBaseName;
}
使用AtomicFile时,需要注意多线程操作。如果多个线程对同一个AtomicFile进行操作,则有可能会造成数据错误。
示例
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private AtomicFile mAtomicFile;
private Button mButton_save,mButton_get;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
File file = new File(Environment.getExternalStorageDirectory(),"mytext.txt");
if (!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
mAtomicFile = new AtomicFile(file);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.button_save:
String text = "This is my text for AtomicFile";
dowrite(text);
break;
case R.id.button_get:
doRead2();
break;
}
}
/**
* 读文件操作
*/
private void doRead() {
FileInputStream inputStream = null;
try {
//读取文件
inputStream = mAtomicFile.openRead();
int size = inputStream.available();
byte[] buffer = new byte[size];
String content = null;
int len = 0;
while ((len = inputStream.read(buffer,0,buffer.length)) != -1){
content = new String(buffer);
}
Toast.makeText(MainActivity.this,"Read sucessful:"+ content,Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}finally {//关闭文件流
if (inputStream != null) {
try {
inputStream.close();
inputStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void doRead2() {
FileInputStream inputStream = null;
try {
byte[] bytes = mAtomicFile.readFully();
String content = new String(bytes,"utf-8");
Toast.makeText(MainActivity.this,"Read sucessful:"+ content,Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 写文件操作
*/
private void dowrite(String content) {
FileOutputStream stream = null;
try {
//开始写文件
stream = mAtomicFile.startWrite();
stream.write(content.getBytes());
//文件写入完成
mAtomicFile.finishWrite(stream);
Toast.makeText(MainActivity.this,"Write sucessful!",Toast.LENGTH_SHORT).show();
}catch (Exception e){
//操作失败时恢复原文件
mAtomicFile.failWrite(stream);
e.printStackTrace();
}
}
private void initView() {
mButton_get = findViewById(R.id.button_get);
mButton_save = findViewById(R.id.button_save);
mButton_save.setOnClickListener(this);
mButton_get.setOnClickListener(this);
}
}
//最后别忘了授读写权限