目录
四、Java字符流的使用:字符输入输出流、字符文件和字符缓冲区的输入输出流
一、Java File类(文件操作类)详解
在操作文件之前必须创建一个指向文件的链接或者实例化一个文件对象,也可以指定一个不存在的文件从而创建它。
Java中的 File 类是文件和目录路径名的抽象形式。使用 File 类可以获取文件本身的一些信息,例如文件所在的目录、文件长度、文件读写权限等。本节将对 File 类进行详细介绍。
1.File 类简介
在 Java 中,File 类是 java.io 包中唯一代表磁盘文件本身的对象。File 类定义了一些与平台无关的方法来操作文件,File类主要用来获取或处理与磁盘文件相关的信息,像文件名、 文件路径、访问权限和修改日期等,还可以浏览子目录层次结构。
File 类表示处理文件和文件系统的相关信息。也就是说,File 类不具有从文件读取信息和向文件写入信息的功能,它仅描述文件本身的属性。
File类提供了如下三种形式构造方法。
- File(File parent,String child):根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
- File(String pathname):通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。如果给定字符串是空字符串,则结果是空的抽象路径名。
- File(String parent,String child):根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
使用任意一个构造方法都可以创建一个 File 对象,然后调用其提供的方法对文件进行操作。在表 1 中列出了 File 类的常用方法及说明。
方法名称 | 说明 |
---|---|
boolean canRead() | 测试应用程序是否能从指定的文件中进行读取 |
boolean canWrite() | 测试应用程序是否能写当前文件 |
boolean delete() | 删除当前对象指定的文件 . |
boolean exists() | 测试当前 File 是否存在 |
String getAbsolutePath() | 返回由该对象表示的文件的绝对路径名 |
String getName() | 返回表示当前对象的文件名 |
String getParent() | 返回当前 File 对象路径名的父路径名,如果此名没有父路径则为 null |
boolean isAbsolute() | 测试当前 File 对象表示的文件是否为一个绝对路径名 |
boolean isDirectory() | 测试当前 File 对象表示的文件是否为一个路径 |
boolean isFile() | 测试当前 File 对象表示的文件是否为一个“普通”文件 |
long lastModified() | 返回当前 File 对象表示的文件最后修改的时间 |
long length() | 返回当前 File 对象表示的文件长度 |
String[] list() | 返回当前 File 对象指定的路径文件列表 |
String[] list(FilenameFilter) | 返回当前 File 对象指定的目录中满足指定过滤器的文件列表 |
boolean mkdir() | 创建一个目录,它的路径名由当前 File 对象指定 |
boolean mkdirs() | 创建一个目录,它的路径名由当前 File 对象指定 |
boolean renameTo(File) | 将当前 File 对象指定的文件更名为给定参数 File 指定的路径名 |
mkdirs()可以建立多级文件夹, mkdir()只会建立一级的文件夹
如下:
new File("/tmp/one/two/three").mkdirs();
执行后, 会建立tmp/one/two/three四级目录
new File("/tmp/one/two/three").mkdir();
则不会建立任何目录, 因为找不到/tmp/one/two目录, 结果返回false
注意:假设在 Windows 操作系统中有一文件 D:\javaspace\hello.java,在 Java 中使用的时候,其路径的写法应该为 D:/javaspace/hello.java 或者 D:\\javaspace\\hello.java。
2.获取文件属性
在 Java 中获取文件属性信息的第一步是先创建一个 File 类对象并指向一个已存在的文件, 然后调用表 1 中的方法进行操作。
例 1
我在这个路径下面建立了一个test.txt文件:D:\java-software\develop\eclipse-workspace\practice--springboot\test.txt。编写 Java 程序获取并显示该文件的长度、是否可写、最后修改日期以及文件路径等属性信息。实现代码如下:
下面的我不用相对路径.\\test.txt,也行,直接使用绝对路径D:\java-software\develop\eclipse-workspace\practice--springboot\test.txt也是可以的。
public class Test {
public static void main(String[] args) throws IOException {
//传入相对路径,找到当前目录下的文件test.txt
File dir=new File(".\\test.txt");
File dir2=new File("..\\test.txt");
System.out.println("目录是否存在:"+dir2.exists());
//File dir2=new File("D:\\博客图片集\\test.txt");
System.out.println("文件长度:"+dir.length()+"字节");
System.out.println("文件或者目录:"+(dir.isFile()?"是文件":"不是文件"));
System.out.println("文件或者目录:"+(dir.isDirectory()?"是目录":"不是目录"));
System.out.println("是否可读:"+(dir.canRead()?"可读取":"不可读取"));
System.out.println("是否可写:"+(dir.canWrite()?"可写入":"不可写入"));
System.out.println("是否隐藏:"+(dir.isHidden()?"是隐藏文件":"不是隐藏文件"));
System.out.println("文件名称:"+dir.getName());
System.out.println("文件路径:"+dir.getPath());
System.out.println("绝对路径:"+dir.getAbsolutePath());
System.out.println("返回此抽象路径名的规范路径名字符串:"+dir.getCanonicalPath());
System.out.println("文件路径:"+dir2.getPath());
System.out.println("绝对路径:"+dir2.getAbsolutePath());
System.out.println("返回此抽象路径名的规范路径名字符串:"+dir2.getCanonicalPath());
}
}
上面代码的File
构造方法中传入的就是相对路径,代码中的.
表示当前目录,..
表示上级目录。
..就去到了当前目录的上一级目录,但是这个文件是不存在的,所以是false
执行结果为:
目录是否存在:false
文件长度:0字节
文件或者目录:是文件
文件或者目录:不是目录
是否可读:可读取
是否可写:可写入
是否隐藏:不是隐藏文件
文件名称:test.txt
文件路径:.\test.txt
绝对路径:D:\java-software\develop\eclipse-workspace\practice--springboot\.\test.txt
返回此抽象路径名的规范路径名字符串:D:\java-software\develop\eclipse-workspace\practice--springboot\test.txt
文件路径:..\test.txt
绝对路径:D:\java-software\develop\eclipse-workspace\practice--springboot\..\test.txt
返回此抽象路径名的规范路径名字符串:D:\java-software\develop\eclipse-workspace\test.txt
3.创建和删除文件
File 类不仅可以获取已知文件的属性信息,还可以在指定路径创建文件,以及删除一个文件。创建文件需要调用 createNewFile() 方法,删除文件需要调用 delete() 方法。无论是创建还是删除文件通常都先调用 exists() 方法判断文件是否存在。
例 2
假设在 D:\\博客图片集 目录下有一个 text.txt文件,程序启动时会检测该文件是否存在,如果不存在则创建;如果存在则删除它再创建。
实现代码如下:
public class Test {
public static void main(String[] args) throws IOException {
String path = "D:\\博客图片集"; // 指定文件目录
String filename = "text1.txt"; // 指定文件名称
File f = new File(path, filename); // 创建指向文件的File对象
if (f.exists()) // 判断文件是否存在
{
f.delete(); // 存在则先删除
}
f.createNewFile(); // 再创建
}
}
4.创建和删除目录
File 类除了对文件的创建和删除外,还可以创建和删除目录。创建目录需要调用 mkdir() 方法,删除目录需要调用 delete() 方法。无论是创建还是删除目录都可以调用 exists() 方法判断目录是否存在。
例 3
编写一个程序判断 D:\\博客图片集,目录下是否存在text.txt目录,如果存在则先删除再创建。实现代码如下:
public class Test {
public static void main(String[] args) throws IOException {
String path = "D:\\博客图片集"; // 指定文件目录
String filename = "text1.txt"; // 指定文件名称
File f = new File(path, filename); // 创建指向文件的File对象
if (f.exists()) // 判断文件是否存在
{
f.delete(); // 存在则先删除
}
f.createNewFile(); // 再创建
}
}
5.遍历目录
通过遍历目录可以在指定的目录中查找文件,或者显示所有的文件列表。File 类的 list() 方法提供了遍历目录功能,该方法有如下两种重载形式。
1. String[] list()
该方法表示返回由 File 对象表示目录中所有文件和子目录名称(也就是文件夹)组成的字符串数组,如果调用的 File 对象不是目录,则返回 null。
提示:list() 方法返回的数组中仅包含文件名称,而不包含路径。但不保证所得数组中的相同字符串将以特定顺序出现,特别是不保证它们按字母顺序出现。
2. String[] list(FilenameFilter filter)
该方法的作用与 list() 方法相同,不同的是返回数组中仅包含符合 filter 过滤器的文件和目录,如果 filter 为 null,则接受所有名称。
例 4
假设要遍历D:/java-software目录下的所有文件和目录,并显示文件或目录名称、类型及大小。使用 list() 方法的实现代码如下:
public class Test {
public static void main(String[] args) throws IOException {
File f = new File("D:/java-software");
System.out.println("文件名称\t\t文件类型\t\t文件大小");
System.out.println("===================================================");
String fileList[]=f.list();
System.out.println("文件和文件夹的数量为:"+fileList.length);
for(int i=0;i<fileList.length;i++){
System.out.print(fileList[i]+"\t\t");
System.out.print((new File("D:/java-software/",fileList[i])).isFile()?"文件"+"\t\t":"文件夹"+"\t\t");
System.out.println((new File("D:/java-software/",fileList[i])).length()+"字节");
}
}
}
由于 list() 方法返回的字符数组中仅包含文件名称,因此为了获取文件类型和大小,必须先转换为 File 对象再调用其方法。如下所示的是实例的运行效果:
文件名称 文件类型 文件大小
===================================================
文件和文件夹的数量为:10
dbeaver 文件夹 4096字节
DBVIS 文件夹 4096字节
DBVIS.zip 文件 39555228字节
develop 文件夹 4096字节
eclipse.zip 文件 674081582字节
git 文件夹 0字节
idea 文件夹 4096字节
soureTree 文件夹 0字节
批次服务 文件夹 4096字节
测试文本.txt 文件 0字节
例 5
假设希望只列出目录下的某些文件,这就需要调用带过滤器参数的 list() 方法。首先需要创建文件过滤器,该过滤器必须实现 java.io.FilenameFilter 接口,并在 accept() 方法中指定允许的文件类型。
如下所示为允许 SYS、TXT 和 BAK 格式文件的过滤器实现代码:
public class ImageFilter implements FilenameFilter {
// 实现 FilenameFilter 接口
@Override
public boolean accept(File dir, String name) {
// 指定允许的文件类型
return name.endsWith(".zip") || name.endsWith(".txt");
}
}
上述代码创建的过滤器名称为 ImageFilter,接下来只需要将该名称传递给 list() 方法即可实现筛选文件。如下所示为修改后的代码为:
再次运行程序,遍历结果如下所示:
文件名称 文件类型 文件大小
===================================================
文件和文件夹的数量为:3
DBVIS.zip 文件 39555228字节
eclipse.zip 文件 674081582字节
测试文本.txt 文件 0字节
6.相对路径和绝对路径
1.绝对路径
反斜杠(\)是windows目录分隔符
斜杠(/)是linux目录的分隔符
而斜杠(/)是java的目录分隔符,java是跨平台的,可以兼容Linux和等其他OS系统
在java中转义字符\\,代表了一个反斜线字符\
比如这两种形式的路径写法:
File f = new File("D:\\CODE\\IT\\test.txt");
//创建了一个File对象,表示一个文件或者路径名的抽象表示形式
File f = new File("D:/CODE/IT/test.txt");
//建议使用“/”表示路径
我们知道windows下的路径形式是这个样子的:D:\CODE\CLion\IT\test.txt
这种是用反斜杠\来表示目录分隔符的,但是用到java文件中的时候,由于反斜杠在java中不能直接使用,而需要在其前面加一个反斜杠\表示转义,才能使用,就是写成这个样子\\,所以说路径才写成
D:\\CODE\\IT\\test.txt这个样子的,也就是第一种形式,而不使用转义字符的话,可以直接使用java的目录分隔符斜杠/,写成第二种形式就是这个样子:D:/CODE/IT/test.txt,所以说直接使用第二种形式是最方便的,不用担心windows系统还是linux系统的问题。
2.相对路径
相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
弄明白相对路径相对的是哪个路径而言。这里指的是相对于该工程根目录而言。如果我不加任何目录,直接操作文件,如下:
可以看到我创建了一个test.txt文件,它是在这个项目的根目录下面的。
如果这个时候加上目录,则是相对于工程目录开始,往什么文件夹下去操作:
File f = new File("src/main/resources/test.txt");
可以看到test.txt文件是出现在我写的工程目录下的,这就是所谓的相对路径,
二、Java流的概念:什么是输入输出流?
在Java中所有数据都是使用流读写的。流是一组有序的数据序列,将数据从一个地方带到另一个地方。根据数据流向的不同,可以分为输入(Input)流和输出(Output)流两种。
下面详细介绍什么是输入流和输出流,以及 Java 中流的类型及每种类型的相关类。
1.什么是输入/输出流
Java 程序通过流来完成输入/输出,所有的输入/输出以流的形式处理。因此要了解 I/O 系统,首先要理解输入/输出流的概念。
输入就是将数据从各种输入设备(包括文件、键盘等)中读取到内存中,输出则正好相反,是将数据写入到各种输出设备(比如文件、显示器、磁盘等)。例如键盘就是一个标准的输入设备,而显示器就是一个标准的输出设备,但是文件既可以作为输入设备,又可以作为输出设备。
总的来说输入流就是读取出来,输出流就是写进去。
数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别。
- 按照流的方向主要分为输入流和输出流两大类。
- 数据流按照数据单位的不同分为字节流和字符流。
- 按照功能可以划分为节点流和处理流。
数据流的处理只能按照数据序列的顺序来进行,即前一个数据处理完之后才能处理后一个数据。数据流以输入流的形式被程序获取,再以输出流的形式将数据输出到其他设备。图 1 所示的是输入流模式,图 2 所示的是输出流模式。
图1 输入流模式
图2 输出流模式
1.1输入流
Java 流功能相关的类都封装在 java.io 包中,而且每个数据流都是一个对象。所有输入流类都是 InputStream 抽象类(字节输入流)和 Reader 抽象类(字符输入流)的子类。其中 InputStream 类是字节输入流的抽象类,是所有字节输入流的父类,其层次结构如图 3 所示。
图3 InputStream类的层次结构图
InputStream 类中所有方法遇到错误时都会引发 IOException 异常。如下是该类中包含的常用方法。
- int read():从输入流读入一个 8 字节的数据,将它转换成一个 0~255 的整数,返回一个整数,如果遇到输入流的结尾返回 -1。
- int read(byte[] b):从输入流读取若干字节的数据保存到参数 b 指定的字节数组中,返回的字节数表示读取的字节数,如果遇到输入流的结尾返回 -1。
- int read(byte[] b,int off,int len):从输入流读取若干字节的数据保存到参数 b 指定的字节数组中,其中 off 是指在数组中开始保存数据位置的起始下标,len 是指读取字节的位数。返回的是实际读取的字节数,如果遇到输入流的结尾则返回 -1。
- void close():关闭数据流,当完成对数据流的操作之后需要关闭数据流。
- int available():返回可以从数据源读取的数据流的位数。
- skip(long n):从输入流跳过参数 n 指定的字节数目。
- boolean markSupported():判断输入流是否可以重复读取,如果可以就返回 true。
- void mark(int readLimit):如果输入流可以被重复读取,从流的当前位置开始设置标记,readLimit 指定可以设置标记的字节数。
- void reset():使输入流重新定位到刚才被标记的位置,这样可以重新读取标记过的数据。
上述最后 3 个方法一般会结合在一起使用,首先使用 markSupported() 判断,如果可以重复读取,则使用 mark(int readLimit) 方法进行标记,标记完成之后可以使用 read() 方法读取标记范围内的字节数,最后使用 reset() 方法使输入流重新定位到标记的位置,继而完成重复读取操作。
Java 中的字符是 Unicode 编码,即双字节的,而 InputStream 是用来处理单字节的,在处理字符文本时不是很方便。这时可以使用 Java 的文本输入流 Reader 类,该类是字符输入流的抽象类,即所有字符输入流的实现都是它的子类。
这里我补充一下字节和字符的区别:
(一)“字节”的定义
字节(Byte)是一种计量单位,表示数据量多少,它是计算机信息技术用于计量存储容量的一种计量单位。
(二)“字符”的定义
字符是指计算机中使用的文字和符号,比如1、2、3、A、B、C、~!·#¥%……—*()——+、等等。
(三)“字节”与“字符”的区别
它们完全不是一个位面的概念,所以两者之间没有“区别”这个说法。
不同编码里,字符和字节的对应关系不同:
①ASCII码中:一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。 一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,换算为十进制。最小值0,最大值255。
②UTF-8编码中:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。
③Unicode编码中:一个英文字符等于两个字节,一个中文(含繁体)等于两个字节。 符号:英文标点占一个字节;中文标点占两个字节。
举例:英文句号“.”占1个字节的大小;中文句号“。”占2个字节的大小。
④UTF-16编码中:一个英文字母字符或一个汉字字符存储都需要2个字节(Unicode扩展区的一些汉字存储需要4个字节)。
⑤UTF-32编码中:世界上任何字符的存储都需要4个字节。
⑥GBK编码中:英文字符占一个字节,中文字符占两个字节
Reader类的具体层次结构如图 4 所示,该类的方法与 InputerSteam 类的方法类似,这里不再介绍。
图4 Reader类的层次结构
1.2输出流
在 Java 中所有输出流类都是 OutputStream 抽象类(字节输出流)和 Writer 抽象类(字符输出流)的子类。其中 OutputStream 类是字节输出流的抽象类,是所有字节输出流的父类,其层次结构如图 5 所示。
图5 OutputStream类的层次结构图
OutputStream 类是所有字节输出流的超类,用于以二进制的形式将数据写入目标设备,该类是抽象类,不能被实例化。OutputStream 类提供了一系列跟数据输出有关的方法,如下所示。
- int write (b):将指定字节的数据写入到输出流。
- int write (byte[] b):将指定字节数组的内容写入输出流。
- int write (byte[] b,int off,int len):将指定字节数组从 off 位置开始的 len 字芳的内容写入输出流。
- close():关闭数据流,当完成对数据流的操作之后需要关闭数据流。
- flush():刷新输出流,强行将缓冲区的内容写入输出流。
字符输出流的父类是 Writer,其层次结构如图 6 所示。
图6 OutputStream类的层次结构图
三、Java字节流的使用:字节输入输出流、文件输入输出流
Java所有表示字节输入流类的父类是 InputStream,它是一个抽象类,因此继承它的子类要重新定义父类中的抽象方法。所有表示字节输出流类的父类是 OutputStream,它也是一个抽象类,同样子类需要重新定义父类的抽象方法。
下面首先介绍上述两个父类提供的常用方法,然后介绍如何使用它们的子类输入和输出字节流,包括 ByteArrayInputStream 类、ByteArrayOutputStream 类、FileInputStream 类和 FileOutputStream 类。
1.字节输入流
InputStream 类及其子类的对象表示一个字节输入流。
InputStream 类的常用子类如下。
- ByteArrayInputStream 类:将字节数组转换为字节输入流,从中读取字节。
- FileInputStream 类:从文件中读取数据。(最常用)
- PipedInputStream 类:连接到一个 PipedOutputStream(管道输出流)。
- SequenceInputStream 类:将多个字节输入流串联成一个字节输入流。
- ObjectInputStream 类:将对象反序列化。
使用 InputStream 类的方法可以从流中读取一个或一批字节。表 1 列出了 InputStream 类的常用方法。
方法名及返回值类型 | 说明 |
---|---|
int read() | 从输入流中读取一个 8 位的字节,并把它转换为 0~255 的整数,最后返 回整数。 如果返回 -1,则表示已经到了输入流的末尾。为了提高 I/O 操作的效率,建议尽量 使用 read() 方法的另外两种形式 |
int read(byte[] b) | 从输入流中读取若干字节,并把它们保存到参数 b 指定的字节数组中。 该方法返回 读取的字节数。如果返回 -1,则表示已经到了输入流的末尾 |
int read(byte[] b, int off, int len) | 从输入流中读取若干字节,并把它们保存到参数b指定的字节数组中。其中,off 指 定在字节数组中开始保存数据的起始下标;len 指定读取的字节数。该方法返回实际 读取的字节数。如果返回 -1,则表示已经到了输入流的末尾 |
void close() | 关闭输入流。在读操作完成后,应该关闭输入流,系统将会释放与这个输入流相关 的资源。注意,InputStream 类本身的 close() 方法不执行任何 操作,但是它的许多 子类重写了 close() 方法 |
int available() | 返回可以从输入流中读取的字节数 |
long skip(long n) | 从输入流中跳过参数n指定数目的字节。该方法返回跳过的字节数 |
void mark(int readLimit) | 在输入流的当前位置开始设置标记,参数 readLimit 则指定了最多被设置 标记的字 节数 |
boolean markSupported() | 判断当前输入流是否允许设置标记,是则返回 true,否则返回 false |
void reset() | 将输入流的指针返回到设置标记的起始处 |
注意:在使用 mark() 方法和 reset() 方法之前,需要判断该文件系统是否支持这两个方法,以避免对程序造成影响。
2.字节输出流
OutputStream 类及其子类的对象表示一个字节输出流。OutputStream 类的常用子类如下。
- ByteArrayOutputStream 类:向内存缓冲区的字节数组中写数据。
- FileOutputStream 类:向文件中写数据。(最常用)
- PipedOutputStream 类:连接到一个 PipedlntputStream(管道输入流)。
- ObjectOutputStream 类:将对象序列化。
利用 OutputStream 类的方法可以从流中写入一个或一批字节。表 2 列出了 OutputStream 类的常用方法。
方法名及返回值类型 | 说明 |
---|---|
void write(int b) | 向输出流写入一个字节。这里的参数是 int 类型,但是它允许使用表达式,而不用强制转换成 byte 类型。为了提高 I/O 操作的效率,建议尽量使用write() 方法的另外两种形式 |
void write(byte[] b) | 把参数 b 指定的字节数组中的所有字节写到输出流中 |
void write(byte[] b,int off,int len) | 把参数 b 指定的字节数组中的若干字节写到输出流中。其中,off 指定字节数组中的起始下标,len 表示元素个数 |
void close() | 关闭输出流。写操作完成后,应该关闭输出流。系统将会释放与这个输出流相关的资源。注意,OutputStream 类本身的 close() 方法不执行任何操作,但是它的许多子类重写了 close() 方法 |
void flush() | 为了提高效率,在向输出流中写入数据时,数据一般会先保存到内存缓冲区中,只有当缓冲区中的数据达到一定程度时,缓冲区中的数据才会被写入输出流中。使用 flush() 方法则可以强制将缓冲区中的数据写入输 出流,并清空缓冲区 |
3.文件输入流
FileInputStream 是 Java 流中比较常用的一种,它表示从文件系统的某个文件中获取输入字节。通过使用 FileInputStream 可以访问文件中的一个字节、一批字节或整个文件。
在创建 FileInputStream 类的对象时,如果找不到指定的文件将拋出 FileNotFoundException 异常,该异常必须捕获或声明拋出。
注意这里我使用FileInputStream来读取字符汉字的,虽然下面的结果是对的,但是其实这样是不对的,先说一下为啥下面的结果是对的,因为我们次读取byte[1024],1024个字节,我的内容也就一段话,所以一下子就可以把内容读取出来,所以是正确的,,但是一旦我把字节数组缩小,比如缩小到byte[5],这个时候,再去读取我那段文字就会出现乱码,因为每次读取5个字节,而我的编码是UTF-8,每个字符占三个字节,导致每次读取的时候就会导致后两个字节不能凑够3个字节,所以导致不够一个汉字,而导致了乱码,所以说,最好的方式还是用FileReader()来读取字符。
FileInputStream 常用的构造方法主要有如下两种重载形式。
- FileInputStream(File file):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
- FileInputStream(String name):通过打开一个到实际文件的链接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
下面的示例演示了 FileInputStream() 两个构造方法的使用。
public class Test {
public static void main(String[] args) {
try {
//第一种方式,以File对象作为参数创建FileInputStream
File f = new File("D:/博客图片集");
InputStream ips = new FileInputStream(f);
//第二种方式,使用字符串类型的文件名来创建FileInputStream
InputStream ips2= new FileInputStream("D:/博客图片集");
} catch (FileNotFoundException e) {
System.out.println("指定的文件找不到!");
}
}
}
例 3
假设有一个 D:/博客图片集/text1.txt 文件,下面使用 FileInputStream 类读取并输出该文件的内容。具体代码如下:
public class Test {
public static void main(String[] args) {
// 以File对象作为参数创建FileInputStream
File f = new File("D:/博客图片集/text1.txt");
InputStream ips = null;
try {
ips = new FileInputStream(f);
// 定义一个字节数组
byte[] bytes = new byte[1024];
// 得到实际读取到的字节数
int n = 0;
System.out.println("D:/博客图片集/text1.txt文件内容如下:");
// 循环读取
while ((n = ips.read(bytes)) != -1) {
//通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String,将数组中从下标0到n的内容给s
String s = new String(bytes, 0, n);
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ips.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上述代码,首先创建了一个 File 对象 f,该对象指向 D:/博客图片集/text1.txt文件。接着使用 FileInputStream 类的构造方法创建了一个 FileInputStream 对象 ips,并声明一个长度为 1024 的 byte 类型的数组,然后使用 FileInputStream 类中的 read() 方法将 text1.txt 文件中的数据读取到字节数组 bytes 中,并输出该数据。最后在 finally 语句中关闭 FileInputStream 输入流。
图 1 所示为 text1.txt 文件的读取内容:
D:/博客图片集/text1.txt文件内容如下:
测试文件输入输出流
注意:FileInputStream 类重写了父类 InputStream 中的 read() 方法、skip() 方法、available() 方法和 close() 方法,不支持 mark() 方法和 reset() 方法。
4.文件输出流
FileOutputStream 类继承自 OutputStream 类,重写和实现了父类中的所有方法。FileOutputStream 类的对象表示一个文件字节输出流,可以向流中写入一个字节或一批字节。在创建 FileOutputStream 类的对象时,如果指定的文件不存在,则创建一个新文件;如果文件已存在,则清除原文件的内容重新写入。
FileOutputStream 类的构造方法主要有如下 4 种重载形式。
- FileOutputStream(File file):创建一个文件输出流,参数 file 指定目标文件。
- FileOutputStream(File file,boolean append):创建一个文件输出流,参数 file 指定目标文件,append 指定是否将数据添加到目标文件的内容末尾,如果为 true,则在末尾添加;如果为 false,则覆盖原有内容;其默认值为 false。
- FileOutputStream(String name):创建一个文件输出流,参数 name 指定目标文件的文件路径信息。
- FileOutputStream(String name,boolean append):创建一个文件输出流,参数 name 和 append 的含义同上。
注意:使用构造方法 FileOutputStream(String name,boolean append) 创建一个文件输出流对象,它将数据附加在现有文件的末尾。该字符串 name 指明了原文件,如果只是为了附加数据而不是重写任何已有的数据,布尔类型参数 append 的值应为 true。
对文件输出流有如下四点说明:
- 在 FileOutputStream 类的构造方法中指定目标文件时,目标文件可以不存在。
- 目标文件的名称可以是任意的,例如 F:\\abc、F:\\abc.de 和 F:\\abC.de.fg 等都可以,可以使用记事本等工具打开并浏览这些文件中的内容。
- 目标文件所在目录必须存在,否则会拋出 java.io.FileNotFoundException 异常。
- 目标文件的名称不能是已存在的目录(也就是说你想写入的文件不能提前在电脑中创建,你写好路径,它会帮你创建)。例如F盘下已存在java文件夹,那么就不能使用 java 作为文件名,即不能使用 F:\\java,否则抛出 java.io.FileNotFoundException 异常。
例 4
同样是读取 D:/博客图片集/text1.txt 文件的内容,在这里使用 FileInputStream 类实现,然后再将内容写入新的文件D:/博客图片集/text2.txt 中。具体的代码如下:
public class Test {
public static void main(String[] args) {
InputStream ips = null;
OutputStream ops= null;
try {
File srcFile=new File("D:/博客图片集/text1.txt");
//实例化FileInputStream对象
ips = new FileInputStream(srcFile);
//创建目标文件对象
File targetFile=new File("D:/博客图片集/text2.txt");
//实例化FileOutputStream对象
ops =new FileOutputStream(targetFile);
// 定义一个字节数组
byte[] bytes = new byte[1024];
// 得到实际读取到的字节数
int n = ips.read(bytes);
// 循环读取
while (n!=-1) {
//向text2.txt写入内容
ops.write(bytes,0,n);
//再次读取看是否循环到末尾
n=ips.read(bytes);
}
System.out.println("写入结束! ");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭FileInputStream对象
ips.close();
//关闭FileOutputStream对象
ops.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上述代码,将 D:/博客图片集/text1.txt文件中的内容通过文件输入/输出流写入到了 D:/博客图片集/text2.txt 文件中。由于 text2.txt 文件并不存在,所以在执行程序时将新建此文件,并写入相应内容。
运行程序,此时,打开 D:/博客图片集/text2.txt文件会发现,其内容与 D:/博客图片集/text1.txt文件的内容相同,如图 2 所示。
图2
技巧:在创建 FileOutputStream 对象时,如果将 append 参数设置为 true,则可以在目标文件的内容末尾添加数据,此时目标文件仍然可以暂不存在。
四、Java字符流的使用:字符输入输出流、字符文件和字符缓冲区的输入输出流
尽管 java 中字节流的功能十分强大,几乎可以直接或间接地处理任何类型的输入/输出操作,但利用它却不能直接操作 16 位的 Unicode 字符。这就要用到字符流。本节将重点介绍字符流的操作。
1.字符输入流
Reader 类是所有字符流输入类的父类,该类定义了许多方法,这些方法对所有子类都是有效的。
Reader 类的常用子类如下。
- CharArrayReader 类:将字符数组转换为字符输入流,从中读取字符。
- StringReader 类:将字符串转换为字符输入流,从中读取字符。
- BufferedReader 类:为其他字符输入流提供读缓冲区。
- PipedReader 类:连接到一个 PipedWriter。
- InputStreamReader 类:将字节输入流转换为字符输入流,可以指定字符编码。
与 InputStream 类相同,在 Reader 类中也包含 close()、mark()、skip() 和 reset() 等方法,这些方法可以参考 InputStream 类的方法。下面主要介绍 Reader 类中的 read() 方法,如表 1 所示。
方法名及返回值类型 | 说明 |
---|---|
int read() | 从输入流中读取一个字符,并把它转换为 0~65535 的整数。如果返回 -1, 则表示 已经到了输入流的末尾。为了提高 I/O 操作的效率,建议尽量使 用下面两种 read() 方法 |
int read(char[] cbuf) | 从输入流中读取若干个字符,并把它们保存到参数 cbuf 指定的字符数组中。 该方 法返回读取的字符数,如果返回 -1,则表示已经到了输入流的末尾 |
int read(char[] cbuf,int off,int len) | 从输入流中读取若干个字符,并把它们保存到参数 cbuf 指定的字符数组中。其中, off 指定在字符数组中开始保存数据的起始下标,len 指定读 取的字符数。该方法返 回实际读取的字符数,如果返回 -1,则表示已经 到了输入流的末尾 |
2.字符输出流
与 Reader类相反,Writer 类是所有字符输出流的父类,该类中有许多方法,这些方法对继承该类的所有子类都是有效的。
Writer 类的常用子类如下。
- CharArrayWriter 类:向内存缓冲区的字符数组写数据。
- StringWriter 类:向内存缓冲区的字符串(StringBuffer)写数据。
- BufferedWriter 类:为其他字符输出流提供写缓冲区。
- PipedWriter 类:连接到一个 PipedReader。
- OutputStreamReader 类:将字节输出流转换为字符输出流,可以指定字符编码。
与 OutputStream 类相同,Writer 类也包含 close()、flush() 等方法,这些方法可以参考 OutputStream 类的方法。下面主要介绍 Writer 类中的 write() 方法和 append() 方法,如表 2 所示。
方法名及返回值类型 | 说明 |
---|---|
void write(int c) | 向输出流中写入一个字符 |
void write(char[] cbuf) | 把参数 cbuf 指定的字符数组中的所有字符写到输出流中 |
void write(char[] cbuf,int off,int len) | 把参数 cbuf 指定的字符数组中的若干字符写到输出流中。其中,off 指定字符数组中的起始下标,len 表示元素个数 |
void write(String str) | 向输出流中写入一个字符串 |
void write(String str, int off,int len) | 向输出流中写入一个字符串中的部分字符。其中,off 指定字符串中的起始偏移量,len 表示字符个数 |
append(char c) | 将参数 c 指定的字符添加到输出流中 |
append(charSequence esq) | 将参数 esq 指定的字符序列添加到输出流中 |
append(charSequence esq,int start,int end) | 将参数 esq 指定的字符序列的子序列添加到输出流中。其中,start 指定子序列的第一个字符的索引,end 指定子序列中最后一个字符后面的字符的索引,也就是说子序列的内容包含 start 索引处的字符,但不包括 end索引处的字符 |
注意:Writer 类所有的方法在出错的情况下都会引发 IOException 异常。关闭一个流后,再对其进行任何操作都会产生错误。
3.字符文件输入流
为了读取方便,Java提供了用来读取字符文件的便捷类——FileReader。该类的构造方法有如下两种重载形式。
- FileReader(File file):在给定要读取数据的文件的情况下创建一个新的 FileReader 对象。其中,file 表示要从中读取数据的文件。
- FileReader(String fileName):在给定从中读取数据的文件名的情况下创建一个新 FileReader 对象。其中,fileName 表示要从中读取数据的文件的名称,表示的是一个文件的完整路径。
在用该类的构造方法创建 FileReader 读取对象时,默认的字符编码及字节缓冲区大小都是由系统设定的。要自己指定这些值,可以在 FilelnputStream 上构造一个 InputStreamReader。
注意:在创建 FileReader 对象时可能会引发一个 FileNotFoundException 异常,因此需要使用 try catch 语句捕获该异常。
字符流和字节流的操作步骤相同,都是首先创建输入流或输出流对象,即建立连接管道,建立完成后进行读或写操作,最后关闭输入/输出流通道。
例 1
要将 E:\myjava\HelloJava.java 文件中的内容读取并输出到控制台,使用 FileReader 类的实现代码如下:
public class Test {
public static void main(String[] args) {
FileReader fr=null;
try {
//创建FileReader对象
fr=new FileReader("D:/博客图片集/text1.txt");
int i=0;
System.out.println("D:/博客图片集/text1.txt文件内容如下:");
// 循环读取
while ((i=fr.read())!=-1) {
//将读取的内容强制转换为char类型
System.out.print((char)i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭FileReader对象
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上述代码,首先创建了 FileReader 字符输入流对象 fr,该对象指向 D:/博客图片集/text1.txt 文件,然后定义变量 i 来接收调用 read() 方法的返回值,即读取的字符。在 while 循环中,每次读取一个字符赋给整型变量 i,直到读取到文件末尾时退出循环(当输入流读取到文件末 尾时,会返回值 -1)。
例 1 使用 int read(char[] cbuf)的方式读取
public class Test {
public static void main(String[] args) {
FileReader fr=null;
try {
//创建FileReader对象
fr=new FileReader("D:/博客图片集/text1.txt");
char[] c= new char[5];
int n=0;
System.out.println("D:/博客图片集/text1.txt文件内容如下:");
// 循环读取
while ((n=fr.read(c))!=-1) {
for (int i= 0; i<c.length; i++) {
//将读取的内容强制转换为char类型
System.out.print(c[i]);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭FileReader对象
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.字符文件输出流
Java 提供了写入字符文件的便捷类——FileWriter,该类的构造方法有如下 4 种重载形式。
- FileWriter(File file):在指定 File 对象的情况下构造一个 FileWriter 对象。其中,file 表示要写入数据的 File 对象。
- FileWriter(File file,boolean append):在指定 File 对象的情况下构造一个 FileWriter 对象,如果 append 的值为 true,则将字节写入文件末尾,而不是写入文件开始处。
- FileWriter(String fileName):在指定文件名的情况下构造一个 FileWriter 对象。其中,fileName 表示要写入字符的文件名,表示的是完整路径。
- FileWriter(String fileName,boolean append):在指定文件名以及要写入文件的位置的情况下构造 FileWriter 对象。其中,append 是一个 boolean 值,如果为 true,则将数据写入文件末尾,而不是文件开始处。
在创建 FileWriter 对象时,默认字符编码和默认字节缓冲区大小都是由系统设定的。要自己指定这些值,可以在 FileOutputStream 上构造一个 OutputStream Writer 对象。
FileWriter 类的创建不依赖于文件存在与否,如果关联文件不存在,则会自动生成一个新的文件。在创建文件之前,FileWriter 将在创建对象时打开它作为输出。如果试图打开一个只读文件,将引发一个 IOException 异常。
注意:在创建 FileWriter 对象时可能会引发 IOException 或 SecurityException 异常,因此需要使用 try catch 语句捕获该异常。
例 2
编写一个程序,将用户输入的 4 个字符串保存到 E:\myjava\book.txt 文件中。在这里使用 FileWriter 类中的 write() 方法循环向指定文件中写入数据,实现代码如下:
public class Test {
public static void main(String[] args) {
FileReader fr=null;
FileWriter fw=null;
try {
//创建FileReader对象
fr=new FileReader("D:/博客图片集/text1.txt");
//创建FileWriter对象
fw=new FileWriter("D:/博客图片集/text2.txt");
char[] c= new char[5];
int n=0;
System.out.println("D:/博客图片集/text1.txt文件内容如下:");
// 循环读取
while ((n=fr.read(c))!=-1) {
for (int i= 0; i<c.length; i++) {
System.out.print(c[i]);
//写入text2.txt文件
fw.write(c[i]);
}
}
System.out.println("\nD:/博客图片集/text2.txt文件写入完毕:");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭FileReader对象
fr.close();
//关闭FileWriter对象
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意:这里我写FIleWriter的时候忘记了在下面关闭就是这个语句:fw.close(),导致一直是输入状态,导致没有文件保存写入到text2.txt中。切记一定要关闭流。
补充一下:
其中\r ,\n, \r\n的区别
\r 是回车,将光标移到当前行的行首;
\n 是换行,光标移到当前行的下一行,并且不是行首,是同列(同当前行的同列);
所以\r\n是回车换行的意思,就是光标移到当前行的行首;
但在不同的系统中它们的功能也不太相同。比如在windows里,\r\n表示回车换行;但在linux中\n就代表回车换行。
5.字符缓冲区输入流
BufferedReader 类主要用于辅助其他字符输入流,它带有缓冲区,可以先将一批数据读到内存缓冲区。接下来的读操作就可以直接从缓冲区中获取数据,而不需要每次都从数据源读取数据并进行字符编码转换,这样就可以提高数据的读取效率。
BufferedReader 类的构造方法有如下两种重载形式。
- BufferedReader(Reader in):创建一个 BufferedReader 来修饰参数 in 指定的字符输入流。
- BufferedReader(Reader in,int size):创建一个 BufferedReader 来修饰参数 in 指定的字符输入流,参数 size 则用于指定缓冲区的大小,单位为字符。
除了可以为字符输入流提供缓冲区以外,BufferedReader 还提供了 readLine() 方法,该方法返回包含该行内容的字符串,但该字符串中不包含任何终止符,如果已到达流末尾,则返回 null。readLine() 方法表示每次读取一行文本内容,当遇到换行(\n)、回车(\r)或回车后直接跟着换行标记符即可认为某行已终止。
例 3
使用 BufferedReader 类中的 readLine() 方法逐行读取 D:/博客图片集/text1.txt 文件中的内容,并将读取的内容在控制台中打印输出,代码如下:
public class Test {
public static void main(String[] args) {
FileReader fr=null;
BufferedReader br=null;
try {
//创建FileReader对象
fr=new FileReader("D:/博客图片集/text1.txt");
//创建BufferedReader对象
br=new BufferedReader(fr);
System.out.println("D:/博客图片集/text1.txt文件内容如下:");
String strLine="";
while ((strLine=br.readLine())!=null) {
//循环读取每行数据
System.out.println(strLine);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭FileReader对象
fr.close();
//关闭BufferedReader对象
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上述代码,首先分别创建了名称为 fr 的 FileReader 对象和名称为 br 的 BufferedReader 对象,然后调用 BufferedReader 对象的 readLine() 方法逐行读取文件中的内容。如果读取的文件内容为 Null,即表明已经读取到文件尾部,此时退出循环不再进行读取操作。最后将字符文件输入流和带缓冲的字符输入流关闭。
运行该程序,输出结果如下所示:
D:/博客图片集/text1.txt文件内容如下:
测试文件输入输出流
6.字符缓冲区输出流
BufferedWriter 类主要用于辅助其他字符输出流,它同样带有缓冲区,可以先将一批数据写入缓冲区,当缓冲区满了以后,再将缓冲区的数据一次性写到字符输出流,其目的是为了提高数据的写效率。
BufferedWriter 类的构造方法有如下两种重载形式。
- BuflferedWriter(Writer out):创建一个 BufferedWriter 来修饰参数 out 指定的字符输出流。
- BufferedWriter(Writer out,int size):创建一个 BufferedWriter 来修饰参数 out 指定的字符输出流,参数 size 则用于指定缓冲区的大小,单位为字符。
该类除了可以给字符输出流提供缓冲区之外,还提供了一个新的方法——newLine(),该方法用于写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行(\n)符。
例1:
public class Test {
public static void main(String[] args) {
FileReader fr=null;
BufferedReader br=null;
FileWriter fw=null;
BufferedWriter bw=null;
try {
//创建FileReader对象
fr=new FileReader("D:/博客图片集/text1.txt");
//创建BufferedReader对象
br=new BufferedReader(fr);
fw=new FileWriter("D:/博客图片集/text2.txt");
//创建BufferedReader对象
bw=new BufferedWriter(fw);
System.out.println("D:/博客图片集/text1.txt文件内容如下:");
String strLine="";
while ((strLine=br.readLine())!=null) {
//循环读取每行数据
System.out.println(strLine);
bw.write(strLine);
}
} catch (Exception e) {
e.printStackTrace();
} finally
{
try {
//关闭FileReader对象
fr.close();
//关闭BufferedReader对象
br.close();
//关闭BufferedWriter对象
bw.close();
//关闭FileWriter对象
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意:写这个例子的时候出现了一个流关闭的问题,就是输入流,我先关闭的fw,再关闭的bw,结果出现了异常,说流被关闭了,因为我们是用BufferedWriter来修饰FileWriter的,如果FileWriter提前被关闭,就会导致BufferedWriter缺少修饰对象,而出错,因此,需要先关闭BufferedWriter,再关闭FileWriter,而FileReader和BufferedReader并没有此问题。
五、Java系统流
每个Java程序运行时都带有一个系统流,系统流对应的类为 java.lang.System。Sytem 类封装了 Java 程序运行时的 3 个系统流,分别通过 in、out 和 err 变量来引用。这些变量的作用域为 public 和 static,因此在程序的任何部分都不需引用 System 对象就可以使用它们。
- System.in:标准输入流,默认设备是键盘。
- System.out:标准输出流,默认设畜是控制台。
- System.err:标准错误流,默认设备是控制台。
例 1
下面的程序演示了如何使用 System.in 读取字节数组,使用 System.out 输出字节数组。
1. package ch13;
2. import java.io.IOException;
3. public class Test01
4. {
5. public static void main(String[] args)
6. {
7. byte[] byteData=new byte[100]; //声明一个字节数组
8. System.out.println("请输入英文:");
9. try
10. {
11. System.in.read(byteData);
12. }
13. catch (IOException e)
14. {
15. e.printStackTrace();
16. }
17. System.out.println("您输入的内容如下:");
18. for(int i=0;i<byteData.length;i++)
19. {
20. System.out.print((char)byteData[i]);
21. }
22. }
}
该程序的运行结果如下所示:
请输入英文:
abcdefg hijklmn opqrst uvwxyz
您输入的内容如下:
abcdefg hijklmn opqrst uvwxyz
System.in 是 InputStream 类的一个对象,因此上述代码的 System.in.read() 方法实际是访问 InputStream 类定义的 read() 方法。该方法可以从键盘读取一个或多个字符。对于 System.out 输出流主要用于将指定内容输出到控制台。
System.out 和 System.error 是 PrintStream 类的对象。因为 PrintStream 是一个从 OutputStream 派生的输出流,所以它还执行低级别的 write() 方法。因此,除了 print() 和 println() 方法可以完成控制台输出以外,System.out 还可以调用 write() 方法实现控制台输出。
write() 的简单形式如下:
void write(int byteval) throws 10Exception
该方法通过 byteval 向文件写入指定的字节。在实际操作中,print() 方法和 println() 方法比 write() 方法更常用。
注意:尽管它们通常用于对控制台进行读取和写入字符,但是这些都是字节流。因为预定义流是没有引入字符流的 Java 原始规范的一部分,所以它们不是字符流而是字节流,但是在 Java 中可以将它们打包到基于字符的流中使用。
总结
各位小主,如果感觉有一点点帮助到你的话,麻烦留下赞赞一枚哦~