笔记记的是一个博客园的博主的。
目录
Input(输入)读 Read,粘贴分享 临时性
Output(输出)写 Write,复制保存 持久化
File 类
构造方法
获取功能的方法
判断功能的方法
创建/删除功能的方法
做目录的遍历
在这么多的功能方法的支持下,我们可以递归遍历文件夹下的所有文件及其子文件
File 的绝对路径和相对路径
字节/字符流(以基本操作单位为区分的流),依次介绍四大流
第一大流 字节 输出流(OutputStream)
例子:文件 输出流(FileOutputStream)
第二大流 字节 输入流(InputStream)
例子:文件 输入流(FileInputStream)
字符 流(降低兼容,提升效率)
第三大流 字符 输入流(Reader)
第四大流 字符 输出流(Writer)
特殊点:关闭 close 和 刷新 flush
1.缓冲流(高效流)
字节 缓冲流(Buffered [In/Out] putStream)
字符 缓冲流(Buffered [Read/Writ] er)
2.转换流(解码/编码流)
字节转字符流 / 解码流(InputStreamReader)
字符转字节流 / 编码流(OutputStreamWriter)
3.序列化流(对象流)
对象输出流 ObjectOutputStream 写
对象输入流 ObjectInputStream 读
4.打印流(显示输出流)
5.数据流(包裹运输流)Data [In/Out] putStream
6.Properties 属性类
7.提一嘴节点流和包装流(处理流)
节点流和处理流的区别和联系
处理流的功能主要体现在以下两个方面
首先什么是 IO 流,流呢?是一种抽象概念(比喻数据的无结构化传递像“流”)。
而 IO 即 input(输入) 和 output(输出),代表了输入和输出操作。
在IO流里,输入/输出分为4步:格式化/解析,缓冲,编码转换和传递。
想想看,当我们要把货车上的货物搬运到仓库里需要几步?
1.确定搬运方式,比如是液体就用瓶子,是固体就用绳子。(格式化/解析)
2.搬运工人在搬运时的过程就承接到了缓冲的作用。(缓冲)
3.比如这个货物原来是果子,但当地人喜欢喝果汁,所以就把果子转化成果汁,感觉果汁更适应运输。(编码转换)
4.一切准备好了,就开始传递了。(传递)bit 单位。
前言,我们每次参与 IO 流操作的时候,必须明确四个目的:
意思就是我们在读取,比如读书、粘贴操作(当我们已复制的前提下,我们想看看复制的内容是什么时,我们就会把它 Ctrl + V 粘贴出来,目的在于分享)
分类(字节 byte / 字符 character): | 字节 输入流 | 字符 输入流 |
---|---|---|
基类(超类): | InputStream | Reader |
访问文件: | FileInputStream | FileReader |
访问数组: | ByteArrayInputStream | CharArrayReader |
访问管道: | PipedInputStream | PipedReader |
访问字符串: | - | StringReader |
缓冲流: | BufferedInputStream | BufferedReader |
转换流: | - | InputStreamReader |
对象流: | ObjectInputStream | - |
抽象基类(也叫抽象超类): | FilterInputStream | FilterReader |
打印流: | - | |
推回输入流: | PushbackInputStream | PushbackReader |
特殊流: | DataInputStream | - |
注解:倾斜 + 下划线 = 抽象类,无法创建实例;而红色的代表节点流,必须直接和指定的物理节点关联。- 代表缺省(此注解下个表格同样适用) |
意思就是我们在记录,比如写书、复制操作(当我们想记住什么时,我们就会把它 Ctrl + C 复制起来,目的在于保存)
分类(字节 byte / 字符 character): | 字节 输出流 | 字符 输出流 |
---|---|---|
基类(超类): | OutputStream | Writer |
访问文件: | FileOutputStream | FileWriter |
访问数组: | ByteArrayOutputStream | CharArrayWriter |
访问管道: | PipedOutputStream | PipedWriter |
访问字符串: | - | StringWriter |
缓冲流: | BufferedOutputStream | BufferedWriter |
转换流: | - | OutpurStreamWriter |
对象流: | ObjectOutputStream | - |
抽象基类(也叫抽象超类): | FilterOutputStream | FilterWriter |
打印流: | PrintStream | PrintWriter |
推回输入流: | - | - |
特殊流: | DataOutputStream | - |
注:以后会补充全比照的表格 |
我们再来细说里面的类
java.io.File
解析上面的两句话:一是File(文件)跟流无关,因为 File 类不能对文件进行 IO 操作(读写操作或输入输出操作);二是 D:\\文件夹 和 D:\\文件目录\\文件.txt,都是File类的操作对象(即文件夹和文件本身),其实文件夹只是一种“特殊”的文件罢了。
在 Java 中一切都是对象,我们要参与 File 类就先构造一个它的对象。
File(File parent, String child)根据 parent 抽象路径名(文件目录)+ child 路径名字符串(文件路径)创建一个新 File 实例
File(String pathname)通过全路径名来创建一个新 File 实例
File(String parent,String child)根据 parent 路径名字符串和 child 路径名字符串来创建一个新 File 实例
File(URI uri)通过给定的file:URI转换为一个抽象路径名来创建一个新的File实例
1. public String getAbsolutePath():返回字符串 = File 的绝对路径名
2. public String getPath():返回字符串 = File 的路径名
3. public String getName():返回字符串 = File 的文件/目录名称
4. public long length():返回 long 数据 = File 的文件长度(单位:字节 byte)
1、
public boolean exists()
:此File表示的文件或目录是否实际存在。
2、public boolean isDirectory()
:此File表示的是否为目录。
3、public boolean isFile()
:此File表示的是否为文件。
public boolean createNewFile()
:文件不存在,创建一个新的空文件并返回true
,文件存在,不创建文件并返回false
。public boolean delete()
:删除由此File表示的文件或目录。public boolean mkdir()
:创建由此File表示的目录。public boolean mkdirs()
:创建由此File表示的目录,包括任何必需但不存在的父目录。- 其中,
mkdirs()
和mkdir()
方法类似,但mkdir()
,只能创建一级目录,mkdirs()
可以创建多级目录比如//a//b//c
,所以开发中一般用mkdirs()
;这些方法中值得注意的是createNewFile方法以及mkdir与mkdirs的区别
主要是创建文件和文件目录的区别
1. public String[] list():返回字符串 = 代表 File 的所有子文件/目录
2. public File[] listFiles():返回一个 File 数组 = 代表 File 目录下的所有子文件/目录(一级目录)
Java代码总结:
public class RecursionDirectory {
public static void main(String[] args) {
File file = new File("D:\\play");
// 创建一个 File,记录文件路径
Recursion(file);
// 调用递归方法
}
public static void Recursion(File file) {
// 递归方法,相当于榨汁机把自己榨出的果汁再放入自己里面再榨一边
if (!file.isDirectory()) {
// 判断不是“目录”,就直接退出递归方法
return;
}
File[] files = file.listFiles();
// 既然已经确定是“目录”了,就使用 listFiles() 方法来遍历一遍其下的子文件/目录
for (File f:files) {
// 使用增强 for 循环来遍历 files 数组
if (f.isDirectory()) {
Recursion(f);
// 确定是目录就继续调用递归方法
} else {
System.out.println(f.getName());
// 否则是文件的话,就把它文件名给打印了
}
}
}
}
绝对路径:一个完整、从头至尾的路径,以盘符开头,如:“D://Document//A//record.txt”
相对路径:一个简化、去头至尾的路径,不以盘符开头,如:“//A//record.txt”
路径书写格式的主要区别是分隔符的书写,如:
第一种 "C:\learn\english\a"
第二种 "C:\\learn\\english\\a"
第三种 "C:/learn/english/a"
前者以字节 byte 为单位(根据bit),后者以字符 character 为单位(根据字符集)
综合起来,按输入、输出、字节、字符四个特点来分类的话,就有四个“祖宗”(基类/超类)
输入流 | 输出流 | |
字节流 | 字节输入流 InputStream | 字节输出流 OutputStream |
字符流 | 字符输入流 Reader | 字符输出流 Writer |
它们发展来的“子孙后代”多可以见到其名字的“祖宗”名后缀。
在细说字节/字符流之前,首先明确的是它们最底层始终是二进制数据,即0101...拼凑而成的一个个的字节。所以字节流的兼容性和操作范围是最广的,那么字符流存在的意义是什么呢?这点我们后面再解释,别着急。
字节输出流类流下的流的基本共性功能方法:
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将 b.length(长度)个字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 也就是说从off个字节数开始读取一直到len个字节结束public abstract void write(int b)
:将指定的 字节 输出流。(之所以是 int b,而不是byte或其他类型 是因为其对应的是 read(int b);而为何 read 方法使用的参数类型是 int 呢?这是因为 int 代表的范围大且适合,而byte的 0~255 范围不足以揽括 read 方法可能遇到的所有返回值,综合考虑下采用了 int 类型)
比方:数据源 byte[] = 小明,偏移量 off = 起点,长度 len = 路程
字节输出流(四大流皆有)有许多“子孙”,我们先从其最简单的“子孙” —— FileOutputStream 开始。
在Java中不管学什么,只要它能创建对象,就从其构造方法开始入手!
1、
public FileOutputStream(File file)
:根据File对象为参数创建对象。
2、public FileOutputStream(String name)
: 根据名称字符串为参数创建对象。
字节输入流类流下的流的基本共性功能方法:
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。public abstract int read()
: 从输入流读取数据的下一个字节。public int read(byte[] b)
: 该方法返回的int值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1
老样子,继续看看其构造方法:
1、
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file 命名。
2、FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name 命名。
扩展:
FileInputStream 读取字节数据:
一 使用 read() 方法,每次读取一个字节大小 byte 的数据,提升为 int 类型,当读取到此文件末尾时,将返回 -1 标志符
public class FIS_Read { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("read.txt"); // name = 相对路径名 int b; // b 记录标志符 while ((b = fis.read()) != -1) { // 当遇到 -1 标志符时退出循环 System.out.println((char) b); // int 类型强制转换成 char 字符显示(利用字符集码值) } fis.close(); } }
(利用循环和 -1 标志符来循环读取数据流)
二 使用 read(byte[] b) 方法,每次读取 b 数组长度个字节大小 byte,并可以返回读取到的有效字节 byte 数量,同样当读取到文件末尾时,就返回标志符 -1
public class FIS_Read { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("read.txt"); // name = 相对路径名 int len; // len 用来记录标志符 byte[] b = new byte[3]; // b 用来缓冲数据 while ((len = fis.read(b)) != -1) { // 当遇到 -1 标志符时退出循环 System.out.println(new String(b)); // byte[] 类型强制转型到 String 再显示 } fis.close(); } }
(数据源每次循环读取 b 大小字节,若不能“整除”;则 b 这个缓冲载体将残留一部分上次读取的数据。在循环读取过程中,b 载体是循环替换显示里面的数据的。
但我们可以改进,已知 read(byte[] b) 方法可以返回此次读取到的有效字节的长度,当我们打印数据时可以选择 System.out.println(new String(b, 0, len)); 这是String 类提供的一个功能方法,即以 0 位为“起点”,len 为“路程”来显示 b 数据。)
例子:利用字节流 FileInputStream 来“克隆”图片,创建副本!
public class Transcript {
public static void main(String[] args) throws IOException {
// 利用流对象来指定
FileInputStream fis = new FileInputStream("D:\\Baby.png");//读
// 指定数据源
FileOutputStream fos = new FileOutputStream("D:\\Baby_Transcript.png");//写
// 指定目的地
byte[] b = new byte[1024];
// 准备缓冲的载体
int len;
// 准备记录每次读取数据的长度
while ((len = fis.read(b)) != -1) {
fos.write(b, 0, len);
// 边读边写出数据
}
}
fos.close();
fis.close();
// 安全的关闭流资源!
}
举一反三的话,你还可以“克隆”其它的资源,比如文本、视频等等。
讲完 字节 流中的文件 File 输入/输出流之后,其它的“兄弟”字节流们;只要因材施用,相信你也能融会贯通的!
由于各国/组织采用的数据编码方式的不同,所以其字符集也就不同,随运而生的是:专门有了对 字符 进行高效操作的流对象 ;但是字符流本质上还是基于字节流来读取的,只是去查取了特定的字符集(码表),这也是为什么字节流直接读取数据会有乱码的问题(如中文)。
由此可见以字符为操作单位(其大小 >= 字节 byte)的字符流,其效率较高且专门处理文本类型的文件,其它非文本类型的文件(比如图片、视频等等)就只能使用字节流了。
字符流 = 字节流 + 字符集(编码表) 具有语言文本性质
第三个“祖宗”登场了!请先看看字符输入流的基本共性功能方法:
1、
public void close()
:关闭此流并释放与此流相关联的任何系统资源。
2、public int read()
: 从输入流读取一个字符。
3、public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组cbuf
中
再从简单 File 相关类入手,FileReader 类是从读取字符文本文件的便利类。其构造时使用系统默认的字符编码(字符集)和默认字节缓冲区。
以下是其构造方法:
1、
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。
2、FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的字符串名称。
它的 read 方法也类似于字节输入流,区别是操作单位不同罢了,就不再赘述了。
字符输出流的基本共性功能方法:
1、
void write(int c)
写入单个字符(以码值)
2、void write(char[] cbuf)
写入字符数组。
3、abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off 数组的开始索引,len 写的字符个数。
4、void write(String str)
写入字符串。
5、void write(String str, int off, int len)
写入字符串的某一部分,off 字符串的开始索引,len 写的字符个数。
6、void flush()
刷新该流的缓冲。
7、void close()
关闭此流,但要先刷新它。
FileWriter 类是写出字符到文本文件的便利类。其构造时使用系统默认的字符编码(字符集)和默认字节缓冲区。
其构造方法:
1、
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。
2、FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
例子:利用 write(int b) 方法来写出一个字符:
public class FWWrite {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("fw.txt");
// 首先利用文件名称创建一个流对象
fw.write(65);//写出第一个字符
fw.write(97);//写出第二个字符
fw.weite(48);//写出第三个字符
//A a 0
//关闭资源时,与FileOutputStream不同 如果不关闭,数据只是保存到缓冲区,并未保存到文件。
fw.close();
}
}
因为字符流内置缓冲区的原因,如果不关闭输出流,就无法写出字符到文件中。但是已关闭的流对象,显然是无法继续写出数据的。倘若我们既要写出数据,又要继续使用流,就需要 flush 方法了。
flush
:刷新缓冲区,流对象可以继续使用。
close
:先刷新缓冲区,然后再通知系统释放资源。此流对象不可以再被使用了。
这里有个重要的词——刷新,我们需要刷新让内存缓冲区的数据刷到文件中去。就像把手上的东西确确实实的放到了目的地一样,就是它已经离开了我的手到达了目的地。
注解:flush()
这个函数是清空的意思,用于清空缓冲区的数据流,进行流的操作时,数据先被读到内存中,然后再用数据写到文件中,那么当你数据读完时,我们如果这时调用 close()
方法来刷新、关闭读写流,这时就可能造成数据丢失,为什么呢?因为,读入数据完成时不代表写出数据也全部完成了,还有一部分数据可能会留在缓存区中,这个时候不会关闭流的 flush()
方法就显得格外重要了(可以完成续写操作)。但是最终释放系统资源时还得使用 close()
方法。
缓冲流的基本原理:
1、使用了底层流对象从具体设备上获取数据,并将数据大量存储到缓冲区的数组内。
2、通过缓冲区的 read() 方法直接从缓冲区获取具体的字符数据,这样就提高了效率。
3、如果用 read() 方法读取字符数据,并存储到另一个容器中,直到读取到了换行符时,将另一个容器临时存储的数据转成字符串返回,就形成了readLine()功能,提高了读取效能。(可以一行一行的读取字符数据了)
即创建流对象时,会创建一个内置的默认大小的缓冲区数组,(像多了一只手)通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
缓冲流按字节、字符分成两类流:
构造方法:
public BufferedInputStream(InputStream in)
:创建一个新的缓冲输入流,注意参数类型为InputStream。
public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流,注意参数类型为OutputStream。
功能使用上与之前的流类似,就是效率高!因为有缓冲区
构造方法:(格式类似字节缓冲流)
public BufferedReader(Reader in)
:创建一个新的缓冲输入流,注意参数类型为Reader。
public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流,注意参数类型为Writer。
一样!但是字符缓冲流比原始的字节流多了一些特有的方法:
- BufferedReader:
public String readLine()
: 读一行数据。 读取到最后返回null(这里的标志符不是 -1 了,而是 null)
- BufferedWriter:
public void newLine()
: 换行,由系统属性定义符号。
一看到解码/编码就知道转换流跟 字符集 有关。
编码:字符(我) ==> 字节(\xced2\x0a)
解码:字节(\xced2\x0a) ==> 字符(我)
从名字中也能看出一些“马脚”,InputStream ==> Reader
其中它所关联的字符集我们可以指定,也可以接受平台的默认字符集(通常是UTF-8)。
构造方法:
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。
InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。
构造方法:
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。
有时我们可以在缓冲流内包装转换流,这样既达到了效率又兼顾了功能!如:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息,像户口本一样。(保存)
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中再创建对象。(重现)
对象流将对象转换成字节流,实现对象的持久化存储和网络传输。(便利性)
构造方法:
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream。(像是一种包装)
序列化操作的条件:
1.该类实现了 java.io.Serializable 接口
2.该类里的所有属性要么是可序列化的,要是瞬态的(transient)
写对象的方法:序列化
public final void writeObject (Object obj)
: 将指定的对象写出。
构造方法:
public ObjectInputStream(InputStream in)
: 创建一个指定InputStream的ObjectInputStream。
读对象的方法:反序列化
public final Object readObject ()
: 读取一个对象。
有时反序列化操作也会失败,如抛出 InvalidClassException
异常。原因可能是:
1、该类的序列版本号与从流中读取的类描述符的版本号不匹配(读写不一致)
2、该类包含未知数据类型
3、该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号。
serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
我们平常使用的 print 和 println
方法都来自 java.io.PrintStream 类
特点:
A:只操作目的地,不操作数据源
B:可以操作任意类型的数据
C:如果启用了自动刷新,在调用println()方法的时候,能够换行并刷新
D:可以直接操作文件(如果某流的构造方法能够同时接收 File 和 String 类型的参数,一般都是可以直接操作文件的!)
特点: 八种基本类型的数据在输入/输出时,会保持类型不变,因此,这种流特别适合在网络上实现基本类型数据和字符串的传递。
直接看其功能方法:
readByte() writeByte()
readShort() writeShort();
readInt() writeInt();
readLong() writeLong();
readFloat() writeFloat();
readDouble() writeDouble();
readBoolean() writeBoolean();
readChar() writeChar();
readUTF() writeUTF();
注意:
1.数据流属于包装流,它必须套接在节点流上。
2.因为数据类型需对应,所以数据流在读取与存储时的顺序要一致。否则,读取数据会失真。
流的使用需要许多 数据源 和 目的地 的路径或者是一些修改频繁的配置信息,我们一般喜欢统一保存在 Properties 属性集合里,方便调用。
java.util.Properties
继承于Hashtable
,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties
方法就是返回一个Properties
对象。
构造方法:
public Properties()
:创建一个空的属性列表。
存储功能的方法:
public Object setProperty(String key, String value)
: 保存一对属性。
public String getProperty(String key)
:使用此属性列表中指定的键搜索属性值。
public Set
:所有键的名称的集合。stringPropertyNames()
与流相关的方法:
public void load(InputStream inStream)
: 从字节输入流中读取键值对。
参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了。
而文本数据格式是键值对形式的,如:length=170;
它们中间的分隔符换成空格、冒号等等。
其实刚刚的四个流(缓冲流、转换流、序列化流、打印流)都是包装流,而文件输入/输出流就属于典型的节点流。
IO流基础的笔记就先点到为止了,Bye!