Java IO基础、Input和Output、Reader和Writer

版权声明:站在巨人的肩膀上学习。 https://blog.csdn.net/zgcr654321/article/details/83241161

Java IO基础:

IO简介:

IO指Input / Output,即输入和输出。

IO流是一种顺序读写数据的模式:

单向流动;

以byte为最小单位(字节流)。

如果字符不是单字节表示的ASCII:

Java提供了Reader/Writer表示字符流;

字符流传输的最小数据单位是char;

字符流输出的byte取决于编码方式。

Reader/Writer本质上是一个能自动编解码的InputStream/OutputStream。

如:

使用Reader:

使用InputStream:

如果数据源不是文本,那就使用InputStream;如果数据源是文本,那么使用Reader更方便一些。

同步IO和异步IO:

同步IO:

读写IO时代码等待数据返回后才继续执行后续代码;

代码编写简单,CPU执行效率低。

异步IO:

读写IO时仅发出请求,然后立刻执行后续代码;

代码编写复杂,CPU执行效率高。

JDK的java.io包提供了同步IO功能,JDK的java.nio包提供了异步IO功能。

总结:

IO流是一种流式的数据输入/输出模型;

二进制数据以byte为最小单位在InputStream/OutputStream中单向流动;

字符数据以char为最小单位在Reader/Writer中单向流动;

JDK的java.io包提供了同步IO功能,JDK的java.nio包提供了异步IO功能;

Java的IO流的接口和实现是分离的:

字节流接口:InputStream / OutputStream

字符流接口:Reader / Writer

File对象:

java.io.File表示文件系统的一个文件或者目录:

构造方法:File(String pathname)

如:

String getPath()/getAbsolutePath()/getCanonicalPath():3种返回路径的方法

如:

boolean isFile():是否是文件

boolean isDirectory():是否是目录

如:

当File对象表示一个文件时(isFile()==true):

boolean canRead():是否允许读取该文件

boolean canWrite():是否允许写入该文件

boolean canExecute():是否允许运行该文件

long length():获取文件大小

如:

boolean createNewFile():创建一个新文件

static boolean createTempFile(String prefix,String suffix):创建一个临时文件

boolean delete():删除该文件

void deleteOnExit():在JVM退出时删除该文件。

如:

当File对象表示一个目录时(isDirectory()==true):

String[] list():列出目录下的文件和子目录名;

File[] listFiles():列出目录下的文件和子目录名;

File[] listFiles(FileFilter filter)

File[] listFiles(FilenameFilter filter)

如:

当File对象表示一个目录时(isDirectory()==true):

boolean mkdir():创建该目录

boolean mkdirs():创建该目录,并在必要时将不存在的父目录也创建出来

boolean delete():删除该目录

如:

总结:

File对象表示一个文件或者目录:

创建File对象本身不涉及IO操作;

获取路径/绝对路径/规范路径:getPath()/getAbsolutePath()/getCanonicalPath();

可以获取目录的文件和子目录;

通过File对象可以创建或删除文件和目录。

Input和Output:

InputStream:

InputStream是所有输入流的超类:

abstract int read():

读取下一个字节,并返回字节(0-255);

如果已读到末尾,返回-1;

int read(byte[] b):

读取若干字节并填充到byte[]数组,返回读取的字节数

int read(byte[] b,int off,int len):

指定byte[]数组的偏移量和最大填充数

void close():关闭输入流

如:

完整地读取一个InputStream的所有字节:

这段代码的问题是,如果读取过程中发生了错误,则无法正常关闭。

改进代码:

我们也可以用JDK1.7新增的try(resource)可以保证InputStream正确关闭:

我们还可以利用缓冲区一次读取多个字节:

read()方法是阻塞(blocking)的:

如:

FileInputStream可以从文件获取输入流:

ByteArrayInputStream可以在内存中模拟一个InputStream:

总结:

InputStream定义了所有输入流的超类;

FileInputStream实现了文件流输入;

ByteArrayInputStream在内存中模拟一个字节流输入;

