写在前面
程序在运行时通常要和外部进行交互,从外部读取数据或向外部设备发送数据,就是所谓的输入/输出(Input/Output)。Java输入/输出系统(Java Input/output)简称Java I/O,在Java语言中所有和输入输出有关的技术都属于 I/O处理技术,例如读取文件内容、把偶才能文档内容、文件的复制等。
Java的I/O是通过java.io包下的类和接口支持,其中最重要的是5个类,分别是File、OutputStream、InputStream、Writer、Reader及一个接口Serializable。下面我们将了解Java I/O处理的基本知识,并对上述的5个类1个接口的部分类容进行了解,如果还想了解更多内容,见参考文献。
一、数据流概念&划分
1.1 数据流的概念
流的概念源于UNIX中的管道的概念。在UNIX中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外部设备、外部文件等。而Java中的流是有序的数据序列,是传递信息的载体,也是程序中的数据所经历的路径。流根据方向可以分为输入和输出两种,输入流是将数据源传递给程序;而输出流是将数据从程序传递到目的地,如内存、文件、网络等。
Java程序的所有读写操作都是通过流来实现的。根据组成流的不同,可以将其分为字节流和字符流,字节流是由字节组成的,主要用在处理二进制数据。字符流是由字符组成,主要是用于处理文本的数据。
流通过Java的输入/输出系统可以与物理设备连接,尽管所连接设备都不一样,但所有流的行为都是一致的。这意味着一个输入流可以抽象成多种不同类型的输入形式。例如:可以从键盘输入数据流,可以从硬盘输入数据流。同理,一个输入流可以从程序输出到屏幕、输出到硬盘或输出到打印机等。
Java中的流的实现是定义在Java.io包内部的类层次结构中。所以要使用流,必须导入java.io包。
1.2 数据流的划分
Java可以分为字节流和字符流。一般来说,用任何一种类型的流都可以完成相应的编程任务,但在一些特殊的情况下,选择其中某一种的流可以获得更高的效率。
字节流类型在Java的输入/输出系统各种提供了InputStream和OutputStream两个抽象类,分为实现字节数据的输入/输出。抽象类InputStream是所有字节输入流的父类,所以在InputStream类中包含的每个方法都会被其子类继承。InputStream子类如下图:
抽象类OutputStream是所有的字节输出流的父类,在实际使用中,一般使用该类的子类进行编程。但OutputStream类内部的方法是实现字节输出流的基础。OutputStream提供了抽象方法write(),下面这些类都是OutputStream类的子类且实现了write()方法:
字符流也分为读和写数据类,即Reader类和Writer类及其子类都是处理字符流。抽象类Reader是读取字符流的父类,所有继承自该类的子类都必须实现抽象方法read()和close()方法。
Writer类是字符流输出类的父类。所有继承该类的子类都必须实现抽象方法write()。
在Java中除了有在java.io包中的“流”类以外,还有一个内置流位于java.lang包中。这个类就是大家熟悉的System类,它表示系统类,但实际上System类对Java输入/输出系统是有支持的。System类包含了3个预定义的流变量,分别是in、out和err,他们被声明为public和static,这意味着可以通过System类的对象就可以直接调用。
System.out是标准的输出流,默认的情况下是想显示器输出。System.in是标准输入流,在默认情况下是输入键盘的数据。System.err是标准的错误流,默认情况下是向显示器输出。开发人员可以根据自身需要,将这3个内置流重新定向到任何兼容的I/O设备。
1.3 输入输出系统的关系结构
按照流的方向可以分为输入流和输出流。按照流的组成不同又可以分为字节流和字符流。所以Java的输入/输出类的基本机构可以划分为四类:字节输入流、字节输出流、字符输入流。字符输出流。
二、文件类:File类
2.1 File类简介
java.io.File类不属于Java流系统,但它是文件流进行文件操作的辅助类,主要用于操纵文件及文件目录,包括删除、重命名文件或目录、查询文件的属性、创建子目录、列出目录下的文件等。
File类的对象时一个“文件或目录”的抽象代表。File类的对象并不打开文件或目录,而是指定要操作的文件或目录。File类的对象一旦创建,就不能再更改,就是说它代表的抽象路径不能改变的。如果想要了解File类的是所有方法见jdk api。
2.2 通过File类对文件进行操作
下面举例介绍File类对象的创建并介绍其部分常用方法。
- 创建目录
File类提供了mkdir()和mkdirs()两个方法创建新目录。不同的是,mkdir()只能创建单级目录,不能含有子目录,换句话说,只能在以存在的目录中创建目录;mkdirs()可以创建多级目录,可以含有子目录,及可以在不存在的目录中创建文件夹。
import java.io.*;
public class Stream {
public static void main(String[] args) {
//mkdir&mkdirs创建单级目录&多级目录
File file = new File("D:\\test");
if(!file.exists()){
if(file.mkdir()){
System.out.println("D:\\test 创建成功!");
};
}else{
System.out.println("文件夹已存在!");
}
File file1 = new File("D:\\test\\secondDir");
if(!file1.exists()){
if(file1.mkdirs()){
System.out.println("D:\\test\\secondDir 创建成功!");
}
}else{
System.out.println("文件夹已存在!");
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
D:\test 创建成功!
D:\test\secondDir 创建成功!
Process finished with exit code 0
- 创建新文件
在使用createNewFile()方法声明IOException,所以使用该方法时,必须使用try…catch语句进行异常处理。
import java.io.*;
public class Stream {
public static void main(String[] args) {
File file = new File("D:\\test\\test.log");
String name=file.getName();
System.out.println(name);
if(!file.exists()) {
try {
file.createNewFile();
System.out.println("文件创建成功!");
} catch (IOException e) {
System.out.println("文件创建失败!");
e.printStackTrace();
}
}else {
System.out.println("文件已存在");
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
test.log
文件创建成功!
Process finished with exit code 0
- 使用delete删除文件
import java.io.*;
public class Stream {
public static void createFile(File file){
try {
file.createNewFile();
System.out.println("文件创建成功!");
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件创建失败!");
}
}
public static void main(String[] args) {
File file = new File("D:\\test\\test.log");
if(file.exists()){
file.delete();
System.out.println("删除成功!");
}else{
createFile(file);
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
删除成功!
Process finished with exit code 0
- 获得文件属性
在File类中提供了许多获取文件属性的方法,下面就列出常用方法:
import java.io.*;
public class Stream {
public static void main(String[] args) {
File file = new File("E:\\installpackage\\jdk-8u111-linux-x64.tar.gz");
if(file.exists()){
System.out.println("文件名:"+file.getName()); //文件名
System.out.println("文件路径:"+file.getPath()); //文件路径
System.out.println("文件大小:"+file.length()); //文件大小
System.out.println("是否是文件:"+(file.isFile()?"是":"否")); //判断是否为文件
System.out.println("文件最后修改时间:"+file.lastModified()); //文件最后修改时间
System.out.println("文件可读:"+(file.canRead()?"是":"否")+"\t文件可写:"+(file.canWrite()?"是":"否")); //文件可读可写属性
}else{
System.out.println("目标文件不存在!");
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
文件名:jdk-8u111-linux-x64.tar.gz
文件路径:E:\installpackage\jdk-8u111-linux-x64.tar.gz
文件大小:181442359
是否是文件:是
文件最后修改时间:1602837033282
文件可读:是 文件可写:是
Process finished with exit code 0
- 输出目录中的所有文件
输出一个目录的所有文件,File类提供了list()和listFiles()两种方法。不同点在于,list()只返回文件名,无路径信息;而listFiles()方法不仅返回文件名还有路径信息。
import java.io.*;
public class Stream {
public static void main(String[] args) {
File file = new File("E:\\BaiduNetdiskDownload");
String[] list =file.list();
System.out.println("------list()------");
for (String s : list) {
System.out.println(s);
}
System.out.println("------listFile()------");
File[] list1 =file.listFiles();
for (File file1 : list1) {
System.out.println(file1);
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
------list()------
hexo-theme-dark
jekyll-theme-basically-basic-master
OBS-Studio-26.0.2-Full-Installer-x64.exe
------listFile()------
E:\BaiduNetdiskDownload\hexo-theme-dark
E:\BaiduNetdiskDownload\jekyll-theme-basically-basic-master
E:\BaiduNetdiskDownload\OBS-Studio-26.0.2-Full-Installer-x64.exe
Process finished with exit code 0
2.3 牛刀小试
question:给定一个目录,如何列出目录下的所有文件和目录?
answer:首先需要能传递路径到程序中,用到Scanner类输出路径;给定的目录可能包含子文件夹,还需要对子文件夹进行递归查询。总的来说,是文件输出文件名,是目录则递归调用遍历方法。
import java.io.File;
import java.util.Scanner;
public class AllFile {
public static void fileList(File file){
if(file.isFile()){ //文件则直接输出文件名
System.out.println("\t"+file.getName());
}else if(file.isDirectory()){ //是目录则需要遍历目录
System.out.println(file.getPath()+"是目录");
File[] fileName = file.listFiles(); //获取文件中的所有文件和目录
for (File file1 : fileName) {
fileList(file1); //递归调用
}
}
}
public static void main(String[] args) {
System.out.println("请输入目标路径");
Scanner scanner = new Scanner(System.in);
String str = scanner.next();
File files = new File(str);
fileList(files);
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
请输入目标路径
D:\note
D:\note是目录
D:\note\collection是目录
ArrayList.txt
hive&mysql差异tip.txt
D:\note\Java是目录
D:\note\Java\highlevel是目录
high.java
reflect.java
stream.java
thread.java
tools.java
Process finished with exit code 0
三、字节流
字节流只要操作的是 B类型数据,而常用操作字节流的类是InputStream类和OutputStream类。
3.1 字节输入流:InputStream
所有的字节输入流都是InputStream的子类,InputStream提供的方法可以说是各种输入流基本的I/O方法接口。但InputStream类确实抽象类,不能实例化,程序中实使用的是它的各种子类对象。InputStream类的常用方法如下图:
下面我们以InputStream的子类FileInputStream的实例,对InputStream类的常用方法进行介绍。FileInputStream的详细方法见API。
- 使用read(byte[] b)读取文本内容
import java.io.*;
public class Test {
public static void main(String[] args) {
File file = new File("D:\\test\\test.txt");
byte[] b = new byte[512];
InputStream input = null;
{
try {
input = new FileInputStream(file); //使用FileInputStream为InputStream实例化
try {
input.read(b); //读取并放入数组b
input.close(); //关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
System.out.println("目标文件异常;");
e.printStackTrace();
}
System.out.println(new String(b));
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同
Process finished with exit code 0
从程序结果可以看出内容是可以被读取的。但是后面还有很多空格,这是因为B类型数组b开辟的空间远大于内容所需要的空间。剩下的空格会浪费内存空间,这是在程序中设计不合理的地方。为此,可以做下一种方法来优化。
- 开辟合适大小的byte数组
import java.io.*;
public class Test {
public static void main(String[] args) {
File file = new File("D:\\test\\test.txt");
byte[] b = new byte[(int)file.length()];
InputStream input = null;
{
try {
input = new FileInputStream(file);
try {
input.read(b); //读取并放入数组b
input.close(); //关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
System.out.println("目标文件异常;");
e.printStackTrace();
}
System.out.println(new String(b));
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同
Process finished with exit code 0
除了上述的方法,还可以循环地从文件中将一个个字节读取出来。这个方式直接使用read()方法,不需要进B数组。
- `循环读取文件内容
import java.io.*;
public class Test {
public static void main(String[] args) {
File file = new File("D:\\\\test\\\\test.txt");
byte[] b = new byte[(int)file.length()];
int temp = 0;
int len = 0;
try {
InputStream input = new FileInputStream(file);
while ((temp = input.read())!=-1){ //使while循环读取文件内容,到达结尾是-1
b[len]=(byte)temp;
len++;
}
input.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(b));
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同
Process finished with exit code 0
3.2 字节输出流:OutputStream
所有的字节输出流都是OutputStream的子类,OutputStream类也是抽象类,不能实例化,程序中使用的是它的各个子类对象。OutputStream类的常用方法如下:
下面我们以OutputStream的子类FileOutputStream为例,对OutputStream类的常用方法进行介绍。可以使用write()方法向指定的文件写入内容,如下。
- 向文件写入内容
import java.io.*;
public class Test {
public static void main(String[] args) {
File file = new File("D:\\test\\write.txt");
String str ="每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
byte[] b = str.getBytes(); //将字符串转换为B数组
try {
OutputStream out = new FileOutputStream(file); //使用FileOutputStream类实例化OutputStream
out.write(b); //写入数据
out.close(); //关闭输出流
System.out.println("写入成功!");
} catch (IOException e) {
System.out.println("写入失败!");
e.printStackTrace();
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
目标文件创建完毕
写入成功!
Process finished with exit code 0
在上面的程序中,通过String类的getByte()方法把字符串转换为byte数组类型。再通过FileOutputStream类的write()方法将内容向文件写入。值得一提的是,若指定文件在操作前不存在,则操作之后系统会自动创建该文件,并将内容写入。
提示:若继续执行,内容将都是一样的,因为重新执行程序会覆盖原文件的内容。在FileOutputStream类的构造方法中,已经提供了在文件末尾追加内容的功能。
public FileOutputStream(File file,boolean append)throws FileNotFoundException
将传入FileOutputStream构造方法中的参数append赋值为true即可,实例如下:
import java.io.*;
public class Test {
public static void main(String[] args) {
File file = new File("D:\\test\\write.txt");
String str ="每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
byte[] b = str.getBytes(); //将字符串转换为B数组
try {
OutputStream out = new FileOutputStream(file,true); //使用FileOutputStream类实例化OutputStream
out.write(b); //写入数据
out.close(); //关闭输出流
System.out.println("写入成功!");
} catch (IOException e) {
System.out.println("写入失败!");
e.printStackTrace();
}
}
}
若想换行,可以在需要换行的字符串出添加转义符“\r\n”即可 。
注意:
- 目标文件名可以任意指定,但不能与已存在的目录同名,否则会抛FileNotFoundException异常
- 目标文件存在的目录必须存在,否则抛FileNotFoundException异常
3.3 牛刀小试
import java.io.*;
import java.util.Scanner;
public class CopyFile {
public static void speed(File file1,File file2,int cache){
long t1 =System.currentTimeMillis(); //开始复制时间
InputStream inPut =null;
OutputStream outPut =null;
try {
inPut = new FileInputStream(file1); //创建读取文件的流
outPut = new FileOutputStream(file2); //创建保存文件的流
int len = 0;
byte[] bytes = new byte[cache];
while((len=inPut.read())!=-1){
outPut.write(bytes,0,len); //向目标位置写文件,实现文件复制
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
outPut.close();
inPut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
long times = System.currentTimeMillis()-t1; //运行时间计算
long length = file1.length();
long lengthKb=length/1024; //转换文件大小单位
System.out.println("花费时间:"+times+"ms");
System.out.println("文件大小:"+lengthKb+"kb");
System.out.println("速度:"+((float)lengthKb/1024/times*1000)+"MB/s");
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int cache =0;
while(true){
System.out.println("请选择版本编号:\n\t1.高速版本\n\t2.普通版本\n\t3.退出");
System.out.println("请输入选项:");
int num = scan.nextInt();
if(num==3){
System.out.println("退出程序成功!");
System.exit(0);
}
System.out.println("请输入源文件:");
String path1 = scan.next();
System.out.println("请输入目标文件");
String path2 = scan.next();
File file1 = new File(path1);
File file2 = new File(path2);
if(file1.exists()){
switch (num){
case 1:
cache = 1024;
speed(file1,file2,cache);
break;
case 2:
cache = 512;
speed(file1,file2,cache);
break;
default:
System.out.println("无该选项");
}
}else{
System.out.println("指定文件不存在");
}
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
请选择版本编号:
1.高速版本
2.普通版本
3.退出
请输入选项:
1
请输入源文件:
E:\installpackage\elasticsearch-analysis-ik-6.2.2.zip
请输入目标文件
D:\test\test.zip
花费时间:15230s
文件大小:4397kb
速度:0.28193995MB/s
请选择版本编号:
1.高速版本
2.普通版本
3.退出
请输入选项:
四、字符流
在程序设计中,绝大多数应用程序都是使用字符流读取或 写入“文本数据”。因此处理“文本数据”时,建议使用字符流。
4.1字符输入流:Reader 类
字符输入流类都是Reader的子类,而Reader是根类Object的子类,是对字符输入流基本功能的抽象。Reader类提供的主要方法和InputStream类极为类似,最大的区别就是Reader的类读取数据的单位是字符(char),也就是每次最少读入两个字节的数据,Reader体系中读数据的方法都是以字符作为最基本的单位。
同样的,Reader的方法没有实质的实现,但在子类被重写后将完成实质的输入功能。Reader类本身就是一个抽象类,如果要使用Reader类,则需要使用其子类为其实例化。Reader类的方法如下图:
下面我们将以Reader的子类FileReader为例,对Reader类的常用方法进行介绍。FileReader的构造方法如下:
public FileReader(File file) throws FIleNotFoundException
与前面的InputStream类读取文件内容类似,这里可以直接使用read(char c[])方法读取文件的内容,
- 读取文件内容
import java.io.*;
public class ReadDemo {
public static void main(String[] args) {
File file = new File("D:\\test\\test.txt");
char[] c = new char[(int)file.length()]; //创建File类实例
try {
Reader reader = new FileReader(file); //使用FileReader为Reader实例化
reader.read(c); //读取内容存入数组c
reader.close(); //关闭流
} catch (IOException e) {
e.printStackTrace();
System.out.println("读取文件失败!");
}
System.out.println(new String(c)); //输出读取内容
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同
Process finished with exit code 0
- 循环读取文件内容
import java.io.*;
public class ReadDemo1 {
public static void main(String[] args) {
File file = new File("D:\\test\\test.txt");
char[] c = new char[(int)file.length()];
try {
Reader reader = new FileReader(file);
int temp = 0;
int len = 0;
while((temp=reader.read())!=-1){
c[len]=(char)temp;
len++;
}
reader.close();
} catch (IOException e) {
System.out.println("文件读取失败!");
e.printStackTrace();
}
System.out.println(c);
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同
Process finished with exit code 0
4.2字符输出流:Writer类
Writer类和OutputStream类,在功能上是一致的,但两者还是存在区别的,前者写入数据的单位是字符,而后者写入的是字节。Writer类本身也是一个抽象类,不能实例化。要想使用则需要通过其子类实例化。Writer类的方法如下:
下面我们将以Writer的子类FileWriter为例,对Writer类的常用方法进行介绍。FileWriter的构造方法如下:
public FileWriter(File file) throws IOException
public FileWriter(File file,boolean append) throws IOException
a.向文件写入内容
这里我们使用write(char[] cbuf)和write(String str)来演示
- 使用write(char[] cbuf)向文件写入内容
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriteDemo {
public static void main(String[] args) {
File file = new File("D:\\test\\secondDir\\one.txt");
String str ="每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
char[] c = str.toCharArray();
try {
Writer writer = new FileWriter(file);
writer.write(c);
writer.close();
System.out.println("写入成功!");
} catch (IOException e) {
System.out.println("写入失败!");
e.printStackTrace();
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
写入成功!
Process finished with exit code 0
上面程序中,需要将字符串转换为char类型数组。此外,我们还可以使用write(String str)向文件写入字符串。
- 使用write(String str)向文件写入内容
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriterDemo1 {
public static void main(String[] args) {
File file = new File("D:\\test\\secondDir\\two.txt");
String str ="每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
try {
Writer writer = new FileWriter(file);
writer.write(str);
writer.close();
System.out.println("写入成功");
} catch (IOException e) {
System.out.println("写入失败");
e.printStackTrace();
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
写入成功!
Process finished with exit code 0
b.追加文件内容
使用字符输出流操作是,可以实现文件的追加内容。这里说两个方式:一个是使用Writer类的append()方法,另一个是使用FileWriter的构造方法FileWriter(File file,boolean append)。两者最大的区别是append()可以实现内容的追加,但是无论重复多写少次,写入的内容都是相同的;而使用FileWriter的构造方法则可以不断更新内容。
- 使用append()实现文件的追加
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriterDemo2 {
public static void main(String[] args) {
File file = new File("D:\\test\\secondDir\\three.txt");
String str ="每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
try {
Writer writer = new FileWriter(file);
writer.write(str);
writer.append("\r\n不要生气要争气,不要看破要突破,不要嫉妒要欣赏,不要拖延要积极,不要心动要行动。");
writer.close();
System.out.println("写入成功");
} catch (IOException e) {
System.out.println("写入失败");
e.printStackTrace();
}
}
}
上面的程序重复执行3词后,数据无任何改变。append()方法仅仅是追加字符串的内容。接下里,使用使用FileWriter的构造方法FileWriter(File file,boolean append)来实现内容的追加。
- 使用FileWriter(File file,boolean append)追加文件内容
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriteDemo3 {
public static void main(String[] args) {
File file = new File("D:\\test\\secondDir\\four.txt");
String str ="\r\n每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
try {
Writer writer = new FileWriter(file,true);
writer.write(str);
writer.close();
System.out.println("写入成功");
} catch (IOException e) {
System.out.println("写入失败");
e.printStackTrace();
}
}
}
4.3 字节流和字符流不同点
到目前为止,我们学习的字符流和字节流的使用都非常类似,除了在处理数据单位上的不同之外,还有哪些不同呢?实际上,字节流是对文件本身进行操作,不需要通过缓冲区;而字符流需要通过缓冲区来操作文件。也就是说,就算没有执行close()方法关闭字节流,还是可以向文件输出内容的。但是字符流,若不执行close()方法,就无法向文件写入内容。
- 操作字节流不执行close()方法
import java.io.*;
public class Test {
public static void main(String[] args) {
File file = new File("D:\\test\\write2.txt");
String str ="每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
byte[] b = str.getBytes(); //将字符串转换为B数组
try {
OutputStream out = new FileOutputStream(file,true); //使用FileOutputStream类实例化OutputStream
out.write(b); //写入数据
// out.close(); //不执行关闭输出流操作
System.out.println("写入成功!");
} catch (IOException e) {
System.out.println("写入失败!");
e.printStackTrace();
}
}
}
从程序执行结果来看,不执行close()方法也还是可以向文件 中写入内容的。
- 操作字符流是不执行close()方法
import java.io.Writer;
public class WriterDemo1 {
public static void main(String[] args) {
File file = new File("D:\\test\\secondDir\\five.txt");
String str ="每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
try {
Writer writer = new FileWriter(file);
writer.write(str);
// writer.close(); //不执行关闭输出流操作
System.out.println("写入成功");
} catch (IOException e) {
System.out.println("写入失败");
e.printStackTrace();
}
}
}
从程序结果来看文件内无内容,因为程序执行过程中引入了缓冲区机制,即写操作先将内容太放入缓冲区。若写操作完毕后,不将缓冲区内容刷新到文件中,文件将为空,但可以使用flush()强行清除内容。
- 使用flush()方法清除内容
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriterDemo1 {
public static void main(String[] args) {
File file = new File("D:\\test\\secondDir\\five.txt");
String str ="每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同";
try {
Writer writer = new FileWriter(file);
writer.write(str);
writer.flush(); //强制清除内容
// writer.close(); //不执行关闭输出流操作
System.out.println("写入成功");
} catch (IOException e) {
System.out.println("写入失败");
e.printStackTrace();
}
}
}
Tips:执行close()方法会默认执行flush()方法,因此close()方法可以将缓冲区的内容清除。
五、转换流
从继承关系来看,InputStreamReader类和OutputStreamWriter类都是属于字符流的,但他们主要是用来将以存在的字节流转换为字符流。转换流在转换时,可依照系统默认的字符编码来转换,或者开发人员指定的字符编码。
5.1 输入字节流——字符流:InputStreamReader类
InputStreamReader类是Reader的子类,可以将一个字节输入流转变为字符输入流,在转换时默认使用本地操作系统的字符编码或指定其他字符编码。InputStreamReader类的常用方法如下:
- 验证InputStreamReader类
import java.io.*;
public class InputStreamReaderDemo {
public static void main(String[] args) {
File file = new File("D:\\test\\test.txt"); //创建File实例
try {
FileInputStream fin = new FileInputStream(file); //创建FileInputStream实例
InputStreamReader isr = new InputStreamReader(fin); //创建InputStreamReader实例
System.out.println("系统默认字符:"+isr.getEncoding()); //获取字符类型
int temp =0;
while ((temp=isr.read())!=-1){ //循环读取内容
System.out.print((char)temp); //转换为char输出
}
isr.close();
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
系统默认字符:UTF8
每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同
Process finished with exit code 0
Tips:若没有InputStreamReader进行转换会出现乱码!
5.2 输出字节流——字符流:OutputStreamWriter类
使用OutputStreamWriter类可以将字节输出流转换为字符输出流,在转换时默认使用本地操作系统的字符编码或指定其他字符编码。OutputStreamWriter类的常用方法如下图:
- 验证OutputStreamWriter类
import java.io.*;
public class OutputStreamWriterDemo {
public static void main(String[] args) {
try {
File file = new File("D:\\test\\secondDir\\six.txt"); //创建File实例
FileOutputStream fos = new FileOutputStream(file); //创建FileOutputStream实例
OutputStreamWriter ops = new OutputStreamWriter(fos); //创建OutputStreamWriter实例
String str ="尔曹身与名俱灭,不废江河万古流!";
ops.write(str); //将字符串写入文件
System.out.println("写入成功!");
ops.close(); //关闭流,先开的后关,后开的先关
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
写入成功!
Process finished with exit code 0
六、缓冲流:BufferReader类
在读写数据时,让数据有缓冲区能有效减少系统实际对有原始数据来源做存取的次数,因此一次做多个数据单位单位操作。相较而言,对于从文件读取数据或将数据写入文件,比起对缓冲区的读写要慢得多。因此使用缓冲区的流,一般都会比没有缓冲区的流效率更高。拥有缓冲区的流称为缓冲流,包括BufferedInputStream、BufferedOuuputStream类和BufferedReader、BufferedWriter类。缓存把数据从原始流成块读入或把数据累到一个大数据块后再成批写出,通过减少系统资源的读写次数来加快程序的运行。这里只介绍BufferedReader类和BufferedWriter类。
6.1 BufferedReader类
BufferedReader是Reader的子类。Reader类的read()方法每次都是从数据源读入一个字符,为了提高效率,可以采用BufferedReader来配合其他字节流。BufferedReader带有缓冲区,它可以先把一批数据读到缓冲区内。接下来的读操作都是从缓冲区内获取数据,避免每次都是从数据源读取数据并进行字符编码转换,从而提高读操作的效率。BufferedReader不但提供通用的缓冲方式文本读取,而且还提供了很多很实用的方法。如eadLine可以读取分行文本。BufferedReader类的常用方法如下:
Tips:BufferReader是针对Reader的,是需要与其他字符流配合使用的,不直接针对文本读取。
- 验证BufferedReader类
import java.io.*;
public class BufferedReaderDemo {
public static void main(String[] args) {
File file = new File("D:\\test\\test.txt");
String str ="";
try {
Reader read = new FileReader(file);
BufferedReader bin = new BufferedReader(read);
str=bin.readLine();
bin.close();
read.close();
} catch (IOException e) {
System.out.println("读取失败!");
e.printStackTrace();
}
System.out.println(str);
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
每个人为了活下去都必须找到点燃自己心头之火的力量,那烈焰就是灵魂的食粮。——刘同
Process finished with exit code 0
BufferedReader类还可以接收来自键盘输入的字节流,但需要使用InputStreamReader将键盘的字节流Sysyem.in转换为字符流。
- 从键盘获取数据
import java.io.*;
public class BufferedReaderDemo {
public static void main(String[] args) {
String str ="";
BufferedReader bin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入内容:");
try {
str=bin.readLine();
bin.close();
} catch (IOException e) {
System.out.println("读取失败!");
e.printStackTrace();
}
System.out.println("您输入的内容:\n"+str);
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
请输入内容:
时间就是金钱!
您输入的内容:
时间就是金钱!
Process finished with exit code 0
上面的程序中,虽然输入数据没有长度限制,但是仅仅可以读取一行数据而已。
6.2 BufferedWriter类
BufferedWriter类是Writer的子类。与Writwe相比,BufferWriter了诶主要的改变是重写了flush()方法,该方法可以确保缓冲区里的数据确实被写到输出流中。使用BufferedWriter类时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。BufferedWriter流提供了缓冲区,能更有效率地写出字符数据。BufferedWriter类的常用方法如下:
- 验证BufferedWriter类
import java.io.*;
public class BufferedWriterDemo {
public static void main(String[] args) {
File file = new File("D:\\test\\secondDir\\seven.txt");
String[] str = {"坚持不懈","锲而不舍","滴水穿石","一气呵成"};
try {
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw);
for (String s : str) {
bw.write(s);
bw.newLine();
}
bw.close();
fw.close();
System.out.println("写入成功!");
} catch (IOException e) {
System.out.println("写入失败!");
e.printStackTrace();
}
}
}
"C:\Program Files\Java\jdk1.8.0_231\bin\java.exe" ...
写入成功!
Process finished with exit code 0
总结
- Java中的流是有序的数据序列,是传递数据信息的载体,也是程序中的数据所经历的路径。
- Java中的流类可以分为字节输入流、字节输出流、字符输入流、字符输出流,需要了解各个类的归属类别。
- File类不属于Java流系统,但它是文件流进行文件操作的辅助类,主要用于操纵文件及文件目录,包括删除、重命名文件或目录,查询文件的属性,创建子目录,列出目录下的 文件等。
- 字节流一般用于读取或写入二进制数据,如图片、音频文件以及自字符流无法正常处理的二进制可执行文件和压缩文件等。字符流主要用于读取或写入“文本数据”。
- InputStream类与OutputStream类和Reader类和Writer类均为抽象类,不能实例化,需要通过它们的子类实例化。使用不同的子类会有不同的特性。
- OutputReader类和InputStreamWriter类都属于字符流,但它们可以将字节流转换为字符流,可有效防止乱码。
- 使用缓冲流可以减少系统资源的读写次数加快程序的执行。
PS:如果有写错或者写的不好的地方,欢迎各位大佬在评论区留下宝贵的意见或者建议,敬上!如果这篇博客对您有帮助,希望您可以顺手帮我点个赞!不胜感谢!
原创作者:wsjslient |
参考来源:Java编程手记——从实践中学习Java /欧二强等编著. --北京:清华大学出版社,2013.6 |