Java之IO流技术详解
何为IO?
首先,我们看看百度给出的解释。
I/O输入/输出(Input/Output),分为IO设备和IO接口两个部分。
i是写入,Input的首字母。o是输出,Output的首字母。
IO 也称为IO流,IO = 流,它的核心就是对文件的操作,对于 字节 、字符类型的输入和输出流。
IO分类
IO流主要分为两大类,字节流和字符流。而按照作用分类,可以分为输入流和输出流。
流?
在电脑上的数据有三种存储方式,一种是外存,一种是内存,一种是缓存。比如电脑上的硬盘,磁盘,U盘等都是外存,在电脑上有内存条,缓存是在CPU里面的。外存的存储量最大,其次是内存,最后是缓存,但是外存的数据的读取最慢,其次是内存,缓存最快。这里总结从外存读取数据到内存以及将数据从内存写到外存中。对于内存和外存的理解,我们可以简单的理解为容器,即外存是一个容器,内存又是另外一个容器。那又怎样把放在外存这个容器内的数据读取到内存这个容器以及怎么把内存这个容器里的数据存到外存中呢?
我们可以将这个整个看成一个水池。水池里面连接了出水口管与注水管。出水相当于我们的输出流。注水相当于我们的输入流。
File(文件类)
首先我们如果需要用IO流的话,我们肯定是需要创建一个我们所谓的“水池”的。
怎么创建呢?我们直接创建一个File类对象。
- package IoDemo;
- import java.io.*;
- public class IoDemo {
- public static void main(String[] args) {
- //创建File对象
- File file = new File("D:\\\\test.txt");
- }
- }
实际上这个对象,说白了就是用来储存一个IO流的文件地址的。
创建了对象后,它也没有任何什么操作,操作得使用这个对象调用方法。
我们先使用createNewFile()
方法创建我们上面那个路径的文件。
- file.createNewFile();
注意定义文件路径时,可以用“/”或者“\\”。
并且在创建一个文件时,如果目录下有同名文件将被覆盖。
因为有时候,可能我们的路径下已经存在了相对应的同名文件,所以我们要使用exists()
方法判断文件是否已经存在。
- //创建File对象
- File file = new File("D:\\\\test.txt");
- //创建文件
- try {
- //判断文件是否存在
- if(!file.exists()){
- file.createNewFile();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
其实,File类里面还存在许多方法,用法都是可以直接调用的,作为一个合格的程序员,我们可以直接阅读相关说明而在合适的时候使用对应方法。
下面列举一些常用方法。
①、创建方法
1.boolean createNewFile() 不存在返回true 存在返回false
2.boolean mkdir() 创建目录,如果上一级目录不存在,则会创建失败
3.boolean mkdirs() 创建多级目录,如果上一级目录不存在也会自动创建
②、删除方法
1.boolean delete() 删除文件或目录,如果表示目录,则目录下必须为空才能删除
2.boolean deleteOnExit() 文件使用完成后删除
③、判断方法
1.boolean canExecute()判断文件是否可执行
2.boolean canRead()判断文件是否可读
3.boolean canWrite() 判断文件是否可写
4.boolean exists() 判断文件或目录是否存在
5.boolean isDirectory() 判断此路径是否为一个目录
6.boolean isFile() 判断是否为一个文件
7.boolean isHidden() 判断是否为隐藏文件
8.boolean isAbsolute()判断是否是绝对路径 文件不存在也能判断
④、获取方法
1.String getName() 获取此路径表示的文件或目录名称
2.String getPath() 将此路径名转换为路径名字符串
3.String getAbsolutePath() 返回此抽象路径名的绝对形式
4.String getParent()//如果没有父目录返回null
5.long lastModified()//获取最后一次修改的时间
6.long length() 返回由此抽象路径名表示的文件的长度。
7.boolean renameTo(File f) 重命名由此抽象路径名表示的文件。
8.File[] liseRoots()//获取机器盘符
9.String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
10.String[] list(FilenameFilter filter) 返回一个字符串数组,命名由此抽象路径名表示的目录中满足指定过滤器的文件和目录。
字节流的使用
我们现在已经建好这个“水池”了,同时还可以使用方法来获取到“水池”的一些信息。
那接下来,我们可以试着创建一套流。
字节流,相当于一滴滴的水在一个管道里运输。这个管道我们可以抽象的称之为流。
我们还是先看看知乎上,某些大佬的解释。
大佬的解释
图中蓝色为主要对应部分,红色为不对应部分,黑色的虚线部分代表这些流一般需要搭配使用。从上面的图中可以看出Java IO中的字节流是非常对称的。我们来看看这些字节流中不对称的几个类。
- LineNumberInputStream 主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。
- PushbackInputStream 的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分的BufferedOutputStream 几乎实现相近的功能。
- StringBufferInputStream 已经被Deprecated,本身就不应该出现在InputStream 部分,主要因为String 应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。
- SequenceInputStream 可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO 包中去除,还完全不影响IO 包的结构,却让其更“纯洁”――纯洁的Decorator 模式。
- PrintStream 也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream 写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO 包!System.out 和System.out 就是PrintStream 的实例。
对于不完全学透的看法
听起来有些难度哈,这些高级用法我们先不管。我们先来看看它到底怎么用的。
那有人又说了,那如果不去学完整,以后要是不会用怎么办?
答:其实只需要掌握主要方法就可以,因为你如果需要实现一个非常用的东西,你肯定是事先就需要去查阅相关资料,而日常开发中,常用的也就可以信手拈来啦。
OutputStream(字节输出流)
这个抽象类是表示输出字节流的所有类的超类。
!!!这里的输出可不是我们正常的输出,它反而想法,是将字节写入到文件,可以理解为将字节输出到文件。
看看里面有些啥常用方法。
我们先演示一下,字节输出流如何使用?
- //定义一个String值
- String str = "Hello World";
等下我们利用字节流将这个String写入我们的文件里面。
等等!这里是字节流,我怎么可以直接写入String
?
嘿嘿,我们使用String类中的getBytes()方法将String转换成字节数组。
- //将String值转换成字节数组
- byte[] bytes = str.getBytes();
整个代码是这样的:
- package IoDemo;
- import java.io.*;
- public class IoDemo {
- public static void main(String[] args) {
- //创建File对象
- File file = new File("D:\\\\test.txt");
- //创建文件
- try {
- //判断文件是否存在
- if(!file.exists()){
- file.createNewFile();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- //定义一个String值
- String str = "Hello World";
-
- //创建字节流
- FileOutputStream fos = null;
- try {
- //将File对象(即地址)给到FileInputStream
- fos = new FileOutputStream(file);
- //将String值转换成字节数组
- byte[] bytes = str.getBytes();
- //循环将字节数组写入到文件中
- for (int i = 0; i < bytes.length; i++) {
- //将bytes写入文件
- fos.write(bytes[i]);
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
我们进我们的文件看看输出效果。
就已经写入了我们的String里面的内容了。
InputStream(字节输入流)
这里我也就不解释了,他这个输入不是将内容输入到文件,而是将文件里面的内容输入到我们的代码中。
- //创建字节输入流
- FileInputStream fis = null;
- try {
- //将File对象(即地址)给到FileInputStream
- fis = new FileInputStream(file);
- //创建字节数组
- byte[] bytes = new byte[1024];
- //输出字节数组
- int len = 0;
- while ((len = fis.read(bytes)) != -1) {
- System.out.println(new String(bytes, 0, len));
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
在上文,我们已经写入了一个Hello Word
我们现在这段代码,我们将会输出文件中的内容。
- len = fis.read(bytes)) != -1
我现在还是先讲解一下这一段。
我们的
len
是定义的一个数值。而read()
方法,是为了读取到整个文件里面内容的字节长度,就像数组的lenth()
一样。
而!= -1
是因为,如果值为-1
那么,这个文件可以说是没有数据的,空的你也没必要输出。
而我们字节流呢,一般是用于读取二进制文件,如音频、图片这些,大片的文字内容还是交给字符流吧。
字符流的使用
上面的字节流,它是一个个的输出的,而我们现在的字符流,是大水管输出,一次可以运输一段。
一般可以用记事本打开的文件,我们可以看到内容不乱码的。就是文本文件,可以使用字符流。而操作二进制文件(比如图片、音频、视频)必须使用字节流。
FileWriter(字符输出流)
老规矩哈,方法自己看看。
先写入一个String
试试。
- package IoDemo;
- import java.io.*;
- public class IoDemo {
- public static void main(String[] args) {
- String str = "嘿嘿!我是字符流·········";
- //创建File对象
- File file = new File("D:\\\\test.txt");
- try {
- //将str用writer写入文件
- FileWriter fw = new FileWriter(file);
- fw.write(str);
- //关闭流,字符流必须关闭流才可以输出
- fw.close();
-
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
写入效果如下:
实际上,写入字符串就几个步骤。
- //1.将str用writer写入文件
- FileWriter fw = new FileWriter(file);
- //2.使用write()方法写入字符串
- fw.write(str);
- //3.关闭流,字符流必须关闭流才可以输出
- fw.close();
FileReader(字符输出流)
- //输出文件内容
- try {
- //创建输入字符流
- FileReader fr = new FileReader(file);
- //输出文件内容
- int ch = 0;
- while ((ch = fr.read()) != -1){
- System.out.print((char)ch);
- }
- //关闭流
- fr.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
这里采用
Char
挨个字符遍历数据。
包装流
缓冲流
为什么使用缓冲流呢?
简单地说就是,写入数据更快,可以加速写入。
缓冲流,也叫高效流。 能够高效读写缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化对象等等。 它是四个基本File流的增强,所以也是4个流,按照数据类型分类。 缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO读取次数,从而提高读写的效率。
- package IoDemo;
- import java.io.*;
- public class IoDemo {
- public static void main(String[] args) {
- //创建File对象
- File file = new File("D:\\\\test.txt");
- //创建缓冲流
- BufferedWriter bw = null;
- try {
- //创建FileWriter对象
- FileWriter fw = new FileWriter(file);
- //创建BufferedWriter对象
- bw = new BufferedWriter(fw);
- //写入一首诗分四次写入
- bw.write("窗前明月光,");
- bw.write("疑是地上霜。");
- bw.write("举头望明月,");
- bw.write("低头思故乡。");
- bw.flush();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
这个很简单,自己看就可以看懂。
转换流
InputStreamReader:把字节输入流转换为字符输入流
OutputStreamWriter:把字节输出流转换为字符输出流
我就不写示范了,网上找了一段。
- //转换流实现将 a.txt 文件 复制到 b.txt 中
-
- //1、创建源和目标
- File srcFile = new File("io"+File.separator+"a.txt");
- File descFile = new File("io"+File.separator+"b.txt");
- //2、创建字节输入输出流对象
- InputStream in = new FileInputStream(srcFile);
- OutputStream out = new FileOutputStream(descFile);
- //3、创建转换输入输出对象
- Reader rd = new InputStreamReader(in);
- Writer wt = new OutputStreamWriter(out);
- //3、读取和写入操作
- char[] buffer = new char[10];//创建一个容量为 10 的字符数组,存储已经读取的数据
- int len = -1;//表示已经读取了多少个字符,如果是 -1,表示已经读取到文件的末尾
- while((len=rd.read(buffer))!=-1){
- wt.write(buffer, 0, len);
- }
- //4、关闭流资源
- rd.close();
- wt.close();
扩展
然后,找相关资料时,还涨了个知识。大家可以看看。
合并流
合并流:把多个输入流合并为一个流,也叫顺序流,因为在读取的时候是先读第一个,读完了在读下面一个流。
- SequenceInputStream seinput = new SequenceInputStream();
- new FileInputStream("io/1.txt"), new FileInputStream("io/2.txt"));
- byte[] buffer = new byte[10];
- int len = -1;
- while((len=seinput.read(buffer))!=-1){
- System.out.println(new String(buffer,0,len));
- }
- seinput.close();
这里先是创建了一个SequenceInputStream
对象,然后new
了两个FileInputStream
对象,我们使用合并流读取,先使用seinput.read方法
读取1.txt
的内容,然后再对其2.txt
的内容。