IO框架
一、流的概念
概念:内存与存储设备之间传输数据的通道。
二、流的分类
按方向分类:
- 输入流:将<存储设备>中的内容读入到<内存>中
- 输出流:将<内存>中的内容读入到<存储设备>中
内存:内存是一种用于暂时存放[CPU]中的运算数据和外部储存器交换数据的随机储存器。
存储:一般可分为机械[硬盘]和[固态硬盘],是一种储存硬件,用于存放数据。
按单位进行划分:
- 字节流:以字节为单位,可以读写所有数据。
- 字符流:以字符为单位,只能读写文本数据。
按功能进行划分:
- 节点流:具有实际传输数据的读写功能。
- 过滤流:在节点流的基础上增强功能,比如缓冲流。
三、字节流
文件字节流
字节流的两个抽象父类:
- 字节输入流:
InputStream
这个抽象类是表示输入字节流的所有类的超类。
常用方法:
- 字节输出流:
OutputStream
这个抽象类是表示字节输出流的所有类的超类。 输出流接收输出字节并将其发送到某个接收器。
常用方法:
字节流的两个子类——文件字节流:
- 文件字节输入流:
FileInputStream
——读取文件
public class FileInputStream extends InputStream
从文件系统中的文件获取输入字节。 什么文件可用取决于主机环境。
FileInputStream
用于读取诸如图像数据的原始字节流。 要阅读字符串,请考虑使用 FileReader
。
构造方法:
常用方法:
public int read(byte[] b)
从流中读取 多个
字节,将读到的内容存入 b
数组,返回实际读到的 字节数
;如果达到文件的尾部,则返回 -1
第一种读取方式:单个字节读取——效率不高!
【参考代码】
import java.io.FileInputStream; import java.io.FileNotFoundException; public class InputStreamTest { public static void main(String[] args) throws Exception { // 1.创建FileInputStream,并指定文件路径 FileInputStream fis = new FileInputStream("d:\\aaa.txt"); // 2.读取文件fis.read():一个一个的读 int data = 0; while ((data = fis.read()) != -1){ System.out.println(); } // 3.读完之后,关闭流 fis.close(); } }
第二种读取方式:一次读取多个字节,多一个字节数组!
import java.io.FileInputStream; import java.io.FileNotFoundException; public class InputStreamTest { public static void main(String[] args) throws Exception { // 1.创建FileInputStream,并指定文件路径 FileInputStream fis = new FileInputStream("d:\\aaa.txt"); // 2.读取文件fis.read():一个一个的读 // int data = 0; // while ((data = fis.read()) != -1){ // System.out.println(); // } // 2.1一次读取多个字节 byte[] buf = new byte[1024]; int count = 0; while ((count = fis.read(buf)) != -1){ System.out.println(new String(buf, 0, count)); // abcdefg } // 3.读完之后,关闭流 fis.close(); } }
- 文件字节输出流:
FileOutputStream
——写入文件
public class FilterOutputStream extends OutputStream
这个类是过滤输出流的所有类的超类。 这些流位于已经存在的输出流( 底层 输出流) 之上 ,它使用它作为数据的基本接收器,但是可能沿着数据方向转换或提供附加功能。
构造方法:
常用方法:
public int write(byte[] b)
一次写多个字节,将b数组中的所有字节,写入输出流中!
【参考代码】
import java.io.FileNotFoundException; import java.io.FileOutputStream; public class FileOutPutStreamTest { public static void main(String[] args) throws Exception { // 创建文件输出流 FileOutputStream fos = new FileOutputStream("d:\\bbb.txt",true); // 加true后新的字节不会覆盖原字节 // 读入多个字节 String str = "abcdef"; fos.write(str.getBytes()); // 关闭流 fos.close(); System.out.println("加载完毕!"); } }
注:如果是文本文件最好用字符流
案例:使用文件字节流进行文件复制
复制文件:利用文件字节流,一边读取,一边写入!
注:使用字节流可以复制任意文件,而字符流却不行!
【参考代码】
import java.io.FileInputStream; import java.io.FileOutputStream; public class FileCopyDemon { public static void main(String[] args) throws Exception{ // 创建文件输入流 FileInputStream fis = new FileInputStream("d:\\bbb.txt"); // 创建文件输出流 FileOutputStream fos = new FileOutputStream("d:\\ccc.txt"); //复制文件:一边读取,一边写入 byte[] buf = new byte[1024]; int count = 0;// count实际读取的个数 while ((count = fis.read(buf)) != -1){ fos.write(buf, 0, count); } fis.close(); fos.close(); System.out.println("复制完毕!"); } }
文件字节缓冲流
缓冲流: BufferedInputStream/BufferedOutputStream
。提高IO效率,减少访问磁盘的次数;数据存储在缓冲区, flush
是将缓冲区的内容写入文件中,也可以直接 close
。
- 字节输入缓冲流:
BufferedInputStream
——快速读取文件
BufferedInputStream
为另一个输入流添加了功能,即缓冲输入和支持 mark
和 reset
方法的功能。 当创建 BufferedInputStream
时,将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。 mark
操作会记住输入流中的一点,并且 reset
操作会导致从最近的 mark
操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。
构造方法:
常用方法:
【参考代码】
import java.io.BufferedInputStream; import java.io.FileInputStream; public class BufferedInputStreamDemon { public static void main(String[] args) throws Exception{ FileInputStream fis = new FileInputStream("d:\\aaa.txt"); //1. 创建字节缓冲输入流 BufferedInputStream bis = new BufferedInputStream(fis); // 维护字节流,从缓冲区读取字节,加快效率 //2. 读取 int data = 0; while((data = bis.read()) != -1){ System.out.println((char) data); } //3. 关闭缓冲流 bis.close(); } }
- 字节输出缓冲流:
BufferedOutputStream
——快速写入文件
该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,不必为写入的每个字节导致底层系统的调用。
构造方法:
常用方法:
【参考代码】
import java.io.BufferedOutputStream; import java.io.FileOutputStream; public class BufferedOutputStreamDemon { public static void main(String[] args) throws Exception{ FileOutputStream fos = new FileOutputStream("d:\\buff.txt"); //1. 创建输出缓冲流 BufferedOutputStream bos = new BufferedOutputStream(fos); //2. 写入文件 String str = "hello"; for(int i = 0; i <= 5; i ++){ bos.write(str.getBytes()); // 写入8k缓冲区 bos.flush(); // 刷新到硬盘 } //3. 关闭流(内部调用) fos.close(flush方法); } }
四、编码方式
注:当编码方式和解码方式不一致时,就会出现乱码!
五、字符流
引入:3个字节相当于一个字符,当我们要读取汉字(字符)时,如果用字节流来读取的话,他是一个一个字节读取的,最终的结果是字节,而不是我们想要的字符了!
字符流的两个父类(抽象类):
-
Reader
:字符输入流常用方法:
public int read() {} public int read(char[] c) {} public int read(char[] b, int off, int len) {}
-
Write
:字符输出流常用方法:
public void write(int n) {} public void write(String str) {} public void write(char[] c) {}
文件字符流
FileReader
——文件字符输入流
阅读字符文件的便利课。 该类的构造函数假定默认字符编码和默认字节缓冲区大小是适当的。 要自己指定这些值,请在FileInputStream上构造一个InputStreamReader。
FileReader
是用于读取字符流。 要读取原始字节流,请考虑使用 FileInputStream
。
public class FileReader extends InputStreamReader
构造方法:
常用方法:继承父类!
public int read(char[] c); 从流中读取多个字符,讲读到内容存入c数组,返回实际读到的字符数;如果文件达到尾部,则返回-1.
【参考代码】
import java.io.FileReader; public class FileReaderDemon { public static void main(String[] args) throws Exception{ //1. 创建FileReader 文件字符输入流 FileReader fr = new FileReader("d:\\hello.txt"); //2. 读取 //2.1 单个读取 // int data = 0; // while ((data = fr.read()) != -1){ // 读取一个字符! // System.out.println((char)data); // } char[] buf = new char[1024]; int count = 0; while((count = fr.read(buf)) != -1){ System.out.println(new String(buf, 0, count)); } //3. 关闭 fr.close(); } }
FileWriter
——文件字符输出流
public void write(String str); 一次写入多个字符,将b数组中所有字符,写入输出流;
【参考代码】
import java.io.FileWriter; public class FileWriterDemon { public static void main(String[] args) throws Exception{ //1. 创建FileWriter对象 FileWriter fw = new FileWriter("d:\\write.txt"); //2. 写入字符 String str = "防不胜防"; for(int i = 0; i < str.length(); i ++){ fw.write(str); fw.flush(); } //3. 关闭 fw.close(); System.out.println("执行完毕"); } }
案例:文件字符流实现文本文件复制
注: FileReader、FileWriter
只能复制文本文件,不能复制图片或者二进制文件!—— 文本文件有字符编码!
【参考代码】
import java.io.FileReader; import java.io.FileWriter; public class CopyDemon { public static void main(String[] args) throws Exception{ //1. 创建FileReader FileWriter 对象 FileReader fr = new FileReader("d:\\write.txt"); FileWriter fw = new FileWriter("d:\\write2.txt"); //2. 读写 int data = 0; while((data = fr.read()) != -1){ fw.write(data); fw.flush(); } //3. 关闭 fr.close(); fw.close(); System.out.println("复制完毕!"); } }
字符缓冲流
字符缓冲流: BufferedReader/BufferedWriter
(1)高效读写
(2)支持输入换行
(3)可一次写一行,读一行。
BufferedReader
——字符缓冲输入流
从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。
可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途。
通常,由读取器做出的每个读取请求将引起对底层字符或字节流的相应读取请求。 因此,建议将BufferedReader包装在其read()操 作可能昂贵的读取器上,例如FileReader
BufferedReader in = new BufferedReader(new FileReader("foo.in"));
将缓冲指定文件的输入。 没有缓冲,每次调用read()或readLine()可能会导致从文件中读取字节,转换成字符,然后返回,这可能非常低效。
构造方法:
常用方法:
【参考代码】
`` import java.io.BufferedReader; import java.io.FileReader; /** * 字符缓冲流读取文件 */ public class BufferedReaderDemon { public static void main(String[] args) throws Exception{ //1. 创建缓冲流 FileReader fr = new FileReader("d:\\write.txt"); BufferedReader br = new BufferedReader(fr); //2. 读取 //2.1 第一种读取方式 // char[] buf = new char[1024]; // int count = 0; // while ((count = br.read(buf)) != -1){ // System.out.println(new String(buf, 0, count)); // } //2.2 第二种读取方式。 一行一行的读取 String line = null; while ((line = br.readLine()) != null){ System.out.println(line); } //3. 关闭 br.close(); } }
BufferedWriter
——字符缓冲输出流(写入字符)
将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。
可以指定缓冲区大小,或者可以接受默认大小。 默认值足够大,可用于大多数用途。
提供了一个newLine()方法,它使用平台自己的系统属性 line.separator
定义的行分隔符概念。 并非所有平台都使用换行符('\ n')来终止行。 因此,调用此方法来终止每个输出行,因此优选直接写入换行符。
构造方法:
常用方法:
【参考代码】
import java.io.BufferedWriter; import java.io.FileWriter; public class BufferedWriterDemon { public static void main(String[] args) throws Exception{ //1. 创建BufferedWriter对象 FileWriter fw = new FileWriter("d:\\buffer.txt"); BufferedWriter bw = new BufferedWriter(fw); //2. 写入 for (int i = 0; i < 5; i ++){ bw.write("好好学习吧!"); bw.newLine();// 换行! bw.flush(); } //3. 关闭 bw.close(); } }
打印流
PrintWriter
——打印流
将对象的格式表示打印到文本输出流。 这个类实现了全部在发现 print种
方法 PrintStream。 它不包含用于编写原始字节的方法,程序应使用未编码的字节流。
不像类,如果启用自动刷新,它只会在调用的 println,printf,
或 format
方法来完成,而不是当一个换行符恰好是输出。 这些方法使用平台自己的行分隔符而不是换行符。
转换流
转换流: InputStreamReader/OutputStreamWriter
(1)可以将字节流转为字符流
(2)可设置字符的编码方式
转换流的使用:
InputStreamReader读取文件
【参考代码】
import java.io.FileInputStream; import java.io.InputStreamReader; public class ZhuanHuanLiuTest { public static void main(String[] args) throws Exception{ //1. 创建InputStreamReader对象 FileInputStream fis = new FileInputStream("d:\\write.txt"); InputStreamReader isr = new InputStreamReader(fis, "utf-8"); // 转换流设置编码方式 //2.读取文件 int data = 0; while ((data = isr.read()) != -1){ System.out.println((char) data); } //3. 关闭 isr.close(); } }
OutputStreamWriter——写入文件
【参考代码】
import java.io.FileOutputStream; import java.io.OutputStreamWriter; public class ZhuanHuanLiuTest { public static void main(String[] args) throws Exception{ //1. 创建OutputStreamWriter对象 FileOutputStream fos = new FileOutputStream("d:\\info.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk"); // 转换流设置编码方式 //2. 写入 for(int i = 0; i < 5; i ++){ osw.write("我爱学习\r\n"); osw.flush(); } //3. 关闭 osw.close(); } }
六、对象流
对象流: ObjectInputStream/ObjectOutputStream
。
(1)增强了缓冲区功能
(2)增强了读取8种基本数据类型和字符串功能
(3)增强了读写对象的功能:
readObject() 从流中读取一个对象(反序列化)
writeObject(Object obj) 向流中写入一个对象(序列化)
使用流传输对象的过程称为序列化,和反序列化。
- 对象输出流:
ObjectOutputStream
——序列化(写入对象)
ObjectOutputStream将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以 通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。
构造方法:
常用方法:
【参考代码】
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectOutputStreamDemon { /** * 使用ObjectOutputStream实现对象的序列化————读入对象 * 要求:序列化类必须实现接口 */ public static void main(String[] args) throws Exception{ //1. 创建对象流 FileOutputStream fos = new FileOutputStream("d:\\stu.bin"); ObjectOutputStream oos = new ObjectOutputStream(fos); //2. 序列化(写入操作) Student student = new Student("张三",19); oos.writeObject(student); //3. 关闭(自带flush()方法了) oos.close(); System.out.println("序列化完毕"); } } class Student implements Serializable { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
使用ObjectOutputStream实现对象的序列化————写入对象
要求:序列化类必须实现接口
- 对象输入流:
ObjectInputStream
——反序列化(读取重构成对象)
ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象。
构造方法:
常用方法:
【参考代码】
import java.io.*; public class ObjectInputStreamDemon { public static void main(String[] args) throws Exception{ //1. 创建对象流 FileInputStream fis = new FileInputStream("d:\\stu.bin"); ObjectInputStream ois = new ObjectInputStream(fis); //2. 读取文件(反序列化) Student st = (Student) ois.readObject(); //3. 关闭 ois.close(); System.out.println(st.toString());// Student{name='张三', age=19} } }
序列化和反序列化注意事项:
(1)序列化类必须实现 Serializable
接口
(2)序列化类中对象属性要求实现Serializable接口
(3)序列化版本号ID,保证序列化的类和反序列化的类是同一个类
(4)使用transient(瞬间的)修饰属性,这个属性不能序列化
(5)静态属性不能序列化
(6)序列化多个对象,可以借助集合
七、File类
概念:代表物理磁盘中的一个 文件 或者 文件夹(目录) 。
常用方法:
文件操作
(1)分隔符:
; \
(2)文件操作:
-
创建文件
createNewFile()
boolean createNewFile()
当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。if(!file.exists()){ // 如果文件不存在则创建 boolean b = file.createNewFile(); System.out.println("创建结果" + b); }
-
删除文件
直接删除:
delete()
boolean delete()
删除由此抽象路径名表示的文件或目录JVM退出时删除:
file.deleteOnExit(); Thread.sleep(5000); // 休眠五秒
-
获取文件信息
-
getAbsolutePath()
:获取到文件的绝对路径 -
getPath()
:获取到文件的路径 -
getName()
:获取文件的名称 -
getParent()
:获取文件的父级目录 -
length()
:获取文件的长度 -
lastModified()
:获取文件的创建时间System.out.println("文件创建时间:" + new Date(file.lastModified()));
-
-
判断
canWrite() isFile() isHidden()
【参考代码】
import java.io.File; import java.util.Date; public class FileTest { public static void main(String[] args) throws Exception{ fileOpe(); } /** * 文件操作 */ public static void fileOpe() throws Exception{ //1. 创建文件 File file = new File("d:\\file.txt"); // 只是创建了一个文件对象,此时在d盘下并没有该文件 if(!file.exists()){ // 如果文件不存在则创建 boolean b = file.createNewFile(); System.out.println("创建结果" + b); } //2. 删除文件 //2.1 直接删除 // System.out.println("删除结果" + file.delete()); // //2.2 JVM退出时删除 // file.deleteOnExit(); // Thread.sleep(5000); // 休眠五秒 //3. 获取文件信息 System.out.println("获取文件绝对路径" + file.getAbsolutePath()); // 获取文件绝对路径d:\file.txt System.out.println("获取路径" + file.getPath()); System.out.println("获取文件名称" + file.getName()); System.out.println("获取文件父目录" + file.getParent()); System.out.println("获取文件长度" + file.length()); System.out.println("文件创建时间:" + new Date(file.lastModified())); //4. 判断 System.out.println("是否可写" + file.canWrite()); System.out.println("是否是文件" + file.isFile()); System.out.println("是否隐藏" + file.isHidden()); } }
文件夹操作
-
创建文件夹
boolean
mkdir()
创建由此抽象路径名命名的目录。boolean
mkdirs()
创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。File dir = new File("d:\\aaa\\bbb\\ccc"); if(! dir.exists()){ dir.mkdir();// 只能单级目录 System.out.println("创建结果:" + dir.mkdirs()); //mkdirs();//可以创建多级目录 }
-
删除文件夹
直接删除:
delete()
——只能删除空目录JVM删除:
file.deleteOnExit();Thread.sleep(5000);
// 休眠五秒 -
获取文件夹信息
getAbsolutePath() getPath() getName() getParent() lastModified()
-
判断
isDirectory() isHidden()
-
遍历文件
(1)
list()
//5. 遍历文件夹 File dir2 = new File("e:\\picture"); String[] files = dir2.list(); for(String str: files){ System.out.println(str); }
(2)
listFiles()
//5. 遍历文件夹 File dir2 = new File("e:\\picture"); File[] files = dir2.listFiles(); // 文件数组 for(File file: files){ System.out.println(file.getName()); }
FileFilter接口
当调用File类中的 listFiles()
方法时,支持传入 FileFilter
接口接口实现类,对获取文件进行过滤,只有满足条件的文件才可以出现在 listFiles()
方法的返回值中。
【参考代码】
File dir2 = new File("e:\\picture"); // 过滤:过滤出满足条件的文件 File[] file2 = dir2.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { if(pathname.getName().endsWith(".png")){ // 只要.png结尾的图片 return true; } return false; } }); // 遍历输出 for(File file : file2){ System.out.println(file.getName()); }
递归遍历与删除
递归遍历文件夹:
遍历拿到 dir.listFiles()
路径的路径数组,数组不为空且有文件的情况下,如果是文件夹递归则进去,直到不是文件夹,然后输出文件,否则输出当前目录下的文件!
【参考代码】
import java.io.File; public class ListDemon { public static void main(String[] args) { listDir(new File("e:\\aaa")); } //递归遍历文件夹 public static void listDir(File dir){ File[] files = dir.listFiles();// 得到所有得子文件与子文件夹 System.out.println("路径:"+ dir.getAbsolutePath()); if(files != null && files.length > 0){ for(File file : files){ if(file.isDirectory()){// 判断是否为文件夹 listDir(file); // 如果是文件夹递归进去,直到不是文件夹 }else{// 不是文件夹 则输出 System.out.println(file.getAbsolutePath()); } } } } }
我电脑E盘下的aaa文件如下图:
递归删除文件夹:
我们直到 delete()
方法只能删除空目录。如果目录里边有内容, delete()
方法是无法删除的,即我们想用 delete()
方法删除上述aaa文件是行不通的!
为此,我们得先将 aaa
文件中所有内容给删除之后,才能将 aaa
文件删除掉! aaa
里边有 bbb
文件夹,为此得先把它里边得内容先删掉, bbb
里边有 ccc
文件夹为此得先把它里边得内容先删掉........然后再逐步回退删除文件夹!
【参考代码】
import java.io.File; public class ListDemon { public static void main(String[] args) { deleteDir(new File("e:\\aaa")); } // 递归删除文件夹 public static void deleteDir(File dir){ File[] files = dir.listFiles(); if(files != null && files.length > 0){ for(File file : files){ if(file.isDirectory()){ deleteDir(file); // 递归 }else { // 删除文件 System.out.println(file.getAbsolutePath() + "删除:" + file.delete()); } } } // 删除文件夹 System.out.println(dir.getAbsolutePath() + "删除:" + dir.delete()); } }
图示:
Properties
Properties:属性集合
public class Properties extends Hashtable<Object,Object>
Properties
类表示一组持久的属性。 Properties
可以保存到流中或从流中加载。 属性列表中的每个键及其对应的值都是一个字符串。
属性列表可以包含另一个属性列表作为其“默认值”; 如果在原始属性列表中找不到属性键,则会搜索此第二个属性列表。
特点:
- 存储属性名和属性值
- 属性名和属性值都是字符串类型
- 没有泛型
- 和流有关
String | getProperty(String key) 使用此属性列表中指定的键搜索属性,得到对应得value。 |
---|---|
Object | setProperty(String key, String value) 同 Hashtable 方法 put 。 |
void | list(PrintStream out) 将此属性列表打印到指定的输出流。 |
void | load(InputStream inStream) 从输入字节流读取属性列表(键和元素对)。 |
void | store(OutputStream out, String comments) 将此属性列表(键和元素对)写入此 Properties 表中,以适合于使用方法加载到 Properties 表中的格式输出流。 |
后面学习的JDBC连接数据会用得到 .properties
文件!
八、总结
注:如果文章有任何错误或不足,请各位大佬尽情指出,评论留言留下您宝贵的建议!如果这篇文章对你有些许帮助,希望可爱亲切的您点个赞推荐一手,非常感谢啦