java 流读取int 数组_JAVA IO分析二:字节数组流、基本数据&对象类型的数据流、打印流...

本文详细介绍了JAVA中DataOutputStream和DataInputStream的使用,包括如何用它们读写字符串、浮点数和整数,并通过实例展示了如何在字节数组中操作数据。此外,还讨论了对象流的概念,强调了对象序列化和反序列化的重要性,以及如何处理serialVersionUID以确保序列化兼容性。最后,文章提到了打印流PrintStream在日志记录中的应用,解释了如何将异常堆栈跟踪信息写入文件并实现日志信息的追加功能。
摘要由CSDN通过智能技术生成

8e8f0f0bf14b7e9405d30c38c0d3e426.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

import java.io.DataOutputStream ;

import java.io.File ;

import java.io.FileOutputStream ;

public class DataOutputStreamDemo{

public static void main(String args[]) throws Exception{ // 所有异常抛出

DataOutputStream dos = null ; // 声明数据输出流对象

File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路径

dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f))) ; // 实例化数据输出流对象

String names[] = {"衬衣","手套","围巾"} ; // 商品名称

float prices[] = {98.3f,30.3f,50.5f} ; // 商品价格

int nums[] = {3,2,1} ; // 商品数量

for(int i=0;i

dos.writeChars(names[i]) ; // 写入字符串

dos.writeChar('\t') ; // 写入分隔符

dos.writeFloat(prices[i]) ; // 写入价格

dos.writeChar('\t') ; // 写入分隔符

dos.writeInt(nums[i]) ; // 写入数量

dos.writeChar('\n') ; // 换行

}

dos.close() ; // 关闭输出流

}

};

48304ba5e6f9fe08f3fa1abda7d326ab.png

使用DataOutputStream写入的数据要使用DataInputStream读取进来。前面说过,是给机器看的,人类看不懂.

48304ba5e6f9fe08f3fa1abda7d326ab.png

import java.io.DataInputStream ;

import java.io.File ;

import java.io.FileInputStream ;

public class DataInputStreamDemo{

public static void main(String args[]) throws Exception{ // 所有异常抛出

DataInputStream dis = null ; // 声明数据输入流对象

File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路径

dis = new DataInputStream(new BufferedInputStream(new FileInputStream(f)) ); // 实例化数据输入流对象

String name = null ; // 接收名称

float price = 0.0f ; // 接收价格

int num = 0 ; // 接收数量

char temp[] = null ; // 接收商品名称

int len = 0 ; // 保存读取数据的个数

char c = 0 ; // '\u0000'

try{

while(true){

temp = new char[200] ; // 开辟空间

len = 0 ;

while((c=dis.readChar())!='\t'){ // 接收内容

temp[len] = c ;

len ++ ; // 读取长度加1

}

name = new String(temp,0,len) ; // 将字符数组变为String

price = dis.readFloat() ; // 读取价格

dis.readChar() ; // 读取\t

num = dis.readInt() ; // 读取int

dis.readChar() ; // 读取\n

System.out.printf("名称:%s;价格:%5.2f;数量:%d\n",name,price,num) ;

}

}catch(Exception e){}

dis.close() ;

}

};

48304ba5e6f9fe08f3fa1abda7d326ab.png

5.2f 表示的是 总共的数字长度为5位,其中2位表示小数,3位表示整数。

下面我们再看一个例子,即回顾到我们开始说的,一般DataInputStream 和 DataOutputStream 这种处理流,和对应的节点流ByteArrayInputStream ByteArrayOutputStream 关联在一起使用,即我们说的将字节数组中内存中存放当做一个小文件对待,例子如下:

48304ba5e6f9fe08f3fa1abda7d326ab.png

import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.io.IOException;

/**

* 数据类型(基本+String)处理流

* 1、输入流 DataInputStream readXxx()

* 2、输出流 DataOutputStream writeXxx()

* 新增方法不能使用多态

*

* java.io.EOFException :没有读取到相关的内容

* @author Administrator

*

*/

