文件和IO流

Posted 猪八戒1.0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了文件和IO流相关的知识,希望对你有一定的参考价值。

一.File 类

1.1File 类中静态属性的使用

File 类介绍

        Java 是面向对象的语言,要想把数据存到文件中,就必须用一个对象表示这个文件。

        File 类生成的对象就代表一个特定的文件或目录,并且 File 类提供了若干方法对这个文件或目录进行读写等各种操作。 File 类在 java.io 包下,与系统输入/输出相关的类通常都在此包下。

        构造一个 File 类的实例,需要文件或目录的路径来创建。

        File 类的构造方法有如下四个:

  • File(String pathname):创建一个新的 File 实例,该实例的存放路径是 pathname。
  • File(String parent, String child):创建一个新的 File 实例,该实例的存放路径是由 parent 和 child 拼接而成的。
  • File(File parent, String child):创建一个新的 File 实例。 parent 代表目录, child 代表文件名,因此该实例的存放路径是 parent 目录中的 child 文件。
  • File(URI uri):创建一个新的 File 实例,该实例的存放路径是由 URI 类型的参数指定的。

        在创建 File 类的实例时,有个问题尤其需要注意 😲 。

        Java 语言的一个显著特点是跨平台,可以做到一次编译、处处运行,所以在使用 File 类创建一个路径的抽象时,需要保证创建的这个 File 对象也是跨平台的。

        但是不同的操作系统对文件路径的设定各有不同的规则,例如在 Windows 操作系统下,一个文件的路径可能是 C:\\cn\\lanqiao\\file\\TestFile.java,而在 Linux 和 UNIX 操作系统下,文件路径的格式就类似于 /cn/lanqiao/file/TestFile.java

        🤔 那么如何统一 Windows 或 Linux 等系统中的路径分隔符呢?

        可以使用 File 类提供的一些静态属性,通过这些静态属性,可以获得 Java 虚拟机所在操作系统的分隔符相关信息,如下所示:

  • File.pathSeparator:与系统有关的路径分隔符,值是一个字符串,如在 Windows 中的此值是 ';',在 Linux 中的此值是 ':'
  • File.pathSeparatorChar:与系统有关的路径分隔符,值是一个字符,如在 Windows 中的此值是 ';',在 Linux 中的此值是 ':'
  • File.separator:与系统有关的路径层级分隔符,值是一个字符串,如在 Windows 中的此值是 '\\',在 Linux 中的此值是 '/'
  • File.separatorChar:与系统有关的路径层级分隔符,值是一个字符,如在 Windows 中的此值是'\\',在 Linux 中的此值是'/'

实验总结

通过实验主要使用了 File 中的静态属性进行信息获取:

  • File.pathSeparator:与系统有关的路径分隔符,在 Linux 中的此值是 ':'
  • File.separator:与系统有关的路径层级分隔符,在 Linux 中的此值是 '/'
import java.io.File;
public class TestFileSeparator 
    public static void main(String[] args) 
        System.out.println("PATH分隔符:" + File.pathSeparator);
        System.out.println("路径层级分隔符:" + File.separator);
    

 1.2 静态导入的使用

JDK 从 1.5 版开始,增加了静态导入的特性,用来导入指定类的某个静态属性或静态方法,或全部的静态属性或静态方法,静态导入使用 import static 语句。

例如我们常用的 System.out.println() 中的 out,以及 Integer 中的 MAX_VALUE 等,都是静态的属性,就可以使用静态导入。

首先是没有使用静态导入的程序:

//静态导入前的代码
public class TestStatic 
    public static void main(String[] args) 
        System.out.println(Integer.MAX_VALUE);
        System.out.println(Integer.toHexString(12));
    

使用静态导入的方式编程:

//静态导入后的代码
import static java.lang.System.out;
import static java.lang.Integer.*;

public class TestImportStatic 
    public static void main(String[] args) 
        out.println(MAX_VALUE);
        out.println(toHexString(12));
    

