[19/03/30-星期六] IO技术_四大抽象类_ 字节流( 字节输入流 InputStream 、字符输出流 OutputStream )_(含字节文件缓冲流)

时间:2021-10-20 03:46:15

一、概念及分类

InputStream(输入流)/OutputStream(输出流)是所有字节输入输出流的父类

【注】输入流和输出流的是按程序运行所在的内存的角度划分的

字节流操作的数据单元是8的字节,字符流操作的数据单元是16位的字符

【流的概念】

——————————

ooooooooooooooooo

—————————— (输入流模型,文件从头(左边)到尾(右边),)

↑,(记录指针)

每个‘’o“”看出一个"水滴",不管是字节流还是字符流,每个水滴是最小的输入/输出单位,对于字节流而言,每个水滴是1个字节(8位)

对于字符流而言,每个水滴是1个字符(16位)。输入流使用隐含的记录指针来表示当前正准备从哪个水滴开始读取,每当程序从InputStream或

Reader里取出一个或多个水滴后,指针自动向后(指针从左往右)移动。此外,还可以控制记录指针的移动,比如说设置偏移量off。

对于输出流相对于把水滴放入输入流水管中,也有记录指针自动控制移动,每当程序向OutputStream或Writer输出一个或多个水滴后,记录指针自

动向后移动。

1、InputStream(重要的是 文件操作字节流FileInputStream子类和带8KB缓冲数组的ButteredInputStream)

此抽象类是表示字节输入流的所有类的父类。InputSteam是一个抽象类,它不可以实例化。 数据的读取需要由它的子类来实现。根据节点的不同,

它派生了不同的节点流子类 。 继承自InputSteam的流都是用于向程序中输入数据,且数据的单位为字节(8 bit)。

int read():从(所写程序的源文件中)一个一个字节读取数据,并将字节的值作为int类型返回(0-255之间的一个值)。

如果未读出字节则返回-1(返回值为-1表示读取结束)。

int (byte b[]) :一段一段的读取数据

void close(): (通知操作系统)关闭输入流对象,释放相关系统资源。

