I/O流
流分类
数据流向:
- 输入流
- 输出流
数据类型:
- 字节流:
byte
(8位)为单位"读写的数据为视屏啊其他等等" - 字符流:
char
(16位)为单位"读写的数据时文本的时候一般"
四个抽象父类:
-
InputStream
; -
OutputStream
;字符流:
-
Reader
; -
Write
;
字节流
适合读任何类型的数据
InputStream
:所有字节输入流的抽象父类**
ByteArrayInputStream
:是以字节的形式从byte数组中读取数FileInputStream
:以字节的形式从文件中读取数据
OutputStream
:所有字节输出流的抽象父类
基本步骤:
- 声明流;
- 创建流;
- 使用流;
- 关闭流;
基础使用:
package com.chapter11.Case;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
public class FileStream {
public static void main(String[] args) {
//从控制台获取数据:输入流
//1.创建流
InputStream in=System.in;//获取到一个从控制台输入的流
// System.out.println("in...");
// System.out.println(in);//输出地址
OutputStream out=System.out;
try {
//读数据
// int read = in.read();//输入a 读出97
// System.out.println(read);
//写数据
// out.write(read);
// out.flush();//清空缓冲区
// int i=0;
// while ((i=in.read())!=-1){
// System.out.println(i);
// }
// byte[] buf=new byte[8];
// int read = in.read(buf);//返回读到的字节数,
// //buf中放的是读到的字节
// System.out.println(read);
// System.out.println(Arrays.toString(buf));
/*输出结果:a
2 //a:97 回车键:10
[97, 10, 0, 0, 0, 0, 0, 0]*/
// out.write(buf);
// out.flush();
// String str="hello world";
// byte[] bytes = str.getBytes();
// out.write(bytes);
/* int read = in.read(buf,1,3);//从哪个位置开始写,写几个
System.out.println(read);
System.out.println(Arrays.toString(buf));
*/
// out.write(buf);
int len=0;
byte[] buf = new byte[8];
while ((len=in.read(buf))!=-1){
// len=in.read(buf);
out.write(buf,0,len);//当len>8数组长度时,会写多次
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (in!=null) {
try {
//关闭资源
in.close();
} catch (IOException e) {
e.printStackTrace();
}
if (out!=null) {
try {
//关闭资源
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
package com.chapter11.Case;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
public class byteStream {
public static void main(String[] args) {
//1.声明流
InputStream in = null;
OutputStream out = null;
//2.创建流,这里使用System中已经创建好的流对象
in = System.in;
out = System.out;
//3 使用流
int data=-1;
try {
/*data=in.read();//一个一个读入,字节
out.write(data);
*/
/* byte[] buf = new byte[8];
data=in.read(buf);//
out.write(buf);
System.out.println(data);//data记录写入的字节数,data的最大值是数组长度
out.write(buf,0,3);
System.out.println(Arrays.toString(buf));//输出数组,每个字符对于的int数字
System.out.println(new String(buf));//输出字符*/
int len = -1;
byte[] buf = new byte[1024];
while((len=in.read(buf))!=-1) {
// len=in.read(buf);
out.write(buf,0,len);//本次读了多几个字节,那么就写出几个字节
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流
if (in!=null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out!=null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
读写方法:
- read()read(byte[] arr) read(byte[] arr,offset,len);有int类型的返回值
- write() write(byte[] arr) write(byte[] arr,offset,len)没有f返回值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7NB2Qtg-1638150567201)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210628141159636.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A3nOKTpE-1638150567204)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210628141217969.png)]
字节数组
java.io.ByteArrayInputStream
负责从字节数组中读取数据 java.io.ByteArrayOutputStream
负责把数据写入到字节数组中
数据来源:字节数组;
数据去向:另一个字节数组
管道
使用字节流,可以从管道中读取数据,以及向管道中写数据。
java.io.PipedInputStream
负责从管道中读取数据
java.io.PipedOutputStream
负责将数据写入到管道中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aw26IPu4-1638150567208)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210629092840366.png)]
- 一定要先建立连接;可以用connect()方法,也可以直接连接;
- 一般都要用到两个线程;
- 可以实现边写边读;
案例
package com.chapter11.Case;
import java.io.*;
public class PipTest {
public static void main(String[] args) {
PipedInputStream in;
PipedOutputStream out;
in=new PipedInputStream();
out=new PipedOutputStream();
try {
//管道对接
in.connect(out);
Thread t1=new WriteThread(out);
Thread t2 = new ReadThread(in);
t1.start();
t2.start();
t1.join();
t2.join();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("程序运行结束!");
}
}
class WriteThread extends Thread{
private OutputStream out;
public WriteThread(OutputStream out) {
this.out = out;
}
@Override
public void run() {
byte[] arr="hello world".getBytes();
try {
for (int i=0;i<arr.length;i++){
out.write(arr[i]);
out.flush();
sleep(1000);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}finally {
if (out!=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
class ReadThread extends Thread {
private InputStream in;
public ReadThread(InputStream in) {
this.in = in;
}
@Override
public void run() {
int data = -1;
try {
while ((data = in.read()) != -1) {
System.out.print("-"+data+"-");
System.out.write(data);
System.out.flush();
}
System.out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
文件流
package com.chapter11.Case;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
public class FileStreamTest {
public static void main(String[] args) throws IOException {
File file=new File("file");
System.out.println(file.exists());
System.out.println(file.canRead());
System.out.println("绝对路径"+file.getAbsolutePath());
// System.out.println("相对路径"+file.getCanonicalPath());
System.out.println(file.getPath());
String[] list = file.list();
System.out.println(Arrays.toString(list));
}
}
//运行结果
false
false
绝对路径E:\java.sql.jdk\IDEA\IdeaProjects\Briup_java\test\file
file
null
相对路径:相对于项目包[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pEgNCWMV-1638150567212)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210628150640221.png)]
数据的来源和数据的去向都是文件;
所以文件的路径非常重要,一般都是用相对路径;
注意,文件不存在不会报异常
- 例如“file”,不存在则会在当前相对路径下床架该文件,即src/file
但是如果文件带了目录就会抛文件不存在异常FileNotFoundException:
例如"zyz/file",这就不会自动创建,会报异常;
以下是案例:
package com.chapter11.Case;
import java.io.*;
import java.util.Arrays;
public class FileStreamTest {
public static void main(String[] args) throws IOException {
// fileReaderTest();
fileWriteTest("zyz");
}
public static void fileReaderTest() throws IOException {
FileInputStream in = null;
//读数据
try {
in=new FileInputStream(
"C:/Users/ZYZ/Desktop/briup.txt");
byte[] buf=new byte[1024];
int len=-1;
while ((len=in.read(buf))!=-1){
System.out.println(new String(buf));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if(in!=null){
in.close();
}
}
}
public static void fileWriteTest(String content) throws IOException {
FileOutputStream fos=null;
try {
fos=new FileOutputStream("file",true);
//true即追加内容,不加则每次运行就覆盖之前的内容
fos.write(content.getBytes());
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if(fos!=null){
fos.close();
}
}
}
}
字符流
一次读16位,相当于两个字节
Reader
和Writer
下的方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m5zfXMDN-1638150567214)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210629092207337.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-koG3Z01l-1638150567216)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210629092252573.png)]
管道
文件
package com.chapter11.Case;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
public class FileRW {
public static void main(String[] args) {
//读取一个txt的文件,把文件的内容写入当前路径下的一个叫data的文件;
FileReader fileReader= null;
FileWriter fileWriter= null;
try {
fileReader = new FileReader("C:/Users/ZYZ/Desktop/briup.txt");
fileWriter = new FileWriter("data");
int len=-1;
char[] ch=new char[1024];
while ((fileReader.read(ch))!=-1){
fileWriter.write(Arrays.toString(ch));
fileWriter.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fileReader!=null){
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}if (fileWriter!=null){
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
节点流
以上介绍并使用的字节流和字符流,都属于节点流。
它们的特点是,可以【直接】读取某一个地方的数据,或者【直接】把数据写入到某一个地方。 除此之外,下面介绍的一些流,都【不是】节点流,它们并不能【直接】从某一个地方读数据或写数 据,它们每一种都有一些特殊的功能,例如有的可以自动转换基本类型数据为字节,有的可以提高其他 流的读写效率,有的可以将字节流转为字符流,有的可以把对象转为字节等
数据流
主要适用于基本数据类型
字节流操作数据的时候,以字节文件单位,一个个的进行读写,很多时候这样并不方便。
我们希望读出来的若干个字节,自动转换为指定类型的数据,例如int、float、char等。
类似的,我们也希望每次能直接把一个数据,自动转成字节再写出去。
例如,把一个long类型数据,写入到一个字节数组中
long a = 1;
//int是642位,8个字节,在内存中表示为
//00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
//写入字节数组后,具体的值应该如下:
byte[] arr = {0,0,0,0,0,0,0,1};
我们需要一个流,可以将long类型的数据1,自动转换为字节后,再写出到数组中、文件中或者其 他地方
输出
注意1, DataOutputStream
必须要“包裹”一个字节流,增强这个字节流的功能,一次可以写出去一个 具体类型的数据
注意2,通过 writeLong
方法可以看出,内部是通过移位操作,拿到每一个字节的值,放到数组中,再 写出去
package com.chapter11.Case;
import java.io.*;
import java.util.Arrays;
public class DataStream {
public static void main(String[] args) {
//声明流
DataOutputStream dos=null;
ByteArrayOutputStream baos=null;
//创建流
baos=new ByteArrayOutputStream();
dos=new DataOutputStream(baos);
try {
dos.writeLong(1000L);
dos.flush();
byte[] byteArray = baos.toByteArray();
System.out.println(Arrays.toString(byteArray));
} catch (IOException e) {
e.printStackTrace();
}finally {
if (dos!=null){
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//运行结果:
[0, 0, 0, 0, 0, 0, 3, -24]
输入
与 DataOutputStream
功能类似, java.io.DataInputStream
可以将读取到的字节自动转化为指定 类型的数据,但一般都需要和 DataOutputStream
配合使用。
DataOutputStream
负责把指定类型的数据,转化为字节并写出去 DataInputStream
负责把读取到的若干个字节,转化为指定类型的数据java
public static void main(String[] args) {
DataOutputStream dos=null;
DataInputStream dis=null;
File file=new File("filedata");
try {
dos=new DataOutputStream(new FileOutputStream(file));
dos.writeLong(1000L);
dos.writeDouble(8.8d);
dos.writeUTF("中国");
dos.writeChar('a');
dos.flush();
FileInputStream fis=new FileInputStream(file);
dis=new DataInputStream(fis);
System.out.println(dis.readLong());
System.out.println(dis.readDouble());
System.out.println(dis.readUTF());
System.out.println(dis.readChar());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (dos!=null){
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dis!=null){
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓冲流
字节缓冲流
概念:缓冲流,可以在创建流对象时,设置一个默认大小的缓冲区数组,通过缓冲区进行读写,减少系统磁盘 的IO次数,从而提高读写的效率。
可以看出,字节缓冲流的构造器,要求一定要传入一个字节流对象,然后缓冲流就可以对这个字节 流的功能进行增强,提供缓冲数据的功能,从而提高读写的效率
注意,操作的文件内容越多,效果越明显
package com.chapter11.Case;
import java.io.*;
public class BufferIO {
public static void main(String[] args) {
//1.声明流
InputStream in = null;
OutputStream out = null;
try {
//2.创建流
File file1 = new File("file");
File file2 = new File("filecopy");
// in = new FileInputStream(file1);
// out = new FileOutputStream(file2);
//使用缓冲流增强文件字节流的功能,提供读写效率
//使用缓存流“包裹”其他的字节流即可
in = new BufferedInputStream(new FileInputStream(file1));
out = new BufferedOutputStream(new FileOutputStream(file2));
//3.使用流
int data = -1;
long start = System.currentTimeMillis();
while ((data = in.read()) != -1) {
out.write(data);
}
out.flush();
long end = System.currentTimeMillis();
System.out.println("使用缓冲流完成读写操作的时间为:" + (end - start) + "毫秒");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
字符缓冲流
java.io.BufferedReader
,负责给字符输入流提供缓冲功能 java.io.BufferedWriter
,负责给字符输出流提供缓冲功能
- 使用缓冲流来增强文件字符流的功能,提高读写效率
public class Test {
public static void main(String[] args) {
//1.声明流
Reader in = null;
Writer out = null;
try {
//2.创建流
File file1 = new File("src/main/java/com/briup/demo/背影.txt");
File file2 = new File("src/main/java/com/briup/demo/背影copy.txt");
// in = new FileReader(file1);
// out = new FileWriter(file2);
//使用缓冲流增强文件字符流的功能,提高读写效率
in = new BufferedReader(new FileReader(file1));
out = new BufferedWriter(new FileWriter(file2));
//3.使用流
int data = -1;
long start = System.currentTimeMillis();
while((data=in.read())!=-1){
out.write(data);
}
out.flush();
long end = System.currentTimeMillis();
System.out.println("使用字符流完成读写操作的时间为:"+(end-start)+"毫秒");
} catch (IOException e) {
e.printStackTrace();
}finally {
//4.关闭流
if(in!=null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out!=null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//运行结果:每次运行结果稍有差异
使用字符流完成读写操作的时间为:30毫秒
可以看出,字符流中使用缓冲流,也会有性能的提升,但是没有那么明显,因为
Writer
类中已经 提供了一个默认的缓冲区,也就是说其实FileWriter
类其实默认已经使用了缓冲区了
常用方法:
BufferedReader
中有一个方法,会使用的比较多:
public class BufferedReader extends Reader{
//一次读取一行字符串,遇到换行符为止,算是一行
//该方法的返回值,【不会】包含回车换行(\r\n)
public String readLine() throws IOException {
//...
}
}
- 如果下一行是空行,
readLine
方法返回空字符串,也就是String line = ""
- 如果没有下一行数据了,
readLine
方法返回null
;
和一次读一行,相对于的,是一次写一行,写完之后自动加换行(如果是Windows系统还会加回车)
public class Test {
public static void main(String[] args) {
//1.声明流
BufferedReader in = null;
PrintWriter out = null;
try {
//2.创建流
File file1 = new File("src/main/java/com/briup/demo/背影.txt");
File file2 = new File("src/main/java/com/briup/demo/背影copy.txt");
in = new BufferedReader(new FileReader(file1));
out = new PrintWriter(new FileWriter(file2));
//3.使用流
String line = null;
while((line=in.readLine())!=null){
out.println(line);
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
//4.关闭流
if(in!=null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out!=null) {
//PrintWriter中重写的close方法没有声明抛出异常
//重写的语法要求,抛出异常可以缩小,但是不能扩大
out.close();
}
}
}
}
注意, PrintWriter
中重载了很次构造器,它可以“包裹”一个字符流对象、字节流对象、文件对象等
注意, PrintWriter
中重载了很次 println 方法,该方法可以再输出数据后,添加一个新行,【默 认】情况下在Windows系统中添加的是回车换行(\r\n),在Linux系统中添加的时候换行(\n)
很多时候,我们都是 BufferedReader 和 PrintWriter ,进行数据的操作,刚好一个可以一次 读取一行数据,另一个可以一次写出一行数据
转换流
转换流,可以在把一个字节流转换为字符的同时,并指定转换的字符编码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qnFpttK3-1638150567220)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210629091603759.png)]
输出
package com.chapter11.Case;
import com.company.B;
import java.io.*;
public class ChangeStream {
public static void outStrean(){
FileOutputStream fos=null;
OutputStreamWriter osw=null;
// byte[] bytes = "你好中国".getBytes();
try {
fos=new FileOutputStream("file");
//将字节流转换成字符流 ISO-8859-1编码会乱码
osw=new OutputStreamWriter(fos,"GBK");
// fos.write(bytes);
// fos.flush();
osw.write("你好中国");//转换流继承了Writer,所以有该方法
osw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {//关闭流:先管里面的流
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}if (osw!=null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void inTest(){
FileInputStream fis=null;
InputStreamReader isr=null;
BufferedReader br=null;
try {
fis=new FileInputStream("file");
//添加转换流
isr=new InputStreamReader(fis,"GBK");
//添加缓冲流
br=new BufferedReader(isr);
// byte[] buf=new byte[12];
// fis.read(buf);
char[] buf=new char[1024];
// isr.read(buf);
System.out.println(br.readLine());
// System.out.println(new String(buf));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (isr!=null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// outStrean();
inTest();
}
}
- 关流的时候,从里往外关;
- 转换流的构造器都是有参的,而且里面都是放字节流的;
对象流
Java 提供了一种对象序列化的机制,可以将对象和字节序列之间进行转换:
- 序列化:
- 程序中,可以用一个字节序列来表示一个对象,该字节序列包含了对象的类型、对象中的数据等。 如果这个字节序列写出到文件中,就相当于在文件中持久保存了这个对象的信息
- 反序列化:
- 相反的过程,从文件中将这个字节序列读取回来,在内存中重新生成这个对象,对象的类型、对象 中的数据等,都和之前的那个对象保持一致。(注意,这时候的对象和之前的对象,内存地址可能 是不同的,因为反序列化之后重新再内存中给对象分配空间,但是内容是相同的)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CuAXDhV3-1638150567222)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210629110724545.png)]
完成对象的序列化和反序列化,就需要用到对象流了
序列化:
在java中,并非所有对象都可以进行序列化和反序列化,而是只有实现了指定接口的对象才可以进行。
java.io.Serializable
接口
public interface Serializable {
}
接口中没有抽象方法,这只是一个“标识”接口,实现它的的对象才可以进行序列化和反序列化操作
例如,不需要实现接口的抽象,因为这个接口中没有定义抽象
public class Student implements Serializable {
}
注意,如果不实现这个接口的,将来序列化操作会报错
transient关键字:
java中的关键字 transient
,可以修饰类中的属性,它是让对象在进行序列化的时候,忽略掉指定的属 性。
常用在一些敏感属性的修饰,例如对象中的password属性,我们并不想将这个敏感属性的值进行序列化 保存,那么就可以使用 transient
来对他进行修饰
transient
的单词含义就是短暂的、转瞬即逝。
public class Student implements Serializable {
private String name;
private transient int age;
//...
}
这时候,再进行序列化操作,对象中的age属性的值将会被忽略掉
序列化版本号
Serializable
接口给需要序列化的类,提供了一个序列版本号:serialVersionUID
该版本号的目的,是用于验证序列化的对象和对应类是否版本匹配
例如,没有声明序列化版本号的情况:
public class Student implements Serializable {
String name;
}
创建对象,并将对象进行序列化
public static void main(String[] args) {
//1.声明流
ObjectOutputStream out = null;
try {
//2.创建流
//创建文件对象
File file = new File("src/main/java/com/briup/demo/a.txt");
//"包裹"一个文件字节输出流
out = new ObjectOutputStream(new FileOutputStream(file));
//3.使用流
Student stu = new Student();
stu.name = "tom";
//序列化:对象->字节
out.writeObject(stu);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
//4.关闭流
if(out!=null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
修改Student类中的属性,加入一个age属性:
public class Student implements Serializable {
String name;
int age;
}
再进行反序列化操作:
public static void main(String[] args) {
//1.声明流
ObjectInputStream in = null;
try {
//2.创建流
//创建文件对象
File file = new File("src/main/java/com/briup/demo/a.txt");
//"包裹"一个文件字节输出流
in = new ObjectInputStream(new FileInputStream(file));
//3.使用流
Object obj = in.readObject();
System.out.println(obj);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
//4.关闭流
if(in!=null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//运行报错:
java.io.InvalidClassException: com.briup.demo.Student; local class incompatible:
stream classdesc serialVersionUID = 6548949808915977718, local class
serialVersionUID = -501096807619780610
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.briup.demo.Test.main(Test.java:22)
注意,这时候反序列化操作报错,因为我们在Student中添加的了一个新属性age
默认情况下,每次修改代码编译生成的新的class文件中,都会根据当前类的信息计算出一个序列 化版本号,之前序列化的字节中的保存的是之前老的版本号,和现在这个新的不一致,然后就报错 了
如果我们手动指定这个版本号之后,那么这个类的序列化版本号就固定下来了:
public class Student implements Serializable {
//属性的名字、类型、修饰符是固定的,值可以随意给一个
private static final long serialVersionUID = 1L;
String name;
}
此时,再重复上面的实验操作,就不会反序列化的时候报错了,因为版本号一直都是手动固定写死 的。
许多类都实现了序列化接口,所以可以直接使用对象流
随机访问流
Class RandomAccessFile
主要适用于文件
特点:
-
继承的是Object类;
-
即可以做读的流,也可以是写的流;
-
两个有参构造器:
-
-
RandomAccessFile(String name, String mode)
创建随机访问文件流,以从中指定名称的文件读取,并可选择写入文件。
-
-
-
RandomAccessFile(File file, String mode)
创建一个随机访问文件流从
File
参数指定的文件中读取,并可选地写入文件。
-
-
之前使用的每一个流,那么是读数据的,要么是写数据的,而这个随机访问流,它的对象即可用作文件 内容的读,又可以用作文件内容的写,同时它还可以任意定位到文件的某一个文件进行读或者写操作
- 对象即可读也可以写
- 随机定位文件中的任意字节位置进行读或写,并且可以前后反复定位
创建该类的对象时,需要指定要操作的文件和操作的模式:
- "r” 模式,以只读方式来打开指定文件夹。如果试图对该RandomAccessFile执行写入方法,都将抛出 IOException异常。
- “rw” 模式,以读写方式打开指定文件。如果该文件尚不存在,则试图创建该文件。
- “rws” 模式,以读写方式打开指定文件。相对于”rw” 模式,还要求对文件内容或元数据的每个更新 都同步写入到底层设备。
- “rwd” 默认,以读写方式打开指定文件。相对于”rw” 模式,还要求对文件内容每个更新都同步写入 到底层设备。
程序中,以r和rw模式最为常用,r模式表示只读,rw模式表示既能读又能写
例如,
package com.chapter11.Case;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomTest {
public static void main(String[] args) {
RandomAccessFile raf=null;
try {
byte[] bytes = new byte[1024];
raf=new RandomAccessFile("file","rw");
System.out.println(raf.read(bytes));
System.out.println(new String(bytes));
raf.seek(4);
raf.write("zyz".getBytes());
raf.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//运行结果:
7
你好zyq
file文件中的内容位:
你好zyz
file文件中"zyqzyq"替换成"zyqzyzzyq":
package com.chapter11.Case;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomTest {
public static void main(String[] args) {
RandomAccessFile raf=null;
ByteArrayOutputStream baos=null;
try {
//文件要插入数据的位置
int insertP=3;
//文件中要出入的内容
String s="zyz";
raf=new RandomAccessFile("file","rw");//读写模式
baos=new ByteArrayOutputStream();
byte[] buf=new byte[1024];
//因为默认写入时会将对应位置上的数据给覆盖掉
//把指定位置后的所有数据写入到buf数组保存起来
int len=-1;
raf.seek(insertP);//跳过了这些位置
while ((len=raf.read(buf))!=-1){
baos.write(buf,0,len);//0是insertP+1这个位置
}
System.out.flush();
raf.seek(insertP);
raf.write(s.getBytes());//写入内容
raf.write(baos.toByteArray());//写入之前保存的数据
System.out.flush();
while ((raf.read(buf))!=-1){
System.out.println(new String(buf));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Properties集合
util包下的一个集合,是Map接口的一个实现类
class Properties extends Hashtable<Object,Object> {
private static final long serialVersionUID = 4112578634029874840L;
public Properties() {
this(null);
}
public Properties(Properties defaults) {
this.defaults = defaults;
}
public synchronized Object setProperty(String key, String value) {
return put(key, value);
}//加锁,保证线程安全
public synchronized void load(Reader reader) throws IOException {
load0(new LineReader(reader));
}//可以从流中读取数据
public synchronized void load(InputStream inStream) throws IOException {
load0(new LineReader(inStream));
}//字节流也可以
由源码可知其有以下特点:
- 继承与Hashtable,是线程安全的键值对存储结构;
- 可以将数据保存在流中,或者从流中加载数据;
- 只能保存字符串的键值对
构造器:
Properties() 创建一个无默认值的空属性列表。 |
---|
Properties(Properties defaults) 创建一个带有指定默认值的空属性列表。 |
常用方法:
String | getProperty(String key) 用指定的键在此属性列表中搜索属性。 |
---|---|
String | getProperty(String key, String defaultValue) 用指定的键在属性列表中搜索属性。 |
Object | setProperty(String key, String value) 调用 Hashtable 的方法 put。 |
案例:
package com.chapter11.Case;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;
import java.util.Set;
public class PropTest {
public static void main(String[] args) {
//属性集合类不支持泛型
Properties prop = new Properties();
//添加键值对
prop.setProperty("name", "zhangsan");
prop.setProperty("age", "10");
prop.setProperty("gender", "male");
PrintWriter pw=null;//声明流
try {
//创建自动刷新字符打印流对象
pw=new PrintWriter(new FileWriter("prop.txt"));
//拿到prop中所有的key值
Set<Object> keySet = prop.keySet();
//遍历
for (Object o :keySet) {
String key=(String)o;//强制转换
//通过key值获取value值
String value = prop.getProperty(key);
pw.println(key+"="+value);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (pw!=null) pw.close();
}
}
}