概述
什么是IO流?
水分子的移动形成了水流。
IO流指的是:程序中数据的流动。数据可以从内存流动到硬盘,也可以从硬盘流动到内存。
Java中IO流最基本的作用是:完成文件的读和写。
IO流的分类?
根据数据流向分为:输入和输出是相对于内存而言的。
输入流:从硬盘到内存。(输入又叫做读:read)
输出流:从内存到硬盘。(输出又叫做写:write)
根据读写数据形式分为:
字节流:一次读取一个字节。适合读取非文本数据。例如图片、声音、视频等文件。(当然字节流是万能的。什么都可以读和写。)
字符流:一次读取一个字符。只适合读取普通文本。不适合读取二进制文件。因为字符流统一使用Unicode编码,可以避免出现编码混乱的问题。
注意:Java的所有IO流中凡是以Stream结尾的都是字节流。凡是以Reader和Writer结尾的都是字符流。
根据流在IO操作中的作用和实现方式来分类:
节点流:节点流负责数据源和数据目的地的连接,是IO中最基本的组成部分。
处理流:处理流对节点流进行装饰/包装,提供更多高级处理操作,方便用户进行数据处理。
Java中已经将io流实现了,在java.io包下,可以直接使用。
IO流的体系结构
右图是常用的IO流。实际上IO流远远不止这些。
InputStream:字节输入流
OutputStream:字节输出流
Reader:字符输入流
Writer:字符输出流
以上4个流都是抽象类,是所有IO流的四大头领!!!
所有的流都实现了Closeable接口,都有close()方法,流用完要关闭。
所有的输出流都实现了Flushable接口,都有flush()方法,flush方法
的作用是,将缓存清空,全部写出。养成好习惯,以防数据丢失。
四大头领:
InputStream
OutputStream
Reader
Writer
File相关的:
FileInputStream
FileOutputStream
FileReader
FileWriter
缓冲流相关的:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
转换流相关的:
InputStreamReader
OutputStreamWriter
打印流相关的:
PrintStream
PrintWriter
对象相关的:
ObjectInputStream
ObjectOutputStream
数据相关的:
DataInputStream
DataOutputStream
字节数组相关的:
ByteArrayInputStream
ByteArrayOutputStream
压缩和解压缩相关的:
GZIPInputStream
GZIPOutputStream
线程相关的:
PipedInputStream
PipedOutputStream
FileInputStream
文件字节输入流,可以读取任何文件。
常用构造方法
FileInputStream(String name):创建一个文件字节输入流对象,参数是文件的路径
常用方法
int read();从文件读取一个字节(8个二进制位),返回值读取到的字节本身,如果读不到任何数据返回-1
int read(byte[] b); 一次读取多个字节,如果文件内容足够多,则一次最多读取b.length个字节。返回值是读取到字节总数。如果没有读取到任何数据,则返回 -1
int read(byte[] b, int off, int len); 读到数据后向byte数组中存放时,从off开始存放,最多读取len个字节。读取不到任何数据则返回 -1
long skip(long n); 跳过n个字节
int available(); 返回流中剩余的估计字节数量。
void close() 关闭流。
使用FileInputStream读取的文件中有中文时,有可能读取到中文某个汉字的一半,在将byte[]数组转换为String时可能出现乱码问题,因此FileInputStream不太适合读取纯文本。
/**
* 测试:int read(byte[] b); 一次最多可以读到b.length个字节(只要文件内容足够多)。返回值是读取到的字节数量。如果这一次没有读取到任何数据,则返回 -1
*/
public class FileInputStreamTest02 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("D:\\test\\abc.txt");
// 开始读
// 提前准备一个byte[]数组(一次最多读取4个字节。)
byte[] bytes = new byte[4];
// 第一次读
/*int readCount = fis.read(bytes);
System.out.println("第一次读取到的字节数量:" + readCount); // 4*/
// 将byte数组转换成字符串
/*String s1 = new String(bytes);
System.out.println(s1);*/
/*String s1 = new String(bytes, 0, readCount);
System.out.println(s1);*/
// 第二次读
/*readCount = fis.read(bytes);
System.out.println("第二次读取到的字节数量:" + readCount); // 2*/
/*String s2 = new String(bytes);
System.out.println(s2);*/
/*String s2 = new String(bytes, 0, readCount);
System.out.println(s2);*/
// 第三次读
/*readCount = fis.read(bytes);
System.out.println("第三次读取到的字节数量:" + readCount); // -1
readCount = fis.read(bytes);
System.out.println("第四次读取到的字节数量:" + readCount); // -1*/
// 循环
/*while(true){
int readCount = fis.read(bytes);
if(readCount == -1) break;
String s = new String(bytes, 0, readCount);
System.out.print(s);
}*/
int readCount = 0;
while((readCount = fis.read(bytes)) != -1){
System.out.print(new String(bytes, 0, readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 测试:
* int read(byte[] b, int off, int len); 一次读取len个字节。将读到的数据从byte数组的off位置开始放。
* long skip(long n); 跳过n个字节。
* int available(); 获取流中剩余的估计字节数。
*/
public class FileInputStreamTest03 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("E:/powernode/02-JavaSE/code/file1.txt");
/*byte[] bytes = new byte[10];
int readCount = fis.read(bytes, 2, 5);
System.out.println("读取到了多少个字节:" + readCount);
for(byte b : bytes){
System.out.println(b);
}*/
/*int readByte = fis.read();
System.out.println(readByte); // 97
// 跳过两个
fis.skip(2);
readByte = fis.read();
System.out.println(readByte); // 100
System.out.println("还剩几个字节没有读取?" + fis.available());*/
byte[] bytes = new byte[fis.available()];
int readCount = fis.read(bytes);
System.out.println(new String(bytes, 0, readCount));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
FileOutputStream
文件字节输出流。
常用构造方法:
FileOutputStream(String name) 创建输出流,先将文件清空,再不断写入。
FileOutputStream(String name, boolean append) 创建输出流,在原文件最后面以追加形式不断写入。
常用方法:
write(int b) 写一个字节
void write(byte[] b); 将字节数组中所有数据全部写出
void write(byte[] b, int off, int len); 将字节数组的一部分写出
void close() 关闭流
void flush() 刷新
使用FileInputStream和FileOutputStream完成文件的复制。
/**
* java.io.FileOutputStream
* 1. 文件字节输出流,负责写。
*
* 2. 常用构造方法:
*
* FileOutputStream(String name)
* 创建一个文件字节输出流对象,这个流在使用的时候,最开始会将原文件内容全部清空,然后写入。
*
* FileOutputStream(String name, boolean append)
* 创建一个文件字节输出流对象,当append是true的时候,不会清空原文件的内容,在原文件的末尾追加写入。
* 创建一个文件字节输出流对象,当append是false的时候,会清空原文件的内容,然后写入。
*
* 注意:
* append==true表示:不会清空原文件内容,在原文件内容后面追加。
* append==false表示:清空原文件内容,在文件中写入。
*
* 3. 常用方法:
* void close();
* void flush();
* void write(int b); 写一个字节
* void write(byte[] b); 将整个byte字节数组写出
* void write(byte[] b, int off, int len) 将byte字节数组的一部分写出。
*/
public class FileOutputStreamTest01 {
public static void main(String[] args) {
// 创建文件字节输出流对象
FileOutputStream out = null;
try {
//out = new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file2.txt", true);
// 以下两行代码一样。
/*out = new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file2.txt");
out = new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file2.txt", false);*/
out = new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file2.txt");
// 开始写
/*out.write(97);
out.write(98);
out.write(99);
out.write(100);*/
byte[] bytes = {97,98,99,100};
out.write(bytes);
out.write(bytes, 0, 2);
byte[] bs = "动力节点,一家只教授Java的培训机构".getBytes();
out.write(bs);
// 记着刷新
out.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 关闭资源
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
文件拷贝
/**
* 文件拷贝,实现原理:
* 使用FileInputStream读文件,使用FileOutputStream写文件。
* 一边读一边写。
*/
public class FileInputOutputStreamCopy {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
// 输入流
FileInputStream in = null;
// 输出流
FileOutputStream out = null;
try {
in = new FileInputStream("E:\\powernode\\02-JavaSE\\video\\上\\012-第一章Java的加载与执行原理.avi");
out = new FileOutputStream("E:\\012-第一章Java的加载与执行原理.avi");
// 一次至少拷贝1KB
byte[] bytes = new byte[1024];
int readCount = 0;
while ((readCount = in.read(bytes)) != -1) {
out.write(bytes, 0, readCount);
}
// 刷新
out.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 关闭(分别try..catch..)
if (in != null) {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
long end = System.currentTimeMillis();
System.out.println("使用不带缓冲区的方式耗时"+(end - begin)+"毫秒"); // 1477
}
}
try-with-risource语法
FileRead
文件字符输入流
常用的构造方法:
FileReader(String fileName)
常用的方法:
int read()
int read(char[] cbuf);
int read(char[] cbuf, int off, int len);
long skip(long n);
void close()
/**
* 文件字符输入流。读。以字符的形式读文件。一次至少读取一个完整的字符。
*/
public class FileReaderTest01 {
public static void main(String[] args) {
try(FileReader reader = new FileReader("E:\\powernode\\02-JavaSE\\code\\file1.txt")){
// 开始读
char[] chars = new char[3];
int readCount = 0;
while((readCount = reader.read(chars)) != -1){
System.out.print(new String(chars, 0, readCount));
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
FileWirter
文件字符输出流
常用的构造方法:
FileWriter(String fileName)
FileWriter(String fileName, boolean append)
常用的方法:
void write(char[] cbuf)
void write(char[] cbuf, int off, int len);
void write(String str);
void write(String str, int off, int len);
void flush();
void close();
Writer append(CharSequence csq, int start, int end)
使用FileReader和FileWriter拷贝普通文本文件
/**
* 文件字符输出流。写。(写普通文本用的。)
*/
public class FileWriterTest01 {
public static void main(String[] args) {
try(FileWriter writer = new FileWriter("E:\\powernode\\02-JavaSE\\code\\file3.txt")){
// 开始写
writer.write("hello world!");
writer.append("\n");
writer.write("张三李四王五赵六", 2, 2);
writer.append("\n");
writer.write("张三李四王五".toCharArray());
writer.append("\n");
writer.write("张三李四王五".toCharArray(), 2, 2);
writer.append("\n");
writer.append("a");
writer.append("b");
writer.append("c");
writer.append("d");
// 建议手动刷新
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
文件拷贝
/**
* 使用文件字符输入输出流进行文件复制,但是这种方式只能复制普通文本文件。
*/
public class FileReaderFileWriterCopy {
public static void main(String[] args) {
try(FileReader reader = new FileReader("E:\\powernode\\02-JavaSE\\document\\02-第一章 Java开发环境搭建.pptx");
FileWriter writer = new FileWriter("E:\\x.ppt")){
// 一边读一边写
char[] chars = new char[3];
int readCount = 0;
while((readCount = reader.read(chars)) != -1){
writer.write(chars, 0, readCount);
}
// 刷新
writer.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 新的内容
// 以下讲解内容有一些代码是和线程有关系的。大致理解一下。
// 或者说这个代码死记硬背也是可以的。
// 从类路径当中加载资源。
// Thread.currentThread() 获取当前线程
// Thread.currentThread().getContextClassLoader() 获取当前线程的类加载器
// getResource()方法就表示从类的根路径下开始加载资源
// 注意:这种方式只能从类路径当中加载资源。如果这个资源是放在类路径之外的,这种方式不合适。
// 如果你代码是以下这种写法的,表示当前路径就是类的根路径。自动从类的根路径下开始加载资源。
// 这种方式的优点:通用,在进行系统移植的时候,这种方式仍然是通用的。适应性强。
// 这种方式的缺点:资源必须放在类路径当中。没有在类路径下,是无法加载到的。
String path = Thread.currentThread().getContextClassLoader().getResource("test/file").getPath();
System.out.println(path);
FileInputStream in = new FileInputStream(path);
缓冲流
BufferedInputStream、BufferedOutputStream(适合读写非普通文本文件)
BufferedReader、BufferedWriter(适合读写普通文本文件。)
缓冲流的读写速度快,原理是:在内存中准备了一个缓存。读的时候从缓存中读。写的时候将缓存中的数据一次写出。都是在减少和磁盘的交互次数。如何理解缓冲区?家里盖房子,有一堆砖头要搬在工地100米外,单字节的读取就好比你一个人每次搬一块砖头,从堆砖头的地方搬到工地,这样肯定效率低下。然而聪明的人类会用小推车,每次先搬砖头搬到小车上,再利用小推车运到工地上去,这样你再从小推车上取砖头是不是方便多了呀!这样效率就会大大提高,缓冲流就好比我们的小推车,给数据暂时提供一个可存放的空间。
缓冲流都是处理流/包装流。FileInputStream/FileOutputStream是节点流。
关闭流只需要关闭最外层的处理流即可,通过源码就可以看到,当关闭处理流时,底层节点流也会关闭。
输出效率是如何提高的?在缓冲区中先将字符数据存储起来,当缓冲区达到一定大小或者需要刷新缓冲区时,再将数据一次性输出到目标设备。
输入效率是如何提高的? read()方法从缓冲区中读取数据。当缓冲区中的数据不足时,它会自动从底层输入流中读取一定大小的数据,并将数据存储到缓冲区中。大部分情况下,我们调用read()方法时,都是从缓冲区中读取,而不需要和硬盘交互。
可以编写拷贝的程序测试一下缓冲流的效率是否提高了!
缓冲流的特有方法(输入流):以下两个方法的作用是允许我们在读取数据流时回退到原来的位置(重复读取数据时用)
void mark(int readAheadLimit); 标记位置(在Java21版本中,参数无意义。低版本JDK中参数表示在标记处最多可以读取的字符数量,如果你读取的字符数超出的上限值,则调用reset()方法时出现IOException。)
void reset(); 重新回到上一次标记的位置
这两个方法有先后顺序:先mark再reset,另外这两个方法不是在所有流中都能用。有些流中有这个方法,但是不能用。
字节缓冲流
/**
* 1. java.io.BufferedInputStream的用法和FileInputStream用法相同。
*
* 2. 他们的不同点是:
* FileInputStream是节点流。
* BufferedInputStream是缓冲流(包装流/处理流)。这个流的效率高。自带缓冲区。并且自己维护这个缓冲区。读大文件的时候建议采用这个缓冲流来读取。
*
* 3. BufferedInputStream对 FileInputStream 进行了功能增强。增加了一个缓冲区的功能。
*
* 4. 怎么创建一个BufferedInputStream对象呢?构造方法:
* BufferedInputStream(InputStream in)
*
*/
public class BufferedInputStreamTest01 {
public static void main(String[] args) {
BufferedInputStream bis = null;
try {
// 创建节点流
//FileInputStream in = new FileInputStream("file.txt");
// 创建包装流
//bis = new BufferedInputStream(in);
// 组合起来写
bis = new BufferedInputStream(new FileInputStream("file.txt"));
// 读,和FileInputStream用法完全相同
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = bis.read(bytes)) != -1){
System.out.print(new String(bytes, 0, readCount));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 包装流以及节点流,你只需要关闭最外层的那个包装流即可。节点流不需要手动关闭。
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 1. java.io.BufferedOutputStream也是一个缓冲流。属于输出流。
* 2. 怎么创建BufferedOutputStream对象?
* BufferedOutputStream(OutputStream out)
* 3. FileOutputStream是节点流。 BufferedOutputStream是包装流。
*/
public class BufferedOutputStreamTest01 {
public static void main(String[] args) {
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream("file.txt"));
bos.write("动力节点".getBytes());
// 缓冲流需要手动刷新。
bos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
// 只需要关闭最外层的包装流即可。
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
文件拷贝
/**
* 使用BufferedInputStream BufferedOutputStream完成文件的复制。
*/
public class BufferedInputOutputStreamCopy {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\powernode\\02-JavaSE\\video\\上\\012-第一章Java的加载与执行原理.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:/012-第一章Java的加载与执行原理.avi"))){
// 一边读一边写
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = bis.read(bytes)) != -1){
bos.write(bytes, 0, readCount);
}
// 手动刷新
bos.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
long end = System.currentTimeMillis();
System.out.println("带有缓冲区的拷贝耗时"+(end - begin)+"毫秒"); // 671
}
}
字符缓冲流
public class BufferedReaderTest01 {
public static void main(String[] args) throws Exception{
// BufferedWriter bw = new BufferedWriter(new FileWriter(""));
// 创建带有缓冲区的字符输入流对象
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
// 开始读(br.readLine()方法每次读取一行,如果读取不到任何数据,则返回null。)
String s = null;
while((s = br.readLine()) != null) {
System.out.println(s);
}
br.close();
}
}
mark 和 reset
/**
* BufferedReader/BufferedInputStream的两个方法:
* 1. mark方法:在当前的位置上打标记
* 2. reset方法:回到上一次打标记的位置
*
* 这两个方法的调用顺序是:
* 先调用mark,再调用reset。
* 这两个方法组合起来完成的任务是:某段内容重复读取。
*/
public class BufferedReaderMarkTest {
public static void main(String[] args) throws Exception{
// 创建文件字符输入流(这个看起来像节点流,其实不是,为什么?一会再说!!!!)
//FileReader reader = new FileReader("E:\\powernode\\02-JavaSE\\code\\file1.txt");
// 创建带有缓冲区的字符输入流(一般把BufferedReader叫做处理流/包装流)
//BufferedReader br = new BufferedReader(reader);
BufferedReader br = new BufferedReader(new FileReader("E:\\powernode\\02-JavaSE\\code\\file1.txt"));
// 开始读
System.out.println(br.read());
System.out.println(br.read());
System.out.println(br.read());
// 打标记
br.mark(3);
System.out.println(br.read());
System.out.println(br.read());
// 回到上一次打标记的位置上
br.reset();
// 重新读刚才的内容
System.out.println(br.read());
System.out.println(br.read());
br.reset();
System.out.println(br.read());
System.out.println(br.read());
// 关闭流
br.close();
}
}
转换流
InputStreamReader(主要解决读的乱码问题)
InputStreamReader为转换流,属于字符流。
作用是将文件中的字节转换为程序中的字符。转换过程是一个解码的过程。
常用的构造方法:
InputStreamReader(InputStream in, String charsetName) // 指定字符集
InputStreamReader(InputStream in) // 采用平台默认字符集
乱码是如何产生的?文件的字符集和构造方法上指定的字符集不一致。
FileReader是InputStreamReader的子类。本质上以下代码是一样的:
Reader reader = new InputStreamReader(new FileInputStream(“file.txt”)); //采用平台默认字符集
Reader reader = new FileReader(“file.txt”); //采用平台默认字符集
因此FileReader的出现简化了代码的编写。
以下代码本质上也是一样的:
Reader reader = new InputStreamReader(new FileInputStream(“file.txt”), “GBK”);
Reader reader = new FileReader("e:/file1.txt", Charset.forName("GBK"));
/**
* 使用InputStreamReader时,可以指定解码的字符集。用来解决读过程中的乱码问题。
* InputStreamReader是一个字符流。是一个转换流。
* InputStreamReader是一个输入的过程。
* InputStreamReader是一个解码的过程。
*
* InputStreamReader常用的构造方法:
* InputStreamReader(InputStream in) 采用平台默认的字符集进行解码。
* InputStreamReader(InputStream in, String charsetName) 采用指定的字符集进行解码。
*
* FileReader实际上是InputStreamReader的子类。
* FileReader也是一个包装流,不是节点流。
*/
public class InputStreamReaderDecodingTest {
public static void main(String[] args) throws Exception{
// 创建一个转换流对象(输入流)
// 节点流
//FileInputStream in = new FileInputStream("");
// 包装流
//InputStreamReader isr = new InputStreamReader(in);
//InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\powernode\\02-JavaSE\\code\\file3.txt"), "GBK");
// 以上代码太长了。在IO流的继承体系结构中,IO流又给InputStreamReader提供了一个子类:FileReader
// 代码可以这样写了:
//FileReader isr = new FileReader("E:\\powernode\\02-JavaSE\\code\\file3.txt", Charset.defaultCharset());
FileReader isr = new FileReader("E:\\powernode\\02-JavaSE\\code\\file3.txt", Charset.forName("GBK"));
// 开始读
int readCount = 0;
char[] chars = new char[1024];
while((readCount = isr.read(chars)) != -1){
System.out.print(new String(chars, 0, readCount));
}
// 关闭流
isr.close();
}
}
OutputStreamWriter(主要解决写的乱码问题)
OutputStreamWriter是转换流,属于字符流。
作用是将程序中的字符转换为文件中的字节。这个过程是一个编码的过程。
常用构造方法:
OutputStreamWriter(OutputStream out, String charsetName) // 使用指定的字符集
OutputStreamWriter(OutputStream out) //采用平台默认字符集
乱码是如何产生的?文件的字符集与程序中构造方法上的字符集不一致。
FileWriter是OutputStreamWriter的子类。以下代码本质上是一样的:
Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”)); // 采用平台默认字符集
Writer writer = new FileWriter(“file1.txt”); // 采用平台默认字符集
因此FileWriter的出现,简化了代码。
以下代码本质上也是一样的:
Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”), “GBK”);
Writer writer = new FileWriter(“file1.txt”, Charset.forName(“GBK”));
/**
* OutputStreamWriter也是一个字符流。也是一个转换流。
* OutputStreamWriter是一个编码的过程。
* 如果OutputStreamWriter在编码的过程中使用的字符集和文件的字符集不一致时会出现乱码。
*
* FileWriter是OutputStreamWriter的子类。
* FileWriter的出现简化了java代码。
* FileWriter是一个包装流,不是节点流。
*/
public class OutputStreamWriterEncodingTest {
public static void main(String[] args) throws Exception{
// 创建转换流对象OutputStreamWriter
// 以下代码采用的是UTF-8的字符集进行编码。(采用平台默认的字符集)
// 注意:以下代码中输出流以覆盖的方式输出/写。
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt"));
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt"), "GBK");
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true), "GBK");
/*OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true));*/
/*OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true), "GBK");*/
FileWriter osw = new FileWriter("E:\\powernode\\02-JavaSE\\code\\file4.txt", Charset.forName("UTF-8"), true);
// 开始写
osw.write("来动力节点学Java");
osw.flush();;
osw.close();
}
}
数据流
DataOutputStream/DataInputStream
这两个流都是包装流,读写数据专用的流。
DataOutputStream直接将程序中的数据写入文件,不需要转码,效率高。程序中是什么样子,原封不动的写出去。写完后,文件是打不开的。即使打开也是乱码,文件中直接存储的是二进制。
使用DataOutputStream写的文件,只能使用DataInputStream去读取。并且读取的顺序需要和写入的顺序一致,这样才能保证数据恢复原样。
构造方法:
DataInputStream(InputStream in)
DataOutputStream(OutputStream out)
写的方法:
writeByte()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble()、writeBoolean()、writeChar()、writeUTF(String)
读的方法:
readByte()、readShort()、readInt()、readLong()、readFloat()、readDouble()、readBoolean()、readChar()、readUTF()
/**
* java.io.DataOutputStream:数据流(数据字节输出流)
* 作用:将java程序中的数据直接写入到文件,写到文件中就是二进制。
* DataOutputStream写的效率很高,原因是:写的过程不需要转码。
* DataOutputStream写到文件中的数据,只能由DataInputStream来读取。
*/
public class DataOutputStreamTest {
public static void main(String[] args) throws Exception{
// 节点流
//OutputStream os = new FileOutputStream("data");
// 包装流
//DataOutputStream dos = new DataOutputStream(os);
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
// 准备数据
byte b = -127;
short s = 32767;
int i = 2147483647;
long l = 1111111111L;
float f = 3.0F;
double d = 3.14;
boolean flag = false;
char c = '国';
String str = "动力节点";
// 开始写
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(flag);
dos.writeChar(c);
dos.writeUTF(str);
dos.flush();
dos.close();
}
}
/**
* java.io.DataInputStream:数据流(数据字节输入流)
* 作用:专门用来读取使用DataOutputStream流写入的文件。
* 注意:读取的顺序要和写入的顺序一致。(要不然无法恢复原样。)
*/
public class DataInputStreamTest {
public static void main(String[] args) throws Exception{
// 创建数据字节输入流对象
DataInputStream dis = new DataInputStream(new FileInputStream("data"));
//System.out.println(dis.readBoolean());
// 开始读
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean flag = dis.readBoolean();
char c = dis.readChar();
String str = dis.readUTF();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(flag);
System.out.println(c);
System.out.println(str);
// 关闭流
dis.close();
/*FileInputStream fis = new FileInputStream("data");
System.out.println(fis.read());
System.out.println(fis.read());
System.out.println(fis.read());
System.out.println(fis.read());
fis.close();*/
}
}
对象流
ObjectOutputStream/ObjectInputStream
通过这两个流,可以完成对象的序列化和反序列化。
序列化(Serial):将Java对象转换为字节序列。(为了方便在网络中传输),使用ObjectOutputStream序列化。
反序列化(DeSerial):将字节序列转换为Java对象。使用ObjectInputStream进行反序列化。
参与序列化和反序列化的java对象必须实现一个标志性接口:java.io.Serializable
实现了Serializable接口的类,编译器会自动给该类添加序列化版本号的属性:serialVersionUID
在java中,是通过“类名 + 序列化版本号”来进行类的区分的。
serialVersionUID实际上是一种安全机制。在反序列化的时候,JVM会去检查存储Java对象的文件中的class的序列化版本号是否和当前Java程序中的class的序列化版本号是否一致。如果一致则可以反序列化。如果不一致则报错。
如果一个类实现了Serializable接口,还是建议将序列化版本号固定死,建议显示的定义出来,原因是:类有可能在开发中升级(改动),升级后会重新编译,如果没有固定死,编译器会重新分配一个新的序列化版本号,导致之前序列化的对象无法反序列化。显示定义序列化版本号的语法:private static final long serialVersionUID = XXL;
为了保证显示定义的序列化版本号不会写错,建议使用 @java.io.Serial 注解进行标注。并且使用它还可以帮助我们随机生成序列化版本号。
不参与序列化的属性需要使用瞬时关键字修饰:transient
/**
* java.io.ObjectOutputStream:对象流(对象字节输出流)
* 1. 它的作用是完成对象的序列化过程。
* 2. 它可以将JVM当中的Java对象序列化到文件中/网络中。
* 3. 序列化:将Java对象转换为字节序列的过程。(字节序列可以在网络中传输。)
* 4. 序列化:serial
*/
public class ObjectOutputStreamTest {
public static void main(String[] args) throws Exception{
// 创建“对象字节输出流”对象
// 包装流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object"));
// 准备一个Java对象
Date nowTime = new Date();
// 序列化 serial
oos.writeObject(nowTime);
// 刷新
oos.flush();
// 关闭
oos.close();
}
}
/**
* java.io.ObjectInputStream:对象流(对象字节输入流)
* 1. 专门完成反序列化的。(将字节序列转换成JVM当中的java对象。)
*/
public class ObjectInputStreamTest {
public static void main(String[] args) throws Exception{
// 包装流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object"));
// 读
Object o = ois.readObject();
System.out.println(o);
// 关闭
ois.close();
}
}
序列化和反序列化自定义类型
/**
* 1. 重点:凡是参与序列化和反序列化的对象必须实现 java.io.Serializable 可序列化的接口。
* 2. 这个接口是一个标志接口,没有任何方法。只是起到一个标记的作用。
* 3. 它到底是标记什么呢??????
* 4. 当java程序中类实现了Serializable接口,编译器会自动给该类添加一个“序列化版本号”。
* 序列化版本号:serialVersionUID
* 5. 序列化版本号有什么用?
* 在Java语言中是如何区分class版本的?
* 首先通过类的名字,然后再通过序列化版本号进行区分的。
* 在java语言中,不能仅仅通过一个类的名字来进行类的区分,这样太危险了。
* 6. 为了保证序列化的安全,只有同一个class才能进行序列化和反序列化。在java中是如何保证同一个class的?
* 类名 + 序列化版本号:serialVersionUID
*
* java.io.InvalidClassException: com.powernode.javase.io.Student;
* local class incompatible:
* stream classdesc serialVersionUID = -4936871645261081394, (三年前的学生对象,是三年前的Student.class创建的学生对象。)
* local class serialVersionUID = 5009257763737485728 (三年后,Student.class升级了。导致了版本发生了变化。)
*/
public class Student implements Serializable {
// 建议:不是必须的。
// 如果你确定这个类确实还是以前的那个类。类本身是合法的。没有问题。
// 建议你将序列化版本号写死!
@Serial
private static final long serialVersionUID = -7005027670916214239L;
private String name;
private transient int age; // transient关键字修饰的属性不会参与序列化。
private String addr;
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class ObjectOutputStreamTest03 {
public static void main(String[] args) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student"));
Student stu = new Student("zhangsan", 20);
oos.writeObject(stu);
oos.flush();
oos.close();
}
}
/**
* 反序列化过程:将文件中的Student字节序列恢复到内存中,变成Student对象。
*/
public class ObjectInputStreamTest03 {
public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student"));
System.out.println(ois.readObject());
ois.close();
}
}
注意
其实ObjectOutputStream中也有这些方法,和DataOutputStream中的方法一样。
oos.writeInt(100);
oos.writeBoolean(false);
oos.writeUTF("张三");
打印流
PrintStream
打印流(字节形式)
主要用在打印方面,提供便捷的打印方法和格式化输出。主要打印内容到文件或控制台。
常用方法:
print(Type x)
println(Type x)
便捷在哪里?
直接输出各种数据类型
自动刷新和自动换行(println方法)
支持字符串转义
自动编码(自动根据环境选择合适的编码方式)
格式化输出?调用printf方法。
%s 表示字符串
%d 表示整数
%f 表示小数(%.2f 这个格式就代表保留两位小数的数字。)
%c 表示字符
/**
* 1. java.io.PrintStream:打印流(专业的负责打印的流,字节形式。)
* 2. PrintStream不需要手动刷新,自动刷新。
*/
public class PrintStreamTest {
public static void main(String[] args) throws Exception{
// 创建一个打印流对象
// 构造方法:PrintStream(OutputStream out)
// 构造方法:PrintStream(String fileName)
PrintStream ps = new PrintStream("log1");
// 没有这样的构造方法。
//PrintStream ps2 = new PrintStream(new FileWriter(""));
//PrintStream ps2 = new PrintStream(new FileOutputStream("log1"));
// 打印流可以打印各种数据类型数据。
ps.print(100);
ps.println(false);
ps.println("abc");
ps.println('T');
ps.println(3.14);
ps.println("hell world");
ps.println(200);
ps.println("\"hello world!\"");
String name = "张三";
double score = 95.5;
ps.printf("姓名:%s,考试成绩:%.2f", name, score);
// 关闭
ps.close();
}
}
PrintWriter
打印流(字符形式)注意PrintWriter使用时需要手动调用flush()方法进行刷新。
比PrintStream多一个构造方法,PrintStream参数只能是OutputStream类型,但PrintWriter参数可以是OutputStream,也可以是Writer。
常用方法:
print(Type x)
println(Type x)
同样,也可以支持格式化输出,调用printf方法。
/**
* java.io.PrintWriter:专门负责打印的流。(字符形式)
* 需要手动刷新flush。
* PrintWriter比PrintStream多一个构造方法:
* PrintStream构造方法:
* PrintStream(OutputStream)
* PrintWriter构造方法:
* PrintWriter(OutputStream)
* PrintWriter(Writer)
*/
public class PrintWriterTest {
public static void main(String[] args) throws Exception{
// 创建字符打印流
//PrintWriter pw = new PrintWriter(new FileOutputStream("log2"));
PrintWriter pw = new PrintWriter(new FileWriter("log2"), true);
// 打印
pw.println("world hello!!!");
pw.println("zhangsan hello!!!");
// 刷新
//pw.flush();
// 关闭
pw.close();
}
}
标准输入流&标准输出流
标准输入流
System.in获取到的InputStream就是一个标准输入流。
标准输入流是用来接收用户在控制台上的输入的。(普通的输入流,是获得文件或网络中的数据)
标准输入流不需要关闭。(它是一个系统级的全局的流,JVM负责最后的关闭。)
也可以使用BufferedReader对标准输入流进行包装。这样可以方便的接收用户在控制台上的输入。(这种方式太麻烦了,因此JDK中提供了更好用的Scanner。)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
当然,你也可以修改输入流的方向(System.setIn())。让其指向文件。
/**
* 标准输入流:System.in
* 1. 标准输入流怎么获取?
* System.in
* 2. 标准输入流是从哪个数据源读取数据的?
* 控制台。
* 3. 普通输入流是从哪个数据源读取数据的?
* 文件或者网络或者其他.....
* 4. 标准输入流是一个全局的输入流,不需要手动关闭。JVM退出的时候,JVM会负责关闭这个流。
*/
public class SystemInTest {
public static void main(String[] args) throws Exception{
// 获取标准输入流对象。(直接通过系统类System中的in属性来获取标准输入流对象。)
InputStream in = System.in;
// 开始读
byte[] bytes = new byte[1024];
int readCount = in.read(bytes);
for (int i = 0; i < readCount; i++) {
System.out.println(bytes[i]);
}
}
}
/**
* 对于标准输入流来说,也是可以改变数据源的。不让其从控制台读数据。也可以让其从文件中或网络中读取数据。
*/
public class SystemInTest02 {
public static void main(String[] args) throws Exception{
// 修改标准输入流的数据源。
System.setIn(new FileInputStream("log2"));
// 获取标准输入流
InputStream in = System.in;
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = in.read(bytes)) != -1){
System.out.print(new String(bytes, 0, readCount));
}
}
}
/**
* 使用BufferedReader去包装一下这个标准输入流,来完成从键盘接收用户的输入。
*/
public class SystemInTest03 {
public static void main(String[] args) throws Exception{
// 创建BufferedReader对象
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
/*InputStream in = System.in;
Reader reader = new InputStreamReader(in);
BufferedReader br = new BufferedReader(reader);*/
String s = null;
while((s = br.readLine()) != null){
if("exit".equals(s)){
break;
}
System.out.println("您输入了:" + s);
}
/*Scanner scanner = new Scanner(System.in);
String name = scanner.next();
System.out.println("您的姓名是:" + name);*/
}
}
标准输出流
System.out获取到的PrintStream就是一个标准输出流。
标准输出流是用来向控制台上输出的。(普通的输出流,是向文件和网络等输出的。)
标准输出流不需要关闭(它是一个系统级的全局的流,JVM负责最后的关闭。)也不需要手动刷新。
当然,你也可以修改输出流的方向(System.setOut())。让其指向文件。
/**
* 标准输出流:System.out
* 1. 标准输出流怎么获取?
* System.out
* 2. 标准输出流是向哪里输出呢?
* 控制台。
* 3. 普通输出流是向哪里输出呢?
* 文件或者网络或者其他.....
* 4. 标准输出流是一个全局的输出流,不需要手动关闭。JVM退出的时候,JVM会负责关闭这个流。
*/
public class SystemOutTest {
public static void main(String[] args) throws Exception {
// 获取标准输出流,标准输出流默认会向控制台输出。
PrintStream out = System.out;
// 输出
out.println("hello world");
out.println("hello world");
out.println("hello world");
out.println("hello world");
out.println("hello world");
// 标准输出流也是可以改变输出方向的。
System.setOut(new PrintStream("log"));
System.out.println("zhangsan");
System.out.println("lisi");
System.out.println("wangwu");
System.out.println("zhaoliu");
// 获取系统当前时间
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String str = sdf.format(now);
System.out.println(str + ": SystemOutTest's main method invoked!");
}
}
File类
File类不是IO流,和IO的四个头领没有关系。因此通过File是无法读写文件。
File类是路径的抽象表示形式,这个路径可能是目录,也可能是文件。因此File代表了某个文件或某个目录。
File类常用的构造方法:File(String pathname);
File类的常用方法:
boolean createNewFile(); boolean delete();
boolean exists(); String getAbsolutePath();
String getName(); String getParent();
boolean isAbsolute(); boolean isDirectory();
boolean isFile(); boolean isHidden();
long lastModified(); long length();
File[] listFiles(); File[] listFiles(FilenameFilter filter);
boolean mkdir(); boolean mkdirs();
boolean renameTo(File dest); boolean setReadOnly();
boolean setWritable(boolean writable);
编写程序要求可以完成目录的拷贝。
/**
* java.io.File
* 1.File是路径的抽象表示形式。
* 2.File和IO流没有继承关系,父类是Object,通过File不能完成文件的读和写。
* 3.File可能是一个文件,也可能是一个目录。
*/
public class FileTest01 {
public static void main(String[] args) throws Exception{
// 构造一个File对象
File file = new File("e:/file");
// 调用File对象的相关方法
System.out.println(file.exists() ? "存在" : "不存在");
// 如果不存在则以新文件的形式创建
/*if(!file.exists()){
// 以新的文件的形式创建出来
file.createNewFile();
}*/
// 如果不存在则以目录的形式新建
if(!file.exists()){
file.mkdir();
}
// 构造一个File对象
File file2 = new File("e:/a/b/c/d");
// 如果不存在则以目录的形式新建
if(!file2.exists()){
file2.mkdirs();
}
}
}
public class FileTest03 {
public static void main(String[] args) {
// 构建一个File对象
File file1 = new File("E:/file");
// 判断该文件是否是隐藏文件
System.out.println(file1.isHidden() ? "隐藏文件" : "非隐藏文件");
// 获取文件的最后修改时间点
long l = file1.lastModified();
Date time = new Date(l);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String str = sdf.format(time);
System.out.println("文件最后修改时间点:" + str);
// 获取文件的大小:总字节数
System.out.println(file1.getName() + "文件的总字节数:" + file1.length() + "字节");
// 重命名
File file2 = new File("file2");
file1.renameTo(file2);
}
}
/**
* File类的常用方法:File[] listFiles();
*/
public class FileTest04 {
public static void main(String[] args) {
File file = new File("E:\\powernode\\02-JavaSE\\document");
// 获取所有的子文件,包括子目录。
File[] files = file.listFiles();
// 遍历数组
for(File f : files){
System.out.println(f.getName());
}
System.out.println("=====================================");
File file1 = new File("E:\\powernode\\02-JavaSE\\document");
File[] files1 = file1.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//过滤作用
/*if (name.endsWith(".mdj")) {
return true;
}
return false;*/
return name.endsWith(".mdj");
}
});
for(File f : files1){
System.out.println(f.getName());
}
}
}
目录的递归拷贝
/**
* 目录拷贝。
*/
public class CopyDir {
public static void main(String[] args) {
// 拷贝源
File src = new File("E:\\powernode\\02-JavaSE\\code"); // E:\powernode\02-JavaSE\code\chapter01\A.java
// 拷贝目标
File dest = new File("E:\\a\\b\\c"); // E:\a\b\c\powernode\02-JavaSE\code\chapter01\A.java
// 开始拷贝
copy(src, dest);
}
/**
* 拷贝目录的方法
* @param src 拷贝源
* @param dest 拷贝目标
*/
private static void copy(File src, File dest) {
if(src.isFile()){
// 是文件的时候要拷贝。
try(FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dest.getAbsoluteFile() + src.getAbsolutePath().substring(2))){
// 开始拷贝
byte[] bytes = new byte[1024 * 1024];
int readCount = 0;
while((readCount = in.read(bytes)) != -1){
out.write(bytes, 0, readCount);
}
out.flush();
}catch(IOException e){
e.printStackTrace();
}
return;
}
// 假设src是一个目录
// 程序能够执行到此处一定是一个目录
// 创建目录
File newDir = new File(dest.getAbsolutePath() + src.getAbsolutePath().substring(2));
if(!newDir.exists()){
newDir.mkdirs();
}
File[] files = src.listFiles();
for (File file : files){
//System.out.println(file.getAbsolutePath());
copy(file, dest);
}
}
}
读取属性配置文件
Properties + IO
xxx.properties文件称为属性配置文件。
属性配置文件可以配置一些简单的信息,例如连接数据库的信息通常配置到属性文件中。这样可以做到在不修改java代码的前提下,切换数据库。
属性配置文件的格式:
key1=value1
key2=value2
key3=value3
注意:使用 # 进行注释。key不能重复,key重复则value覆盖。key和value之间用等号分割。等号两边不要有空格。
Java中如何读取属性配置文件?
当然,也可以使用Java中的工具类快速获取配置信息:ResourceBundle
这种方式要求文件必须是xxx.properties
属性配置文件必须放在类路径当中
/**
* 使用Properties集合类 + IO流来读取属性配置文件。
* 将属性配置文件中的配置信息加载到内存中。
*/
public class LoadProperties {
public static void main(String[] args) throws Exception {
// 创建输入流对象
//FileReader reader = new FileReader("chapter08/src/db.properties");
String path = Thread.currentThread().getContextClassLoader().getResource("jdbc.properties").getPath();
//该路径不要有中文
FileReader reader = new FileReader(path);
// 创建一个Map集合(属性类对象)
Properties pro = new Properties();
// 加载:将jdbc.properties文件中的配置信息加载到Properties对象中。
pro.load(reader);
// 获取所有key
/*Enumeration<?> names = pro.propertyNames();
while (names.hasMoreElements()) {
String name = (String)names.nextElement();
String value = pro.getProperty(name);
System.out.println(name + "=" + value);
}*/
// 通过key来获取value
String driver = pro.getProperty("driver");
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
System.out.println(driver);
System.out.println(url);
System.out.println(user);
System.out.println(password);
// 关闭输入流
reader.close();
}
}
/**
* 使用JDK中提供的资源绑定器来绑定属性配置文件。
*/
public class BundleProperties {
public static void main(String[] args) {
// 获取资源绑定器对象
// 使用这个工具要求文件也必须是一个属性配置文件。xxx.properties
ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.io.jdbc");
//ResourceBundle bundle = ResourceBundle.getBundle("com/powernode/javase/io/jdbc");
// 这个获取的是类的根路径下的jdbc.properties文件。
//ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
// 这个代码的意思是从类的根路径下找db.properties文件。
//ResourceBundle bundle = ResourceBundle.getBundle("db");
// 以下两行都是错误的:资源找不到。
//ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.io.db.properties");
//ResourceBundle bundle = ResourceBundle.getBundle("com/powernode/javase/io/db.properties");
// 通过key获取value
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
System.out.println(driver);
System.out.println(url);
System.out.println(user);
System.out.println(password);
}
}
装饰者模式
* 0. 装饰器设计模式的主要目标:在松耦合的前提下,能够完成功能的扩展。
* 1. 在装饰器设计模式中有两个非常重要的角色:装饰者,被装饰者。
* 2. 装饰器设计模式当中要求:装饰者 与 被装饰者 应实现同一个接口/同一些接口,继承同一个抽象类....
* 3. 为什么装饰者 与 被装饰者 要实现同一个接口呢?
* 因为实现了同一个接口之后,对于客户端程序来说,使用装饰者的时候就向在使用被装饰者一样。
* 4. 装饰者含有被装饰者的引用。(A has a B。尽量使用has a【耦合度低一些】。不要使用is a。)
压缩和解压缩流
GZIPOutputStream(压缩)
使用GZIPOutputStream可以将文件制作为压缩文件,压缩文件的格式为 .gz 格式。
核心代码:
FileInputStream fis = new FileInputStream("d:/test.txt"); // 被压缩的文件:test.txt
GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream("d:/test.txt.gz")) // 压缩后的文件
接下来就是边读边写:
int length;
while ((length = fis.read(buffer)) > 0) {
gzos.write(buffer, 0, length);
}
gzos.finish(); // 在压缩完所有数据之后调用finish()方法,以确保所有未压缩的数据都被刷新到输出流中,并生成必要的 Gzip 结束标记,标志着压缩数据的结束。
注意(补充):实际上所有的输出流中,只有带有缓冲区的流才需要手动刷新,节点流是不需要手动刷新的,节点流在关闭的时候会自动刷新。
public static void main(String[] args) throws Exception{
// 创建文件字节输入流(读某个文件,这个文件将来就是被压缩的。)
FileInputStream in = new FileInputStream("e:/test.txt");
// 创建一个GZIP压缩流对象
GZIPOutputStream gzip = new GZIPOutputStream(new FileOutputStream("e:/test.txt.gz"));
// 开始压缩(一边读一边读)
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = in.read(bytes)) != -1){
gzip.write(bytes, 0, readCount);
}
// 非常重要的代码需要调用
// 刷新并且最终生成压缩文件。
gzip.finish();
// 关闭流
in.close();
gzip.close();
}
GZIPInputStream(解压缩)
使用GZIPInputStream可以将 .gz 格式的压缩文件解压。
核心代码:
GZIPInputStream gzip = new GZIPInputStream(new FileInputStream("d:/test.txt.gz"));
FileOutputStream out = new FileOutputStream("d:/test.txt");
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = gzip.read(bytes)) != -1){
out.write(bytes, 0, readCount);
}
public static void main(String[] args) throws Exception {
// 创建GZIP解压缩流对象
GZIPInputStream gzip = new GZIPInputStream(new FileInputStream("e:/test.txt.gz"));
// 创建文件字节输出流
FileOutputStream out = new FileOutputStream("e:/test.txt");
// 一边读一边写
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = gzip.read(bytes)) != -1){
out.write(bytes, 0, readCount);
}
// 关闭流
gzip.close();
// 节点流关闭的时候会自动刷新,包装流是需要手动刷新的。(补充的知识点。)
out.close();
}
字节数组流(内存流)
ByteArrayInputStream和ByteArrayOutputStream都是内存操作流,不需要打开和关闭文件等操作。这些流是非常常用的,可以将它们看作开发中的常用工具,能够方便地读写字节数组、图像数据等内存中的数据。
ByteArrayInputStream和ByteArrayOutputStream都是节点流。
ByteArrayOutputStream,将数据写入到内存中的字节数组当中。
ByteArrayInputStream,读取内存中某个字节数组中的数据。
public static void main(String[] args) {
// ByteArrayOutputStream的基本用法。
ByteArrayOutputStream baos = new ByteArrayOutputStream(); //节点流
// 开始写
baos.write(1);
baos.write(2);
baos.write(3);
// 怎么获取内存中的哪个byte[]数组呢?
byte[] byteArray = baos.toByteArray();
for (byte b : byteArray){
System.out.println(b);
}
}
public static void main(String[] args) throws Exception{
// 节点流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 包装流
ObjectOutputStream oos = new ObjectOutputStream(baos);
// 开始写
oos.writeInt(100);
oos.writeBoolean(false);
oos.writeDouble(3.14);
oos.writeUTF("动力节点");
oos.writeObject(new Date());
// 使用了包装流就需要手动刷新一下。
oos.flush();
// 获取内存中的大byte数组
byte[] byteArray = baos.toByteArray();
/*for(byte b : byteArray){
System.out.println(b);
}*/
// 使用ByteArrayInputStream将上面这个byte数组恢复。
// 读的过程,读内存中的大byte数组。
// 节点流
ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
// 包装流
ObjectInputStream ois = new ObjectInputStream(bais);
// 开始读
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
System.out.println(ois.readObject());
}
对象克隆
除了我们之前所讲的深克隆方式(之前的深克隆是重写clone()方法)。使用字节数组流也可以完成对象的深克隆。
原理是:将要克隆的Java对象写到内存中的字节数组中,再从内存中的字节数组中读取对象,读取到的对象就是一个深克隆。
目前为止,对象拷贝方式:
调用Object的clone方法,默认是浅克隆,需要深克隆的话,就需要重写clone方法。
可以通过序列化和反序列化完成对象的克隆。
也可以通过ByteArrayInputStream和ByteArrayOutputStream完成深克隆。
public static void main(String[] args) throws Exception{
// 准备对象
Address addr = new Address("北京", "朝阳");
User user = new User("zhangsan", 20, addr);
// 将Java对象写到一个byte数组中。
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(user);
oos.flush();
// 从byte数组中读取数据恢复java对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
// 这就是哪个经过深拷贝之后的新对象
User user2 = (User) ois.readObject();
user2.getAddr().setCity("南京");
System.out.println(user);
System.out.println(user2);
}