public class DataDemo02 {

/**

* @param args

*/

public static void main(String[] args) {

try {

byte[] data=write();

read(data);

System.out.println(data.length);

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

/**

* 从字节数组读取数据+类型

* @throws IOException

*/

public static void read(byte[] src) throws IOException{

//选择流

DataInputStream dis =new DataInputStream(

new BufferedInputStream(

new ByteArrayInputStream(src)

)

);

//操作 读取的顺序与写出一致 必须存在才能读取

double num1 =dis.readDouble();

long num2 =dis.readLong();

String str =dis.readUTF();

dis.close();

System.out.println(num1+"-->"+num2+"-->"+str);

}

/**

* 数据+类型输出到字节数组中

* @throws IOException

*/

public static byte[] write() throws IOException{

//目标数组

byte[] dest =null;

double point =2.5;

long num=100L;

String str ="数据类型";

//选择流 ByteArrayOutputStream DataOutputStream

ByteArrayOutputStream bos =new ByteArrayOutputStream();

DataOutputStream dos =new DataOutputStream(

new BufferedOutputStream(

bos

)

);

//操作 写出的顺序 为读取准备

dos.writeDouble(point);

dos.writeLong(num);

dos.writeUTF(str);

dos.flush();

dest =bos.toByteArray();

//释放资源

dos.close();

return dest;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

三、对象流【处理流】

因为前面的是针对于基本的数据类型的操作,那么针对对象,于是就有了对象流;ObjectInputStream 和 ObjectOutputStream 的作用是,对基本数据和对象进行序列化操作支持。

创建“文件输出流”对应的ObjectOutputStream对象,该ObjectOutputStream对象能提供对“基本数据或对象”的持久存储;当我们需要读取这些存储的“基本数据或对象”时,可以创建“文件输入流”对应的ObjectInputStream,进而读取出这些“基本数据或对象”。

注意: 只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能被ObjectInputStream/ObjectOutputStream所操作!

主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了

使用:

对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。(查看源码可得知:Serializable接口没有任何的方法,只是作为一个标识接口存在)。

1、将User类的对象序列化

48304ba5e6f9fe08f3fa1abda7d326ab.png

class User implements Serializable{//必须实现Serializable接口

String uid;

String pwd;

public User(String _uid,String _pwd){

this.uid = _uid;

this.pwd = _pwd;

}

@Override

public String toString() {

return "账号:"+this.uid+" 密码:"+this.pwd;

}

}

public class Demo1 {

public static void main(String[] args) throws IOException {

//假设将对象信息写入到obj.txt文件中,事先已经在硬盘中建立了一个obj.txt文件

File f = new File("F:\\obj.txt");

writeObjec(f);

System.out.println("OK");

}

//定义方法把对象的信息写到硬盘上------>对象的序列化。

public static void writeObjec(File f) throws IOException{

FileOutputStream outputStream = new FileOutputStream(f);//创建文件字节输出流对象

ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

objectOutputStream.writeObject(new User("酒香逢","123"));

//最后记得关闭资源,objectOutputStream.close()内部已经将outputStream对象资源释放了,所以只需要关闭objectOutputStream即可

objectOutputStream.close();

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

运行程序得到记事本中存入的信息:可见已经序列化到记事本中

80f6accff71666401617679691968672.png

2、将序列化到记事本的内容反序列化

48304ba5e6f9fe08f3fa1abda7d326ab.png

public class Demo1 {

public static void main(String[] args) throws IOException, ClassNotFoundException {

//假设将对象信息写入到obj.txt文件中,事先已经在硬盘中建立了一个obj.txt文件

File f = new File("F:\\obj.txt");

//writeObjec(f);

readObject(f);

System.out.println("OK");

}

//定义方法把对象的信息写到硬盘上------>对象的序列化。

public static void writeObjec(File f) throws IOException{

FileOutputStream outputStream = new FileOutputStream(f);//创建文件字节输出流对象

ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

objectOutputStream.writeObject(new User("酒香逢","123"));

//最后记得关闭资源,objectOutputStream.close()内部已经将outputStream对象资源释放了,所以只需要关闭objectOutputStream即可

objectOutputStream.close();

}

//把文件中的对象信息读取出来-------->对象的反序列化

public static void readObject(File f) throws IOException, ClassNotFoundException{

FileInputStream inputStream = new FileInputStream(f);//创建文件字节输出流对象

ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);

User user = (User)objectInputStream.readObject();

System.out.println(user);

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

运行代码得到的结果:

账号:酒香逢 密码:123

OK

但是,如果这时候这个obj.txt是我们项目中一个文件,而项目到后期在原来User类的基础上添加成员变量String userName;

48304ba5e6f9fe08f3fa1abda7d326ab.png

class User implements Serializable{//必须实现Serializable接口

String uid;

String pwd;

String userName="名字";//新添加的成员变量

public User(String _uid,String _pwd){

this.uid = _uid;

this.pwd = _pwd;

}

@Override

public String toString() {

return "账号:"+this.uid+" 密码:"+this.pwd;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

这时候如果我们再反序列化,则会引发下面的异常:

48304ba5e6f9fe08f3fa1abda7d326ab.png

Exception in thread "main" java.io.InvalidClassException: com.User; local class incompatible: stream classdesc serialVersionUID = 2161776237447595412, local class serialVersionUID = -3634244984882257127

at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:604)

at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1601)

at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)

at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)

at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)

at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)

。。。。。

48304ba5e6f9fe08f3fa1abda7d326ab.png

异常信息解读:

serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是JVM(JAVA虚拟界)通过一个类的类名、成员、包名、工程名算出的一个数字。而这时候序列化文件中记录的serialVersionUID与项目中的不一致,即找不到对应的类来反序列化。

3、如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后

在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。

去掉刚才添加的成员变量userName;,并且在User类中指定一个serialVersionUID

48304ba5e6f9fe08f3fa1abda7d326ab.png

class User implements Serializable{//必须实现Serializable接口

private static final long serialVersionUID = 1L;

String uid;

String pwd;

//String userName="名字";//新添加的成员变量

public User(String _uid,String _pwd){

this.uid = _uid;

this.pwd = _pwd;

}

@Override

public String toString() {

return "账号:"+this.uid+" 密码:"+this.pwd;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

重新序列化到obj.txt文件中,然后再类中再将userName添加回来(将上面User类中userName字段解注释),再一次执行反序列化操作,执行的结果跟之前反序列化的结果是一致的。可见这样解决后我们后期修改类也是可行的。

4、如果在User类中再添加成员变量,而这个变量为一个class ,如Address,那么Address类也必须要实现Serializable接口。

48304ba5e6f9fe08f3fa1abda7d326ab.png

class Address implements Serializable{

String country;

String city;

}

class User implements Serializable{//必须实现Serializable接口

private static final long serialVersionUID = 1L;

String uid;

String pwd;

String userName="名字";//新添加的成员变量

Address address;//成员变量为Address

public User(String _uid,String _pwd){

this.uid = _uid;

this.pwd = _pwd;

}

@Override

public String toString() {

return "账号:"+this.uid+" 密码:"+this.pwd;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

5、最后再提一下关键字transient关键字,当你不想要某些字段序列化时候,可以用transient关键字修饰

48304ba5e6f9fe08f3fa1abda7d326ab.png

class User implements Serializable{//必须实现Serializable接口

private static final long serialVersionUID = 1L;

String uid;

String pwd;

transient String userName="名字";//新添加的成员变量//添加关键字transient后,序列化时忽略

Address address;//成员变量为Address

public User(String _uid,String _pwd){

this.uid = _uid;

this.pwd = _pwd;

}

@Override

public String toString() {

return "账号:"+this.uid+" 密码:"+this.pwd;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

上面我们演示的文件的操作,如果换成字节数组流也是一样的方式。最后总结下:

1. 如果对象需要被写出到文件上,那么对象所属的类必须要实现Serializable接口。 Serializable接口没有任何的方法,是一个标识接口而已。

2. 对象的反序列化创建对象的时候并不会调用到构造方法的、(这点文中没有说到,想要验证的同学在构造方法后面加一句System.out.println("构造方法执行吗?");,实际上构造方法是不执行的,自然这句话也没有输出了)

3. serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出的一个数字。

4. 使用ObjectInputStream反序列化的时候,ObjeectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID

进行对比,如果这两个id不一致,反序列则失败。

5. 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后

在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。

6. 如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰。

7. 如果一个类维护了另外一个类的引用,则另外一个类也需要实现Serializable接口。

四、打印流【处理流】

PrintStream 用于向文本输出流打印对象的格式化表示形式。它实现在PrintStream 中的所有 print 方法。它不包含用于写入原始字节的方法,对于这些字节,程序应该使用未编码的字节流进行写入。

在理解PrintStream如何使用之前,先了解一下System类中三个字段:

b4bc0682fde2fd4dbd639a8780519ff7.png

往控制台输出时 ,使用System.out.println();

其中System.out这个字段返回的就是打印流,PrintStream

PrintStream ps=System.out; ps.print("hello"); 就等同于 System.out.println("hello");

err和out其实是一样的,只不过在控制台输出时,err输出内容是红色的

Scanner也是一个处理流,创建一个Scanner对象使用到的就是in字段

Scanner console=new Scanner(System.in);

Scanner类其实就是一个输入流,那么我们可以从控制台输入,怎样从文件中输入呢?

InputStream is=System.in;

File file=new File("F:/Picture/test/test2.txt");

is=new BufferedInputStream(new FileInputStream(file));

Scanner sc=new Scanner(is);

System.out.println(sc.nextLine());

从上面也可以看出Scanner 其实就一个处理流,用于增强节点流。

使用打印流输出内容到文件中,也是很容易的

这是PrintStream的构造方法

08571df982ca4b3ecabb98af1c4ffddd.png

File file=new File("F:/Picture/test/test.txt");

PrintStream ps=new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));

ps.append("hellohahaha");

ps.close();

最后总结一句话:对于标准的输入和输出,JDK 中封装好了比较好的操作类,输入的Scanner   输出PrintStream

最后一个小问题:如何将system.out 的输出不是输出到控制台,而是记录到文件中呢?即记录日志利用打印流来实现的。

文本信息中的内容为String类型。而像文件中写入数据,我们经常用到的还有文件输出流对象FileOutputStream.

1 File file = new File("F:\\a.txt");

2 FileOutputStream outputStream = new FileOutputStream(file,true);//第二个参数为追加文本

3 outputStream.write(97);

上面的代码执行完之后,a.txt中的内容存的是a,因为write方法接收的为byte类型的数据,97对应的ASCII码为a。

假设我就想将97写入到文件中呢?那么得将第三行代码改为

outputStream.write("97".getBytes());//先将97作为字符串再转换为byte数组

而PrintStream得出现,是的我们写数据入文件变得十分方便,你传入的是什么,就会给你写入什么数据。原因是他内部帮我们转换了

48304ba5e6f9fe08f3fa1abda7d326ab.png

File file = new File("F:\\a.txt");

PrintStream printStream = new PrintStream(file);

printStream.println(97);

printStream.println('a');

printStream.println("hello world");

printStream.println(true);

printStream.println(3.14);

printStream.println(new Student("酒香逢"));

48304ba5e6f9fe08f3fa1abda7d326ab.png

上面这段代码得到的结果为:

4fb48b8ae8d85add3d8d95cdf7e95ed0.png

可见不管什么数据类型,都会转换为字符串,甚至是对象也不例外。

这里再说一下学习java时少不了用到的一句代码:System.out.println();代码中的out,为System类中的一个PrintStream对象,称之为标准输出流对象。标准输出流对象会将数据打印到控制台上。查阅API可知有如下方法,

static void setOut(PrintStream out) //重新分配“标准”输出流。

可以重新指定输出流对象,即将System.out.println();的输出内容打印到我们想打印到的地方。

1 File file = new File("F:\\a.txt");

2 PrintStream printStream = new PrintStream(file);

3 System.setOut(printStream);

4 System.out.println("打印到F:\\a.txt中");

这时候内容回写入到文件a.txt中去,而不是打印在控制台中。

最后回归本文重点,日志信息的保存。

假设有代码:

1 try{

2 int n = 5/0;

3 }catch(Exception e){

4 e.printStackTrace();

5 }

执行结果会抛出我们想要的错误日志信息。

java.lang.ArithmeticException: / by zero

at log.DemoLog.main(DemoLog.java:26)

这时候想将日志信息保存起来怎么办呢?

c0e4ff9b113a16a25b44bf7208c06041.png

看到Exception类中的这3个重载方法,我们不难得知,只要给他指定一个打印输出流对象当中,即可将日志信息保存到我们想要的地方。

48304ba5e6f9fe08f3fa1abda7d326ab.png

File file = new File("F:\\a.log");

PrintStream printStream = new PrintStream(file);

try{

int n = 5/0;//除数为零异常

}catch(Exception e){

e.printStackTrace(printStream);

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

上面这段代码执行重复执行多次,

207c6504f4911208df43e7af749e2b9e.png

但是记录的日志信息永远只会记录一条。这明显不是我们想得到的,日志信息,总不能只记录一条吧?那么它存在又有什么用?

其实,追加文本信息的决定者不是e.printStackTrace(printStream);方法,关键点在于流对象,

8c3289888317e0f95c39e2cc54079576.png

可见打印流对象是存在一个OutputStream接口作为参数的传入对象。既然是接口,那么就无法new出OutputStream的对象了,可以用他的子类FileOutputStream对象作为参数传入。并且,FileOutputStream流是可以追加的,

new FileOutputStream(file,true);//第二个参数为追加文本

此时将其作为参数传入,PrintStream流自然也就可以追加内容了。

48304ba5e6f9fe08f3fa1abda7d326ab.png

File file = new File("F:\\a.log");

PrintStream printStream = new PrintStream(new FileOutputStream(file,true),true);

try{

int n = 5/0;//除数为零异常

}catch(Exception e){

e.printStackTrace(printStream);

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

将代码执行3次后:

425e54c088099b56a6d13080e293521e.png

可以看到日志信息是保存有3条的,日志信息记录保存目的达成!

最后如果我们如果想回来呢?

改为控制台输出需要借助FileDescript这个类,之后的输出就会显示在控制台了

System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out),true));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值