Java笔记-I/O字节流

Java的流

Java 流与目标数据源和程序之间的关系图示:

这里写图片描述

下图描述了输入流和输出流的类层次图:
这里写图片描述

  • 字符流:顾名思义,该流只能处理字符,但处理字符速度很快
  • 字节流:可以处理所有以bit为单位储存的文件,也就是说可以处理所有的文件,但是在处理字符上的速度不如字符流

读写文本文件

1.字节输入流 InputStream 类

InputStream 作用:将文件中的数据输入内部存储器(简称内存)中,它提供了一系列和读取数据有关的方法,常用方法如下表所示:

方法名称 说明
int read() 读取一个字节数据
int read(byte[] b) 将数据读取到字节数组中
int read(byte[] b, int off, int len) 从输入流中读取最多 len 长度的字节,保存到字节数组 b 中,保存的位置从 off 开始
void close() 关闭输入流
int available() 返回输入流读取的估计字节数

2.字节输出流 OutputStream 类

字节输出流 OutputStream 类的作用是把内存中的数据输出到文件中,它提供了一些列向文件中写数据的方法,如下表所示:

方法名称 说明
void write(int c) 写入一字节数据
void write(byte[] buf) 写入数组 buf 的所有字节
void write(byte[] b, int off, int len) 将字节数组中从 off 位置开始,长度为 len 的字节数据输出到输出流中
void close() 关闭输出流

注意close方法是必须要有的

close的作用:把当前对象从指向文件的地方断开

以前的对象没有考虑过回收问题,现在考虑是因为:InputSteam 和 OutputSteam 对象不单单是纯内存资源,涉及到一部分物理资源。
而垃圾回收器只能回收纯内存资源,无法回收物理资源,所以需要程序员手动释放资源。

close 方法:就是把对象从跟当前操作系统资源互相连接的地方断开。


字节输出流FileOutputStream类

FileOutputStream构造方法作用详解

//FileOutputStream 构造方法:
//1.该方法每次write 都是从文件开始部分写入
//即每次都清空txt内的内容,再重新写入
FileOutputStream fos = new FileOutputSteam(file);
//2.该方法boolean=true的时候,从文件末尾进行拼接写入
//否则与1.一样,即1.是默认boolean=false
FileOutputStream fos = new FileOutputSteam(file,boolean);

File file = new File("D:\\a.txt");
//上句代码执行之后,系统中并不会创建 D:\\a.txt 文件
//而是由下一句中的 2 步骤实现的
FileOutputStream fos = new FileOutputSteam(file);
//该构造器做了3步:
//1:把对象在内存中创建出来,把路径赋值给对象
//2:把这个路径所对应的文件创建出来
//3:把当前对象指向该文件,等着输出
//其中如果2中的文件已经存在,则跳过2直接执行3

FileOutputStream常用方法

方法名称 说明
void write(int ch) 一次写一个字节,写出到输出流所指向的文件里面
void write(byte[] bs) 一次写一个字节数组,写出到输出流所指向的文件里面
void write(byte[] bs ,int offset,int length) 一次写一个字节数组的一部分,写出到输出流所指向的文件里面。从 offset 位置开始(0是第一个),写 length 长度

FileOutputSteam向txt中写内容

public static void main(String[] args) throws IOException {
        File file = new File("D:\\a.txt");
        FileOutputStream fos = new FileOutputStream(file);
        //97并不是数字,而是ASCII表中的97-->a
        fos.write(97);
        //"\r\n"换行
        fos.write("\r\n".getBytes());
        //fos.write()方法内不可以传String字符串
        //所以我们将String字符串转成字节数组即可
        fos.write("你瞅啥?".getBytes());
        fos.close();
}
//文件打开结果是:
//a
//你瞅啥?

换行在不同操作系统中的形式:

  • Windows:\r\n
  • Linux:\n
  • MAC:\r

所以在不同的系统中我们如何操作呢?这时我们可以动态获取换行符

扫描二维码关注公众号,回复: 3223313 查看本文章
//动态获取换行符:我们不需要知道操作系统就可以动态换行
String sep = System.getProperty("line.separator");
System.out.println("a" + sep + "b");
//输出结果:
//a
//b

