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

JavaSE学习 第十四章 高级IO编程和对象序列化

2013-07-30 13:00 489 查看


参考 IO流概述 /article/3875750.html


Java对象序列化 /content/2743075.html

1.基本概念

I/O(Input/Output):输入输出

数据源(Data Source):数据的来源,可以是文件,数据库等等

数据宿(Data Sink):数据传输到的终端(destination)

Java中把不同的数据源与程序间的数据传输都抽象表述为“流”(Stream),java.io包中定义了多种I/O流类型实现数据I/O功能。

I/O流分类:

输入流(Input Stream)和输出流(Output Stream)

节点流(Node Stream)和处理流(Processing Stream)

字符流(Character Stream)和字节流(Byte Stream)

2.分类解析

输入流(Input Stream)和输出流(Output Stream)

按照 数 据 流 动 的 方 向 , Java 流 可 分 为 输 入 流(Input Stream)和输出流(Output Stream)

输入流只能从中读取数据,而不能向其写出数据;输出流则只能向其写出数据,而不能从中读取数据;

特例:java.io.RandomAccessFile类。 (它既可以写,也可以读)

节点流(Node Stream)和处理流(Processing Stream)

根据数据流所关联的是数据源还是其他数据流,可分 为 节 点 流 ( Node Stream)和 处 理 流(Processing Stream)

节点流直接连接到数据源处理流是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现增强的数据读/写功能,处理流并不直接连到数据源。



字符流(Character Stream)和字节流(Byte Stream)

按传输数据的“ 颗粒大小” 划分,可分为字符流(Character Stream)和字节流(Byte Stream)

字节流以字节为单位进行数据传输,每次传送一个或多个字节;字符流以字符为单位进行数据传输,每次传送一个或多个字符。

Java命名惯例:凡是以InputStream或OutputStream结尾的类型均为字节流,凡是以Reader或Writer结尾的均为字符流。

3.InputStream 和 OutputStream

抽象类java.io.InputStream 是所有字节输入流类型的父类,该类中定义了以字节为单位读取数据的基本方法,并在其子类中进行了分化和实现。

三个基本的read方法:

int read():读取一个字节(8位),将这个字节的高24位补零,转换成了一个int(32位),并输出

int read(byte[] buffer):从当前的数据源中成批的读取数据并保存到buffer数组中,如果实际读取的字节数小于buffer数组的大小,则返回实际读取的字节数

int read(byte[] buffer, int offset, int length):与上面的方法类似,如果实际读取的字节数小于length的大小,则返回实际读取的字节数

其它方法:

void close()

int available():数据源后面还有多少个可以读入的字节数

skip(long n):跳过n个字节

boolean markSupported():是否支持做标记(在InputStream的子类中进行了分化)

void mark(int readlimit):在不超过readlimit范围内可以重新跳到标记处

void reset():跳转到原来标记的地方

InputStream层次



java.io.OutputStream与java.io.InputStream对应,是所有字节输出流类型的抽象父类。

三个基本的write方法:

void write(int c):将参数int对应的整型的高24位丢弃,剩下的8位作为一个字节输出到当前关联的数据宿

void write(byte[] buffer):将buffer的数组中的内容输出到当前关联的数据宿

void write(byte[] buffer, int offset, int length):将buffer的数组中从下标offset长度为length的内容输出到当前关联的数据宿

其它方法:

void close()

void flush():清空输出缓冲区

层次:



4.Reader 和 Writer

抽象类java.io.Reader 是所有字符输入流类型的父类,其中声明了用于读取字符流的有关方法。

三个基本的read方法:

int read():从当前关联的输入流中读取一个字符,并将这个字符的高16位补零后转换成int类型并返回

int read(char[] cbuf):从当前关联的输入流中读取cbuf.length个字符并填充到数组cbuf中,如果没有length个返回实际的大小

int read(char[] cbuf, int offset, int length)

其它方法:

void close()

boolean ready():判断当前的字符流是否就绪

skip(long n)

boolean markSupported()

void mark(int readAheadLimit)

void reset()



java.io.Writer与java.io.Reader类对应,是所有字符输出流类型的共同父类。

五个基本的write方法:

void write(int c):将int的低16位转换成一个char并输出到当前关联的输出流中

void write(char[] cbuf)

void write(char[] cbuf, int offset, int length)

void write(String string):将字符串string写出到当前关联的输出流中

void write(String string, int offset, int length)

其它方法:

void close()

void flush()



5.常用的IO流类型

①FileInputStream/FileOutputStream

