Java基础学习17之IO流、字节流、字符流 Java基础学

Java基础学习17之IO流、字节流、字符流

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」。

关于作者

  • 作者介绍

🍓 博客主页:作者主页

🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。

🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。

IO— 字节流与字符流

image-20210818173800455

字符流和字节流最本质的区别就是只有一个字节流是原生的操作,二字符流是经过处理后的操作。经过磁盘数据保存所保存的支持的数据类型只有:字节,所有磁盘中的数据必须先读到内存后才可以操作,内存里面会帮助我们把字节变为字符。字符更加适合处理中文。

字节操作流:OutputStream,InputStream;

字符操作流:Writer,Reader。

但是不管是字节流还是字符流的操作,本身都表示资源操作,而执行所有的资源都会按照如下几个步骤进行,下面以文件操作为例(对文件进行读,写操作):

  1. 要根据文件创建File对象
  2. 根据字节流或字符流的子类实例化我们的父类对象
  3. 进行数据的读取、写入操作
  4. 关闭流(clone())

对于IO操作属于资源处理,所有的对于资源的处理进行处理完成后必须进行关闭,否则资源就再也无法执行。

字节输出流:OutputStream

Java.io.OutputStream主要的功能是进行字节数据的输出的,而这个类的定义如下:

1
java复制代码public abstract class OutputStream extends Object implements Closeable, Flushable

发现OutputStream类定义的时候实现了两个接口:Closeable,Flushable,这两个接口定义如下:

Closeable: Flushable:
public interface Closeableextends AutoCloseable{ public void close() throws IOException; } public interface Closeable{ public void flush() throws IOException; }

当取得了OutputStream类实例化对象之后,下面肯定要进行输出操作,在OutputStream类之中定义了三个方法:

输出单个字节数组数据:public abstract void write(int b) throws IOException

输出一组字节数组数据:public void write(byte[] b) throws IOException

输出部分字节数组数据:public void write(byte[] b,int off,int len) throws IOException

提示:对于Closeable继承的AutoCloseable接口

AuotCloseable实在JDK1.7的时候又增加了一个新的接口,但是这个接口的定义和Closeable定义是完全一样的,我个人认为:有可能在一些其他的类上出现了自动的关闭功能,Closeable是手工关闭,AutoCloseable属于自动关闭。

但是对于Closeable和Flushable这两个接口实话而言不需要关注,因为从最早的习惯对于flush()和close()连个方法都是直接在OutputStream类之中定义的,所以很少去关心这些父接口问题。

对于OutputStream类而言发现其本身定义的是一个抽象类(abstract class),按照抽象类的使用原则来讲,如果要想为父类实例化,那么就需要使用子类,就需要定义抽象的子类,而现在如果要执行的是文件操作,则可以使用FileOutputStream子类完成。如果按照面向对象的开发原则,子类要为抽象类进行对象的实例化,而后调用的方法以父类中定义的方法为主,而具体的实现找实例化这个父类的子类完成,也就是说在整个的操作之中,用户最关心的只有子类的构造方法:

