Java的IO通过java.io包下的类和接口来支持,在java.io包下主要包括输入,输出两种IO流,,每种输入、输出流可分为字节流和字符流两大类。字节流以字节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出的操作。除此以外,Java的IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理流。
对于任何程序设计语言而言,输入输出(Input/Output)系统都是非常核心的功能。程序运行需要数据,数据的获取往往需要跟外部系统进行通信,外部系统可能是文件、数据库、其他程序、网络、IO设备等等。外部系统比较复杂多变,那么我们有必要通过某种手段进行抽象、屏蔽外部的差异,从而实现更加便捷的编程。
输入(Input)指的是:可以让程序从外部系统获得数据(核心含义是“读”,读取外部数据)。常见的应用:
Ø 读取硬盘上的文件内容到程序。例如:播放器打开一个视频文件、word打开一个doc文件。
Ø 读取网络上某个位置内容到程序。例如:浏览器中输入网址后,打开该网址对应的网页内容;下载网络上某个网址的文件。
Ø 读取数据库系统的数据到程序。
Ø 读取某些硬件系统数据到程序。例如:车载电脑读取雷达扫描信息到程序;温控系统等。
输出(Output)指的是:程序输出数据给外部系统从而可以操作外部系统(核心含义是“写”,将数据写出到外部系统)。常见的应用有:
Ø 将数据写到硬盘中。例如:我们编辑完一个word文档后,将内容写到硬盘上进行保存。
Ø 将数据写到数据库系统中。例如:我们注册一个网站会员,实际就是后台程序向数据库中写入一条记录。
Ø 将数据写到某些硬件系统中。例如:导弹系统导航程序将新的路径输出到飞控子系统,飞控子系统根据数据修正飞行路径。
java.io包为我们提供了相关的API,实现了对所有外部系统的输入输出操作,这就是我们这章所要学习的技术。
首先引入File类:
File类是java.io包下代表与平台无关的文件和目录,也就是说,如果希望在程序中操作文件和目录,都可以通过File类来完成。不管是文件还是目录都是使用File来操作的,File能新建、删除、重命名文件和目录,File不能访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。
1.File类操作:文件
public class Test01 {
public static void main(String[] args) throws IOException {
//通过File类关联硬盘上的一个文件:
File f=new File("D:\\test.txt");
// File f=new File("D:/test.txt");
// File f=new File("D:+File.separator+"test.txt");
// File f=new File("a.txt");
// System.out.println(f.exists());
// if(f.exists()){//如果存在,我就删除
// f.delete();
// }else{//如果不存在,我就新建一个文件
// f.createNewFile();
// }
// System.out.println("是否是一个目录:"+f.isDirectory());
// System.out.println("是否是一个文件:"+f.isFile());
// System.out.println("是否隐藏:"+f.isHidden());
// System.out.println("是否可写:"+f.canWrite());
// System.out.println("是否可读:"+f.canRead());
System.out.println("绝对地址:"+f.getAbsolutePath());//这个地址就是具体的精准的位置
System.out.println("相对地址:"+f.getPath());//相对项目的地址
System.out.println("toString:--跟相对地址一样:"+f.toString());
System.out.println("文件的大小:"+f.length());
}
}
2.File类操作:文件夹
public class Test02 {
public static void main(String[] args) {
File f=new File("d:/sddd");
//创建文件夹:
// f.mkdir();//创建一层目录
// f.mkdirs();//创建多层目录
// String[] list = f.list();
// for(String s:list){
// System.out.println(s);
// }
File[] listFiles = f.listFiles();
for(File file:listFiles){
System.out.println(file.getName()+"---"+file.getPath());
}
}
}
使用递归遍历目录:
public class Test03 {
public static void main(String[] args) {
File f=new File("d:/bjsxt");
bianLi(f,1);
}
public static void bianLi(File f,int level){
File[] listFiles = f.listFiles();
for(File file:listFiles){
for(int i=1;i<=level;i++){
System.out.print("-");
}
System.out.println(file.getName());
if(file.isDirectory()){//文件夹
bianLi(file,level+1);
}
}
}
}
流的分类:
按处理的方向分为:
1. 输入流:数据流向是数据源到程序(以InputStream、Reader结尾的流)。
2. 输出流:数据流向是程序到目的地(以OutPutStream、Writer结尾的流)。
按处理的数据单元分为:1. 字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流,如FileInputStream、FileOutputStream。
2. 字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWriter。
按处理的对象来分类:
1. 节点流:可以直接从数据源或目的地读写数据,如FileInputStream、FileReader、DataInputStream等。
2. 处理流:不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如BufferedInputStream、BufferedReader等。处理流也叫包装流。
节点流处于IO操作的第一线,所有操作必须通过它们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性。
一个简单的I/O流程序:
import
java.io.*;
public
class
TestIO1 {
public
static
void
main(String[] args) {
try
{
//创建输入流
FileInputStream fis =
new
FileInputStream(
"d:/a.txt"
);
// 文件内容是:abc
//一个字节一个字节的读取数据
int
s1 = fis.read();
// 打印输入字符a对应的ascii码值97
int
s2 = fis.read();
// 打印输入字符b对应的ascii码值98
int
s3 = fis.read();
// 打印输入字符c 对应的ascii码值99
int
s4 = fis.read();
// 由于文件内容已经读取完毕,返回-1
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
// 流对象使用完,必须关闭!不然,总占用系统资源,最终会造成系统崩溃!
fis.close();
}
catch
(Exception e) {
e.printStackTrace();
}
}
}
|
常用的流:
1. InputStream/OutputStream ------》字节流的抽象类。
2. Reader/Writer 字符流的抽象类。
3. FileInputStream/FileOutputStream 节点流:以字节为单位直接操作“文件”。
4. ByteArrayInputStream/ByteArrayOutputStream 节点流:以字节为单位直接操作“字节数组对象”。
5. ObjectInputStream/ObjectOutputStream 处理流:以字节为单位直接操作“对象”。
6. DataInputStream/DataOutputStream 处理流:以字节为单位直接操作“基本数据类型与字符串类型”。
7. FileReader/FileWriter 节点流:以字符为单位直接操作“文本文件”(注意:只能读写文本文件)。
8. BufferedReader/BufferedWriter 处理流:将Reader/Writer对象进行包装,增加缓存功能,提高读写效率。
9. BufferedInputStream/BufferedOutputStream 处理流:将InputStream/OutputStream对象进行包装,增加缓存功能,提高 读写效率。
10. InputStreamReader/OutputStreamWriter 处理流:将字节流对象转化成字符流对象。
11. PrintStream 处理流:将OutputStream进行包装,可以方便地输出字符,更加灵活。
四大I/O流抽象对象
InputStream/OutputStream和Reader/writer类是所有IO流类的抽象父类,我们有必要简单了解一下这个四个抽象类的作用。然后,通过它们具体的子类熟悉相关的用法。
·InputStream
此抽象类是表示字节输入流的所有类的父类。InputSteam是一个抽象类,它不可以实例化。 数据的读取需要由它的子类来实现。根据节点的不同,它派生了不同的节点流子类 。
继承自InputSteam的流都是用于向程序中输入数据,且数据的单位为字节(8 bit)。
常用方法:
int read():读取一个字节的数据,并将字节的值作为int类型返回(0-255之间的一个值)。如果未读出字节则返回-1(返回值为-1表示读取结束)。
void close():关闭输入流对象,释放相关系统资源。
· OutputStream
此抽象类是表示字节输出流的所有类的父类。输出流接收输出字节并将这些字节发送到某个目的地。
常用方法:
void write(int n):向目的地中写入一个字节。
void close():关闭输出流对象,释放相关系统资源。
· Reader
Reader用于读取的字符流抽象类,数据单位为字符。
int read(): 读取一个字符的数据,并将字符的值作为int类型返回(0-65535之间的一个值,即Unicode值)。如果未读出字符则返回-1(返回值为-1表示读取结束)。
void close() : 关闭流对象,释放相关系统资源。
· Writer
Writer用于写入的字符流抽象类,数据单位为字符。
void write(int n): 向输出流中写入一个字符。
void close() : 关闭输出流对象,释放相关系统资源。
文件字节流实现文件的复制:
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.io.IOException;
public
class
TestFileCopy {
public
static
void
main(String[] args) {
//将a.txt内容拷贝到b.txt
copyFile(
"d:/a.txt"
,
"d:/b.txt"
);
}
/**
* 将src文件的内容拷贝到dec文件
* @param src 源文件
* @param dec 目标文件
*/
static
void
copyFile(String src, String dec) {
FileInputStream fis =
null
;
FileOutputStream fos =
null
;
//为了提高效率,设置缓存数组!(读取的字节数据会暂存放到该字节数组中)
byte
[] buffer =
new
byte
[
1024
];
int
temp =
0
;
try
{
fis =
new
FileInputStream(src);
fos =
new
FileOutputStream(dec);
//边读边写
//temp指的是本次读取的真实长度,temp等于-1时表示读取结束
while
((temp = fis.read(buffer)) != -
1
) {
/*将缓存数组中的数据写入文件中,注意:写入的是读取的真实长度;
*如果使用fos.write(buffer)方法,那么写入的长度将会是1024,即缓存
*数组的长度*/
fos.write(buffer,
0
, temp);
}
}
catch
(Exception e) {
e.printStackTrace();
}
finally
{
//两个流需要分别关闭
try
{
if
(fos !=
null
) {
fos.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
try
{
if
(fis !=
null
) {
fis.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
}
|
2.文件字符流实现文件的复制:
使用FileReader与FileWriter实现文本文件的复制
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
33
34
35
36
37
38
39
40
41
42
|
import
java.io.FileNotFoundException;
import
java.io.FileReader;
import
java.io.FileWriter;
import
java.io.IOException;
public
class
TestFileCopy2 {
public
static
void
main(String[] args) {
// 写法和使用Stream基本一样。只不过,读取时是读取的字符。
FileReader fr =
null
;
FileWriter fw =
null
;
int
len =
0
;
try
{
fr =
new
FileReader(
"d:/a.txt"
);
fw =
new
FileWriter(
"d:/d.txt"
);
//为了提高效率,创建缓冲用的字符数组
char
[] buffer =
new
char
[
1024
];
//边读边写
while
((len = fr.read(buffer)) != -
1
) {
fw.write(buffer,
0
, len);
}
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
finally
{
try
{
if
(fw !=
null
) {
fw.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
try
{
if
(fr !=
null
) {
fr.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
}
|
1. 为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应地,读取时使用的方法为:read(byte[] b);写入时的方法为:write(byte[ ] b, int off, int length)。
2. 程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流无法关闭的情况。
缓冲流的介绍:
Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流)。
当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。
因此,缓冲流还是很重要的,我们在IO操作时记得加上缓冲流来提升性能。
BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。
利用缓冲流实现文件的复制:
import
java.io.BufferedInputStream;
import
java.io.BufferedOutputStream;
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.io.IOException;
public
class
TestBufferedFileCopy1 {
public
static
void
main(String[] args) {
// 使用缓冲字节流实现复制
long
time1 = System.currentTimeMillis();
copyFile1(
"D:/电影/华语/大陆/传奇.mp4"
, "D:/电影/华语/大陆/尚学堂越
"+"
来越传奇.mp4");
long
time2 = System.currentTimeMillis();
System.out.println(
"缓冲字节流花费的时间为:"
+ (time2 - time1));
// 使用普通字节流实现复制
long
time3 = System.currentTimeMillis();
copyFile2(
"D:/电影/华语/大陆/传奇.mp4"
, "D:/电影/华语/大陆/尚学堂越
"+"
来越传奇
2
.mp4");
long
time4 = System.currentTimeMillis();
System.out.println(
"普通字节流花费的时间为:"
+ (time4 - time3));
}
/**缓冲字节流实现的文件复制的方法*/
static
void
copyFile1(String src, String dec) {
FileInputStream fis =
null
;
BufferedInputStream bis =
null
;
FileOutputStream fos =
null
;
BufferedOutputStream bos =
null
;
int
temp =
0
;
try
{
fis =
new
FileInputStream(src);
fos =
new
FileOutputStream(dec);
//使用缓冲字节流包装文件字节流,增加缓冲功能,提高效率
//缓存区的大小(缓存数组的长度)默认是8192,也可以自己指定大小
bis =
new
BufferedInputStream(fis);
bos =
new
BufferedOutputStream(fos);
while
((temp = bis.read()) != -
1
) {
bos.write(temp);
}
}
catch
(Exception e) {
e.printStackTrace();
}
finally
{
//注意:增加处理流后,注意流的关闭顺序!“后开的先关闭!”
try
{
if
(bos !=
null
) {
bos.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
try
{
if
(bis !=
null
) {
bis.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
try
{
if
(fos !=
null
) {
fos.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
try
{
if
(fis !=
null
) {
fis.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
/**普通节流实现的文件复制的方法*/
static
void
copyFile2(String src, String dec) {
FileInputStream fis =
null
;
FileOutputStream fos =
null
;
int
temp =
0
;
try
{
fis =
new
FileInputStream(src);
fos =
new
FileOutputStream(dec);
while
((temp = fis.read()) != -
1
) {
fos.write(temp);
}
}
catch
(Exception e) {
e.printStackTrace();
}
finally
{
try
{
if
(fos !=
null
) {
fos.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
try
{
if
(fis !=
null
) {
fis.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
}
注意:
1. 在关闭流时,应该先关闭最外层的包装流,即“后开的先关闭”。
2. 缓存区的大小默认是8192字节,也可以使用其它的构造方法自己指定大小
缓冲字符流实现文件的复制:
ObjectInputStream/ObjectOutputStream是以“对象”为数据源,但是必须将传输的对象进行序列化与反序列化操作。
import
java.io.BufferedReader;
import
java.io.BufferedWriter;
import
java.io.FileNotFoundException;
import
java.io.FileReader;
import
java.io.FileWriter;
import
java.io.IOException;
public
class
TestBufferedFileCopy2 {
public
static
void
main(String[] args) {
// 注:处理文本文件时,实际开发中可以用如下写法,简单高效!!
FileReader fr =
null
;
FileWriter fw =
null
;
BufferedReader br =
null
;
BufferedWriter bw =
null
;
String tempString =
""
;
try
{
fr =
new
FileReader(
"d:/a.txt"
);
fw =
new
FileWriter(
"d:/d.txt"
);
//使用缓冲字符流进行包装
br =
new
BufferedReader(fr);
bw =
new
BufferedWriter(fw);
//BufferedReader提供了更方便的readLine()方法,直接按行读取文本
//br.readLine()方法的返回值是一个字符串对象,即文本中的一行内容
while
((tempString = br.readLine()) !=
null
) {
//将读取的一行字符串写入文件中
bw.write(tempString);
//下次写入之前先换行,否则会在上一行后边继续追加,而不是另起一行
bw.newLine();
}
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
finally
{
try
{
if
(bw !=
null
) {
bw.close();
}
}
catch
(IOException e1) {
e1.printStackTrace();
}
try
{
if
(br !=
null
) {
br.close();
}
}
catch
(IOException e1) {
e1.printStackTrace();
}
try
{
if
(fw !=
null
) {
fw.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
try
{
if
(fr !=
null
) {
fr.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
}
|
注意
1. readLine()方法是BufferedReader特有的方法,可以对文本文件进行更加方便的读取操作。
2. 写入一行后要记得使用newLine()方法换行。
字节数组流:
ByteArrayInputStream和ByteArrayOutputStream经常用在需要流和数组之间转化的情况!
说白了,FileInputStream是把文件当做数据源。ByteArrayInputStream则是把内存中的”某个字节数组对象”当做数据源。
import
java.io.ByteArrayInputStream;
import
java.io.IOException;
public
class
TestByteArray {
public
static
void
main(String[] args) {
//将字符串转变成字节数组
byte
[] b =
"abcdefg"
.getBytes();
test(b);
}
public
static
void
test(
byte
[] b) {
ByteArrayInputStream bais =
null
;
StringBuilder sb =
new
StringBuilder();
int
temp =
0
;
//用于保存读取的字节数
int
num =
0
;
try
{
//该构造方法的参数是一个字节数组,这个字节数组就是数据源
bais =
new
ByteArrayInputStream(b);
while
((temp = bais.read()) != -
1
) {
sb.append((
char
) temp);
num++;
}
System.out.println(sb);
System.out.println(
"读取的字节数:"
+ num);
}
finally
{
try
{
if
(bais !=
null
) {
bais.close();
}
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
}
DataInputStream和DataOutputStream是处理流,可以对其他节点流或处理流进行包装,增加一些更灵活、更高效的功能。
ObjectInputStream/ObjectOutputStream是以“对象”为数据源,但是必须将传输的对象进行序列化与反序列化操作。
流的转换:
import
java.io.BufferedReader;
import
java.io.BufferedWriter;
import
java.io.IOException;
import
java.io.InputStreamReader;
import
java.io.OutputStreamWriter;
public
class
TestConvertStream {
public
static
void
main(String[] args) {
// 创建字符输入和输出流:使用转换流将字节流转换成字符流
BufferedReader br =
null
;
BufferedWriter bw =
null
;
try
{
br =
new
BufferedReader(
new
InputStreamReader(System.in));
bw =
new
BufferedWriter(
new
OutputStreamWriter(System.out));
// 使用字符输入和输出流
String str = br.readLine();
// 一直读取,直到用户输入了exit为止
while
(!
"exit"
.equals(str)) {
// 写到控制台
bw.write(str);
bw.newLine();
// 写一行后换行
bw.flush();
// 手动刷新
// 再读一行
str = br.readLine();
}
}
catch
(IOException e) {
e.printStackTrace();
}
finally
{
// 关闭字符输入和输出流
if
(br !=
null
) {
try
{
br.close();
}
catch
(IOException e) {
e.printStackTrace();
}
}
if
(bw !=
null
) {
try
{
bw.close();
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
}
}
Java的序列化机制,使用序列化机制可以把内存中的Java对象转换成二进制字节流,可以把Java对象存储到磁盘里,或者在网上传输Java对象。
当两个进程远程通信时,彼此可以发送各种类型的数据。 无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串信息;我们也可以在网络上直接发送Java对象。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象才能正常读取。
把Java对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为Java对象的过程称为对象的反序列化。
对象序列化的作用有如下两种:
1. 持久化: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中,比如:休眠的实现。以后服务器session管理,hibernate将对象持久化实现。
2. 网络通信:在网络上传送对象的字节序列。比如:服务器之间的数据通信、对象传递。
ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。 ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable接口的类的对象才能被序列化。 Serializable接口是一个空接口,只起到标记作用。