使用try(resource)保证InputStream正确关闭。

OutputStream:

java.io.OutputStream是所有输出流的超类:

abstract write(int b):写入一个字节

void write(byte[] b):写入byte[]数组的所有字节

void write(byte[] b,int off,int len):写入byte[]数组指定范围的字节

void close():关闭输出流

void flush():将缓冲区的内容输出,缓冲区满时会自动调用flush(),在关闭OutputStream之前也会自动调用flush()

将byte写入OutputStream:

写入过程中如果发生错误,就无法正确地关闭。

我们使用JDK1.7的try(resource)保证OutputStream正确关闭。

将byte[]写入OutputStream:

write()方法是阻塞(blocking)的:

FileOutputStream可以输出到文件:

ByteArrayOutputStream可以在内存中模拟一个OutputStream:

总结:

OutputStream定义了所有输出流的超类;

FileOutputStream实现了文件流输出;

ByteArrayOutputStream在内存中模拟一个字节流输出;

使用try(resource)保证OutputStream正确关闭。

Filter模式:

JDK提供的InputStream包括:

FileInputStream:从文件读取数据;

ServletInputStream:从http请求读取数据;

Socket.getInputStream:从TCP连接读取数据。

如果要给FileInputStream添加各种InputStream功能:

这样一来,我们发现添加各种功能需要很多的子类:

这就是子类数量爆炸的情况。

Filter模式是为了解决子类数量爆炸的问题。

为了解决这个问题,JDK把InputStream分为两类:

直接提供数据的InputStream:FileInputStream、ByteArrayInputStream、ServletInputStream

提供附加功能的InputStream从FilterInputStream派生:BufferedInputStream、DigestInputStream、CipherInputStream、GZIPInputStream

使用InputStream时,根据情况组合:

首先必须要有一个真正提供数据源的InputStream,如图,即FileInputStream;然后用BufferedInputStram来包装FileInputStream;再假设文件已经被GZIP压缩过,我们希望能直接读取解压缩后的内容,我们就再用GZIPInputStream包装一层。

无论我们包装了多少次,得到的对象总是InputStream,所以我们用InputStream来引用它读取数据。

组合功能而非继承的设计模式称为Filter模式(又称Decorator模式)。

通过少量的类实现了各种功能的组合。

InputStream的继承体系:

OutputStream的继承体系和InputStream类似。

总结:

Java IO使用Filter模式为InputStream/OutputStream增加功能;

可以把一个InputStream和任意FilterInputStream组合;

可以把一个OutputStream和任意FilterOutputStream组合;

Filter模式可以在运行期间动态增加功能(又称Decorator模式)。

操作zip:

ZipInputStream是一种FilterInputStream。

它可以直接读取Zip的内容。

JarInputStream提供了额外读取jar包内容的能力。

ZipInputStream的基本用法:

ZipOutputStream是一种FilterOutputStream,可以直接写入Zip的内容。

ZipOutputStream的基本用法:

总结:

ZipInputStream可以读取Zip格式的流;

ZipOutputStream可以把数据写入Zip;

ZipInputStream/ZipOutputStream都是FilterInputStream/FilterOutputStream;

配合FileInputStream和FileOutputStream就可以读写Zip文件。

classpath资源:

Java存放.class的目录或jar包也可以包含任意其他类型的文件:

.properties,.txt,.jpg,.mov,...

从classpath读取文件可以避免不同环境下文件路径不一致的问题。

如:

这段代码在windows下可以正常执行,但是在linux系统下路径就完全不同。

如果资源文件不存在,会返回null。

这样我们就可以不用管不同环境下文件路径不一致的问题。

总结:

把资源存储在classpath中可以避免文件路径依赖;

Class对象的getResourceAsStream()可以从classpath中读取资源;

需要检查返回的InputStream是否为null。

序列化:

序列化是指把一个Java对象变成二进制内容(byte[]数组):

序列化后可以把byte[]保存到文件中;

序列化后可以把byte[]通过网络传输;

一个Java对象要能实现序列化,必须实现Serializable接口:

Serializable接口没有定义任何方法;

空接口被称为“标记接口”(Marker Interface);

反序列化是指把一个二进制内容(byte[])变成Java对象:

反序列化后可以从文件读取byte[]并变为Java对象;

反序列化后可以从网络读取byte[]并变为Java对象。

ObjectOutputStream负责把一个Java对象写入二进制流:

ObjectInputStream负责从二进制流读取一个Java对象:

readObject()可能抛出的异常:

ClassNotFoundException:没有找到对应的Class;

InvalidClassException:Class不匹配;

反序列化的重要特点:反序列化由JVM直接构造出Java对象,不调用构造方法。

总结:

可序列化的Java对象必须实现java.io.Serializable接口;

类似Serializable这样的空接口被称为“标记接口”(Marker Interface);

反序列化时不调用构造方法;

可设置serialVersionUID作为版本号(非必需);

Java的序列化机制仅使用与Java,如果需要与其他语言交换数据,必须使用通用的序列化方法,例如JSON。

Reader和Writer:

Reader:

java.io.Reader和java.io.InputStream的区别:

java.io.Reader是所有字符输入流的超类:

int read():读取下一个字符并返回字符(0-65535),如果已读到末尾,返回-1;

int read(char[] c):读取若干字符并填充到char[]数组,返回读取的字符数;

int read(char[] c,int off,int len):指定char[]数组的偏移量和最大填充数;

void close():关闭Reader。

完整地读取一个Reader的所有字符:

同样的,如果读取过程中出现错误,无法正常关闭。

我们修改一下代码,使用try...finally写法:

或使用try(resource)写法(JDK>=1.7):

利用缓冲区一次读取多个字符:

FileReader可以从文件获取Reader对象:

注意这里使用的字符编码是系统默认的编码。

CharArrayReader可以在内存中模拟一个Reader对象:

实际上是把一个char[]数组变成一个Reader对象。

Reader是基于InputStream构造的:

FileReader内部持有一个FileInputStream;

Reader可以通过InputStream构造。

任何InputStream都可指定编码并通过InputStreamReader转换为Reader:Reader reader = new InputStreamReader(input, "UTF-8")。

总结:

Reader定义了所有字符输入流的超类;

FileReader实现了文件字符流输入;

CharArrayReader在内存中模拟一个字符流输入;

Reader是基于InputStream构造的:

FileReader使用系统默认编码,无法指定编码,可以通过InputStreamReader指定编码

使用try(resource)保证Reader正确关闭。

Writer:

java.io.Writer和java.io.OutputStream的区别:

java.io.Writer是所有字符输入流的超类:

void write(in c):写入下一个字符(0-65535);

void write(char[] c):写入字符数组的所有字符;

void write(char[] c,int off,int len):写入字符数组指定范围的字符;

void write(String s):写入String表示的所有字符。

向Writer对象写入字符:

显然当写入出现错误时无法正常关闭。

我们可以使用try...finally来保证出错时正常关闭。

或使用try(resource)写法(JDK>=1.7):

一次写入多个字符:

write()方法是阻塞(blocking)的:

FileWriter可以从文件获取Writer:

字符编码是系统默认编码。

CharArrayWriter可以在内存中模拟一个Writer:

实际上是把一个char[]数组变成一个Writer对象。

Writer是基于OutputStream构造的:

FileWriter内部持有一个FileOutputStream;

Writer可以通过OutputStream构造;

任何OutputStream都可指定编码并通过OutputStreamWriter转换为Writer:Writer writer = new OutputStreamWriter(output, "UTF-8")。

总结:

Writer定义了所有字符输出流的超类;

FileWriter实现了文件字符流输出;

CharArrayWriter在内存中模拟一个字符流输出;

Writer是基于OutputStream构造的:

FileWriter使用系统默认编码,无法指定编码;可以通过OutputStreamWriter指定编码。

使用try(resource)保证Writer正确关闭。

猜你喜欢

转载自blog.csdn.net/zgcr654321/article/details/83241161
今日推荐