【代码示例】字节文件操作流_不带缓冲数组 (FileInputStream)

 /*输入流(从数据源输入到程序中) :数据源-->>程序,是从数据源(源文件、类比书本上知识)读取(read)数据到程序(目标程序、大脑)
* 输入流对应read()方法 ,从书本上流入(输入、Input)到大脑中,而大脑需要读取(read)书本上的知识
* 步骤:选择源数据-->>选择流(选哪个搬家公司)-->>操作(一个一个读/写or一段一段读/写,即怎么搬家)-->>释放系统资源(让搬家公司走)
*
一、 read():从输入流中读取一个8位的字节的数据,(程序自动)把它转成0-255之间的整数,并返回这个整数。 采用逐个读取字节
1.从读取流读取的是一个一个字节
2.返回的是字节的(0-255)内的字节值 ASCII码
3.读一个下次就自动到下一个,如果碰到-1说明没有值了. 二、 read(byte[] bytes): 从输入流中最多读取bytes.length(定量)个字节的数据(最后一次可能不满,前面肯定是满的),并将
它们存在byte数组中,返回实际读取的字节数
1.从读取流读取一定数量的字节,如果比如文件总共是102个字节
2.我们定义的数组长度是10,那么默认前面10次都是读取10个长度
3.最后一次不够十个,那么读取的是2个
4.这十一次,每次都是放入10个长度的数组. 三、read(byte[] bytes,int off ,int len):从输入流中最多读取len个字节(字节可以设定,但不超过数组最大容量)将其存在bytes
数组中,但放入数组的时候并不是从起点开始的是从可以自己设定的off位置开始的(当然可以设为0,表示从起点开始)返回实际读取的字节数
1.从读取流读取一定数量的字节,如果比如文件总共是102个字节
2.我们定义的数组长度是10,但是这里我们写read(bytes,0,9)那么每次往里面添加的(将只会是9个长度),就要读12次,最后一次放入3个.
3.所以一般读取流都不用这个而是用上一个方法:read(byte[]); void close()关闭输入流并释放与该流相关的所有系统资源
*/
package cn.sxt.test; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; public class Test_0329_InputStream {
public static void main(String[] args) throws IOException { //与外界(操作系统读写)有联系,可能有异常,所以抛出异常
//1、选择源文件 内容为:hello China I love you
File file=new File("编码_GBK_纯英文.txt"); //GBK编码 英文占一个字节
//2、选择流 FileInputStream 顺序的读取文件,只要不关闭每次调用read()方法就顺序的读取源数据中【剩余】的内容
InputStream iStream=new FileInputStream(file); //这种写法也对,综合了第一步和第二步,更加简单
FileInputStream iStream2=new FileInputStream("编码_GBK_纯英文.txt"); //3-1-1、有点绕的写法 测试read()方法选择操作 (读/写) 循环读取 一个一个字节去读
/*int temp;
while ((temp=iStream.read())!=-1) {
System.out.print((char)temp); }*/
//3-1-2、最可以让人理解的一种写法
/*int b=0;//b的作用记住每次读取的一个字节的整形返回值(英文、数字及常见符号其实就是ASCII码的值)
while (true) {
b=iStream.read();
if (b==-1) {//如果读出字节为-1 跳出整个循环表示已经到文件末尾
break;
}
System.out.print((char)b);
}*/ /*//3-2 测试read(byte[] b)方法 将数据读取到准备好的字节数组(byte[],这里名字叫datas)中,同时返回读取字节数组的实际长度
// 如果到结尾,返回-1
byte datas[]=new byte[4]; //新建一个大小为4的字节(byte)数组,名字叫datas.
int length=iStream.read(datas);//length=4 就是每次读取时字符的实际长度,因为读到最后是可能剩余的字符不足4 可能为3
//当最后读取完后,再去读取时发现没有了,则返回-1 表示读取结束
//String msg2=new String(datas, 0, datas.length, "GBK"); 标准解码语句
String msg2=new String(datas, 0, length); //字节数组解码成字符
System.out.println(msg2); //输出 "hell" 因为没用循环只表示一次读取的情况,一次只读取了4个字节的字符
//可以加循环或者加大datas数组的大小,让其一次读完
*/ //3-3 测试read(byte[] b,int off,int len)方法 。读取任意一段字节。off(偏移量):表示把源文件中从头(即下标)开始读取到的字节数存在
//datas数组下标为off开始的位置,读取源文件的长度为len,也就是在datas数组中下标为off开始位置存储长度为len字节的源数据
byte datas[]=new byte[7]; //虽然定义的数组datas大小为7个字节,但每次只添加4个字节的字符,表示使用datas数组的实际使用空间为4个字节。
//2的含义:表示把读取到数据存在datas数组下标为2的位置 即datas[2]='h'意味着datas[0]=datas[1]=0,即null空字符
//4的含义:在datas数组中从下标为2的位置开始存储一段长度为4的源数据字节(从源文件开头读的) 即
//datas[2]=104,'h';datas[3]=101,'e';datas[4]=108,'l';datas[5]=108,'l' datas[6]=0 ,null
int length=iStream.read(datas, 2, 4);//read()方法返回length=4 4:代表实际存储一段长度为4的字节,其它下标位置存储为空
System.out.println(length);
for (int i=0;i<datas.length ;i++) {
System.out.printf("datas[%d]=%d;",i,datas[i]); } //ASCII码 (范围 0-127)null 0;a 97;b 98;c 99;d 100;e 101;f 102; g 103 ;h 104;i 105; g 106;k 107;l 108
//字节数组解码成字符 从数组datas下标为1的地方开始解码(只测试纯英文的情况,含中文数字可能报异常)。
//解码长度length=4,代表解码一段长度为4的字节码文件 输出" hel";也可以length-1 解码一段长度为3的datas数组文件
//输出结果为" he"
String msg2=new String(datas, 1, length); //String 构造方法,字节-->>字符
System.out.println("\n"+msg2); /*
int data1=iStream.read();//读取第一个字符 "h" 输出的是"h"的ASCLL码104
int data2=iStream.read();//读取第二个字符 "e"
int data3=iStream.read();//读取第三个字符 "l"
System.out.println(data1);//返回的数据的ASCII码需要强制转型 。文件的末尾如果没有数据返回-1
System.out.println((char)data2);
System.out.println((char)data3);*/ //4、释放资源
iStream.close(); } }

2、 OutputStream  (重要的是 文件操作字节流FileOutputStream子类和带8KB缓冲数组的ButteredOutputStream子类))

