IO流

IO流

【摘要】IO流~

前言

IO流相关概念

IO能力是一门编程语言的标准能力,几乎所有的编程语言都有IO的能力,但是,java语言的IO能力还是很强的。

IO可以简单解释为 对文件的输入输出操作。

  • 文件[File],它是一个宽泛的概念,包含:
    普通文件
    块文件
    网络文件
    文件夹
    设备文件

在OS中,比较重要一个概念是文件系统[FS], 比如:NTFS, FAT32, …
在java中,通过 java.io.File 类型来表示OS的文件,每一个OS的文件都可以使用一个路径来表示,而这个路径可以使用String类型来表达,并且我们可以很容易地把这个字符串所表示的路径转换成 File的实例。

如:

1
2
3
4
String path = "E:\\JAVA实训\\note\\javase\\day01.txt"; //文件的路径
String path2 = "code\\javase\\hehe"; //相对路径

File f = new File(path);
  • 路径[path]
    • 绝对路径[absolute path]:以盘符开头的路径
    • 相对路径[relative path]:不以盘符开头的路径
      每一个资源[文件]都有唯一的绝对路径。

File类

  • File类[没有能力操作文件的内容]。
  • 核心方法:
    • length()
    • canRead()
    • canWrite()
    • getAbsolutePath()
    • getPath()
    • getName()
    • isFile()
    • isDirectory()
    • createNewFile(); //创建文件
    • mkdir(); //创建目录
    • mkdirs(); //创建目录结构,含父目录
    • delete(); //删除文件、空目录
    • deleteOnExit(); 在JVM进程退出之前再删除
    • listFiles
    • list

注:利用File类的delete方法删除文件时,需要注意

A.被其它进程占用的文件 删除会失败。
B.有内容的文件夹 删除会失败。

流 [stream]

流的源头

文件、网络、内存、扫描器,….

流的目的地

文件、网络、内存、终端,…

流的分类

  • 根据数据的流向
    以JVM为界,流可以分为:
    • 输入流:把外界的数据读入到JVM之中。
    • 输出流:把JVM之中的数据写出到外界。
  • 以功能来划分
    • 字节流:以字节为单位的流。
    • 字符流:以字符为单位的流。[专门用来处理 文本类的文件]
  • 根据流数据的包装过程:
    • 节点流:又称为低级流,特点是:数据源明确,真实负责读写数据的流。
    • 处理流:又称为高级流,特点是:不能单独存在(没意义),用来处理其他流,所有高级流都封装了某些特定功能的读写操作,目的是简化我们的读写操作具体的流。

综合以上前两种划分,共计可以分为:

字节流

字节输入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java.io.InputStream
方法:
read(); 每次读1个字节,以返回值返回
read(byte[] buf); 每次至多读取buf.length个字节,存放在buf中
read(byte[] buf, int off, int length);
后2个read方法的返回值是int,表示实际读到的字节数。
当这个返回值是-1时,表示读到文件尾[EOF]
close(); //关闭流
具体的子类:
FileInputStream 文件输入流,以文件为源头
ObjectInputStream 对象输入流,以对象来源头
FilterInputStream 过滤流
DataInputStream 基本数据类型输入流,以基本类型为源头
BufferedInputStream 提供缓存能力的输入流
...

字节输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java.io.OutputStream
方法:
write(int b); 每次写入1个字节
write(byte[] buf);每次写入buf.length个字节
write(byte[] buf, int off, int length);
从off位置处,每次写入length个字节
close();
具体的子类:
FileOutputStream 文件输出流,以文件为目的地
ObjectOutputStream 对象输出流,以对象为目的地
FilterOutputStream 过滤流
DataOutputStream ...
BufferedOutputStream ...
...
  • 使用IO流的基本步骤:

    1.把路径字符串转换成 File实例
    2.有效性判断
    3.创建流 [new ….]
    4.循环读、写
    5.释放资源

作业:

  1. 写一个程序,完成文件的COPY
  2. 写一个程序,完成文件夹的COPY。

节点流:

拥有真正的读写能力的流

1
2
3
4
FileInputStream/FileOutputStream   *[针对文件操作的字节流]
以 File/String 为构造参数
ByteArrayInputStream/ByteArrayOutputStream [针对内存中的字节数组的流]
以 byte[] 为构造参数

过滤流 [FilterInputStream/FilterOutputStream]

本身没有读写能力,它必需架接在节点流的基础上,才能做读写操作,它可以在
节点流的基础上添加新的功能,如:

1
2
3
4
5
6
7
BufferInputStream/BufferedOutputStream  [添加缓存功能]
以 InputStream/OutputStream 为构造参数
DataInputStream/DataOutputStream [添加读写基本数据类型的功能]
以 InputStream/OutputStream 为构造参数

ObjectInputStream/ObjectOutputStream [添加读写对象数据类型的功能]
以 InputStream/OutputStream 为构造参数

IO流中API 的这种设计叫 装饰模式,它把类型分成两种:

A. 拥有核心基本能力的类
B. 装饰能力的类 [必需以 拥有基本能力的类 为构造]

基于这种模式,我们可以构建成功能强大的流:
1.构建一个带缓存功能的文件流

1
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));

2.构建一个带缓存并且读基本数据类型的流

1
2
3
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream(file)));

