Java——I/O(输入/输出)
1、File类
File类中的java.io包是唯一代表磁盘文件本身的对象,能够创建、删除或重命名文件,判断硬盘上某个文件是否存在,查询文件最后修改时间等。
File类的常用的构造方法
1.1 创建File对象
所有的构造方法都需要传入文件的路径。如上图,如果程序只处理一个目录或文件,并且知道该目录或文件的路径,使用第一个构造方法较方便。如果程序处理的是一个公共目录中的若干子目录或文件,那么使用第二个或者第三个构造方法会更方便。
【案例】,
需要注意的是在创建File文件时传入的路径使用了" \\ " ,这是因为在windows中目录符号为反斜线" \ ",但反斜线在java中是特殊字符,表示转义符,所以使用反斜线时,前面应该再添加一个反斜线,即为" \\ ",除此之外,目录符号还可以用正斜线" / "表示,如:src/Hello.java
import java.io.File;
public class test{
public static void main (String[] args){
File f = new File("E:\\Java base\\第七章IO\\a.txt"); //使用绝对路径构造File对象
File f1=new File("src\\Hello.java"); //使用相对路径构造File对象
System.out.println(f);
System.out.println(f1);
}
}
【运行结果】
1.2 File类的常用方法
File类提供了一系列方法,用于操作其内部封装的路径指向的文件或目录。例如,判断文件或目录是否存在,文件的创建于删除文件等,如下图:
【案例】
import java.io.File;
import java.io.IOException;
public class test{
public static void main (String[] args)throws IOException{
File f = new File("E:\\Java base\\第七章IO\\b.txt"); //使用绝对路径构造File对象
if(f.exists()){ //如果存在这个文件就删除,否则就创建
f.delete();
}else{
System.out.println(f.createNewFile());
}
//在磁盘下创建一层目录,并且在目录下创建文件
File fileDemo=new File("E:\\Java base\\第七章IO\\hello\\b.txt");
if(!(fileDemo.getParentFile().exists())){ //判断E:\\Java base\\第七章IO\\hello\目录是否存在
fileDemo.getParentFile().mkdir();
}
if(fileDemo.exists()){ //如果存在这个文件就删除,否则就创建
fileDemo.delete();
}else{
System.out.println(fileDemo.createNewFile());
}
}
}
【运行结果】
1.3 遍历目录下的文件
File类的list()方法用于遍历指定目录下的所有文件。
【案例】
import java.io.File;
import java.io.IOException;
public class test{
public static void main (String[] args)throws IOException{
//创建File对象
File f = new File("E:\\Java base\\第七章IO");
if(f.isDirectory()){ //判断File对象对应的目录是否存在
String[] names=f.list(); //获得目录下的所有文件的文件名
for (String name:names){
System.out.println(name); // 输出文件名
}
}
}
}
【运行结果】
下面通过一个案例来演示如何遍历指定目录下所有扩展名为" .java "的文件。
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
public class test{
public static void main (String[] args)throws IOException{
//创建File对象
File f = new File("E:\\Java base\\第七章IO");
//创建过滤器对象
FilenameFilter filter = new FilenameFilter() {
//实现accept()方法
public boolean accept (File dir,String name){
File currFile = new File(dir, name);
// 如果文件名以.java结尾返回true,否则返回false
if (currFile.isFile()&&name.endsWith(".java")){
return true;
}else{
return false;
}
}
};
if (f.exists()){ // 判断File对象对应的目录是否存在
String[] lists=f.list(filter); //获得过滤后的所有文件名数组
for(String name:lists){
System.out.println(name);
}
}
}
}
【运行结果】
前面两个案例演示的都是遍历目录下文件的文件名,有时候在一个目录下,除了文件,还有子目录,如果想得到所有子目录下的File类对象,list()方法显然不能满足要求,这时需要使用FIle类提供的另一种方法listFiles()。
listFiles()方法返回一个File对象数组,当对数组中的元素进行遍历时,如果元素中还有子目录需要遍历,则需要使用递归。
【案例】
调用listFiles()方法把该目录下所有的子目录和文件存到一个File类型的数组fires中,在通过for循环遍历数组files,并对当前遍历的File对象进行判断,如果是目录就重新调用fileDir()方法进行递归,如果是文件就直接打印输出文件的路径,这样就成功遍历了该目录下所有的文件。
import java.io.File;
import java.io.IOException;
public class test{
public static void main (String[] args)throws IOException{
File file=
new File("E:\\Java base\\第七章IO");
fileDir(file); //调用FileDir方法
}
public static void fileDir(File dir){
File[] files = dir.listFiles(); //获得表示目录下所有文件的数组
for (File file :files){ //遍历所有的子目录和文件
if(file.isDirectory()){
fileDir(file); //如果是目录,递归调用fileDir()
}
System.out.println(file.getAbsolutePath()); //输出文件的绝对路径
}
}
}
【运行结果】
这样就遍历到了所有的文件
1.4 删除文件及目录
【案例】
import java.io.File;
import java.io.IOException;
public class test{
public static void main (String[] args)throws IOException{
File file= new File("E:\\Java base\\第七章IO\\hello");
if(file.exists()){
System.out.println(file.delete());
}
}
}
【运行结果】
运行结果中输出了false,这说明删除文件失败了,原因在于File类的delete()方法只能删除一个指定的文件,如果File对象代表目录,并且目录下包含子目录或者文件,则File类的detele()方法不允许直接删数这个目录。
这时候我们需要使用递归的方式来进行删除了
import java.io.File;
import java.io.IOException;
public class test{
public static void main (String[] args)throws IOException{
File file= new File("E:\\Java base\\第七章IO\\hello");
deleteDir(file); // 调用deleteDir删除方法
}
public static void deleteDir(File dir){
if (dir.exists()){ //判断传入的File对象是否存在
File[] files=dir.listFiles(); //得到FIle数组
for(File file:files){ //遍历所有的子目录和文件
if(file.isDirectory()){
deleteDir(file); //如果是目录,则递归调用deleDir()
}else{
//如果是文件,则直接删除
file.delete();
}
}
// 删除完一个目录里的所有文件后,就删除这个目录
dir.delete();
}
}
}
【运行结果】
可以发现,该目录已经被删除了。
2 字节流
2.1 字节流的概念
在程序开发过程中,经常会需要处理设备之间的数据传输,而所有的文件都是以二进制(字节)的形式存在的。为字节的输入/输出(I/O)l流提供了一系列的流。被统称为字节流。根据数据的传输方向可将其分为字节输入流和字节输出流。
在JDK中,提供了两个抽象类InputStream和OutputStream,他们是字节流的顶级父类,所有的字节输入流都继承自InputStream,所有的字节输出流都继承自OutputStream。
在JDK中,InputStream和OutputStream提供了一系列与读写数据相关的方法,如下两图。
前三个read()方法都是用来读数据的,其中第一个read()方法是从输入流逐个读入字节,而第二个和第三个read()方法则将若干字节以字节数组的形式一次性读入,从而提高读数据的效率。
在进行I/O流操作时,当前I/O流会占用一定的内存,因此在操作结束之后,应该调用close()方法关闭流,从而释放当前I/O流所占的系统资源。
前三个是重载的write()方法,都用于向输出流写入字节。其中第一个方法逐个写入字节,第二个和第三个方法是将若干个字节以字节数组的形式一次性写入,从而提高写数据的效率。
flush()方法是用来将当前输出缓存区(通常是字节数组)中的数据强制写入目标设备,这个过程称为刷新。close()方法用来关闭流并释放与当前I/O流相关的系统资源。
InputStream和OutputStream这两个类虽然提供了一系列与读写数据相关的方法,但是这两个类都是抽象类,不能被实例化,因此针对不同的的功能,InputStream和OutputStream提供了不同的子类,这些子类形成了一个体系结构,如下图
2.2 InputStream读文件
InputStream就是JDK提供的基本输入流·。但InputStream并不是接口,而是抽象类,他是所有输入流的父类,FileInputStream是InputStream的子类,它是操作文件的字节输入流,专门用于读取文件中的数据。由于从文件读取数据是重复操作,因此需要通过循环语句来实现数据的持续读取。
下面通过一个案例来实现字节流对文件数据的读取,首先在java项目的根目录下创建一个文本文件test.txt,在文件中输入内容"itcast"并保存;然后使用字节输入流对象来读取test.txt文本文件。
【案例】
import java.io.FileInputStream;
import java.io.IOException;
public class test{
public static void main (String[] args)throws IOException{
//创建一个文件字节输入流
FileInputStream in = new FileInputStream("test.txt");
int b = 0; //定义一个int类型的变量b,记住每次读取的一个字节
while(true){
b = in.read(); //变量b记住读取的一个字节
if(b==-1){ //如果读取的字节为-1,跳出while循环
break;
}
System.out.println(b); //否则将b写出
}
in.close();
}
}
【运行结果】
计算机中的数据都是以字节的形式存在的,每个字符都占一个字节,因此最终结果显示的就是文件"test.txt"中的6个字节所对应的十进制数。
105
116
99
97
115
116
有的时候文件不存在时控制台会的报错信息会有一个潜在的问题,即如果文件不存在或别的原因导致读取过程中发生了I/O错误,InputStream就无法正常的关闭,资源也无法及时释放,针对这个问题,可以使用try...finally来保证无论是否发生I/O错误InputStream都能够正确关闭。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class test{
public static void main (String[] args)throws IOException{
InputStream input=null;
try{
//创建一个文件字节输入流
FileInputStream in = new FileInputStream("test.txt");
int b = 0; //定义一个int类型的变量b,记住每次读取的一个字节
while(true){
b = in.read(); //变量b记住读取的一个字节
if(b==-1){ //如果读取的字节为-1,跳出while循环
break;
}
System.out.println(b); //否则将b写出
}
}finally{
if(input !=null){
input.close();
}
}
}
}
2.3 OutputStream
OutputStream是JDK提供的最基本的输出流,也是抽象类,是所有输出流的父类。
【案例】——如何使用FileOutputStream将数据写入文件
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Example01 {
public static void main (String[] args)throws IOException{
//创建一个文件字节输出流
OutputStream out = new FileOutputStream("example.txt");
String str="传智播客";
byte[] b=str.getBytes();
for(int i = 0;i<b.length;i++){
out.write(b[i]);
}
out.close();
}
}
【运行结果】
在目录下会新生成一个文本文件“example.txt”,但是我打开以后是乱码的
2.4 文件的复制
首先在项目里创建两个文件夹source和target,然后在source里存放一个五环之歌.doc
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Example01 {
public static void main (String[] args)throws Exception{
//创建一个字节输入流,用于读取当前目录下source文件夹中的文件
InputStream in = new FileInputStream("source/五环之歌.doc");
//创建一个文件字节输出流,用于将读取的数据写入target目录下的文件中
OutputStream out = new FileOutputStream("target/五环之歌.doc");
int len; //定义一个int类型的变量len,记住每次读取的一个字节
//获取复制文件前的系统时间
long begintime = System.currentTimeMillis();
while((len = in.read())!=-1){ //读取一个字节并判断是否读到文件末尾
out.write(len); //将读到的字节写入文件
}
// 获取文件复制结束时的系统时间
long endtime = System.currentTimeMillis();
System.out.println("复制文件所消耗的时间是:"+(endtime-begintime)+"毫秒");
in.close();
out.close();
}
【运行结果】
在target中自动生成了一个doc文件
并且传输文件的时间为1毫秒
在复制文件时,受计算机性能等方面的影响,会导致复制文件所消耗的时间不确定,上述的文件复制是一个字节一个字节地读写,需要频繁地去操作文件,效率非常低,为了提高效率也可以定义一个字节数组作为缓冲区。在复制文件时,可以一次性读取多个字节的数据,并保存在字节数组中,然后将字节数组中的数据一次性写入文件。
【案例】
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Example01 {
public static void main (String[] args)throws Exception{
//创建一个字节输入流,用于读取当前目录下source文件夹中的文件
InputStream in = new FileInputStream("source/五环之歌.doc");
//创建一个文件字节输出流,用于将读取的数据写入target目录下的文件中
OutputStream out = new FileOutputStream("target/五环之歌.doc");
//以下是用缓冲区读写文件
byte[] buff = new byte[1024]; //定义一个字节数组,作为缓冲区
// 定义一个int类型的变量len记住读取读入缓冲区的字节数
int len;
//获取复制文件前的系统时间
long begintime = System.currentTimeMillis();
while((len = in.read(buff))!=-1){ //读取一个字节并判断是否读到文件末尾
out.write(buff,0,len); //将读到的字节写入文件
}
// 获取文件复制结束时的系统时间
long endtime = System.currentTimeMillis();
System.out.println("复制文件所消耗的时间是:"+(endtime-begintime)+"毫秒");
in.close();
out.close();
}
}
【运行结果】
同样实现了文件的复制,在复制过程中,使用while循环语句逐渐实现字节文件的复制,每循环一次,从文件读取若干字节填充字节数组,并通过变量len记住读入数组的字节数,然后从数组的第一个字节开始 ,将len个字节依次写入文件,循环往复,当len值为-1的时候,说明已经读到了文化的末尾,循环结束。
2.5 字节缓冲流
I/O提供两个带缓冲的字节流,分别是BufferedInputStream和BufferedOutputStream,他们的构造·方法中分别接收InputStream和OutputStream类型的参数作为对象,在读写数据时提供缓冲功能
下面通过案例学习BufferedInputStream和BufferedOutputStream这两个流的用法。首先在目录下创建一个src.txt的文件,并且随意写入一些内容;然后创建一个类,在类中使用FileOutputStream创建文件des.txt,并使用字节缓冲流对象将文件src.txt中的内容复制到文件des.txt中。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Example01 {
public static void main (String[] args)throws Exception{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("example.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("des.txt"));
int len;
while((len=bis.read())!=-1){
bos.write(len);
}
bis.close();
bos.close();
}
}
3. 字符流
3.1 字符流定义及基本用法
同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader和Writer。其中Reader是字符输入流,用于从某个源设备读取字符;Writer是字符输出流,用于向某个目标设备写入字符。Reader和Writer作为字符流的顶级父类,也有许多子类。下面通过一张继承关系图列举Reader和Writer的一些常用子类。
3.2 字符流操作文件
在程序开发中,经常需要对文本文件的内容进行读取,如果想从文件中直接读取字符便可以使用字符输入流FileReader,通过此流可以从关联文件中读取一个或一组字符。
下面通过一个案例来学习如何使用FileReader读取文件的字符
首先在目录下新建文本文件"test.txt"并在其中输入字符"itcast"。
import java.io.FileReader;
public class Example01 {
public static void main (String[] args)throws Exception{
// 创建一个FileReader对象用来读取文件中的字符
FileReader reader = new FileReader("test.txt");
int ch; //定义一个变量用于用于记录读取的字符
while ((ch=reader.read())!=-1){ //循环判断是否读取到文件的末尾
System.out.println((char)ch); //不是字符流末尾就转为字符打印
}
reader.close(); //关闭文件读取流·,释放资源
}
}
【运行结果】
实现了读取文件字符的功能,创建了一个FileReader对象与文件关联,接着通过while循环每次从文件中读取一个字符并打印,这样便实现了FIleRead读文件字符的操作。需要注意的是,字符输入流的read()方法返回的是int类型的值