运行结果相同: 

通过代码对比可以看出,使用静态导入省略了 System 和 Integer 的书写,编写代码相对简单。

实验总结

在使用静态导入的时候,需要注意以下几点。

  1. 静态导入在代码中必须写 import static
  2. 提防静态导入冲突。例如,如果同时对 Integer 类和 Long 类执行了静态导入,引用 MAX_VALUE 属性将导致一个编译器错误,因为 Integer 类和 Long 类都有一个 MAX_VALUE 常量,在使用时编译器就无法区分重名的 MAX_VALUE。
  3. 虽然静态导入让代码编写相对简单,但毕竟没有完整地写出静态成员所属的类名,程序的可读性有所降低。

在一些程序中, System.out 被书写了多次。对于这种情况,程序员就可以考虑静态导入 System 类下的 out 静态变量,这样在之后代码内直接书写 out 即可代表此静态变量。

1.3 File 类中常用方法使用

File 类中常用的方法,如下:

方法说明
canExecute()判断 File 类对象是否可执行。
canRead()判断 File 类对象是否可读。
canWrite()判断 File 类对象是否可写。
createNewFile()当不存在该文件时,创建一个新的空文件。
exists()判断 File 类对象是否存在。
getAbsoluteFile()获取 File 类对象的绝对路径。
getName()获取 File 类对象的名称。
getParent()获取 File 类对象父目录的路径。
getPath()获取 File 类对象的路径。
isAbsolute()判断 File 类对象是否是绝对路径。
isDirectory()判断 File 类对象是否是目录。
isFile()判断 File 类对象是否是文件。
isHidden()判断 File 类对象是否有隐藏的属性。
lastModified()获取 File 类对象最后修改时间。
length()获取 File 类对象的长度。
listRoots()列出可用的文件系统根目录。

本实验中我们采用 System.out.format(format, args) 使用指定格式化字符串输出。

其中 format 参数为格式化转换符。关于转换符的说明如下表所示:

转换符说明
%s字符串类型
%c字符类型
%b布尔类型
%d整数类型(十进制)
%x整数类型(十六进制)
%o整数类型(八进制)
%f浮点类型
%e指数类型
%%百分比类型
%n换行符
%tx日期与时间类型( x 代表不同的日期与时间转换符)
import java.io.File;
import java.io.IOException;
/**
 * File 类中的方法使用
 */
public class TestFile 
    public static void main(String args[]) 
        System.out.print("文件系统根目录");
        for (File root : File.listRoots()) 
            //format方法以格式化形式输出字符串
            System.out.format("%s ", root);
        
        System.out.println();
        try 
            showFile();
         catch (IOException e) 
            e.printStackTrace();
        
    

    public static void showFile() throws IOException
        //创建File类对象file,注意使用转义字符 `\\`
        File f = new File("C:\\\\Users\\\\27955\\\\Desktop\\\\test.java");
        File f1 = new File("C:\\\\Users\\\\27955\\\\Desktop\\\\test.java");
        //当不存在该文件时,创建一个新的空文件
        f1.createNewFile();
        System.out.format("输出字符串:%s%n", f);
        System.out.format("判断File类对象是否存在:%b%n", f.exists());
        //%tc,输出日期和时间
        System.out.format("获取File类对象最后修改时间:%tc%n", f.lastModified());
        System.out.format("判断File类对象是否是文件:%b%n", f.isFile());
        System.out.format("判断File类对象是否是目录:%b%n", f.isDirectory());
        System.out.format("判断File类对象是否有隐藏的属性:%b%n", f.isHidden());
        System.out.format("判断File类对象是否可读:%b%n", f.canRead());
        System.out.format("判断File类对象是否可写:%b%n", f.canWrite());
        System.out.format("判断File类对象是否可执行:%b%n", f.canExecute());
        System.out.format("判断File类对象是否是绝对路径:%b%n", f.isAbsolute());
        System.out.format("获取File类对象的长度:%d%n", f.length());
        System.out.format("获取File类对象的名称:%s%n", f.getName());
        System.out.format("获取File类对象的路径:%s%n", f.getPath());
        System.out.format("获取File类对象的绝对路径:%s%n",f.getAbsolutePath());
        System.out.format("获取File类对象父目录的路径: %s%n", f.getParent());
    

         程序中的代码 for(File root:File.listRoots())…,通过一个增强 for 循环,遍历 File.listRoots() 方法获取的根目录集合(File 对象集合)。 f1.createNewFile(); 用于当不存在该文件时,创建一个新的空文件,所以在目录下创建了一个空文件,文件名为 TestFile1.java。另外,这个方法在执行过程中,如果发生 I/O 错误,会抛出 IOException 检查时异常,必须进行显式的捕获或继续向外抛出该异常。