此抽象类是表示字节输出流的所有类的父类。输出流接收输出字节并将这些字节发送到某个目的地。

void write(int n):向目的地(即所写程序)中写入一个字节。

void write(byte b[]):一段一段去写数据

void  (byte b[],int off,int len) :从指定的字节数组写入len字节,从偏移量off开始输出到此输出流

void flush() :刷新此输出流,并强制任何缓冲的输出字节流被写出。

void close(): (通知操作系统)关闭输出流对象,释放相关系统资源。

【代码示例】

 /*
*输出(Output)流(Stream)。
*通过写字( write()方法 ),把大脑中的知识(类比程序)输出(写出)到(Output)外界的作业本上(类比源文件,如a.txt文件),去更改作业本上原有的内容
步骤:创建源-->>选择流-->>操作(写出内容)-->>释放资源
void write(int n): 向目的地(即所写程序)中写入一个字节。逐个写入字节
n应是读取到的源数据编码值。如把"love"写入目标文件, 一个一个字节读,读到'l' 此时这里的n应该是'l'的ASCII码值108,依次往下读
while(( len=in.read() )!=-1){
out.write(len);
} 这段程序完成文件的复制,表示读到的十进制编码值(0-255之间,英文就是ASCII码),然后把这个值送到write()方法中,让其执行写入操作
但是效率很低,它是读一个字节取到编码值后,然后再根据编码值去写入,循环往复。一个字节一个字节读和写.好比很多货物但每次只送一个,
循环往复,尽管车厢很大,只装一个,浪费油。电脑中就是浪费内存,不停进行磁盘读写,速度很慢。 void write(byte b[]):一段一段去写数据。把参数b指定的字节数组的【所有字节】写入到目标文件中去。批量写入
为了提高送货速度则可以每次把车厢装满,减少送货次数 车厢就是byte[]数组 批量送货,减少磁盘读写
void (byte b[],int off,int len) :将指定的byte数组【从偏移量off开始的len个字节】写入到目标文件。 批量写入 void flush() :刷新此输出流,并强制写出所有缓冲中的字节
void close() : 关闭输出流并释放系统资源 *
*/
package cn.sxt.test; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream; public class Test_0329_OutputStream {
public static void main(String[] args) throws IOException {
//1、给出输出流目的地(给出作业本)(与read()不同那个源文件必须存在,才可以被正确读出,这里可不存在,帮你创建源文件)
File file=new File("编码_GBK_输出流.txt");
//2、创建指向目的地的输出流。参数:true:(前提是文件已存在)表示同意在文件末尾追加新字节数组, false:表示不同意,即覆盖目的地文件的内容
OutputStream oStream=new FileOutputStream(file,true);
//3、让输出流把字节数组写入目的地
String msg="I love you";
byte datas[]=msg.getBytes();
//ASCII码 :范围是十进制的 0-127
//byte:整形变量(8位,范围十进制的-128-127) 字符串-->>字节存在名叫datas的数组 :编码
//再次理解getBytes()方法: 此方法所做的工作就是英文字符(按GBK的方式,本电脑eclipse默认是GBK)进行编码,然后把它们的
//十进制编码存在datas数组中,GBK中文占3个字节,英文占1个字节.GBK兼容ACSII码,所以英文字符的编码就是它们的ACSII码
//而且一个datas数组下标对应一个字节,对于英文1个下标存储1个字符如:datas[0]=73对应英文字符'I' datas[1]=32 对应' '(空格)
//假设有中文字符则是datas[n-1][n][n+1]这3个连续的空间共同存储一个中文字符(占3个字节)。这3个空间返回的分别是这个
//中文字符的编码的(23-16位,15-8位,7-0位)的十进制数。 for (int i=0;i<datas.length ;i++) {
System.out.printf("datas[%d]=%d;",i,datas[i]);
//3-1:利用循环向txt文件中一个一个字节写入
//oStream.write(datas[i]); }
//3-2 一段一段字节去写入
//oStream.write(datas);//把datas数组中的全部字符写入目的文件 //3-3 写入部分字节 往目的地的txt文件中写了datas数组的部分字节(从下标2(字符'l')开始,长度为8的一段字节)
oStream.write(datas, 2, datas.length-2); //4、关闭输出流
oStream.close();;
} }

