Android之IO流详解

(1)概念

文件在程序中是以的形式来操作的。

流:是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两个存储位置之间的传输称为流。流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

另外,在下文中演示IO的时候,会用到文件路径的获取。我特地整理了一篇博客Android之获取内、外置存储器路径

(2)分类
  • 按流向分类:输入流和输出流

输入流:用于读文件,并将读到的数据暂存到内存。
输出流:用于写文件,将内存中的数据存入到文件。

  • 按传输单位分类:字节流和字符流

字节流:可以用于读写二进制文件及任何类型文件。
字符流:可以用于读写文本文件。

(3)字节流和字符流的区别?
  • 读写单位不同

字节流以字节(1 byte,1byte=8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。

  • 处理对象不同

字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

  • 缓存

字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件。

总结:优先选用字节流。因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。

(4)超类

字节流: InputStream(读入流) OutputStream(写出流)
字符流: Reader(读入流) Writer (写出流)

它们都是抽象类。

(5)文件流

为了操作文件,所以出现了文件流,用文件流来对文件进行读写操作。

字节流
-----文件输入流:FileInputStream: 使用read方法读取文件
-----文件输出流:FileOutputStream : 使用write方法将二进制写到文件

字符流
-----文件输入流:FileReader: 使用read方法读取文件
-----文件输出流:FileWriter: 使用write方法将内容写到文件

例子一: 使用FileOutputStream将字符串写入文件,在使用 FileInputStream将文件的内容读出来。

    //是否存在外置存储器
    boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
    String filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
    } else {
        filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
    }

    File file = new File(filePath);
    if(!file.exists()){
        //如果文件不存在,则创建一个文件
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    FileOutputStream fos = null;
    FileInputStream fis = null;

    String text = "abcdefg";
    try {
        fos = new FileOutputStream(filePath);
        fos.write(text.getBytes());//必须将字符串转成字节数组才能写入输出流
        fos.flush();//写完之后刷新一下才能真正将字符串写入文件

        fis = new FileInputStream(filePath);
        byte[] cache = new byte[text.getBytes().length];
        StringBuilder sb = new StringBuilder();
        fis.read(cache);//由于字符串确定长度,那么cache的长度也确定了,所以这里只要读一次就能全部读完
        sb.append(new String(cache));

        Log.d("aaa", sb.toString());

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {

        if(fos != null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(fis != null){
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

例子二: 使用FileInputStreamFileOutputStream实现文件的拷贝。

    //是否存在外置存储器
    boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
    String filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
    } else {
        filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
    }

    File file = new File(filePath);
    if(!file.exists()){
        //如果文件不存在,则创建一个文件
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    String copy_filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        copy_filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
    } else {
        copy_filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
    }

    FileOutputStream fos = null;
    FileInputStream fis = null;

    try {

        fis = new FileInputStream(filePath);
        fos = new FileOutputStream(copy_filePath);
        byte[] cache = new byte[1024];//由于读取的文件长度不确定,所以就暂定一个大小为1024字节的临时缓存
        int index = 0;
        while ((index = fis.read(cache)) != -1){//如果读完就会返回-1
            fos.write(cache, 0, index);//将缓存cache的[0, index]范围的内容写入文件
            fos.flush();//写完之后刷新一下才能真正将字符串写入文件
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {

        if(fis != null){
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(fos != null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

例子三: 使用FileReaderFileWriter实现文件的拷贝,和上面字节流写法类似。

    //是否存在外置存储器
    boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
    String filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
    } else {
        filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
    }

    File file = new File(filePath);
    if(!file.exists()){
        //如果文件不存在,则创建一个文件
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    String copy_filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        copy_filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
    } else {
        copy_filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
    }

    FileWriter fw = null;
    FileReader fr = null;

    try {

        fr = new FileReader(filePath);
        fw = new FileWriter(copy_filePath);
        char[] cache = new char[1024];//由于读取的文件长度不确定,所以就暂定一个大小为1024字符数的临时缓存
        int index = 0;
        while ((index = fr.read(cache)) != -1){//如果读完就会返回-1
            fw.write(cache, 0, index);//将缓存cache的[0, index]范围的内容写入文件
            fw.flush();//写完之后刷新一下才能真正将字符串写入文件
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {

        if(fr != null){
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(fw != null){
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
(6)缓冲流

缓冲流是对文件流的操作的功能的加强,提高了数据的读写效率。既然缓冲流是对流的功能和读写效率的加强和提高,所以在创建缓冲流的对象时应该要传入要加强的流对象。

BufferedXxx是对其他流的封装,符合装饰设计模式

字节缓冲流:
-----缓冲输入流:BufferedInputStream: 使用read读取文件
-----缓冲输出流:BufferedOutputStream : 使用write将二进制写入文件

字符缓冲流:
-----缓冲输入流:BufferedReader: 使用read或者readLine读取文件,使用readLine可能会出现中文乱码的问题。
-----缓冲输出流:BufferedWriter: 使用write将内容写入文件

例子一: 使用BufferedInputStreamBufferedInputStream实现文件的拷贝,和FileInputStream+FileOutputStream实现文件拷贝的代码基本一致。使用装饰设计模式,在FileInputStream的基础上再次封装,变成了BufferedInputStream,在FileOutputStream的基础上再次封装,变成了BufferedInputStream

    //是否存在外置存储器
    boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
    String filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
    } else {
        filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
    }

    File file = new File(filePath);
    if(!file.exists()){
        //如果文件不存在,则创建一个文件
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    String filePath_copy = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath_copy = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
    } else {
        filePath_copy = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
    }

    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;

    try {

        bis = new BufferedInputStream(new FileInputStream(filePath));
        bos = new BufferedOutputStream(new FileOutputStream(filePath_copy));

        byte[] cache = new byte[1024];//由于读取的文件长度不确定,所以就暂定一个大小为1024字节数的临时缓存
        int index = 0;
        while ((index = bis.read(cache)) != -1){//如果读完就会返回-1
            bos.write(cache, 0, index);//将缓存cache的[0, index]范围的内容写入文件
            bos.flush();//写完之后刷新一下才能真正将字符串写入文件
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {

        if(bis != null){
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(bos != null){
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

例子二: 使用BufferedReaderBufferedWriter实现文件拷贝

BufferedReader新增了一个readLine方法,请谨慎使用,因为容易导致编码问题,比如“中文乱码”。

    //是否存在外置存储器
    boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
    String filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
    } else {
        filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
    }

    File file = new File(filePath);
    if(!file.exists()){
        //如果文件不存在,则创建一个文件
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    String filePath_copy = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath_copy = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
    } else {
        filePath_copy = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
    }

    BufferedReader br = null;
    BufferedWriter bw = null;

    try {

        br = new BufferedReader(new FileReader(filePath));
        bw = new BufferedWriter(new FileWriter(filePath_copy));

        String temptext;
        while ((temptext = br.readLine()) != null){//如果读完就会返回-1
            bw.write(temptext);//将缓存cache的[0, index]范围的内容写入文件
            bw.flush();//写完之后刷新一下才能真正将字符串写入文件
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {

        if(br != null){
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(bw != null){
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

以上需要注意的是:

    br = new BufferedReader(new FileReader(filePath));
    bw = new BufferedWriter(new FileWriter(filePath_copy));

以上两句话的默认编码是GBK或者gb2312,如果我们读取一个“UTF-8”格式的文件时,就会出现乱码。

有关中文乱码的问题,我有必要重点提一下:

  • IO操作的默认编码是UTF-8

以下两句代码的默认编码就是UTF-8

    //默认编码是UTF-8
    br = new BufferedReader(new FileReader(filePath));
    //默认编码是UTF-8
    bw = new BufferedWriter(new FileWriter(filePath_copy));

相当于

    //可以指定一个编码
    br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"));
    //可以指定一个编码
    bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8"));
  • 当读取文件时,如果文件的编码是GBK,我们读取的时候必须指定一个编码,这个时候我们必须使用转换流来指定编码格式。(有关转换流的只是下面会讲)
br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "GBK"));
  • 当我们将数据写到文件时,默认编码依然是UTF-8,如果需要指定编码格式,就使用下面的代码
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath_copy), "GBK"));
  • 假如我们指定“UTF-8”编码将数据写入文件,此时,当前文件的编码就是“UTF-8”,当我们使用三方工具打开文件时,如果出现乱码,那么表明该工具的默认打开的编码不是“UTF-8”。
(7)转换流

顾名思义,转换流就是讲字节流转成字符流,从构造方法就可以看出来了

InputStreamReader(InputStream in, String charsetName)
OutputStreamWriter(OutputStream out, String charsetName)

完整的代码如下:

    //是否存在外置存储器
    boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
    String filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
    } else {
        filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
    }

    File file = new File(filePath);
    if(!file.exists()){
        //如果文件不存在,则创建一个文件
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    String filePath_copy = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath_copy = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
    } else {
        filePath_copy = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
    }

    InputStreamReader isr = null;
    OutputStreamWriter osw = null;
    try {
        isr = new InputStreamReader(new FileInputStream(filePath), "UTF-8");
        osw = new OutputStreamWriter(new FileOutputStream(filePath_copy), "GBK");

        char[] cache = new char[1024];
        int index;
        while ((index = isr.read(cache, 0, cache.length)) != -1){//如果读完就会返回-1
            osw.write(cache, 0, index);//将缓存cache的[0, index]范围的内容写入文件
            osw.flush();//写完之后刷新一下才能真正将字符串写入文件
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {

        if(isr != null){
            try {
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(osw != null){
            try {
                osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
(8)对象流

主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了。

对象的输出流: ObjectOutputStream
对象的输入流: ObjectInputStream

对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。

代码如下:

public class UserInfo implements Serializable{//必须实现Serializable接口

    private String name;

    private int age;

    public UserInfo(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


    //是否存在外置存储器
    boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
    String filePath = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
    } else {
        filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
    }

    File file = new File(filePath);
    if(!file.exists()){
        //如果文件不存在,则创建一个文件
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    String filePath_copy = "";
    if (isExternalStorageExist && isRootDirExist) {
        filePath_copy = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
    } else {
        filePath_copy = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
    }

    ObjectInputStream ois = null;
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream(filePath));
        UserInfo userInfo1 = new UserInfo("张三", 14);
        UserInfo userInfo2 = new UserInfo("李四", 15);
        oos.writeObject(userInfo1);
        oos.writeObject(userInfo2);
        oos.flush();

        ois = new ObjectInputStream(new FileInputStream(filePath));
        oos = new ObjectOutputStream(new FileOutputStream(filePath_copy));

        Object object;
        while ((object = ois.readObject()) != null){//如果获取的对象不为空
            if(object instanceof UserInfo){
                UserInfo userInfo = (UserInfo) object;
                Log.d("aaa", "name:"+userInfo.getName()+"===age:"+userInfo.getAge());
                oos.writeObject(userInfo);
            }
            oos.flush();//写完之后刷新一下才能真正将字符串写入文件
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {

        if(ois != null){
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(oos != null){
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

以上代码体现了对象的序列化和反序列化的过程。主要注意的是,对象必须实现Serializable接口。

(9)ByteArrayInputStream

包含一个内部缓冲区,其中包含可以从流中读取的字节。 内部计数器跟踪由read方法提供的下一个字节。关闭一个ByteArrayInputStream没有任何效果。 该流中的方法可以在流关闭后调用,而不生成IOException 。意思就是说,比如文件流的处理对象的外存中的文件,而它的处理对象是内存中的缓冲区。它是从一个内存读到另一个内存方式。

    ByteArrayInputStream bis = null;
    ByteArrayOutputStream bos = null;
    try {

        String text = "我是中国人!123!abc!";
        //第一个数组
        byte[] textBytes = text.getBytes();
        bis = new ByteArrayInputStream(textBytes);
        bos = new ByteArrayOutputStream();

        //第二个数组
        byte[] tempText = new byte[1024];
        int index;
        while ((index = bis.read(tempText)) != -1){
            bos.write(textBytes, 0 , index);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {

        if(bis != null){
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(bos != null){
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
(10)数据流

DataInputStream和DataOutputStream

(11)打印流

PrintStream和PrintWriter

(12)随机访问文件流

RandomAccessFile

(13)结构图
图片.png
图片.png
图片.png

结构图文件已保存到百度网盘:

链接:https://pan.baidu.com/s/16_BpUSdwfalGR7zezU2-uw
提取码:bu70

说明:

这篇写到最后也懒得多写了,因为我自己都看的比较乱,等后期有时间了我会重新整理下。

你可能感兴趣的:(Android之IO流详解)