您的位置:首页 > 编程语言 > Java开发

Java的IO流与对象的序列化浅谈

2016-01-16 13:54 597 查看
今天再次回顾了一些关于java IO流的知识。虽然以前会点IO的方面的编程,但是还是知其然不知其所以然。

首先上一张IO流的主要家族图吧。





该图给出了IO流的具体分类和各类之间的继承关系。其中还给出了各个类的使用场景和用途。

一、IO流的主流分类

IO流主要分为字节流和字符流。它们之间的区别就是:

1、读写的单位不同,字节流是以字节(8bit)为单位的。字符流是以字符为单位的,根据字符码表映射的字符,一次可能读取多个字节。

2、处理的对象不同。字节流处理所有类型的数据,包括图片、音视频等。而字符流只能处理字符型的数据。

3、字节流是直接传输的流通道,在进行流操作时是没有缓冲区的。但是字符流在操作的时候会使用到缓冲区。这就是为什么字符流的类实例都会有flush()方法。

然后针对字节流和字符流会分成相应的输入输出流。

字节流:InputStream输入字节流、OutputStream输出字节流。

字符流:Reader字符输入流、Writer字符输出流。

二、文件类的介绍和说明

在java中表示文件类的有File和RandomAccessFile类。而IO流的类主要是针对文件类中的文件内容来进行读写的操作的。

1、File类表示文件的本身,可以直接使用此类来完成文件的各种操作。比如:创建、删除、判断是否为目录等。

2、RandomAccessFile类可以从指定的位置开始读取信息,主要是因为这个类中定义了文件指针。但是这个类是要求文件中各个数据的保存的长度必须是固定的。

接下来举一个经常会问到的例子:如何遍历一个文件目录中的所用文件??



代码如下:

package com.io;

import java.io.*;

public class listFile {

public static void main(String[] args) {
// TODO Auto-generated method stub
//1 新建一个File类的实例
File file=new File("d:"+File.separator+"Xiaomi");
//2 进行文件目录的打印
print(file);
}

public static void print(File file) {
// TODO Auto-generated method stub
if(file!=null){//判断文件不为空
if(file.isDirectory()){//判断文件是不是目录
File[] fs=file.listFiles();//返回所有的子文件
if(fs!=null){
//开始遍历子文件
for (int i = 0; i < fs.length; i++) {
print(fs[i]);
}
}
}else{//如果不是目录就打印文件的路径
System.out.println(file);
}
}
}

}


结果显示:



三、输入输出流的介绍
输入输出流主要分为字节流和字符流。但是在传输中使用字节流的操作会比较的多。InputStream输入字节流、OutputStream输出字节流、Reader字符输入流、Writer字符输出流它们都是抽象类,根据使用的子类不同输入或输出的位置会不同。
比如向对上面的File文件类来进行操作时,我们应该会想到使用FileInputStream和FileOutputStream类。
Java的JDK中也提供了将字节流转换为字符流的类:InputStreamReader(InputStream)和OutputStreamWriter(OutputStream)这两个类。
使用ByteArrayInputStream和ByteArrayOutputStream类是可以对内存进行输入和输出的。
在线程之间进行输入和输出时主要使用的是PipedOutputStream和PipedInputStream类。
这里注意一下,在日常的IO输出时最好是使用打印流来进行输出。即PrintWriter和PrintStream类。这样可以方便的打印各种类型的数据,说到底就是打印流中的print方法实现了对各种数据打印方法的重载。还有我们经常使用的System.out实际上就是一个PrintStream类。
-----------------------------------------------------*-------------------------------------------------------------------------
当然对于这些IO输入输出类实际上我们是可以进行装饰的。这就是FilterInputStream、FilterReader等类的作用,它们的子类就是对应的各种各样的装饰类。比如以 FilterOutputStream的子类是:BufferedOutputStream、 DataOutputStream、PrintStream。
BufferedOutputStream是可以直接向缓冲区写入数据。该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
DataOutputStream是数据输出流,提供了与平台无关的数据操作。数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
注意这些经过装饰类修饰的IO字节流,有的会带有缓冲区功能。所以也就会有flush()方法。
BufferedOutputStream为IO提供了带有缓冲区的操作,一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能。从应用程序中把输入放入文件,相当于将一缸水倒入另一个缸中。
1> FileOutputStream---->write方法相当于一滴一滴的把水转移过去。

2> DataOutputStream-->writeXXX方法会方便一些,相当于一瓢一瓢的把水转移过去

3> BufferedOutputStream-->write()方法会更方便一些,相当于一瓢一瓢先放入水桶中,在从桶中倒入缸中,性能提高了。

四、对象的序列化

什么叫对象的序列化。实际上就是把一个对象变为二进制的字节数据流的一种方法。



