一、IO流
1.1 File类
1.1.1 File类对象的含义
java.io.File类是文件和目录路径名的抽象表示形式。
- File类的对象是用于表示一个文件或文件夹
- 想要表示一个文件或文件夹,需要通过描述它的路径名来创建它的对象。例如:d:\1.txt
- File类的对象代表的文件或文件夹不一定真实存在
1.1.2 File类的常用方法
1、创建、删除、重命名
package com.mytest.io;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
public class TestFile {
@Test
public void test1(){
File f = new File("d:\\1.txt");
//这里的f对象,想要代表 d:\1.txt 这个文件
//这个文件此时不存在,我们可以将它创建出来
try {
f.createNewFile();//在磁盘空间对应位置创建一个文件
//创建这个文件可能失败(1)没有D盘(2)没有权限(3)磁盘空间已满 ....
//如果创建不成功,会报异常
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void test2(){
File f =new File("d:\\test");//代表一个文件夹
f.mkdir();//在磁盘空间对应位置创建一个文件夹
//如果创建不成功,不会报异常
}
@Test
public void test3(){
File f = new File("d:\\1.txt");
//从磁盘空间对应位置删除这个文件
f.delete();
}
@Test
public void test4(){
File f =new File("d:\\test");
//从磁盘空间对应位置删除这个文件夹
f.delete();//空文件夹可以删除
}
@Test
public void test6(){
File f =new File("d:\\temp");
//从磁盘空间对应位置删除这个文件夹
f.delete();//非空文件夹不可以这样删除
}
@Test
public void test7(){
File f =new File("d:\\test");
File f2 = new File("d:\\测试");
f.renameTo(f2);//重命名文件夹名
}
@Test
public void test8(){
File f = new File("d:\\1.txt");
File f2 = new File("d:\\2.txt");
f.renameTo(f2);//重命名文件名
}
}
2、查看文件或文件夹的信息
package com.mytest.io;
import org.junit.Test;
import java.io.File;
import java.util.Date;
public class TestFile2 {
@Test
public void test1(){
File f = new File("d:\\2.txt");//文件存在
System.out.println("文件名:" + f.getName());
System.out.println("文件大小:" + f.length());
System.out.println("文件的最后修改时间:" + f.lastModified());//距离1970-1-1 8:0:0的毫秒值
Date d = new Date(f.lastModified());
System.out.println("文件的最后修改时间:" + d);//Mon Dec 23 11:47:00 CST 2024
System.out.println("f是一个文件吗?" + f.isFile());
System.out.println("f是一个文件夹吗?" + f.isDirectory());
System.out.println("f是隐藏的文件或文件夹吗?" + f.isHidden());
System.out.println("f是存在的吗?" + f.exists());
System.out.println("f是可读的吗?" + f.canRead());
}
@Test
public void test2(){
File f = new File("d:\\temp");//文件夹存在
System.out.println("文件夹名:" + f.getName());
System.out.println("文件夹大小:" + f.length());//错误的,无法直接获取文件夹的大小
System.out.println("文件夹的最后修改时间:" + f.lastModified());//距离1970-1-1 8:0:0的毫秒值
Date d = new Date(f.lastModified());
System.out.println("文件夹的最后修改时间:" + d);//Mon Dec 23 11:47:00 CST 2024
System.out.println("f是一个文件吗?" + f.isFile());
System.out.println("f是一个文件夹吗?" + f.isDirectory());
System.out.println("f是隐藏的文件或文件夹吗?" + f.isHidden());
System.out.println("f是存在的吗?" + f.exists());
System.out.println("f是可读的吗?" + f.canRead());
}
@Test
public void test3(){
File f = new File("d:\\temp");//文件夹存在
String parent = f.getParent();
System.out.println("上级目录:" + parent);
//parent.方法 如果要调用方法,只能调用String类的方法,因为parent变量是String类型
String[] subList = f.list();
for (String sub : subList) {
System.out.println(sub);
}
}
@Test
public void test4(){
File f = new File("d:\\temp");//文件夹存在
File parentFile = f.getParentFile();
//parentFile.方法 如果要调用方法,可以调用File类的方法
System.out.println(parentFile.isDirectory());//继续调用File类的方法
File[] files = f.listFiles();
for (File subFile : files) {
System.out.println("下一级" + subFile + "是文件吗:" + subFile.isFile());
}
}
@Test
public void test6(){
//求temp文件夹的总大小
File f = new File("d:\\temp");//文件夹存在
// System.out.println(f.length());//错误,无法直接获取文件夹大小
long result = totalLength(f);
System.out.println(result+"字节");
//753239269字节
}
public long totalLength(File f){
if(f.isFile()){
return f.length();
}else if(f.isDirectory()){
long sum = 0;
File[] files = f.listFiles();
for (File sub : files) {
// sum= sum + sub的大小;
// sum =sum + sub.length();//错误,sub可能是文件夹
sum =sum + totalLength(sub);
}
return sum;
}
return 0;
}
@Test
public void test7(){
//思考:如何删除非空文件夹?
File f = new File("d:\\柴林燕");//文件夹存在
// f.delete();//无法直接删除非空文件夹
forceDeleteDirectory(f);
}
public void forceDeleteDirectory(File f){
if(f.isDirectory()){//如果是文件夹,先清空文件夹内部的下一级
File[] files = f.listFiles();
for (File sub : files) {
forceDeleteDirectory(sub);//删除下一级,它可能是文件或文件夹
}
}
f.delete();//删除文件,或删除以及清空下一级的文件夹
}
}
3、文件或文件夹的路径
用来表示文件或文件夹的路径名表示有多种方式:
- 绝对路径:
- Windows操作:以盘符:开头的路径,例如:D:\temp\java\Hello.java
- Linux或Mac系统:以/开头的路径
- 相对路径:
- Windows或Linux或Mac系统:以文件夹名或文件名开头的就是相对路径
- 相对于谁呢?
- main方法,相对于当前的Project
- @Test方法,相对于当前的Module
- 非规范路径:包含../ 等描述符的路径
- 规范路径:不包含../ 等描述符的路径
public void test(){
//它俩都是相对路径,因为它们不是以盘符开头(Windows操作系统)
//因为是@Test方法,相对于当前的module
File f1 = new File("../../test1.txt");//非规范路径
File f2 = new File("test2.txt");//规范路径
System.out.println("f1构造路径(构造器实参列表中写的路径):" + f1.getPath());
System.out.println("f2构造路径(构造器实参列表中写的路径):" + f2.getPath());
System.out.println("f1的绝对路径:" + f1.getAbsolutePath());
System.out.println("f2的绝对路径:" + f2.getAbsolutePath());
try {
System.out.println("f1的规范路径:" + f1.getCanonicalPath());
System.out.println("f2的规范路径:" + f2.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
}
1.2 IO流类
通过File类只能创建、删除、重命名文件,获取文件的元数据(包括文件名、路径名、大小等)。如果想要操作文件的内容,必须通过IO流类。
提醒:文件夹是没有具体内容。只有文件才能装数据。
1.2.1 方向
- I:输入 input
- O:输出 output
1.2.2 方式
- 字节流:以字节为单位,最小读/写一个字节。可以操作任意类型的文件,包括纯文本文件,图片、音频、视频等。
- 字符流:以字符为单位,最小读/写一个字符,一个字符占就一个字节要看编码方式(这里不是只JVM中,是说在读或写的过程中,传输的过程中,编码方式有ISO8859-1,GBK,UTF-8等)。只能操作纯文本文件。
- 什么是纯文本文件?.txt,.java,.class,.css,.js,.html,.sql 等
- 以下这些不是纯文本文件: .doc,.docx,.ppt,.pptx,.xls,.jpg,.mp3
提醒:当你指定文件的时候,一定要带上后缀名或扩展名。后缀名或扩展名是代表或能体现文件类型的。
1.2.3 四个抽象基类
- InputStream:字节输入流
- 子类:FileInputStream,BufferedInputSream,ObjectInputStream等
- OutputStream:字节输出流
- 子类:FileOutputStream,BufferedOutputStream,ObjectOutputStream,PrintStream等
- Reader:字符输入流
- 子类:FileReader,BufferedReader,InputStreamReader等
- Writer:字符输出流
- 子类:FileWriter,BufferedWriter,OutputStreamWriter,PrintWriter等
1.2.4 文件IO流
1、案例:输出一句话到纯文件中
package com.mytest.io;
import org.junit.Test;
import java.io.FileWriter;
import java.io.IOException;
public class TestFileWriter {
@Test
public void test1() throws IOException {//暂时先不处理异常,后面单独讲IO流的异常处理问题
//输出一句话到纯文件中
//数据流向:程序 -> 文件
//(1)创建一个输出流,因为这里是操作纯文本文件
//对于不存在的文件,会自动创建
FileWriter fw = new FileWriter("java/1.txt");
//(2)输出输出一句话到纯文件中
fw.write("test test");
//(3)关闭IO流
// fw.flush();//刷新输出流,把数据及时写出去(用flush)
fw.close(); (最后是close)
}
// fw.flush(), fw.close() 都会把数据写出去,flush只能中间使用。close 最后使用,close 后不能再写出数据。
}
2、案例:读取一个纯文本文件的内容
package com.mytest.io;
import org.junit.Test;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
public class TestFileReader {
@Test
public void test()throws IOException{
//此时文件内容:hellojavachar
FileReader fr = new FileReader("java/1.txt");
char[] arr = new char[5];
while(true){
int len = fr.read(arr);
if(len == -1){
break;
}
//把char[] 转为 字符串
System.out.print(new String(arr,0,len));
//这里(arr,0,len),表示从arr数组中取len个字符构成字符串,从arr[0]开始取
}
fr.close();
}
}
3、案例:用字节流输出一句话到纯文本文件中
package com.mytest.io;
import org.junit.Test;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestFileOutputStream {
@Test
public void test1()throws IOException {
//输出一句话到纯文件中
//数据流向:程序 -> 文件
//(1)创建一个输出流,这里是操作纯文本文件,既可以使用FileWriter,也可以使用FileOutputStream
//建议:FileWriter
FileOutputStream fos = new FileOutputStream("java/3.txt");
//(2)输出一句话到纯文件中
fos.write("疯狂星期四".getBytes());//如果getBytes()参数里面是空的,代表用当前运行环境的编码方式
//我现在IDEA设置的是UTF-8
//(3)关闭
fos.close();
}
}
4、案例:用字节流读取纯文本文件
package com.mytest.io;
import org.junit.Test;
import java.io.FileInputStream;
public class TestFileInputStream {
@Test
public void test1()throws Exception{
//读取java/1.txt文件的内容到程序中
//数据流向:文件 -> 程序
// 程序 -> 控制台
//(1)创建输入流 可以是FileReader的对象,也可以是FileInputStream
//建议 FileReader的对象,这里演示FileInputStream
FileInputStream fis =new FileInputStream("java/1.txt");
byte[] data = new byte[5];
while(true){
int len = fis.read(data);//把读取的内容放到data数组中,并且返回本次读取了几个字节
if(len == -1){
break;
}
//把byte[]转为字符串
System.out.println(new String(data,0,len));
}
fis.close();
}
}
5、读取不同编码的问题
结论:
如果是整体读取(例如:复制),那么字节流和字符流都可以。
如果是一边读取,一边显示(构建字符串查看内容),那么字节流是不适合读取纯文本文件的。需要用字符流,因为FileReader和FileWriter在读和写的时候可以指定文件编码。
6、案例:如何读写数字等非纯文本数据
package com.mytest.io;
import org.junit.Test;
import java.io.*;
public class TestNotString {
@Test
public void test1()throws Exception{
/*
输出以下数据:
int num = 6;
double d = 3.14;
boolean b = true;
String s = "hello";
到一个文件 5.aaa
注意,这里的扩展名是我随便写的,表示不是纯文本。不与已有的扩展名相同即可。
不能用字符流,只能用字节流。因为字符流只能操作纯文本文件。
*/
FileOutputStream fos = new FileOutputStream("java\\5.aaa");
//如果此时只用FileOutputStream,无法实现功能,
//需要借助其他IO流来帮我们完成目标。可以借助的IO流有:DataOutputStream和ObjectOutputStream
//它俩有共同的父接口 DataOutput
//这里介绍 ObjectOutputStream ,因为后续我们输出对象时,也是用它
ObjectOutputStream oos = new ObjectOutputStream(fos);
//数据的流向: 程序 -> oos -> fos -> 文件
int num = 6;
double d = 3.14;
boolean b = true;
String s = "hello";
oos.writeInt(num);
oos.writeDouble(d);
oos.writeBoolean(b);
oos.writeUTF(s);//用修改版,特定版的UTF-8来输出
oos.close();
fos.close();
//上述写出去的文件内容,用现有的任意类型的软件都打不开。因为它不是纯文本文件,也不是word文档,不是图片,不是视频等。
}
@Test
public void test2()throws Exception{
//但是可以通过对应的Java程序读取这个文件的内容查看。
FileInputStream fis = new FileInputStream("java\\5.aaa");
//如果此时只用FileInputStream,无法实现功能,
//需要借助其他IO流来帮我们完成目标。可以借助的IO流有:DataInputStream和ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(fis);
//数据的流向: 文件 -> fis -> ois -> 程序
//读的顺序必须与写的顺序、类型一致
int a = ois.readInt();
double b = ois.readDouble();
boolean c = ois.readBoolean();
String d = ois.readUTF();
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
ois.close();
fis.close();
}
}
7、案例:提高文件的读写效率
方案一:在读和写的时候,把字节或字符数组定义的大一点。
方案二:借助缓冲流(缓冲流的本质仍然是内部有一个大一点的数组,默认长度是8192)
- BufferedInputStream可以包装InputStream系列的IO流提高读取效率
- BufferedOutputStream可以包装OutputStream系列的IO流提高输出效率
- BufferedReader可以包装Reader系列的IO流提高读取效率
- BufferedWriter可以包装Writer系列的IO流提高输出效率
package com.mytest.io;
import java.io.*;
public class FileTools {//工具类
//这个方法可以适用于任意类型的文件的复制,所以只能选择字节流
public static void copy(String srcFilePath, String destFilePath)throws IOException {
//从srcFilePath源文件读取内容,写到destFilePath目标文件中
FileInputStream fis = new FileInputStream(srcFilePath);
FileOutputStream fos = new FileOutputStream(destFilePath);
//数据流向:srcFilePath文件 -> fis -> data数组 -> fos -> destFilePath目标文件
byte[] data = new byte[10];//这里长度奇偶数都可以,因为咱们是整个文件的复制
while(true){
int len = fis.read(data);
if(len == -1){
break;
}
fos.write(data,0,len);//本次读取了几个字节,就输出几个字节
}
fos.close();
fis.close();
}
public static void copyQuick(String srcFilePath, String destFilePath)throws IOException {
//从srcFilePath源文件读取内容,写到destFilePath目标文件中
FileInputStream fis = new FileInputStream(srcFilePath);
FileOutputStream fos = new FileOutputStream(destFilePath);
//缓冲流的作用是提供效率,了解一下
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//数据流向:srcFilePath文件 -> fis -> bis -> data数组 -> bos -> fos -> destFilePath目标文件
byte[] data = new byte[10];//这里长度奇偶数都可以,因为咱们是整个文件的复制
while(true){
int len = bis.read(data);
if(len == -1){
break;
}
bos.write(data,0,len);//本次读取了几个字节,就输出几个字节
}
//倒着关 bos与fos倒着关,bis与fis倒着关,先new的后关
bos.close();
fos.close();
bis.close();
fis.close();
}
}
3.2.5 IO流的关闭和异常处理
1、关闭顺序
FileWriter fw = new FileWriter("java\\1.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("test");
fw.close();
bw.close();//错误:java.io.IOException: Stream closed
//数据的流向:程序 -> bw -> fw -> 文件
//(1)先关闭fw,相当于断开了fw与文件的连接
//(2)再关闭bw时,如果bw中还有数据需要写出,那么就此路不通
2、异常处理
try{
业务代码
}catch(异常类型 e){
异常处理
}finally{
资源关闭
}
Java7开始,引入了try-catch-with-resource的新语法糖:
try(资源声明){
业务代码
}catch(异常类型 e){
异常处理
}
//只要在try()中声明的资源,就可以自动关闭。
@Test
public void test2(){
//Java7开始,引入了try-catch-with-resource的新语法糖
try(
FileWriter fw = new FileWriter("java\\1.txt");
BufferedWriter bw = new BufferedWriter(fw);
) {
bw.write("test");
} catch (IOException e) {
e.printStackTrace();
}
}