Java - I/O 流

本文概述了Java中流的概念,包括输入流、输出流、字节流与字符流的区别,以及BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter等缓存流的作用。重点讲解了FileWriter和FileReader的使用,以及如何利用缓存流提高文件操作的效率。

一、概述

  • 按照流向可以分为:输入流、输出流。从数据源中读取数据是输入流,将数据写入到目的地是输出流。
  • 按照数据处理单位可以分为:字节流、字符流。字节占1个byte即8位,字符占2个byte即16位。对于文本文件(txt、xml、java)应该使用字符流,字符涉及编码格式,对于二进制文件(jpg、doc、mp3)应该使用字节流,字节不涉及编码格式。
抽象基类

InputStream

字节输入流

OutputStream

字节输入流

Reader

字符输入流

Writer

字符输出流

缓存流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
文件流FileInputStreamFileOutputStreamFileReaderFileWriter
数组流ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
字符串流--StringReaderStringWriter
管道流PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
对象流ObjectInputStreamObjectOutputStream--
转换流--InputStreamReaderOutputStreamWriter
打印流-PrintStream-PrintWriter
基本数据类型流DataInputStreamDataOutputStream--

1.1 字符流抽象基类

1.1.1 Writer

close()

​abstract public void close() throws IOException

刷新后关闭流并释放资源,流不能再被使用。

flush()

abstract public void flush() throws IOException

刷新该流的缓冲,流可以继续使用。

write()

public void write(int c) throws IOException

写入单个字符(0~65535)。

public void write(char cbuf[]) throws IOException
abstract public void write(char cbuf[], int off, int len) throws IOException

写入字符数组。

public void write(String str) throws IOException
public void write(String str, int off, int len) throws IOException

写入字符串。

append()

public Writer append(char c) throws IOException

续写字符。

public Writer append(CharSequence csq) throws IOException
public Writer append(CharSequence csq, int start, int end) throws IOException

续写字符序列。

1.1.2 Reader

close()

abstract public void close() throws IOException

关闭流并释放资源,流不能再使用。

read()

public int read() throws IOException

读取单个字符,返回的是字符在内存中对应的数字(0~65535),该方法再次调用会自动读取下一位,没有了则返回 -1。

public int read(char cbuf[]) throws IOException

abstract public int read(char cbuf[], int off, int len) throws IOException

将读取的字符存入数组,返回的是读到的个数,没有了则返回 -1。

1.2 字节流抽象基类

1.2.1 OutputStream

close()

public void close() throws IOException

刷新后关闭流并释放资源,流不能再被使用。

flush()

public void flush() throws IOException

刷新该流的缓冲,流可以继续使用。

write()

public abstract void write(int b) throws IOException
写入单个字节,写入有效位int的最低8位。

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

写入字节数组。

1.2.2 InputStream

close()

public void close() throws IOException

关闭流并释放资源,流不能再使用。

available()

public int available() throws IOException

返回的是读到的字节个数,\r 和 \n 各是一位。

read()

public abstract int read() throws IOException

读取单个字节,读取有效位int的最低8位,该方法再次调用会自动读取下一位,没有了则返回 -1。

public int read(byte b[]) throws IOException
public int read(byte b[], int off, int len) throws IOException

将读取的字节存入数组,返回的是读到的个数,没有了则返回 -1。

1.3 Java7 特性

实现了 AutoClose 接口的对象,能自动关闭流资源。格式:try(流对象){读写代码}。

二、文件流

文件流可以直接操作文件的读写。

2.1 FileWriter

如果有同名文件会覆盖(因此续写模式从构造入手而不是write()方法入手)。

FileWriter()

public FileWriter(File file) throws IOException

根据给定的文件构造,从开头写。
public FileWriter(File file, boolean append) throws IOException

参数 append:true从结尾处追加续写,false覆盖。

public FileWriter(String fileName) throws IOException

根据给定的文件名构造(文件名前不带路径则为当前目录),从开头写。
public FileWriter(String fileName, boolean append) throws IOException

append续写模式,true从结尾处写,false覆盖。

2.2 FileReader

FileReader()

public FileReader(File file) throws FileNotFoundException

根据给定的文件构造。

public FileReader(String fileName) throws FileNotFoundException

根据给定的文件名构造(文件名前不带路径则为当前目录)。

/*创建一个FileReader对象并和指定文件关联
要保证该文件是已经存在的,否则抛异常*/
try (FileReader fr = new FileReader("demo.txt")) {
    int num = 0;
//  每次读取单个字符效率低下不推荐
//  while ((num = fr.read()) != -1) {
//      System.out.print((char)num);
//  }
    char[] buffer = new char[1024]; //定义一个数组用于存储读到的字符(建议为1024的整数倍)
    while ((num = fr.read(buffer)) != -1) { //直到返回-1读不到数据了便结束
        System.out.print(new String(buffer, 0, num));   //最后一次读取,不一定装满1024所以是num不是buffer.length,否则超过num后会把上一次存入的打印出来
    }
} catch (IOException e) {
    e.printStackTrace();
}

2.3 FileOutputStream

2.4 FileInputStream

三、缓存流

