IO:I —— Input,O —— Output
概念性工具
通常来说,我们以 Stream (水)流
作为具象化描述 数据的输入输出——输入流,输出流
IO流
是用来处理源节点和目标节点间数据传输的工具。
源节点Source及目标节点Target:可以是文件、网络服务器、内存、键盘、显示器等
简单理解:水流 ——> 如何测量水流?——> 横截面 * 流速
IO流 ——> byte[] * 读写速度
。圆柱体[ 1个byte对象作为1层薄薄圆柱体 * 个数] = IO流
注:为什么不是以bit作为读写基本单位,而是以byte?因为1bit只是代表2种情况0、1对人类来说没意义。而8bit=1byte表示一个字符,对于程序猿来说是较为有意义【1bit太细没必要控制这么精细】
IO 实现体系概述
一共有4个抽象基类,及共计40多个具体实现类。
而对于JAVA初学者来说,我们需要重点了解、掌握的只有如下18个类。
简单的解释一下
1.继承关系
紫色箭头为
- <InputStream,OutputStream>字节型输入输出流(下文中简称IS,OS)
- <Reader,Writer>字符型输入输出流(下文中简称R,W)抽象基类的实现类
红色箭头为 <IS,OS>,<R,W>的间接子类
2.类关系
IS与OS【一对】一般是配套使用,例如:先IS字节byte读入,再OS字节输出
R,W【一对】也同样是配套使用,例如:先R字符char/String读入,再W字符输出
<IS,OS>,<R,W>这两套抽象基类的关系也很简单——2byte = 1char
也就是,<R,W>底层具体的读取写入也是<IS,OS>,只不过为了让广大程序猿更方便的使用(大部分时间都是对字符进行操作,而不是对字节),<R,W>抽象基类屏蔽了字符转字节的细节。
所谓的底层转换细节可以提及java.nio.charset包下的几个类:反正我是吐了告辞
Charset,CharsetEncodor,CharsetDecoder,CharsetProvider,CharsetMapping,CharsetString
3.接口实现关系
只有输出流实现了Flushable接口,所有流都实现了Closeable接口
Object对象流实现的是Closeable接口的父接口AutoCloseable
其他的几对流,关系其实很明显,就不多赘述了。越常用的流越靠下。下面进行分重点解析。
[文档级]
①IO基石 四抽象基类
【IS,OS / R,W】抽象基类简述
字节流:以字节byte为单位,8bit来流动
字符流:以字符char为单位,16bit来流动
字节流 | 字符流 | |
---|---|---|
输入流 读 | InputStream(基类) | Reader(基类) |
输出流 写 | OutputStream (基类) | Writer(基类) |
流中的数据 | 二进制字节(8位) | Unicode字符(16位) |
子类及其实现接口
注:所有的父类子类及接口——都是直接子类,直接父类,直接实现
字节(FIS,OIS)字符(BR,ISR)读
注:FIS,OIS分别是节点流、对象流字节读;BR,ISR分别是缓冲流、编码转换流字符读。
①InputStream
- All Implemented Interfaces:
Closeable, AutoCloseable - Direct Known Subclasses:
AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream
②Reader
- All Implemented Interfaces:
Closeable, AutoCloseable, Readable - Direct Known Subclasses:
BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, StringReader
字节(FOS,OOS,PS)字符(BW,OSW,PW)写
注:FOS,OOS,PS分别是节点流、对象流、打印字节写;BW,OSW,PW分别是缓冲流、编码转换流打印字符写。
①OutputStream
- All Implemented Interfaces:
Closeable, Flushable, AutoCloseable - Direct Known Subclasses:
ByteArrayOutputStream, FileOutputStream, FilterOutputStream, ObjectOutputStream, PipedOutputStream, PrintStream
②Writer
- All Implemented Interfaces:
Closeable, Flushable, AutoCloseable, Appendable - Direct Known Subclasses:
BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, PipedWriter, PrintWriter, StringWriter
构造器(0)
四大抽象基类,均是空构造器——简称 四大皆空
常用函数
以下用对比的方式,令我们更容易理解
字节/字符读read函数
InputStream字节输入流顶级抽象类 && Reader字符输入流顶级抽象类
InputStream | Reader | 功能 |
---|---|---|
int read() | int read() | 读取流单个byte/char作为返回值返回 |
int read(byte[]) | int read(char[]) | 读取流至byte[]/char[]返回读入流长度 |
int read(byte[],int,int) | int read(char[],int,int) | 将流读入byte/char[]指定部分 |
int read(CharBuffer) | 将字符流读入指定的字符缓冲区 |
解析:
1.其实两者抽象类方法都差不多,核心就是read函数,只不过是各自的形式参数不同。
2.Reader读取的流装入 char、char[] 类型变量,InputStream读取的流装入 byte、byte[] 类型变量。
3.注意此处的byte[]/char[]变量是由main()内程序猿自己定义的,通常称为缓冲区域/缓冲数组。
read函数详解
以Reader的第三行详解,InputStream同理
public int read(@NotNull char[] cbuf,
int offset,
int length);
//此read函数是将字符流读入char[]的指定部分(从offset开始,流入length个字符)
//而不是跳过输入流
//实例
psvm(){
//程序猿自己在main中定义的缓冲区/缓冲数组
char[] buffer = new char[3];
fileReader = new FileReader("hello.txt"); //abcdefg
int ch = fileReader.read(chars,0,2);
while (ch != -1) {
System.out.print(ch+":");
for (char temp:chars
) {
System.out.print(temp);
}
System.out.println("");
ch = fileReader.read(buffer,0,2);
}
}
//console
2:ab
2:cd
2:ef
1:gf
注意,char[2]是 空白符 不是没有
其他非重要相同函数
long skip(long n) 【此方法为同步方法会阻塞IO,索取Reader/InputStream对象锁】
跳过并丢弃此输入流的n个byte/char的数据,返回跳过的byte/char数量,一般为n(内部还是用临时数组read)
注:字节输入流最大跳过2048byte,字符输入流最大跳过8192char=16384byte=8倍
void close()
关闭此输入流,并释放任何与其相关的系统资源。
void reset()
输入流重定位至上次mark方法在这个流上被调用时的位置
void mark(int readAheadLimit)
限制在保留标记的同时可以读取的字符数。 读取限制量的字符后尝试重置流可能会失败。
boolean MarkSupported()
测试此输入流是否支持标记和重置方法。
其余函数
①InputStream
long available() throws IOException
返回可从该输入流读取(或跳过)的字节数的估计值,在没有被此输入流其他方法下一次调用阻塞前。
②Reader
boolean ready() throws IOException
表示此流是否已准备好读取
字节/字符写write函数
OutputStream字节输出流顶级抽象类 && Writer字符输出流顶级抽象类
OutputStream | Writer | 功能 |
---|---|---|
void write(int) | void write(int) | 写入单个byte/char到目标文件 |
void write(byte[]) | void write(char[]) | 写入byte[]/char[]到~ |
void write(byte[],int,int) | void write(char[],int,int) | 写入数组的指定部分内容到~ |
void write(String) | 写入字符串到~ |
解析:
1.其实这两者抽象类方法也都差不多,核心就是write函数,只不过是各自的形式参数不同。
2.Writer写入的流是 char、char[]、String ,OutputStream写入的流是byte、byte[] 类型变量。
3.注意此处的byte[]/char[]变量是由main()内程序猿自己定义的,通常称为缓冲区域/缓冲数组。
其他非重要相同函数
Flushable
与Closeable
接口的抽象函数
void flush()
刷新此输出流并强制写出任何缓冲的输出字节/字符。但不保证一定写入(例如由OS底层实现的写入操作)
void close()
关闭此输出流并释放任何与其有关的系统资源
其余函数
①Writer
Appendable
接口的抽象函数
Writer append(char)
Writer append(CharSequence)
Writer append(CharSequence,int,int)
追加char、CharSequence、CharSequence的特定部分
四大基类总结
由四大IO抽象基类派生的实现类共有40多个,但实际上非常规则,分为2派:字节、字符IO流
由这四个抽象类派生出来的子类名称都是以其父类名作为子类名后缀
文件读写一般步骤
一般步骤:
1.创建节点流(+处理流)对象,并赋值为null
2.try catch finally
(3).数据处理
3.在final中判断并关闭流对象
4.在catch中处理异常
5.在try中创建流对象
6.数据处理
注1:对于文本文件,应使用字符输入流FileReader流,对于二进制文件,应使用字节输入流FileInputStream流,当然对于文本文件也可以使用字节输入流。
注2:当前目录:程序所在项目目录/根目录
注3:\r\n
= 13 10
windows 10及其之前 TXT记事本要求两个,其他文件要求\n
即可换行
②节点流
【FIS,FOS / FR,FW】文件流概述[基本]
一共两套,输入输出文件流。区别在于以字符、字节,而方法与构造器基本相同,略有不同。
1.字符型:FileReader / FileWriter:单个字符char \ 多个字符chars[] 缓冲数组 Buffer 的读写
2.字节型:FileInputStream / FileOutputStream:单字节byte \ 多字节bytes[] 缓存数组 Buffer 的读写
父类(IS,OS/ISR,OSW)
SuperClass:
- FileInputStream extends InputStream 【FIS -> IS】
- FIleOutputStream extends OutputStream【FOS -> OS】
- FileReader extends InputStreamReader【FR -> ISR】
- FileWriter extends OuputStremWriter【FW -> OSW】
无java.io下的子类,也无本类实现接口
构造器(3种2重载)
不论读写,构造器的参数基本相同。只有3种对象可以成为文件流对象(无论IO)的构造参数:
1.File对象,2.FileDescriptor对象,3.String对象——内容是 路径+文件名
注:写比读构造器只是增加了2个 可否append布尔参数 的函数重载
读·FIS/FR
FileInputStream | FileReader | 功能 |
---|---|---|
同右(改构造器名) | FileReader(File file) | 通过文件对象打开连接 |
FileReader(FileDescriptor fdObj) | 通过文件描述符对象读入 | |
FileReader(String name) | 通过路径文件名打开连接 |
写·FOS/FW
FileOutputStream | FileWriter | 功能 |
---|---|---|
同右(改构造器名) | ①FileWriter(File file) | 写入文件对象 |
FileWriter(File file, boolean append) | 是否以追加形式写 | |
②FileWriter(FileDescriptor fd)er | 写入文件描述符对象 | |
③FileWriter(String fileName) | 写入路径文件名 | |
FileWriter(String fileName, boolean append) | 是否以追加形式写 |
全部函数【Buffer流基本全部封装】
注:Buffer流就是在下面会提到的缓冲流
。
字节读
读·FIS / FR(无,全为父类ISR函数)
函数 | 描述 |
---|---|
int available() | |
void close() | |
FileChannel getChannel() | 返回当前流关联的用于读写、映射和操作文件的通道对象 |
FileDescriptor getFD() | 返回当前流关联的文件描述符对象 |
int read() | 读取一个byte |
int read(byte[] b) | 读取b.length个byte并放置到byte[]中 |
int read(byte[] b, int off, int len) | 读取len个byte并放置在byte[]指定位置从off放 |
long skip(long n) | 跳过并按顺序从输入流中丢弃n个byte |
字节写
写·FOS / FW(无,全为父类OSW函数)
函数 | 描述 |
---|---|
void close() | |
FileChannel getChannel() | 返回当前流关联的用于读写、映射和操作文件的通道对象 |
FileDescriptor getFD() | 返回当前流关联的文件描述符对象 |
void write(int b) | 写入一个byte |
void write(byte[] b) | 将指定byte[]写入文件 |
void write(byte[] b, int off, int len) | 将指定部分的byte[]写入文件 |
读写实例
字节与字符读写,流程相同,只不过是换配套对象罢了。
//节点流(字节流)读写·案例【先读后写--复制文件】
//注:音频文件无法用字符使用
@Test
public void test4() {
//1.Stream
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
//2.try catch finally
try {
//Decleration Stream
fileInputStream = new FileInputStream("落花流水.mp3");
fileOutputStream = new FileOutputStream("落花4.mp3");
//buffer[]
byte[] bytes = new byte[1024*1024];
int realRead = fileInputStream.read(bytes);
//3.process data
while (realRead != -1){
//1.拒绝一次性全部写入【脏数据,全部以一个一个byte形式写入】
//result:1m6s
/*for(int i = 0;i<realRead ;i++)
fileOutputStream.write(bytes[i]);*/
//2.只拒绝最后一次的全部写入
//result:3.16s
/*if(realRead!=1024*1024){
for(int i = 0;i<realRead ;i++)
fileOutputStream.write(bytes[i]);
}
else
fileOutputStream.write(bytes);*/
//3.完全按照realRead进行写入
//result:116ms
fileOutputStream.write(bytes,0,realRead);
realRead = fileInputStream.read(bytes);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
if(fileOutputStream != null)
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
模糊问题
这个4个File流类是我们真正遇到可以用的实现类,那么小白会出现,一些概念上的模糊?
模糊举例:字节流能不能写字符?
答案是当然可以,但是呢,你需要手动将字符转为字节。那么这里就涉及到了一些底层的问题。我们JAVA中代码里写的char字符到底是什么编码方式呢?是UTF-8?还是GBK?熟悉字符的老哥应该会第一反应疑问这个。以我搜集评判的结果是——Java char 使用16位的 Unicode字符集—UTF-16作为编码方式。而我们真正读取写入文件的时候,我们可以采用其他字符集,例如UTF-8/GBK。
另一个模糊举例:UTF-8是变长编码,写字符是分中英文,英文=1byte,中文=2byte=1char,那写入文件的时候怎么办呢?
答案是,java会在底层处理转换 UTF-16 --> UTF-8
最后一个疑问:UTF-16听起来,感觉就是16bit的Unicode编码,那么怎么表示65535之外的符号呢?
答案是,没错UTF-16与UTF-8、UTF-32一样,都是以位数作为划分标准,那么2^16确实是0~65535
怎么办呢?其实还是用双char来解决的,也就是4byte字节与UTF-32一样,只不过它们每个都有的各自诡异的识别转换规则。
最最最后一个:字节写入的时候,需要考虑编码吗????哈哈哈哈
答案是,不需要,因为编码步骤是:字符char ——》字节byte的转换过程中的一环。解码是字节byte转换为字符char时的步骤。你都转换完了,还考虑什么编码?顺便一提,我们的ISR与OSW就是做这个事情的。帮助我们封装了具体的转换算法。
字节写字符实例
//字节流写探究
@Test
public void test12(){
//1.Stream
FileOutputStream fos = null;
//2.try catch finally
try {
fos = new FileOutputStream("test3");
//3.process data
String str = new String("hhh哈哈哈");
//如果不写字符集,则使用项目/本机默认字符集--中文系统为GBK,可手动调整项目为UTF-8
// byte[] bytes = str.getBytes();
byte[] bytes = str.getBytes("gbk");
//First:use write(byte[] bs) success
// fos.write(str.getBytes());
//Second:use write(byte b) success
for (int i = 0;i<bytes.length;i++)
fos.write(bytes[i]);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件流总结
文件流的父类很特殊,需要仔细记忆一下
字节文件流IO是直接继承自字节流抽象基类,但是字符文件流IO并不是直接继承自字符抽象基类R,W
而是继承自InputStreamReader,OutputStreamWriter——下面提到的转换流。而这个两个转换流是继承自字符抽象基类R,W
③转换流
【ISR,OSW】编码转换流简述
InputStreamReader 输入字节字符编码转换流,OuputStreamWriter 输出字节字符编码转换流
注:为了获得最高效率,请在BufferedWriter中包装OutputStreamWriter,以避免频繁的转换器调用
父类(R,W)子类(FR,FW)
输入字节字符转换流
①InputStreamReader
- SuperClass:
Reader - Direct Known Subclasses:
FileReader - All Implemented Interfaces:(父类)
Closeable, AutoCloseable, Readable
输出字节字符
②OuputStreamWriter
- SuperClass:
Writer - Direct Known Subclasses:
FileWriter - All Implemented Interfaces:(父类)
Closeable, Flushable, Appendable, AutoCloseable
构造器(2种*4重载)
InputStreamReader | 描述 |
---|---|
InputStreamReader(InputStream in) | 通过IS子类构建,默认字符集 |
InputStreamReader(InputStream in, Charset cs) | ~,指定字符集对象 |
InputStreamReader(InputStream in, CharsetDecoder dec) | ~,指定字符解码对象 |
InputStreamReader(InputStream in, String charsetName) | ~,指定字符集名称 |
注意:ISR传入的都是IS子类,和R子类没有一点关系
OutputStreamWriter | 描述 |
---|---|
OutputStreamWriter(OutputStream out) | 通过OS子类构建,默认字符集 |
OutputStreamWriter(OutputStream out, Charset cs) | ~,指定字符集对象 |
OutputStreamWriter(OutputStream out, CharsetEncoder enc) | ~,指定字符编码器对象 |
OutputStreamWriter(OutputStream out, String charsetName) | ~,指定字符集名称 |
注意:OSR传入的都是OS子类,和W子类没有一点关系
总结:这也就是为什么叫它字节byte字符char转换流——构造参数是IS/OS字节流及字符编码集,而外层包裹的缓冲流是却是字符缓冲流BufferedReader,BufferedWriter【BR,BW】。
全部函数【基本不用,使用BR/BW缓冲流】
返回值 | 方法 | 描述 |
---|---|---|
InputStreamReader | ||
void | close() | |
String | getEncoding() | 返回编码器名称 |
int | read() | 读一个字符 |
int | read(char[] cbuf, int offset, int length) | 读多个字符到数组指定部分 |
boolean | ready() | 此输入流是否已就绪 |
返回值 | 方法 | 描述 |
---|---|---|
OutputStreamWriter | ||
void | close() | |
void | flush() | |
String | getEncoding() | 返回字符编码器名称 |
void | write(int c) | 写入单个字符 |
void | write(char[] cbuf, int off, int len) | 写入指定部分char[] |
void | write(String str, int off, int len) | 写入指定部分String |
但是呢,其实BW,BR与上面的都是差不多的……hhh,有的甚至参数都没改。
读写不同编码文件转换实例
//也可以不把写读分开,放在一个try catch finally中,使用bw.flush()立即刷入缓存即可
//下面分开的意思是,可以将这个Test测试代码分为2个Java程序运行。当然记得拿走 Stream声明
@Test
public void test(){
//1.Stream
FileInputStream fis = null;
FileOutputStream fos = null;
InputStreamReader isr = null;
OutputStreamWriter osw = null;
BufferedReader br = null;
BufferedWriter bw = null;
//2.try catch finally
//先写
try {
fos = new FileOutputStream("GBKUTF.txt");
//"gbk"可改为本机已有CharSet,例如"UTF-8"
osw = new OutputStreamWriter(fos,"gbk");
bw = new BufferedWriter(osw);
//3.process data
bw.write("倒萨氪金大佬完结且old结案率等级为契机耳机嘞蒂萨傲娇穿裤子考虑到了");
bw.newLine();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//try catch finally
//后读
try {
fis = new FileInputStream("GBKUTF.txt");
//此处如果不加入"gbk"参数(本项目编码为UTF-8)会导致读出为乱码
isr = new InputStreamReader(fis,"gbk");
br = new BufferedReader(isr);
//3.process data
String string = br.readLine();
System.out.println(string);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
转换流总结
这个流,简单来说——就是屏蔽了UTF-16转换至其他常用编码方式的实现原理。方便广大程序猿只需要设定字符集就可以完美读取写入不同编码格式的文件。这个流可以被认为是过渡流,只需要知道在构造时改变默认字符集即可。
BR与BW在ISR与OSR基础上做了一些更加实用的封装,方便使用。
总结
IO流的基础,三个部分——①四大抽象基类,②节点流,③转换流
理解了这个3个基础部分,基本上就能随便使用后面的其他流。
举个简单的例子,缓冲流
缓冲流建立在字节流,字符流的基础上,可以简单的认为,只是增加了上面我手动写的buffer数组,并且根据程序猿在实践中大量的实际使用情况进行了常用功能封装。
更多细节请在IO入门系列②处理流Buffer流查看。