1.4 通过 File 类获取指定路径下的文件

File 类还提供了一些用于返回指定路径下的目录和文件的方法,如下所示:

  • String[] list():返回一个字符串数组,这些字符串代表此抽象路径名表示的目录中的文件和目录。
  • String[] list(FilenameFilter filter):返回一个字符串数组,这些字符串代表此抽象路径名表示的目录中,满足过滤器 filter 要求的文件和目录。
  • File[] listFiles():返回一个 File 对象数组,表示此当前 File 对象中的文件和目录。
  • File[] listFiles(FilenameFilter filter):返回一个 File 对象数组,表示当前 File 对象中满足过滤器 filter 要求的文件和目录。
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
/**
 * 通过 File 类获取指定路径下的文件
 */
public class TestListFile 
    public static void main(String args[]) throws IOException 
        File f = new File("D:\\\\software\\\\IdeaProjects\\\\src");
        System.out.println("*** 使用list()方法获取String数组 ***");
        //返回一个字符串数组,由文件名组成
        String[] fNameList = f.list();
        for (String fName : fNameList) 
            System.out.println(fName);
        
        System.out.println("*** 使用listFiles()方法获取File数组 ***");
        //返回一个File数组,由File实例组成
        File[] fList = f.listFiles();
        for (File f1 : fList) 
            System.out.println(f1.getName());
        
        //使用匿名内部类创建过滤器,过滤出.java结尾的文件
        System.out.println("*** 使用listFiles(filter)方法过滤出.java文件 ***");
        //nb连写
        File[] fileList = f.listFiles(new FileFilter() 
            public boolean accept(File pathname) 
                if (pathname.getName().endsWith(".java"))
                    return true;
                return false;
            
        );
        for (File f1 : fileList) 
            System.out.println(f1.getName());
        
    

实验总结

通过本实验,巩固 File 类的一些用于返回指定路径下的目录和文件的方法。

可以通过 list() 和 listFiles() 方法根据提供的路径获取该路径下的所有文件,也可以通过设置 FilenameFilter 过滤器来进行文件筛选操作。

1.5 查找指定目录下的文本文件

挑战介绍

通过所学的 File 类来查找指定目录下的文本文件,并将文本文件名打印输出。

知识点

  • File 文件类的使用
  • FilenameFilter

挑战目标

根据你输入的路径来判断,若路径不存在,给出提示信息,程序结束;若路径存在,将指定路径下将所有的文本文件查找出来,并将文本文件名进行打印输出。

挑战要求

  1. 使用 Scanner 获取指定的路径,需要有提醒字符串 “请输入读取路径:”,完成操作后记得资源释放。

  2. 根据输入的路径,需要通过 File 类中的方法进行判断:

    • exists() 方法来判断目录是否存在;

    • isDirectory() 方法判断是否为目录;

  3. 若输入路径不正确时,输出字符串 “该目录不存在,或者它不是个目录” ,程序结束。

  4. 输入路径,现需要将该目录下的 .txt 文本文件通过 FileFilter 或 FilenameFilter 进行文件过滤,然后查找出来;

  5. 最终可以通过 foreach 循环遍历将文本文件名打印输出。

  6. 输出的信息必须按照要求内容要求进行书写,不能随意扩展。

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Scanner;

