Java学习笔记(六)——Java IO编程,
Java学习笔记(六)——Java IO编程,
一、IO基础
(一)IO简介
1 . IO是指Input和Output,分别代表输入和输出
(1)Input输入:从外部读取数据到内存
为什么我们要把数据读取到内存中才能够处理这些数据呢?
因为代码是在内存中运行的,数据也必须读取到内存中,最终的表示方式为byte数组、String字符串等。
从Java代码来看,输入,会将硬盘中的某个文件的内容,读取到内存中,并且以Java提供的某种数据形式来表示,这样后续的代码才能够处理文件内容。
(2)Output输出:把数据从内存输出到外部
- 从Java代码来看,输出,是将内存中的Java数据格式,输出到某个位置,例如硬盘中的文件等等
(3)IO流:IO流是一种顺序读写数据的模式
- Java提供了Reader/Writer表示字符流
- 字符流传输的最小数据单位是char
- 字符流输出的byte取决于编码方式
(4)Reader/Writer本质上是能够自动解码的InputStream/OutputStream
(5)同步IO和异步IO
- 读写IO时代码等待数据返回后才继续执行后续代码
- 代码编写简单,执行效率低
- JDK提供的java.io是同步IO
- 异步IO
- 读写IO时仅发出请求,然后立即执行后续代码
- 代码编写复杂,执行效率高
- JDK提供的java.nio是异步IO
- Java.IO接口
java.io | ------ | ------ | ------ | ------ |
---|---|---|---|---|
抽象类 | InputStream | OutputStream | Reader | Writer |
实现类 | FileInputStream | FileOutputStream | FileReader | FileWriter |
- Java IO流的接口和实现相分离:
- 字节流接口:InputStream,OutputStream
- 字符流接口:Reader,Writer
(二)File对象
1 . File的概念
- java.io.File表示文件系统的一个文件或目录
- 构造方法:File(String pathname)
- pathname的表示形式与操作系统相关
//Linux
File f = new File("usr/bin/javac");
//Windows
File f = new File("C:\\Windows\\notepad.exe");
//绝对路径(从根目录开始的完整路径)
File f1 = new File("C:\\Windows\\notepad.exe");
//相对路径(相对于当前工作目录的路径)
File f2 = new File("sub\\javac");//当前目录为"C:\Docs",绝对目录为"C:\Docs\sub\javac"
//1个“.”表示当前目录,2个“..”表示上一级目录
File f2 = new File(".\\javac");//绝对目录为"C:\Docs\sub\javac";
File f3 = new File("..\\javac");//绝对目录为"C:\Docs\javac";
2 . 获取File对象的路径
- File对象有3种形式的路径:
- String getPath():获取路径
- getAbsolutePath():返回绝对路径参数
- getCanonicalPath():返回规范路径参数
File f = new File("...");
String path = f.getPath();//"..."
String apath = f.getAbsolutePath();//"C:\\workspace\\IOFile"
String cpath = f.getCanonicalPath();//"C:\\workspace"
3 . 判断File对象的类别
- boolean isFile():判断是否是文件
- boolean DirectoryFile():判断是否是目录
new File("C:\\Windows\\notepad.exe").isFile;//true
new File("C:\\Windows").isDirectoryFile;//true
4 . File文件的操作
如果识别一个File对象为文件,就可以调用以下方法:
- canRead():是否允许读取该文件
- canWrite():是否允许写入该文件
- canExecute():是否允许运行该文件
- length():获取文件大小
- createNewFile():创建一个新文件
- static createTempFile():创建一个临时文件
- delete():删除该文件
- deleteOnExit():在JVM退出时删除该文件
File f = new File("C:\\Windows\\notepad.exe");
f.canExecute();//true
File tmpFile = File.createTempFile("tmp-", ".txt");//创建临时文件:tmp-0001.txt
tmpFile.deleteOnExit();//在JVM退出时删除该文件
5 . File目录的操作
如果识别一个File对象为目录,就可以调用以下方法:
- String[] list():列出目录下的文件和子目录名
- File[] listFiles():列出目录下的文件和子目录名
- File[] listFiles(FileFilter filter):用来过滤不需要的文件
- File[] listFiles(FilenameFilter filter):用来过滤不需要的文件
- mkdir():创建该目录
- mkdirs():创建该目录,并在必要时将不存在的父目录也创建出来
- delete():删除该目录
//返回所有.exe文件
File dir = new File("C:\\Windows");
File[] fs = dir.listFiles(new FilenameFilter(){
public boolean accept(File dir, String name){
return name.endsWith(".exe");
}
}
File dir = new File("C:\\Sample\\test");
dir.mkdir();//C:\\Sample必须存在
dir.mkdirs();//如果C:\\Sample不存在那就自动创建C:\\Sample
dir.delete();//删除该目录
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File win = new File("C:\\Windows");
System.out.println(win.isDirectory()); // true
File notepad = new File("C:\\Windows\\notepad.exe");
System.out.println(notepad.isFile()); // true
File dir = new File("C:\\abc\\xyz");
System.out.println(dir.mkdir()); // -> mkdirs
File readme = new File("./src/readme.txt");
System.out.println(readme.isFile()); // false
System.out.println(readme.getAbsolutePath());
System.out.println(readme.getCanonicalPath());
}
}
二、Input和Output
(一)InputStream
1 . 什么是InputStream
java.io.InputStream定义了所有输入流的超类
(1)InputStream最重要的方法是抽象方法:
- abstract int read()
- 读取下一个字节,并返回字节(0~255)
- 如果已读到末尾,则返回 -1
(2)InputStream拥有2个重载方法:
- int read(byte[] b):读取若干字节并填充到byte[]数组,返回读取的字节数
- int read(byte[], int off, int len):指定byte[]数组的偏移量和最大填充数
- void close():关闭输入流
2 . 如何完整的读取InputStream的所有字节
编写代码:
public void readFile() throws IOException {
//获取一个InputStream
InputStream input = new FileIntputStream("src/readme.txt");
int n;
//通过while循环不断调用read()方法来返回下一个字节,当返回-1时退出循环
while((n = input.read()) != -1) {
System.out.println(n);
}
//最后关闭InputStream输入流
input.close();
}
在上面这段代码中存在一个问题:当读取的过程中出现了IO错误,InputStream就无法正确关闭,资源就不能及时得到释放。所以需要改写成以下代码,无论IO错误是否出现,InputStream都能够正常关闭。
public void readFile() throws IOException {
InputStream input = null;
//获取一个InputStream
input = new FileIntputStream("src/readme.txt");
int n;
//通过while循环不断调用read()方法来返回下一个字节,当返回-1时退出循环
while((n = input.read()) != -1) {
System.out.println(n);
}
//最后关闭InputStream输入流
}finally {
if(input != null {
input.close();
}
}
}
JDK 1.7中的新增编写方法:
public void readFile() throws IOException {
//编写一个try语句,让编译器自动关闭资源
try (InputStream input = new FileInputStream("src/readme.txt")) {
int n;
while((n = input.read()) != -1) {
System.out.println(n);
}
}//在此自动关闭InputStream
input.close()
}
3 . 利用缓冲区获取多个字节
public void readFile() throws IOException {
try(InputStream input = new FileInputStream("src/readme.txt")) {
//定义一个byte[]数组作为缓冲区
byte[] buffer = new byte[1000];
//使用read()方法尽可能多的将字节读取到缓冲区,但是不会超过缓冲区的大小
int n = input.read(buffer);
//最后可以检查read()方法一共读取了多少字节
System.out.println("read " + n + "bytes.");
}
}
如果需要读取的文件较大,一次读取1000个字节还没有读完,这时就需要使用while循环:
public void readFile() throws IOException {
try(InputStream input = new FileInputStream("src/readme.txt")) {
//定义一个byte[]数组作为缓冲区
byte[] buffer = new byte[1000];
//使用read()方法尽可能多的将字节读取到缓冲区,但是不会超过缓冲区的大小
int n = input.read(buffer);
//使用while循环不断调用read()方法读取字节到缓冲区,当返回值为-1时退出while循环
while((n = input.read(buffer)) != -1) {
System.out.println("read " + n + "bytes.");
}
}
}
4 . read()方法是一个阻塞(blocking)方法
阻塞的意义:InputStream的代码是顺序执行的,在调用read()方法时,虽然其处理速度较慢,但是编译器必须等待read()返回字节后才能继续处理下一行代码
5 . FileInputStream
FileInputStream是InputStream的一个而实现类,可以从文件获取输入流。通常调用new FileInputStream传入文件路径,之后转型为InputStream就可以正常使用InputStream
try(InputStream input = new FileInputStream(("test.src")) {
}
6 . ByteArrayInputStream
ByteArrayInputStream可以在内存中模拟一个InputStream,其作用实际上是将一个byte[]数组转换为一个InputStream,可以在测试中用来构造InputStream。
byte[] data = {72, 101, 108, 108, 111, -28, -67, -96, -27, -91, -67 };
try(InputStream input = new ByteArrayInputStream(data)) {
int n;
while((n = input.read()) != -1) {
System.out.println(n);
}
}
(二)OutputStream
1 . 什么是OutputStream
java.io.OutputStream定义了所有输出流的超类
- abstract write(int b):写入一个字节
- void write(byte[] b):写入byte[]数组表示的所有字节
- void write(byte[], int off, int len):写入byte[]数组指定范围的字节
- void close():关闭输出流
- void flush():将缓冲区的内容输出
2 . 将byte写入OutputStream
public void writeFile() throws IOException {
try(OutputStream output = new FileOutputStream("out/readme.txt")) {
output.write(72);// H
output.write(101);// e
output.write(108);// l
output.write(108);// l
output.write(111);// o
}
output.close()
}
3 . 利用重载方法一次写入多个字节
public void writeFile() throws IOException {
try(OutputStream output = new FileOutputStream("out/readme.txt")) {
byte[] b = "Hello".getBytes("UTF-8");
output.write(b);
}
output.close()
}
4 . write()方法是一个阻塞(blocking)方法
阻塞的意义:OutputStream的代码是顺序执行的,在调用write()方法时,虽然其处理速度较慢,但是编译器必须等待write()返回字节后才能继续处理下一行代码
5 . FileOutputStream可以输出到文件
通过FileOutputStream可以输出到文件
try(OutputStream output = new FileOutputStream("test.out")) {
}
6 . ByteArrayOutputStream
ByteArrayOutputStream可以在内存中模拟一个OutputStream,其作用是作用于缓冲区,将任意的数据写入以后,调用toByteArray()来获得最终的byte[]数组
try(ByteArrayOutputStream output = new ByteArrayOutputStream()) {
output.write("Hello ".getBytes("UTF-8"));
output.write("World!".getBytes("UTF-8"));
byte[] data = output.toByteArray();
}
Input/Output练习
FileInputStream可以从文件读入数据,FileOutputStream可以把数据写入文件。
如果我们一边从一个文件读取数据,一边把数据写入到另一个文件,就完成了文件的拷贝。
编写一个程序,接收两个命令行参数,分别表示源文件和目标文件, 然后用InputStream/OutputStream把源文件复制到目标文件。
复制后,请检查源文件和目标文件是否相同(文件长度相同,内容相同)。
分别用文本文件、图片文件和zip文件测试。
(三)Filter模式
1 . 子类爆炸
JDK提供的InputStream
(1)FileInputStream:从文件读取数据
(2)ServletInputStream:从HTTP请求读取数据
(3)Socket.getInputStream():从TCP连接读取数据
以FileInputStream为例:
提供附加功能的InputStream从FilterInputStream派生:
(1)添加缓冲功能:BufferedInputStream
(2)添加计算签名功能:DigestInputStream
(3)添加加密/解密功能:CipherInputStream
所以我们发现,如果要给InputStream添加各种附加功能,需要添加三种功能,至少需要三个子类,而三种功能的组合又需要更多的子类。如果出现了FileInputStream以外的InputStream,那么很快就会出现子类爆炸的情况。为了解决以来继承而导致子类数量失控的问题,JDK将InputStream分成两类:
(1)直接提供数据的InputStream:
- FileInputStream
- ByteArrayInputStream
- ServletInputStream
- …
(2)提供额外附加功能的InputStream:
- BufferedInputStream(提供缓冲功能)
- DigestInputStream(提供计算签名功能)
- CipherInputStream(提供加密/解密功能)
- …
2 . 组合InputStream
在使用InputStream时,需要根据情况进行组合使用:
- 首先,必须拥有一个能够真正提供数据源的InputStream(例如FileInputStream,来源于某个文件)。
- 之后,如果我们需要FileInputStream能够提供缓冲功能,以便提高读取的效率,这时就可以用BufferedInputStream来包装FileInputStream,得到的包装类型是BufferedInputStream,但是依然可以向上转型为InputStream。
- 如果这个文件已经被GZIP压缩,我们希望直接读取解压缩的内容,可以再包装一个GZIPInputStream。
- 无论我们进行多少次包装,得到的对象始终都是InputStream,直接使用InputStream来引用它,就可以正常的读取数据。
InputStream input = new GZIPInputStream;
new BufferedInputStream(new BufferedInputStream(
new FileInputStream("test.gz")));
- 这种组合功能而非继承功能的模式称为Filter模式(或Decorator模式)。
- Filter通过少量的类实现了各种功能的组合。
(四)操作ZIP
1 . ZipInputStream
(1)作用:
ZipInputStream是一种FilterInputStream
可以直接读取ZIP的内容
JarInputStream则是从ZipInputStream派生出来的:
InputStream
↑
FilterInputStream
↑
InflaterInputStream
↑
ZipInputStream
↑
JarInputStream
(2)ZipInputStream的基本用法:
//首先,传入一个ZipInputStream,通常传入FileInputStream
try(ZipInputStream zip = new ZipInputStream(...)) {
ZipEntry entry = null;
//循环调用getNextEntry()方法,直至返回null,表示ZIP流结束
while ((entry = zip.getNextEntry()) != null) {
//对于每个entry都表示一个压缩文件或目录//对于每个entry都表示一个压缩文件或目录
String name = entry.getName();
//如果是一个压缩文件,使用read()方法不断读取,直至返回-1
if (!entry.isDirectory()) {
int n;
while ((n = zip.read()) != -1) {
...
}
}
}
}
2 . ZipOutputStream
(1)作用:
ZipOutputStream是一种FilterOutputStream
- 可以直接写入ZIP的内容
(2)ZipOutputStream的基本用法:
//首先创建ZipOutputStream
try(ZipOutputStream output = new ZipOutputStream(...)) {
File[] files = ...
for (File file : files) {
//在写入文件之前,调用putNextEntry()
zip.putNextEntry(new Entry(file.getName())) ;
//调用write()方法写入Byte数据
zip.write(getFileDataAsBytes(file));
//写入完毕后,调用closeEntry()表示结束文件的打包
zip.closeEntry();
}
}
配合FileInputStream和FileOutputStream就可以读写Zip文件
(五)classpath资源
Java存放.class的目录或jar包也可以包含任意其他类型的文件
从classpath读取文件可以避免不同环境下文件路径不一致的问题
Class对象的getResourceAsStream()可以从classpath读取资源:
try(InputStream input = getClass().getResourceAsStream("/default.properties")) {
if (input != null) {
// Read from classpath
}
}
需要检查返回的InputStream是否为null
(六)序列化
1 . 什么是序列化
(1)序列化是指把一个Java对象变成二进制内容(byte[])
- 序列化以后可以将byte[]保存到文件中
- 序列化以后可以将byte[]传输到网络中
(2)Java对象实现序列化必须实现Serializable接口
- Serializable接口没有任何定义方法(空接口)
- 空接口被称为标记接口(Marker Interface)
2 . 反序列化
(1)反序列化是指把一个二进制内容(byte[])变成Java对象
- 反序列化以后可以从文件读取byte[]并变为Java对象
- 反序列化以后可以从网络读取byte[]并变为Java对象
(2)ObjectOutputStream负责把一个Java对象写入二进制流:
try (ObjectOutputStream output = new ObjectOutputStream(...)) {
output.writeObject(new Person("Hello "));
output.writeObject(new Person("World!"));
}
(3)ObjectInputStream负责从二进制流读取一个Java对象:
try(ObjectInputStream input = new ObjectInputStream(...)) {
Object p1 = input.readObject();
Person p2 = (Person) input.readObject();
}
(4)readObject()可能抛出的异常:
- ClassNotFoundException:没有找到对应的Class
- InvalidClassException:Class不匹配
(5)反序列化的重要特点: - 反序列化由JVM直接构造出Java对象,不调用构造方法
注意:
- 可设置serialVersionUID作为版本号(非必需)
- Java的序列化机制仅适用于Java,如果需要与其他语言交换数据,必须使用通用的序列化方法,例如JSON
三、Reader和Writer
(一)Reader
1 . Reader与InputStream的区别
InputStream | Reader |
---|---|
字节流,以byte为单位 | 字符流,以char为单位 |
读取字节(-1,0~255):int read() | 读取字符(-1,0~65535):int read() |
读到字节数组:int read(byte[] b) | 读到字符数组:int read(char[] c) |
int read(byte[] b, int offset, int len) | int read(char[] b, int offset, int len) |
2 . 构造方法
java.io.Reader是所有字符输入流的超类
- int read()
- 读取下一个字符,并返回字符(0~65535)
- 如果已读到末尾,则返回-1
- int read(char[] c):读取若干字符并填充到char[]数组,返回读取的字符数
- int read(char[] b, int off, int len) :指定char[]数组的偏移量和最大填充数
- void close():关闭Reader输入流
3 . 完整读取Reader的所有字符
public void readFile() throws IOException {
Reader reader = null;
try {
reader = new FileReader("readme.txt");
int n;
while ((n = reader.read()) != -1) {
System.out.println((char)n);
}
} finally {
if (reader != null) {
reader.close();
}
}
}
JDK 1.7新增的编写方法:
public void readFile() throws IOException {
try (Reader reader = new FileReader("readme.txt")) {
int n;
while ((n = reader.read()) != -1) {
System.out.println((char)n);
}
}//在此自动关闭Reader
}
4 . 利用缓冲区一次读取多个字符
public void readFile() throws IOException {
try (Reader reader = new FileReader("readme.txt")) {
char[] buffer = new char[1000];
int n = reader.read(buffer);
System.out.println("read " + n + " chars.");
}
5 . read()是阻塞方法
6 . FileReader
- FileReader可以从文件获取Reader对象
try (Reader reader = new FileReader("readme.txt")) {
//字符编码跟随系统默认编码
}
- FileReader使用系统默认编码,无法指定编码
- 可以通过InputStreamReader指定编码
7 . CharArrayReader
CharArrayReader可以在内存中模拟一个Reader
char[] data = {'Z', 'E', 'R', 'O' };
try (Reader reader = new CharArrayReader(data) {
int n;
while ((n = reader.read()) != -1) {
System.out.println((char)n);
}
}
8 . Reader与InputStream的关系
Reader是基于InputStream构造的
- FileReader内部持有一个FileInputStream
- Reader可以通过InputStream构造
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Main {
public static void main(String[] args) throws IOException {
try (Reader reader = new FileReader("readme.txt")) {
int n;
while ((n = reader.read()) != -1) {
System.out.println((char) n);
}
}
}
}
(二)Writer
1 . Writer与OutputStream的区别
OutputStream | Writer |
---|---|
字节流,以byte为单位 | 字符流,以char为单位 |
写入字节(0~255):void write(int b) | 写入字符(0~65535):void write(int c) |
写入字节数组:void write(byte[] b) | 写入字符数组:void write(char[] c) |
void write(byte[] b, int offset, int len) | void write(char[] c, int offset, int len) |
void write(String s) |
2 . 构造方法
java.io.Writer是所有字符输出流的超类
- void write(int c):写入一个字符(0~65535)
- void write(char[] c):写入字符数组的所有字符
- void write(char[] c, int offset, int len):写入字符数组的指定范围的字符
- void write(String s):写入String表示的所有字符
3 . 写入字符
public void writeFile() throws IOException {
try (Writer writer = new FileWriter("readme.txt")) {
wtiter.write(65);
}//在此自动关闭Writer
}
4 . 写入多个字符
public void writeFile() throws IOException {
try (Writer writer = new FileWriter("readme.txt")) {
wtiter.write("Hello " .toCharArray());
wtiter.write("World ");
}//在此自动关闭Writer
}
5 . write()是阻塞方法
6 . FileWriter
FileWriter可以从文件获取Writer
try (Writer writer = new FileWriter("readme.txt")) {
//字符编码跟随系统默认编码
}
7 . CharArrayWriter
CharArrayWriter可以在内存中模拟一个Writer
try (Writer writer = new CharArrayWriter()) {
wtiter.write(65);
wtiter.write(66);
wtiter.write(67);
char[] data = writer.toCharArray();// {"A", "B", "C"}
}
8 . Writer与OutputStream的关系
Writer是基于OutputStream构造的
- FileWriter内部持有一个FileOutputStream
- Writer可以OutputStream构造
OutputStream output = new FileOutputStream(filename);
Writer writer = new OutputStreamWriter(output, "UTF-8") {
writer.close();
//不用调用output.close()
}
相关文章
- 暂无相关文章
用户点评