(java) IO流
学习IO流之前,我们需要先认识file对象,帮助我们更好的使用IO流
1.1 file
作用:关联硬盘上的文件
写法:
- File(String path); (推荐)
- File(String parent, String child); //由父级路径,再子级路径拼接而成
- File(File parent, String child);
路径的写法:
- 绝对路径: 从盘符开始写!!!
- 相对路径: 不从盘符开始, 默认从项目的根目录开始关联
//小案例
//绝对路径
File file = new File("d:/a.txt");
File file2 = new File("b.txt");//直接找本项目之的a.txt文件
//相对路径
File file3 = new File("d:/");
File file4 = new File(file3,"a.txt");
String str = "d:/";
File file5 = new File(str,"a.txt");
//补充:对于书写的斜杠,我们可以使用/和\
//对于/来说,linux, mac, window都使用
//对于\来说,只能适应Windows,且因为java中转义符也是\所以需要输入两个\\才能表示一个\
1.2 File 方法:
判断方法:
- exist 文件是否存在
- isFile 是否为文件
- isDirectory 是否为文件夹
获取方法:
-
getName 获取文件名
-
getAbsolutePath 获取绝对路径
-
length 获取文件字节长度
-
lastModified 获取最后一次修改时间的毫秒值
创建方法:
- createdNewFile();
- 只能创建文件,而非文件夹,必须保证文件所在文件夹存在,否则创建失败,并且报错IOException
- 文件存在,则会创建失败
- mkdir 创建一级目录
- mkdirs 创建多级目录
删除方法:
- delete() 删除文件或者文件夹, 但是文件夹必须没有内容才能删除, 否则不能删, 而且删除不走回收站
遍历方法:
- File[] listFile 获取文件夹的一级子目录,返回值为数组
遍历注意:
- 通常使用文件夹才使用该遍历方法
- 如果是文件非要使用这个方法会得到一个null数组
- 没有权限的文件夹我们使用该方法, 得到也是null数组
- 文件夹里面没有内容使用这个方法的, 会得到一个长度为0的数组, 预防空指针异常()
//练习
File file2 = new File("a.txt");//相对路径
Date date1 = new Date(file2.lastModified());
System.out.println(date1);//Thu Jan 02 22:08:22 CST 2025
//文件创建与删除
File file1 = new File("HomeWork0102\\homeWork\\src\\cn\\itcast\\practice\\");
File file2 = new File(file1,"a.txt");
boolean isCreate = file2.createNewFile();//创建文件
System.out.println(isCreate);
boolean isDelete = file2.delete();//删除文件
System.out.println(isDelete);
//文件夹的多级创建
File file3 = new File("a/b/c");//表示在当前项目下,创建a,b,c三层文件夹
boolean isCreate = file3.mkdirs();
System.out.println(isCreate);
//文件夹需要保证其为空才能使用delete删除
//进阶-遍历某个盘,输出我们想要的文件后缀 的文件
public static void main(String[] args) {
System.out.println("请输入你需要查找的文件后缀,后缀之间用逗号隔开");
Scanner sc = new Scanner(System.in);
String str = sc.next();
File file = new File("d:/");
// File[] files = File.listRoots();老师使用该方法获取整个电脑的盘根目录
String[] splitStrs = str.split(",");
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, splitStrs);
showFiles(list1, file);//投入查询内容和文件起始位置
}
public static void showFiles(ArrayList<String> list, File file) {
File[] files = file.listFiles();
if (files != null) { //排除全选和文件,只寻找文件夹
for (File file1 : files) {
//切文件,拿到文件的后缀名
int index = file1.getName().lastIndexOf(".");
if (index > 0) {
String substring = file1.getName().substring(index);
if (list.contains(substring)) {
System.out.println(file1.getAbsoluteFile());
}
}
if (file1.isDirectory()) {
showFiles(list, file1);
} // 如果遇到下一级文件夹,进行递归操作
}
}
}
//再补充几个案例,更好的帮助我们理解什么是递归
//案例1 递归删除文件夹
public static void deleteDirectory(File file) {
File[] files = file.listFiles();
if (files != null) {//排除权限和文件
for (File file1 : files) {
if (file1.isFile()) {
file1.delete();
} else {
deleteDirectory(file1);
}
}
}
file.delete();//循环最后,删除文件夹
}//方法
没有IO流之前,我们的数据全部存储了内存中,当程序关闭,数据消失,如果部分数据需要保留,就存放到硬盘之中,我们的JAVA想要访问硬盘,就需要一系列的本地方法。
2. IO流
作用:在硬盘上储存文件
从内容上来分,分为字符流和字节流,每一种流又分为输出流和输入流,此时的输入和输出的角度是站在内存的角度来考虑的,内存向硬盘输入数据叫输出流,从硬盘读取数据叫输入流。
2.1 字节流
(一)FileOutputStream输出流构造方法:
- FileOutputStream(String name)/FileOutputStream(String name, boolean append)
- FileOutputStream(File file)/FileOutputStream(File file, boolean append)
起始底层也是把String包装为File类型进行创建,后面的布尔值表示是续写,默认为false
功能:
- 创建一个输出流对象
- 如果关联的文件不存在则会创建, 而且底层采用是createNewFile帮你创建
- 如果文件存在,则清空文件内容
- write(xxx) 能写单个元素, byte数组, byte数组的一部分
- close(); 关闭资源,如果不关闭资源,就无法对文件进行更改
(二)FileInputStream输入流构造方法
-
FileInputStream(String path);
-
FileInputStream(File path);
-
read(); 可以读一个字节,读一个byte数组,以及byte数组的一部分
补充:如果一个一个读,读不到字节就返回-1,如果一个byte数组一个数组的读,返回的值是所读到的位数,如果没有读到内容,就会返回-1。
-
close()关闭资源
//对于字节流来说,最经典的还是利用自创小数组来复制文件
public static void main(String[] args) throws IOException {
//利用自创小数组来复制文件
FileOutputStream fos = new FileOutputStream("rehearsal\\src\\cn\\itcast\\practice\\a.txt");
FileInputStream fis = new FileInputStream("rehearsal\\src\\cn\\itcast\\practice\\b.txt");
int len;
byte[] arr = new byte[8 * 1024];
while ((len = fis.read(arr)) != -1) {
fos.write(arr, 0, len);
};
fos.close();
fis.close();
}//main
//补充:此处如果选择一个字节一个字节读取,则更改读取和输入的逻辑即可
int by;
while ((by = fis.read()) != -1) {
fos.write(by);
};//替换核心内容即可
2.2 缓冲字节流(非重点)
BufferedInputStream 字节缓冲输入流 语法:BufferedInputStream(InputStream in);
BufferedOutputStream字节缓冲输出流 语法:BufferedOutputStream(OutputStream out);
其的read和write跟字节输入输出流完全一样。
常见问题:
(1) 字节流读写就是字节, 之所以能看到字符是因为文本编辑器进行解码的动作!!!
(4) IO只能操作文件不能操作文件夹, 否则就会出现拒绝访问错误!!!
为什么不用字节缓冲流?
因为其底层还是在使用字节输入输出流,只不过通过一个byte数组读取和存取数据,减少了与硬盘的交互次数,但是字节缓冲流的读取速度还是比不上使用小数组读取数据的字节流,因为增加了数据的倒手次数。
//优秀案例:文件加密(涉及后面一点点^ 异或运算的内容)
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("homeWork\\src\\cn\\itcast\\practice\\c.txt");//写入
FileInputStream fis = new FileInputStream("homeWork\\src\\cn\\itcast\\practice\\a.txt");//读取
int password = 10;
int by;
while ((by = fis.read()) != -1) {
fos.write(by ^ password);
}
fis.close();
fos.close();
}
//如果为了提高效率,可以使用自定义小数组进行包装,其核心代码快
int password = 110;
int len;
byte[] arr = new byte[8 * 1024];
while (( len = fis.read(arr)) != -1){
for (int i = 0; i < arr.length; i++) {
arr[i] *= arr[i] ^ password;
}
fos.write(arr,0,len);
}
2.3 字符流
硬盘只认识字节, 不认识字符, 我的字符流的所有写的功能都是写到了底层的缓冲字符数组中了, 并没有直接写到硬盘,
(一)Reader(抽象类) —> FileReader
构造:
-
FileReader(File file)
-
FileReader(String fileName)
方法:
- int read(); 一次读一个字符
- int read(char[] arr) 一次读多个字符, 返回读到的有效字符个数
(二)Writer(抽象类) —> FileWriter
构造:
-
FileWriter(File file)
-
FileWriter(String fileName)
-
FileWriter(File file, boolean append)
-
FileWriter(String fileName, boolean append)
功能:
- write(xxx); //写单个字符, 多个字符, 多个字符的一部分,字符串, 字符串的一部分
- close(); //关闭资源,并且简化缓冲区中是否有内容, 如果有则先刷新在关闭!!!
- flush(); //我们需要手动调用flush方法,这个方法可以对照码表将字符编码成字节, 然后才能保存到硬盘
2.4 缓冲字符流(重点)
BufferedReader(Reader reader);
方法:
int read();
int read(char[] arr);
String readLine(); //一次读一行 ,此时如果读完最后一行,返回值为null
BufferedWriter(Writer writer);
方法:
write(xxx) 单个字符, 多个字符, 多个字符的一部分, 字符串, 字符串一部分
newLine(); 可以根据操作系统写换行符:【不同系统的换行符不同window: \r\nlinux: \nmac: \r】
//
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\javaprojects\\HomeWork0102\\homeWork\\src\\cn\\itcast\\practice\\c.txt"));
bw.write("这是一句诗歌");
bw.newLine();
bw.write("我又写了一句诗歌");
bw.newLine();
bw.write("再多写几句诗歌");
bw.close();
BufferedReader br = new BufferedReader(new FileReader("D:\\javaprojects\\HomeWork0102\\homeWork\\src\\cn\\itcast\\practice\\c.txt"));
String str;
while ( (str =br.readLine()) != null){
System.out.println(str);
}
2.5 补充:释放资源的异常捕获
对于我们一般的情况之下,遇到异常是直接抛出,不影响我们进一步书写代码,但是如果非要处理,我们可能需要自己try catch捕获,对于输入流/输出流来说,其无论报错与否都希望其正常释放资源,所有引入finally,对于输入和输出来说,可能还得分别包裹其再书写try catch逻辑,我们JDK1.8提供了新语法,可以将需要释放资源的语法放到try后的小括号中,需要确定其有.close()方法,此语法无需后续书写释放资源。
//jdk1.7及以前
private static void demo1() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("C:\\Users\\Lenovo\\Desktop\\颈椎操.flv");
fos = new FileOutputStream("day12_io/a.flv");
byte[] arr = new byte[1024 * 8];
long start = System.currentTimeMillis();
int len;
while ((len = fis.read(arr)) != -1) {
fos.write(arr, 0, len);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//释放资源
try {
if (fis != null) fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (fos != null) fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
//新语法
public static void main(String[] args) {
try (
FileInputStream fis = new FileInputStream("C:\\Users\\Lenovo\\Desktop\\颈椎操.flv");
FileOutputStream fos = new FileOutputStream("day12_io/a.flv");
) {
byte[] arr = new byte[1024 * 8];
long start = System.currentTimeMillis();
int len;
while ((len = fis.read(arr)) != -1) {
fos.write(arr, 0, len);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3. 其他流(了解)
3.1 转换流
转换流:(才是真正的字符流, 而我们学习的FileReader和FileWriter其实是字符流的便捷流)
优势: 指定编码表进行读写操作!!!,但是已经淘汰了
世间没有字符流, 因为磁盘只认识字节, 所谓的字符流 = 字节流 + 码表
InputStreamReader 构造:
InputStreamReader(InputStream in, String charsetName);
OutputStreamWriter 构造:
OutputStreamWriter(OutputStream out,String charsetName )
剩下的功能和FileReader和FileWriter 一模一样
3.2 打印流
本质就是一条输出流
PrintStream, PrintWriter
构造:
PrintStream(String path);
方法:
write(xxx); 写单个字节, 写多个字节, 写多个字节的一部分
println(xxx); 原样写东西,并换行
print(xxx); 原样写东西
close(); 释放资源
3.3 数据流
优势:可以将多个数据之间使用一个特殊的分隔符存放, 将数据一帧一帧存放, 一帧一帧的读取!!!
DataInputStream: 数据输入流
构造: DataInputStream(InputStream in);
功能:
readXxx(); 将数据一帧一帧的读取出来
DataOutputStream: 数据输出流
构造: DataOutputStream(OutputStream out);
功能:
writeXxx(xxx); 将数据一帧一帧写出去
3.4 对象流:(序列化流)
对象输入流(反序列化流)
ObjectInputStream(InputStream in);
对象输出流(序列化流)
ObjectOutputStream(OutputStream out);
补充概念:
什么序列化: 将对象从内存中保存磁盘的过程我们称为序列化
什么是反序列化: 将磁盘中的数据恢复到内存的过程我们称为反序列化
注意事项:
对象想要保存到硬盘中必须实现Serializable接口, 一旦实现这个接口之后, 默认会生成一个序列化ID,这个id默认跟你类中组成部分算出来
构造, 成员变量,成员方法, 内部类, 代码块, 但是修改了任何的内容, 这个序列化id就会变化, 如果反序列化的时候你的类中的当前的id跟你当时保存的时候id不一致就会报错,检测到你的类的内容改变了!!!
我们可以让这个id跟类的组成部分没关系即可!!!,不要让系统生成, 你手动给出序列化id即可