public class SelectTxtFile 
    public static void main(String[] args)throws IOException 
        Scanner input = new Scanner(System.in);

        System.out.println("请输入读取路径:");
        String str = input.next();
        File f = new File(str);

        if (!(f.exists()&&f.isDirectory())) 
            System.out.println("该目录不存在,或者它不是一个目录");

         else 
            //使用匿名内部类创建过滤器,过滤出.java结尾的文件
            System.out.println("*** 使用listFiles(filter)方法过滤出.txt文件 ***");
            //nb连写
            File[] fileList = f.listFiles(new FileFilter() 
                public boolean accept(File pathname) 
                    if (pathname.getName().endsWith(".txt"))
                        return true;
                    return false;
                
            );
            for (File f1 : fileList) 
                System.out.println(f1.getName());
            
        
        input.close();
    

二.IO 流

2.1 字节输入输出流的使用

        本实验将学习字节输入输出流的使用,如何使用 FileInputStream 和 FileOutputStream 两个字节流类,实现复制文件内容的功能。相信你一定能学会 ~

知识点

  • FileInputStream
  • FileOutputStream

IO 流介绍

        流是对 I/O 操作的形象描述,水从一个地方转移到另一个地方就形成了水流,而信息从一处转移到另一处就叫做 I/O 流。

        在 Java 中,文件的输入和输出是通过流(Stream)来实现的,流的概念源于 UNIX 中管道(pipe)的概念。在 UNIX 系统中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。

        一个流,必有源端和目的端,它们可以是计算机内存的某些区域,也可以是磁盘文件,甚至可以是 Internet 上的某个 URL。对于流而言,不用关心数据是如何传输的,只需要从源端输入数据(读),向目的端输出数据(写)。

Java 的输入输出流

🤔 如何理解输入和输出呢?

简单地说,你听别人唠叨就是输入,你向别人发牢骚就是输出。

在计算机的世界中,输入 Input 和输出 Output 都是针对计算机的内存而言的。比如读取一个硬盘上的文件,对于内存就是输入;向控制台打印输出一句话,就是输出。Java 中对于此类输入/输出的操作统称为 I/O,即 Input/Output。

输入流的抽象表示形式是接口 InputStream;输出流的抽象表示形式是接口 OutputStream。

JDK 中 InputStream 和 OutputStream 的实现就抽象了各种方式向内存读取信息和向外部输出信息的过程。

之前常用的 System.out.println(); 就是一个典型的输出流,目的是将内存中的数据输出到控制台。

而 new Scanner(System.in); 就是一个典型的输入流,目的是将控制台接收的信息输入到内存中。

System.in 和 System.out 两个变量实际就是 InputStream 和 OutputStream 的实例对象。

以 InputStream 对象为例,如下是定义 System 的源码。

public final class System 
    ...
    public final static InputStream in = null;
    ...

在 Java 中流分为 :字节流和字符流。

按照处理数据的单位,流可以分为字节流和字符流。

字节流的处理单位是字节,通常用来处理二进制文件,如音乐、图片文件等,并且由于字节是任何数据都支持的数据类型,因此字节流实际可以处理任意类型的数据。而字符流的处理单位是字符,因为 Java 采用 Unicode 编码,Java 字符流处理的即 Unicode 字符,所以在操作文字、国际化等方面,字符流具有优势

字节流的输入输出类