实例化FileOutputStream(新建数据):public FileOutputStream([File file) throws FileNotFoundException

实例化FileOutputStream(追加数据):public FileOutputStream(File file,boolean append) throws FileNotFoundException

image-20210818181217500

实现文件内容的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码package com.day14.demo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

public class OutputDemo {
public static void main(String[] args) throws Exception {
//1.定义文件路径
File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
if(!file.getParentFile().exists()){//父路径不存在
file.getParentFile().mkdirs();//创建父目录
}
//2.要输出的数据
String str = "Hello,zsr!!!!";
//3.实例化对象
FileOutputStream stream = new FileOutputStream(file);
//4.将内容写进stream
stream.write(str.getBytes());//输出数据,要将输出的数据变为字节数组输出
//5.关闭流
stream.close();
}
}

这里默认执行时进行文档内容的覆写,如果不希望进行文档内容的覆写可以直接将FileOutputStream改为

1
java复制代码FileOutputStream stream = new FileOutputStream(file,true);

如果对写入的内容需要换行操作必须使用\r\n进行换行操作。

字节输入流:InputStream

如果现在要从指定的数据源之中读取数据,使用InputStream,而这个类的定义如下:

1
java复制代码public abstract class InputStreamextends Objectimplements Closeable

发现InputStream只实现了Closeable接口

在InputStream之中定义了三个读取数据的方法:

读取单个字节:public abstract int read() throws IOException

说明:每次执行read()方法都会读取一个数据源的指定数据,如果现在发现已经读取到了结尾则返回-1.

读取多个字节:public int read(byte[] b) throws IOException

说明:如果现在要读取的数据小于开辟的字节数组,这个时候read()方法的返回值int返回的是数据个数;如果现在开辟的字节数组小于读取的长度,则这个时候返回就是长度;如果数据已经读完了,则这个时候的int返回的是-1.

读取指定多个字节:public int read(byte[] b,int off,int len) throws IOException

说明:每次只读取传递数组的部分内容,如果读取满了,返回就是长度;如果没有读取满,返回的就是读取的个数;如果读取到最后没有数据了,就返回-1

image-20210818192150209

既然InputStream为抽象类,那么这个抽象类要使用就必须有子类,现在是通过文件读取内容,肯定使用FileInputStream子类进行操作,与OutputStream类的使用一样,对于FileInputStream也只关心构造方法:

FileInputStream类构造方法:public FileInputStream(File file) throws FileNotFoundException

文件信息的读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码package com.day14.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;


public class InputDemo {
public static void main(String[] args) throws Exception {
File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
if(!file.exists()){
System.out.println("指定文件不存在!!");
}else{
FileInputStream is = new FileInputStream(file);
byte[] result = new byte[1024];
int length = is.read(result);
System.out.println("读取的内容为:" + new String(result,0,length));
}
}
}

字符输出流:Writer

Writer类也是一个专门用来数据输出的操作类,对于中文数据来说是比较友好的,这个类定义:

1
2
3
java复制代码public abstract class Writer
extends Object
implements Appendable, Closeable, Flushable

在Writer类之中定义的writer()方法都是以字符数据为主,但是在这些方法之中,只关心一个:

输出一个字符串:public void write(String str) throws IOException

如果要操作文件肯定使用FileWriter子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码package com.day14.demo;

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

public class WriterDemo {
public static void main(String[] args) throws IOException {
//1.定义文件路径
File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
if(!file.getParentFile().exists()){//父路径不存在
file.getParentFile().mkdirs();//创建父目录
}
//2.要输出的数据
String str = "我正在学java这门课程!!!!\r\n";
//如果想要进行内容不覆盖的直接使用true就可以了
//FileWriter out = new FileWriter(file,true);
FileWriter out = new FileWriter(file);
out.write(str);
out.close();
}
}

字符输入流:Reader

Reader是进行字符数据读取的一个操作类,其定义:

1
2
3
java复制代码public abstract class Reader
extends Object
implements Readable, Closeable

在Writer类之中存在了直接输出一个字符串数据的方法,可是在Reader类之中并没有定义这样的方法,只是定义了三个按照字符串读取的方法?为什么会这样?

因为在使用OutputStream输出数据的时候,其程序可以输出的大小一定是程序可以承受的数据的大小,那么如果说使用InputStream读取的时候,可能被读取的数据非常大,那么如果一次性全读进来,就会出现问题,所以只能一个一个的进行读取。

Reader依然是抽象类,那么如果从文件读取,依然使用FileReader类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码package com.day14.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class ReadDemo {
public static void main(String[] args) throws IOException {
File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
if(!file.exists()){
System.out.println("指定文件不存在!!");
}else{
FileReader reader = new FileReader(file);
char[] result = new char[1024];
int length = reader.read(result);
System.out.println("读取的内容为:" + new String(result,0,length));
}
}
}

字符比字节的好处就是在于字符串数据的支持上,而这个好处还只是在Writer()类中体现,所以与字节流相比,字符流的操作并不是对等的关系。

字节流与字符流区别

通过我们一系统的分析,可以发现字节流和字符流的代码操作区别不大,如果从我们实际的使用,我们字节流是优先考虑,只有再我们使用中文的时候才考虑使用字符流,因为所有的字符都需要通过内存缓冲来进行处理。

image-20210819114608877

既然读数据需要缓存的处理,那么写数据也同样需要。如果使用字符流没有进行刷新,那么我们的内容可能再缓存之中,所以必须进行强制刷新才能得到完整的数据内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码package com.day14.demo;

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

public class WriterDemo {
public static void main(String[] args) throws IOException {
//1.定义文件路径
File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
if(!file.getParentFile().exists()){//父路径不存在
file.getParentFile().mkdirs();//创建父目录
}
//2.要输出的数据
String str = "我正在学java这门课程!!!!\r\n";
//如果想要进行内容不覆盖的直接使用true就可以了
//FileWriter out = new FileWriter(file,true);
FileWriter out = new FileWriter(file);
out.write(str);
out.flush();
}
}

在以后的IO处理的时候,如果处理的是图片、音乐、文字都可以使用字节流,只有再处理中文的时候才会使用字符流。

转换流

现在对于IO操作就存在了字节流和字符流两种操作,那么对于这两种操作流之间也是可以进行转换的,而转换的操作类有两个:

将字节输出流变为字符输出流(OutputStream->Writer)——OutputStreamWriter;

将字节输入流变为字符输入流(InputStream->Reader)——InputStreaReader。

OutputStreamWriter InputStreamReader
public class OutputStreamWriterextends Writer public class InputStreamReaderextends Reader
public OutputStreamWriter(OutputStream out) public InputStreamReader(InputStream in)

image-20210819115616861

通过以上的继承结构和构造方法可以清楚发现,既然OutputStreamWriter是Writer的子类,那么必然OutputStreamWriter可以通过Writer类执行对象的向上转型进行接收,而同时这个OutputStreamWriter类的构造方法可以接收OutputStream,这样就可以完成转型。

将字节输出流变为字符输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码package com.day14.demo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class OutWriterDemo {
public static void main(String[] args) throws Exception {
//1.定义文件路径
File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
if(!file.getParentFile().exists()){//父路径不存在
file.getParentFile().mkdirs();//创建父目录
}
//2.要输出的数据
String str = "Hello,world!!!!";
//3.实例化对象
OutputStream stream = new FileOutputStream(file,true);
//4.将内容写进stream
Writer out = new OutputStreamWriter(stream);
out.write(str);
//5.关闭流
out.close();
}
}

对于文件操作可以使用FileInputStream,FileOutputStream,FileReader,FileWriter四个类,那么下面分别观察这四个类的继承结构。

观察FileInputStream,FileOutoutStream类的继承结构

FileInputStream FileOutoutStream
java.lang.Object java.io.InputStream java.io.FilterInputStream java.lang.Object java.io.OutputStream java.io.FileOutputStream

观察FileReader,FileWriter类的继承结构

FileReader FileWrite
java.lang.Object java.io.Reader java.io.InputStreamReader java.io.FileReader java.lang.Object java.io.Writer java.io.OutputStreamWriter java.io.FileWriter

image-20210819120636862

通过以上的继承关系也可以发现,实际上所有的字符数据都是需要进行转换的,依靠转换流完成,以后真正保存或者是传输的数据是不可能有字符的,全部都是字节,而字节只是在电脑之中处理后的结果。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%