为了提高数据读写的速度,JavaAPI提供了带缓冲功能的流,在使用这些流类,会创建一个内部缓冲区数组。缓冲流要“套接”在相应的其他流之上(创建时需要传入其他流),对读写的数据提供了缓冲的功能,提高了读写的效率。写出的数据会先在内存中缓存,使用flush()将会使内存中的数据立刻写出。

3.1 BufferedWrite

将文本写入字符输出流,缓存各个字符,从而提供高效写入。

BufferedWrite()

public BufferedWriter(Writer out)

缓冲区的出现是为了提高流的效率,所以创建缓冲区需要提供一个流对象。

newLine()

public void newLine() throws IOException

写入一个换行符(回车换行效果)。

try (BufferedWriter bw = new BufferedWriter(new FileWriter("demo.txt"))) {
    for (int i = 1; i < 5; i++) {
        bw.write("Hello" + i);
        bw.newLine();   //换行
        bw.flush(); //每次刷新避免数据丢失,用到缓存流就要记得刷新
    }
} catch (IOException e) {
    e.printStackTrace();
}

3.2 BufferedReader

从字符输入流中读取文本,缓存各个字符,从而提供高效读取。

BufferedReader()

public BufferedReader(Reader in)

缓冲区的出现是为了提高流的效率,所以创建缓冲区需要提供一个流对象。

readLine()

public String readLine() throws IOException

读取一行文本(不包含换行符)。

try (BufferedReader br = new BufferedReader(new FileReader("demo.txt"))) {
    String line = null;
    while ((line = br.readLine()) != null) {    //一行一行读更方便
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

通过字符缓存流复制文本文件

try (BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\original\\demo.txt"));
     BufferedReader br = new BufferedReader(new FileReader("E:\\destination\\demo_copy.txt"))
) {
    String line = null;
    while ((line = br.readLine()) != null) {
        bw.write(line);
        bw.newLine();
        bw.flush();
    }
} catch (IOException e) {
    e.printStackTrace();
}

3.3 BufferedOutputStream

3.4 BufferedInputStream

四、对象流

        Data***Stream 可以实现基本数据类型和String的输入输出,Object***Stream 可以实现自定义数据类型的输入输出,包含了 Data***Stream 的功能可以将其替代。

        序列化:对象的持久化存储(将对象存储到文件中)。

并不是每一个对象都可以写到输出流,可以写入到输出流中的对象称为可序列化的。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。

4.1 ObjectOutputStream

4.2 ObjectInputStream

五、字节数组流

ByteArrayInputStream、ByteArrayOutputStream

字节数组流,因为两个流对象操作的是数组,没有使用系统资源所以不会出现 IOException。

六、RandomAccessFile

上边讲的都只能追加或者覆盖,无法实现任意位置的读写,所以Java 提供了 RandomAccessFile 类, 允许从文件的任何位置进行数据的读写 。RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置。

  • RandomAccessFile的read()和write()都会使指针往后移动,移动的值就是读取或者写入的字节长度。
  • 指针移动时,seek方法传入的是字节长度,不是字符长度。
  • write()其实是在指针位置覆盖,所以要做插入操作时,必须先将插入点之后的数据暂存起来,在插入点插入数据后,再将暂存的数据追加到后边,这样才能实现插入。例如:要在第一行和第二行之前插入数据,也就是新插入的数据作为第二行,那么插入之前需要将第二行及之后的数据暂存起来,然后将数据插入(覆盖)到第二行,然后再将之前暂存的第二行(插入前的第二行)及之后的数据取出,追加都文件末尾。

getFilePointer()

long getFilePointer()

获取文件记录指针的当前位置

seek()

void seek(long pos)

将文件记录指针定位到pos位置

//RandomAccessFile的read和write都会改变指针位置。
val randomAccessFile = RandomAccessFile(file, "rw")
val data1 = "这是数据1\n"
randomAccessFile.write(data1.toByteArray())
randomAccessFile.write("这是数据2\n".toByteArray())
randomAccessFile.close()

val randomAccessFile1 = RandomAccessFile(file, "rw")
randomAccessFile1.seek(data1.toByteArray().size.toLong())
println("当前指针位置1," + randomAccessFile1.filePointer)
//取出data1之后的数据
var stringAfterData1 = ""
var buffer = ByteArray(1024)
var len: Int
while (randomAccessFile1.read(buffer).also { len = it } != -1) {
    stringAfterData1 += String(buffer, 0, len)
}
println("当前指针位置2," + randomAccessFile1.filePointer)
randomAccessFile1.seek(data1.toByteArray().size.toLong())
randomAccessFile1.write("这一行数据插入在数据1和2之间\n".toByteArray())
//将插入之前data1之后的数据追加上
randomAccessFile1.write(stringAfterData1.toByteArray())
println("当前指针位置3," + randomAccessFile1.filePointer)
println("当前数据长度," + randomAccessFile1.length())
//重新移动指针到开头,取出所有数据
randomAccessFile1.seek(0)
while (randomAccessFile1.read(buffer).also { len = it } != -1) {
    print(String(buffer, 0, len))
}
println("当前指针位置4," + randomAccessFile1.filePointer)
randomAccessFile1.close()

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值