IO流
- 程序到文件,文件到程序
- 分为输出流和输入流
- 流分为字节流,字符流
字节流可以操作所有类型文件,字符流只能操作文本文件(可以用windows记事本打开并且能正常读懂的文件) - 俩都有
.read()``.write()``.read(byte[] bytes)
等方法,空参的返回值是文件字节,有参返回的是指针移动了多少位,一般受到数组长短的影响,有参无参有个相同点就是指针走到末尾了,就会返回-1
流的结构图:只有下面接口的实现类才可以创建对象,如FileOutStream(文件字节输出流)
字节流: 一般不用于读入写出中文
从下图更清晰的知道,再上面接口名字的基础上添加前缀就可以形成新的流(这里还有一些流没有展示,但是命名规则都是按照上面这句话来)
创建一个文件字节输出流:
package test;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test01 {
public static void main(String[] args) throws IOException {
FileOutputStream outputStream=new FileOutputStream("a.txt");
//下面的数字走的是阿斯克码值
outputStream.write(97);
outputStream.close();
}
}
关于创建上面这个流的细节值得念叨一下:
细节1:和上面的创建有点不一样欧,但是上面创建方式的底层其实就下面这货
FileOutputStream outputStream=new FileOutputStream(new File("a.txt"));
细节2:注意是父类,不是根目录。在这里可以通俗点看成路径名中的倒数第二位,如
c/b/a.txt
中b
是父类,而c
是根目录位置
细节3:就是会覆盖掉之前的内容
字节输出流的三种方式:
第一种:直接放
第二种:
byte[] bytes={97,98,99};
//走的数组
outputStream.write(bytes);
第三种:
byte[] bytes={96,97,98,99,100};
//参数一:数组,参数二:从哪个下标开始,
//参数三:显示多少个元素
outputStream.write(bytes,3,2);
输出字符串: 将String类型数据装进数组中,进行输出到文件中
换行: 换行操作,将String类型\r\n
装进数组
package test;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test04 {
public static void main(String[] args) throws IOException {
//将转义字符装进数组中
String br="\r\n";
byte[] bytes1=br.getBytes();
//将String类型装进数组中
String arg="daliwang";
byte[] bytes=arg.getBytes();
//打开字节流
FileOutputStream outputStream=new FileOutputStream("d.txt");
outputStream.write(bytes1);
outputStream.write(bytes);
outputStream.close();
}
}
续写: 接着文件中已经有的内容进行续写,就是不删除原来的内容,给创建字节流的时候添加参数
//第二个参数添加true,表示续写
FileOutputStream outputStream=new FileOutputStream("d.txt",true);
outputStream.write(bytes1);
outputStream.write(bytes);
outputStream.close();
文件输入字节流:
将数据读取出来,.read()
的返回值是int
类型的对应文件中的阿斯克码值,一次只会读一个字节(对于其他类型的文件也生效,如.mp4
),读一次指针移动一次,数据读完了,想要再读,就会返回-1(别的情况就正常返回应当的数),想循环读取就加个循环呗:
package test;
import java.io.FileInputStream;
import java.io.IOException;
public class Test05 {
public static void main(String[] args) throws IOException {
FileInputStream inputStream=new FileInputStream("d.txt");
int b;
while((b=inputStream.read())!=-1){
System.out.println(b);
}
}
}
文件拷贝:
将文件读取一下,再将其写出(下面的是.mp3
文件,可知读出时返回的值不是简单的int
类型数字,而是当前文件的每一个字节对应的数字,后面再读入将这些字节转换成回文件本来的类型)
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test06 {
public static void main(String[] args) throws IOException {
FileInputStream inputStream=new FileInputStream("C:\\Users\\李家院刘俊辉\\Desktop\\老歌共享\\多年以后我还能不能活着.mp3");
FileOutputStream outputStream=new FileOutputStream("e.mp3");
int b;
while((b=inputStream.read())!=-1){
outputStream.write(b);
}
outputStream.close();
inputStream.close();
}
}
当文件比较大时,会减缓速度,于是加个数组(读到字节数据会放入数组),从文件中读取了一定的量(和数组长度有关),返回值和指针移动有关,再进行输出到新文件中:看注释,答案都在里面
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test07 {
public static void main(String[] args) throws IOException {
FileInputStream inputStream=new FileInputStream("d.txt");
FileOutputStream outputStream=new FileOutputStream("c.txt");
byte[] bytes=new byte[2];
int b;
//数组长度为多少,一次可以就读多少,
//返回值为本次指针移动了多少,因为剩下未读的
//字节数不一定满足
//设返回值为b,为当前指针移动了多少,直到文件的末尾时
//就会返回-1
while ((b=inputStream.read(bytes))!=-1){
//看下当前的返回值指的是什么:2为多少个字节数据,-1为到末尾了
System.out.println(b);
outputStream.write(bytes);
}
outputStream.close();
inputStream.close();
}
}
有个问题:这里的数组大小是2,于是每一次只读2个,如果文件的字节数为奇数个,则指针走到最后一个时,就会用最后一个字节替换掉上一个成对字节中的第一个字节(数据结构):
输出的文件为:
关键在于每次读的大小都是固定的,所以就会这样
解决办法:用.read(三个参数)
指定每次都写出的多少为读入时指针移动的个数即可:
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test08 {
public static void main(String[] args) throws IOException {
FileInputStream inputStream=new FileInputStream("d.txt");
FileOutputStream outputStream=new FileOutputStream("c.txt");
byte[] bytes=new byte[2];
int b;
//数组长度为多少,一次就读多少,返回值为本次读到了多少
//返回值为b,为当前返回了多少字节数据,知道文件的末尾时
//就会返回-1
while ((b=inputStream.read(bytes))!=-1){
//看下当前的返回值指的是什么:2为多少个字节数据,-1为到末尾了
System.out.println(b);
//设置写出的数据量为读入时的指针移动数
outputStream.write(bytes,0,b);
}
outputStream.close();
inputStream.close();
}
}
结果就是两文件一模一样了
乱码:
将字符数据
转成字节格式,就是编码
将字节格式的数据转换成字符类型
的数据,就是解码
只有编码解码用的用一种方式才可以正确的得到想要的数据,idea
默认UTF-8
,eclipse
默认为GBK
具体的,这里简单展示idea
的代码:指定编码解码格式
package test;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Test09 {
public static void main(String[] args) throws UnsupportedEncodingException {
String name="张舟荥";
//将其转换成字节格式,方式叫做GBK编码
byte[] bytes=name.getBytes("GBK");
System.out.println(Arrays.toString(bytes));
//转换字符串,方式叫做GBK解码
String st=new String(bytes,"GBK");
System.out.println(st);
}
}
字符流: 可以用来读入写出中文
适用于对文本文件进行操作,并且输入流的read()空参方法遇到英文时就像上面的字节流一样,一次读一个字节,但是遇到中文时,一次读多个字节,然后都要再将其转换成十进制返回
创建对象,然后在里面写入文件的路径有两种方式,和前面字节流一样,然后调用的方法也一样,.read()
这种不含有参数的返回数据,含参数的返回指针移动的位数
.read()
不含参数: 具体的细节看后面的流程原理
package test;
import java.io.FileReader;
import java.io.IOException;
public class characterTest01{
public static void main(String[] args) throws IOException {
FileReader reader=new FileReader("z.txt");
int b;
while((b=reader.read())!=-1){
//直接将其转换为char型的数据
System.out.print((char) b);
}
}
}
.read(char[] chars)
含参数: 和字节流的一样,先创建一个char
型的数组,再放在.read(参数)
中
具体的细节看后面的流程原理
package test;
import java.io.FileReader;
import java.io.IOException;
public class characterTest01{
public static void main(String[] args) throws IOException {
FileReader reader=new FileReader("z.txt");
int b;
char[] chars=new char[2];
//有参的.read()方法,将数据放入数组中
while((b=reader.read(chars)) !=-1){
System.out.print(new String(chars,0,b));
}
reader.close();
}
}
字符输入流含参空参流程原理:
空参:读到的数据进行解码,然后将其进行转换成十进制
含参:多了一个将十进制的数据强转char[]
类型数据的操作
输入流底层原理:
字符输出流
方法要比字节输出流多一点:
代码实现:
package test;
import java.io.FileWriter;
import java.io.IOException;
public class characterTest02 {
public static void main(String[] args) throws IOException {
FileWriter writer=new FileWriter("z.txt");
//将下面的十进制先转成字节数据,
// 再将其转化成汉字放在指定的文件里面
writer.write(25105);
//将下面的汉字写入,转换成字节数据,
//再将其转成汉字放入指定的文件中
writer.write("将来进行时");
//将数组写入分别转换成字节数组,
//再将其装换成汉字放入文件中
char[] chars={'s','我'};
writer.write(chars);
//关闭流
writer.close();
}
}
输出流的底层原理:
先将输出的数据放入一个大小为8192的缓冲区中,当缓冲区满了之后,才真正的将其放入文件中,所以没装满缓冲区的时候想要放入文件中,就要刷新writer.flush()
。
还有一点,流关闭之前会将缓冲区中的数据全部放入文件中,再关闭流
拷贝文件夹
将一个文件夹(a
)中的子文件夹和文件全部拷贝到指定的文件夹下(b
)
先获得文件夹得来去路径,用File类的方法得到循环问遇到文件夹还是文件,遇到a
中文件就直接执行字节流进行读入和写出,遇到a
中文件夹就直接递归,再次调用本方法,就是回到前面那句话 用File类的
的位置再来一遍就可以得出a
文件夹中的文件夹中的文件,如此就全部拷贝了:
package test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ExampleTest01 {
public static void main(String[] args) throws IOException {
File src=new File("z:\\a");
File dest=new File("z:\\b");
copydir(src,dest);
}
private static void copydir(File src, File dest) throws IOException{
//为防止b文件夹没有创建,就自己创建一下,如果已经
//创建了,就失败呗,不会有影响
dest.mkdirs();
int a=0;
//将当前传进来的文件的这条路径写成数组
File[] files=src.listFiles();
//判断这条路径指的东西是文件还是文件夹?
for (File file:files) {
//为文件,就正常拷贝
if(file.isFile()){
System.out.println("文件"+a);
FileInputStream fis=new FileInputStream(file);
FileOutputStream fos=new FileOutputStream(new File(dest,file.getName()));
byte[] bytes=new byte[1024];
int b;
while ((b=fis.read(bytes))!=-1){
fos.write(bytes,0,b);
}
System.out.println("文件执行");
fos.close();
fis.close();
}
//为文件夹就再调用本方法,
//这样这个文件夹的中的文件就可以被拷贝了
else {
copydir(file,new File(dest,file.getName()));
}
}
}
}
缓冲流 :基本流的包装款#
字节缓冲流:BufferedInputStream
和BufferedOutputStream
,这两的底层还是字节流,所以用他们的时候,还是把他们当成字节流来用就行,他们的意义在于与磁盘进行传输数据的总体花费时间变短,先把数据存入长度为8192
的缓冲区中,缓冲区满了,再和磁盘进行传输数据操作,这样就会提高速度
简单的看一下创建,写入的时候记得刷新一下.flush()
,当然关闭流就会自动将缓冲区中的所有数据写在指定文件中
关闭流:关闭缓冲流的,就会自动先关闭非缓冲流
package test;
import java.io.*;
public class bufferStreamTest01 {
public static void main(String[] args) throws IOException {
BufferedInputStream inputStream=new BufferedInputStream(new FileInputStream("a.txt"));
BufferedOutputStream outputStream=new BufferedOutputStream(new FileOutputStream("b.txt"));
int b;
byte[] bytes=new byte[2];
while((b=inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,b);
}
outputStream.close();
inputStream.close();
}
}
原理:输出和输入的缓冲区不是同一个,而这个有参方法自己也有字节型的缓冲区,先存如本身的缓冲中,满了之后,再给外面的缓冲区传
字符缓冲流:BufferedReader
和BufferedWriter
,有大小为8192的缓冲区,别忘了字符流本身也有一个缓冲
关闭流:关闭缓冲流的,就会自动先关闭非缓冲流
他俩各有一个独特的方法
输入流特有方法:
package test;
import java.io.*;
public class bufferStreamest02 {
public static void main(String[] args) throws IOException {
BufferedReader reader=new BufferedReader(new FileReader("c.txt"));
BufferedWriter writer=new BufferedWriter(new FileWriter("d.txt"));
String str=reader.readLine();
System.out.println(str);
}
}
输出流特有方法:先输出,再调用.newLine()
进行换行:既然是输出流自然还先存在缓冲中
package test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class bufferStreamest03 {
public static void main(String[] args) throws IOException {
BufferedWriter writer=new BufferedWriter(new FileWriter("d.txt"));
writer.write("小日子过得不错的日本选手 ");
//换行
writer.newLine();
writer.write("哦,我的朋友,你在这里呀!!!");
writer.close();
}
}
缓冲区长度: 字节缓冲流和字符缓冲流的底层缓冲区的长度都为8192,但是需要注意,字节的缓冲区是长度为8192的字节数组,为8k
,而字符的缓冲区为8192的字符数组,一个字符为两个字节,所以为16k
转换流:基本流的包装款
InputStreamReader(将字节流转换成字符流)和OutputStreamWriter(将字符流转换成字节流)
看下淘汰了方法:从GBK编码文件中获取中文,然后打印出来:InputStreamReader
可以指定字符集为GBK
package test;
import java.io.*;
public class readGBKExample {
public static void main(String[] args) throws IOException {
InputStreamReader inputStreamReader=new InputStreamReader(new FileInputStream("C:\\Users\\李家院刘俊辉\\Desktop\\xinxi.txt"),"GBK");
int b;
//遇到中文就一次多读几个字节
while((b=inputStreamReader.read())!=-1){
//中文转换表中的数字
System.out.print(b+" ");
//char可以将中文转换表中的数字转换成中文
System.out.println((char)b);
}
inputStreamReader.close();
}
}
JDK11之后就淘汰了上面(InputStreamReader
)这种指定字符集的方法,现在创建FileReader
就可以实例化的时候指定字符编码
public static void show2() throws IOException {
FileReader inputStreamReader=new FileReader("C:\\Users\\李家院刘俊辉\\Desktop\\xinxi.txt",Charset.forName("GBK"));
int b;
//遇到中文就一次多读几个字节
while((b=inputStreamReader.read())!=-1){
//中文转换表中的数字
System.out.print(b+" ");
//char可以将中文转换表中的数字转换成中文
System.out.println((char)b);
}
inputStreamReader.close();
}
InputStreamWriter
和上面一个道理,JDK后也有新的解决办法FileWriter
再来个将GBK文件读入,再写入UTF-8文件:
public static void show2() throws IOException {
InputStreamReader inputStreamReader=new InputStreamReader(new FileInputStream("C:\\Users\\李家院刘俊辉\\Desktop\\xinxi.txt"),"GBK");
OutputStreamWriter outputStreamWriter=new OutputStreamWriter(new FileOutputStream("C:\\Users\\李家院刘俊辉\\Desktop\\xix.txt"),"UTF-8");
int b;
while ((b=inputStreamReader.read())!=-1){
//字符输出本来就可以把中文当成参数放进去
outputStreamWriter.write((char)b);
}
outputStreamWriter.close();
inputStreamReader.close();
}
当然JDK11之后就用上面所说的那种方式
题目:
利用字节读取GBK文件,每次都只读一行,并且不能出现乱码
先创建一个文件字节输入流,然后通过转换流将这个字节流转换成字符流,并且指定将GBK文件按照GBK进行解码,再将这个字符流转换成字符缓冲流,最后就可以调用它独有的方法进行只读一行了:
public static void show3() throws IOException {
//什么格式编码的文件就要用什么方法进行解码,然后再正常操作
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(new FileInputStream("C:\\Users\\李家院刘俊辉\\Desktop\\xinxi.txt"),"GBK"));
//读一行字符数据,会根据遇到中文自动的一次读多个字节
String readLine=bufferedReader.readLine();
System.out.println(readLine);
bufferedReader.close();
}
序列化流: 基本流的包装款#
将数据包装一下存起来,别人来看时,看不懂,只有自己再用反序列流搞一下,才能还原真的数据
首先数据必须得被标记类(没有抽象方法的接口)标记一下,由此表示当前类产生的数据,才可以被调用者进行序列化和反序列化操作:
package com.itjh.pojo;
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
set和get和toString各种方法
}
现在将数据序列化存起来,再反序列化拿出来打印:
package test;
import com.itjh.pojo.Student;
import java.io.*;
public class xuliehualiuTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
show1();
}
public static void show1() throws IOException, ClassNotFoundException {
Student student=new Student("张三",19);
//将数据序列化,再放入指定的文件中
ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("b.txt"));
//读取指定的序列化文件
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("b.txt"));
//数据序列化写入
outputStream.writeObject(student);
//将序列化数据读出,并且打印
System.out.println((inputStream.readObject()));
inputStream.close();
outputStream.close();
}
}
版本号问题
当把数据序列化输出进文件之后,再改变实体类中的数据,然后再反序列化读入数据,这时就会报错
因为输出的时候产生的数据的实体类在后来改变了,(而程序在输入输出时本身会根据实体类生成一个版本号),所以需要给他一个版本号,这样表示改变前和改变后是同一个版本,就不会因为找不到想要的版本号而报错了
那么要让在实体类中创建:我这里是自动生成的,需要在setting中设置
private static final long serialVersionUID = 序列号;
注意: 在实体类中的数据都搞完后,再去生成,因为它是根据这个实体类中的所有数据来生成的版本号
实体类:
package com.itjh.pojo;
import java.io.Serializable;
public class Student implements Serializable {
//自动生成的版本号
private static final long serialVersionUID = 5527154188739101805L;
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'\'' +
'}';
}
}
这样即使再在实体类中新添加数据adress
,反序列化也是成功的,只是新添加的数据显示为默认值罢了,并序列化数据的时候还没有这个新添加的数据呢
package com.itjh.pojo;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 5527154188739101805L;
private String name;
private int age;
private String adress;
public String getAdress() {
return adress;
}
public void setAdress(String adress) {
this.adress = adress;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", adress='" + adress + '\'' +
'}';
}
}
结果:
Student{name='张三', age=21, adress='null'}
Process finished with exit code 0
瞬态关键字:看注释
//transient:瞬态关键字
//序列化的时候不会讲此数据也序列化
private transient int id;
总结:
序列化可以一次搞多个数据进去,然后在指定文件中产生一个更长的序列化数据,但是反序列化时,不知道此文件中包含个几个数据,不然直接把.readObject()
复制几行(几个数据就复制几行),就OK了,于是,可以在序列化之前,全部装入一个集合中,在将集合写入即可
文件:
�� sr java.util.ArrayListx����a� I sizexp w sr com.itjh.pojo.StudentL�esV�Dm I ageL namet Ljava/lang/String;xp t 张三sq ~ t 李四sq ~ t 王五x
测试类:用哪个就把另外个注释掉
package test;
import com.itjh.pojo.Student;
import java.io.*;
import java.util.ArrayList;
public class xuliehuaTest02 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
show1();
//反序列化
show2();
}
public static void show1() throws IOException {
Student student1=new Student("张三",2);
Student student2=new Student("李四",2);
Student student3=new Student("王五",2);
ArrayList<Student> list=new ArrayList<>();
list.add(student1);
list.add(student2);
list.add(student3);
ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("a.txt"));
outputStream.writeObject(list);
outputStream.close();
}
public static void show2() throws IOException, ClassNotFoundException {
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("a.txt"));
System.out.println(inputStream.readObject());
}
}
打印流
字节打印流:
构造方法:第一行,三个参数的底层其实都是创建一个字节输出流对象
方法
简单的代码实现:占位符有很多,各个字母代表着不同的含义,这里的%s
指代替后面的字符
package test;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
public class printStreamTest01 {
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
//创建字节输出流对象,自动刷新,指定编码格式
PrintStream stream=new PrintStream(new FileOutputStream("c.txt"),true,"UTF-8");
//体现自动刷新
stream.println("王二麻子");
//体现占位符,会自动刷新
stream.printf("%s小飞棍来喽!!%s","呦! 快看!!","芜湖");
stream.close();
}
}
生成的文件:
王二麻子
呦! 快看!!小飞棍来喽!!芜湖
字符打印流:
PrintWriter
:和字节打印流一样
需要手动刷新,如在构造函数中加参数
Commons-io
工具包:将之前的从文件拷贝数据到另一个文件的操作等等的这种操作,全部用一个工具类,调用其中的一个方法就解决了,只用这个工具类提供的方法就可以全部解决,不用手动敲这么多代码
Hutool
和上面的工具包一样,但是这个是国货之光,国人自己写的,就是牛!,不懂得看它的文档就行