IO流
1.掌握Java I / O系统中流的概念
程序的运行都在内存中进行。在程序结束时,数据全部丢失。为了让操作后的数据永久保存下来,则需要外部存储器来完成。完成内存和外存之间的数据传输,使用输入和输出(I/O)机制来完成。
I—input,输入(读取);O—Output,输出(写入)。
1.1流(stream)的基本概念
在Java当中把IO操作设计成了一个统一的模型,也就是“流模型”,这种模型将输入和输出看成是两个端点之间建立一根管道,数据就是流水一样从数据源流向目的地。数据源–>管道–>目的地
1.2流(stream)的分类体系
1.2.1根据流的方向分为:
输入流(从数据源中将数据读取到内存中时,为输入流,读取流);
数据源–>内存
输出流(把内存中的数据写入到数据源时,成为输出流,写入流)
内存–>数据源
1.2.2根据流能传输处理数据的最小单位(管道粗细)分为:
字节流–> 以byte为最小单位传输;
字符流–> 以char为最小单位传输。
选择用哪个看资源是啥? 文档型—>字符流 图片、音频等—>字节流
1.2.3根据流的功能进行划分:
节点流–> 可以直接从/向数据源读/写数据;
流的一个端点是外部设备(节点流)
处理流–> 可以在数据传递过程中执行处理任务。Ps:处理流不单独使用,而是连接在已经存在的流上
基于节点流或处理流进行创建的(数据流)BufferedInputReader
看构造方法传的数据是啥?
2.Java.io包
操作流相关的类都在Java.io包中;
字节流抽象父类:InputStream 和 OutputStream
字符流的抽象父类:Reader 和 Writer
**为什么提供的是流的抽象父类?**答:因为在现实生活中输入输出的外部设备(数据源)有很多种,比如:键盘、鼠标、手柄、电视、电脑…,不同的设备数据传输处理的方式是不同的,统一提供了4大抽象父类,把所有的情况都考虑进去,不同的外部设备继承抽象父类,根据自身特点,重写其中的数据处理的操作。对我们开发者而言,可以屏蔽不同设备之间进行数据传输的差异,只要是读就是read/只要是写就是write。
流的选择:1、先确定数据源和目的地—>确定数据传输的方向(是输入 还是输出);2、确定管道粗细—>确定采用字符流还是字节流。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-twEHetUq-1682258065087)(C:\Users\mx\AppData\Roaming\Typora\typora-user-images\image-20230417121653919.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q51S59sd-1682258065089)(C:\Users\mx\Desktop\iostream2xx.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RiV6eona-1682258065090)(C:\Users\mx\Desktop\lesson\java\Java IO流\iostream2xx.png)]
流的子类:不同的数据源读写数据的方式会有所不同,所以在字符流和字节流的父类中,预留了读写数据的方法,不同的子类根据自己数据源的特点分别取实现。
2.1字节流InputStream共用的方法
2.1.1输入流操作的步骤:
1、建立流。建立内存和数据源的数据通道
2、操作流。读取输入流中的数据
3、关闭流。释放内存资源
read()----下一个字节,下一个字节没有返回-1。这个方法从InputStream 对象读取指定字节的数据。返回为整数值。返回下一字节数据,如果已经到结尾则返回-1。
public static void main(String[] args) {
try {
//1、建立管道 数据源与目的地进行数据传输的管道
InputStream in = new FileInputStream("1");
//2、处理数据
// read() 读取到流中下一个字节数据,并返回该数据对应的Unicode码,如果流中没有数据 则返回-1
int n = 0; //保存read()方法一次读取返回的个数
while ((n = in.read()) != -1) {
System.out.print((char) n);
//(char)将Unicode码转换为char字符
}
//关闭流 释放资源
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
read(byte[] r)----这个方法从输入流读取r.length长度的字节。返回读取的字节数。如果是文件结尾则返回-1。
//方式二
try {
InputStream in = new FileInputStream("1");
//read(byte[] bytes) 返回读取字节的个数,如果流中没有数据了,则返回-1
byte[] bytes = new byte[500];
int len = 0;
//保存read(byte[] bytes)读取的字节个数
while ((len = in.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, len)); //(new String)将Unicode码转换为字符串字符
}
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
close( )—>关闭输入流
2.2字节流OutputStream共用的方法
2.2.1输入流操作的步骤:
1、建立流。建立内存和数据源的数据通道
2、操作流。向数据源写入数据
3、关闭流。释放内存资源
Write(byte[] b)—>将b.length个字节从指定的字节数组写入此输出流
write(byte[] b , int off , int len)—>将指定字节数组中从偏移量off开始的了呢个字节写入此输出流
flush( )—>刷新流,将数据真正写入数据源
close( )—>关闭输入流
字节输出流也只能对字节数据进行写入。如果要操作字符数据,需要将字符数据转化为字节数据。
public static void main(String[] args) {
//1建立数据源输出传输到目的地的通道
try {
OutputStream out = new FileOutputStream("2.txt");
//对数据进行处理,写入流中
//wirte()本质上是把数据写入到缓存区
out.write("hello world".getBytes());
//强制把缓存区的数据写入到数据源中
out.flush();
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
2.3字符流Reader共用的方法
read()
//方法一
try {
Reader reader = new FileReader("1");
int n = 0 ;
while((n = reader.read()) != -1){
System.out.print((char)n);
}
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
//节点流:可以直接向数据量写数据/从数据源读数据
// 操作流(处理流) 基于节点流或操作流,来创建,
// 可以在数据传输途中,对数据加以操作
InputStream in = new FileInputStream("1.txt");
Reader reader = new InputStreamReader(in);
int n = 0;
while ((n = reader.read()) != -1){
System.out.print((char) n);
}
// 关闭流 先开启的后关闭
reader.close();
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
read(byte[] r)
//方法二
try {
Reader reader = new FileReader("1");
char[] chars=new char[100];
int n = 0;
while ( ( n=reader.read(chars)) != -1){
System.out.print(new String(chars,0,n));
}
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
//节点流:可以直接向数据量写数据/从数据源读数据
// 操作流(处理流) 基于节点流或操作流,来创建,
// 可以在数据传输途中,对数据加以操作
InputStream in = new FileInputStream("1.txt");
Reader reader = new InputStreamReader(in);
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();
} catch (IOException e) {
throw new RuntimeException(e);
}
readLine() 读取文档中一行数据,如果流中没有数据,返回null
try {
Reader reader = new FileReader("1");
BufferedReader reader1 = new BufferedReader(reader);
String i = null;
// readLine() 读取文档中一行数据,如果流中没有数据,返回null
// 其他方法 如read() read(char[] chars) 同其他字符流
while((i = reader1.readLine()) != null){
System.out.println(i);
}
reader1.close();
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
字符流用于操作字符数据,可以直接对字符数据进行读取和写入
2.4字符流Writer共用的方法
//方法一
try {
Writer writer = new FileWriter("1");
writer.write("好好学习,天天向上");
writer.flush();
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
FileWriter–> 向文件中写入字符信息
BufferWriter–>套接字符流,需要以另一个字符流作为基础,将缓冲区数据,写入数据源
//方法二
try {
Writer writer = new FileWriter("1");
BufferedWriter writer1 = new BufferedWriter(writer);
writer1.write("nihaonihaonihao");
writer1.flush();
writer1.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
2.5数据拷贝的方法:
public class CopyFile {
//源文件--->目标文件 数据拷贝
public static void main(String[] args) {
copyFile2("1","2.txt");
}
//方法一
public static void copyFile(String src,String dest){
try {
//得到字节输出流和输入流对象
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest);
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
out.flush();
out.close();
in.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
//方法二
public static void copyFile1(String src,String dest){
try {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest);
byte[] bytes=new byte[500];
int len = 0; //保存read(byte[] bytes)读取的字节个数
while ( ( len=in.read(bytes)) != -1){
out.write(bytes,0,len);
}
out.flush();
out.close();
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//方法三
public static void copyFile2(String src,String dest){
try {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest);
byte[] bytes=new byte[500];
int i = 0;
StringBuilder sb = new StringBuilder();
while ( ( i=in.read(bytes)) != -1){
sb.append(new String(bytes,0,i));
}
out.write(sb.toString().getBytes());
out.flush();
out.close();
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2.6关闭流
流操作后,需要关闭以释放内存资源。那么,如果操作完流以后,不关闭流,会发生什么情况呢?
如果操作完流以后,不关闭流,那么,在内存和数据源之间建立的数据通道不会消失,会浪费内存资源(除非程序结束,通道才会消失)。
PS:流关闭时,先开的流后关闭。
另外,写入流将数据写入数据源时,需要通过flush()刷新语句,才能将数据真正写入数据源。在流关闭时,会自动执行flush()刷新语句。所以,写入流在不关闭,也不刷新的情况下,有可能写不进数据。
由此可见,流的关闭很重要。在开发时,一般将流的关闭书写在finally语句块中。避免因为发生了异常,而没有对流进行关闭操作。
并且我们在调用流的close()方法前,应当判断流是否被成功的建立(流的变量是否为null,为空会出现空指针异常NullPointException)。
补充异常的处理:
事前处理—针对运行期异常,通过经验和代码的特性提前做好预备工作,比如,对对象进行调用前,判断是否为null,进行类型强转之前,instanceof判断类型;通过下标获取元素时,先判断下标是否越界。
事后处理—针对编译期异常,try-catch捕获 或者throw抛出,try-catch捕获后再创建一个异常抛出。
public static void copyFile(String src,String dest){
InputStream in = null;
OutputStream out = null;
try {
//得到字节输出流和输入流对象
in = new FileInputStream(src);
out = new FileOutputStream(dest);
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
out.flush();
} catch (IOException ex) {
throw new RuntimeException(ex);
}finally {
try {
if(out != null){ out.close();}
if(in != null){in.close();}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
try-with-resource (JDK7后出现)写法:去掉了finally块,更简洁,自动完成资源清理工作(去掉close()代码)。没有显示调用close()方法,但是编译期对上述代码进行编译后,会自动生成流关闭的close方法。
用法:
try(//流的声明创建语句){
//可能会抛异常的代码
}catch(异常类 变量){
//异常处理的代码
}
—>try后面多了个()—>把资源声明放入()—>去掉finally块**
try (InputStream in = new FileInputStream(src);
OutputStream out= new FileOutputStream(dest);
){
//得到字节输出流和输入流对象
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
out.flush();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
3.操作文件流
File类提供了定位本地文件系统、描述文件和目录的功能。是 java.io 包中引用实际磁盘文件的对象。File类只能操作文件、操作目录,无法改变文件内容。
File类构造方法:
public File(String pathname) 通过文件路径或目录路径,创建File文件对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mCIofeyc-1682258065091)(C:\Users\mx\AppData\Roaming\Typora\typora-user-images\image-20230418134622575.png)]
Delete( ) —> 删除当前文件、或空目录,不能删除非空目录
makdir( )—>只能创建一级目录;makdirs( )—>创建多级目录
public static void main(String[] args) {
File file = new File("./src/FileTest/1.txt");
System.out.println("绝对路径:" + file.getAbsolutePath());
//可以用来判断文件是否存在,存在就可以创建对象
System.out.println("文件或目录是否存在:" + file.exists());
//判断文件是否是文件
System.out.println("文件对象是否是文件:" + file.isFile());
//判断文件是否是目录
System.out.println("文件对象是否是目录:" + file.isDirectory());
//删除当前文件对象、空目录,不能删除非空目录
System.out.println("删除当前文件对象表示的文件或目录;删除空目录;不能删除非空目录:"+file.delete());
//得到目录里所有的文件对象File[] listFiles()
File file1 = new File("./src/FileTest");
File[] files = file1.listFiles();
for (File f : files) {
System.out.println(f.getAbsolutePath());
}
//创建一级目录 mkdir()用文件对象调用,文件对象一般为不存在的目录
File info = new File("info");
System.out.println("创建一个目录" + info.mkdir());
//创建多级目录 mkdirs()用文件对象调用,文件对象一般为不存在的目录
File info1 = new File("info1/test1/test2");
System.out.println("创建一个目录" + info1.mkdirs());
}
4.对象流
在对字符流进行读取和写入时,需要完成对象数据和字符数据的转换,需要进行大量的字符串拼接操作。
能不能直接完成对对象数据的操作,减化这样的数据转换过程呢?
对象流可以完成这样的需求,通过对象流,可以直接对对象数据进行操作。
4.1对象序列化
对象流在操作对象数据时,如何对对象进行传输呢?
在传输对象时,由于对象的数据庞大,无法直接传输。所以,在传输之前,先将对象打散成字节序列,以利于传输。这个过程,称为序列化过程。
对象----打散---->字节序列 序列化
在字节序列到达目的地以后,又需要将字节序列还原成对象。这个过程,称为反序列化过程。
字节序列----还原---->对象 反序列化
4.2如何实现对象序列化
但是,不是任何对象都能被打散成字节序列,在java中,所有需要实现对象序列化的对象,必须首先实现java.io.Serializable接口 。
java.io.Serializable接口用于标识类的对象可以被序列化。java.io.Serializable序列化接口是标识性接口,该接口中没有定义方法。
public class UserBean implements java.io.Serializable
如果对未实现序列化接口的对象进行传输,那么会抛出java.io.NotSerializableException异常
4.3对象流
在java语言中,我们可以将对象直接保存在硬盘上,此时,对象中的引用及相关状态就永久保存了。在需要的时候我们可以将硬盘上的拷贝重新读入内存并恢复成原对象。
4.3.1写入
在java.io包中,提供了ObjectOutputStream,对对象信息进行保存操作。
public final void writeObject(Object obj) 将对象序列化写入数据源
public static void writeObject(String fileSrc,List<UserBean> userBeans){
//打散成序列化 写入
try(OutputStream outputStream = new FileOutputStream(fileSrc);
ObjectOutputStream out = new ObjectOutputStream(outputStream);) {
out.writeObject(userBeans);
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
4.3.2读取
在java.io包中,提供了ObjectInputStream,对对象信息进行读取操作。
public final Object readObject() 将数据源的二进制数据,反序列化成对象。
public static List<UserBean> readObject(String fileSrc){
//反序列化 读取
try(InputStream inputStream = new FileInputStream(fileSrc);
ObjectInputStream in = new ObjectInputStream(inputStream);){
return (List<UserBean>)in.readObject();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
4.3.3transient关键字
在传输对象时,默认情况下,对象的每个属性值都会被传输,如果想对对象的属性进行选择性传输,可以使用transient关键字。
transient是用于属性的修饰符,表示在对象序列化时,被transient修饰的属性值,不被传输。
private transient int money;
putStream inputStream = new FileInputStream(fileSrc);
ObjectInputStream in = new ObjectInputStream(inputStream)😉{
return (List)in.readObject();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
#### 4.3.3transient关键字
在传输对象时,默认情况下,对象的每个属性值都会被传输,如果想对对象的属性进行选择性传输,可以使用transient关键字。
transient是用于属性的修饰符,表示在对象序列化时,被transient修饰的属性值,不被传输。
private transient int money;