IO流
IO流概述
IO流就是读取跟写入文件
分为输入(Input)和输出(Output)
输入:从硬盘传入到内存称为输入,输入中发生的数据的流动叫做输入流(InputStream),也叫做读(Read)
输出:从内存传出到硬盘称为输出,输出中发生的数据的流动叫做输出流(OnputStream),也叫做写(Write)
IO:
I就是Input
O就是Output
通过IO流可以完成硬盘文件的读和写
IO流的分类:
有多种分类方式:
一种方式是按照流的方向进行分类:
以内存为参照物:
往内存中去,叫做输入(Input)。或叫做读(Read)。
从内存中出来,叫做输出(Output)。或叫做写(Write)。
另一种方式是按照读取数据方式的不同进行分类:
按照字节方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。
这种流被称为万能流,什么都可以读取,包括(文本,图片,音视频等)
假设文件filel.txt,采用字节流的话是:
a中国
第一次读:’a‘字符(windows系统中占一个字节),正好读完
第二次读:’中‘字符(windows系统中占两个字节),只能读取一半
第三次读:’中‘字符(windows系统中占两个字节),加上第二次读的正好读完
按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的。这种流只能读取纯文本文档,不能读取视频,声音,图片等,连word都无法读取
假设文件filel.txt,采用字符流的话是:
a中国
第一次读:’a‘字符(windows系统中占一个字节)
第二次读:’中‘字符(windows系统中占两个字节)
综上所述:流的分类
输入流、输出流
字节流,字符流
Java中的IO流都已经写好了,java中所有的流都是在:java.io.*;中
IO流下的四大家族:
四大家族首领都是抽象类(abstract class)
java.io.InputStream;
字节输入流
java.io.OutputStream;
字节输出流
java.io.Reader;
字符输入流
java.io.Writer;
字符输出流
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close()方法,流毕竟是一个管道,是内存与硬盘之间的通道,用完即关,不然会浪费很多资源
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush()方法
用完输出流后要调用flush()方法清空管道,flush方法会将未输出完的强行输出完(清空管道)
如果没有flush()可能会导致数据丢失
以Stream结尾都是字节流
以Reader/Writer结尾都是字符流
常用流:
文件专属:
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWirter
转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
对象专属流:
java.io.ObjectInputStream
java.io.ObjectOutputStream
标准输出流:
java.io.PrintStream
java.io.PrintWriter
流的使用和概念
文件专属:
java.io.FileInputStream
-
万能文件输入流,任何文件都可以采用这个流来读
-
字节的方式,完成输入的操作(硬盘–>内存)
FileInputStream fis = null; try{ //创建文件字节输入流 fis = new FileInputStream("路径"); int readData = fis.read();//开始读取文件,每次调用读取一个字节,这个方法会返回读到的”字节“本身 //读取的位置如果什么都没有返回-1,表示读到末尾了 System.out.println(readData); }catch(FileNotFoundException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); }finally{ if(fis != null){ try{ fis.close();//关闭流,流不是空没必要关 }catch(IOexception e){ e.printStackTrace(); } } }
//程序改进,上方程序虽然也能读取,但是我们不可能无数次的去调用,因此我们需要设定一个循环让他自动 FileInputStream fis = null; try{ //创建文件字节输入流 fis = new FileInputStream("路径"); while(true){ int readData = fis.read();//开始读取文件,每次调用读取一个字节,这个方法会返回读到的”字节“本身,也就是ASCII //读取的位置如果什么都没有返回-1,表示读到末尾了 if(readDate == -1){ break; } System.out.println(readData); } }catch(FileNotFoundException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); }finally{ if(fis != null){ try{ fis.close();//关闭流,流不是空没必要关 }catch(IOexception e){ e.printStackTrace(); } } }
对while再次改进
int readData; while((readData = file.read()) != -1){ System.out.println(readData); }
这种一个一个字节读取方式太慢,和文件交互太频繁,浪费太多时间和资源
byte[] b = new byte[4]; //int read(byte[] b),一次最多读取数组.length的数量 int readCount = fis.read(b);//读取的不是字符本身,而是数量 //若你的文件中有abcdef六个字符 //第一次调用会读取4个字符也就是abcd放进数组中,所以返回4 System.out.println(readCount); System.out.println(new String(b),0,readCount); readCount = fis.read(b); //第二次调用会读取后面的,已读的不会再读,读取出来的会覆盖数组前两个,任何返回读取数量 System.out.println(readCount); System.out.println(new String(b),0,readCount); readCount = fis.read(b); //第三次读取会发现里面以及没有可读取的内容了,那么会返回-1 System.out.println(readCount); System.out.println(new String(b),0,readCount);
这时我们可以调用String将读取的字符数组转换输出
System.out.println(new String(b),0,readCount);
返回剩下没获取到的字节数量:
fis.available();
结合available使用
byte[] b = new byte[fis.available()];
这样可以省下循环,一次直接读取完,但不适合大文件,因为byte数据不能太大
skip():跳过几个字节不读取
fis.skip(3);
跳过3个字节不读取文件的路径,idea的默认路径是Project的根
若文件位于根下则路径直接写文件名.文件类型
若在根中的某个文件夹中则文件夹名/文件名.文件类型
在根外面则写全路径
java.io.FileOutputStream
byte[] b = new byte[]{2,5,6,7,9,3,8,1};
FileOutputStream fos = null;
try{
fos = new FileOutputStream("路径",true);//文件不存在则自动新建,true表示追加,不会清空原内容
fos.write(b);//将byte数组中的数据写出文件中,会将文件本身内容清空再写入
fos.write(b,2,4);//跳过某一部分数据写出
fos.flush();//用完一定要刷新
}catch(FileNotFoundException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}finally{
if(fos != null){
try{
fos.close();//关闭流,流不是空没必要关
}catch(IOexception e){
e.printStackTrace();
}
}
文件的复制:先读取文件放入数组中,在通过数组转移到新文件中
FileInputStream fis = null;
FileOutputStream fos = null;
int count = 0;
try {
fis = new FileInputStream("file");
fos = new FileOutputStream("D:/file",true);
byte[] b = new byte[4];
while((count = fis.read(b)) != -1){}
fos.write(b,0,count);
}
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
java.io.FileReader
文件字符输入流,只能读取普通文本
读取文本内容时,比较方便,快捷
根FileInputStream一样的作法
FileReader fr = null;
int count = 0;
char[] c = new char[5];
try {
fr = new FileReader("file");
//while ((count = fr.read(c)) != -1){
// System.out.println(new String(c,0,count));
//}
fr.read(c);//按字符读取,任何foreach输出
for(char c2 : c){
System.out.println(c2);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
skip():
跳过指定数量字符
java.io.FileWirter
FileWriter fw = null;
int count = 0;
char[] c = new char[]{'人','国','中','是','我'};
try {
fw = new FileWriter("file",true);//表示在末尾添加
//fw.write(c);//会清空原内容
fw.write(c,1,4);
//fw.write("ssaasfsf");可以直接写出字符串
fw.write("\n");//表示换行
fw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileOutputStream和Write只能复制普通文件
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("file");
fw = new FileWriter("D:/file",true);
char[] b = new char[4];
int count = 0;
while((count = fr.read(b)) != -1) {
fw.write(b,0,count);
}
fw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
缓冲流专属:
java.io.BufferedReader
带有缓冲区的字符输入流
使用这个流的时候不需要自定义数组,自带缓冲
//当一个流的构造方法中需要一个流时,这个被传进来的流叫做节点流
//外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流
//像下面的例子:FireReader就是节点流,BufferedReader就是包装流(处理流)
FileReader fr = null;
BufferedReader br = null;
String str = null;
String s = null;
try {
fr = new FileReader("file");
br = new BufferedReader(fr);
//对于包装流来说,只需要关闭包装流,节点流会自动关闭
str = br.readLine();//读取一行
System.out.println(str);
while((s = br.readLine()) != null){//循环获取每一行数据,要是获取到的是null表示获取结束
System.out.println(s);//不会自动换行,是println换的行
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
br.close();
}
BufferedReader只能传如字符流,无法传入字节流,需要将字节流转换成字符流
java.io.BufferedWriter
带有缓冲区的字符输出流
FileWriter fw = null;
BufferedWriter bw = null;
String str = null;
String s = null;
try {
fw = new FileWriter("file");
bw = new BufferedWriter(fr);
//对于包装流来说,只需要关闭包装流,节点流会自动关闭
str = br.writer("dfdffsfas");//写入数据,不会清空,会追加在后方
bw.writer();
bw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
bw.close();
}
new BufferedWriter(new OutputStreamWriter(new FileOutputStream));
将字节流转换成字符流传入BufferedWriter中,简化
java.io.BufferedInputStream(自己了解,名字已经说明一切)
java.io.BufferedOutputStream(自己了解,名字已经说明一切)
转换流:(将字节流转换成字符流)
java.io.InputStreamReader
创建一个字节流
FileInputStream fis = new FileInputStream("file");
将字符类转换成字符流
InputStreamReader isr = new InputStreamReader(fis);
将转换好的字符流传入缓存流读取
BufferedReader br = new BufferedReader(isr);
java.io.OutputStreamWriter
FileOutputStream fos = new FileOutStream("file");
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
数据流专属:
java.io.DataInputStream
这个流可以将数据连同数据的类型一并读取文件,是用来读取DataOutputStream写入的内容的,并且必须按照写进去的顺序取出,不然会报错。
FileInputStream fis = new FileInputStream("file");
DataInputStream dis = new DataInputStream(fis);
byte b3 = dis.readByte();
short s1 = dis.readShort();
int i1 = dis.readInt();
long l1 = dis.readLong();
float f1 = dis.readFloat();
double d1 = dis.readDouble();
char c1 = dis.readChar();
boolean b2 = dis.readBoolean();
dis.close();
System.out.println(b3);
System.out.println(s1);
System.out.println(i1);
System.out.println(l1);
System.out.println(f1);
System.out.println(d1);
System.out.println(c1);
System.out.println(b2);
java.io.DataOutputStream
这个流可以将数据连同数据的类型一并写入文件
这个文件不是普通文本文档(使用记事本无法打开)
并且只有DataInputStream数据流才能读取
FileOutputStream fos = new FileOutputStream("file");
DataOutputStream dos = new DataOutputStream(fos);
byte b = 'a';
short s = 100;
int i = 500;
long l = 1000;
float f = 13.5f;
double d = 45.4;
char c = '你';
boolean b1 = true;
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(b1);
dos.flush();
if(dos!=null) {
dos.close();
}
标准流:
java.io.PrintStream
标准字节输出流,默认输出到控制台
联合写:
System.out.println("HelloWorld!");
分开写:
PrintStream ps = System.out;
ps.println("HelloWorld!");
标准输出流不需要手动close关闭
PrintStream printStream = new PrintStream(new FileOutputStream("log"));//标准输出流不再指向控制台,而是指向log文件
System.Out(printStream);
java.io.PrintWriter
日志文件
try {
PrintStream ps = new PrintStream(new FileOutputStream("log",true));
System.setOut(ps);
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:ss:mm sss");
String str = sdf.format(date);
System.out.println(str+": "+amg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
java.io.File类
File不是一个流,所以不能完成文件的读写。
File是一个文件或路径名的一个抽象表示形式
如:D:\LOL或者D:\LOL\log 都是File对象
一个File对象有可能对应的是目录,也可能是文件
File只是一个路径名和的抽象表现形式
FIle类的常用方法:
File f1 = new File("路径");
f1.exists();//判断路径中的文件是否存在
f1.createNewFile();//以文件形式创建
//判断文件是否存在,不存在则创建
if(f1.exists())
//只能创建文件
f1.createNewFile();
f1.mkdir();//创建目录
if(f1.exists())
f1.mkdirs();//创建多重目录,创建文件夹
File f1 = new File("路径");
//获取文件父路径,就是文件上一层目录的位置
System.out.println(f1.getParent());
File parenetFile = f1.getParentFile();
System.out.println("获取绝对路径"+parentFile.getAbsolutePath());
File file = new File("log");
System.out.println("获取绝对路径"+file.getAbsolutePath());
delete()
删除文件或目录
File f1 = new File("路径");
//获取文件名
System.out.println(f1.getName());
f1.isDirectory();//判断是否是一个目录
f1.isFile();//判断是否是一个文件
f1.isHidden();//是否是隐藏文件
f1.lastModified();//返回最后一次修改的时间,是一个毫秒,从1970年1月1日开始计算的
f1.length();//获取文件长度,字节
File[] file = f1.listFiles();//获取当前目录下的所有子目录
for(File file2 : file){
System.out.println(file2.getAbsolutePath());
}
序列号和反序列化
java.io.ObjectOutputStream
Java序列化就是指把Java对象转换为字节序列的过程
就是将java对象从内存存储到文件夹中,将java对象的状态保存下来的过程
Serializable 序列化 : 将对象转化为便于传输的格式, 常见的序列化格式:二进制格式,字节数组,json字符串,xml字符串。
参与序列号的对象必须实现Serializable接口
通过源代码发现,Serailizable接口只是一个标志接口:
接口中什么代码都没有,主要起的是一个标识,标志作用,java虚拟机看见这个类,可能会对这个类进行特殊待遇
Serializable这个标志是给java虚拟机参考的,java虚拟机看到这个接口后,会为该对象类自动生成一个序列号版本号
序列号版本号,对以前的类的源代码进行改变后JVM会重新分配的序列号,用来区分类
java语言中采用什么机制区分类:
-
首先通过类名进行对比,如果类名不一样,看到不是同一个类
-
如果类名一样,则靠版本号区分
只要实现了序列化接口Serializable即便你类名相同,JVM同样可以分辨出这是不同的两个类,因为实现了序列化接口后会产生序列化版本号来进行分辨
只要修改了,必然会重新分配,此时会生成一个新的序列化版本号,这时候Java虚拟机会认为这时一个全新的类
因此,继承了Serializable的类一旦代码确定后,将不可修改
//先创建一个Student类继承(implements)Serializable,给上几个属性
//接着定义一个测试类
public static void main(String[] args){
//创建一个java对象
Student s = new Student(111,"zhangsan");
//序列化,将java对象Student序列化存储进student文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student"));
oos.writeObject(s);//序列化对象
oos.flush();
oos.close();
}
java.io.ObjectInputStream
Java反序列化就是指把字节序列恢复为Java对象的过程。
deseriallization 反序列化:将序列化的数据恢复为对象的过程。
//将需要反序列化的文件传入
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student"));
Object obj = ois.readObject();
System.out.println(obj);
ois.close();
多对象序列号,可以将对象添加到ArrayList集合中,在反序列化中,readObject()获取到的会是一个ArrayList只需要强转后遍历即可
ArrayList<StudyClass> asc = new ArrayList<>();
asc.add(new StudyClass(1,"语文"));
asc.add(new StudyClass(2,"数学"));
asc.add(new StudyClass(3,"English"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("study"));
oos.writeObject(asc);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("study"));
ArrayList<StudyClass> asc2 = (ArrayList<StudyClass>) ois.readObject();
for (StudyClass sc : asc2) {
System.out.println(sc.toString());
}
public class StudyClass implements Serializable {
private int no;
private String clasies;
public void setNo(int no) {
this.no = no;
}
public int getNo(){
return no;
}
public String getClasies(){
return clasies;
}
public void setClasies(String clasies){
this.clasies = clasies;
}
public StudyClass(){}
public StudyClass(int no,String clasies){
this.clasies = clasies;
this.no = no;
}
@Override
public String toString() {
return "StudyClass{" +
"no=" + no +
", clasies='" + clasies + '\'' +
'}';
}
}
transient关键字
表示游离的,不参与序列号
private transient int no;
序列化版本号
序列号版本号,对以前的类的源代码进行改变后JVM会重新分配的序列号,用来区分类
java语言中采用什么机制区分类:
-
首先通过类名进行对比,如果类名不一样,看到不是同一个类
-
如果类名一样,则靠版本号区分
只要实现了序列化接口Serializable即便你类名相同,JVM同样可以分辨出这是不同的两个类,因为实现了序列化接口后会产生序列化版本号来进行分辨
只要修改了,必然会重新分配,此时会生成一个新的序列化版本号,这时候Java虚拟机会认为这时一个全新的类
因此,继承了Serializable的类一旦代码确定后,将不可修改
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号
这样,以后或者各类即使代码修改了,但是版本号不变,java虚拟机会认为这时同一个类
建议在该类中写上:
private static final long serialVersionUID = 6546189194999991616161L;
固定好后,重新编译也不会出现问题
IO和Properties联合使用
将Key=Value数据的文件通过IO加载到类中(反射使用)
//写入数据在properties文件
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("properties"));
osw.write("key=value\n1=zhangsan");
osw.flush();
osw.close();
//读取properties存放在Properties集合当中
FileReader reader = new FileReader("properties");
Properties p = new Properties();
p.load(reader);//从输入流中获取数据key跟value对应Properties集合中的key-Value
System.out.println(p);//输出
System.out.println(p.getProperty("1"));//通过key获取value
我们把properties这种文件叫做配置文件
java规范中建议属性配置文件以 .properties结尾
Properties这个类是专门存放数学配置文件内容的一个类
属性配置文件
属性配置文件中#是注释
key重复的话,后面会覆盖签名的
建议key和value之间使用=方式