Java的IO流(一)
目录
Java的IO流(一)
前置小知识
分隔符
父路径
File类
File类介绍
File类中的静态成员
File类的构造方法
File类对象相关获取方法
File类创建文件的方法
File类删除文件方法
File类的判断方法
File类的遍历方法
File类练习
IO流分类
字节流
字节输出流
字节输入流
字节流与文件的复制
字符流
字节流读取中文问题
字符流介绍
字符输出流
字符输入流
IO异常处理方式
Java的IO流(一)
前置小知识
分隔符
分隔符一共分为两种:
- 路径名称分隔符:在路径中用于分隔文件或者文件夹,在Windows下使用
\
表示,在Linux下使用/
表示 - 路径分隔符:多个路径之间的分隔,一般使用
;
表示
在Windows下也可以使用/
表示路径名称分隔符,但是系统默认的路径名称分隔符为\
父路径
例如,在路径中:E:\Test\test\test.jpg
test.jpg
的父路径即为E:\Test\test
,对于test
文件夹来说,其父路径为E:\Test
,以此类推
File
类
File
类介绍
File
类在官方文档中的描述是:文件和目录(文件夹)路径名的抽象表示
本意是:File
类的对象存储着文件或者目录的路径
File
类中的静态成员
对于文件/文件夹的操作经常会使用到路径,其中避免不了出现分隔符,而对于「分隔符」来说,不同的系统有不同的分隔符,所以Java为了确保代码的通用性,提供了两个针对分隔符的静态变量,如下:
static String pathSeparator
:与系统有关的默认路径名称分隔符,在Windows下值为\
,在Linux下值为/
static String separator
:与系统有关的默认路径分隔符,在Windows下值为;
File
类的构造方法
File类中常用有三种构造方法:
File(String parent, String child)
:根据子路径和父路径一起确定一个完整的路径,使用此构造方法后,实例化的对象中存储的是一个结合父路径和子路径一体的完整路径File(File parent, String child)
:与第一个构造方法作用相同,不同的是,此构造方法的父路径是File
对象引用File(String pathname)
:参数直接填入文件的完整路径
File
类对象不会检查路径是否是真实存在的路径,所以理论上上面三个构造方法中的路径可以随便写,但是没有意义
如果在路径中不使用Java提供的分隔符常量,注意\
需要使用\\
进行转义
基本实例代码如下:
public class Test {public static void main(String[] args) {// 1. File(String parent, String child)File file1 = new File("D:\\", "a.txt");System.out.println(file1);// 2. File(File parent, String child)File file2 = new File("D:\\");File file3 = new File(file2, "a.txt");System.out.println(file3);// 3. File(String pathname)File file4 = new File("D:\\a.txt");System.out.println(file4);}
}输出结果:
D:\a.txt
D:\a.txt
D:\a.txt
File
类对象相关获取方法
在File
类创建完对象后,如果想获取到该对象中的一些内容,可以使用下面File
类的常用方法获取指定的内容:
String getAbsolutePath()
:获取File
类对象中文件路径的绝对路径(从根目录开始的路径)String getPath()
:获取File
类对象中的值String getName()
:获取到File
类对象中文件路径对应的文件/文件夹long length()
:获取到File
对象中文件路径对应的文件大小,以字节为单位
基本使用如下:
public class Test01 {public static void main(String[] args) {File file = new File("D:\\", "a.txt");// 1. String getAbsolutePath()System.out.println(file.getAbsoluteFile());// 2. String getPath()File file1 = new File("D:\\a.txt");System.out.println(file1.getPath());new File("./a.txt");System.out.println(file1.getPath());// 3. String getName()File file2 = new File("D:\\test");System.out.println(file2.getName());File file3 = new File("D:\\test\\a.txt");System.out.println(file3.getName());// 4. long length()File file4 = new File(".\\a.txt");System.out.println(file4.length());}
}
在上面的代码中,需要注意最后一个方法,因为当前a.txt
文件中没有任何内容,所以输出file4
对象对应文件的大小为0,此处一般获取到的length
与文件属性中的文件大小保持一致
如果指定的文件不存在,则计算文件大小的方法也会返回0
在IDEA中,使用./
代表的当前路径为模块所在路径,即「项目文件夹」中
File
类创建文件的方法
当需要使用File
类对象在对象路径位置创建文件/文件夹时,可以使用下面的两个方法:
boolean createNewFile()
:在File
对象路径下创建一个文件,如果文件已经存在,则创建失败,否则创建成功boolean mkdirs()
:在File
对象路径下创建一个文件夹,如果文件夹已经存在,则创建失败,否则创建成功;本方法可以一次创建多级文件夹
上面两个方法在使用时必须要在路径中指定创建的文件/文件夹名
需要注意,上面两个方法都会抛出异常
基本使用如下:
public class Test02 {public static void main(String[] args) throws IOException {File file = new File(".\\b.txt");// 1. boolean createNewFile()System.out.println(file.createNewFile());// 2. boolean mkdirs()File file1 = new File(".\\test\\test1");System.out.println(file1.mkdirs());}
}
在上面的代码中,对于createNewFile
来说,会在项目文件夹中创建一个名为b.txt
的文件;对于mkdirs
方法来说,会在项目文件夹中创建一个名为test
的文件夹,并且因为test
后面依旧还有文件夹,所以会在test
文件夹中继续创建新的名为test1
的文件夹
File
类删除文件方法
在Java中,如果想要删除文件/文件夹时,可以使用:boolean delete()
,如果删除成功,返回true
,否则返回false
需要注意,删除文件夹时必须确保文件夹为空,否则删除失败
使用该方法删除的文件/文件夹不会存在于系统回收站中
基本使用如下:
public class Test03 {public static void main(String[] args) {File file = new File("./b.txt");System.out.println(file.delete());File file1 = new File("./test/test1");System.out.println(file1.delete());}
}
File
类的判断方法
在Java中,判断文件类型或者文件是否存在可以使用下面的方法:
boolean isDirectory()
:判断File
对象中的路径的文件是否是文件夹,如果不是返回false
,否则返回true
boolean isFile()
:判断File
对象中的路径的文件是否是文件,如果不是返回false
,否则返回true
boolean exists()
:判断File
对象中的路径的文件是否存在,如果不存在返回false
,否则返回true
基本使用如下:
public class Test04 {public static void main(String[] args) {File file = new File("./b.txt");System.out.println(file.exists());System.out.println(file.isDirectory());System.out.println(file.isFile());}
}
File
类的遍历方法
当需要一次拿到多个文件的名称时,可以使用下面的方法:
String[] list()
:遍历指定的文件夹,将读取到的文件名称存储到String
数组中File[] listFiles()
:遍历指定的文件夹,将读取到的文件名称存储到File
数组中
list
方法遍历结果只会显示File
类对象路径下文件的文件名,但是listFiles
会显示File
类对象路径+内部文件的文件名
需要注意,两个方法在遍历到File
类对象路径下的文件夹时不会进入对应的文件夹
基本使用如下:
public class Test05 {public static void main(String[] args) {File file = new File("./test/test1");// 1. String[] list()String[] list = file.list();for (String s : list) {System.out.println(s);}// 2. File[] listFiles()File[] files = file.listFiles();for (File f : files) {System.out.println(f);}}
}
一般推荐使用listFiles
方法,因为返回的是File
类对象,之后的操作会更加方便
File
类练习
遍历当前项目文件夹下的test
文件夹中所有txt
文件,如果存在子文件夹,遍历子文件夹中所有txt
文件,以此类推,直到不存在txt
文件
思路:
- 创建
File
类对象指定路径 - 调用
listFiles
方法获取到当前路径下的所有文件 - 判断是否是文件,其次判断其文件名称是否以
.txt
结尾,如果是,打印出文件名,如果不是,判断是否是文件夹,时文件夹继续遍历,重复第三步
对于第三步来说,可以考虑使用递归的方式,因为文件目录本质还是树结构
参考代码:
public class Test06 {public static void main(String[] args) {File file = new File("./test/test1");File[] files = file.listFiles();isTxt(files);}private static void isTxt(File[] file) {for (File file1 : file) {if (file1.isFile()) {if (file1.getName().endsWith(".txt")) {System.out.println(file1);}} else {isTxt(file1.listFiles());}}}
}
IO流分类
在Java中,IO流有两种分类:
- 字节流:可以理解为「万用流」,因为一切均文件
- 字符流:针对于文本文件
字节流有两个对应的流:
- 字节输出流:对应
OutputStream
抽象类 - 字节输入流:对应
InputStream
抽象类
字符流有两个对应的流:
- 字符输出流:对应
Writer
抽象类 - 字符输入流:对应
Reader
抽象类
字节流
在Java中,字节流两个抽象流:
- 字节输出流:对应
OutputStream
抽象类 - 字节输入流:对应
InputStream
抽象类
字节输出流
OutputStream
抽象类的子类是FileOutputStream
构造方法如下:
FileOutputStream(File file)
:通过File
类对象创建FileOutputStream
对象FileOutputStream(String name)
:通过文件路径字符串创建FileOutputStream
对象
需要注意,上面两个构造方法都会抛出异常
基本使用如下:
public class Test {public static void main(String[] args) throws FileNotFoundException {File file = new File("./a.txt");// 1. FileOutputStream(File file) :通过File类对象创建FileOutputStream对象FileOutputStream outputStream = new FileOutputStream(file);// 2. FileOutputStream(String name):通过文件路径字符串创建FileOutputStream对象FileOutputStream outputStream1 = new FileOutputStream("./b.txt");}
}
字节输出流的特点:
- 如果指定的文件不存在就会创建新文件,否则就在指定的文件中写
- 默认覆盖写,如果原来文件中有内容,再次写就会覆盖原始的内容
字节输出流常用方法:
void write(int b)
:每一次写一个字节的数据,参数为写入内容的码值void write(byte[] b)
:每一次写一个字节数组中的数据,参数为写入内容的码值数组void write(byte[] b, int off, int len)
:每一次写一个字节数组中的部分数据,第一个参数为写入内容的码值数组,第二个参数为第一个写入的内容对应的下标,第三个参数为指定数组的元素个数void close()
:关闭输出流
基本使用如下:
public class Test01 {public static void main(String[] args) throws IOException {FileOutputStream outputStream = new FileOutputStream("./a.txt");outputStream.write(97);byte[] bytes = {98, 99, 100, 101, 102};outputStream.write(bytes);outputStream.write(bytes, 1, 3);outputStream.close();}
}
需要注意,如果使用了 close
方法关闭了当前输出流,就不可以再调用当前输出流对象,否则编译报错
如果需要追加写,则可以使用:FileOutputStream(String name, boolean append)
创建字节输出流对象,其中第二个参数此时需要为true
如果写的内容需要换行,则可以在写的内容中添加换行符:
public class Test03 {public static void main(String[] args) throws IOException {File file = new File("./a.txt");FileOutputStream outputStream = new FileOutputStream(file, true);outputStream.write("Hello\n".getBytes());outputStream.write("World\n".getBytes());outputStream.close();}
}
字节输入流
InputStream
抽象类的子类是FileInputStream
类
构造方法如下:
FileInputStream(File file)
:通过File
类对象创建FileInputStream
对象FileINputStream(String name)
:通过文件路径字符串创建FileINputStream
对象
需要注意,上面两个构造方法都会抛出异常
使用方法同字节输出流
字节输入流的特点:如果指定的文件不存在就会打开失败抛出异常,否则就在指定的文件中读取
字节输入流的常见方法:
int read()
:从指定文件中一次读取一个字节的数据,返回读取到的字节int read(byte[] b)
:从指定文件中一次读取n
个字节的数据,n
由参数数组大小决定,返回读取到的字节个数int read(byte[] b, int off, int len)
:从指定文件中一次读取n
个字节中的部分数据,n
由数组大小决定,第一个参数代表存储读取内容的数组,第二个参数为第一个读取的内容,第三个参数为字节个数。方法返回读取到的字节个数void close()
:关闭字节输入流
需要注意:
如果读取文件内容的过程中,一个流在读取的过程中会按照文件内容顺序读取,每一次读取的位置为上一次读取的结束位置,已经读到文件内容的结尾,此时三个read
方法都会返回-1。
如果使用了close
方法关闭了当前输出流,就不可以再调用当前输出流对象,否则编译报错
- 一次读取一个字节
public class Test04 {public static void main(String[] args) throws IOException {File file = new File("./a.txt");FileInputStream fileInputStream = new FileInputStream(file);int read = fileInputStream.read();System.out.println((char)read);int read1 = fileInputStream.read();System.out.println((char)read1);int read2 = fileInputStream.read();System.out.println((char)read2);fileInputStream.close();}
}
- 一次读取
n
个字节
public class Test04 {public static void main(String[] args) throws IOException {File file = new File("./a.txt");FileInputStream fileInputStream = new FileInputStream(file);byte[] bytes = new byte[2];while (fileInputStream.read(bytes) != -1) {System.out.println(new String(bytes));}}
}
上面的代码需要注意,如果文件中的内容只有5个字节,则在最后一次读取的过程中因为只读取到一个字节的数据,所以只会覆盖字节数组中第一个元素的位置,此时会出现打印结果与文件实际内容不一致的情况,可以考虑修改方法:因为read
方法会返回读取到的字节个数,所以可以通过该字节个数控制bytes
数组每次转换为字符串的个数
public class Test04 {public static void main(String[] args) throws IOException {File file = new File("./a.txt");FileInputStream fileInputStream = new FileInputStream(file);byte[] bytes = new byte[2];int len = 0;while ((len = fileInputStream.read(bytes)) != -1) {System.out.println(new String(bytes,0, len));}fileInputStream.close();}
}
对于「一次读取n
个字节中的部分数据」方法来说,在实际开发中并不常用,因为读取部分数据可能导致数据读取不全造成的问题,所以不做演示
字节流与文件的复制
文件的复制本质是将文件输入到内存中,再从内存输出到硬盘中,并且因为文件都是字节组成的,所以可以考虑字节流,步骤如下:
- 通过字节输入流打开指定文件
- 依次读取文件内容写到指定位置
整个过程中需要注意:
输出位置必须指明文件保存的名字,否则会导致无法找到文件
为了确保写入的都是有效数据,可以使用写入部分数据的write
方法
因为创建了字节输入流和字节输出流对象,所以需要关闭两个流,但是需要满足先开流后关
示例代码:
public class Test05 {public static void main(String[] args) throws IOException {File file_in = new File("./1.mp3");FileInputStream fileInputStream = new FileInputStream(file_in);File file_out = new File("./1-copy.mp3");FileOutputStream outputStream = new FileOutputStream(file_out);byte[] bytes = new byte[1024];int len = 0;while ((len = fileInputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, len);}outputStream.close();fileInputStream.close();}
}
字符流
字节流读取中文问题
使用字节流读取文本内容需要确保读取的字节个数是对应编码中的一个字符占用的字节个数
在UTF-8中,中文占3个字节,在GBK中,中文占2个字节,如果数组的长度为2,一次读取两个字节的数据,在GBK编码下就不会发生问题,但是如果是UTF-8就会出现读取的字节不全导致解码出现问题,从而乱码
下面的代码不论是GBK还是UTF-8都会出现读取中文乱码问题
public class Test {public static void main(String[] args) throws IOException {File file = new File("./b.txt");FileInputStream fileInputStream = new FileInputStream(file);byte[] bytes = new byte[1];int len = 0;while ((len = fileInputStream.read(bytes)) != -1) {System.out.println(new String(bytes, 0, len));}}
}
字符流介绍
字符流有两个对应的流:
- 字符输出流:对应
Writer
抽象类 - 字符输入流:对应
Reader
抽象类
字符输出流
Writer
抽象类的子类是FileWriter
构造方法如下:
FileWriter(File file)
:使用File
对象构造FileWriter
对象FileWriter(String fileName)
:使用文件路径构造FileWriter
对象FileWriter(String fileName, boolean append)
:通过文件路径和是否追加构造FileWriter
对象
基本使用:
public class Test01 {public static void main(String[] args) throws IOException {File file = new File("./a.txt");// 1. FileWriter(File file):使用File对象构造FileWriter对象FileWriter fileWriter = new FileWriter(file);// 2. FileWriter(String fileName):使用文件路径构造FileWriter对象FileWriter fileWriter1 = new FileWriter("./a.txt");// 3. FileWriter(String fileName, boolean append):通过文件路径和是否追加构造FileWriter对象FileWriter fileWriter2 = new FileWriter("./a.txt", true);}
}
字符输出流的特点(与字节输出流一样):
- 如果指定的文件不存在就会创建新文件,否则就在指定的文件中写
- 默认覆盖写,如果原来文件中有内容,再次写就会覆盖原始的内容
常用方法如下:
void write(int c)
:向文本文件中写入一个字符void write(char[] cbuf)
:向文本文件写一串字符void write(char[] cbuf, int off, int len)
:向文本文件写一串字符的一部分,第一个参数代表待写入的字符数组,第二个参数为第一个写入的字符对应的下标,第三个参数为数组的元素个数void write(String str)
:向文本文件写一个字符串void flush()
:刷新输出流缓冲区void close()
:关闭字符输出流
需要注意第五个方法和第六个方法:使用flush
方法和close
都可以将字符输出缓冲区的内容输出到文本文件中,但是flush
刷新后,当前输出流对象依旧可以使用,但是close
执行后,对应的输出流对象不可以再使用
基本使用如下:
public class Test02 {public static void main(String[] args) throws IOException {File file = new File("./b.txt");FileWriter fileWriter = new FileWriter(file);fileWriter.write(97);fileWriter.write("你好".toCharArray());fileWriter.write("Hello World");// fileWriter.flush();fileWriter.close();}
}
字符输入流
Reader
抽象类的子类是FileReader
类
构造方法:
FileReader(File file)
:使用File
对象构造FileReader
对象FileReader(String path)
:使用文件路径构造FileReader
对象
使用方式与字符输出流基本一致,不做演示
常用方法如下:
int read()
:每次从文本文件中读取一个字符,返回读取字符对应的int
值int read(char[] cbuf)
:每次从文本文件中读取一串字符存储到字符数组中,返回读取字符的个数int read(char[] cbuf, int off, int len)
:每次从文本文件中读取一串字符的一部分存储到字符数组中,返回读取字符的个数,第一个参数代表存储读取字符的数组,第二个参数为第一个写入的字符,第三个参数为字符个数void close()
:关闭字符输入流
使用方式与字节输出流类似,不再演示
IO异常处理方式
前面在遇到异常时直接使用了throws
向上抛出异常,但是这种处理方式只是为了代码简洁
在实际开发中,需要使用try...catch...finally
语句块处理异常,例如下面的代码:
public class Test {public static void main(String[] args) {File file = new File("./a.txt");// 确保在finally的作用域中可以调用close方法FileOutputStream outputStream = null;try {outputStream = new FileOutputStream(file);} catch (IOException e) {e.printStackTrace();} finally {try {// 文件未打开时不需要关闭if (outputStream != null) {outputStream.close();}} catch (IOException e) {e.printStackTrace();}}}
}
上面是传统的处理IOException
的方式,需要手动关闭流
在JDK7之后,可以使用try(IO对象1;IO对象2;...)...catch
处理IOException
,这种处理方式的特点是会自动关闭流
当存在多个IO对象时,在try()
中使用;
隔开
public class Test {public static void main(String[] args) {File file = new File("./a.txt");try(FileOutputStream outputStream = new FileOutputStream(file)) {} catch (IOException e) {e.printStackTrace();}}
}