字节输入流 FileInputStream 类

FileInputStream常用方法:

方法名称 说明
int read() 每次读取一个字节,并把读取到的字节作为返回值,如果读到文件末尾,则返回-1
注意此种方法每次去读取一个字节,而汉字占两个字节,所以处理中文尽量不要用这个流去直接读取打印
int read(byte[] bs)() 每次读取当前参数中字节数组的最大容量,如果一次读不满,则读取一次结束,返回值是读到的字节总数,如果读到文件末尾 返回-1。
注意此处读取到的字节数组,不要把每个字节单独拿出来处理,因为中文单独处理有问题。可以把字节数组转换成字符串,转换的长度就是当前读到的字节总数。

FileInputStream简单使用:

使用 FileInputStream 读取纯英文txt文件:

public static void main(String[] args) throws IOException {
    File file = new File("D:\\a.txt");
    FileInputStream fis = new FileInputStream(file);
    int ch = 0;
    while ((ch = fis.read()) != -1) {
        System.out.print((char) ch);
    }
}

注意:上述方法并不适用于读取汉字,因为1个汉字占2字节,我们是1个1个字节读取的,如果直接控制台输出的话会有乱码。


FileInputStream和FileOutputStream拷贝文件

注意:不能对一个同一个文件同时进行读写操作!!!!!

从 FileOutputStream 到 FileIntputStream 其实就是复制一个文件的过程,将文件读取到 FileIntputStream 中,后输出到 FileOutputStream 也就是相当于输出到了硬盘的文件中。


问题:字节数组该声明多大合适呢?

情况1:文件不大的时候,按照文件可读取的字节数声明数组:
使用fis.available()获取当前输入流所指向的文件的最大可读取的字节总数。

情况2:文件比较大的时候:

  1. 一次读一个字节 多次读取
    一旦读取的不是一个纯文本文件,那么直接打印出来的东西是看不懂的,也不是用来直接打印的,需要输出流配合输出到本地操作系统其它地方,依旧按照原来格式保存
    这是文件拷贝,一次拷贝一个字节,效率非常低下
  2. 一次读一个字节数组 多次读取
    每次读取完写出去的时候要注意写字节数组一部分,因为字节数组的声明的大小到最后一次需要写读取到的字节总数,而不是整个字节数组。
    通过此种方式拷贝文件效率比一个一个字节要高很多

情况1代码示例:

public static void main(String[] args) throws IOException {
    File file = new File("D:\\a.txt");
    FileInputStream fis = new FileInputStream(file);
    byte[] bs = new byte[fis.available()];
    int total = fis.read(bs);
    System.out.println(new String(bs, 0, total));
    fis.close();
}

情况2的一次读一个字节,效率低:

public static void main(String[] args) throws IOException {
    File file1 = new File("D:\\a.txt");
    File file2 = new File("D:\\b.txt");
    FileInputStream fis = new FileInputStream(file1);
    FileOutputStream fos = new FileOutputStream(file2);
    int ch = 0;
    while ((ch = fis.read()) != -1) {
        fos.write(ch);
    }
    fis.close();
    fos.close();
}

情况2的一次读一个字节数组,效率高:

public static void main(String[] args) throws IOException {
    File file1 = new File("D:\\a.txt");
    File file2 = new File("D:\\b.txt");
    FileInputStream fis = new FileInputStream(file1);
    FileOutputStream fos = new FileOutputStream(file2);
    // 用来接收当前字节数组接受了多少个字符
    int total = 0;
    // 数组长度自己设,就是一次读的字节个数
    // 最后一个数组所包含的字节个数会小于等于255,重要!!!
    byte[] bs = new byte[255];
    while ((total = fis.read(bs)) != -1) {
        //所以必须用(bs,0,total)方法
        //否则由于最后一个数组的大小问题
        //会导致拷贝出来的文件比原文件大
        fos.write(bs, 0, total);
    }
    fis.close();
    fos.close();
}

上面这个我们可以以两个桶为例,一个桶为 FileIntputStream ,另一个桶为 FileOutputStream,如果要把一个桶里的水转移到另一个桶中,我们首先需要一个水瓢,一次次的舀水才能完成我们的需求。