输入字节流类继承自抽象类 InputStream,输出字节流继承自抽象类 OutputStream,这两个抽象类拥有的方法可以通过查阅 Java API 获得。JDK 提供了不少字节流的实现类,下面列举了六个输入字节流类,输出字节流类和输入字节流类存在对应关系,大家可以对比学习。​​​​​​

  • FileInputStream:把一个文件作为输入源,从本地文件系统中读取数据字节,实现对文件的读取操作。

  • ByteArrayInputStream:把内存中的一个缓冲区作为输入源,从内存数组中读取数据字节。

  • ObjectInputStream:对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化,用于恢复那些以前序列化的对象,注意这个对象所属的类必须实现 Serializable 接口。

  • PipedInputStream:实现了管道的概念,从线程管道中读取数据字节。主要在线程中使用,用于两个线程间的通信。

  • SequenceInputStream:其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直至到达文件末尾,接着从第二个输入流读取,依次类推。

  • System.in:从用户控制台读取数据字节,在 System 类中,in 是 InputStream 类型的静态成员变量。

InputStream 输入流的方法,如下:

  • int read():从输入流中读取数据的下一字节,返回 0 ~ 255 范围内的整型字节值;如果输入流中已无新的数据,则返回 -1。

  • int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在字节数组 b 中,以整数形式返回实际读取的字节数(要么是字节数组的长度,要么小于字节数组的长度)。

  • int read(byte[] b, int off, int len):将输入流中最多 len 个数据字节读入字节数组 b 中,以整数形式返回实际读取的字节数,off 指数组 b 中将写入数据的初始偏移量。

  • void close():关闭此输入流,并释放与该流关联的所有系统资源。
  • int available():返回可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
  • void mark(int readlimit):在此输入流中标记当前的位置。
  • void reset():将此输入流重新定位到上次 mark 的位置。
  • boolean markSupported():判断此输入流是否支持 mark() 和 reset() 方法。
  • long skip(long n):跳过并丢弃此输入流中数据的 n 字节。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 字节流的使用
 */
public class TestByteStream 
    public static void main(String[] args)
        FileInputStream in = null;
        FileOutputStream out = null;
        try 
            File f = new File("D:\\\\software\\\\IdeaProjects\\\\src\\\\new_file.txt");
            f.createNewFile();
            //通过构造方法之一:String构造输入流
            in = new FileInputStream("D:\\\\software\\\\IdeaProjects\\\\src\\\\ori_file.txt");
            //通过构造方法之一:File类构造输出流
            out = new FileOutputStream(f);
            //通过逐个读取、存入字节,实现文件复制
            int c;
            while ((c = in.read()) != -1) 
                out.write(c);
            
         catch (IOException e) 
            System.out.println(e.getMessage());
         finally 
            if (in != null) 
                try 
                    in.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
            if (out != null) 
                try 
                    out.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    

编写了一个 ori_file.txt,运行程序可以实现拷贝出现new_file.txt

上面的代码分别通过传入字符串和 File 类,创建了文件输入流和输出流,然后调用输入流类的 read() 方法从输入流读取字节,再调用输出流的 write() 方法写出字节,从而实现了复制文件内容的目的。

⭐ 注意:

  1. read() 方法碰到数据流末尾时(即读取完毕时),返回值是 -1,否则返回值 > -1;
  2. 在输入、输出流用完之后,要在异常处理的 finally 块中关闭输入、输出流,以释放资源。

从结果中我们看到,程序会在目录下新建一个 new_file.txt 文件,打开该文件和 ori_file.txt 对比,内容一致。再次运行程序,并再次打开 new_file.txt 文件, new_file.txt 里面的原内容没有再重复增加一遍,这说明输出流的 write() 方法是覆盖文件内容,而不是在文件内容后面追加内容。如果想采用追加的方式,则在使用构造方法创建字节输出流时,增加第二个值为 true 的参数即可,如 new FileOutputStream(f,true)

以上是关于文件和IO流的主要内容,如果未能解决你的问题,请参考以下文章

16个IO常用流

Java IO流-合并流

fread()函数如何判断是不是到文件末尾?

(20)IO流之SequenceInputStream 序列流

Java学习之IO流(序列流--SequenceInputStream)

IO流-----(字节流)