27--缓冲流、转换流、对象流及其他流的使用
1、处理流之一:缓冲流
为了提高数据读写的速度,Java API提供了带缓冲功能的流类:缓冲流。
缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
- 字节缓冲流:BufferedInputStream,BufferedOutputStream
- 字符缓冲流:BufferedReader,BufferedWriter
缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(缺省使用8192个字节(8Kb)的缓冲区),通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
1.1 字节缓冲流
1.1.1 构造器
- public BufferedInputStream(InputStream in) :创建一个 新的字节型的缓冲输入流。
- public BufferedOutputStream(OutputStream out): 创建一个新的字节型的缓冲输出流。
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abc.jpg"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc_copy.jpg"));
1.1.2 字节缓冲输出流
package com.suyv.test1;
import org.junit.Test;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 09:46
* @Description: 字节缓冲输出流的使用--BufferedOutputStream
*/
public class BufferedOutputStreamDemo {
@Test
public void Test01() throws IOException {
//相当于把字节流进行了一次装
FileOutputStream fos = new FileOutputStream("file\\hello.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
//简单写法
// BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("file\hello.txt"));
//也可以进行追加
//BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("file\hello.txt",true));
// 写一个字节
bos.write(107);
// 写一个字节数组
byte[] buffer = {97,98,99};
bos.write(buffer);
bos.write("hello".getBytes());
/*写入内容为:kabchello*/
// 关闭流资源
bos.close();
}
}
1.1.3 字节缓冲输入流
package com.suyv.test1;
import org.junit.Test;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 09:54
* @Description: 字节缓冲输入流的使用--BufferedInputStream
*/
public class BufferedInputStreamDemo {
@Test
public void Test01() throws IOException {
// 创建流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file\\hello.txt"));
// 操作流
// 一次读一个字节
System.out.println((char) bis.read());
// 一次读一个字节数组
byte[] buffer = new byte[5];
int len;
while ((len = bis.read(buffer)) != -1){
System.out.println(new String(buffer,0,len));
}
// 关闭流资源
bis.close();
}
}
1.1.4 字节缓冲流复制
package com.suyv.test1;
import org.junit.Test;
import java.io.*;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 10:48
* @Description: 使用字节缓冲流复制文件--BufferedInputStream-BufferedOutputStream
*/
public class BufferedInputOutputStream {
@Test
public void Test01() throws IOException {
// 创建流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file\\cat.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("file\\cat_copy.jpg"));
// 操作流
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
// 关闭流
bis.close();
bos.close();
}
}
1.2 字符缓冲流
1.2.1 构造器
- public BufferedReader(Reader in) :创建一个 新的字符型的缓冲输入流。
- public BufferedWriter(Writer out): 创建一个新的字符型的缓冲输出流。
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
1.2.2 字符缓冲输出流
package com.suyv.test1;
import org.junit.Test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 11:03
* @Description: 字符缓冲输出流的使用--BufferedWriter
*/
public class BufferedWriter01 {
@Test
public void Test01() throws IOException {
//创建流
BufferedWriter bw = new BufferedWriter(new FileWriter("file\\bw.txt"));
// 操作流
bw.write(97);
bw.write("hello");
char[] chs = {'j','a','v','a',};
bw.write(chs);
/*文件内容为:ahellojava*/
// 关闭流
bw.close();
}
}
1.2.3 字符缓冲输入流
package com.suyv.test1;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 11:07
* @Description: 字符缓冲输入流的使用--BufferedReader
*/
public class BufferedReader01 {
@Test
public void Test01() throws IOException {
// 创建流
BufferedReader br = new BufferedReader(new FileReader("file\\bw.txt"));
// 操作流
//读一个字节
System.out.println((char) br.read());
//循环读字符数组
char[] buffer = new char[5];
int len;
while ( (len = br.read(buffer)) != -1){
System.out.print(new String(buffer,0,len));
}
// 关闭流
br.close();
}
}
1.2.4 字符缓冲流复制
package com.suyv.test1;
import org.junit.Test;
import java.io.*;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 11:37
* @Description: 使用字符缓冲流复制文件--BufferedInputStream-BufferedOutputStream
*/
public class BufferedReaderWriter {
/*
* 需求:把当前项目目录下的snow.txt内容复制到当前项目目录下的snow_copy.txt中
*
* 数据源:
* a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader -- BufferedReader
* 目的地:
* b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter -- BufferedWriter
*/
@Test
public void Test01() throws IOException {
// 创建缓冲流
BufferedReader br = new BufferedReader(new FileReader("file\\snow.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("file\\snow_copy.txt"));
// 操作流
char[] buffer = new char[1024];
int len;
while ((len = br.read(buffer)) != -1){
bw.write(buffer,0,len);
}
// 关闭流
bw.close();
br.close();
}
}
1.2.5 字符缓冲流特有方法
- BufferedReader:public String readLine(): 读一行文字。
- BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。
package com.suyv.test1;
import org.junit.Test;
import java.io.*;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 12:15
* @Description: 字符缓冲流特有方法
*/
public class BufferedReaderWriter01 {
// BufferedReader:public String readLine(): 读一行文字。
// BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。
@Test
public void Test01() throws IOException {
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("file\\snow.txt"));
// 定义字符串,保存读取的一行文字
String line;
// 循环读取,读取到最后返回null
while ((line = br.readLine()) != null) {
System.out.println(line);
}
// 释放资源
br.close();
}
@Test
public void Test02() throws IOException {
// 创建流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("file\\out.txt"));
// 写出数据
bw.write("Hello");
// 写出换行
bw.newLine();
bw.write("中国");
bw.newLine();
bw.write("欢迎你");
bw.newLine();
// 释放资源
bw.close();
}
}
说明:
- 涉及到嵌套的多个流时,如果都显式关闭的话,需要先关闭外层的流,再关闭内层的流。
- 其实在开发中,只需要关闭最外层的流即可,因为在关闭外层流时,内层的流也会被关闭。
1.3 效率测试
查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(375MB),测试它的效率。
package com.suyv.test1;
import org.junit.Test;
import java.io.*;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 12:24
* @Description: TODO
*/
public class EfficiencyDemo {
/*节点流:FileInputStream、FileOutputStream
缓冲流:BufferedInputStream、BufferedOutputStream
效率测试*/
//方法1:使用节点流FileInputStream\FileOutputStream实现非文本文件(260MB)的复制
public void copyFileWithFileStream(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//1. 造文件-造流
fis = new FileInputStream(new File(srcPath));
fos = new FileOutputStream(new File(destPath));
//2. 复制操作(读、写)
byte[] buffer = new byte[100];
int len;//每次读入到buffer中字节的个数
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("复制成功");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//3. 关闭资源
try {
if (fos != null)
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (fis != null)
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void test1(){
String srcPath = "E:\\JavaTest\\test.mp4";
String destPath = "E:\\JavaTest\\test_copy.mp4";
long start = System.currentTimeMillis();
copyFileWithFileStream(srcPath,destPath);
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//7645毫秒
}
//方法2:使用缓冲流BufferedInputStream\BufferedOuputStream实现非文本文件(260MB)的复制
public void copyFileWithBufferedStream(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1. 造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2. 造流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3. 读写操作
int len;
byte[] buffer = new byte[100];
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
System.out.println("复制成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源(如果有多个流,我们需要先关闭外面的流,再关闭内部的流)
try {
if (bos != null)
bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (bis != null)
bis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void test2(){
String srcPath = "E:\\JavaTest\\test.mp4";
String destPath = "E:\\JavaTest\\test_copy1.mp4";
long start = System.currentTimeMillis();
copyFileWithBufferedStream(srcPath,destPath);
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//460毫秒
}
}
2、处理流之二:转换流
2.1 问题引入
引入情况1:
使用FileReader 读取项目中的文本文件。由于IDEA设置中针对项目设置了UTF-8编码,当读取Windows系统中创建的文本文件时,如果Windows系统默认的是GBK编码,则读入内存中会出现乱码。
package com.suyv.test2;
import org.junit.Test;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 14:29
* @Description: 字符输入流乱码
*/
public class Demo01 {
@Test
public void Test01() throws IOException {
// 创建FileReader对象
FileReader fr = new FileReader("file\\gbk.txt");
// 操作流
int ch;
while ((ch = fr.read()) != -1){
System.out.print((char) ch);
}
/*输出内容:
���?��
����*/
// 关闭流
fr.close();
}
}
2.2 转换流的理解
作用:转换流是字节与字符间的桥梁!
具体来说:
2.3 InputStreamReader 与 OutputStreamWriter
2.3.1 InputStreamReader
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造器:
- InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
- InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
举例:
//使用默认字符集
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("in.txt"));
//使用指定字符集
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
示例代码:
package com.suyv.test2;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 14:43
* @Description: 转换输入流--InputStreamReader
*/
public class InputStreamReader01 {
@Test
public void Test01() throws IOException {
// 定义文件路径,文件为gbk编码
String fileName = "file\\gbk.txt";
//方式1:
// 创建流对象,默认UTF8编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(fileName));
// 定义变量,保存字符
int ch;
// 使用默认编码字符流读取,乱码
while ((ch = isr.read()) != -1) {
System.out.print((char)ch);
}
isr.close();
}
@Test
public void Test02() throws IOException {
// 定义文件路径,文件为gbk编码
String fileName = "file\\gbk.txt";
//方式2:
// 创建流对象,指定GBK编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(fileName) , "GBK");
// 使用指定编码字符流读取,正常解析
int ch;
while ((ch = isr.read()) != -1) {
System.out.print((char)ch);
}
isr.close();
}
}
2.3.2 OutputStreamWriter
转换流java.io.OutputStreamWriter
,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造器:
- OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
- OutputStreamWriter(OutputStream in,String charsetName): 创建一个指定字符集的字符流。
举例:
//使用默认字符集
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
//使用指定的字符集
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");
示例代码:
package com.suyv.test2;
import org.junit.Test;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-21 14:47
* @Description: 转换输出流--OutputStreamWriter
*/
public class OutputStreamWriter01 {
@Test
public void Test01() throws IOException {
// 定义文件路径
String FileName = "file\\out_utf8.txt";
// 创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
// 写出数据
osw.write("你好"); // 保存为6个字节
// 关闭流
osw.close();
}
@Test
public void Test02() throws IOException {
// 定义文件路径
String FileName2 = "file\\out_gbk.txt";
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
// 写出数据
osw2.write("你好");// 保存为4个字节
// 关闭流
osw2.close();
}
}
2.4 字符编码和字符集
2.4.1 编码与解码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。
字符编码(Character Encoding) : 就是一套自然语言的字符与二进制数之间的对应规则。
编码表:生活中文字和计算机中二进制的对应规则
乱码的情况:按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
编码:字符(人能看懂的)--字节(人看不懂的)
解码:字节(人看不懂的)-->字符(人能看懂的)
2.4.2 字符集
字符集Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。
ASCII字符集 :
- ASCII码(American Standard Code for Information Interchange,美国信息交换标准代码):上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码。
- ASCII码用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
- 基本的ASCII字符集,使用7位(bits)表示一个字符(最前面的1位统一规定为0),共128个字符。比如:空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。
- 缺点:不能表示所有字符。
ISO-8859-1字符集:
- 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰语、德语、意大利语、葡萄牙语等
- ISO-8859-1使用单字节编码,兼容ASCII编码。
GBxxx字符集:
- GB就是国标的意思,是为了显示中文而设计的一套字符集。
- GB2312:简体中文码表。一个小于127的字符的意义与原来相同,即向下兼容ASCII码。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,这就是常说的"全角"字符,而原来在127号以下的那些符号就叫"半角"字符了。
- GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
- GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
Unicode字符集 :
- Unicode编码为表达任意语言的任意字符而设计,也称为统一码、标准万国码。Unicode 将世界上所有的文字用2个字节统一进行编码,为每个字符设定唯一的二进制编码,以满足跨语言、跨平台进行文本处理的要求。
- Unicode 的缺点:这里有三个问题:
- 第一,英文字母只用一个字节表示就够了,如果用更多的字节存储是极大的浪费。
- 第二,如何才能区别Unicode和ASCII?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?
- 第三,如果和GBK等双字节编码方式一样,用最高位是1或0表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。
- Unicode在很长一段时间内无法推广,直到互联网的出现,为解决Unicode如何在网络上传输的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现。具体来说,有三种编码方案,UTF-8、UTF-16和UTF-32。
UTF-8字符集:
- Unicode是字符集,UTF-8、UTF-16、UTF-32是三种将数字转换到程序数据的编码方案。顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。其中,UTF-8 是在互联网上使用最广的一种 Unicode 的实现方式。
- 互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。UTF-8 是一种变长的编码方式。它使用1-4个字节为每个字符编码,编码规则:
- 28个US-ASCII字符,只需一个字节编码。
- 拉丁文等字符,需要二个字节编码。
- 大部分常用字(含中文),使用三个字节编码。
- 其他极少使用的Unicode辅助字符,使用四字节编码。
小结:
3、处理流之三/四:数据流、对象流
3.1 数据流与对象流说明
如果需要将内存中定义的变量(包括基本数据类型或引用数据类型)保存在文件中,那怎么办呢?
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
String name = "巫师";
Student stu = new Student("张三",23,89);
Java提供了数据流和对象流来处理这些类型的数据:
数据流:DataOutputStream、DataInputStream
- DataOutputStream:允许应用程序将基本数据类型、String类型的变量写入输出流中
- DataInputStream:允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型、String类型的变量。
对象流DataInputStream中的方法:
byte readByte() ????????????????????????????????short readShort()
int readInt() ????????????????????????????????????????long readLong()
float readFloat() ????????????????????????????????double readDouble()
char readChar() ????????????????????????????????boolean readBoolean()
String readUTF() ????????????????????????????????void readFully(byte[] b)
- 数据流DataOutputStream中的方法:将上述的方法的read改为相应的write即可。
- 数据流的弊端:只支持Java基本数据类型和字符串的读写,而不支持其它Java对象的类型。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以重点介绍对象流ObjectOutputStream和ObjectInputStream。
对象流:ObjectOutputStream、ObjectInputStream
- ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。通过在流中使用文件可以实现Java各种基本数据类型的数据以及对象的持久存储。
- ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream 写出的基本数据类型的数据和对象进行读入操作,保存在内存中。
说明:对象流的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
3.2 对象流API
ObjectOutputStream中的构造器:
public ObjectOutputStream(OutputStream out)
: 创建一个指定的ObjectOutputStream。
FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
ObjectOutputStream中的方法:
- public void writeBoolean(boolean val):写出一个 boolean 值。
- public void writeByte(int val):写出一个8位字节
- public void writeShort(int val):写出一个16位的 short 值
- public void writeChar(int val):写出一个16位的 char 值
- public void writeInt(int val):写出一个32位的 int 值
- public void writeLong(long val):写出一个64位的 long 值
- public void writeFloat(float val):写出一个32位的 float 值。
- public void writeDouble(double val):写出一个64位的 double 值
- public void writeUTF(String str):将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式。根据字符的值,将字符串 s 中每个字符转换成一个字节、两个字节或三个字节的字节组。注意,将 String 作为基本数据写入流中与将它作为 Object 写入流中明显不同。 如果 s 为 null,则抛出 NullPointerException。
- public void writeObject(Object obj):写出一个obj对象
- public void close() :关闭此输出流并释放与此流相关联的任何系统资源
ObjectInputStream中的构造器:
public ObjectInputStream(InputStream in)
: 创建一个指定的ObjectInputStream。
FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
ObjectInputStream中的方法:
- public boolean readBoolean():读取一个 boolean 值
- public byte readByte():读取一个 8 位的字节
- public short readShort():读取一个 16 位的 short 值
- public char readChar():读取一个 16 位的 char 值
- public int readInt():读取一个 32 位的 int 值
- public long readLong():读取一个 64 位的 long 值
- public float readFloat():读取一个 32 位的 float 值
- public double readDouble():读取一个 64 位的 double 值
- public String readUTF():读取 UTF-8 修改版格式的 String
- public void readObject(Object obj):读入一个obj对象
- public void close() :关闭此输入流并释放与此流相关联的任何系统资源
3.3 认识对象序列化机制
1、何为对象序列化机制?
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
- 序列化过程:用一个字节序列可以表示一个对象,该字节序列包含该对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
- 反序列化过程:该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。
2、序列化机制的重要性
序列化是 RMI(Remote Method Invoke、远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。
序列化的好处,在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。
3、实现原理
序列化:用ObjectOutputStream类保存基本类型数据或对象的机制。方法为:
public final void writeObject (Object obj)
: 将指定的对象写出。
反序列化:用ObjectInputStream类读取基本类型数据或对象的机制。方法为:
public final Object readObject ()
: 读取一个对象。
3.4 如何实现序列化机制
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现java.io.Serializable
接口。Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。
- 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
- 静态(static)变量的值不会序列化。因为静态变量的值不属于某个对象。
举例1:序列化变量
package com.suyv.test1;
import org.junit.Test;
import java.io.*;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 11:26
* @Description: 对象流的使用--ObjectInputStream和 ObjectOutputStream
*/
public class Demo01 {
@Test
public void save() throws IOException {
String name = "巫师";
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file\\game.dat"));
oos.writeUTF(name);
oos.writeInt(age);
oos.writeChar(gender);
oos.writeInt(energy);
oos.writeDouble(price);
oos.writeBoolean(relive);
oos.close();
}
@Test
public void reload()throws IOException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file\\game.dat"));
String name = ois.readUTF();
int age = ois.readInt();
char gender = ois.readChar();
int energy = ois.readInt();
double price = ois.readDouble();
boolean relive = ois.readBoolean();
System.out.println(name+"," + age + "," + gender + "," + energy + "," + price + "," + relive);
ois.close();
}
}
举例2:序列化对象
package com.suyv.test1;
import java.io.Serializable;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 11:31
* @Description: 实体类 -- Employee
*/
public class Employee implements Serializable {
private static final long serialVersionUID = -1446398935944895849L;
public static String company; //static修饰的类变量,不会被序列化
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
public Employee(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public static String getCompany() {
return company;
}
public static void setCompany(String company) {
Employee.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
", company=" + company +
'}';
}
}
package com.suyv.test1;
import org.junit.Test;
import java.io.*;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 11:45
* @Description: 序列化流对象的使用
*/
public class Demo02 {
@Test
public void save() throws IOException {
Employee.setCompany("云和数据");
Employee e = new Employee("三石", "河南郑州", 23);
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file\\employee.dat"));
// 写出对象
oos.writeObject(e);
// 释放资源
oos.close();
System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
}
@Test
public void reload() throws IOException, ClassNotFoundException {
// 创建反序列化流
FileInputStream fis = new FileInputStream("file\\employee.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取一个对象
Employee e = (Employee) ois.readObject();
// 释放资源
ois.close();
fis.close();
System.out.println(e);
}
}
举例3:如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可。
package com.suyv.test1;
import org.junit.Test;
import java.io.*;
import java.util.ArrayList;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 11:48
* @Description: 序列化多个对象--序列化集合
*/
public class Demo03 {
@Test
public void save() throws IOException {
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "周口", 23));
list.add(new Employee("李四", "郑州", 24));
list.add(new Employee("王五", "洛阳", 25));
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file\\employees.dat"));
// 写出对象
oos.writeObject(list);
// 释放资源
oos.close();
}
@Test
public void reload() throws IOException, ClassNotFoundException {
// 创建反序列化流
FileInputStream fis = new FileInputStream("file\\employees.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取一个对象
ArrayList<Employee> list = (ArrayList<Employee>) ois.readObject();
// 释放资源
ois.close();
fis.close();
System.out.println(list);
}
}
3.5 反序列化失败问题
问题1:
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
问题2:
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
解决办法:
Serializable
接口给需要序列化的类,提供了一个序列版本号:serialVersionUID
。凡是实现 Serializable接口的类都应该有一个表示序列化版本标识符的静态变量:
static final long serialVersionUID = 234242343243L; // 它的值由程序员随意指定即可。
- serialVersionUID用来表明类的不同版本间的兼容性。简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。
- 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。因此,建议显式声明。
- 如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。
package com.atguigu.object;
import java.io.Serializable;
public class Employee implements Serializable {
private static final long serialVersionUID = 1324234L; //增加serialVersionUID
//其它结构:略
}
4、其他流的使用
4.1 标准输入、输出流
System.in
和System.out
分别代表了系统标准的输入和输出设备
默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
重定向:通过System类的setIn,setOut方法对默认设备进行改变。
- public static void setIn(InputStream in)
- public static void setOut(PrintStream out)
举例:从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。
package com.suyv.test02;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 11:52
* @Description: 标准输入流输出流的使用
*/
public class Demo01 {
@Test
public void Test01(){
System.out.println("请输入信息(退出输入e或exit):");
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = null;
try {
while ((s = br.readLine()) != null) { // 读取用户输入的一行数据 --> 阻塞程序
if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
System.out.println("安全退出!!");
break;
}
// 将读取到的整行字符串转成大写输出
System.out.println("-->:" + s.toUpperCase());
System.out.println("继续输入信息");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
拓展:System类中有三个常量对象:System.out、System.in、System.err
查看System类中这三个常量对象的声明:
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
奇怪的是,
- 这三个常量对象有final声明,但是却初始化为null。final声明的常量一旦赋值就不能修改,那么null不会空指针异常吗?
- 这三个常量对象为什么要小写?final声明的常量按照命名规范不是应该大写吗?
- 这三个常量的对象有set方法?final声明的常量不是不能修改值吗?set方法是如何修改它们的值的?
final声明的常量,表示在Java的语法体系中它们的值是不能修改的,而这三个常量对象的值是由C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有set方法。
public static void setIn(InputStream in) {
checkIO();
setIn0(in);
}
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
public static void setErr(PrintStream err) {
checkIO();
setErr0(err);
}
private static void checkIO() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setIO"));
}
}
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
4.2 打印流
平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
打印流的分类
名称 | 类名 |
字节打印流 | PrintStream |
字符打印流 | PrintWriter |
打印流的特点
只负责输出数据,不负责读取数据
永远不会抛出IOException
有自己的特有方法
PrintWriter特点
自动换行 println()
不能输出字节 可以输出字节以外的内容
必须是通过配置 自动刷新 (println,printf,format)
boolean autoFlush: true 自动刷新 false,不自动刷新
包装流本身没有写出功能
将字节输出流转换字符输出流
PrintWriter构造方法
方法名 | 说明 |
PrintWriter(String fileName) | 使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新 |
PrintWriter(Writer out, boolean autoFlush) | 创建一个新的PrintWriter out:字符输出流 autoFlush: 一个布尔值,如果为真,则println , printf ,或format方法将刷新输出缓冲区 |
PrintWriter特有方法
方法名 | 说明 |
void write(String s) | 使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新 |
void print(String s) | 输出字符串, 没有换行 |
void println(String s) | 输出字符串并换行. 如果启动了自动刷新, 则会执行自动刷新写入数据 |
void printf(Locale l, String format, Object... args) | 使用指定格式字符串和参数将格式化的字符串写入输出流. 如果启动了自动刷新, 则会执行自动刷新写入数据 |
void format(Locale l, String format, Object... args) | 使用指定格式字符串和参数将格式化的字符串写入输出流. 如果启动了自动刷新, 则会执行自动刷新写入数据 |
代码举例1
package com.suyv.test02;
import org.junit.Test;
import java.io.FileNotFoundException;
import java.io.PrintStream;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 12:25
* @Description: TODO
*/
public class Demo02 {
@Test
public void Test01() throws FileNotFoundException {
PrintStream ps = new PrintStream("file\\io.txt");
ps.println("hello");
ps.println(1);
ps.println(1.5);
ps.close();
}
}
代码举例2
package com.suyv.test02;
import org.junit.Test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 12:26
* @Description: TODO
*/
public class Demo03 {
@Test
public void Test03(){
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(new File("file\\text.txt"));
// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
ps = new PrintStream(fos, true);
if (ps != null) {// 把标准输出流(控制台输出)改成文件
System.setOut(ps);
}
for (int i = 0; i <= 255; i++) { // 输出ASCII字符
System.out.print((char) i);
if (i % 50 == 0) { // 每50个数据一行
System.out.println(); // 换行
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
}
代码举例3:自定义一个日志工具
package com.suyv.test02;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 12:28
* @Description: 日志工具类
*/
public class Logger {
/*
记录日志的方法。
*/
public static void log(String msg) {
try {
// 指向一个日志文件
PrintStream out = new PrintStream(new FileOutputStream("log.txt", true));
// 改变输出方向
System.setOut(out);
// 日期当前时间
Date nowTime = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(nowTime);
System.out.println(strTime + ": " + msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
package com.suyv.test02;
import org.junit.Test;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 12:29
* @Description: TODO
*/
public class LogTest {
@Test
public void Test01(){
//测试工具类是否好用
Logger.log("调用了System类的gc()方法,建议启动垃圾回收");
Logger.log("调用了TeamView的addMember()方法");
Logger.log("用户尝试进行登录,验证失败");
}
}
5、Properties集合
Properties集合概述
是一个Map体系的集合类
Properties可以保存到流中或从流中加载
属性列表中的每个键及其对应的值都是一个字符串
Properties基本使用
package com.test03;
import org.junit.Test;
import java.util.Properties;
import java.util.Set;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 12:32
* @Description: Properties的使用
*/
public class Demo01 {
@Test
public void Test01(){
// 创建集合对象
// Properties<String,String> prop = new Properties<String,String>(); //错误
Properties prop = new Properties();
// 存储元素
prop.put("itfxp001", "林青霞");
prop.put("itfxp002", "张曼玉");
prop.put("itfxp003", "王祖贤");
// 遍历集合
Set<Object> keySet = prop.keySet();
for (Object key : keySet) {
Object value = prop.get(key);
System.out.println(key + "," + value);
}
}
}
Properties集合的特有方法
方法名 | 说明 |
Object setProperty(String key, String value) | 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
Set<String> stringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
代码演示
package com.test03;
import org.junit.Test;
import java.util.Properties;
import java.util.Set;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 13:06
* @Description: TODO
*/
public class Demo02 {
/*
* 特殊功能:
* public Object setProperty(String key,String value):添加元素
* public String getProperty(String key):获取元素
* public Set<String> stringPropertyNames():获取所有的键的集合
*/
@Test
public void Test01(){
// 创建集合对象
Properties prop = new Properties();
// 添加元素
prop.setProperty("张三", "30");
prop.setProperty("李四", "40");
prop.setProperty("王五", "50");
// public Set<String> stringPropertyNames():获取所有的键的集合
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
String value = prop.getProperty(key);//获取元素(value)
System.out.println(key + "---" + value);
}
}
}
Properties和IO流相结合的方法
方法名 | 说明 |
void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合于使用 load(InputStream)方法的格式写入输出字节流 |
void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流 |
代码示例
package com.test03;
import org.junit.Test;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 13:07
* @Description: TODO
*/
public class Demo03 {
// 写入数据
@Test
public void Test01() throws IOException {
// 创建集合对象
Properties prop = new Properties();
prop.setProperty("江一燕", "27");
prop.setProperty("jack", "30");
prop.setProperty("肉丝", "18");
//public void store(Writer writer,String comments) 把集合中的数据存储到文件
FileWriter w = new FileWriter("file\\name.properties");
//写
prop.store(w, "写入文件的说明:因为我要学习所有我要写入");//comments:信息的描述
w.close();
}
// 展示数据
@Test
public void Test02() throws IOException {
Properties prop = new Properties();
// public void load(Reader reader) 把文件中的数据读取到集合中
// 注意:这个文件的数据必须是键值对形式
FileReader r = new FileReader("file\\name.properties");
//读
prop.load(r);
r.close();
System.out.println("prop:" + prop);
}
}
6、apache-common包的使用
6.1 介绍
IO技术开发中,代码量很大,而且代码的重复率较高,为此Apache软件基金会,开发了IO技术的工具类commonsIO
,大大简化了IO开发。
Apahce软件基金会属于第三方,(Oracle公司第一方,我们自己第二方,其他都是第三方)我们要使用第三方开发好的工具,需要添加jar包。
6.2 导包及举例
在导入commons-io-2.5.jar包之后,内部的API都可以使用。
6.2.1 IOUtils类的使用
- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
- 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。
package com.suyv.test04;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 13:23
* @Description: commons-io包下IOUtils类的使用
*/
public class Demo01 {
// 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
// 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。
@Test
public void Test01() throws IOException {
IOUtils.copy(new FileInputStream("file\\text.txt"),new FileOutputStream("file\\text_copy.txt"));
System.out.println("复制成功");
}
@Test
public void Test02() throws IOException {
FileWriter fw = null;
try {
fw = new FileWriter("file\\writer.txt");
fw.write("hahah");
} catch (IOException e) {
e.printStackTrace();
}finally {
IOUtils.closeQuietly(fw);
}
}
}
6.2.2 FileUtils类的使用
- 静态方法:void copyDirectoryToDirectory(File src,File dest):整个目录的复制,自动进行递归遍历
参数:src:要复制的文件夹路径 dest:要将文件夹粘贴到哪里去
- 静态方法:void writeStringToFile(File file,String content):将内容content写入到file中
- 静态方法:String readFileToString(File file):读取文件内容,并返回一个String
- 静态方法:void copyFile(File srcFile,File destFile):文件复制
package com.suyv.test04;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import java.io.IOException;
import java.io.File;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2023-12-22 13:31
* @Description: commons.io 包下 FileUtils 类的使用
*/
public class Demo02 {
@Test
public void Test01(){
try {
// 静态方法:void copyDirectoryToDirectory(File src,File dest);
FileUtils.copyDirectoryToDirectory(new File("E:\\Idea\\io\\aa"),new File("E:\\Idea\\io\\file"));
// 静态方法:writeStringToFile(File file,String str)
FileUtils.writeStringToFile(new File("day21\\io\\commons.txt"),"柳岩你好");
// 静态方法:String readFileToString(File file)
String s = FileUtils.readFileToString(new File("day21\\io\\commons.txt"));
System.out.println(s);
// 静态方法:void copyFile(File srcFile,File destFile)
FileUtils.copyFile(new File("io\\yangm.png"),new File("io\\yangm2.png"));
System.out.println("复制成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!