IO流
IO概述:程序的运行都在内存中进行。在程序结束时,数据全部丢失。为了让操作后的数据永久保存下来,我们需要使用外部存储器来完成。
那么,如何完成内存和外存之间的数据传输呢?我们可以使用输入输出(I/O)机制来完成
这些类统一放在java.io和java.nio包中,统称 Java I/O系统。
其中,I表示Input,意为输入,O表示Output,意为输出。
一、I/O系统中流的概念
流的基本概念
在Java中把IO操作设计成了一个统一的模型,也就是“流模型”。
这种模型将输入和输出看成是两个端点之间建立一根管道,然后数据就像流水一样从数据源流向目的地。
数据源 ----> 管道 ---->目的地
什么是数据源?
- 数据源(Data Source)是提供数据的原始媒介,分为为程序提供数据的源设备(一般对应输入流)和接收程序数据的目标设备(一般对应输出流)。
- 常见数据源有:数据库、文件、其他程序、网络、内存、IO设备。
二、I/O系统中流的分类(三种)
按照流的方向分类:
输入流(写入流)和输出流(读取流)
-
在Java语言中,流的方向以内存作为参照物,当数据源中将数据渠道内存中时,称为输出流,也叫读取流。
-
当从内存中将数据写入到数据源时,称为输入流,也叫写入流
按流能够处理数据的最小单位(管道粗细)来进行划分
-
字节流 --------->以byte为最小单位
-
字符流 --------->以char为最小单位
按照流的功能分类
-
节点流 ---------> 可以直接从/向数据源读/写数据。如FileInputSteam,DataInputSteam
-
处理流 --------->可以在数据传递过程中执行某个处理任务;处理流不能单独使用,需要连接到已经存在的节点或流上面。如BufferedInputSteam,BufferedReader
流的子类
Java.io包
-
字节流有两个抽象父类:InputStream 和OutputStream
-
字符流有两个抽象父类:Reader 和Writer
-
*补充:*为什么父类要设为abstract抽象类呢?
因为不同的数据源读取的方式不同,所以在字节流和字符流的父类中,预留了读写数据的抽象方法,不同的子类根据自己数据源的特点分别去实现。
详细解释:在现实中,输入输出的外部设备(数据源)有很多种,比如键盘、鼠标、U盘、手柄等,不同的设备数据传输处理的方式是不同的,统一提供了四大抽象父类,然后各自不同的外部设备继承抽象父类,根据自身特点重写其中数据处理的操作。对于开发者来说,可以屏蔽不同设备之间进行数据传输的差异,只要是读就是read/写就是write。
- BufferedInputStream ---------> 数据源是缓冲区
- FileInputStream ---------> 数据源是文件
- ObjectInputStream ---------> 数据源是对象
三、各类流的通用操作步骤
字节输入流InputStream共有的方法
常用方法 | 作用 |
---|---|
read() | 从流中读取下一个字节 |
read(byte[] b) | 从输入流中读取一些字节,并存入数组b |
read(byte[] b, int off, int len) | 从输入流中读取len个字节,并存入数组b |
close() | 关闭输入流 |
字节输入流OutputStream共有的方法
常用方法 | 作用 |
---|---|
write(byte[] b) | 从输出流中写入一些字节,并存入数组b |
public void write(byte[] b,int off,int len) | 从输出流中写入len个字节,并存入数组b |
flush() | 刷新流,将数据真正写入数据源 |
close() | 关闭输入流 |
输入/输出流的操作步骤
-
建立流,建立数据源与目的地进行数据传输的管道
-
操作流,数据处理(读取输入流中的数据)
-
关闭流,释放资源
-
输入流:
//1、建立数据源与目的地进行数据传输的管道 try { InputStream in = new FileInputStream("1.txt"); //文件需放在项目 的根目录下,文件名找不到可以用try catch包裹或者throws抛出 //2.处理数据 //read() 读取到流中的下一个字节数据,并返回该数据对应的Unicode码 //如果 流中没有数据,则返回-1 int n = 0; while ((n = in.read()) != -1){ System.out.print((char)n); } //3.关闭流 释放资源 in.close(); } catch (IOException e) { throw new RuntimeException(e);
补充步骤二输入流对数据的处理方法:
-
方法一:
int n = 0; while ((n = in.read()) != -1){//in指的是建立的输入流通道 System.out.print((char)n); }
-
方法二:(如果是Reader的话byte[]变为char[])
//存入byte数组中 byte[] bytes = new byte[1024]; int len = 0; while ((len=fileInputStream.read(bytes)) != -1){ System.out.println(new String(bytes,0,len));//对 }
-
方法三:
byte[] bytes = new byte[1024]; int n = 0;// 读取个数 StringBuffer sb = new StringBuffer(); while ((n = in.read(bytes)) != -1){ sb.append(new String(bytes,0,n)); } out.write(sb.toString().getBytes());
-
-
输出流:
//1.建立数据源 传输数据到目的地的通道 //内存 ---->数据源 (FileOutputStream数据源是文件) try { OutputStream out = new FileOutputStream("2.txt"); //2.对数据进行处理,写入流中 // write()本质上只是把数据写入到缓存区中 out.write("hello world".getBytes()); //3.将缓存区的数据,强制写入数据源 out.flush(); out.close(); } catch (IOException e) { throw new RuntimeException(e); }
-
四、主要流类的使用方法
字符读取流Reader类
字符流用于操作字符数据,可以直接对字符数据进行读取和写入
常用方法 | 作用 |
---|---|
FileReader | 读取文件中的字符信息 |
InputStreamReader | 将字节流转换为字符流 |
BufferedReader | 套接字符流,需要以另一个字符流作为基础。将数据读入缓冲区,然后从缓冲区读取 |
相关代码:
public class ReaderTest {
public static void main(String[] args) {
try {
Reader fileReader = new FileReader("1.txt");
BufferedReader reader = new BufferedReader(fileReader);
//readLine() 读取文档中一行数据,如果流中没有数据,就返回null
//其他方法 如read() read(char[] chars) 同其他字符流
String info = null;
while ((info = reader.readLine()) != null){
System.out.println(info);
}
//关闭流,先开后关
reader.close();
fileReader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
字符读取流Writer类
常用方法 | 作用 |
---|---|
FileWriter | 向文件中写入字符信息 |
BufferedWriter | 套接字符流,需要以另一个字符流作为基础。将缓冲区数据写入数据源 |
相关代码:
public class WriterTest {
public static void main(String[] args) {
try {
Writer writer = new FileWriter("2.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer);
//用FileWriter直接写入
//writer.write("好好学习,天天向上\n"+
// "Good Good Study,Day Day Up");
//用BufferedWriter套接字符流
bufferedWriter.write("小鸡炖蘑菇 宝塔镇河妖");
//writer.flush();
bufferedWriter.flush();
bufferedWriter.close();
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
关闭流
补充:写入流在写入数据的时候,需要通过flush()刷新语句,才能将数据真正写入数据源。
如果不关闭会有什么影响?如果不关闭流,那么内存与数据源之间建立的通道不会消失,会浪费内存资源。另外,在写入流关闭时,会自动执行flush()刷新语句。所以,写入流在不关闭也不刷新的情况下,有可能写不进数据。一般情况下将关闭流写在finally语句块中,避免因发生异常,而没有对流进行关闭操作。
代码:
public class WriterTest {
public static void main(String[] args) {
try {
Writer writer = new FileWriter("2.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer);
//用FileWriter直接写入
//writer.write("好好学习,天天向上\n"+
// "Good Good Study,Day Day Up");
//用BufferedWriter套接字符流
bufferedWriter.write("小鸡炖蘑菇 宝塔镇河妖");
//writer.flush();
bufferedWriter.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//关闭流
try {
//如果找不到操作文件,捕捉FileNotFoundException异常
if (bufferedWriter != null){
bufferedWriter.close();
}if (writer != null){
writer.close();
}
}catch (IOException e){
throw new RuntimeException();
}
}
}
}
try-with-resource语法糖(自动捕获异常并关闭流)
没哟偶显示调用流的close方法,但是编译器对上述代码进行编译后,会自动生成刘关闭的close方法。
语法:
try(流的声明创建语句){
//可能会异常的代码
}catch(异常类 变量){
//异常处理的代码
}
具体实现:
try(Writer writer = new FileWriter("2.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer);){
bufferedWriter.write("小鸡炖蘑菇 宝塔镇河妖");
//writer.flush();
bufferedWriter.flush();
}catch (IOException e){
e.printStackTrace();
}
补充:异常的处理
- 事前处理——针对运行期异常,通过经验和代码的特性提前做好预备工作。比如:对对象进行调用前,判断是否为null,进行类型强转之前,instanceof 判断类型,通过下标获取元素时,先判断下标是否越界
- 事后处理——针对编译期异常,try-catch捕获或者throws抛出,try-catch捕获后再建立一个运行时异常抛出
操作文件流
操作文件流时,如果操作的文件不存在,写入流或读取流会抛出FileNotFoundException异常。
File类
当我们创建一个操作文件的流时(FileInputStream/FileReader),根据路径找不到文件,产生FileNotFoundException(文件未找到异常)。所以为了让我们在创建流时,可以确定文件或目录是否存在,可以通过File来完成。
File类提供了定位本地文件系统、描述文件和目录的功能。是java.io包中引用实际磁盘文件的对象。
构造方法
public File(String pathname) //通过文件路径或目录路径,创建File文件对象
常用方法
常用方法 | 作用 |
---|---|
String getAbsolutePath() | 得到当前文件对象的物理路径 |
boolean exists() | 判断文件或目录是否存在 |
boolean isFile() | 判断文件对象是否是文件 |
boolean isDirectory() | 判断文件对象是否是目录 |
boolean delete( ) | 删除当前文件对象表示的文件或目录, 如果删除目录,那么目录必须为空 |
boolean mkdir( ) | 创建一个目录 |
File[] listFiles() | 得到目录里所有的文件对象 |
相关代码:
//getAbsoluteFile获取当前文件的绝对路径
File file = new File("info/user.txt");
System.out.println("绝对路径:"+file.getAbsolutePath());
//exists判断一个文件或目录是否存在
File flaseFile = new File("F:\\part2\\hello");
System.out.println("文件目录是否存在:"+flaseFile.exists());
//isFile判断是不是文件
System.out.println("file对象是否为文件:"+file.isFile());
//isDirectory判断是否为目录
System.out.println("file对象是否为文件夹:"+file.isDirectory());
//delete删除文件或空目录
System.out.println(file.delete());
//删除info目录
File file1 = new File("info");
System.out.println(file1.delete());
//mkdir 创建单级目录
File file3 = new File("info");
file3.mkdir();
//mkdirs 创建多级目录
File info = new File("test1/test2/test3");
System.out.println(info.mkdirs());
//listFiles 得到一个目录下所有的文件
File file2 = new File("F:\\workspace\\part2\\javaoo");
File[] files = file2.listFiles();
for (File f :files){
System.out.println(f);
}
注意:
- 用delete删除目录时只能删除空目录
- 除了绝对路径其他返回值都为true/false
五、对象序列化和反序列化的方法
对象流
在对字符流进行读取和写入时,需要完成对象数据和字符数据的转换,需要进行大量的字符串拼接操作。那么能不能直接完成对对象数据的操作,简化这样的数据转换过程呢?
对象流可以完成这样的需求,通难过对象流,可以直接对对象数据进行操作。
在java语言中,我们可以将对象直接保存在硬盘上,此时,对象中的引用及相关状态就永久保存了。在需要的时候我们可以将硬盘上的拷贝重新读入内存并恢复成源对象。
对象序列化
对象流在操作对象数据时,如何对对象进行传输呢?
在传输对象时,由于对象的数据庞大,无法直接传输。所以,在传输之前,先将对象打散成字节序列,以利于传输。这个过程,称为序列化过程。
public final void writeObject(Object obj) //将对象序列化写入数据源
如何实现对象序列化
但是,不是任何对象都能被打散成字节序列,在java中,所有需要实现对象序列化的对象,必须首先实现java.io.Serializable接口。
public class UserBean implements java.io.Serializable
java.io.Serializable序列化接口是标示性接口,该接口中没有定义方法。
如果对尉氏县序列化接口的对象进行传输,那么会抛出java.io.NotSerializableException异常
对象反序列化
在字节序列达到目的地以后,有需要将字节序列还原成对象。这个过程,称为反序列化过程。
public final Object readObject() //将数据源的二进制数据,反序列化成对象
transient
在传输对象时,默认情况下,对象的每个属性值都会被传输,如果想对对象的属性进行选择性传输,可以使用transient关键字。
transient是用于属性的修饰符,表示在对象序列化时,被transient修饰的属性值,不被传输。
private transient int money;//这里不传输money金额
六、文件类的使用方法
Properties常用方法
Properties是唯一一个与IO流相交互的集合,表示了一个持久的属性类,可以保存在流中或从流中加载。在后期一般使用属性文件做项目或框架的配置。属性列表中每个键及其对应值都是一个字符串;数据都以键值对(键=值)形式保存。
常用方法 | 作用 |
---|---|
load() | 加载属性列表 |
store() | 第一个参数为 OutputStream/Writer 用来指向加载的配置文件, 第二个参数为 String 用来给配置文件添加注释。 |
getProperty() | 得到键对应的值 |
setProperty() | 添加键值对 |
相关代码:
//首先得创建一个properties属性文件,这里的文件为根目录下的“info.properties”
Properties properties = new Properties();
try(Reader reader = new FileReader("info.properties")) //()里是想要读取的properties属性文件的路径
{
//加载properties属性文件
properties.load(reader);
//添加"test","add"键值对
properties.setProperty("test","add");
//打印输出得到键对应的值
System.out.println(properties.getProperty("username"));
System.out.println(properties.getProperty("password"));
System.out.println(properties.getProperty("money"));
System.out.println(properties.getProperty("test"));
} catch (IOException e) {
throw new RuntimeException(e);
}
//查看store方法的代码:
try (
Writer writer = new FileWriter("info.properties");){
//writer指向加载的配置文件,"第二次写入"给配置文件添加注释,输出为一串
properties.store(writer,"第二次写入");
}
catch (IOException ex) {
throw new RuntimeException(ex);
}