如果一个类想实现序列化只需要实现Serializable接口即可。接着我们是如何将这个类进行传输的。这时会有两个字节流:ObjectInputStream和ObjectOutputStream(对象输入输出流)。我们要知道类的序列化序列的是属性的内容,不会序列化方法的。如果我们使用transient关键字来修饰某个属性,这就表示这个属性是不会被序列化的。

为何要进行序列化呢.不进行序列化,我的程序不跑的好好的吗?你想要什么结果,我也能给解决不是.我想说确实是这样,如果你的程序与网络无关,那很好你已经可以摒弃它了.

那下面我来简单分析下为何java需要进行序列化呢?

首先我们要明白,序列化是做什么作用的?java序列化:以特定的方式对类实例的瞬时状态进行编码保存的一种操作。(可能不是很精确,咱不是搞学术的,看懂即可)。从此定义可以看出,序列化作用的对象是类的实例。对实例进行序列化,就是保存实例当前在内存中的状态。包括实例的每一个属性的值和引用等。

既然后序列化,便会有反序列化。反序列化的作用便是将序列化后的编码解码成类实例的瞬时状态,申请等同的内存保存该实例。

从上述定义可以发现,序列化就是为了保存java的类对象的状态的。保存这个状态的作用主要用于不同jvm之间进行类实例间的共享。在ORMaping中的缓存机制,进行缓存同步时,便是常见的java序列化的应用之一。在进行远程方法调用,远程过程调用时,采用序列化对象的传输也是一种应用...当你想从一个jvm中调用另一个jvm的对象时,你就可以考虑使用序列化了。

简而言之:序列化的作用就是为了不同jvm之间共享实例对象的一种解决方案。由java提供此机制,效率之高,是其他解决方案无法比拟的。

五、新的IO(NIO)

我们知道在文件操作的时候,速度的提高是很重要的。速度的提高是来自于我们使用了更接近于操作系统执行I/O的方式:通道和缓冲器。



这里唯一直接与通道交互的缓冲器(即缓冲区)就是ByteBuffer。这是新的IO,主要是为了提升IO操作的速度的。建议有时间去看JDK中的描述。

下面的代码演示三种类型的流的可写、可读的通道:

package com.io;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/*注意该方法只能测试英文字符。如果测试中文就会出现乱码*/
public class GetChannels {
private static final int BSIZE=1024;//用于后面设置字节缓冲区的大小
public static void main(String[] args) throws  Exception{
// TODO Auto-generated method stub
//写一个文件
FileChannel fc=new FileOutputStream("date.txt").getChannel();//获取与此文件输出流相关的唯一通道
fc.write(ByteBuffer.wrap("my dear,".getBytes()));//注意是先获取字符串的字节数组,然后在将字节数组包装到缓冲区中
fc.close();//关闭通道

//尝试在文件中添加内容
fc=new RandomAccessFile("date.txt", "rw").getChannel();//重新获取文件的任意操作流的唯一通道
fc.position(fc.size());//将文件指针指向最末尾,这样可以防止之前的字符被新的字符给覆盖
fc.write(ByteBuffer.wrap("my family".getBytes()));
fc.close();

//读取文件
fc=new FileInputStream("date.txt").getChannel();
ByteBuffer buff=ByteBuffer.allocate(BSIZE);//分配一个新的字节缓冲区,大小为1k
fc.read(buff);//将文件内容读取到字节缓冲区buff中
buff.flip();//为读取字节缓冲区做准备
while(buff.hasRemaining())//字节缓冲区是否还有字节元素
System.out.print((char)buff.get());//获取字节,并转化为字符

}

}


结果显示:



注意这种使用方法是无法读取中文字符的。会出现乱码的。所以尝试使用编码的方式来解决:

package com.io;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

public class BufferToText {
private static final int BSIZE=1024;
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//写入file中
FileChannel fc=new FileOutputStream("date2.txt").getChannel();
fc.write(ByteBuffer.wrap("my love我的爱".getBytes()));//将字符转换为字节数组,在放入字节缓冲器中。最后写入到通道中
fc.close();
fc=new FileInputStream("date2.txt").getChannel();
ByteBuffer buffer=ByteBuffer.allocate(BSIZE);//定义一个指定大小的字节缓冲区
fc.read(buffer);
buffer.flip();
//打印读取到的字节
//System.out.print(buffer.asCharBuffer());//这种方法没什么用,会出现乱码。

//我们需要系统的指定默认的字符集
buffer.rewind();
String encoding =System.getProperty("file.encoding");//获取文件的字节码
System.out.println("Decoding using:"+encoding+":"+Charset.forName(encoding).decode(buffer));

//或者我们也可以使用编码来向文件中写入字符
fc=new FileOutputStream("date3.txt").getChannel();
fc.write(ByteBuffer.wrap("我是一份大菠菜。".getBytes("UTF-16BE")));
fc.close();

//接下来我们进行读文件
fc=new FileInputStream("date3.txt").getChannel();
buffer.clear();
fc.read(buffer);
buffer.flip();
System.out.print(buffer.asCharBuffer());
}

}


结果显示:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: