大数据开发基础入门与项目实战(一)Java SE之8.IO流

Java Stream 流 详解分类及与 IO 流的关联 是 Java 8 引入的函数式编程工具,它以「流式方式」对集合、数组等数据源进行流水线式的数据处理,无需手动编写循环,代码更简洁、高效,且支持并行处理提升性能。惰性求值:中间操作不会立即执行,只有调用终止操作时,才会执行整个流水线。不可变:操作不会修改原始数据源,只会返回处理后的新结果。一次性:流对象只能被消费一次,消费完毕后无法再次使用。Stream 流(处理内存数据)和IO 流(传输物理数据),名称相似但本质不同。 阅读详情

前言

本文主要介绍了Java中的IO流,包括IO流的基本概念、文件操作流缓冲操作流和其他流的内容。

1.IO流的概念

(1)IO流的概念和分类

IO是Input和Output的简写,也就是输入和输出的含义,用于进行数据的读和写。

IO流是指读写数据时像流水一样从一端流到另外一端,因此得名为“流”。

按照不同的维度,可以对IO流进行不同的分类,如下:

  • 按照读写数据的基本单位不同:

    - 字节流

        主要指以字节为单位进行数据读写的流,可以读写任意类型的文件 。

    - 字符流

        主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件 。

        之所以要使用字符流,是为了满足汉字等多字节文字的需要,提供了一个直接操作字符的接口,方便对字符进行流操作。

  • 按照读写数据的方向不同:

    - 输入流

        主要指从文件中读取数据内容输入到程序中,也就是读文件 。

    - 输出流

        主要指将程序中的数据内容输出到文件中,也就是写文件 。

  • 按照流的角色不同:

    - 节点流

        主要指直接和输入输出源对接的流。

    - 处理流

        主要指需要建立在节点流的基础之上的流。

(2)IO流的框架结构

IO流的框架结构如下:

java io structure

其中,最顶层是抽象基类;

下面分别是处理文件、数组、管道、字符串、缓冲、对象、打印、输入和基本数据类型的流。

最核心的IO流如下:
java key io

2.文件操作流

(1)FileWriter类的概念和使用

java.io.FileWriter类主要用于将文本内容写入到文本文件。

FileWriter类中常用的方法如下:

方法声明功能介绍
FileWriter(String fileName)根据参数指定的文件名构造对象
FileWriter(String fileName, boolean append)以追加的方式根据参数指定的文件名来构造对象
void write(int c)写入单个字符
void write(char[] cbuf, int off, int len)将指定字符数组中从偏移量off开始的len个字符写入此文件输出流
void write(char[] cbuf)将cbuf字符数组写入文件输出流中
void flush()刷新流
void close()关闭流对象并释放有关的资源

先测试普通创建模式:

package com.stage1.module4;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/4 17:06
 * @description javase-com.stage1.module4
 */
public class FileWriterTest {

    public static void main(String[] args) {
        FileWriter fw = null;

        try {
            // 1.构造FileWriter类型对象与E:/Test/tmp.txt文件关联
            fw = new FileWriter("E:/Test/tmp.txt");

            // 2.通过流对象写入数据内容
            fw.write('a');
            System.out.println("写入数据成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fw) {
                // 3.关闭流对象并释放有关的资源
                try {
                    fw.close();                         // 自带刷新流的功能
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

写入数据成功

此时在E:/Test下会新建文件,同时文件的内容为单个a;

可以得到,选择FileWriter(String fileName)构造方法时,若文件不存在,FileWriter流会自动创建新的空文件,若文件存在则会清空原有的内容。

再测试追加模式,如下:

package com.stage1.module4;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/4 17:06
 * @description javase-com.stage1.module4
 */
public class FileWriterTest {

    public static void main(String[] args) {
        FileWriter fw = null;

        try {
            // 1.构造FileWriter类型对象与E:/Test/tmp.txt文件关联
            // fw = new FileWriter("E:/Test/tmp.txt");
            fw = new FileWriter("E:/Test/tmp.txt", true);

            // 2.通过流对象写入数据内容
            fw.write('a');
            System.out.println("写入数据成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fw) {
                // 3.关闭流对象并释放有关的资源
                try {
                    fw.close();                         // 自带刷新流的功能
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

写入数据成功

多次执行后,会看到文件中的内容为多个a,说明此时多次写的时候是在后面追加内容的;

选择FileWriter(String fileName, true)构造方法时,是以追加的方式去创建对象,若文件不存在则自动创建新的空文件并写入内容,若文件存在则保留原有的数据内容、并在后面追加内容。

再测试写入字符数组,如下:

package com.stage1.module4;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/4 17:06
 * @description javase-com.stage1.module4
 */
public class FileWriterTest {

    public static void main(String[] args) {
        FileWriter fw = null;

        try {
            // 1.构造FileWriter类型对象与E:/Test/tmp.txt文件关联
            // fw = new FileWriter("E:/Test/tmp.txt");
            fw = new FileWriter("E:/Test/tmp.txt", true);

            // 2.通过流对象写入数据内容
            fw.write('a');

            // 3.写入数组
            // 声明字符数组并初始化
            char[] cArr = new char[]{'C', 'o', 'r', 'l', 'e', 'y'};
            // 写入部分数组
            fw.write(cArr, 1, 3);
            // 写入整个数组
            fw.write(cArr);

            // 刷新流
            fw.flush();

            System.out.println("写入数据成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fw) {
                // 3.关闭流对象并释放有关的资源
                try {
                    fw.close();                         // 自带刷新流的功能
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

写入数据成功

再查看文件,内容为aaaaaorlCorley,说明成功写入了字符数组。

其中,write()方法每写入一个字符后文件中的读写位置后移一位。

(2)FileReader类的概念和使用

java.io.FileReader类主要用于从文本文件读取文本数据内容。

FileReader类常用的方法如下:

方法声明功能介绍
FileReader(String fileName)根据参数指定的文件名构造对象
int read()读取单个字符的数据并返回,返回-1表示读取到末尾
int read(char[] cbuf, int offset, int length)从输入流中将最多length个字符数据读入字符数组cbuf中offset开始的位置,返回读取到的字符个数,返回-1表示读取到末尾
int read(char[] cbuf)从输入流中将字符数据读入字符数组cbuf中,返回读取到的字符个数,返回-1表示读取到末尾
void close()关闭流对象并释放有关的资源

先进行简单使用如下:

package com.stage1.module4;

import java.io.FileReader;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/4 18:51
 * @description javase-com.stage1.module4
 */
public class FileReaderTest {

    public static void main(String[] args) {
        FileReader fr = null;

        try {
            // 1.构造FileReader类型的对象与E:/Test/tmp.txt文件关联
            fr = new FileReader("E:/Test/tmp.txt");

            // 2.读取数据内容
            int res = fr.read();
            System.out.println("读取到的单个字符是:" + (char) res);
            while ((res = fr.read()) != -1) {
                System.out.println("读取到的单个字符是:" + (char) res);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fr) {
                // 3.关闭流对象并释放有关的资源
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是:o
读取到的单个字符是:r
读取到的单个字符是:l
读取到的单个字符是:C
读取到的单个字符是:o
读取到的单个字符是:r
读取到的单个字符是:l
读取到的单个字符是:e
读取到的单个字符是:y

可以看到,实现了读取单个字符,并遍历读完了所有字符。

再使用其他方法,如下:

package com.stage1.module4;

import java.io.FileReader;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/4 18:51
 * @description javase-com.stage1.module4
 */
public class FileReaderTest {

    public static void main(String[] args) {
        FileReader fr = null;

        try {
            // 1.构造FileReader类型的对象与E:/Test/tmp.txt文件关联
            fr = new FileReader("E:/Test/tmp.txt");

            // 2.读取数据内容
            /*int res = fr.read();
            System.out.println("读取到的单个字符是:" + (char) res);
            while ((res = fr.read()) != -1) {
                System.out.println("读取到的单个字符是:" + (char) res);
            }*/
            // 定义一个字符数组,用于保存数据内容
            char[] cArr = new char[5];
            // 读取字符数组的部分空间,读取3个字符放入数组cArr中下标从1开始的位置上
            int res = fr.read(cArr, 1, 3);
            System.out.println("实际读取到的字符个数为:" + res);
            for (char c : cArr) {
                System.out.println("读取到的单个字符是:" + (char) c);
            }
            System.out.println("---------------------------------------");
            // 读满整个字符数组
            fr = new FileReader("E:/Test/tmp.txt");
            res = fr.read(cArr);
            System.out.println("实际读取到的字符个数为:" + res);
            for (char c : cArr) {
                System.out.println("读取到的单个字符是:" + (char) c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fr) {
                // 3.关闭流对象并释放有关的资源
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

实际读取到的字符个数为:3
读取到的单个字符是: 
读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是: 
---------------------------------------
实际读取到的字符个数为:5
读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是:a
读取到的单个字符是:a

可以看到,将读取到的内容存到了数组中。

(3)文件字符流实现文件的拷贝

可以使用FileReader和FileWriter类实现文件的拷贝。

如下:

package com.stage1.module4;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/5 7:20
 * @description javase-com.stage1.module4
 */
public class FileCharCopyTest {

    public static void main(String[] args) {
        FileReader reader = null;
        FileWriter writer = null;

        try {
            // 1.创建FileReader类型的对象与E:/Test/a.txt文件关联
            reader = new FileReader("E:/Test/a.txt");

            // 2.创建FileWriter类型的对象与E:/Test/b.txt文件关联
            writer = new FileWriter("E:/Test/b.txt");

            // 3.不断地从输入流中读取数据内容并写入到输出流中
            System.out.println("开始拷贝...");
            int res = 0;
            while ((res = reader.read()) != -1) {
                writer.write(res);
            }
            System.out.println("拷贝成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放相关的资源
            if (null != writer) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != reader) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

开始拷贝...
拷贝成功!

查看本地文件,新建了b.txt文件,并将a.txt中的内容拷贝到了b.txt中。

关闭IO流的顺序没有明确的规定, 但是一般情况下先创建的流对象后关闭、后创建的流对象先关闭。

(4)文件字节流实现文件的拷贝

前面通过字符流实现了文本文件的拷贝,但是不能成功实现二进制文件(图片、音视频文件等)的拷贝、拷贝得到的文件无效,此时需要使用文件字节流类来实现。

java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中,常用方法如下:

方法声明功能介绍
FileOutputStream(String name)根据参数指定的文件名来构造对象
FileOutputStream(String name, boolean append)以追加的方式根据参数指定的文件名来构造对象
void write(int b)将指定字节写入文件输出流
void write(byte[] b, int off, int len)将指定字节数组中从偏移量off开始的len个字节写入文件输出流
void write(byte[] b)将指定字节数组中的全部字节数据写入文件输出流中
void flush()刷新此输出流并强制写出任何缓冲的输出字节
void close()关闭流对象并释放有关的资源

java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等,常用方法如下:

方法声明功能介绍
FileInputStream(String name)根据参数指定的文件路径名来构造对象
int read()从输入流中读取单个字节的数据并返回,返回-1表示读取到末尾
int read(byte[] b, int off, int len)从输入流中将最多len个字节的数据读入字节数组中off开始的位置,返回读取到的字节个数,返回-1表示读取到末尾
int read(byte[] b)从输入流中将最多 b.length 个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾
void close()关闭流对象并释放有关的资源
int available()获取输入流所关联文件的大小

文件字节流和文件字符流的方法使用类似。

现在使用文件字节流实现二进制文件的拷贝,如下:

package com.stage1.module4;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/5 7:41
 * @description javase-com.stage1.module4
 */
public class FileByteCopyTest {

    public static void main(String[] args) {
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;

        try {
            // 1.创建FileInputStream类型的对象与E:/Test/io-stream.png文件关联
            inputStream = new FileInputStream("E:/Test/io-stream.png");

            // 2.创建FileOutputStream类型的对象与E:/Test/io-stream-2.png文件关联
            outputStream = new FileOutputStream("E:/Test/io-stream-2.png");

            // 3.不断从输入流中读取数据内容并写到输出流中
            System.out.println("开始拷贝...");
            int res = 0;
            while (-1 != (res = inputStream.read())) {
                outputStream.write(res);
            }
            System.out.println("拷贝成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放相关的资源
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

开始拷贝...
拷贝成功!

可以看到,成功将二进制文件图片拷贝到副本中,并且查看本地会发现文件是有效的。

(5)文件字节流拷贝文件的分析和改进

前面使用文件字节流方式拷贝文件时,是以单个字节为单位进行拷贝,也就是每次读写一个字节后再写入另一个字节,当文件较小时,可以正常拷贝,但是当文件较大时拷贝的效率很低、需要花很长时间才能复制成功。

此时可以进行改进,准备一个和文件大小一样的缓冲区,一次性将文件的所有内容读入到缓冲区,然后一次性写入输出流。

如下:

package com.stage1.module4;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/5 7:41
 * @description javase-com.stage1.module4
 */
public class FileByteCopyTest {

    public static void main(String[] args) {
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;

        try {
            // 1.创建FileInputStream类型的对象与E:/Test/凤凰于飞.flac文件关联
            inputStream = new FileInputStream("E:/Test/凤凰于飞.flac");

            // 2.创建FileOutputStream类型的对象与E:/Test/凤凰于飞-2.flac文件关联
            outputStream = new FileOutputStream("E:/Test/凤凰于飞-2.flac");

            // 3.不断从输入流中读取数据内容并写道输出流中
            System.out.println("开始拷贝...");
            // 方式一:以单个字节为单位进行拷贝,大文件效率较低
            /*int res = 0;
            while (-1 != (res = inputStream.read())) {
                outputStream.write(res);
            }*/
            // 方式二:使用和文件大小相同的缓冲区,先读入缓冲区、再写入输出流
            int size = inputStream.available();
            System.out.println("获取到的文件大小:" + size);
            byte[] bArr = new byte[size];
            int res = inputStream.read(bArr);
            System.out.println("实际读取到的文件大小:" + res);
            outputStream.write(bArr);
            System.out.println("拷贝成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放相关的资源
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

开始拷贝...
获取到的文件大小:37562183
实际读取到的文件大小:37562183
拷贝成功!

此时,再拷贝大文件时,效率更高。

但是这种方式也有一些缺点,当文件过大时,需要申请和文件大小一样的缓冲区,会占用较大的内存,甚至会出现真实物理内存不足的的情况。

再进行优化,可以准备一个大小相对适当的缓冲区(可以以1024的整数倍作为缓冲区大小),分多次完成文件拷贝。

如下:

package com.stage1.module4;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/5 7:41
 * @description javase-com.stage1.module4
 */
public class FileByteCopyTest {

    public static void main(String[] args) {
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;

        try {
            // 1.创建FileInputStream类型的对象与E:/Test/凤凰于飞.flac文件关联
            inputStream = new FileInputStream("E:/Test/凤凰于飞.flac");

            // 2.创建FileOutputStream类型的对象与E:/Test/凤凰于飞-2.flac文件关联
            outputStream = new FileOutputStream("E:/Test/凤凰于飞-2.flac");

            // 3.不断从输入流中读取数据内容并写道输出流中
            System.out.println("开始拷贝...");
            // 方式一:以单个字节为单位进行拷贝,大文件效率较低
            /*int res = 0;
            while (-1 != (res = inputStream.read())) {
                outputStream.write(res);
            }*/
            // 方式二:使用和文件大小相同的缓冲区,先读入缓冲区、再写入输出流,文件过大时会占用较大内存
            /*int size = inputStream.available();
            System.out.println("获取到的文件大小:" + size);
            byte[] bArr = new byte[size];
            int res = inputStream.read(bArr);
            System.out.println("实际读取到的文件大小:" + res);
            outputStream.write(bArr);*/
            // 方式三:准备大小适当的缓冲区,多次完成拷贝
            byte[] bArr = new byte[1024 * 5];
            int res = 0;
            while (-1 != (res = inputStream.read(bArr))) {
                outputStream.write(bArr, 0, res);
            }
            System.out.println("拷贝成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放相关的资源
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

开始拷贝...
拷贝成功!

显然,这种方式更合理、安全,效率也更高。

3.缓冲操作流

(1)缓冲字节流实现文件的拷贝

前面的方式是使用自定义缓冲区来提高拷贝的效率,也可以使用Java官方提供的缓冲字节流来实现。

java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层系统,常用方法如下:

方法声明功能介绍
BufferedOutputStream(OutputStream out)根据参数指定的引用构造对象
BufferedOutputStream(OutputStream out, int size)根据参数指定的引用和缓冲区大小构造对象
void write(int b)写入单个字节
void write(byte[] b, int off, int len)写入字节数组中的一部分数据
void write(byte[] b)写入参数指定的整个字节数组
void flush()刷新流
void close()关闭流对象并释放有关的资源

java.io.BufferedInputStream类主要用于描述缓冲输入流,常用方法如下:

方法声明功能介绍
BufferedInputStream(InputStream in)根据参数指定的引用构造对象
BufferedInputStream(InputStream in, int size)根据参数指定的引用和缓冲区大小构造对象
int read()读取单个字节
int read(byte[] b, int off, int len)读取len个字节到b数组中
int read(byte[] b)读取b.length个字节到b个数组中
void close()关闭流对象并释放有关的资源

现在再使用缓冲字节流实现文件拷贝,如下:

package com.stage1.module4;

import java.io.*;

/**
 * @author Corley
 * @date 2021/8/5 8:43
 * @description javase-com.stage1.module4
 */
public class BufferedByteCopyTest {

    public static void main(String[] args) {
        BufferedInputStream inputStream = null;
        BufferedOutputStream outputStream = null;

        try {
            // 1.创建BufferedInputStream类型的对象与E:/Test/凤凰于飞.flac文件关联
            inputStream = new BufferedInputStream(new FileInputStream("E:/Test/凤凰于飞.flac"));

            // 2.创建BufferedOutputStream类型的对象与E:/Test/凤凰于飞-2.flac文件关联
            outputStream = new BufferedOutputStream(new FileOutputStream("E:/Test/凤凰于飞-2.flac"));

            // 3.不断从输入流中读取数据并写入到输出流中
            System.out.println("开始拷贝...");
            byte[] bArr = new byte[1024 * 3];
            int res = 0;
            while (-1 != (res = inputStream.read(bArr))) {
                outputStream.write(bArr, 0, res);
            }
            System.out.println("拷贝成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关的资源
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

执行效果与前面相同;

查看源码可以看到,BufferedInputStream提供了默认的缓冲区大小DEFAULT_BUFFER_SIZE,为8192,也可以在构造BufferedInputStream对象时自定义缓冲区的大小。

(2)缓冲字节流和文件字节流效率比较

现在通过计算文件字节流和缓冲字节流的运行时间,来比较两者的执行效率。

文件字节流测试如下:

package com.stage1.module4;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/5 7:41
 * @description javase-com.stage1.module4
 */
public class FileByteCopyTest {

    public static void main(String[] args) {

        // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数
        long t1 = System.currentTimeMillis();

        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;

        try {
            // 1.创建FileInputStream类型的对象与E:/Test/demo.mp4文件关联
            inputStream = new FileInputStream("E:/Test/demo.mp4");

            // 2.创建FileOutputStream类型的对象与E:/Test/demo-2.mp4文件关联
            outputStream = new FileOutputStream("E:/Test/demo-2.mp4");

            // 3.不断从输入流中读取数据内容并写道输出流中
            System.out.println("开始拷贝...");
            // 方式一:以单个字节为单位进行拷贝,大文件效率较低
            /*int res = 0;
            while (-1 != (res = inputStream.read())) {
                outputStream.write(res);
            }*/
            // 方式二:使用和文件大小相同的缓冲区,先读入缓冲区、再写入输出流,文件过大时会占用较大内存
            /*int size = inputStream.available();
            System.out.println("获取到的文件大小:" + size);
            byte[] bArr = new byte[size];
            int res = inputStream.read(bArr);
            System.out.println("实际读取到的文件大小:" + res);
            outputStream.write(bArr);*/
            // 方式三:准备大小适当的缓冲区,多次完成拷贝
            byte[] bArr = new byte[1024];
            int res = 0;
            while (-1 != (res = inputStream.read(bArr))) {
                outputStream.write(bArr, 0, res);
            }
            System.out.println("拷贝成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放相关的资源
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        long t2 = System.currentTimeMillis();
        System.out.println("使用文件字节流拷贝文件消耗的时间:" + (t2 - t1));

    }
}

输出:

开始拷贝...
拷贝成功!
使用文件字节流拷贝文件消耗的时间:1343

缓冲字节流测试如下:

package com.stage1.module4;

import java.io.*;

/**
 * @author Corley
 * @date 2021/8/5 8:43
 * @description javase-com.stage1.module4
 */
public class BufferedByteCopyTest {

    public static void main(String[] args) {

        // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数
        long t1 = System.currentTimeMillis();

        BufferedInputStream inputStream = null;
        BufferedOutputStream outputStream = null;

        try {
            // 1.创建BufferedInputStream类型的对象与E:/Test/demo.mp4文件关联
            inputStream = new BufferedInputStream(new FileInputStream("E:/Test/demo.mp4"));

            // 2.创建BufferedOutputStream类型的对象与E:/Test/demo-2.mp4文件关联
            outputStream = new BufferedOutputStream(new FileOutputStream("E:/Test/demo-2.mp4"));

            // 3.不断从输入流中读取数据并写入到输出流中
            System.out.println("开始拷贝...");
            byte[] bArr = new byte[1024];
            int res = 0;
            while (-1 != (res = inputStream.read(bArr))) {
                outputStream.write(bArr, 0, res);
            }
            System.out.println("拷贝成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关的资源
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        long t2 = System.currentTimeMillis();
        System.out.println("使用缓冲字节流拷贝文件消耗的时间:" + (t2 - t1));

    }
}

输出:

开始拷贝...
拷贝成功!
使用缓冲字节流拷贝文件消耗的时间:337

可以看到,在拷贝相同的文件时,文件字节流需要1343毫秒、缓冲字节流需要337毫秒,执行时间存在一定的差距。

所以缓冲字节流拷贝文件的效率更高,在以后的使用中推荐使用缓冲字节流。

(3)缓冲字符流的使用

java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中,常用方法如下:

方法声明功能介绍
BufferedWriter(Writer out)根据参数指定的引用来构造对象
BufferedWriter(Writer out, int sz)根据参数指定的引用和缓冲区大小来构造对象
void write(int c)写入单个字符到输出流中
void write(char[] cbuf, int off, int len)将字符数组cbuf中从下标off开始的len个字符写入输出流中
void write(char[] cbuf)将字符串数组cbuf中所有内容写入输出流中
void write(String s, int off, int len)将参数s中下标从off开始的len个字符写入输出流中
void write(String str)将参数指定的字符串内容写入输出流中
void newLine()用于写入行分隔符到输出流中
void flush()刷新流
void close()关闭流对象并释放有关的资源

java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串,常用方法如下:

方法声明功能介绍
BufferedReader(Reader in)根据参数指定的引用来构造对象
BufferedReader(Reader in, int sz)根据参数指定的引用和缓冲区大小来构造对象
int read()从输入流读取单个字符,读取到末尾则返回-1,否则返回实际读取到的字符内容
int read(char[] cbuf, int off, int len)从输入流中读取len个字符放入数组cbuf中下标从off开始的位置上,若读取到末尾则返回-1,否则返回实际读取到的字符个数
int read(char[] cbuf)从输入流中读满整个数组cbuf
String readLine()读取一行字符串并返回,返回null表示读取到末尾
void close()关闭流对象并释放有关的资源

使用缓冲字符流实现文本拷贝,如下:

package com.stage1.module4;

import java.io.*;

/**
 * @author Corley
 * @date 2021/8/5 9:18
 * @description javase-com.stage1.module4
 */
public class BufferedCharCopyTest {

    public static void main(String[] args) {
        BufferedReader reader = null;
        BufferedWriter writer = null;

        try {
            // 1.创建BufferedReader类型的对象与E:/Test/a.txt文件关联
            reader = new BufferedReader(new FileReader("E:/Test/a.txt"));

            // 2.创建BufferedWriter类型的对象与E:/Test/b.txt文件关联
            writer = new BufferedWriter(new FileWriter("E:/Test/b.txt"));

            // 3.不断地从输入流中读取一行字符串并写入到输出流中
            System.out.println("开始拷贝...");
            String str = null;
            while (null != (str = reader.readLine())) {
                writer.write(str);
                writer.newLine();
            }
            System.out.println("拷贝成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关的资源
            if (null != writer) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != reader) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

开始拷贝...
拷贝成功!

查看本地文件可以发现,将a.txt中的内容逐行读取并写入到b.txt中;

同时因为b.txt中最后一行后面换行,即添加了换行符\n、占2字节,所以会比a.txt中多一个字节。

3.其他流

(1)打印流和转换流模拟聊天的功能实现

java.io.PrintStream类主要用于更加方便地打印各种数据内容,常用方法如下:

方法声明功能介绍
PrintStream(OutputStream out)根据参数指定的引用来构造对象
void print(String s)用于将参数指定的字符串内容打印出来
void println(String x)用于打印字符串后并终止该行
void flush()刷新流
void close()用于关闭输出流并释放有关的资源

可以看到,PrintStream类除了可以向之前经常使用到的控制台打印,还可以向文件写入。

java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流,常用方法如下:

方法声明功能介绍
PrintWriter(Writer out)根据参数指定的引用来构造对象
void print(String s)将参数指定的字符串内容打印出来
void println(String x)打印字符串后并终止该行
void flush()刷新流
void close()关闭流对象并释放有关的资源

java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换,常用方法如下:

方法声明功能介绍
OutputStreamWriter(OutputStream out)根据参数指定的引用来构造对象
OutputStreamWriter(OutputStream out, String charsetName)根据参数指定的引用和编码构造对象
void write(String str)将参数指定的字符串写入
void flush()刷新流
void close()用于关闭输出流并释放有关的资源

java.io.InputStreamReader类主要用于实现从字节流到字符流的转换,如下:

方法声明功能介绍
InputStreamReader(InputStream in)根据参数指定的引用构造对象
InputStreamReader(InputStream in, String charsetName)根据参数指定的引用和编码构造对象
int read(char[] cbuf)读取字符数据到参数指定的数组
void close()用于关闭输出流并释放有关的资源

案例题目:
不断地提示用户输入要发送的内容,若发送的内容是"bye"则聊天结束,否则将用户输入的内容写入到文件E:/Test/chat.txt中。
要求使用BufferedReader类来读取键盘的输入 System.in代表键盘输入;
要求使用PrintStream类负责将数据写入文件。

先实现基本功能如下:

package com.stage1.module4;

import java.io.*;

/**
 * @author Corley
 * @date 2021/8/5 9:59
 * @description javase-com.stage1.module4
 */
public class PrintStreamChatTest {

    public static void main(String[] args) {

        BufferedReader reader = null;
        PrintStream printStream = null;
        try {
            reader = new BufferedReader(new InputStreamReader(System.in));
            printStream = new PrintStream(new FileOutputStream("E:/Test/chat.txt"));

            while (true) {
                // 1.提示用户输入要发送的聊天内容
                System.out.println("请输入要发送的聊天内容:");
                String str = reader.readLine();

                // 2.判断用户输入的内容是否是bye,如果是则结束
                if ("bye".equals(str)) {
                    System.out.println("聊天结束!");
                    break;
                }

                // 3.若不是则将用户输入的内容保存到E:/Test/chat.txt中
                printStream.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放所有的资源
            if (null != printStream) {
                printStream.close();
            }
            if (null != reader) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

请输入要发送的聊天内容:
123
请输入要发送的聊天内容:
abc
请输入要发送的聊天内容:
bye
聊天结束!

程序执行结束后,查看本地可以看到新建的文本文件,内容为输入的聊天内容;

因为BufferedReader类的构造方法需要传入Reader类型的引用,但Reader类是个抽象类,实参只能传递子类的对象,属于字符流,而System.in代表健盘输入,是InputStream类型,属于字节流,类型不同,需要将字节流类型转换为字符流类型,此时就需要用到转换流中的InputStreamReader类。

现在进一步实现聊天双方切换聊天的功能,如下:

package com.stage1.module4;

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author Corley
 * @date 2021/8/5 9:59
 * @description javase-com.stage1.module4
 */
public class PrintStreamChatTest {

    public static void main(String[] args) {

        BufferedReader reader = null;
        PrintStream printStream = null;
        String dateTime = null;
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        try {
            reader = new BufferedReader(new InputStreamReader(System.in));
            printStream = new PrintStream(new FileOutputStream("E:/Test/chat.txt", true));

            // 声明boolean类型的遍历作为发送方切换的标志
            boolean flag = true;

            while (true) {
                // 1.提示用户输入要发送的聊天内容
                System.out.println("请" + (flag ? "Corley" : "  Jack") + "输入要发送的聊天内容:");
                String str = reader.readLine();

                // 2.判断用户输入的内容是否是bye,如果是则结束
                if ("bye".equals(str)) {
                    System.out.println("聊天结束!");
                    break;
                }

                // 3.若不是则将用户输入的内容保存到E:/Test/chat.txt中
                // 获取当前系统时间并调整格式
                dateTime = LocalDateTime.now().format(timeFormatter);
                printStream.println(dateTime + (flag ? " Corley" : "     Jack") + "说:" + str);

                flag = !flag;
            }

            printStream.println();
            printStream.println();
            printStream.println();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放所有的资源
            if (null != printStream) {
                printStream.close();
            }
            if (null != reader) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

请Corley输入要发送的聊天内容:
1
请  Jack输入要发送的聊天内容:
one
请Corley输入要发送的聊天内容:
2
请  Jack输入要发送的聊天内容:
two
请Corley输入要发送的聊天内容:
13
请  Jack输入要发送的聊天内容:
thirteen
请Corley输入要发送的聊天内容:
bye
聊天结束!

查看本地文件,如下:

2021-08-05 10:56:52 Corley说:1
2021-08-05 10:56:54     Jack说:one
2021-08-05 10:56:55 Corley说:2
2021-08-05 10:56:57     Jack说:two
2021-08-05 10:56:59 Corley说:13
2021-08-05 10:57:29     Jack说:thirteen

(2)字符编码

计算机只能识别二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要将各个国家的文字采用数字编号的方式进行描述并建立对应的关系表,该表就叫做编码表。

常用的编码表如下:

编码表说明
ASCII美国标准信息交换码, 使用一个字节的低7位二位进制进行表示
ISO8859-1拉丁码表,欧洲码表,使用一个字节的8位二进制进行表示
GB2312中国的中文编码表,最多使用两个字节16位二进制进行表示
GBK中国的中文编码表升级,融合了更多的中文字符,最多使用两个字节16位二进制位表示
Unicode国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的文字都用两个字节16位二进制位来表示

其中,GB2312和GBK编码最多使用2个字节来表示1个中文字符,也有使用1个字节来表示,为了便于区分,最高位为0表示用1个字节来表示、最高位为1表示用2个字节来表示。

Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。但是因为在网络上传输的问题,Unicode在很长一段时间内无法推广,面向传输的众多 UTF(UCS Transfer Format)标准的出现很好地解决了这个问题,推荐的Unicode编码是UTF-8和UTF-16,UTF-8每次8个位传输数据、UTF-16每次16个位。

UTF-8是为传输而设计的编码,并使编码无国界,包含了全世界上所有文化的字符。作为一种变长的编码方式,UTF-8可用1-4个字节来表示一个字符,其中最高位是0表示用1个字节来表示、最高位是110表示用2个字节来表示、最高位是1110表示用3个字节来表示。

(3)数据流的概念和使用

java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中,常用方法如下:

方法声明功能介绍
DataOutputStream(OutputStream out)根据参数指定的引用构造对象, OutputStream类是抽象类,实参需要传递子类对象
void writeInt(int v)用于将参数指定的整数一次性写入输出流,优先写入高字节
void close()用于关闭文件输出流并释放有关的资源

java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据,常用方法如下:

方法声明功能介绍
DataInputStream(InputStream in)根据参数指定的引用来构造对象, InputStream类是抽象类,实参需要传递子类对象
int readInt()用于从输入流中一次性读取一个整数数据并返回
void close()用于关闭文件输出流并释放有关的资源

先测试数据输出流,如下:

package com.stage1.module4;

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/5 11:25
 * @description javase-com.stage1.module4
 */
public class DataOutputStreamTest {

    public static void main(String[] args) {
        DataOutputStream outputStream = null;

        try {
            // 1.创建DataOutputStream类型的对象与E:/Test/tmp.txt文件关联
            outputStream = new DataOutputStream(new FileOutputStream("E:/Test/tmp.txt"));

            // 2.将基本类型数据写入输出流
            int num = 111;
            outputStream.writeInt(num);
            boolean b = false;
            outputStream.writeBoolean(b);
            System.out.println("写入数据成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放相关的资源
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

写入数据成功!

查看文件,可以看到,文本中为o,即前面有3个空格,这是因为int类型为4个字节,111对应的高位(前面3个字节)均为0,ASCII码0对应的就是空字符Null,ASCII码111对应的字符为o;

数据输出流DataOutputStream用1个字节byte的0或1表示false和true,所以false对应的ASCII码为0,对应的字符也是空字符Null;

outputStream.writeInt()是写入4个字节,outputStream.write()是写入1个字节,两者有所区别。

再测试数据输入流,如下:

package com.stage1.module4;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author Corley
 * @date 2021/8/5 11:41
 * @description javase-com.stage1.module4
 */
public class DataInputStreamTest {

    public static void main(String[] args) {
        DataInputStream inputStream = null;

        try {
            // 1.创建DataInputStream类型的对象与E:/Test/tmp.txt文件关联
            inputStream = new DataInputStream(new FileInputStream("E:/Test/tmp.txt"));

            // 2.从输入流中读取数据
            int i = inputStream.readInt();
            System.out.println("i = " + i);
            boolean b = inputStream.readBoolean();
            System.out.println("b = " + b);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放所有的资源
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

i = 111
b = false

可以看到,读出了之前写入的数据;

需要注意,如果DataOutputStream调用的是writeInt()方法,则DataInputStream也要调用对应的readInt()方法;如果DataOutputStream调用的是write()方法,则DataInputStream也要调用对应的read()方法,否则会读到错误的数据。

(4)对象流的概念和使用

java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。

只能将支持 java.io.Serializable 接口的对象写入流中,类通过实现 Serializable 接口来启用其序列化功能 。

其中,序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。

ObjectOutputStream类常用方法如下:

方法声明功能介绍
ObjectOutputStream(OutputStream out)根据参数指定的引用来构造对象
void writeObject(Object obj)用于将参数指定的对象整体写入到输出流中
void close()用于关闭输出流并释放有关的资源

java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来,即反序列化。

其中,反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。

ObjectInputStream类常用方法如下:

方法声明功能介绍
ObjectInputStream(InputStream in)根据参数指定的引用来构造对象
Object readObject()主要用于从输入流中读取一个对象并返回,无法通过返回值来判断是否读取到文件的末尾
void close()用于关闭输入流并释放有关的资源

序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的:

在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会发生序列化版本不一致的异常(InvalidCastException)。

transient是Java语言的关键字,用来表示一个成员不是该对象串行化的一部分,当一个对象被串行化的时候,transient型变量的值不包括在串行化的范围之内,非transient型的变量是被包含在串行化的范围之内的。

先测试ObjectOutputStream,User类如下:

package com.stage1.module4;

/**
 * @author Corley
 * @date 2021/8/5 12:02
 * @description javase-com.stage1.module4
 */
public class User implements java.io.Serializable {

    private static final long serialVersionUID = 6439331742069744321L;
    private String userName;                // 用户名
    private String password;                // 密码
    private transient String phoneNum;      // 手机号,不被序列化

    public User() {
    }

    public User(String userName, String password, String phoneNum) {
        this.userName = userName;
        this.password = password;
        this.phoneNum = phoneNum;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", phoneNum='" + phoneNum + '\'' +
                '}';
    }
}

测试类如下:

package com.stage1.module4;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * @author Corley
 * @date 2021/8/5 12:04
 * @description javase-com.stage1.module4
 */
public class ObjectOutputStreamTest {

    public static void main(String[] args) {
        ObjectOutputStream outputStream = null;

        try {
            // 1.创建ObjectOutputStream类型的对象与E:/Test/tmp.txt文件关联
            outputStream = new ObjectOutputStream(new FileOutputStream("E:/Test/tmp.txt"));

            // 2.创建一个User类型的对象并初始化
            User user = new User("Corley", "123456", "13122223333");

            // 3.将User对象写入输出流
            outputStream.writeObject(user);
            System.out.println("写入对象成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关的资源
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

写入对象成功!

查看文件可以看到,对象的信息已经保存到文件中,虽然是乱码,但是还是可以看到一些关键的信息;

如果User类没有实现Serializable接口开启序列化,会发生java.io.NotSerializableException异常;

实现Serializable接口时建议显式声明serialVersionUID值,用于序列化时将每个可序列化类与版本号相关联,在反序列化时使用该版本号来验证序列化对象的发送方和接收方是否已加载与该序列化兼容的该对象的类。

再测试ObjectInputStream类,如下:

package com.stage1.module4;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * @author Corley
 * @date 2021/8/5 12:19
 * @description javase-com.stage1.module4
 */
public class ObjectInputStreamTest {

    public static void main(String[] args) {
        ObjectInputStream inputStream = null;

        try {
            // 1.创建ObjectInputStream类型的对象与E:/Test/tmp.txt文件关联
            inputStream = new ObjectInputStream(new FileInputStream("E:/Test/tmp.txt"));

            // 2.从输入流中读取对象
            Object object = inputStream.readObject();
            System.out.println("获取到的对象是:" + object);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放有关的资源
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

获取到的对象是:User{userName='Corley', password='123456', phoneNum='null'}

可以看懂, 获取到了之前保存的对象信息;

因为User类中phoneNum变量被transient关键字修饰,所以不被序列化、未写入到文件中,所以反序列化时的值是null。

需要注意:

ObjectInputStream类的readObject()方法无法通过返回值判断是否读到文件末尾。所以当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,从而避免了通过返回值进行是否达到文件末尾的判断。

(5)RandomAccessFile类的概念和使用

java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。

前面的IO流都是从文件开头开始进行读写,再向后移动读写位置;

但是有时候需要从文件中间的某个位置开始读写,此时就需要用到RandomAccessFile类。

RandomAccessFile类常用的方法如下:

方法声明功能介绍
RandomAccessFile(String name, String mode)根据参数指定的名称和模式构造对象:
(1)r:以只读方式打开
(2)rw:打开以便读取和写入
(3)rwd:打开以便读取和写入,同步文件内容的更新
(4)rws:打开以便读取和写入,同步文件内容和元数据的更新
int read()读取单个字节的数据
void seek(long pos)用于设置从此文件的开头开始计算的文件指针偏移量
void write(int b)将参数指定的单个字节写入
void close()用于关闭流并释放有关的资源

使用如下:

package com.stage1.module4;

import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * @author Corley
 * @date 2021/8/5 12:44
 * @description javase-com.stage1.module4
 */
public class RandomAccessFileTest {

    public static void main(String[] args) {
        RandomAccessFile accessFile = null;

        try {
            // 1.创建RandomAccessFile类型的对象与E:/Test/tmp.txt文件关联
            accessFile = new RandomAccessFile("E:/Test/tmp.txt", "rw");     // In love folly is always sweet.

            // 2.对文件内容进行随机读写操作
            // 设置距离文件开头位置的偏移量,从文件开头向后偏移5个字节
            accessFile.seek(5);
            int res = accessFile.read();
            System.out.println("获取到的单个字符:" + (char) res);
            res = accessFile.read();
            System.out.println("获取到的单个字符:" + (char) res);
            accessFile.write('X');                                                    // In loveXfolly is always sweet.
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放有关的资源
            if (null != accessFile) {
                try {
                    accessFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

获取到的单个字符:v
获取到的单个字符:e

文件在执行的内容为In love folly is always sweet.,执行后为In loveXfolly is always sweet.;

可以看到,通过seek(long pos)方法设置了偏移量后,是从偏移后的位置开始读写;

write(int b)方法写入字节时,不是将当前位置之后的字符后移,而是覆盖替换掉当前位置的字符,可以用于数据的修改。

总结

IO流用于Java中处理文件,包含很多基础六,包括文件流、缓冲路、对象流和数据流等,可以根据需要选择进行文件处理。

大数据学习路线总结 这是一篇大数据学习路线和相关面试总结,可以符合当下主流组件的使用。 阅读详情

相关推荐

面渣逆袭:Java基础面试题,46道Java基础八股文(4.8万字,30+手绘图)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。这是大佬写的,7701页的BAT大佬写的刷题笔记,让我offer拿到手软。

程序员江小北 3718

大数据项目实战(Hadoop集群搭建)

2、百度网盘链接:https://pan.baidu.com/s/1AkJCiEuxdkG_t-PfumbQDQ?②百度网盘链接:https://pan.baidu.com/s/1AkJCiEuxdkG_t-PfumbQDQ?(2)修改hadoop-env.sh配置文件。安装了防火墙,查看防火墙显示的是这个页面。系统重新启动后,防火墙将重新打开。只在hadoop01里面输入,(自己手敲吧,注意别错了哈)(自己手敲吧,注意别错了哈)(注意目录和版本名称)(注意jdk的版本名称)(1)复制模板并重命名。

qq_68383591的博客 3183

Java大数据处理项目实战:代码与文档解析

本文还有配套的精品资源,点击获取 简介:本项目“java代码-大数据1班47吴依琦”旨在为学习大数据处理的Java编程者提供代码示例和文档指导。项目包括 main.java 源代码文件和 README.txt 说明文档,涉及Java在大数据处理中的应用,如并发容器使用、I/O流操作、分布式计算框架Hadoop与Spark的应用,以及数据序列化、Y...

weixin_42356162的博客 2124

Java大数据项目实战:架构设计与关键技术实现

在实际的毕业设计过程中,利用Java开发了一套大数据处理系统,用于分析用户行为数据和提供实时数据处理功能。本文将详细介绍该项目的架构设计、主要技术实现以及一些关键的开发经验。

dragon 1347

Java编程中Map集合与IO流的深入解析

Map集合不同于List和Set,它通过键(key)与值(value)之间的映射关系来存储数据。每个键映射到一个值,并且一个Map中的键是唯一的,而值则可以重复。Map接口的实现类通常提供了快速查找、插入和删除等操作。TreeMap是基于红黑树结构的NavigableMap实现,它能够维持键的自然顺序,或者根据构造时提供的Comparator进行自定义排序。红黑树是一种自平衡的二叉查找树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是红色或黑色。

weixin_42593130的博客 947

最新大数据开发项目实战视频

【课程内容】 大数据时代下的中国梦 10年开发工程师带你轻松入门 10年爬虫经验发开的一个案例让你直接搞定全球80%以上的网站 Django框架真实项目现场开发(企业级开发实战从0到1) Python实现12306全自动抢票系统 Java人工智能扫一扫人脸识别【Java最新实战应用】 Java开发人工智能多国语言识别翻译系统【全球28种语言实时互译,无访问频次限制】 Java开发搜索引擎自动补全大数据核心系统【站内搜索解决方案】+ 数据库 Java开发百度开放云大数据智能存储系统【百度云计算核心技术】 百度开发云大数据技术:分布式文件系统的高级特性及实战应用

java+大数据实战 短链项目

首先选这套课的目的是 包含了我所学的大部分技术比如springboot ssm redis kafka flink clickhouse 等就业环境一般 目前来看暂时还没但是有后续潜在的毕业 或者 离职个人的技术追求暂时是在技术总监 技术架构项目具备一定的通用性 中大厂基本用得到 我这是天翼云买了三台服务器 四核八G 配置最少要两核8G 因为后续要运行的程序会比较多 比较吃内存运用以下命令搭建好docker环境#⽹络安全组记得开放端⼝ 3306这里使用客户端连接mysql报错了:caching_sha2_p

JavaCoder_juejue的博客 1367

大数据实战项目教程等

大数据实战项目教程等

大数据基础实战系列之企业级JavaSE实践

本章演示了Java开发环境搭建、比较全面介绍Java基本语法,Linux下安装MySQL数据库将详细介绍:1、重点讲解Java集合框架技术2、对象序列化使用场景、演示对象序列化开发技术3、演示多线程开发技术4、使用Sql语句操作数据库技术,包括对数据库和表的增删改查5、演示了不同隔离级别下,多用户操作数据库的特殊现象6、JDBC远程操作数据库的开发技术

大数据人工智能入门 JAVA高级项目实战【完整视频+源码】

Itstar大数据完整视频 JAVA高级项目实战 【程序员】高薪就业班 大数据开发必备工具 大数据项目实战 人工智能 最新版hadoop分布式搭建

升降机SEW MOVIDRIVE变频器设置

选择PO1 第2,3,9,13 位,点击“SendPA”Start reference,编码器反馈值为0(PI2/3 Actual position=0)提示:传送参考位置前请断开变频器安全24V电源,否则会弹出禁止修改提示框;提示:做这一步时,如果出现电机不能转动,或产生报警不能通过编码器匹配。下载配置的参数,提示:下载参数需要断开变频器的安全24V电源。编码器匹配通过后点击下一步,进入下图画面传送参考位置。1电机不能转动:a可能是由于变频器的安全24V未给。点击2.SEW总线扩展定位,定位块设置。

xdpcxq的专栏 5394

大数据入门:Java基础要掌握哪些?

在大数据当中,对于Java基础部分的学习,其实也是非常重要的一个部分。在执行大数据开发任务时,Java是主流的开发语言,也是大数据开发者们的“主要工具”。今天的大数据入门分享,我们就来讲讲,大数据学习当中Java基础要掌握哪些? 早些年,行业的大数据开发者,很多其实都是原来的Java开发者,因为本身具备良好的Java基础,也有一定的开发经验和能力,再入手大数据,可以说是水到渠成。 Java基础要掌握哪些?我们从初级入门和进阶提升两个阶段来看—— 初级入门阶段 1、Core Java,就是Java

shuimuzh123的博客 578

光谱分布、光谱辐射通量密度与不同时间段分布光谱(图示)

1、光谱分布图 2 太阳辐射能量图 3、不同时间段的太阳分布光谱图 4、不同波长的光的能量分布主要区域 5、不同波段的使用场景

maopig的专栏 2万+

视频教程-Java异常处理+集合+IO流+多线程+网络编程-Java

Java异常处理+集合+IO流+多线程+网络编程 拥有10年大数据以及Jav...

weixin_31025561的博客 363

基于STM32的智能循迹小车设计(基础版)

基于STM32的智能循迹小车设计 硬件准备 1、小车底盘+四直流电机(带轮) 2、STM32F103C8T6核心板 3、12V 8700mAh锂电池(可以用几节18650锂电池) 4、循迹模块 选择的是TCRT500L,五路集成在一起,集成度比较高,但是可能不如五个单独的循迹模块好用 5、L298N电机驱动模块 四驱车为啥用L298N电机驱动模块? 可以把左边的两个电机并联,用输出A控制。把右边的两个电机并联,用输出B控制。 12V供电接电源正极,供电GND接电源负极和核心板GND,5V供电接核心

Rendezvous1的博客 3万+

EPLAN中3D宏文件的创建与优化技巧(以开关电源为例)

本文以开关电源为例,详细讲解了在EPLAN中创建和优化3D宏文件(.ema)的全流程。从搭建宏项目、导入模型,到精确定义安装面、调整视野方向,再到定义3D连接点并关联部件库,旨在帮助电气工程师在设计阶段实现精准布局、避免安装冲突,从而提升设计效率与质量。

weixin_29219539的博客 547

Java笔记——如何快速入门 Java?

这是一个基于 Spring Boot、MyBatis-Plus、MySQL、Redis、ElasticSearch、MongoDB、Docker、RabbitMQ 等技术栈实现的社区系统,采用主流的互联网技术架构、全新的UI设计、支持一键源码部署,拥有完整的文章&教程发布/搜索/评论/统计流程等,代码完全开源,没有任何二次封装,是一个非常适合二次开发/实战的现代化社区项目?对于新人来说,最重要的是掌握 Java 后端开发四大件,Java 基础、Spring Boot、MySQL、Redis。

a25847a的博客 1769

Windows 上安装 OpenCode 完全指南:从零到成功,人人能看懂!

跟着这篇教程,你应该已经成功在 Windows 上安装并运行了 OpenCode。步骤操作要点1安装 Chocolatey必须以管理员身份运行 PowerShell2安装 OpenCode3验证安装关闭并重新打开终端,输入4进入项目目录必须是 Git 仓库(git init5配置模型输入/connect,输入 API Key6开始使用输入问题测试最后的小建议如果没有 API Key,可以从 DeepSeek 或 Kimi 开始,有免费额度初次使用建议先在简单项目上测试。

专注于编程、AI、人工智能等多个领域内容研究和分析 3880

大数据开发基础入门与项目实战(一)Java SE之5.常用类的概述和使用

本文主要介绍了Java常用核心类库的概述和使用:API的使用和常用包的概述;Object类的概述,包括Object类的概念和构造方法、equals方法的使用和重写、hashCode方法的重写、toString方法的重写和equals方法和hashCode方法的生成等;包装类,包括包装类的概念和分类、Integer类的概念和使用、Double类的概念和使用、Boolean类的概念和使用和Character类的概念和使用等;数学处理类包括Math、BigDecimal和BigInteger类的概念和使用。

CUFEECR的博客 890

Android TV 遥控器全链路按键适配指南:从物理按键到应用响应

本文深入探讨了Android TV遥控器按键适配方案,涵盖红外和蓝牙遥控器的工作原理及完整适配流程。红外遥控部分详细介绍了从硬件调试、驱动适配到键值映射配置的方法,并提供了实用的调试命令。蓝牙遥控器部分解析了HID协议和报告描述符,给出内核层与Android层的适配代码示例。文章还介绍了游戏手柄、语音遥控器等特殊外设的适配技巧,并提供了系统级调试工具和内核级调试方法,为开发者提供了一套完整的Android TV输入设备适配解决方案。

学无止境 1262

PCL 最小生成树用于法向量定向【2025最新版】

PCL 最小代价生成树用于点云法向量的定向。博客长期更新,本文最近更新时间为:2025年9月15日。

点云侠的博客 2067

Java 流(Stream)、文件(File)和IO——《跟老吕学Java》

Java的流、文件和IO库为Java应用程序提供了强大且灵活的数据处理能力。无论是处理文本数据、二进制数据还是网络数据,Java都能够提供合适的工具和API来满足需求。同时,随着Java的发展,NIO库的引入进一步提高了Java在IO处理方面的性能和效率。因此,熟练掌握Java的流、文件和IO库是Java开发人员必须掌握的基本技能之一。

Python老吕的博客 1140

基于DO-178C/331的飞控软件研发环境的研究

本研究提出了一套基于DO-178C/331标准的飞控系统软件开发和验证框架。通过优化模型库的设计、代码生成配置和测试验证流程,显著提升了飞控系统软件的开发效率和安全性。未来的研究可以进一步扩展模型库的功能,以适应更复杂的飞控系统需求。

qq_41854911的博客 561

Java学习路线从入门到入土

Java学习路线从入门到入土 简介 一门永不过时的编程语言——Java 编程开发。Java编程语言占比:据官方数据统计,在全球编程语言工程师的数量上,Java编程语言以900万的程序员数量位居首位。 而且很多软件的开发都离不开Java编程,因此其程序员的数量最多。而在以Java编程为核心的开发领域中,javaEE程序员的需求量10年来一直居于首位!Java工程师人才缺口:根据IDC的统计数字:就2017年来说,我国Java人才的缺口已达42.5万,并且以每年20%左右 的速度增长。在未来5年内,合格软件人才

qq_45077173的博客 1万+

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Java保姆级教程:从零基础到接单赚钱的完整学习路径与实战指南

Java作为一门面向对象的编程语言,其核心在于通过类与对象的概念来模拟现实世界,实现代码的模块化和复用。其工作原理基于JVM(Java虚拟机),实现了‘一次编写,到处运行’的跨平台特性。掌握Java不仅能够构建健壮的后端系统,更是进入企业级应用开发领域的基石,广泛应用于金融、电商、大数据等场景。本文聚焦于**Java保姆级教程**和**Spring Boot**框架,通过环境搭建、核心语法、项目实战到问题排查的全流程拆解,为初学者提供一条清晰可落地的学习路径,帮助学习者快速具备接单或求职所需的实践能力。

weixin_33671935的博客 434

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

【大数据】学习笔记 1 Java SE 第14章 File类与IO流 14.5 缓冲流

【大数据】学习笔记 1 Java SE 第14章 File类与IO流 14.5 缓冲流

代码改变世界 207

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

上一篇: 大数据开发基础入门与项目实战(一)Java SE之7.异常机制和File类
下一篇: Java开发常见异常及解决办法总结
东哥说AI
博客等级 码龄7年 5934粉丝 385原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东哥说AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值