Java 流(Stream)、文件(File)和IO
java流在我们平时开发的时候经常被使用,对于小白来说首先要了解一下什么是流?流这个概念第一次听说肯定很抽象。
流的定义
流在Java中是指计算中流动的缓冲区。
从外部设备流向中央处理器的数据流成为“输入流”,反之成为“输出流”。
看懂了吗?说的专业可能似懂非懂,我们来说的通俗易懂一点:
Java中的流是个抽象的概念,当程序需要从某个数据源读入数据的时候,就会开启一个数据流,数据源可以是文件、内存或网络等等。相反地,需要写出数据到某个数据源目的地的时候,也会开启一个数据流,这个数据源目的地也可以是文件、内存或网络等等。所以说不管我们从数据库,文件读取文件的时候,其实都是个“流”,我们把数据放到这个“流”中先存放着,用到的时候再读取出来就好啦!这个也就是缓存区的概念!
缓存区本身也是一块内存空间,可以简单地理解为一段内存区域,可以简单地把缓冲区理解为一段特殊的内存。某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。
流的分类
java中的流大体上分为两大部分:
- 字符流
- 字节流
而字节流和字符流下面,又派生出好多的“子流”,具体的可以参考下面的图(图是我拷来的,不太清晰不要介意)
那么,什么又叫字符流和字节流呢?他们的区别是什么?
字符流
Java中的字符流处理的最基本的单元是Unicode码元(大小2字节),它通常用来处理文本数据。所谓Unicode码元,也就是一个Unicode代码单元,范围是0x0000~0xFFFF。在以上范围内的每个数字都与一个字符相对应,Java中的String类型默认就把字符以Unicode规则编码而后存储在内存中。然而与存储在内存中不同,存储在磁盘上的数据通常有着各种各样的编码方式。使用不同的编码方式,相同的字符会有不同的二进制表示。实际上字符流是这样工作的:
- 输出字符流:把要写入文件的字符序列(实际上是Unicode码元序列)转为指定编码方式下的字节序列,然后再写入到文件中;
- 输入字符流:把要读取的字节序列按指定编码方式解码为相应字符序列(实际上是Unicode码元序列)从而可以存在内存中。
说简单点,也就是存储单位为字符的“流”就叫做字符流。
字节流
Java中的字节流处理的最基本单位为单个字节,它通常用来处理二进制数据。Java中最基本的两个字节流类是InputStream和OutputStream,它们分别代表了最基本的输入字节流和输出字节流。InputStream类与OutputStream类均为抽象类,我们在实际使用中通常使用Java类库中提供的它们的一系列子类。
存储单位为字节码的“流”就叫做字节流。
字符流与字节流的区别
字节流与字符流之间主要的区别体现在以下几个方面:
- 字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
- 字节流默认不使用缓冲区;字符流使用缓冲区。
- 字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。
那么以上是关于java流的一些介绍,相信你们已经明白了什么是“流”,那么下面我们开始练习一些常见的java“流”。
读取控制台输入
Java 的控制台输入由 System.in 完成。
为了获得一个绑定到控制台的字符流,你可以把 System.in 包装在一个 BufferedReader 对象中来创建一个字符流。
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
while(true){
System.out.println("请输入:");
String str = br.readLine();
if (str.equals("quit")){
System.out.println("程序已退出!");
break;
}
System.out.println("我输入的是"+str+"\n");
}
}
输出如下:
请输入:
test1
我输入的是test1
请输入:
test2
我输入的是test2
请输入:
test3
我输入的是test3
请输入:
quit
程序已退出!
这里配合死循环,循环的读取控制台输入的字符,然后再次打印出来,直接输入quit跳出死循环,结束程序。
除了利用流操作之外,我们还可以借助java.util下的Scanner类来实现相同的功能:
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while(true){
System.out.println("请输入:");
String str = scan.nextLine();
if (str.equals("quit")){
System.out.println("程序已退出!");
break;
}
System.out.println("我输入的是"+str+"\n");
}
}
读取控制台输出
可能你们没想到,平时我们用的最多的System.out.println其实就是用到了流的操作,控制台的输出由 print( ) 和 println() 完成。这些方法都由类 PrintStream 定义,System.out 是该类对象的一个引用。
PrintStream 继承了 OutputStream类,并且实现了方法 write()。这样,write() 也可以用来往控制台写操作。
PrintStream 定义 write() 的最简单格式如下所示:
public static void main(String[] args) {
int b;
b = 'A';
System.out.write(b);
System.out.write('\n');
}
输出结果:
A
其实这个用的不多啦,我们还是用我们喜欢的print( ) 和 println()吧!
public static void main(String[] args) {
System.out.println("输出换行");
System.out.print("输出不换行");
}
读写文件
这个也是相当重要的拉!平时用的也是比较多,要好好学习一下!
下面将要讨论的两个重要的流是 FileInputStream 和 FileOutputStream,分别称为文件输入字节流和文件输出字节流。
读文件
首先我们需要在java项目根目录下面创建一个文件,叫test.txt,并写入字符如下:
hello world
I am kaikai
I love java/python!
读取文件代码如下:
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
InputStream fileInputStream = new FileInputStream(file);
while (true){
char i = (char)fileInputStream.read();
System.out.print(i);
}
}
输出结果:
hello world
I am kaikai
I love java/python!
以上是用字节流的写法,我们平时读文件的话,用字符流还是比较方便的,下面是字符流的写法
public static void main(String[] args) throws IOException {
BufferedReader file = new BufferedReader(new FileReader("test.txt"));
String data = null;
while ((data=file.readLine())!=null){ // 循环输出每一行的内容
System.out.println(data);
}
file.close(); // 关闭输入流
}
输出结果:
hello world
I am kaikai
I love java/python!
这里可以看到,FileReader外套了一个BufferedReader,那么这两个是什么东西呢?
FileReader类从InputStreamReader类继承而来。该类按字符读取流中数据。包含read方法,返回的是int类型,也可以理解为是字符类型。假如我们要对读取的字符一个一个输出的话,这样效率很低,所以借用了BufferedReader的特性,更加方便我们读取文件。为了提高效率我们可以使用BufferedReader对Reader进行包装,这样可以提高读取得速度,我们可以一行一行的读取文本,使用readLine()方法。
注意⚠️:这边顺带提一嘴,java中相对路径的默认路径是在项目根路径下面,不是你的同级目录下,所以你把文件放在同级目录然后直接写文件名去读取是会找不到文件的!这是一个坑!
写文件
先来个字节流的写法:
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
OutputStream outputStream = new FileOutputStream("test.txt");
byte[] bytes = {'L','O','V','E'};
outputStream.write(bytes);
}
接着是字符流的写法(推荐):
public static void main(String[] args) throws IOException {
PrintWriter printWriter = new PrintWriter(new FileWriter("test.txt"));
printWriter.println("i love vue"); // 输出内容
printWriter.println("i love html"); // 输出内容
printWriter.flush(); // 刷新文件内容
printWriter.close(); // 关闭输出流
}
FileWriter 类从 OutputStreamWriter 类继承而来。该类按字符向流中写入数据。
PrintWriter实现了PrintStream 中的所有 print 方法,除了那些用于写入原始字节的方法,对于那些字节,程序应该使用未编码的字节流进行写入,是不是很方便?我们只需要像System.out.println一样输出,然后flush一次,文件里就会有内容了!
要是不想每次都flush,可以在这里设置为autoFlush为true。
/**
* Creates a new PrintWriter.
*
* @param out A character-output stream
* @param autoFlush A boolean; if true, the <tt>println</tt>,
* <tt>printf</tt>, or <tt>format</tt> methods will
* flush the output buffer
*/
public PrintWriter(Writer out,
boolean autoFlush) {
super(out);
this.out = out;
this.autoFlush = autoFlush;
lineSeparator = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}
PrintWriter printWriter = new PrintWriter(new FileWriter("test.txt"),true);
细心的朋友会发现,这里每次都把文件内容覆盖了,那么如何在原有的文件内容上追加文件内容呢?
我们只需要创建文件流的时候,设置一个参数append为true即可
FileWriter(File file, boolean append) // api
new FileWriter("test.txt",true)
public static void main(String[] args) throws IOException {
PrintWriter printWriter = new PrintWriter(new FileWriter("test.txt",true),true);
printWriter.println("i love vue"); // 输出内容
printWriter.println("i love html"); // 输出内容
printWriter.flush(); // 刷新文件内容
printWriter.close(); // 关闭输出流
}
这样每次运行代码就会在原有的文件内容上进行追加了!
除了文件流相关的操作,还有其他很多的流,我们后续再慢慢介绍!当然文件流是重中之重,要熟练掌握!