FileInputStream 用 于 读 取 本 地 文 件 中 字 节 数 据 ,FileOutputStream用于将字节数据写出到文件。

例14-1 使用字节流实现文件复制

package v512.chap14;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyFile{
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream ("D:\\test.txt");
FileOutputStream fos = new FileOutputStream ("D:\\testcopy.txt");
int read = fis.read();
while ( read != -1 ) {
fos.write(read);
read = fis.read();
}
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

结果就是D盘下面多了一个文件,testcopy.txt,而且内容和 test.txt 一样

②FileReader/FileWriter

FileReader用于以字符为单位读取文本文件,FileWriter类用于将字符数据写出到文本文件中。

例14-2 使用字符流实现文件复制

import java.io.*;
public class Test {
public static void main(String[] args) {
try {
FileReader input = new FileReader("a.txt");
FileWriter output = new FileWriter("temp.txt");
int read = input.read();
while ( read != -1 ) {
output.write(read);
read = input.read();
}
input.close();
output.close();
} catch (IOException e) {
System.out.println(e);
}
}
}


在JavaVM中字符是用UniCode编码保存的(每个字符占2个字节),保存在系统磁盘中的文本文件是使用的是当前操作系统默认的字符编码

(中文操作系统:GBK)在读写文件时是先对文本文件按照本地的字符编码进行编码,然后再转换成Unicode编码格式

③BufferedReader/BufferedWriter

BufferedReader用于缓冲读取字符,BufferedWriter则提供字符的缓冲写出功能。

例14-3 使用字符处理流实现文件复制

import java.io.*;
public class Test {
public static void main(String[] args) {
try {
FileReader input = new FileReader("Test.java");
BufferedReader br = new BufferedReader(input);
FileWriter output = new FileWriter("temp.txt");
BufferedWriter bw = new BufferedWriter(output);
String s = br.readLine();

//s中只包含有效的信息,并不包含换行符

while ( s!=null ) {
bw.write(s);
bw.newLine();
s = br.readLine();
}
br.close();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}


④InputStreamReader

InputStreamReader可封装字节输入流并从中读取字节数据,然后将之转换为字符。

转换时所使用的字符编码可以在构造方法中显式指定,也可以使用平台的默认字符编码。其构造方法格式为:

• public InputStreamReader(InputStream in)

• public InputStreamReader(InputStream in, String enc)

OutputStreamWriter

与 InputStreamReader 对 应 , OutputStreamWriter 可 按 照特定的字符编码规则把字符转化为字节并写出到它所封装的字节输出流。

⑥PrintStream

PrintStream 在 OutputStream 基 础 之 上 提 供 了 增 强 的 功能,即可以方便地输出各种类型数据(而不仅限于byte型)的格式化表示形式。

PrintStream 的方法从不抛出IOException。

PrintWriter

PrintWriter提供了PrintStream的所有打印方法,其方法也从不抛出IOException。

与PrintStream的区别:

作为处理流使用时,PrintStream只能封装OutputStream 类型的字节流

PrintWriter 既可以封装OutputStream(字节输出流),还能够封装Writer类型字符输出流并增强其功能。

⑦DataInputStream/DataOutputStream

二者分别实现了DataInput/DataOutput接口

DataInputStream能够以一种与机器无关的方式,直接从底层字节输入流读取Java基本类型和Sring类型的数据

常用方法包括:

public DataInputStream(InputStream in)

public final boolean readBoolean()

public final byte readByte()

public final char readChar()

public final double readDouble()

public final float readFloat()

public final int readInt()

public final long readLong()

public final short readShort()

public final String readUTF()

DataOutputStream则能够直接将Java基本类型和String类型数据写出到其他的字节输出流。

实例:

package v512.chap14;
import java.io.*;
public class Test{
public static void main(String args[]){
try{
FileOutputStream fos;
DataOutputStream dos;
FileInputStream fis;
DataInputStream dis;
fos = new FileOutputStream("myfile.data");
dos = new DataOutputStream(fos);
dos.writeUTF("休.格兰特");
dos.writeInt(40);
dos.close();
fis = new FileInputStream("myfile.data");
dis = new DataInputStream(fis);
System.out.println("name:" + dis.readUTF());
System.out.println("age:" + dis.readInt());
fis.close();
}catch(IOException e){
System.out.println(e);
}
}
}


⑧CharArrayReader 和 CharArrayWriter

CharArrayReader 实现了一个可用作字符输入流的字符缓冲区。

import java.io.*;
public class MyReader{
public void getInfo(Reader reader){
try{
int b = reader.read();
while(b != -1){
System.out.print((char)b);
b = reader.read();
}
System.out.println();
}catch(IOException e){
e.printStackTrace();
}
}
}


import java.io.*;
public class TestCharArrayReader{
public static void main(String[] args){
char[] b = {'I',' ','a','m',' ','S','a','i','l','i','n','g','!'};
CharArrayReader car = new CharArrayReader(b);
MyReader mr = new MyReader();
mr.getInfo(car);
}
}


CharArrayWriter

CharArrayWriter实现了一个可当作Writer使用的字符输出缓冲区。

package v512.chap14;
import java.io.*;
import java.util.Date;

public class TestCharArrayWriter{
public static void main(String[] args){
try{
BufferedReader br = new BufferedReader(new FileReader("D:\\a.txt"));
String s ;
while((s=br.readLine()) != null){
System.out.println(s);
}
}catch(IOException e){
CharArrayWriter cw = new CharArrayWriter();
PrintWriter pw = new PrintWriter(cw,true);
e.printStackTrace(pw);
//char[] ca = cw.toCharArray();
String info = cw.toString();
Date time = new Date();
//将time和info信息写入数据库---
System.out.println("出错时间: " + time);
System.out.println("错误信息:\n" + info);
}
}
}


结果: (注意:我的D盘下没有那个文本文件)

出错时间: Fri Sep 02 11:33:05 CST 2011
错误信息:
java.io.FileNotFoundException: D:\a.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:106)
at java.io.FileInputStream.<init>(FileInputStream.java:66)
at java.io.FileReader.<init>(FileReader.java:41)
at v512.chap14.TestCharArrayWriter.main(TestCharArrayWriter.java:8)


6. I/O应用专题

①标准I/O重定向

例:标准输入重定向

package v512.chap14;
import java.io.*;

public class TestSetInput{
public static void main(String[] args){
try{
FileInputStream fis = new FileInputStream("D:\\source.txt");
System.setIn(fis);
int avg = 0;
int total = 0;
int count = 0;
int num = 0;
int i;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
while(s != null && !s.equals("") && !s.equals("over")){
i = Integer.parseInt(s);
num++;
total += i;
avg = total/num;
System.out.println("num=" + num + "\ttotal=" + total + "\tavg=" + avg);
s = br.readLine();
}
}catch(Exception e){
e.printStackTrace();
}
}
}


source.txt 内容就是一些数字:

23

50

46

89

77

输出结果:

num=1    total=23    avg=23
num=2    total=73    avg=36
num=3    total=119    avg=39
num=4    total=208    avg=52
num=5    total=285    avg=57


例: 标准输出/标准错误输出重定向

package v512.chap14;
import java.io.*;
import java.util.Date;

public class TestSetOutput{
public static void main(String[] args){
PrintStream ps = null;
PrintStream ps_error = null;
try{
FileInputStream fis = new FileInputStream("D:\\source.txt");
System.setIn(fis);
ps = new PrintStream(new FileOutputStream("D:\\output.txt",true));
System.setOut(ps);//设置标准输出
ps_error = new PrintStream(new FileOutputStream("D:\\errorLog.txt",true));
System.setErr(ps_error);//设置标准错误输出

int avg = 0;
int total = 0;
int num = 0;
int i;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
while(s != null && !s.equals("over")){
i = Integer.parseInt(s);
num++;
total += i;
avg = total/num;
System.out.println("num=" + num + "\ttotal=" + total + "\tavg=" + avg);
s = br.readLine();
}
}catch(Exception e){
System.err.println("出错时间: " + new Date());
System.err.print("错误信息:");
e.printStackTrace(System.err);
}finally{
try{
ps.close();
ps_error.close();
}catch(Exception e1){
System.err.println("出错时间: " + new Date());
System.err.print("错误信息:");
e1.printStackTrace(System.err);
}
}
}
}


程序的得到的结果是:

输出文件内容:

num=1    total=23    avg=23
num=2    total=73    avg=36
num=3    total=119    avg=39
num=4    total=208    avg=52
num=5    total=285    avg=57


②属性信息导入/导出

例: 属性导出/导入

导出:

package v512.chap14;
import java.io.*;
import java.util.Properties;

public class SaveProperties{
public static void main(String[] args){
try{
Properties ps = new Properties();
ps.setProperty("name","Scott");
ps.setProperty("password","Tiger");
FileWriter fw = new FileWriter("D:\\props.txt");
ps.store(fw,"loginfo");
fw.close();
}catch(Exception e){
e.printStackTrace();
}
}
}


属性信息: #开头的信息是注释

#loginfo
#Fri Sep 02 13:29:35 CST 2011
password=Tiger
name=Scott


导入:

package v512.chap14;
import java.io.*;
import java.util.Properties;

public class LoadProperties{
public static void main(String[] args){
try{
Properties ps = new Properties();
FileReader fr = new FileReader("D:\\props.txt");
ps.load(fr);
fr.close();
ps.list(System.out);
}catch(Exception e){
e.printStackTrace();
}
}
}


控制台输出:

-- listing properties --
password=Tiger
name=Scott


③随机存取文件

例:实现文件随机存/取操作

package v512.chap14;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class TestRandomAccessFile{
private File file;
public static void main(String[] args){
TestRandomAccessFile traf = new TestRandomAccessFile();
traf.init();
traf.record("Billy",22);
traf.listAllRecords();
}

//更新指定玩家的记录值
public void record(String record_breaker, int times){
try{
RandomAccessFile raf = new RandomAccessFile(file,"rw");
boolean flag = false;
while(raf.getFilePointer() < raf.length()){
String name = raf.readUTF();
if(record_breaker.equals(name)){
raf.writeInt(times);

//这里会覆盖原来的记录值

flag = true;
break;
}else{
raf.skipBytes(4);
}
}
if(!flag){
raf.writeUTF(record_breaker);
raf.writeInt(times);
}
raf.close();
}catch(Exception e){
e.printStackTrace();
}
}

//初始化,如果文件不存在就创建一个,用于保存用户的记录
public void init(){
if(file == null){
file = new File("D:\\record.txt");
try{
file.createNewFile();
}catch(IOException e){
e.printStackTrace();
}
}
}

//显示所有的玩家的记录
public void listAllRecords(){
try{
RandomAccessFile raf = new RandomAccessFile(file,"r");
while(raf.getFilePointer() < raf.length()){
String name = raf.readUTF();
int times = raf.readInt();
System.out.println("name:" + name + "\trecord:" + times);
}
raf.close();
}catch(Exception e){
e.printStackTrace();
}
}
}


输出:

name:Billy record:22

④临时文件

例: 使用临时文件

package v512.chap14;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

import javax.swing.JButton;
import javax.swing.JFrame;

public class TestTempFile implements ActionListener{
private File tempPath;
public static void main(String args[]){
TestTempFile ttf = new TestTempFile();
ttf.init();
ttf.createUI();
}

//创建窗体
public void createUI(){
JFrame frame = new JFrame();
JButton jb = new JButton("创建临时文件");
jb.addActionListener(this);
frame.add(jb,"North");
frame.setSize(200,100);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}

//初始化方法,如果是第一次则创建一个临时文件夹
public void init(){
tempPath = new File("temp");
if(!tempPath.exists() || !tempPath.isDirectory()){
tempPath.mkdir();  //如果不存在,则创建该文件夹
}
}

//重载方法,执行创建临时文件
public void actionPerformed(ActionEvent e){
try    {
//在tempPath路径下创建临时文件"mytempfileXXXX.tmp"
//XXXX 是系统自动产生的随机数, tempPath对应的路径应事先存在
//要创建的文件的前缀mytempfile,后缀txt,临时文件存放的目录
File tempFile=File.createTempFile("mytempfile",".txt",tempPath);//第三个参数null的话文件会在系统默认的临时文件夹下
System.out.println(tempFile.getAbsolutePath());

FileWriter fout=new FileWriter(tempFile);
PrintWriter out=new PrintWriter(fout);
out.println("some information in file" );
out.close(); //注意:如无此关闭语句,文件将不能删除

//tempFile.delete();
tempFile.deleteOnExit();
}
catch(IOException e1){
System.out.println(e1);
}
}
}

界面显示:然后点击按钮



输出:E:\workspace\JavaStudy\temp\mytempfile8638932010211823469.txt

查看上面路径中的文件信息:

some information in file

如果将File.createTempFile(……)中的第三个参数改成 null

文件就会保存到系统默认的路径:我的电脑的临时文件存放目录

D:\ADMINS~1\WINDOW~1\mytempfile5941252212613977212.txt

7.对象的序列化

①分析:如何保存数据?

基本概念

对象的持久性( Object Persistance):长久保存一个对象的状态并在需要时获取该对象的信息以重新构造一个状态完全相同的对象。

对象序列化(Object Serialization):通过写出对象的状态数据来记录一个对象。

对象序列化的主要任务:写出对象的状态信息,并遍历该对象对其他对象的引用,递归的序列化所有被引用到的其他对象,从而建立一个完整的序列化流

②实现对象的序列化

要序列化一个对象,其所属的类必须实现以下两种接口之一:

java.io.Serializable

java.io.Externalizable

java.io.ObjectOutputStream/ObjectInputStream 类 分别提供了对象的序列化和反序列化功能。

用法举例:

例: 实现对象序列化

雇员类:

package v512.chap14;
import java.io.Serializable;

public class Employee implements Serializable{
private String name;
private int age;
private String dept;

//public Employee(){}
public Employee(String name,int age,String dept){
this.name = name;
this.age = age;
this.dept = dept;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void setDept(String dept){
this.dept = dept;
}
public String getDept(){
return dept;
}
public void showInfo(){
System.out.println("name:" + name + "\tage:" + age + "\tdept:" + dept);
}
}

写对象:

package v512.chap14;
import java.io.*;

public class WriteObject {
public static void main(String[] args){
try{
FileOutputStream fos = new FileOutputStream("data.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(new Employee("张三",28,"市场部"));
oos.writeObject(new Employee("李四",34,"技术部"));
oos.close();
}catch(IOException e){
e.printStackTrace();
}
}
}

如果Employee类没有实现 Serializable 接口的话是会报错的

写出去的数据如下图所示:它是与JavaVM中的格式是一样的 (视频截图,我的电脑中显示不是这样)



我的电脑显示:貌似是一堆乱码,但是可以读对象



读对象:

package v512.chap14;
import java.io.*;

public class ReadObject{
public static void main(String[] args){
try{
FileInputStream fis = new FileInputStream("D:\\employee.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Employee e1 = (Employee)ois.readObject();
Employee e2 = (Employee)ois.readObject();

e1.showInfo();
e2.showInfo();
ois.close();
}catch(IOException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}


输出结果:

name:张三 age:28 dept:市场部

name:李四 age:34 dept:技术部

③对象序列化API层次



前面的 DataInputStream,DataOutputStream 分别实现了 java.io.DataOutput/DataInput 接口,这里 ObjectOutput/ObjectInput 两个接口在上面的两个接口之上

还分别添加了方法:writeObject / readObject,而 ObjectOutputStream /ObjectInputStream 分别实现了上面的两个接口,还继承了 java.io.OutputStream/InputStream

java.io.Externalizable 继承了 java.io.Serializable ,用于在对象序列化过程中提供增强的功能

④对象序列化过程中的数据保护

标记性接口Serializable (这个接口中没有任何的抽象方法,标记性接口一般是用于作为一种过滤器)

在对象序列化过程中,其所属类的static属性和方法代码不会被序列化处理。

对于个别不希望被序列化的非static属性,可以在属性声明时使用transient关键字进行标明。

用法举例:

例:

Person.java

package v512.chap14;
import java.io.Serializable;
public class Person implements Serializable{
private static int resource = 100;//static属性,不会序列化
private String name ;//会序列化
private transient String password;//transient 不会序列化

public Person(String name,String password){
this.name = name;
this.password = password;
}

public static void setResource(int resource){
Person.resource = resource;
}
public static int getResource(){
return resource;
}
public String toString(){
return "name:" + name + "\tpassword:" + password;
}
}


WriteObject.java

package v512.chap14;
import java.io.*;

public class WriteObject {
public static void main(String[] args){
try{
Person.setResource(2345);
FileOutputStream fos = new FileOutputStream("D:\\person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Person p1 = new Person("Scott","tiger");
Person p2 = new Person("Spring","field");
System.out.println(Person.getResource());  // 2345
oos.writeObject(p1);
oos.writeObject(p2);
oos.close();
}catch(IOException e){
e.printStackTrace();
}
}
}


ReadObject.java

package v512.chap14;
import java.io.*;

public class ReadObject{
public static void main(String[] args){
try{
//Person.setResource(5555);
FileInputStream fis = new FileInputStream("D:\\person.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Person e1 = (Person)ois.readObject();
Person e2 = (Person)ois.readObject();
System.out.println(e1);
System.out.println(e2);
System.out.println(Person.getResource());
System.out.println(e1.getResource());
ois.close();
}catch(IOException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}


控制台输出:

name:Scott password:null

name:Spring password:null

100

100

密码没有,说明password没有序列化,而且resource还是100,不是2345,说明static属性也是不会序列化的

而且static属性的值不会因为序列化时resource的值改变而改变

8. NIO (暂时没有仔细看)

什么是NIO?

NIO基础:以下三者是NIO的核心

缓冲区(Buffer)

通道(Channel):和操作系统底层的双向输入输出管道关联

字符集转换(Charset)

高级NIO技术

子缓冲区和数据共享

只读缓冲区

内存映射文件

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