文件和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
的书写,编写代码相对简单。
实验总结
在使用静态导入的时候,需要注意以下几点。
- 静态导入在代码中必须写
import static
。 - 提防静态导入冲突。例如,如果同时对
Integer
类和Long
类执行了静态导入,引用 MAX_VALUE 属性将导致一个编译器错误,因为Integer
类和Long
类都有一个 MAX_VALUE 常量,在使用时编译器就无法区分重名的 MAX_VALUE。 - 虽然静态导入让代码编写相对简单,但毕竟没有完整地写出静态成员所属的类名,程序的可读性有所降低。
在一些程序中, 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
挑战目标
根据你输入的路径来判断,若路径不存在,给出提示信息,程序结束;若路径存在,将指定路径下将所有的文本文件查找出来,并将文本文件名进行打印输出。
挑战要求
-
使用
Scanner
获取指定的路径,需要有提醒字符串 “请输入读取路径:”,完成操作后记得资源释放。 -
根据输入的路径,需要通过
File
类中的方法进行判断:-
exists()
方法来判断目录是否存在; -
isDirectory()
方法判断是否为目录;
-
-
若输入路径不正确时,输出字符串 “该目录不存在,或者它不是个目录” ,程序结束。
-
输入路径,现需要将该目录下的
.txt
文本文件通过FileFilter
或FilenameFilter
进行文件过滤,然后查找出来; -
最终可以通过
foreach
循环遍历将文本文件名打印输出。 -
输出的信息必须按照要求内容要求进行书写,不能随意扩展。
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()
方法写出字节,从而实现了复制文件内容的目的。
⭐ 注意:
read()
方法碰到数据流末尾时(即读取完毕时),返回值是 -1,否则返回值 > -1;- 在输入、输出流用完之后,要在异常处理的
finally
块中关闭输入、输出流,以释放资源。
从结果中我们看到,程序会在目录下新建一个 new_file.txt
文件,打开该文件和 ori_file.txt
对比,内容一致。再次运行程序,并再次打开 new_file.txt
文件, new_file.txt
里面的原内容没有再重复增加一遍,这说明输出流的 write()
方法是覆盖文件内容,而不是在文件内容后面追加内容。如果想采用追加的方式,则在使用构造方法创建字节输出流时,增加第二个值为 true 的参数即可,如 new FileOutputStream(f,true)
。
以上是关于文件和IO流的主要内容,如果未能解决你的问题,请参考以下文章
(20)IO流之SequenceInputStream 序列流