3.构建一个带缓存并且读对象类型的流

1
2
3
4
ObjectInputStream dis = 
new ObjectInputStream(
new BufferedInputStream(
new FileInputStream(file)));

读写基本数据类型

1
2
3
4
5
6
7
8
DataInputStream
readInt
readLong
readDouble
DataOutputStream
writeInt
writeLong
writeDouble

读写对象数据类型 – 对象序列化

要想持久化一个对象,要求这个对象必需要实现 java.io.Serializable接口,而且这个对象的属性类型也必需要实现 Serializable。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student implements Serializable{
private int id;
private String name;
private Date birth;

private Clazz clazz;
}

public class Clazz implements Serializable {
private int id;
private String name;
...
}

之所以要把对象写入文件中,是为了保存此对象的状态[持久化它的数据]。

对于对象的集合 又该如何?
直接持久化集合即可【要求集合中的元素必需实现 Serializable】

如:

1
2
3
4
5
6
7
List<Student> stuList = ....;

ObjectInputStream
Object readObject();

ObjectOutputStream
void writeObject(Object o);

利用IO流的知识,完成如下业务
我们之前开发过银行帐户类:Account,
改写:要求它的no属性是自动生成的,起始值A1000,每创建一个帐户,no自动增1,
如:A1001, A1002, A1003, …

1
2
3
4
5
//Code
public Account(String name) {
this.realName = name;
this.no = "A"+产生唯一性整数值的方法,从1000开始
}

并且也开发过它的业务类:AccountService

现在,在之前的基础上,提供一个业务接口:IAccountService
然后,改写之前的AccountService类,使用实现IAccountService接口.

新要求:
开发一个银行类 Bank, 要求此类是一个单例,它提供如下属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final String PATH = ""; // 存放帐户文件的路径
private List<Account> accountList; //存放所有帐户

//业务功能:开户, 新建一个帐户
public String open(String realName) {
//创建一个Account

//获取这个帐户的编号

//把这个帐户保存到 集合中

//并且要把这个集合固化到文件

//成功,则返回这个帐户的编号
//失败,则返回null
}

字符流

以字符为处理的基本单位的流

字符输入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java.io.Reader
核心方法
int read(); //读取1个字符
int read(char[] buf); //至多读取 buf.length 个字符
int read(char[] buf, int off, int len); //读取 len 个字符

close(); //释放资源
具体的子类:[节点流]
\- FileReader
\- CharArrayReader
\- BufferedReader *[读取整行 readLine]
\- InputStreamReader * [桥接器,把字节流转成字符流的桥梁]
\- FilterReader
\- PushbackReader

字符输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
java.io.Writer
void write(int c); //写入1个字符
void write(char[] buf); //写入 buf.length 个字符
void write(char[] buf, int off, int len); //写入len个字符

close(); //释放资源
具体的子类: [节点流]
\- FileWriter
\- CharArrayWriter
\- BufferedWriter
\- PrintWriter *[写入一整行, println(String line); ]
\- OutputStreamWriter 桥接器
\- FilterWriter

InputStreamReader
把字节流转换成字符流的桥梁,它的构造:
public InputStreamReader(InputStream in); //默认字符集
public InputStreamReader(InputStream in, String charSet);//指定字符集

如:
d:\\temp\\hehe.txt 文本文件,则可以用如下方式来打开:

1
2
3
4
5
6
7
8
9
10
//1.
String path = "d:\\temp\\hehe.txt";
InputStream in = new FileInputStream(path); //使用字节流
//但是,使用字节去操作字符不方便,所以,我们可以把它转换字符流
BufferedReader br =
new BufferedReader(new InputStreamReader(in)); //默认字符集

//2. 直接使用字符流去打开
BufferedReader br =
new BufferedReader(new FileReader(path)); //不涉及到解码的问题

了解
System.in => InputStream
它被JVM默认指定为 标准输入设备,也就是键盘

System.out => PrintStream [OutpuStream的子类]
它被JVM默认指定为 标准输出设备,也就是终端[console]

JVM允许我们修改标准输出输出设备,也就是对流进行重定向。
System.setIn() 和 System.setOut()

利用System.in和桥接器,我们可以让用户从键盘输入任意字符。

如:

1
2
3
4
5
BufferedReader br = 
new BufferedReader(
new InputStreamReader(System.in));
//然后,就可以从键盘读到用户的输入
String line = br.readLine(); //阻塞方法

随机存取流

java.io.RandomAccessFile这个流既可以读也可以写。而且支持基本数据类型、对象类型的读和写。更关键的是,这个流支持文件指针的定位【指定从哪个位置开始读】

1
2
3
4
5
6
7
8
9
10
11
12
13
构造方法:
RandomAccessFile(String path, String mode);
RandomAccessFile(File file, String mode);

mode是指打开文件的模式,支持:
r 只读模式
rw 读写模式

主要方法:
long getFilePointer(); //获取当前的文件指针偏移量
void seek(long pos); //定位
long length(); //
...

思考:如何读取一个巨大的日志文件的最后一行?

作业:
请把 文件夹“文本文档”下的8个文本文件全部读取出来进行分析:
要求:
统计出空行的数量
英文单词的数量
中文词的数量

进阶:
分析每一个英文单词出现的次数。

结束语

作业现在不写~

评论