IO流(高级流)
IO体系结构
一,缓冲流
1.1 缓冲流体系结构
1.2 字节缓冲流
原理:底层自带了长度为8kb的缓冲区提高性能。利用缓冲区可以一次读取8kb数据提高了缓冲效率。
构造方法:
方法名 | 说明 |
---|---|
public BufferedInputStream(InputStream is) | 把基本流装成高级流,提高读取数据的性能 |
public BufferedOutputStream(OutputStream os) | 把基本流装成高级流,提高写出数据的性能 |
缓冲流本身是不能直接操作文件中的数据的,是在创建对象的时候关联了基本流,真正的读写数据的还是被关联的那个基本流。只不过通过缓冲流让这个基本流读写效率更高了。
这个构造方法的参数是抽象类,使用字节输入流字节输出流对应的实现类用多态即可。
字节缓冲流的写入和读取都是和字节流一模一样的,只是额外提供了读取性能而已。
-
字节缓冲流读写原理
缓冲流实际上还是由基本流进行数据的读取的。
但是基本流会一次性读取8192个数据交给缓冲流中的缓冲区。
缓冲输入流的缓冲区和缓冲输出流的缓冲区不是同一个缓冲区,而是各自有一个缓冲区。
当右边的缓冲区读取完了8192个数据,左边的缓冲区就会继续读取8192个数据。
变量b就是来回搬运数据的。
虽然两个缓冲区之间也是1个字节1个字节的交换,但是这是在内存中进行的交换!内存的运输速度极快。
缓冲流节约的是内存和硬盘之间交互浪费的时间。
当然变量b也可也是数组b,如果使用数组的话,就一次性一共数组的最大长度进行交换缓冲区间数据了。速度更加快了。
1.3 字符缓冲流
原理:底层自带了长度为8kb的缓冲区提高性能。但是字符流的底层本身就带了8kb的缓冲区,故提高的效率不是很明显。
字符缓冲流构造方法:
方法名 | 说明 |
---|---|
public BufferedReader(Reader r) | 把基本流变为高级流 |
public BufferedWriter(Writer w) | 把基本流变为高级流 |
字符缓冲输入流特有的方法
方法名 | 说明 |
---|---|
public String readLine() | 读取一行数据,如果没有数据可读了,返回null |
遇到 \r \n的时候才会停止读取。即这个虽然一次读取一行数据,但是不会读取到回车换行!
字符缓冲输出流特有的方法
方法名 | 说明 |
---|---|
public void newLine() | 跨平台的换行 |
因为不同的平台换行符号是不一样的。
细节 :缓冲流的续写功能是在Writer里面开启的!BufferedWriter是没有续写功能的!!!
二,转换流
转化流是字符流的高级流。属于字符流。
转换流是字符流和字节流的桥梁。
-
InputStreamReader:
- 解决不同编码时,字符流读取文本内容乱码的问题。
- 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了,
-
OutputStreamWriter:
- 作用:可以控制写出去的字符使用什么字符集编码
- 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了
构造方法:
方法名 | 说明 |
---|---|
public InputStreamReader(InputStream e,String charsetName) | 创建对象并指定字符编码 |
public OutputStreamWriter(OutputStream e,String charSetName) | 创建对象并指定字符编码 |
注:转换流虽然可以指定字符编码,但是在jdk11的时候可以直接使用字符流进行替代了。
三,数据流
数据流属于字节流的一部分,他们是高级流,用来对数据和数据的类型进行写入和读取操作的。
3.1 数据输入流
3.2 数据输出流
四,序列化流
序列化流是字节流的高级流,属于字节流。
分别为:
- ObjectInputStream(反序列化流)
- ObjectOutputStream(序列化流)
4.1 ObjectOutputStream(序列化流)
作用:可以把java中的对象写到本地文件中。
写出的数据有个特点:人无法读取,需要通过反序列化流来读取。
构造方法:
构造方法 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 把基本流包装成高级流 |
方法:
方法名 | 说明 |
---|---|
public final void writeObject(Object obj) | 把对象序列化(写出去)到文件中 |
小细节:如果直接使用序列化流去把对象序列化会报异常:NotSerializableException
- 解决方法:需要让被序列化的那个javabean实现
Serializable
接口
注:
Serializable
这个接口是没有抽象方法的,这种接口叫标记接口,一旦实现了这种接口,那么就表示当前的类可以被序列化。如果需要指定字段不进行序列化就在对应的属性前面加上关键字
transient
4.2 ObjectInputStream(反序列化流)
作用:把序列化到本地文件中的对象,读取到程序中。
构造方法:
构造方法 | 说明 |
---|---|
public ObjectInputStream(InputStream out) | 把基本流变成高级流 |
方法:
方法名 | 说明 |
---|---|
public Object readObject() | 把序列化到本地文件中的对象,读取到程序中 |
细节:这个返回值是Object类型的,如果想要获得对象真正的类型,需要进行强制类型转换。
4.3 注意要点
如果一个javabean类继承了Serializable接口。那么java会对这个类所有内容进行计算获得一个序列号。
如果已经序列化了这个类后,再对这个类进行修改,此时会得到新的版本号。如果此时使用反序列化。那么由于序列号不一致会导致异常的出现。
解决办法:固定这个序列号
public class Student implements Serializable{
private static final long serialVersionUID=1L;//这样就可以固定序列号,必须这么写不能改
private String name;
...
}
问题:如果对象中有一个成员属性不想序列化到本地
解决方法:加上transient 关键字即可
public class Student implements Serializable{
private static final long serialVersionUID=1L;//这样就可以固定序列号,必须这么写不能改
private transient String name;//加上这个关键字这个属性就不会序列化到本地文件中
...
}
问题:将多个对象序列化,解决如何多个反序列化。
public class a {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student s1=new Student(23,"张三");
Student s2=new Student(13,"李四");
Student s3=new Student(43,"王五");
ArrayList<Student> list=new ArrayList<>();//将要序列化的对象存入集合中,序列化这个集合即可解决
list.add(s1);
list.add(s2);
list.add(s3);
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("3.txt"));
oos.writeObject(list);
oos.close();
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("3.txt"));
ArrayList<Student> l1=(ArrayList<Student>)ois.readObject();
System.out.println(l1);
ois.close();
}
}
五,打印流
打印流是高级流,打印流不能读只能写,打印流属于输出流。
打印流分为:PrintStream
(字节打印流),PrintWriter
(字符打印流)
打印流特点:
-
打印流只能写不能读
-
打印流有特有的写出方法,可以实现数据的原样写出
-
特有的写出方法可以实现,自动刷新,自动换行
打印一次数据=写出+换行+刷新
5.1 字节打印流
字节打印流是默认自动刷新的
构造方法:
构造方法 | 说明 |
---|---|
public PrintStream(OutputStream/File/String) | 关联字节输出流/文件/文件路径 |
public PrintStream(String fileName, Charset charset) | 指定字符编码 |
public PrintStream(OutputStream out,boolean autoFlush) | 自动刷新 |
public PrintStream(OutputStream out,boolean autoFlush, String encoding) | 指定字符编码并且自动刷新 |
成员方法:
成员方法 | 说明 |
---|---|
public void write(int b) | 常规方法:规则和之前一样,将指定的字节写出 |
public void println(Xxx xx) | 特有方法:打印任意数据,自动刷新,自动换行 |
public void print(Xxx xx) | 特有方法:打印任意数据,不换行 |
public void printf(String format,Object... args) | 特有方法:带有占位符的打印语句,不换行 |
举例:
public class a {
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
PrintStream ps=new PrintStream(new BufferedOutputStream(new FileOutputStream("1.txt",true)),true,"GBK");
//这里创建方法中因为是父类OutputStream故参数可以写这个父类里面的所有子类
//最里面的true,是字节输出流的true,表示开启续写功能
//之所以写BufferedOutputStream是因为这个让字节打印流有缓冲区,从而可以实现自动刷新功能
ps.printf("%s helllo world","你好");
ps.print(11);//直接打印的就是11
ps.println(true);//直接打印的就是true
ps.close();
}
}
5.2 字符打印流
字符打印流和字节打印流的区别就是,字符打印流底层多了缓冲区,想要刷新需要开启。
构造方法:
构造方法 | 说明 |
---|---|
public PrintWriter(OutputStream/Writer/File/String) | 关联字节输出/文件/文件路径 |
public PrintWriter(String fileName, Charset charset) | 指定字符编码 |
public PrintWriter(Write w, boolean autoFlush) | 自动刷新 |
public PrintWriter(OutputStream out, boolean autoFlush, Charset charset) | 指定字符编码并自动刷新 |
成员方法:和字节打印流一模一样。。。
PrintStream和PrintWriter的区别
- 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法。
- PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。
5.3 打印流和输出语句的关系
输出语句里面的System.out.printkn
中out
是一个字节打印流类型,即public static final PrintStream out=null
这样的形式在System类中展示的。这样打印流默认指向控制台。
举例:
//获取打印流对象,此打印流在虚拟机启动时,由虚拟机创建,默认指向控制台。
//特殊的打印流:系统中的标准输出流
//标准输出流是不能关闭额,在系统中是唯一的!
PrintStream os=System.out;
//调用打印流中的方法
//写出数据,自动换行,自动刷新
ps.println("123");
ps.close();//关闭了系统标准输出流后!下面的方法就不能执行了
System.out.println("123");//就是上面的方法,用链式编程搞出来的
PrintStream os=new PrintStream("/1.txt");
System.setOut(ps);//输出语句重定向
六,解压缩流与压缩流
应用场景:
- 如果传输的数据较大,可以先压缩再传输。
- 对压缩包的解压
解压缩流属于:InputStream,字节输入流
压缩流属于:OutputStream,字节输出流
6.1 解压缩流
解压缩直接看下面的步骤即可,所有的压缩通用这样方法即可,简单过一遍,要用再查。。。
这样压缩包必须是Zip压缩的,其他的rar,7zip需要使用Maven导入相关依赖解决,暂不考虑。
压缩包里面的每一个文件都是ZipEntry
对象
解压的本质:把每一个ZipEntry按照层级拷贝到本地的另一个文件夹中。
可以把解压缩流当作FileInputStream,只不过这个FileInputStream是读取压缩包的即可。
构造方法:
构造方法 | 说明 |
---|---|
public ZipInputStream(InputStream in) | 创建一个解压缩流对象 |
public ZipInputStream(InputStream in, Chartset charset) | 创建一个解压缩流对象,使用指定编码 |
使用解压缩流的时候,默认使用UTF-8打开,而中国这边的windows操作系统默认是GBK。
需要在创建对象的时候指定编码方式,要不然会报非法参数异常。
细节:
- 调用一次getNextEntry()就获取一个文件对应得ZipEntry对象,如果获取了全部的ZipEntry对象,再次获取就返回null
解压步骤:
public class a {
public static void main(String[] args) throws IOException {
//1.创建一个File表示要解压的压缩包
File src=new File("C:\\Users\\WDADWA\\Desktop\\learn_file\\java.zip");
//2.创建一个File对象表示要解压的目的地
File dest=new File("C:\\Users\\WDADWA\\Desktop\\learn_file\\新建文件夹");
unzip(src,dest);
}
//定义一个方法来解压
public static void unzip(File src,File dest) throws IOException {
//解压的本质就是把压缩包里面的每一个文件读取出来,按照层级拷贝到目的地中
//创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip=new ZipInputStream(new FileInputStream(src),Charset.forName("GBK"));
//表示当前在压缩包获取到的文件/文件夹
ZipEntry entry;
while((entry=zip.getNextEntry()) != null){
//zip.getNextEntry()获取压缩包内文件,执行一次获取一次,获取完毕就返回null
if(entry.isDirectory()){
//文件夹:需要在目的地dest同样创建一共文件夹
File file=new File(dest,entry.toString());
file.mkdirs();
}else{
FileOutputStream fos=new FileOutputStream(new File(dest,entry.toString()));
//文件:需要读取到压缩包中的文件,并把它按照层级目录放到目的地desk中
int b;
while((b=zip.read()) != -1){
fos.write(b);
}
fos.close();
//表示压缩包中的一个文件处理完毕
zip.closeEntry();
}
}
zip.close();
}
}
6.2 压缩流
压缩流和解压缩流的创建方法差不多,了解一下常用的压缩办法即可,要压缩的时候直接查。
- 压缩单个文件
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class a {
public static void main(String[] args) throws IOException {
//创建File对象表示要压缩的文件
File src=new File("a.txt");
//创建file对象表示压缩包位置
File dest=new File("C:\\Users\\WDADWA\\Desktop\\learn_file\\java");
//调用方法来压缩
toZip(src,dest);
}
/*
作用:压缩
参数一:表示要压缩的文件
参数二:表示压缩包位置
*/
public static void toZip(File src,File dest) throws IOException {
//1.创建压缩流关联压缩包
ZipOutputStream zos=new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
//2.创建ZipEntry对象,表示压缩包里面每一个文件夹
ZipEntry entry=new ZipEntry("a.txt");
//3.把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
//4.把src文件中的数据写入压缩包
FileInputStream fis=new FileInputStream(src);
int b;
while((b=fis.read()) != -1){
zos.write(b);
}
zos.closeEntry();
zos.close();
}
}
- 压缩多个文件
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class a {
public static void main(String[] args) throws IOException {
//1.创建File对象表示要压缩的文件夹
File src=new File("C:\\Users\\WDADWA\\Idea_coding\\Array_text");
//2.创建file对象表示压缩包位置
File dest=new File("C:\\Users\\WDADWA\\Desktop\\learn_file",src.getName()+".zip");
//3.创建压缩流关联压缩包
ZipOutputStream zos=new ZipOutputStream(new FileOutputStream(dest));
//4.获取src里面的每一个文件,变成ZipEntry对象,放入压缩包当中
toZip(src,zos,src.getName());//src.getName()放入的是Array_text
//5.释放资源
zos.close();
}
/*
作用:获取到src里面的每一个文件,变成ZipEntry对象,放入压缩包中
参数一:数据源
参数二:压缩流
参数三:压缩包内部的路径
*/
public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
//1. 进入src文件夹
File[] files=src.listFiles();
//2.遍历数组
for (File file : files) {
if(file.isFile()){
//3.判断-文件,变成ZipEntry对象放入压缩包
ZipEntry entry=new ZipEntry(name+"\\"+file.getName());//难点在这Array_text\file.getName()
zos.putNextEntry(entry);//把entry对象放入压缩包中
//读取文件中的数据,写入压缩包中
FileInputStream fis=new FileInputStream(file);
int b;
while((b=fis.read())!=-1){
zos.write(b);
}
fis.close();
zos.closeEntry();//代表这个文件处理完毕
}else{
//4.判断-文件夹,递归
toZip(file,zos,name+"\\"+file.getName());
}
}
}
}