上一篇《Java中的IO流(五)》把流中的打印流PrintStream,PrintWriter,序列流SequenceInputStream以及结合之前所记录的知识点完成了文件的切割与文件的合并功能,接下来我们接着记录有关于Java中流的其它知识点。

  这一系列的知识点虽然取名叫“Java中的IO流”,便实质上有些知识点并非IO包中的内容,只是跟IO流相关联或相类似,所以归纳进来了。

一,能操作对象的流:ObjectOutputStream和ObjectInputStream

  ObjectOutputStream将Java对象的基本数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。

  此流只能支持实现了Serializable接口的对象写入流中。每个实现了Serializable对象的类都被编码,编码内容包括类名和类签名,对象的字段值和数组值,以及从初始对象中物其他所有对象的闭包。

  实现了Serializable接口的Pserson类代码如下

     public class Person implements Serializable {
private String name; 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;
} private int age;
}

  Serializable接口里没有任何的方法,它只是一个标识接口,标识所实现了此接口的类具有被序列化以及反序列化的功能。

  将一个Person对象序列化到obj.object文件中代码如下

     private static void function_demo1() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));
Person person = new Person();
person.setName("张三");
person.setAge(32);
oos.writeObject(person);
oos.close();
}

  将此被序列化的对象反序列化读出来的代码如下

     private static void function_demo2() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.object"));
Person p = (Person) ois.readObject();
System.out.println(p.getName());
System.out.println(p.getAge());
ois.close();
}
二,Serializable接口
  鉴于以上序列化与反序列化的例子,我们对Person类稍作更改,将属性name的访问修饰符改为public,保存,然后再运行反序列化的那段代码,将得到以下的结果

Exception in thread "main" java.io.InvalidClassException: com.zw.otherslikestream.Person; local class incompatible: stream classdesc serialVersionUID = 3588443867695946454, local class serialVersionUID = -3836108205857858437
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at com.zw.otherslikestream.OtherLikeStreamExercise.function_demo2(OtherLikeStreamExercise.java:19)
at com.zw.otherslikestream.OtherLikeStreamExercise.main(OtherLikeStreamExercise.java:14)

  从上面的错误信息我们可以看出,它的意思是“ stream classdesc serialVersionUID = 3588443867695946454”和“local class serialVersionUID = -3836108205857858437”不一样,所以才会出错。

  其原理是这样的:当我们让一个类实现了Serializable接口时,编译时编译器会用一个名叫serialVersionUID的版本号与该类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID。

  如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面(类中字段的访问修饰符啊,数据类型啊等等)计算该类的默认 serialVersionUID 值,不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。

  了解了以上的原理后我们手动为Person类添加一个serialVersionUID:private static final long serialVersionUID = 1L;然后再序列化此类后修改此类信息再反序列化此类便不会报错。

三,transient关键字

  我们再次修改Person类

 public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private static int 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;
} }

  即将字段age改为静态的。再次运行序列化,然后再反序列化此类,发现age变为0了;原理是这样的,静态的字段是被放在JVM的静态区域的,属于共享数据,序列化Person类的时候不可能拿到静态区域里的东西即Serializable接口不可以序列化静态字段或方法;

  若类中的某一个字段不能作为共享数据即不需要被static所修饰,又不想被序列化,则此字段可以被transient关键安来修饰,如下

 public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private transient String name;
