IO流
1、掌理解Java l/O系统中流的概念
2、掌握Java l/O系统中流的分类
3、掌握各类流的通用操作步骤
4、掌握主要流类的使用方法
5、掌握对象序列化和反序列化的方法
6、掌握文件类的使用方法
l/O概述
程序的运行都在内存中进行。在程序结束时,数据全部丢失。为了让操作后的数据永久保存下来, 我们需要使用外部存储器来完成。
那么,如何完成内存和外存之间的数据传输呢?我们可以使用输入输出(/o)机制来完成。
Java类库中提供了大量类,可以帮助我们从不同的设备读取数据,并保存或输出到不同的设备中。 这些类统一放在java io包和java.nio包中,统称Java l/O 系统。
其中,l 表示 input,意为输入,0 表示 Output,意为输出。
流(Stream)的基本概念
在java当中把 IO操作设计成一个统一的模型,也就是 “流模型 ”。
将IO操作,看做是两个端点(节点)之间进行数据的传输,流就是端点(节点)之间数据传输的通道,在流模型下,数据就好像流质一般从通道一端到另一端。
数据源 ------> 管道 --------> 目的地
流的分类
1. 按照数据传输方向来分
在java语言中,流的方向以内存作为参照物,当从数据源将数据读取到内存中时,称为输入流,也叫读取流。
数据源 --------> 内存 读取流
从内存将数据写入数据时,称为输出流(写入流)
数据源 <-------- 内存
2.在java语言中,按照流处理数据的最小单位分为
字节流 ————————> 以byte为最小单位传输
字符流 ————————>以char为最小单位传输
3.按照流的功能分
节点流 ------------> 可以直接从/向数据源读/写数据
处理流 ------------> 可以在数据传递过程中执行某个处理任务
处理流不能单独使用,可以在数据传输过程中加入处理操作。
字节流有两个抽象父类 :InputStream (字节输入流)和 OutputStream(字节输出流)
字符流有两个抽象父类 :Reader (字节输入流) 和 Writer(字节输出流)
提供四大抽象方法作用:因为在现实中,输入输出的外部设备(数据源)比如键盘等,不同的设备数据传输处理方式是不同的,统一提 供了四大抽象父类,各自不同的外部设备继承抽象父类,根据自身特点重写其中数据处理的操作,对于开发者来说,可以屏蔽不同设备之间进行数据传输的差异,只要是读就是read,只要是写就是write。
输出流
OutputStream | Writer
字节流 <---------------------------------O----------------------------------> 字符流
InputStream | Reader
输入流
流的子类
不同的数据源读写数据的方式会有所不同,所以在字节流和字符流的父类
中,预留了读写数据的抽象方法,不同的子类根据自己数据源的特点分别
去实现。
BufferedInputStream 数据源是缓冲区
FileInputStream 数据源是文件
ObjectInputStream 数据源是对象
流的选择
1.确定数据源 和目的地(数据传输的方向),输入、输出
2.确定管道粗细(以字节为单位传输还是以字符单位传输,字节/y字符)
输入流操作的步骤
第一步:建立流。建立内存和数据源的数据通道。
InputStream in = new FileInputStream(“1.txt”);
第二步:操作流。读取输入流中的数据。
int n = 0;
while((n = in.read()) != -1 ){
……
}
第三步:关闭流,释放内存资源。
in.close();
输出流操作的步骤
第一步:建立流。建立内存和数据源的数据通道。
OutputStream out = new FileOutputStream(“1.txt”);
第二步:操作流。向数据源写入数据。
out.write(“Hello World”.getByte());
第三步:关闭流,释放内存资源。
in.close();
输入流常用方法:read 、close
输出流常用方法:wirte、flush、close
InputStream方法
public class InputStreamTest {
public static void main(String[] args) {
// 数据源 管道 目的地
try {
// 1 建立数据源与目的地进行数据传输的管道
InputStream in = new FileInputStream("1.txt");
// 2.处理数据
// read() 读取到流中的下一个字节数据,并返回该数据对应的Unicode,
// 如果流中没有数据 则返回-1
// int n = 0;
// while ((n = in.read()) != -1){
// System.out.print((char)n);
// }
// read(byte[] bytes)
// 返回读取字节的个数,如果流中没有数据则返回-1
byte[] bytes = new byte[1024];
int len = 0;// 保存read方法一次读取返回的个数
while ((len = in.read(bytes)) != -1){
// System.out.println(len + "=============================");
System.out.print(new String(bytes,0,len));
}
// 关闭流 释放资源
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
OutPutStream方法
方法 | 作用 |
---|---|
write() | 向文件中写入指定字符 |
write(byte[] b) | 向文件中写入指定byte数组 |
write(byte[] b,int off, int ) | |
out.flush() | 将缓存区数据强制写入数据源 |
close() | 关闭流 |
public class OutPutStreamTest {
/**
* 文件拷贝 将源文件内容拷贝至目标文件中
* 1.将源文件数据读取到内存 数据源-->内存 输入流
* 2.内存汇总源文件的数据写入到目标文件 内容 ---> 数据源 输出流
* @param src 源文件
* @param dest 目标文件
*/
public static void copyFile(String src,String dest){
try {
// 得到字节输出流和输入流对象
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest);
// 利用输入流读取文件数据
// 方式一
// int n = 0;
// while ((n = in.read()) != -1){
// out.write(n);
// }
// 方式2
byte[] bytes = new byte[1024];
int len = 0;
while ((len = in.read(bytes)) != -1){
out.write(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());
out.flush();
out.close();
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
copyFile("4.jpg","5.jpg");
// 建立数据源传输数据到目的地的通道
// 内存 ---> 数据源 (FileOutputStream数据源是文件)
// try {
// OutputStream out = new FileOutputStream("2.txt");
// // 2.对数据进行处理,写入流中
// // write()本质上只是把数据写入到缓存区
// out.write("hello world".getBytes());
//
// // 将缓存区的数据 强制写入数据源
// out.flush();
// out.close();
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
}
}
Reader方法
方法 | 作用 |
---|---|
read() | 读取到流中下一个字节数据,并返回该数据对应的Unicode码 如果流中没有数据,则返回-1 |
read(byte[] b) | 返回读取的字节的个数 如果流中没有数据,返回-1 |
read(byte[] b,int off, int ) | |
close() | 关闭流 |
字符读取流Reader类
FileReader ------------------> 读取文件中的字符信息
InputStreamReader ---------------> 将字节流转换为字符流
BufferedReader --------------------> 套接字符流,需要以另一个字符流作为基础。将数据读入缓冲区,然后从缓冲区读取
字符写入流Writer类
FileWriter ------------------> 向文件中写入字符信息
BufferedWriter -----------------> 套接字符流,需要以另一个字符流作为基础。将缓冲区数据,写入数据源。
public class ReaderTest {
public static void main(String[] args) {
// FileReader
try {
// 1、创建了一个文件字符读取流对象 通道
// Reader reader = new FileReader("1.txt");
// 操作数据
// int n = 0;
// while ((n = reader.read()) != -1){
// System.out.print((char) n);
// }
// char[] chars = new char[1024];
// int len = 0;
// while ((len = reader.read(chars)) != -1){
// System.out.println(new String(chars,0,len));
// }
// // 关闭流
// reader.close();
// 节点流:可以直接向数据量写数据/从数据源读数据
// InputStream in = new FileInputStream("1.txt");
// // 操作流(处理流) 基于节点流或操作流,来创建,
// // 可以在数据传输途中,对数据加以操作
// Reader reader = new InputStreamReader(in);
//
int n = 0;
while ((n = reader.read()) != -1){
System.out.print((char) n);
}
// char[] chars = new char[1024];
// int len = 0;
// while ((len = reader.read(chars)) != -1){
// System.out.println(new String(chars,0,len));
// }
//
// // 关闭流 先开启的后关闭
// reader.close();
// in.close();
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.print(info);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Writer方法
public class WriterTest {
public static void main(String[] args) {
try {
// 字符输出流
// Writer writer = new FileWriter("1.txt");
// writer.write("好好学习,天天向上\n" +
// " Good Good Study, Day Day Up");
//
// writer.flush();
// writer.close();
Writer writer = new FileWriter("1.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer);
bufferedWriter.write("小鸡炖蘑菇 宝塔镇河妖");
bufferedWriter.flush();
bufferedWriter.close();
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
关闭流
写入流将数据写入数据源时,需要通过flush()刷新语句,才能将数
据真正写入数据源。在流关闭时,会自动执行flush()刷新语句。所以,
写入流在不关闭,也不刷新的情况下,有可能写不进数据。
为什么要关闭流:因为当我们完成流的操作后,流不会自动的进行关闭(除非程序结束),为了让无用的流不在占据消耗内存资源,需要主动调用close()方法完成流的关闭,释放内存资源。
ps:流关闭时,先开启的流,后关闭
并且为了保证流在除了程序结束(虚拟机关闭)的情况下,都会执行关闭操作,需要将关闭流的代码,写在就算异常发生都会被执行的位置(Finaly块),并且我们在调用流的close方法前,应当判断流是否被成功的建立(流的变量是否为null)
补充:异常的处理
事前处理:针对运行期异常,通过经验和代码特性提前做好预备工作,比如,对象进行调用前,判断是否为null,进行类型强转之前,instarceof判断类型,通过下标获取元素时,先判断下标是否越界。
事后处理:针对编译期异常,try-catch捕获或者throws抛出,try-catch捕获后再创建一个运行时运行时异常抛出。
//try-with-resource写法(JDk7后出现的) 会自动生成关闭流的代码
//这种写法资源在括号里面声明,自动关闭资源,作用域只能try内部使用
try ( Reader fileReader=null;
BufferedReader bufferedReader=null;){
//代码部分
} catch (IOException e) {
throw new RuntimeException(e);
}
操作文件流
写入流会在指定的目录下创建新文件。不过,指定的目录必须存在,
否则,也会抛出FileNotFoundException异常。
File类
File类构造方法:
public File(String pathname) 通过文件路径或目录路径,创建File文件对象
String getAbsolutePath() 得到当前文件对象的物理路径
boolean exists() 判断文件或目录是否存在
boolean isFile() 判断文件对象是否是文件
boolean isDirectory() 判断文件对象是否是目录
boolean delete( ) 删除当前文件对象表示的文件或目录,如果删除目录,那么目录必须为空
boolean mkdir( ) 创建一个目录
mkdirs( ) 创建多个目录
File[] listFiles() 得到目录里所有的文件对象
File类提供了定位本地文件系统、描述文件和目录的功能。是 java.io 包中引用实际磁盘文件的对象。
对象序列化
在传输对象时,由于对象的数据庞大,无法直接传输。所以,在传输之前,
先将对象打散成字节序列,以利于传输。这个过程,称为序列化过程。
在字节序列到达目的地以后,又需要将字节序列还原成对象。这个过程,称为反序列化过程。
注意:能够被打散成为字节序列的对象必须是要实现序列化接口(Serializable),Serializable接口其实是一个空接口(没有任何抽象方法),一般称为空接口为标识接口。
实现对象序列化
public class UserBean implements java.io.Serializable
java.io.Serializable序列化接口是标识性接口,该接口中没有定义方法。
如果对未实现序列化接口的对象进行传输,那么会抛出 java.io.NotSerializableException异常。
对象流
在java.io包中,提供了ObjectOutputStream,对对象信息进行保存操作。
public final void writeObject(Object obj) 将对象序列化写入数据源
public final void writeObject(Object obj) 将对象序列化写入数据源
public final Object readObject() 将数据源的二进制数据,反序列化成对象。
transient
在传输对象时,默认情况下,对象的每个属性值都会被传输,如果想对对象的属性进行选择性传输,可以使用transient关键字。
transient是用于属性的修饰符,表示在对象序列化时,被transient修饰的属性值,不被传输。
private transient int money;
Properties类
Properties类可以将Map形式的键/值对数据存在文本文件中。能轻松的完成对属性文件数据的读取,以及将Properties对象中的数据保存在属性文件中。常用作软件的配置文件。 在后期一般使用属性文件做项目或框架的配置。
属性文件文件中,数据都已键/值对形式保存。
通过Properties类加载属性文件后,可以通过gitProperties(k)获取值,也可以通过setProperties(键、值)去给已存在的键修改或新增键值对。通过Store方法将内存中的Properties类数据保存到属性文件中。
Properties常用方法
load() 加载属性文件中的数据
store() 将Properties对象中现有的键值对保存进属性文件
getProperty() 根据键对象,得到值对象。如果键对象不存在,返回null
setProperty() 向Properties对象中添加元素。如果键相同,则覆盖。
传统try-catch-finally的写法
/**
* 手动关闭
*/
public static void test1() {
final String FILE_PATH = "XXXXXXXXXXXX";
FileOutputStream fileOutputStream = null;
FileInputStream fileInputStream = null;
try {
fileOutputStream = new FileOutputStream(new File(filePath));
fileInputStream = new FileInputStream(new File(filePath));
// 写
fileOutputStream.write("ab1024".getBytes(Charset.defaultCharset()));
// 读取
int data = fileInputStream.read();
while (data != -1) {
System.out.print((char) data);
data = fileInputStream.read();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭写流
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭读流
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
try-with-resources写法
/**
* 自动关闭 try-with-resource
*/
public static void test2() throws Exception {
final String FILE_PATH = "XXXXXXXXXXXX";
// java7中的try-with-resource,java9中有所改善
try (FileOutputStream fileOutputStream = new FileOutputStream(new File(filePath));
FileInputStream fileInputStream = new FileInputStream(new File(filePath))) {
// 写
fileOutputStream.write("ab1024".getBytes(Charset.defaultCharset()));
// 读取
int data = fileInputStream.read();
while (data != -1) {
System.out.print((char) data);
data = fileInputStream.read();
}
// 模拟异常
// int a = 0 / 0;
}
}
package onlytest;
import static java.lang.System.out;
public class testAutoClose {
public static void main(String[] args) {
try (MyClose close = new MyClose()) {
close.println("liuqinhou");
throw new Throwable("exist error");
} catch (Exception e) {
out.println("exception exists");
e.printStackTrace();
} catch (Throwable throwable) {
out.println("throwable exists");
throwable.printStackTrace();
}
out.println("main thread end");
}
}
class MyClose implements AutoCloseable {
public void println(String str) {
out.println(str + " is working...");
}
@Override
public void close() throws Exception {
out.println("MyClose is closed now");
}
}
File类
File f = new File("./test");
if (f.isDirectory()) {
System.out.println(f.delete());
}
Properties类方法案例
public class PropertiesTest {
public static void main(String[] args) {
Properties p = new Properties();
try (Reader r = new FileReader("./configs/configs.properties")) {
p.load(r);
p.setProperty("pwd", "1234");
System.out.println(p.getProperty("pwd"));
System.out.println(p.getProperty("username"));
} catch (IOException e) {
throw new RuntimeException(e);
}
try (Writer w = new FileWriter("./configs/configs.properties")) {
p.store(w,"Test");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
结果:
#Test
#Tue Apr 18 16:59:37 CST 2023
pwd=1234
username=123;
I/O流文件路径
在Unix/Linux
中,路径的分隔采用正斜"/"
,比如"cd /home/java"
;
而在Windows
中,路径分隔采用反斜杠"\"
,比如"F:\yihong_\book"
。
开发是在Windows
平台上,所以Java
程序配置文件中文件路劲都是用的"\\"
,而项目是部署在Linux
上的,所有文件路劲都是用的"/"
。
转义字符
Windows
中,我在F盘复制地址“F:\yihong_\book”
,粘贴至Java程序,会自动变成“F:\\yihong_\\book”
。这个时候就发生了转义,这个操作是idea自动完成的。
String path = "F:\\yihong_\\book";` 对的
`String path = "F:\yihong_\book";` 错的
Java常见的系统路径与获取方法
// 分隔符
String fileSeperator = File.separator;
// 用户主目录
String userHome = System.getProperty("user.home");
// Java实时运行环境的安装目录
String javaPath = System.getProperty("java.home");
// 操作系统名称
String osName = System.getProperty("os.name");
// 当前用户程序所在目录
String userDir = System.getProperty("user.dir");
// JDK的安装目录
String jdkDir = System.getProperty("java.ext.dirs");
总结
1、java中使用java.io包中相关的api,完成输入输出数据操作。
2、I/O流按传输内容分为:字节流、字符流、对象流。按流的方向分为:输入流和输出流。
3、字节流的父类是InputStream和OutputStream;字符流的父类是Reader和Writer。
4、输入流常用的方法有:read(读取)、close(关闭)。输出流常用的方法有:write(写入)、flush(刷新)、close(关闭)。
5、File类提供定位本地文件系统、描述文件和目录的功能。
6、对象序列化是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。
7、需要实现对象序列化的对象必须实现Serializable接口。
8、通过ObjectInputStream和ObjectOutputStream可以完成对象流操作。