*IO流
内存不能永久化存储,程序停止时数据丢失
将文件存储到硬盘的文件中可以保存数据
io流是存储和读取数据的解决方案
分类
按流传输方向
以程序/内存为参照物,读入/写出
-
输入流input
本地文件-->程序文件
-
输出流output
程序文件-->本地文件
按文件类型
-
字节流
所有类型文件🖼🎵🎥
-
字符流
纯文本文件📑
*基本流
抽象类 | 具体实现 | 作用 |
---|---|---|
InputStream | FileInputStream | 操作本地文件的字节输入流, 本地-->程序 |
OutputStream | FileOutputStream | 操作本地文件的字节输出流, 程序-->本地 |
Reader | FileReader | 操作本地文件的字符输入流, 本地-->程序 |
Writer | FileWriter | 操作本地文件的字符输出流, 程序-->本地 |
FileOutputStream
文件输出流: 程序-->本地
步骤
-
创建字节输出流对象
-
调用write方法写数据
-
关闭流/释放资源
//1. 创建抽象类OutStream实现类FileOutStream
FileOutputStream fos=new FileOutputStream("src\\IO\\a.txt");
//2. 调用write方法写数据
fos.write(104);//ascll码
fos.write(105);
//释放资源
fos.close();
-
FileOutputStream("输出路径"/File对象,[追加写?])
创建程序-->本地的"通道"
-
如果文件不存在会创建一个新文件,但是要保证父路径存在
-
如果文件存在,覆盖写
-
-
fos.write(写入数据ascll码)
通车
方法 说明 void write(int b) 一次写出一个字节数据 void write(byte[] b) 一次写出一个字节数组数据 void write(byte[], int sta, int len) 一次写出一个字节数组从sta索引开始,长度为len的数据 //1. 创建字节输出流对象 FileOutputStream fos = new FileOutputStream("src\\IO\\b.txt"); //2. 写数据 byte[] bytes = {104 ,101 ,108 ,108 ,111}; fos.write(bytes); //hello fos.write(bytes,2,2); //ll //3. 关闭流 fos.close();
-
fos.close()
关闭通道
getBytes
字符串-->字节数组
fos.write("hello\r\n".getBytes());
换行
-
windows:
/r/n
java对其做了优化,
无论写/r或/n java都会帮我们补齐
-
Linux:
/n
FileInputStream
文件输入流: 本地-->程序
步骤
-
创建文件输入流
-
读文件
-
释放资源
//1. 创建字节输入流对象FileInputStream("读入目标文件路径")
FileInputStream fis=new FileInputStream("src\\IO\\ByteOutStream\\c.txt");
//2.1 读一个字节
System.out.print(fis.read()); //h-->104
System.out.print((char)fis.read()); //e
//每次读一个字节,指针后移一位,最后一位为-1
//2.2 连续读
int b=fis.read();
while(b !=-1){
System.out.print((char)b);
b=fis.read();
}
//3 关闭流
fis.close();
-
FileInputStream("读入目标文件路径"/File对象)
-
如果文件不存在,报错
-
read
方法 说明 public int read() 每次读一个字节, 返回数据的ascll码(读不到数据返回-1), 指针后移一位 public int read(byte[] buffer) 每次读一个字节数组, 返回本次读的字节数量(读不到数据返回-1), 指针到数组下一位 public int read(byte[] buffer)
buffer是一个缓存区,当最后读入的数据没有装满时,会留下上一次的残存数据
解决方法:
fos.write(bytes,0,len)
拷贝
FileInputStream fis=new FileInputStream("src\\IO\\ByteOutStream\\c.txt");
FileOutputStream fos=new FileOutputStream("src\\IO\\ByteInStream\\a.txt");
int b;
while ((b=fis.read())!=-1){
fos.write((char)b);
}
fis.close();
fos.close();
FileInputStream一次读一个字节(暂存在b中),速度非常慢
可以考虑用 public int read(byte[] buffer) 每次读一个字节数组
buffer太小会很慢,buffer太大占内存.
buffer大小一般为1024的倍数( 推荐 5M )
FileInputStream fis=new FileInputStream("src\\IO\\ByteOutStream\\c.txt");
FileOutputStream fos=new FileOutputStream("src\\IO\\ByteInStream\\a.txt");
byte [] bytes=new byte[3];
int len;
while ((len=fis.read(bytes))!=-1){ //-1表示文件结束
fos.write(bytes,0,len); //解决缓存区残存数据问题
}
fis.close();
fos.close();
中文乱码问题
-
ascll码用来存欧美人的字符
-
GB 2312-80
是简体汉字编码编码表 -
GBK
是GB 2312-80扩展,包括简繁中文,英文,日文,韩文操作系统默认GBK编码
英文字符一个字节(2^9-1 bit/个)表示就够了,但是汉字比较多(2^9-1)个不够放,
所以用两个字节表示一个汉字(可表示2^17-1个)
-
Unicode
万国码,包含世界上大多数国家字符UTF-8:( unicode transfer format)用1~4个字符存储( 根据不同用于改变 )
-
中文用3个字节表示: 1110xxxx 10xxxxxx 10xxxxxx
-
英文用1个字节表示: 0xxxxxxx
-
乱码原因
-
读取数据时未读完整个汉字编码
一个汉字占3个字节,当只读取其中部分字节时对应10进制为负数,找不到对应字符
-
编码和解码方式不统一
比如编码用UTF-8时3B表示一个汉字,
若解码时用GBK,前两个字节被解成一个汉字剩下1B 乱码?
解决方法
-
不要用字节流读取文本文件
因为字节流每次读1B,会把中文拆开
-
编码译码用同一个编码表
同编码表代码实现
-
编码方法
String类中方法 说明 public byte[] getBytes() 使用默认方法进行编码,idea默认UTF-8 public byte[] getBytes(String charsetName) 使用指定方法进行编码 -
解码方法
String类中方法 说明 String(bytes[] bytes) 使用默认方法进行编码 String(bytes[] bytes,String charsetName) 使用指定方式进行编码
getBytes: 数据-->字节数组
//编码:getBytes String str = "diva在学习"; byte[] bytes = str.getBytes("UTF-8"); //String-->字节数组 System.out.println(Arrays.toString(bytes)); //解码:new String(byte[] bytes,int offset,int length) String str2 = new String(bytes,"UTF-8");//字节数组-->String System.out.println(str2);
结果
[100, 105, 118, 97, -27, -100, -88, -27, -83, -90, -28, -71, -96]
diva在学习
一个英文一个字节,一个汉字3个字节
对于字节流会未读完整个汉字编码的情况,我们可以使用字符流
字符流一次读一个字符,但是只能处理纯文本文件
FileReader
文件输入流: 本地-->程序
步骤
-
创建字符流输入对象
-
读取数据
-
释放资源
//1. 创建对象
FileReader fr = new FileReader("src\\IO\\CharInStream\\a.txt");
//2.1 调用read()方法读取数据
// int ch = 0;
// while ((ch = fr.read()) != -1) {
// System.out.print((char) ch);
// }
//2.2 调用read(char[])方法读取数据
char[] chars = new char[7];
int len = 0;
while ((len = fr.read(chars)) != -1) {
System.out.print(new String(chars,0,len));//解决缓存区覆盖问题
}
//3. 释放资源
fr.close();
-
FileReader("输出路径"/File对象)
从本地(外存)读取文件时,程序先关联文件,并自动在内存建立缓冲区(8192B大小数组)
-
read
成员方法 说明 public int read() 读入数据,返回解码后10进制值,文件末尾返回-1 public int read(char[] buffer) 读入多个数据,返回一次读的数据个数,文件末尾返回-1。数据解码10进制再强转字符后放到buffer里
底层原理
int ch = 0;
System.out.print((char) ch);
变量ch从缓冲区读,以便减少内存对外存的访问次数
-
缓冲区有数据
ch从缓冲区读
-
缓冲区没有数据
外存文件-->缓冲区(尽可能装满)-->ch
字节流:外存文件-->程序
中间无缓冲区
FileWriter
文件输出流: 程序-->本地
步骤
-
创建字符流输出对象
-
写数据
-
释放资源
//1. 创建字符输出流对象FileWriter
FileWriter fw = new FileWriter("src\\IO\\CharOutStream\\b.txt",true);//追加写
//2. 调用write()方法写数据
fw.write(97); //a
fw.write("hello"); //hello
fw.write(new char[]{'a','b','c'});//abc只是在缓冲区
//3. 释放资源
fw.close();//abc被写到本地文件
-
FileWriter("输出路径"/File对象,[追加写?])
-
write
成员方法 说明 void write(int c) 写出整数c在字符集上对应字符 void write(String str) 写出字符串str void write(String str, int sta, int len) 写出字符串str从sta开始长度为len字符串 void write(char[] cbuf) 写出一字符数组 void write(char[] cbuf, int sta, int len) 写出字符数组cbuf从sta开始长度为len字符串
底层原理
内存程序-->内存缓冲区(8192字节数组)-->外存文件
缓冲区-->外存条件
-
缓冲区装满了(8192字节)
-
手动刷新(
flush
) -
关流(
close
)
flush和close区别是:
flush把缓冲区数据刷新到本地文件,通道还在,仍可以继续写出数据
close之后,通道断开,无法继续写出数据
方法 | 说明 |
---|---|
public void flush() | 将缓冲区中数据刷新到本地文件 |
public void close() | 释放资源 |
案例
文件夹拷贝
public static void main(String[] args) throws IOException {
File from=new File("src\\IO\\example\\aaa");
File to=new File("src\\IO\\example\\bbb");
copyDir(from,to);
}
//copyDir(源文件,目标文件)
public static void copyDir(File from, File to) throws IOException {
//如果是文件夹,递归创建文件夹
if(from.isDirectory()) {
//创建建目标文件夹
to.mkdir();
//获取源文件夹下的所有文件或文件夹并遍历
File[] files = from.listFiles();
for (File file : files) {
copyDir(file, new File(to, file.getName()));
}
}
//如果是文件,直接复制文件
else{
//创建输入输出流
FileInputStream fis=new FileInputStream(from);
FileOutputStream fos=new FileOutputStream(to);
byte [] bytes=new byte[3];
int len; //解决缓冲区覆盖
//调用read()方法读取数据,写入文件中
while((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
}
//释放资源
fos.close();
fis.close();
}
}
结果
注意
-
文件夹拷贝前要用
to.mkdir()
创建对应目标文件夹 -
若文件全是文本文件,可以用
FileReader
和FileWriter
-
若有音频/视频等格式文件,可以用二进制传输
FileInputStream
和FileOutputStream
文件加密
加密原理
^
:异或符.例如2^10
0010^1010对应位上同假(0)异真(1). 结果为1000.在转10进制为8
接着8^10
1000^1010.结果又变回2(0010)
public static void main(String[] args) throws IOException {
File f=new File("src\\IO\\example\\aaa\\a2.txt");
File ef=new File("src\\IO\\example\\aaa\\a3.txt");
File df=new File("src\\IO\\example\\aaa\\a4.txt");
//加密
EDcryption(f,ef);
//解密
EDcryption(ef,df);
}
//文件加解密(源文件,加解密文件)
private static void EDcryption(File f,File ef) throws IOException {
//创建二进制对象输入流,输出流
FileInputStream fis=new FileInputStream(f);
FileOutputStream fos=new FileOutputStream(ef);
//循环读取文件内容,并加密写入文件
int b;
while((b=fis.read())!=-1){
fos.write(b^2);
}
//释放资源
fis.close();
fos.close();
}
你好 我是a2 你好 我是a2 <===> 濢秿"䊓䚭c0濢秿"䊓䚭c0濢秿"䊓䚭c0 你好 我是a2
修改文件数据
把文件内容(6-8-2-9-4)按顺序排序
public static void main(String[] args) throws IOException {
FileInputStream fis=new FileInputStream("src\\IO\\example\\aaa\\a1\\a11");
FileOutputStream fos=new FileOutputStream("src\\IO\\example\\aaa\\a1\\a12");
int b;
StringBuffer sb = new StringBuffer(); //stringbuffer是一个可变的字符序列,用于存储字符串。
while ((b=fis.read())!=-1){
sb.append((char)b);
}
ArrayList<Integer> list=new ArrayList<>(); //arraylist是一个可变的数组,用于存储对象。
String[] s=sb.toString().split("-");
for(String t:s){
list.add(Integer.parseInt(t)); //Integer.parseInt:字符串-->int
}
//Collections.sort对list排序
Collections.sort(list);
for(Integer i:list){
fos.write(i.toString().getBytes()); //getBytes():字符串-->字节数组
fos.write("-".getBytes());
}
fis.close();
fos.close();
}
*缓冲流
把基本流封装成高级流,提高数据性能
在内存中增加缓冲区(字节缓冲流默认8192B,字符缓冲流默认8192char),提高传输效率.
(字符流本来就有缓冲区,提高效果不明显)
方法 | |
---|---|
public BufferedInputStream(InputStream is) | 对字节输入流封装 |
public BufferedOutputStream(OutputStream os) | 对字节输出流封装 |
public BufferedReader(Reader r) | 对字符输入流封装 |
public BufferedWriter(Writer r) | 对字符输出流封装 |
BufferedInputStream
字节缓冲流
BufferedInputStream
(BufferedOutputStream
类似)
关闭字节输入缓冲流close方法内部自己关闭基本流
类似于FileInputStream也有public int read()
和public int read(byte[] buffer)
-
案例:用缓冲流实现文件拷贝
public static void main(String[] args) throws IOException {
//创建缓冲流
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("src\\IO\\BufferStream\\a.txt"));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("src\\IO\\BufferStream\\b.txt"));
//读写数据
int b;
while ((b=bis.read())!=-1){
bos.write(b);
}
//释放资源
bos.close();
bis.close();
}
也可以用带参read,一次读写多个字节
//一次读写多个数据
byte[] bytes=new byte[1024];
int len;
while ((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
-
原理
BufferedReader
BufferedReader
BufferedWriter
,由于字符缓冲流本来就有缓冲区,所以加速效果不明显.
但是他们拥有不同于FileReader和FileWriter的特有方法
-
public String readLine()
字符缓冲输入流特有方法
一次读入一行数据,遇到回车换行结束.但不会把回车换行读入内存
如果没有数据可读就返回null
//创建缓冲区 BufferedReader br = new BufferedReader(new FileReader("src/IO/BufferStream/a.txt")); String line = new String(); while ((line=br.readLine())!=null){ System.out.println(line); //默认没有回车换行 } //释放资源 br.close();
-
public void newLine()
字符缓冲输出流特有方法
跨平台换行
无论是Windows(\r\n), Linux(\n)还是mac(\r)都实现换行
//创建对象 BufferedWriter bw=new BufferedWriter(new FileWriter("src/IO/BufferStream/c.txt")); //写出数据 bw.write("hello"); bw.newLine(); //无论是什么系统都可以换行 bw.write("world"); //释放资源 bw.close();
*转换流
字符流和字节流之间桥梁
InputStreamReader
继承抽象方法Reader.
对字节流封装,使数据不会乱码
-
参数
-
InputStreamReader(FileInputStream(...))
-
InputStreamReader(FileInputStream(...),"编码方式")淘汰
FileReader在jdk11后增加
public FileReader(File file, Charset charset) throws IOException { super(new FileInputStream(file), charset); }
参数"Charset"指定编码方式.Charset.forname("utf-8")
取代InputStreamReader(FileInputStream(...),"编码方式")
-
-
读入utf-8编码的数据,写出gbk编码的数据
//创建对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("src/IO/ConvertStream/a.txt"),"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src/IO/ConvertStream/b.txt"),"gbk");
//写出出数据
int len;
while ((len=isr.read())!=-1){
osw.write(len);
}
//释放资源
isr.close();
osw.close();
替代方法
FileReader isr = new FileReader("src/IO/ConvertStream/a.txt", Charset.forName("utf-8"));
FileWriter osw = new FileWriter("src/IO/ConvertStream/b.txt", Charset.forName("gbk"));
//写出出数据
int len;
while ((len=isr.read())!=-1){
osw.write(len);
}
//释放资源
isr.close();
osw.close();
-
用字节流读文件,每次读一行,不出现乱码
//创建对象
FileInputStream fis = new FileInputStream("src/IO/ConvertStream/a.txt");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
FileOutputStream fos = new FileOutputStream("src/IO/ConvertStream/c.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
String line;
while ((line=br.readLine())!=null){
bw.write(line);
bw.newLine();
}
//释放资源
bw.close();
br.close();
*序列化流
对基本流封装,把java对象写入本地文件
方法 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 对基本流封装 |
public ObjectInputStream(InputStream in) | 对基本流封装 |
ObjectOutputStream
序列化流/对象操作输出流.
将实现Serializable接口的对象写入到本地文件(直接看文件看不懂, 反序列化后才能看懂 )
成员方法
-
public final void writeObject(Object obj)
把对象序列化到文件中
//创建Student对象 Student s=new Student("diva",20); //创建ObjectOutputStream对象 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("src/IO/ObjectStream/b.txt")); //写入对象 oos.writeObject(s); //释放资源 oos.close();
对象要实现Serializable接口.
Serializable接口中没有方法,是标记型接口
表示该类可被序列化
写入文件结果为
ObjectInputStream
反序列化流/对象操作输入流
把序列化的文件从本地读入程序
成员方法
-
public Object readObject()
把已序列化本地的文件反序列化,读取到程序
//创建ObjectInputStream对象 ObjectInputStream ois=new ObjectInputStream(new FileInputStream("src/IO/ObjectStream/b.txt")); //读入 System.out.println(ois.readObject()); //释放资源 ois.close();
版本号问题
当业务需求要更改JavaBean时,之前被序列化的文件就不能被反序列化成JavaBean对象了
-
原因
底层会给实现Serializable接口的JavaBean一个序列号/版本号
使用序列化流写出对象时,也会将JavaBean版本号写入
当JavaBean改变时,其版本号也改变
但是文件中版本号不变. JavaBean版本号!=文件版本号
因此文件反序列化会报错
-
解决方法
-
在JavaBean中固定版本号
serialVersionUID
public class Student implements Serializable { private static final long serialVersionUID=1L; String name; int age; ... }
-
在setting中设置
在JavaBean类名上alt+enter
-
-
自动生成
@Serial private static final long serialVersionUID = -4946731678200075446L;
保密属性问题
在序列化对象时可能会有些对象是保密的,不希望被序列化到本地文件
此时可以在保密属性前加上transient
修饰
-
transient
瞬态关键字
作用就是不会把当前属性序列化到本地文件
*打印流
PrintStream
PrintWriter
只能操作文件目的地,不能操作数据源(只能写,不能读)
特有方法可以实现数据写出和自动刷新换行
PrintStream
字节打印流
构造方法 | 说明 |
---|---|
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 print(...) | 支持任意数据原样写出,不换行 |
public void println(...) | 支持任意数据原样写出,自动刷新换行 |
public void printf(String 含有占位符字符串,Object 对应位置%占位符值) | 带有占位符的打印语句,不换行 |
//创建对象
PrintStream ps=new PrintStream(new FileOutputStream("src/IO/PrintStream/a.txt"),true,"utf-8");
//写出
ps.println(97); //写出97而不是a
ps.print("哈哈哈,");
ps.printf("%d是%s的幸运数字",10,"diva");
//释放资源
ps.close();
PrintWriter
字符打印流
底层有缓冲区,想要刷新需要开启
构造方法 | 说明 |
---|---|
public PrintWriter(Writer/File/String) | 关联字符输出流/文件/文件路径 |
public PrintWriter(String fileName,Charset charset) | 指定字符编码方式 |
public PrintWriter(Write w,boolean autoFlush) | 自动刷新否 |
public PrintWriter(Writer w,boolean autoFlush,Charset charset) | 指定字符编码方式+自动刷新否 |
成员方法同PrintStream
*标准输出流
System.out
虚拟机启动时自动创建,默认指向控制台
不能关闭,在系统中是唯一的