private static int 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;
} }

  此时Person类的对象被序列化,再被反序列化时,此字段的值为null

 四,RandomAccessFile随机读取与写入类
  RandomAccessFile类直接继承自Objec类,此类支持随机的读取与写入文件(仅限于文件);
  此类内部维护着一个大型的byte类型数组,存在指向该数组的隐含光标或指针被称为文件指针;
  输入操作从文件指针开始读取字节并随着对字节的读取而前移指针;
  文件指针可以通过getFilePointer来获取位置,并可通过seek来设置指针位置;
  该类有两个构造函数
    RandomAccessFile(File file,String mode)创建从中读取和向其中写入(可选)的随机访问文件流,该文件由File参数指定。
    RandomAccessFile(String name,String mode)创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
  由此构造函数也可以看出是此类主要是用来操作文件的;构造函数的第二个参数mode的枚举值包括以下几种 
    r      以只读方式打开。调用结果对象的合作write方法都将导致抛出IOException。
    rw      打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
      rws     打开以便读取和写入,对于“rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备
      rwd       打开以便读取和写入,对于“rw”,还要求对文件内容的每个更新都同步写入到底层存储设备
 
  以下代码演示了通过RandomAccessFile类实现写入操作

     private static void function_demo3() throws IOException {
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");//以读取写入方法创建文件(若文件不存在)
raf.write("sb".getBytes());//写入两个字节
raf.writeInt(97);//写入一个Int数据,此方法按四个字节写入文件,先写高位
raf.write("小龙".getBytes());//由于是utf8编码形式,所以两个汉字占用了六个字节
raf.writeInt(109);//写入四个字节
raf.close(); }

  RandomAccessFile类的读取操作

     private static void function_demo4() throws IOException {
RandomAccessFile raf = new RandomAccessFile("raf.txt", "r");
byte[] bt = new byte[2];//因为存储时是两个字节,所以这里声明字节数组长度为2
raf.read(bt);//从RandomAccessFile所维护的byte数组中读取前两个字节的内容
System.out.println(new String(bt));//转为字符输出
System.out.println(raf.readInt());//用readInt方法读取写入的int数据97
byte[] be = new byte[6];//因为存储时两个汉字共6个字节,所以声明长度为6
raf.read(be);//读取6个字节到be
System.out.println(new String(be));//转成字符输出
System.out.println(raf.readInt());//读取109数据
raf.close();
}

  RandomAccessFile类的随机读取与写入操作只是针对于此类所维护的那个byte数组的指针的位置,以下是示例代码

     private static void function_demo5() throws IOException {
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");
raf.seek(10);// 将RandomAccessFile维护的数组指针移动到下标为10的位置
raf.write("李四".getBytes());// 写入六个字节
System.out.println(raf.getFilePointer());// 获取当前指针的位置16
raf.seek(10);// 将指针移动到10的位置,可直接读取到刚才写放的内容
byte[] bt = new byte[3];
int len;
while ((len = raf.read(bt)) != -1) {
System.out.print(new String(bt) + " ");
}
raf.close();
}
 五,管道流PipedInputStream和PipedOutputStream
  管道输入流与管道输出流是为了操作线程间的通信而设立的;管道输入流提供管道输出流要写入的所有字节数据;通常情况下,数据由某个线程从管道输入流对象读取数据,并由其它线程将数据写入到与之相对应的输出流对象;
  管道输入流与管道输出流对象关联可通过两种方法:
    1,通过构造函数,即创建管道流的时候传入相对应的流对象,如下:
      PipedOutputStream output=new PipedOutputStream();
      PipedInputStream input=new PipedInputStream(output);
      或
      PipedInputStream input=new PipedInputStream();
      PipedOutputStream output=new PipedOutputStream(input);
    2,通过connect方法传入相应的流对象
      PipedOutputStream output=new PipedOutputStream();
      PipedInputStream input=new PipedInputStream();
      output.connect(input);
      或
      PipedInputStream input=new PipedInputStream();
      PipedOutputStream output=new PipedOutputStream();
      input.connect(output);
  管道流的具体操作如下
 public class Input implements Runnable {
private PipedInputStream input; public Input(PipedInputStream input) {
this.input = input;
} @Override
public void run() {
try {
int len;
byte[] bt = new byte[1024];
while ((len = input.read(bt)) != -1) {
System.out.println(new String(bt));
}
input.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
 public class Output implements Runnable {
private PipedOutputStream output; public Output(PipedOutputStream output) {
this.output = output;
} @Override
public void run() {
try {
output.write("测试管道流".getBytes());
output.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
     private static void function_demo6() throws IOException {
PipedInputStream input = new PipedInputStream();
PipedOutputStream output = new PipedOutputStream();
input.connect(output);
new Thread(new Input(input)).start();
new Thread(new Output(output)).start();
}

六,操作基本数据类型的流对象DataInputStream和DataOutputStream

  IO流中提供了两个操作基本数据类型的流对象,可对基本数据类型进行流的读取与写入操作,这两个流提供了对各种基本数据类型的读与写的操作,其中有两个特殊的读写方法readUTF()和writeUTF(),这两个方法分别使用UTF-8修改版编码将一个字符串写入或读出流中,演示如下

     private static void function_demo7() throws IOException {
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream("output.txt"));
outputStream.writeUTF("张三");
outputStream.close();
}
     private static void function_demo8() throws IOException {
DataInputStream inputStream = new DataInputStream(new FileInputStream("output.txt"));
System.out.println(inputStream.readUTF());
inputStream.close();
}

七,操作数组的流ByteArrayInputStream和ByteArrayOutputStream,操作字符的流CharArrayReader和CharArrayWriter,操作字符串的流StringReader和StringWriter

  由于这几个流的操作方式基本相同,所以下面演示一下操作数组的流,其它两个流就不再演示了

     private static void function_demo9() throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream("李四".getBytes());
ByteArrayOutputStream ba = new ByteArrayOutputStream();
int len;
while ((len = in.read()) != -1) {
ba.write(len);
}
System.out.println(ba.toString());
}
 

Java中的IO流(六)的更多相关文章

  1. Java中的IO流(四)

    上一篇<Java中的IO流(三)>把IO流中的文件及目录操作的对象File类记录了一下,本篇把本不属性IO流但又和IO流有关系的一个对象作一下记录,此对象本属于集合框架里的一个子集,即Pr ...

  2. Java中的IO流(三)

    上一篇<Java中的IO流(二)>把学习Java的字符流以及转换流作了一下记录,从本篇开始将把IO流中对文件或文件夹操作的对象File类的学习进行一下记录. 一,File类的构造函数及字段 ...

  3. Java中的IO流(二)

    上一篇<Java中的IO流(一)>把学习IO流的字符流作了一下记录,本篇把字节流记录一下. 一,Java中的字节流 Java中的字节流的操作方式与字符流的操作方式大致相同,连方法名都是类似 ...

  4. java中的IO流

    Java中的IO流 在之前的时候我已经接触过C#中的IO流,也就是说集中数据固化的方式之一,那么我们今天来说一下java中的IO流. 首先,我们学习IO流就是要对文件或目录进行一系列的操作,那么怎样操 ...

  5. Java中的IO流总结

    Java中的IO流总结 1. 流的继承关系,以及字节流和字符流. 2. 节点流FileOutputStream和FileInputStream和处理流BufferedInputStream和Buffe ...

  6. Java中的IO流大体介绍

    由于Java中的IO流是在是知识点繁多,所以我大约花了1周的时间将其整理起来.但是整理起来后并不是将完事了,我还是要分字节流和字符流来讲述.然后字节流和字符流中还有是否带有缓冲流. 讲述完IO流后我将 ...

  7. Java中的IO流,Input和Output的用法,字节流和字符流的区别

    Java中的IO流:就是内存与设备之间的输入和输出操作就成为IO操作,也就是IO流.内存中的数据持久化到设备上-------->输出(Output).把 硬盘上的数据读取到内存中,这种操作 成为 ...

  8. Java中的IO流(五)

    上一篇<Java中的IO流(四)>记录了一下Properties类,此类不属于IO流,它属于集合框架.接下来说一下IO流中的其它流 一,打印流PrintStream PrintStream ...

  9. JAVA 中的IO流

    Java中的IO流是用来处理设备与设备之前的数据传输,在java中以流的形式传输.流分为两类:字节流和字符流. 字节流:InputStream,OutPutSteam.(计算机内的数据都是以字节存储的 ...

随机推荐

  1. react生命周期es6

    基本函数有 import React from 'react' export default class MyClass extends React.Component { constructor(p ...

  2. 怎样修复grub开机引导(grub rescue)

    很多时候,特别是在linux调整分区后,开机重启时会出现         error : unknow filesystem         grub rescue>         的字样,系 ...

  3. MUI框架-07-HBuilder+夜神安卓模拟器

    MUI框架-07-HBuilder+夜神安卓模拟器 有时候我们在 HBuilder 里面 web 浏览器预览我们的 MUI 项目界面时,总感觉这个 web 浏览器随便拖拉比例,大小可调,但它毕竟是浏览 ...

  4. Linux-文件目录命令

    黑色 代表是普通的文件 蓝色 代表是目录(文件夹) 紫色 代表是图片文件 绿色 代表是可以执行的文件(脚本文件)-->存放linux命令的 红色 代表是压缩文件 clear:清屏pwd:显示当前 ...

  5. 在Windows下为PHP5.6安装redis扩展和memcached扩展

    一.php安装redis扩展   1.使用phpinfo()函数查看PHP的版本信息,这会决定扩展文件版本       2.根据PHP版本号,编译器版本号和CPU架构, 选择php_redis-2.2 ...

  6. TCP状态统计 - 脚本命令

    一.netstat命令说明 netstat常见参数 -a (all)显示所有选项,默认不显示LISTEN相关 -t (tcp)仅显示tcp相关选项 -u (udp)仅显示udp相关选项 -n 拒绝显示 ...

  7. laravel model relationship

    laravel支持多种模型之间的relation,对应着模型间的one2one, one2many,many2many,hasManyThrough,Polymorphic, many2many po ...

  8. Linux下tomcat端口、自启问题

    一.防火墙开放8080端口命令: 以下命令只针对 CentOs 7 以上版本,CentOS升级到7之后,使用firewalld代 替了原来的iptables: 启动: # systemctl star ...

  9. linux Mint 安装网易云音乐

    第一天从官网下来了最新版netease-cloud-music_1.1.0_amd64_ubuntu.deb安装,用的挺好的,第二天就打不开了郁闷,查了下很多人都遇到了这样的问题,需要root权限运行 ...

  10. Sql Server关于日期查询时,如果表中日期到具体某个时间

    1.如果查询日期参数为'2017/02/21',而数据库表中的字段为'2017/02/21 12:34:16.963',则需要格式化一下日期才能查询出来,如下 select * from table ...