IO简介
IO(in
和out
),即输入和输出,指程序和外部设备之间的数据传递,常见的包括文件、管道、网络连接等。
在Java中,通过流处理IO。
流指的是一连串的数据(字符或字节)。
当程序需要读取数据时,会开启一个通向数据源的流;当程序需要写入数据时,会开启一个通向目的地的流。
流具有以下特征:
- 先进先出:最先写入输出流的数据最先被输入流读到
- 顺序存取:一个接着一个地往流中写入或读取一串字节,不能随机访问。(RandomAccessFile除外)
- 只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能。
IO流的分类
按数据流的方向:输入流、输出流
输入流和输出流是相对程序而言的。
读取是流向程序,是输入流;写入是流出程序,是输出流。
按处理数据单位:字节流、字符流
字节流和字符流的用法几乎完全一样,区别在于操作的数据单元不同。
字节流操作的是8bit(1byte),字符流操作的是16bit(2byte)的字符。
为什么要有字符流:
在Java中,字符(包括英文、中文以及其他语言)是通过Character实现,采用Unicode编码。在Unicode中,一个字符占用两个字节,即16bit。
而在UTF-8编码中,一个中文字符占用3byte。
在这种背景下,如果按字节读取字符将会出现乱码的情况。为了更方便处理中文这些字符,Java推出字符流。
字节流一般用来处理图像、视频、Word等类型的非纯文本文件;字符流用来处理纯文本类型的文件。
字节流本身没有缓冲区,缓冲字节流相对于字节流来说效率提升非常高;字符流本身带有缓冲区,但是缓冲字符流相对于字符流来说,效率提升没有字节流那么明显。
按功能:节点流、处理流
节点流:直接操作数据的读写的流,如FileInputStream
、FileOutputStream
。
处理流:对一个已存在的流(节点流)的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream
(缓冲字节流)。
处理流和节点流应用了Java的装饰者设计模式。
在诸多处理流中,非常经典的是缓冲流。
程序与磁盘的交互相对于内存运算是很慢的,容易成为程序的性能瓶颈。因此减少程序与磁盘的交互是提升效率的一种有效手段。
在缓冲流中,在内存中设置一个缓存区,缓冲区先存储足够的带操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。
理论介绍
File类
在和文件交互的流中,需要对文件进行加载和操作。
File类可以用来操作文件,但是不能操作文件中的数据。
public class File extends Object implements Serializable, Comparable<File>
File类实现了Serializable
,说明其支持序列化;实现了Comparable
,说明其支持排序。
File类的构造方法
构造方法 | 说明 |
---|---|
File(File parent, String child) | 根据parent路径File对象和child路径名字符串创建一个新的File实例 |
File(String pathname) | 根据给定路径名字符串转化为File实例 |
File(String parent, String chile) | 根据parent路径名祖父穿和child路径名字符串创建一个新的File实例 |
File(URI uri) | 通过给定的URI对象创建File实例 |
File类常用方法
| 方法 | 说明 |
| createNewFile()
| 当且仅当不存在具有此路径名指定名称的文件时,不可分地创建一个新的空文件。 |
| delete
| 删除文件或目录。 |
| exists
| 测试文件或目录是否存在。 |
| getAbsoluteFile()
| 返回的绝对路径名形式。 |
| getAbsolutePath()
| 返回路径的绝对路径名字符串。 |
| length()
| 返回文件的长度。 |
| mkdir()
| 创建目录。 |
InputStream类和OutputStream类(字节流)
InputStream
和OutputStream
是两个抽象类,是字节流的基类。
public abstract class InputStream implements Closeable
public abstract class OutputStream implements Closeable, Flushable
InputStream
InputStream
:是所有字节输入流的抽象基类。FileInputStream
:文件输入流,用于对文件进行读取操作。PipeInputStream
:管道字节输入流,能实现多线程间的管道通信。ByteArrayInputStream
:字节数组输入流,从字节数组(byte[])中进行以字节为单位的读取,即将资源文件以字节的形式存入到该类的字节数组中。FilterInputStream
:装饰者类,具体的装饰者继承该类,这些类都是处理类。DataInputStream
:数据输入流,用来装饰其他输入流,允许程序以与机器无关的方式从底层输入流中读取基本Java数据类型。BufferedInputStream
:缓冲流,利用内部缓存区提高输入流的效率。ObjectInputStream
:对象输入流,用来提供对基本数据或对象的持久存储。通常应用在反序列化中。
其主要方法有:
read()
:从输入流中读取一个字节。read(byte[] b)
:从输入流中将最多b.length
个字节读入数组中。read(byte[] b, int off, int len)
:从输入流中将最多len
字节的数据读入数组中。close()
:关闭输入流,释放资源。
OutputStream
与InputStream
类似,其中PrintStream
是打印输出流。
其主要方法有:
write(byte[] b)
:将b.length
个字节写入输出流中。write(byte[] b, int off, int len)
:将指定的byte数组中从偏移量off开始的len个字节写入输出流中。write(int b)
:将指定字节写入文件输出流。close()
:关闭输出流,释放资源。
Reader和Writer(字符流)
与字节流类似,字符流也有两个抽象基类,分别是Reader和Writer。其他的字符流实现类都是继承了这两个类。
InputStreamReader
:从字节流到字符流的桥梁,读取字节并使用指定的字符集将其解码为字符。BufferedReader
:从字符输入流中读取文本,设置一个缓冲区来提高效率,是对InputStreamReader
的封装。FileReader
:用于读取字符文件,new FileReader(File file)
等同于new InputStreamReader(new FileInputStream(file, true), "UTF-8")
,但FileReader
不能指定字符编码和默认缓冲区大小。PipeReader
:管道字符输入流,实现多线程间的管道通信。CharArrayReader
:从char
数组中读取数据的介质流。StringReader
:从String
中读取数据的介质流。
Writer与Reader结构类似,方向相反。唯一有区别的是,Writer的子类PrintWriter,是文本输出流打印。
Reader的主要方法:
read()
:读取单个字符read(char[] cbuf)
:将字符读入数组read(char[] cbuf, int off, int len)
:将字符读入数组的某一部分read(CharBuffer target)
:试图将字符读入指定的字符缓冲区flush()
:刷新该流的缓冲close()
:关闭流,释放资源。(需要先刷新)
Writer的主要方法:
write(char[] cbuf)
:写入字符数组write(char[] cbuf, int off, int len)
:写入字符数组的某一部分write(int c)
:写入单个字符write(String str)
:写入字符串write(String str, int off, int len)
:写入字符串的某一部分flush()
:刷新该流的缓冲。close()
:关闭流,释放资源。(需要先刷新)
另外,字符缓冲流还有两个独特的方法:
BufferedWriter
类newLine()
:写入一个行分隔符(该方法会自动适配所在操作系统的行分隔符)。BufferedReader
类readLine()
:读取一个文本行。
ObjectInputStream和ObjectOutputStream(序列化流)
序列化:如果需要将对象保存到磁盘中,或者在网络中传输,就必须对对象进行序列化。在Java中,实现Serializable
接口可以实现序列化。
序列化是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据、对象中存储的属性等信息。
字节序列写到文件之后,相当于文件中持久保存了一个对象的信息;反之,该字节序列还可以从文件中读取回来,重构对象,尽心反序列化。
使用ObjectInputStream
可以实现对对象的序列化,使用ObjectInputStream
可以实现对象的反序列化。
代码操作
File类
常量
File.pathSeparatorChar
:用于分割多个路径的系统分隔符,在Unix系统上为:
,在Windows系统上为;
File.separatorChar
:表示该操作系统的路径分隔符,在Unix系统上为/
,在Windows系统上为\
File.pathSeparator
:用于分割多个路径的系统分隔符,由pathSeparatorChar
扩展而来的字符串常量File.separator
:表示该操作系统的路径分隔符,由separatorChar
扩展而来的字符串常量
实例
需求:
- 检测某文件是否存在,若存在则删除,不存在则创建。输出该文件的绝对路径和长度。
public static void main(String[] args) {
File file = new File("test.txt");
if (file.exists()) {
file.delete();
} else {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println(file.getAbsolutePath());
System.out.println(file.length());
}
- 检测某目录是否存在,若存在则删除,不存在则创建。输出该文件的绝对路径和长度。
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
if (file.exists()) {
file.delete();
} else {
file.mkdir();
}
System.out.println(file.getAbsolutePath());
System.out.println(file.length());
}
字节流的操作
字节输出流
步骤:
- 创建字节输出流对象
- 调用字节输出流对象的写数据方法
- 释放资源
需求1:创建文件并写入一个字符串"Hello world!\n你好,世界!",其中\n
是换行。
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream(new File("test.txt"));
byte[] bytes = "Hello World!\n你好,世界!".getBytes();
fos.write(bytes);
fos.close();
}
FileOutputStream还有另外一个构造方法,
FileOutputStream(File file, boolean append)
,
需求2:在上述操作之后,在其文件后追加字符串"你好,Java!"
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream(new File("test.txt"), true);
byte[] bytes = "你好,Java!".getBytes();
fos.write(bytes);
fos.close();
}
字节输入流
步骤:
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
需求3:读取上述文件
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(new File("test.txt"));
int read;
while ((read = fis.read()) != -1) {
System.out.print((char) read);
}
fis.close();
}
读取后发现输出的结果中中文是乱码状态,这需要使用字符流来解决:
Hello World!
ä½ å¥½ï¼ä¸çï¼ä½ 好ï¼Javaï¼
字节流的综合应用(实现拷贝文件)
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(new File("test.txt"));
FileOutputStream fos = new FileOutputStream(new File("test-cp.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
}
使用字符缓冲流实现拷贝文件
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test-cp.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
bis.close();
}
字符流
字符流 = 字节流 + 编码表
public static void main(String[] args) throws IOException {
InputStreamReader isw = new InputStreamReader(new FileInputStream("test.txt"));
int ch;
while ((ch = isw.read()) != -1) {
System.out.print((char) ch);
}
isw.close();
}
Hello World!
你好,世界!你好,Java!
对象序列化流
public class Test01 {
public static void main(String[] args) throws IOException {
Student stu = new Student("xiaoming", 19);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"));
oos.writeObject(stu);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"));
Object readObject = null;
try {
readObject = ois.readObject();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(readObject.getClass());
System.out.println(readObject instanceof Student);
System.out.println(readObject);
}
}
class Student implements Serializable {
String name;
Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "[name=" + this.name + ", age=" + this.age + "]";
}
}
参考文章: