AtomicFile使用

AtomicFile是Android API17引入的类,确保文件操作的原子性。它在写入时创建备份文件,如果操作失败则回滚到备份,成功则删除备份。在读取时实际读取备份文件。主要API包括构造方法、startWrite/finishWrite/failWrite方法、openRead()和delete()。在多线程环境下需谨慎使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);
    }
}

//最后别忘了授读写权限
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值