public static void main(String[] args) throws IOException {
    File fil1 = new File("D:/111.pdf");
    File fil2 = new File("D:/222.pdf");
    // 一个叫输入流的桶,装满了一桶叫做D:/111.pdf文件的水
    FileInputStream fis = new FileInputStream(fil1);
    // 一个叫输出流的空桶,但想装满叫做"D:/222.pdf"文件的水
    FileOutputStream fos = new FileOutputStream(fil2);
    // 叫做buf的水瓢,一次装521个字节
    byte[] buf = new byte[521];
    // 用来测量每次水瓢装了多少字节
    int len = -1;
    // 一次次的用水瓢在输入流的桶里舀水,并用len测舀了多少水,
    //当len等于-1意味着水舀光了,该结束舀水了。
    while ((len = fis.read(buf)) != -1) {
        // 一次次把水瓢里的水放到了输出流的桶里
        fos.write(buf, 0, len);
    }
    fos.flush();
    fis.close();
    fos.close();
}

其实这种方法可以针对于很多的输入流和输出流。


从输入流到字符串

其实这个和上一种很类似,只不过换了种实现方式。
直接上代码:

public static void main(String[] args) throws IOException {
    File file = new File("D:/123.txt");
    // 同样是叫做输入流的桶
    FileInputStream fis = new FileInputStream(file);
    // 把输出流的桶换成了StringBuffer用来储存字符串
    // 其实也可以直接用String,但是StringBuffer速度更快。
    StringBuffer sb = new StringBuffer();
    // 水瓢没变
    byte[] buf = new byte[256];
    // 测水瓢舀了多少水没变
    int len = -1;
    // 和上面的原理基本一样,只不过换了个水瓢而已
    while ((len = fis.read(buf)) != -1) {
        // new String(buf, 0, buf.length)是将
        //buf里面的内容转换为字符串
        sb.append(new String(buf, 0, buf.length));
    }
    System.out.println(sb.toString());
}

文件切割

将一个mp3文件,切割成每个1m 大小的mp3文件,代码如下:

public class FileCut {
    public static void main(String[] args) {
        cut(new File("D:\\a.mp3"));
    }

    private static void cut(File file) {
        try {
            FileInputStream fis = new FileInputStream(file);
            FileOutputStream fos = null;
            // 切割成每个1m大小
            byte[] bs = new byte[1024 * 1024];
            int total = -1;
            int num = 0;
            while ((total = fis.read(bs)) != -1) {
                fos = new FileOutputStream("D:\\" + (++num) + ".mp3");
                fos.write(bs, 0, total);
                fos.close();
            }
            fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文件合并

将上面切割好的文件合并成一个mp3文件,我的切割出来是8个文件,名字分别为:
1.mp3、2.mp3、…、8.mp3
代码如下:

public class FileConcat {
    public static void main(String[] args) {
        fileConcat("D:\\newConcat.mp3");
    }

    private static void fileConcat(String path) {
        try {
            FileOutputStream fos = new FileOutputStream(path);
            // 上边分割的每个文件大小就是1m
            byte[] bs = new byte[1024 * 1024];
            int total = -1;
            for (int i = 0; i < 8; i++) {
                FileInputStream fis = new FileInputStream("D:\\" + (i + 1) + ".mp3");
                while ((total = fis.read(bs)) != -1) {
                    fos.write(bs, 0, total);
                }
                fis.close();
            }
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文件加密

其实通过文件的切割和合并我们就可以做到文件加密了。

比如:

1.将文件切割成 n 个部分,每个部分固定大小。

2.在每个切割好的文件末尾加上点没用的字节。

3.将修改后的文件合并到一起,就加密成功了。

4.如果解密的话,就反过来操作:按固定大小+添加没用字节大小,来切割加密后的文件;每个文件末尾删除你添加的没用的字节;再合并到一起就变成原来的文件了。


缓冲字节流

BufferedInputStream

BufferedOutputStream


两者都是从 FileInputStream 发展过来的,将 FileInputStreamFileOutputStream (低效字节流),转化为高效字节流。

不再是读一个字节或者一个字节数组,就直接写到文件中,反复读写。

而是将一个字节或者一个字节数组,一直读,直到缓冲字节流被刷新flush,或者关闭close,才向文件中写入数据。

如果不flushclose,就不向文件写数据。

如果写了close则不需要写flush了,close默认执行一次flush

具体代码跟FileInputStream, FileOutputStream基本一样,只是根据低效字节流进行改进。

具体代码如下方:4种字节流拷贝文件的方法的效率对比 中的代码示范


4种字节流拷贝文件的方法的效率对比

  • 一次一个字节
  • 一次一个字节数组
  • 字节缓冲流(高效),一次一个字节
  • 字节缓冲流(高效),一次一个字节数组

测试代码如下:

//测试文件是一个9m大小的mp3文件
//
//低效字节流(FileInputStream,FileOutputStream):
//  1.一次拷贝一个字节
//      耗时:30096ms
//  2.一次拷贝一个字节数组
//      耗时:355ms
//高效字节流(BufferedInputStream,BufferedOutputStream):
//  3.一次拷贝一个字节
//      耗时:290ms
//  4.一次拷贝一个字节数组
//      耗时:35ms
public class DemoFourIOStream {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        read1("D:\\test.mp3", "D:\\read1.mp3");
        read2("D:\\test.mp3", "D:\\read2.mp3");
        read3("D:\\test.mp3", "D:\\read3.mp3");
        read4("D:\\test.mp3", "D:\\read4.mp3");
        long end = System.currentTimeMillis();
        System.out.println("耗时为:" + (end - start) + "ms");
    }

    private static void read1(String srcPath, String destPath) {
        try {
            FileInputStream fis = new FileInputStream(srcPath);
            FileOutputStream fos = new FileOutputStream(destPath);
            int ch = -1;
            while ((ch = fis.read()) != -1) {
                fos.write(ch);
            }
            fis.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void read2(String srcPath, String destPath) {
        try {
            FileInputStream fis = new FileInputStream(srcPath);
            FileOutputStream fos = new FileOutputStream(destPath);
            byte[] bs = new byte[100];
            int total = -1;
            while ((total = fis.read(bs)) != -1) {
                fos.write(bs, 0, total);
            }
            fis.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void read3(String srcPath, String destPath) {
        try {
            FileInputStream fis = new FileInputStream(srcPath);
            FileOutputStream fos = new FileOutputStream(destPath);
            BufferedInputStream bis = new BufferedInputStream(fis);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            int ch = -1;
            while ((ch = bis.read()) != -1) {
                bos.write(ch);
            }
            bis.close();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void read4(String srcPath, String destPath) {
        try {
            FileInputStream fis = new FileInputStream(srcPath);
            FileOutputStream fos = new FileOutputStream(destPath);
            BufferedInputStream bis = new BufferedInputStream(fis);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            byte[] bs = new byte[100];
            int total = -1;
            while ((total = bis.read(bs)) != -1) {
                bos.write(bs, 0, total);
            }
            bis.close();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

效率结论:
高效>低效
字节数组>单个字节

所以最快的是:高效字节数组


转换流(字符流)

点击 跳转到我的笔记:Java笔记-转换流(字符流)的基本使用


二进制文件的读写

暂时省略

序列化和反序列化

对对象进行序列化及反序列化

使用工具:ObjectOutputStream,ObjectInputStream
介绍:将对象以文件的形式保存在硬盘中,使之能更方便的传输。
条件:必须实现Serializable接口(实现了这个接口,但并不需要重写任何方法)

例子1:

package file_io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class DemoObject implements Serializable {
    int date = 23;
}

public class IoTest {
    public static void main(String[] args) throws Exception {
        // 建立对象输出流准备向文件中写入对象
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream(new File("D:/123.obj")));
        // 向文件中写入新建立的对象
        oos.writeObject(new DemoObject());
        // 输出流记得要flush
        oos.flush();
        // 建立对象输入流准备在文件中读出刚写入的对象
        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream(new File("D:/123.obj")));
        // 建立一个新对象用于保存刚刚读出的对象
        DemoObject newObject = (DemoObject) ois.readObject();
        // 输出这个对象
        System.out.println(newObject.date);
    }
}

猜你喜欢

转载自blog.csdn.net/u011753266/article/details/80421919
今日推荐