JavaIO流
IO流概述
在Java程序中,对于数据的输入/输出操作以“流(stream)”方式进行;Java提供了各种各样的“流”类,用以获取不同类的数据;程序中通过标准的方法输出或输入数据。Java的流类型一般位于
java.io
包中
数据源data source,提供原始数据的原始媒介。常见的:数据库、文件、其他程序、网络连接、IO设备(既可以做数据的源头,也可以做数据的目的地)
流是一个抽象、动态的概念,是一连串连续动态的数据集合。
数据元就像水箱,流就像水管中留着的水流,程序就是我们最终的用户。流是一个抽象、动态的概念,是一连串连续动态的数据集合。
IO流分类
按流的方向分类:
- 输入流:数据流向是数据源到程序(以
InputStream
、Reader
结尾的流) - 输出流:数据流向是程序到目的地(以
OutPutStream
、Writer
结尾的流)
输入输出流的划分是相对于程序而言的,并不是相对数据源
- 输入流:数据流向是数据源到程序(以
按处理的数据单元分类:
- 字节流:以字节为单位获取数据,命名上以
Stream
结尾的流一般是字节流,顶级类InputStream
、OutPutStream
- 字符流:以字符为单位获取数据,命名上以
Reader
、Writer
结尾的一般是字符流,顶级类Reader
、Writer
- 字节流:以字节为单位获取数据,命名上以
按处理对象不同分类:
- 节点流:可以直接从数据源或目的地读写数据,如
FileInputStream
、FileReader
等 - 处理流:不直接连接到数据源或目的地,是“处理流的流”,通过对其他流的处理提高程序的性能,如BufferedInputStream、BufferedReader等。处理流也叫包装流
节点流处于IO操作的第一线,所有操作必须通过它们进行,处理流可以对节点流进行包装,提高性能或提高程序的灵活性
- 节点流:可以直接从数据源或目的地读写数据,如
IO流体系结构
字节流
InputStream
和OutPUtStream
是Java语言中最基本的两个字节输入输出类,其他所有字节输出输入流类都继承这两个基类- 这两个类都是抽象类,不能创建它们的实例,只能使用它们的子类
FilterInputStream
和FilterOutPutStream
是所有包装流的父类
字符流
Reader
和Writer
,Java语言中最基本的两个字符输入输出类- 其他所有字符输入输出流都继承自这两个基类
- 这两个类都是抽象类,不能创建它们的实例,只能使用它们的子类
字符流只适合读写文本之类的,不适合音频文件
File
类的使用
File
类用来代表文件和文件夹- 主要作用有两个:
- 获取文件或者文件加的属性
- 实现对文件、文件夹的创建和删除
方法 | 备注 |
---|---|
file.isFile() | 是否是文件 |
file.isDirectory() | 是否是目录 |
file.exists() | 是否存在 |
file.length() | 文件大小(字节) |
file.lastModified() | 最后修改时间(毫秒值) |
file.canRead() | 是否可读 |
file.canWrite() | 是否可写 |
file.canExecute() | 是否可执行 |
file.getPath() | 获取文件全路径 |
file.getParent() | 获取文件父级目录 |
file.getParentFile() | 获取父级文件 |
file.getAbsolutePath() | 返回全路径 |
file.mkdir() | 创建一级目录 |
file.mkdirs() | 创建多级目录 |
file.createNewFile() | 创建新文件 |
file.getName() | 获取文件名称 |
file.list() | 获取文件夹内文件的名称集合 |
file.listFiles() | 获取文件夹内文件的集合 |
file.delete() | 删除文件 |
文件流
文件字节流FileInputStream
和FileOutPutStream
FileInputStream
和FileOutPutStream
是字节流,数据源和目的地是文件- 实现复制文件的功能
- 复制文件需要分别创建一个输入流和输出流完成文件读写
- 需要创建一个中转站,借助循环和中转站完成复制
- 流使用完毕一定要关闭,这和垃圾回收没有关系
代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14public class TestCopyFile {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("E:\\壁纸.zip");
FileOutputStream fos = new FileOutputStream("E:\\壁纸2.zip")){
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1){
fos.write(bytes,0,len);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
文件字符流FileReader
和FileWriter
FileReader
和FileWriter
是字符流,数据源和目的地是文件- 实现复制文件的功能,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class TestCopyFile1 {
public static void main(String[] args) {
try ( Reader rd = new FileReader("E:\\readme.txt");
Writer wt = new FileWriter("E:\\readme2.txt");){
char[] chars= new char[1024];
int len;
while ((len = rd.read(chars)) != -1){
wt.write(chars,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}其实只有字节流,没有字符流。字符流的底层还是字节流,进行了封装转换可以更简单的来处理非英文字符
字节流可以完成所有类型文件的复制(文本、音频、视频、图片、chm),字符流只能完成文本的复制(txt),doc不是文本文件,字符流一般只用来操作普通的文本文件
缓冲流
缓冲字节流BufferedInputStream
和BufferedOutPutStream
- 原理是在内存中创建一个缓冲区,默认大小是
8192
字节,输入流每次去缓冲区中获取,如果缓存区为空,会去磁盘一次读取8192字节,然后再根据自定义的中转站大小向输出流中转 - 输出流也是默认创建一个8192的缓存区,等缓冲区满了或者最后关毕流的时候会一次性写入磁盘,减少于磁盘的交互,提升性能
- 代码如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class TestCopyFile2 {
public static void main(String[] args) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\照片.zip"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\照片2.zip"))){
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
缓冲字符流BufferedReader
和BufferedWriter
- 实现一次读取一行
- 需要注意换行符问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class TestCopyFile3 {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("E:\\readme.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\readme2.txt"))){
String str;
while ((str = br.readLine()) != null){
bw.write(str);
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
数据流和对象流
之前使用文件流、缓冲流读取文件只能按照字节、数组方式读取,最方便的也是按行读取,能否很方便的实现各种基本类型和引用类型数据的读写,并保留其本身的类型。
数据流DataInputStream
和DataOutPutStream
和对象流ObjectInputStream
和ObjectOutPutStream
可以解决这个问题,最大的优势就是提供了方便操作各种数据类型的方法,直接调用,简单方便
注意:
- 只有字节流,没有字符流
- 都是处理流,不是节点流
- 数据流只能操作基本类型和字符串,对象流还可以操作对象
- 写入的是二进制数据,无法直接通过记事本等查看
- 写入的数据需要使用对应的输入输出流来读取
数据流DataInputStream
和DataOutPutStream
DataInputStream
和DataOutPutStream
只能读取基本数据类型
1 | public class DataStream { |
对象流ObjectInputStream
和ObjectOutPutStream
- 可以读写基本数据类型和引用数据类型
- 包含
DataInputStream
和DataOutPutStream
的所有功能 - 想要写入的对象类必须实现
Serializable
序列化接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public class ObjectStream {
public static void main(String[] args) {
// writer();
reader();
}
private static void writer(){
try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("E:\\readme.txt")));){
oos.writeInt(10);
oos.writeBoolean(true);
oos.writeUTF("今天的天气还是不错的");
oos.writeObject(new Date());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void reader(){
try (ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream("E:\\readme.txt")));){
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readUTF());
System.out.println(((Date)ois.readObject()).toLocaleString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
序列化和反序列化
什么是序列化和反序列化
- 序列化:
Serialization
,将对象的状态信息转换为可以存储或者传输的形式的过程- 对象(内存)—> 字节数组 字节序列(外存、网络)
- 反序列化:
DeSerialization
将字节数组转换成对象- 字节数组 字节序列(外存、网络)—–> 对象(内存)
什么时候需要序列化和反序列化
- 存储或传输 比如存储到外存(硬盘)中,传输到网络
如何实现序列化和反序列化
- 相应的类要实现
Serializable
接口 - 静态变量是不参与序列化的
- 用 transient 关键字修饰的字段也不参与序列化
其他流
打印流
PrintStream
和PrintWriter
- 只有输入流,没有输出流
System.out
、System.err
是PrintStream
的实例变量- 代码如下所示:
System.out.println()
就是使用PrintStream
做的打印1
2
3
4
5
6
7
8
9
10
11
12
13public class PrintStreamTest {
public static void main(String[] args) {
try (PrintStream ps = new PrintStream("E:\\readme.txt")){
ps.println(12);
ps.println(2.3);
ps.println(true);
ps.println("今天天气不错啊");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
转换流
- InputStreamReader和OutputStreamWriter
- 实现字节流到字符流的转换,是适配器设计模式的应用
- 只能从字节流转成字符流,可以带来处理字符的便利,没有字符流转换成字节流的转换流,因为没有这种需求
- 将字节流转换成字符流,实现从键盘输入保存到文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class TestCopyFile4 {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintStream bw = new PrintStream("E:\\readme2.txt")){
String str;
while (!"bye".equals(str = br.readLine())){
bw.println(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字节数组流
ByteArrayInputStream
和ByteArrayOutPutStream
- 是节点流,数据源是字节数组,可以实现各种基本和引用数据类型与字节数组之间的相互转换
Java IO流的设计使用了装饰模式,动态组装流 ,可以减少子类的数量,是继承的一种替代方案
XML,可扩展标记语言
XML,Extensible Markup Language(可扩展标记语言),由SGML语言发展而来,允许用户自定义标签,可以将标签和内容有效分离。它主键演变成为一种跨平台的数据交互格式(一种在不同平台、不同系统之间的数据交换格式),一种轻量级的持久化方案(保存简单数据,无需使用数据库)
XML只是纯文本,只是一种独立于软件、硬件的数据存储和传输攻击,它可以对外提供一些信息,但由于C、Java这些编程语言不同,XML无法提供任何“动态行为”。
和HTML提供预定义标签不同,开发者可以自定义任务标签,因此具有很强的扩张性,不同于HTML侧重于数据的展示,XML更加关注数据的存储和传输,不同于HTML可以使用浏览器来解析并显示,XML需要自行编写软件或程序,才能传递、接收、显示这个文档
1
2
3
4
5
6
7
8
9
10
11
12
13
<students>
<student id = "001">
<name>张三</name>
<age>23</age>
<score>98</score>
</student>
<student id="002">
<name>李四</name>
<age>24</age>
<score>96</score>
</student>
</students>一个XML文件分为如下几个部分
文档声明
1
<?xml version="1.0" encoding="utf-8" ?>
元素
<name>
称为开始标签,</name>
称为结束标签,”李四“表示标签的内容。
开始标签、内容、结束标签组合成元素。元素是XML文档的主要部分,元素内容可以是普通文本,也可以是子元素,比如student
元素的内部就是有多个子标签
一个XML文档有且仅有一个根元素,比如students
属性
元素
<student id="001">
中的id就是属性名,001是属性值,属性值要使用双引号括起来,属性加载一个元素的开始开始标签上,用来对元素进行描述
一个元素可以有多个属性,空格隔开
属性没有先后顺序,同一个XML元素不允许有同名属性注释
<!-- 学生信息 -->
对XML内容进行解释说明的文字CDATA标记、字符实体
有时元素文本中会有一些特殊字符,比如
<、>、"、&
等,这些字符在XML文档结构本身中已经用到了,此时主要通过两种办法,实现正确解释这些特殊字符
方法1,个别的特殊字符,可以使用字符实体替换
字符实体 | 特殊字符 | 含义 |
---|---|---|
< |
< |
小于 |
> |
> |
大于 |
& |
& |
和号 |
' |
' |
单引号 |
&aqot; |
" |
双引号 |
严格地讲,在XML中仅有字符
<
和&
是非法的,单引号、双引号和大于号是合法的,但是把它们替换成实体引用是个好习惯
方法2,大量特殊字符可以使用CDATA标记来处理
CDATA标记中的所有字符都会被当作普通字符来处理,而不是XML标签
CDATA标记语法<!CDATA[[要显示的字符]]>
格式良好的XML文档,遵循XML文档的基本规则
- 元素正确嵌套
- XML文件的第一行必须是xml声明
- XML文件只能有一个根节点
- 英文字符的大小写是有差异的
- 开始的控制标记与结束的控制标记缺一不可
- 属性值的设置必须被双引号包围起来
有效的XML文档
- 首先必须是格式良好的(语法约束)
- 使用DTD和XSD(XML Scheme)定义语义约束
内部DTD
1 |
|
外部DTD
新建一个dtd文件,将内容复制进去
student.dtd
1 | <!ELEMENT students (student+)> |
在xml中引用<!DOCTYPE 根元素 SYSTEM "外部DTD文件路径">
1 |
|
公用DTD
- 其实也算一种外部DTD,是有某个权威机构定制的,供特定行业或者公众使用
- 公用DTD用过PUBLIC关键字引入,而不是SYSTEM
- 另外还要再增加一个标识名
<!DOCTYPE 跟元素 PUBLIC "DTD标识名" "公用DTD的URI">
DOM解析XML
XML解析的四种方式
- DOM和SAX是XML解析的两种规范,目前主流的XML解析器都会为DOM和SAX提供实现
- 使用这两种技术解析XML比较繁琐,代码冗长,可读性也不高
- 所以Java领域中又出现了两个开源XML解析器:DOM4J和JDOM
- 其中DOM4J是面向接口编程
- 而JDOM是面向实现编程
- DOM4J比JDOM更灵活,性能表现也比较优异
- DOM
- 基于XML树结构
- 比较耗资源
- 适用于多次访问XML
- SAX
- 基于事件
- 耗资源小
- 适用于数据量比较大的XML
- JDOM
- 比DOM更快
- JDOM仅使用具体类而不使用接口
- DOM4J
- 非常优秀的Java XML API
- 性能优异
- 功能强大
- 开放源码
DOM:Document Object Model 文档对象模型
使用该技术解析XML文档时,会根据要操作的文档,构建一颗驻留内存中的树,然后就可以使用DOM接口来操作这棵树。
由于树是驻留再内存中,所以非常方便各种操作。
但是也因为这棵树包含了XML文档的所有内容,是比较耗费资源的,该方式适合小文档的解析、适合多次访问的文档的解析
SAX:Simple API for XML
是基于事件的解析,它是为了解决DOM解析的资源耗费而出现的。
SAX在解析一份XML文档时,会以次发出文档开始、元素开始、元素结束、文档结束等事件。
该方式不需要是事先调入整个文档,优势是占用资源少,内存耗费小,一般在解析数据量比较大的文档时采用该方式。
DOM4J:DOM for JAVA
开源XML解析工具,完全支持DOM、SAX机制,具有性能优异、功能强大和操作简单等特点。越来越多的Java软件都在使用DOM4J处理XML
JDOM:Java DOM
JDOM的目的是成为Java特定义文档模型。
行至半路,一部分人产生了新的想法,而这些想法又无法在JDOM中实现,干脆就从该项目中分离出来,单独取开发另外一套专属的XML API,这就是DOM4J。
因此,两者具有相同的设计目的,用法也非常相似。
从组中解决来看,JDOM的主要API以类为主,DOM4J的API以接口为主。
使用DOM解析XML
1 | public class TestDOM { |
缺点:
- 前面三个步骤,每次都要书写一遍,能否封装好
getChildNodes()
不仅包括Element
,也包括空白形成的Text
,遍历时需要进行筛选getChildNodes()
也包括注释,也包括外部DTD
引用,大部分情况下并不是用户需要的
使用DOM4J解析XML
Attribute | 定义了XML的属性 |
---|---|
Node | DOM4J树中所有节点的根接口 |
Branch | 指包含子节点的节点,如元素(Element)和文档(Docuemnts) |
Document | 代表XML文档 |
Element | 代表XML元素 |
CharacterData | 所有文本元素的父接口,如CDATA、Comment、Text |
CDATA | 代表XML CDATA区域 |
Comment | 代表XML注释内容 |
DocumentType | 代表XML DOCTYPE声明 |
processingInstruction | 代表XML处理指令 |
Attribute | 代表XML元素的属性 |
添加DOM4J的依赖
1 | <dependency> |
DOM4J解析XML代码如下
1 | public class TestDom4j1 { |
DOM4J创建XML代码如下
1 |
|
DOM4J修改XML代码如下
在XML末尾添加子节点,默认就是在末尾添加
1 | public class TestDom4j3 { |
在XML中间插入节点
1 | public class TestDom4j4 { |
根据条件删除某一个元素
1 | public class TestDom4j5 { |
根据条件修改某一个元素
1 | public class TestDom4j6 { |