IO概述
数据的传输,可以看作一种数据的流动,按照流动的放心,以内存为基准,分为输入input和输出output,即流向内存是输入流,流出内存是输出流
java中I/O操作,主要是指使用java.io包下的内容,进行输入输出操作,输入也被叫做读取数据,输出也被叫做写出数据
IO分类
根据数据的流向分为:输入流和输出流
- 输入流:把数据从其他设备上读取到内存的流
- 输出流:把数据从内存中写出到其他设备上的流
根据数据的类型分为:字节流和字符流
- 字节流:以字节为单位,读写数据的流
- 字符流:以字符为单位,读写数据的流
顶级父类
流 | 输入流 | 输出流 |
---|---|---|
字节流 | 字节输入流InputStream | 字节输出流OutputStream |
字符流 | 字符输入流Reader | 字符输出流Writer |
分类概述
- OutputStream:输出流的抽象类
- FileOutputStream:字节输出流
- ObjectOutputStream:序列化流
-
FilterOutputStream :自带过滤器的输出流
———- BufferedOutputStream:字节缓冲输出流
———- PrintStream:打印流 - InputStream:输入流的抽象类
- FileInputStream:字节输入流
- ObjectInputStream:反序列化流
- FilterInputStream:自带过滤器的输入流 —–> BufferedInputStream:字节缓冲输入流
- Writer:字符输出流的抽象类
- BufferedWriter:字符缓冲输出流
- OutputStreamWriter:转换编码的字符输出流 —–> FileWriter :字符输出流
- Reader:字符输入流的抽象类
- BufferedReader:字符缓冲输入流
- InputStreamReader:转换编码的字符输入流 —–> FileReader:字符输入流
字节流
一切文本数据在存储时,都是以二进制数字的形式保存,都是一个一个的字节,在传输时也一样如此,所以,字节流可以传输任意文件数据,在操作流的时候,要注意无论使用任何流对象,底层传输的始终为二进制数据
字节输出流【OutputStream】
java.io.OutputStream 抽象类是所有字节输出流的超类,将指定的字节信息写出到目的地,它定义了字节输出流的基本共性功能方法
public void close()
:关闭此输出流并释放与此流相关联的所有系统资源public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出public void write(byte[] b)
:将b.length字节从指定的字节数组写入此输出流public void write(byte[] b,int off,int len)
:从指定的字节数组写入len字节,从偏移量off开始输出到此输出流public abstract void write(int b)
:将指定的字节输出流
close方法,当完成流的操作时,必须调用此方法,释放系统资源
FileOutputStream类
java.io.FileOutputStream类是文件输出流,用于将数据写出到文件
构造方法
public FileOutputStream(File file)
:创建文件输出流以写入由指定的File对象表示的文件public FileOutputStream(String name)
:创建文件输出流以指定的名称写入文件
当创建一个流对象的时候,必须传入一个文件路径,若在该路径下不存在这个文件,便会创建该文件,如果有这个文件会清空这个文件的数据
代码:
public class FileOutputStreamConstructor throws IOException{
public static void main(String[] args){
//使用File对象创建流对象
File file = new File("a.txt");
FileOutputStream fos = new FileOutputStream(file);
//使用文件名称创建流对象
FileOutputStream fos1 = new FileOutputStream("b.txt");
}
}
写出字节数据
1.写出字节:write(int b)方法,每次可以写出一个字节数据:public class FOSWrite{
public static void main(String[] args) throws IOException{
//使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
//写出数据
fos.write(97);
fos.write(97);
fos.write(97);
//关闭资源
fos.close();
//输出abc
}
}
虽然参数为int类型四个字节,但是只会保留一个字节的信息写出
流操作完毕后必须调用close方法释放系统资源
2.写出字节数组:write(byte[] b) 每次可以写出数组中的数据,代码:
public class FOSwrite{
public static void main(Srting[] args) throws IOException{
//使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
//字符串转换为字节数组
byte[] b = "张三丰".getBytes();
//写出字节数组数据
fos.write(b);
//关闭资源
fos.close();
//输出张三丰
}
}
3.写出指定长度字节数组:write(byte[] b,int off,int len),每次写出从off索引开始len个字节,代码:
public class FOSwrite{
public static void main(Srting[] args) throws IOException{
//使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
//字符串转换为字节数组
byte[] b = "abcde".getBytes();
//写出从索引2开始,2个字节长度
fos.write(b,2,2);
//关闭资源
fos.close();
//输出cd
}
}
数据追加续写
以上代码每次程序运行都会创建输出流对象,清空目标文件中的数据,其实还有保留目标文件中的数据,添加新数据的方法public FileOutputStream(File file,boolean append)
:创建文件输出流以写入由指定的File对象表示的文件public FileOutputStream(String name,boolean append)
:创建文件输出流以以指定的名称写入文件
这两个构造方法参数中都需要传入一个布尔值,true表示追加数据,flase表示清空原有数据,这样创建的输出流对象就可以指定是否追加续写了,代码:
public class FOSwrite{
public static void main(Srting[] args) throws IOException{
//使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt",true);
//字符串转换为字节数组
byte[] b = "abcde".getBytes();
fos.write(b);
//关闭资源
fos.close();
//输出cdabcde
}
}
写出换行
在windows系统中换行符号是\r\n,可以指定追加续写
- 回车符\r和换行符\n
回车符:回到一行的开头
换行符:下一行- 系统中的换行
windows系统里,每行结尾是回车加换行即\r\n
Linux系统里每行结尾只有换行即\n
Mac系统里,每行结尾是回车即\r,从Mac OS X开始于Linux统一
字节输入流【InputStream】
java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中,它定义了字节输入流的基本共性功能方法
public void close()
:关闭此输入流并释放与此流相关联的所有系统资源public abstract int read()
:从输入流读取数据的下一个字节public int read(byte[] b)
:从输入流中读取一些字节数,并将它们存储到字节数组b中
close方法,当完成流的操作时,必须调用此方法,释放系统资源
FileInputStream类
java.io.FileInputStream 类是文件输入流类,从文件中读取字节 **构造方法** - `public FileInputStream(File file)` :通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的File对象file命名 - `public FileInputStream(String name)` :通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名 当创建一个流对象时,必须传入一个文件路径,此路径下没有存在该文件就会抛出FileNotFoundException 构造举例:public class FileInputStreamConstructor{
public static void main(String[] args)throws IOException{
//使用File对象创建流对象
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file);
}
}
读取字节数据
1.读取字节:read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾时返回-1,代码使用:public class FISRead{
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("read.txt");
int read = fis.read();
System.out.println((char)read);
read = fis.read();
System.out.println((char)read);
read = fis.read();
System.out.println((char)read);
read = fis.read();
System.out.println((char)read);
read = fis.read();
System.out.println((char)read);
//末尾处返回-1
read = fis.read();
System.out.println((char)read);
fis.close();
}
]
循环改进读取方式:
public class FISRead{
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("read.txt");
int b;
while((b = fis.read()) != -1){
System.out.println((char)b);
}
fis.close();
}
}
2.使用字节数组读取:read(byte[] b),每次读取b的长度字节到数组中,返回读取到的有效字节个数,读取到末尾时返回-1,代码使用:虽然读取了一个字节,但是会自动提升为int类型
流操作完毕之后必须执行close方法
public class FISRead{
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("read.txt");
int len;
byte[] b = new byte[2];
while((len = fis.read(b)) != -1){
System.out.println(new String(b,0,len));
}
fis.close();
}
}
使用数组读取,每次读取多个字节,减少了系统间IO操作次数,从而提高了读写效率,建议开发中使用
流的关闭原则,先开后关,后开先关
字符流
使用字节流读取文本文件的时候,可能会遇到一些问题,例如遇到中文字符时可能不会显示完整的字符,因为一个中文字符可能占用多个字节存储,所以java提供了一些字符流类,以字符为单位读写数据,专门用于处理文本文件字符输入流【Reader】
java.io.Reader 抽象类时表示用于读取字符流的所有类的超类,可以读取字符信息到内存中,它定义了字符输入流的基本共性功能方法public void close()
:关闭此流以及相关联的任何系统资源public int read()
:从输入流读取一个字符public int read(char[] cbuf)
:从输入流中读取一些字符,并存储到字符数组cbuf中
FileReader类
java.io.FileReader 类就是读取字符文件的便利类,构造时使用系统默认的字符编码和默认字节缓冲区
字符编码:字节与字符的对应规则,windows系统的中文编码默认时GBK编码表,idea中为UTF-8
字节缓冲区:一个字节数组,用来临时存储字节数据
构造方法
FileReader(File file)
:创建一个新的FileReader,给定要读取的File对象FileReader(String fileName)
:创建一个新的FileReader,给定要读取的文件的名称
当创建一个流对象的时候,必须传入一个文件路径
构造:
public class FileReaderConstructor{
public static void main(String[] args) throws IOException{
File file = new File("a.txt");
FileReader fr = new FileReader(file);
FileReader fr1 = new FileReader("b.txt");
}
}
读取字符数据
1.读取字符:使用read方法每次可以读取一个字符,并且提升为int型,读取到文件末尾时返回-1,可以利用循环来读取
public class FRRead{
public static void main(String[] args) throws IOException{
//创建对象
FileReader fr = new FileReader("read.txt");
//定义一个局部变量保存数据
int b;
//循环读取
while((len = fr.read()) != -1){
System.out.println((char)b);
}
fr.close();
}
}
2.使用字符数组读取:read(char[] cbuf):每次读取多个字符到数组中,返回读取到的有效字符个数,读取到末尾时返回-1
public class FRRead{
public static void main(String[] args) throws IOException{
FileReader fr = new FileReader("read.txt");
int len;
char[] cbuf = new char[1024];
while((len = fr.read(cbuf)) != -1){
System.out.println(new String(cbuf,0,len))
}
fr.close();
}
}
字符输出流【Writer】
java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地,它定义了字节输出流的基本共性功能方法
void write(int c)
:写入单个字符void writer(char[] cbuf)
:写入字符数组abstract void write(char[] cbuf,int off,int len)
:写入字符数组的某一部分,off数组的开始索引,off数组的开始索引,len写的字符个数void write(String str)
写入字符串void write(String ,int off,int len)
:写入字符串的某一部分,off字符串的开始索引,len写的字符个数void flush()
:刷新该流的缓冲void close()
:关闭此流,并先刷新
FileWriter类
java.io.FileWriter 类是写出字符到文件的便利类,构造时使用系统默认的字符编码和默认字节缓冲区
构造方法
FileWriter(File file)
:创建一个新的FileWriter,给定要读取的File对象FileWriter(String fileName)
:创建一个新的FileWriter,给定要读取的文件的名称
当你创建一个流对象的时候,必须传入一个文件路径
举例
public class FileWriterConstructor{
public static void main(String[] args){
File file = new File("a.txt");
FileWriter fw = new FileWriter(file);
FileWriter fw1 = new FileWriter("b.txt");
}
}
基本写出数据
使用write方法可以一次写出一个字节,字符数组或者字符串
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,就无法写出字符到文件中,但是关闭流对象,就无法继续写出数据,那么就要使用flush方法
- flush:刷新缓冲区,并可以继续使用流对象
- close:先刷新缓冲区,然后通知系统释放资源,不可继续使用流对象
即便调用了flush方法,在操作的最后还是要调用close方法释放资源
字符流只能操作文本文件,不能操作图片,视频等非文本文件
当只是单纯读写文本文件的时候使用字符流,其他情况使用字节流
IO异常的处理
**JDK7之前的处理方式** 实际开发中不能通过抛出异常的方式来处理异常,建议使用try..catch…finally代码public class HandleExcepetion1{
public static void main(String[] args){
//声明变量
FileWriter fw = null;
try{
//创建流对象
fw = new FileWriter("fw.txt");
//写出数据
fw.write("123456");
} catch (IOException e){
e.printStackTrace();
} finally{
try{
if(fw != null){
fw.close();
}
} catch (IOException e){
e.printStackTrace();
}
}
}
}
**JDK7的处理** 可以使用JDK7的优化后的try-with-resource语句,该语句确保了每个资源在语句结束时关闭,所谓的资源是指程序完成后必须关闭的对象 格式:
try(创建流对象,多个对象之间使用;分隔){
//读写数据
} catch (IOException e){
e.printStackTrace();
}
演示
public class HandleException2{
public static void main(String[] args){
//创建流对象
try(FileWriter fw= new FileWriter("fw.txt");){
//写出数据
fw.write("123456");
} catch (IOException e){
e.printStackTrace();
}
}
}
缓冲流
除了以上一些基本流以外,还有其他功能更强大的流,比如能够高效读写的缓冲流,转换编码的转换流,持久存储对象的序列化流等等,而这些流都是在基本的流对象基础之上创建而来的,相当于对基本流对象的一种增强。概述
缓冲流,也叫高效流,是对基本的四个Filexxx流的增强,所以也是四个流,按照数据类型分类:- 字节缓冲流:BufferedInputStream,BufferedOutputStream
- 字符缓冲流:BufferedReader,BufferedWriter
缓冲流的基本原理,是在创建流对象的时候,创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率
字节缓冲流
构造方法:
- public BufferedInputStream(InputStream in)
:创建一个新的缓冲输入流
- public BufferedOutputStream(OutputStream out)
:创建一个新的缓冲输出流
构造举例:
//创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
//创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
字符缓冲流
构造方法
public BufferedReader(Reader in)
:创建一个新的缓冲输入流public BufferedWriter(Writer out)
:创建一个新的缓冲输出流
构造举例
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
特有方法
字符缓冲流的基本方法与普通字符流的调用方法一致,但是还有一些特有方法
- BufferedReader:
public String readLine()
:读一行文字 - BufferedWriter:
public void newLine()
:写一行行分隔符
转换流
编码引出的问题
在IDEA中使用FileReader读取文本文件时,默认使用的是UTF-8编码,而windows系统创建文件时,默认使用GBK编码,那么读取的时候会产生乱码
InputStreamReader类
转换流java.io.InputStream,作为Reader得子类是从字节流到字符流得桥梁,它能够读取字节并使用指定得字符集将其解码为字符,它得字符集可以由名称指定,也可以接收平台得默认字符集
构造方法
InputStreamReader(InputStream in)
:创建一个使用默认字符集得字符流InputStreamReader(InputStream in,String charsetName)
:创建一个使用默认字符集的字符流
构造举例:
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("in.txt"),"GBK");
OutputStreamWriter类
转换流java.io.OutputStreamWriter,作为Writer的子类,是字符流到字节流的桥梁,使用指定的字符集将字符编码为字节,它的字符集可以由名称指定,也可以接受平台的默认字符集
构造方法
OutputStreamWriter(OutputStream out)
:创建一个使用默认字符集的字符流OutputStreamWriter(OutputStream out,String charsetName)
:创建一个使用指定字符集的字符流
构造举例
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("out.txt"),"GBK");
序列化
java提供了一种对象序列化的机制,用一个字节序列可以表示一个对象,该字节序列包含该对象的数据,对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化,读取信息并且在内存中创建对象
ObjectOutputStream类
java.io.ObjectOutputStream类,将java对象的原始数据类型写出到文件,实现对象的持久存储
构造方法:
public ObjectOutputStream(OutputStream out)
:创建一个指定OutputStream的ObjectOutputStream
构造举例
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.txt"));
序列化操作
一个对象如果想被序列化必须满足两个条件
- 该类必须实现java.io.Serializable接口,该接口是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
- 该类的属性必须是可序列化的,如果有一个属性不需要可序列化,则该属性必须注明是瞬态的,使用transient修饰
public class Employee implements Serializable{
public String name;
public transient int age;//瞬态修饰成员无法被序列化
public void method(){
System.out.println("===");
}
}
写出对象方法
public final void writeObject(Obkect obj)
:写出指定的对象
public class SerializeDemo{
public static void main(String args){
Employee e = new Employee();
e.name = "11";
e.age = 18;
try{
//创建序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.txt"));
//写出对象
oos.writeObject(e);
//释放资源
oos.close();
} catch(IOException i){
i.printStackTrace();
}
}
}
ObjectInputStream类
java.io.ObjectInputStream 反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复成对象
构造方法
public ObjectInputStream(InputStream in)
:创建一个指定InputStream的ObjectInputStream
反序列化操作1
如果能找到一个对象的class文件,就可以进行反序列化操作,调用ObjectInputStream读取对象的方法:
public final Object readObject()
:读取一个对象
public class DeserializeDemo{
public static void main(String[] args){
Employee e = null;
try{
//创建反序列化流
FileInputStream fileIn = new FileInputStream("employee.txt");
ObjectInputStream ois = new ObjectInputStream(fileIn);
//读取一个对象
e = (Employee) ois.readObject();
//释放资源
in.close();
fileIn.close();
} catch(IOException i){
i.printStackTrace();
return;
} catch(ClassNotFoundException c){
c.printStackTrace();
return;
}
System.out.println("Name:" + e.name);
System.out.println("Name:" + e.age);
}
}
对于JVM可以反序列化对象,它必须是能够找到class文件的类,如果找不到该类的class文件,则抛出一个ClassNotFoundException 异常
反序列化操作2
另外,当JVM反序列对象时,能找到class文件,但是class文件在序列化对象之后发生了改变,那么反序列化操作也会失败,抛出InvalidClassException异常,原因如下
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable 接口给需要序列化的类,提供了一个序列版本号,serialVersionUID该版本号的目的在于验证序列化的对象和对应类是否版本匹配
public class Employee implements Serializable{
//加入序列版本号
private static final long serialVersionUID = 1L;
public String name'
public int age;
//添加新的属性,重新编译,可以反序列化,该属性赋为默认值
public int eid;
public void method(){
System.out.println("===");
}
}
打印流
概述
print 和 println的方法都来自于java.io.PrintStream类
PrintStream类
构造方法
public PrintStream(String fileName)
:使用指定的文件名创建一个新的打印流
举例
PrintStream ps = new PrintStream("ps.txt");
改变打印流向
System.out 就是PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上,不过既然是流对象,就可以改变它的流向
public class PrintDemo{
public static void main(String[] args){
//调用系统打印流,控制台直接输出97
System.out.println(97);
//创建打印流,指定文件的名称
PrintStream ps = new PrintStream("ps.txt");
//设置系统的打印流流向,输出到ps.txt
System.setOut(ps);
//调用系统的打印流 ps.txt中输出97
System.out.println(97);
}
}