Java IO流操作

Java IO 知识

思维导图

一、字符、字节、位(bit)

1 字符占用的字节数是不一定的,如果是 UTF-8 编码:
1 字符 = 2 字节
1 字节 = 8 位

基本数据类型关系如下:

类型占用字节占用位数
byte18
short216
int432
long864
float432
double864
char216
boolean18

一、UTF-8、UTF-16、GBK 编码

java 使用的字符集是 unicode,但是编码格式是不确定的,在不同的系统上,编码格式是不一样,比如我的 mac 上 java 默认的编码格式是 UTF-8 的。

下面通过代码看一下 String 类型的汉字、字母分别在三种不同编码下占用的字节长度:

结果:

默认编码 UTF-8:
汉字:3  字母: 1
utf-8编码:
汉字:3  字母: 1
utf-16编码:
汉字:4  字母: 4
GBK编码:
汉字:2  字母: 1

可以看到虽然使用的 unicode 字符集,但是采用不同的编码方式,占用的字节数是不同的,所以单独的讲某个字符占用多大的长度意义不是很大,需要在讨论特定环境下讨论。

java 中的 io 流主要分为:

  • 字节流
  • 字符流

一、字节流

字节流又分为

  • 输入流 InputStream
  • 输出流 OutputStream

输入流负责从 源(可以是文件) 读取数据到 Java程序 中。

输出流负责把 Java程序 中数据写入到 源(可以是文件) 中。

1.1 输入流 InputStream

InputStream 是一个抽象类,抽象了应用程序读取数据的方式。

输入流基本方法:

  • int b = in.read();读取一个字节无符号填充到int低八位.-1是 EOF
  • in.read(byte[] buf)
  • in.read(byte[] buf,int start,int size)

常用的实现类有以下三个

1.1.1 FileInputStream

FileInputStream 用于从文件读取数据,可以通过 new 关键字构造,比如:

File file = new File(文件路径);
FileInputStream inputStream = new FileInputStream(file);

也可以:

FileInputStream inputStream = new FileInputStream(文件路径);

使用示例:
本示例从项目根目录的 test.txt 读取内容并打印,

public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream inputStream = new FileInputStream("test.txt");
        int len;
        byte[] bytes = new byte[8 * 1024];
        while ((len=inputStream.read(bytes,0,bytes.length))!=-1){
            String s = new String(bytes,0,len,"utf-8");
            System.out.println(s);
        }
        inputStream.close();
    }
}
1.1.1 DataInputStream

DataInputStream 对"流"功能的扩展,可以更加方便的读取 int, long,字符等类型数据。

使用 Demo 如下:

public class DataInputStreamDemo {

    public static void main(String[] args) throws IOException {
        FileInputStream inputStream = new FileInputStream("test.txt");
        DataInputStream dataInputStream = new DataInputStream(inputStream);

        int len;
        byte[] buffer = new byte[8 * 1024];
        while ((len = dataInputStream.read(buffer, 0, buffer.length)) != -1) {
            System.out.println(new String(buffer,0,len));
        }
        inputStream.close();
        dataInputStream.close();
    }
}
1.1.3 BufferedInputStream

BufferedInputStream 为 IO 提供了带缓冲区的操作,一般打开文件进行写入
或读取操作时,都会加上缓冲,这种流模式提高了IO的性能。

使用如下:

public class BufferedInputStreamDemo {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("test.txt"));
        int len;
        byte[] buffer = new byte[8*1024];
        while ((len=bufferedInputStream.read(buffer,0,buffer.length))!=-1){
            System.out.println(new String(buffer,0,len));
        }
        bufferedInputStream.close();
    }
}

1.2 输出流 OutputStream

OutputStream 也是一个抽象类,抽象了应用程序写数据的方式。

输出流基本方法:

  • out.write(int b) 写出一个byte到流,b的低8位
  • out.write(byte[] buf)将buf字节数组都写入到流
  • out.write(byte[] buf,int start,int size)

同样,常用的输出流实现类有下面三个:

1.2.1 FileOutputStream

FileOutputStream 用于写入数据到文件中,可以通过 new 关键字很轻松的创建

File file = new File(文件路径);
FileOutputStream outputStream = new FileOutputStream(file);

或者

FileOutputStream outputStream = new FileOutputStream(文件路径);

使用示例:
本示例是写文字到项目根目录的文件 test.txt。

public class FileOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("test.txt");
        String  s = "测试FileOutputStreamDemo";
        outputStream.write(s.getBytes("utf-8"));
        outputStream.close();
    }
}
1.2.2 DataOutputStream

DataOutputStream 对"流"功能的扩展,可以更加方便的写入 int, long,字符等类型数据。

使用 demo 如下:

public class DataOutputStreamDemo {
    public static void main(String[] args) throws IOException {

        FileOutputStream outputStream = new FileOutputStream("test.txt");
        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
        dataOutputStream.writeInt(123321);
        dataOutputStream.writeBoolean(true);
        dataOutputStream.writeUTF("DataOutputStreamDemo测试");
        outputStream.close();
        dataOutputStream.close();
    }
}

如果按照上面执行是会发现,只能在 test.txt 文件中看到

DataOutputStreamDemo测试

前面写入的 123321 和 true 都是乱码,比如我这边看到的是

乱码
那是因为 DataOutputStream 是一种格式化的数据输出方式,而并非都是字符流,如果写到文件中他的数据格式就和在内存中一样,这样他读出来是会很方便,我们打开文件看到的内容是字符编码的,int、boolean 不是字符编码的,所以会乱码。

然后使用 dataOutputStream.writeUTF 可以正确显示 是因为 UTF-8的字符编码是对的。

注意:
用 DataOutputStream 输出的数据并不是为了用记事本打开看的而是为了储存数据的 一般来保存为.dat文件区别开文本本件。

1.2.3 BufferedOutputStream

BufferedOutputStream 为 IO 写操作提供了带缓冲区的操作,提高了IO的性能。

使用如下:

public class BufferedOutputStreamDemo {
    public static void main(String[] args) throws IOException {

        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("test.txt"));
        bufferedOutputStream.write("BufferedOutputStreamDemo测试".getBytes("utf-8"));
        bufferedOutputStream.close();
    }

字节流先写这么多,还有很多 InputStream 和 OutputStream 的实现类,有兴趣的可以去看下。

接下来我们通过一个实例,看看使用哪种方式操作更快。

public class IoUtil {

    /**
     * 文件拷贝,单字节、不带缓冲进行文件拷贝
     *
     * @param srcFile
     * @param destFile
     * @throws IOException
     */
    public static void copyFileByByte(File srcFile, File destFile) throws IOException {
        if (!srcFile.exists()) {
            throw new IllegalArgumentException("srcFile is not exists!");
        }
        if (!srcFile.isFile()) {
            throw new IllegalArgumentException("srcFile is not files");
        }
        FileInputStream in = new FileInputStream(srcFile);
        FileOutputStream out = new FileOutputStream(destFile);
        int len;
        while ((len = in.read()) != -1) {
            out.write(len);
            out.flush();
        }
        in.close();
        out.close();
    }

    /**
     * 文件拷贝,字节批量读取
     *
     * @param srcFile
     * @param destFile
     * @throws IOException
     */
    public static void copyFile(File srcFile, File destFile) throws IOException {
        if (!srcFile.exists()) {
            throw new IllegalArgumentException("srcFile is not exists!");
        }
        if (!srcFile.isFile()) {
            throw new IllegalArgumentException("srcFile is not files");
        }
        FileInputStream in = new FileInputStream(srcFile);
        FileOutputStream out = new FileOutputStream(destFile);
        byte[] buf = new byte[8 * 1024];
        int len;
        while ((len = in.read(buf, 0, buf.length)) != -1) {
            out.write(buf, 0, len);
            out.flush();//最好加上
        }
        in.close();
        out.close();

    }

    /**
     * 文件拷贝,使用带缓冲的字节流进行
     *
     * @param srcFile
     * @param destFile
     */
    public static void copyFileByBuffer(File srcFile, File destFile) throws IOException {
        if (!srcFile.exists()) {
            throw new IllegalArgumentException("srcFile is not exists!");
        }
        if (!destFile.isFile()) {
            throw new IllegalArgumentException("srcFile is not files");
        }
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFile));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFile));
        int len;
        while ((len = bufferedInputStream.read()) != -1) {
            bufferedOutputStream.write(len);
            bufferedOutputStream.flush();
        }
        bufferedOutputStream.close();
        bufferedOutputStream.close();
    }
}

接下来我们分别使用三种方式拷贝一份文件,看看用时分别多少

测试代码:

public class Main {

    public static void main(String[] args) throws IOException {
        long start1 = System.currentTimeMillis();
        IoUtil.copyFileByByte(new File("alfred.dmg"), new File("alfred1.dmg"));
        long end1 = System.currentTimeMillis();
        System.out.println("使用单字节、不带缓冲拷贝文件用时:" + (end1 - start1));

        long start2 = System.currentTimeMillis();
        IoUtil.copyFile(new File("alfred.dmg"), new File("alfred2.dmg"));
        long end2 = System.currentTimeMillis();
        System.out.println("使用字节批量读取拷贝文件用时:" + (end2 - start2));
        
        long start3 = System.currentTimeMillis();
        IoUtil.copyFileByBuffer(new File("alfred.dmg"), new File("alfred3.dmg"));
        long end3 = System.currentTimeMillis();
        System.out.println("使用使用带缓冲的字节流拷贝文件用时:" + (end3 - start3));
    }
}

运行看看:

使用单字节、不带缓冲拷贝文件用时:28138
使用字节批量读取拷贝文件用时:8
使用使用带缓冲的字节流拷贝文件用时:22406

是不是很恐怖,使用字节批量读取的速度快的不可思议,所以拷贝文件时使用哪种方式,心里应该有数了。

总结下:
可以看到,字节流的实现类一般都是成对出现的,上面讲了三对:

  • FileInputStream 和 FileOutputStream
  • DataInputStream 和 DataOutputStream
  • BufferedInputStream 和 BufferedOutputStream

二、字符流

字符流又分为

  • 输入流 Writer
  • 输出流 Reader

输入流负责从 源(可以是文件读取数据到 Java程序 中。

输出流负责把 Java程序 中数据写入源(可以是文件) 中。

字节流和字符流很类似,Writer 和 Reader的实现类一般也是成对出现的。

2.1 OutputStreamWriter 和 InputStreamReader

使用如下:

public class IsrAndOswDemo {
    public static void main(String[] args) throws IOException {

        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("test1.txt"));
        outputStreamWriter.write("IsrAndOswDemo测试");
        outputStreamWriter.close();

        InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("test1.txt"));
        while (inputStreamReader.ready()) {
            System.out.print((char) inputStreamReader.read());
        }
        int len;
        char[] bytes = new char[8 * 1024];
        //批量读取,放入bytes这个字符数组,从第0个位置开始放置,最多放bytes.length个
        //返回的是读到的字符的个数
        while ((len = inputStreamReader.read(bytes, 0, bytes.length)) != -1) {
            String s = new String(bytes, 0, len);
            System.out.println(s);
        }
        inputStreamReader.close();
    }
}

输出:

IsrAndOswDemo测试

2.2 BufferedReader 、BufferedWriter、PrintWriter

下面的例子是通过 BufferedReader 读取 test1.txt 中的内容,然后分别写入到 test4.txt 和 test5.txt 中。

public class BrAndBwAndPwDemo {
    public static void main(String[] args) throws IOException {
        
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("test1.txt")));

        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test4.txt")));

        PrintWriter printWriter = new PrintWriter("test5.txt");

        String line;
        //一次读一行,不识别换行
        while ((line=bufferedReader.readLine())!=null){
            System.out.println(line);
            // 这里是BufferedWriter的写操作
            bufferedWriter.write(line);
            bufferedWriter.flush();
            bufferedWriter.newLine();//单独写出换行操作

            // 这里是PrintWriter的写操作
            printWriter.print(line);//不换行
            printWriter.println(line);//换行
        }
        bufferedReader.close();
        bufferedWriter.close();
        printWriter.close();
    }
}

三、RandomAccessFile 随机访问文件

RandomAccessFile 是 Java 提供的对文件内容的访问,既可以读文件,也可以写文件

RandomAccessFile 支持随机访问文件,可以访问文件的任意位置。

Java 文件模型

在硬盘上的文件是 byte byte byte 存储的,是数据的集合

打开文件方式

访问文件的两种方式:

  • 读写(rw)
  • 只读 (r)

比如创建一个可读写的 RandomAccessFile

RandomAccessFile raf = new RandomAccessFile(file,"rw");

创建好的时候, RandomAccessFile 的指针 pointer = 0,可以通过void seek(long pos)函数设置访问位置。通过long getFilePoint() 获得指针的位置

RandomAccessFile 包含 InputStream 的三个 read 方法,也包含 OutputStream 的三个 write 方法。同时 RandomAccessFile 还包含一系列的 readXxx 和 writeXxx 方法完成输入输出。

RandomAccessFile常用方

RandomAccessFile常用方法如下:

void write(int d)

该方法会根据当前指针所在位置处写入一个字节,是将参数int的”低8位”写出

int read()

如果返回-1表示读取到了文件末尾EOF(EOF:End Of File)! 每次读取后自动移动文件指针, 准备下次读取。

void write(byte[] d)

根据当前指针所在位置处连续写出给定数组中的所有字节

void write(byte b[], int off, int len)

根据当前指针所在位置处连续写出给定数组中的部分字节,这个部分是从数组的 off 处开始,连续 len 个字节

int read(byte[] b)

从文件中尝试最多读取给定数组的总长度的字节量,并从给定的字节数组第一个位置开始,将读取到的字节顺序存放至数组中,返回值为实际读取到的字节量

int read(byte b[], int off, int len)

根据当前指针所在位置连续读给定字节数据中的部分字节,这部分是从数组的 off 位置开始,连续读 len 个字节,最终放入字节数组中。

void close()

RandomAccessFile在对文件访问的操作全部结束后,要调用close()方法来释放与其关联的所有系统资源

其实 RandomAccessFile 的使用还是非常简单的,这里就不在实例展示了。

欢迎关注我的公众号:

我的公众号

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值