【示例】利用输入流和输出流进行文件复制_不带缓冲数组 (FileInputStream)

 /*
*利用输入流和输出流完成文件的复制
* 输入流(InputStream) 书本的知识输入到大脑中,大脑要读取(read)知识
* 输出流(OutputStream) 大脑中的知识要写出(write)到作业本上
* 1、为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应地,读取时使用的方法为:read(byte[] b)
* 写入时的方法为:write(byte[ ] b, int off, int length)。
*/
package cn.sxt.test; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; public class Test_0330_CopyFile {
public static void main(String[] args) throws Exception { //copy("SongYi.jpg", "Song2.jpg"); 图片也可复制,但是要注意设置缓冲数组的大小
copy("src.txt", "src_copy3.txt");
} static void copy(String src,String dest) throws IOException{
//1、获取源数据
File file =new File(src);//源文件 从这里读取字节
File file2=new File(dest);//目标文件 往这里写入字节 //2、选择流
InputStream iStream=new FileInputStream(file);//输入流 。从源文件(书本)输入到程序中(大脑中)
OutputStream oStream=new FileOutputStream(file2);//输出流。 从程序中(大脑中)输出到目标文件(作业本)上.采用的是覆盖,而不是追加 //3、选择操作
/*把读到数据存在butter数组从下标0开始的位置,每次存储长度为数组的长度(也即一次就读完)。若butter.length 改为4表示
*每次只读取源文件的(从头开始的)前4个字节,存在butter[0]--butter[3]中,至于源文件剩下的需要再次运行程序读取或加个循环
*对于较大的文件(如图片)可以加大缓冲数组的容量
*/
byte butter[]=new byte[1024];//缓冲数组,数组的大小为1024 。中转站 大脑 /* //3-1:整体读取写入,每次都固定读取1024个字节
iStream.read(butter);
oStream.write(butter);*/ // 3-2 :可以选择整体读取或者部分读入(即把butter.length的数值改低).这里起到效果是整体读取与3-1是一个道理
iStream.read(butter,0,butter.length);
oStream.write(butter, 0, butter.length); /*//可以具体看看读取到的内容 详细步骤
iStream.read(butter,0,butter.length);
String msg=new String(butter,0,butter.length);//字节-->>字符串:解码
System.out.println(msg);
byte datas[]=msg.getBytes();//对读取到的msg字符串进行编码:字符串-->>字节
oStream.write(datas, 0, datas.length); */ //4、关闭流
oStream.close();
iStream.close(); }
}

【代码示例】字节文件操作流_带缓冲数组 (ButteredInputStream)大小为8192B(大小就是8KB,2^13次方,1KB=1024B,)

·

 /**
* 学习 BufferedInputStream/BufferedOutputStream 缓冲字节流类
*复制文件 测试Java自带的在缓冲数组的输入输出流
*/ package cn.sxt.test; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; public class Test_0330_Buffered {
public static void main(String[] args) throws IOException {
/*File file=new File("poem.txt");
InputStream iStream=new FileInputStream(file);*/
//带缓冲的输入流 缓冲区为8KB 8192B (8192个字节)
FileInputStream file=new FileInputStream("poem.txt");
BufferedInputStream iStream=new BufferedInputStream(file); FileOutputStream file2=new FileOutputStream("poem2.txt");
BufferedOutputStream oStream=new BufferedOutputStream(file2);
long b=System.currentTimeMillis();//获取系统当前时间
int len;
while ( (len=iStream.read()) !=-1) {
oStream.write(len); //当调用read()或write()方法时 首先将读写的数据存入类内部定义好8KB的数组中,然后一次性
//读写到文件中,类似于前边自己自定义的大小为1KB的数组,极大提高读写效率
}
long c=System.currentTimeMillis();
System.out.println("共花费:"+(c-b)+"毫秒");
iStream.close();
oStream.close(); //不带缓存缓冲的
FileInputStream file3=new FileInputStream("poem.txt");
FileOutputStream file4=new FileOutputStream("poem3.txt"); long d=System.currentTimeMillis();//获取系统当前时间
int length;//不带缓冲区的,老办法,读取一个字节,往文件中写一个字节。循环往复直到结束。速度很慢
while ( (length=file3.read()) !=-1) {
file4.write(length);
}
long e=System.currentTimeMillis();
System.out.println("共花费:"+(e-b)+"毫秒"); //很明显比带缓冲区的花费时间多
file3.close();
file4.close(); } }