【Java基础】——IO流(下)

时间:2023-02-24 22:39:19

 一、对象序列化

1、概述

①对象序列化概念
程序运行时,会在内存中创建多个对象,如果希望永久保存这些对象,则可以将对象转化为字节数据写入到硬盘,这个过程称为对象的序列化。
②对象序列化前提
当对象要序列化,必须保证该对象所属类实现 Serializable 接口,以启用其序列化功能。
其中Serializable类无任何方法,属于 标记接口。
③UID:能序列化的对象所属类编译后都有一个UID,当对象属性修改后再编译对象(指类)会产生一个新序列号
(即UID,UID是根据类中成员算的),想要属性修改后对象的序列号不变,加 static final long serialVersionUID = 42L;修饰。
注意三点:
  • 静态是不能被序列化的,因为静态存在与方法区,对象存在于堆内存中。
  • 如果非静态想被序列化可以加 transient 修饰。
  • 想被序列化的对象要实现 Serializable 接口。

2、ObjectInputStream 和 ObjectOutputStream

①ObjectOutputStream:将 Java 对象的基本数据类型和图形写入流中
  • 构造方法:ObjectOutputStream(OutputStream out) 
  • 方法:writeObject(Object obj)//将对象写入流中,称为对象的序列化
②ObjectInputStream :对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
  • 构造方法:ObjectInputStream(InputStream in)
  • 方法:Object readObject()将对象反序列化

二、管道流(PipedInputStream 和 PipedOutputStream)

1、概述

管道流是一种特殊的流,必须先建立连接才能进行彼此间通信。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream 。
不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。

2、连接方法

①void connect(PipedOutputStream src)//使此管道输入流连接到管道输出流 src。
②void connect(PipedInputStream snk)//将此管道输出流连接到接收者。

代码演示如下所示:
package com.huang.stream;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

/**
* @author huangxiang
* @date 创建时间:2015年5月26日上午10:10:21
* @version 1.0
*/
public class PipedStreamDemo {
public static void main(String[] args) throws IOException {

PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);

Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();

}
}

class Read implements Runnable {
private PipedInputStream in;

Read(PipedInputStream in) {
this.in = in;
}

public void run() {
try {
byte[] buf = new byte[1024];

System.out.println("读取前。。没有数据阻塞");
int len = in.read(buf);
System.out.println("读到数据。。阻塞结束");

String s = new String(buf, 0, len);

System.out.println(s);

in.close();

} catch (IOException e) {
throw new RuntimeException("管道读取流失败");
}
}
}

class Write implements Runnable {
private PipedOutputStream out;

Write(PipedOutputStream out) {
this.out = out;
}

public void run() {
try {
System.out.println("开始写入数据,等待6秒后。");
Thread.sleep(6000);
out.write("piped lai la".getBytes());
out.close();
} catch (Exception e) {
throw new RuntimeException("管道输出流失败");
}
}
}

三、随机访问文件(RandomAccessFile)

1、概述

①该类直接继承自Object类,不属于流类,但是它是IO包中成员,具备读写文件数据的功能。 内部封装了一个数组和记录指针,而且可通过指针对数组的元素进行操作。
②具备读写数据功能原理:是其内部封装了字节输入和输出流。

2、构造方法

①构造方法
  • RandomAccessFile(File file,String mode)
  • RandomAccessFile(String name, String mode)
②从构造方法中我们可以看出:
  • file:被访问的文件
  • name:被访问文件的路径
  • mode:指定访问文件的模式,其中只读"r"不会创建文件,会去读取一个已存在文件,如果文件不存在或执行写入操作,则会出现异常。
  • "rw"表示读写文件,如果文件不存在,会自动创建。如果存在,不会覆盖原文件

3、常用方法

①void seek(long pos)//设定读写指针的位置,与文件开头(0处)相隔POS个字节数。
②int skipBytes(int n)//使读写指针从当前位置开始,跳过n个字节。
③void writeInt(int v)//按4个字节将 int 写入该文件,先写高字节。

代码演示如下:
package com.huang.stream;

import java.io.IOException;
import java.io.RandomAccessFile;

/**
* @author huangxiang
* @date 创建时间:2015年5月26日上午12:06:52
* @version 1.0
*/
/*
* RandomAccessFile 该类不是算是IO体系中子类。 而是直接继承自Object。
*
* 但是它是IO包中成员。因为它具备读和写功能。 内部封装了一个数组,而且通过指针对数组的元素进行操作。
* 可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。
*
* 其实完成读写的原理就是内部封装了字节输入流和输出流。 通过构造函数可以看出,该类只能操作文件。
* 而且操作文件还有模式:只读r,,读写rw等。
*
* 如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。
* 如果模式rw。操作的文件不存在,会自动创建。如果存则不会覆盖。
*/
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
// writeFile_2();
readFile();

// System.out.println(Integer.toBinaryString(258));

}

public static void readFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt", "r");

// 调整对象中指针。
// raf.seek(8*1);

// 跳过指定的字节数
raf.skipBytes(8);

byte[] buf = new byte[4];

raf.read(buf);

String name = new String(buf);

int age = raf.readInt();

System.out.println("name=" + name);
System.out.println("age=" + age);

raf.close();

}

public static void writeFile_2() throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");
raf.seek(8 * 0);
raf.write("周期".getBytes());
raf.writeInt(103);

raf.close();
}

public static void writeFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");

raf.write("李四".getBytes());
raf.writeInt(97);
raf.write("王五".getBytes());
raf.writeInt(99);

raf.close();
}
}

四、基本数据操作流(DataInputStream 与 DataOutputStream)

1、概述

是两个与平台无关的数据操作流,不仅提供了读写各种基本数据类型的方法,还提供了readUTF()和writeUTF()方法。

2、方法

a、void writeUTF(String str):使用 UTF-8 修改版编码将一个字符串写入输出流
b、String readUTF():读取writeUTF方法写入的字节   

代码演示如下:
package com.huang.stream;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;

/*
DataInputStream与DataOutputStream

可以用于操作基本数据类型的数据的流对象。
*/
/**
* @author huangxiang
* @date 创建时间:2015年5月27日上午12:13:26
* @version 1.0
*/
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
// writeData();
// readData();

// writeUTFDemo();

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
"gbk.txt"), "gbk");

osw.write("你好");
osw.close();

readUTFDemo();

}

public static void readUTFDemo() throws IOException {
DataInputStream dis = new DataInputStream(
new FileInputStream("utf.txt"));

String s = dis.readUTF();

System.out.println(s);
dis.close();
}

public static void writeUTFDemo() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"utfdate.txt"));

dos.writeUTF("你好");

dos.close();
}

public static void readData() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream(
"data.txt"));

int num = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();

System.out.println("num=" + num);
System.out.println("b=" + b);
System.out.println("d=" + d);

dis.close();
}

@SuppressWarnings("null")
public static void writeData() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"data.txt"));

dos.writeInt(234);
dos.writeBoolean(true);
dos.writeDouble(9887.543);

dos.close();

ObjectOutputStream oos = null;
oos.writeObject(new Object());

}
}   

五、字节数组操作流(ByteArrayInputStream 和 ByteArrayOutputStream)

1、概述

此类源和目的都是内存,不会调用系统资源,故不需要关流,此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException.    

2、方法摘要

①ByteArrayInputStream:包含一个内部缓冲区,该缓冲区包含从流中读取的字节
a、字段    
  • protected  byte[] buf 由该流的创建者提供的 byte 数组。
  • protected  int count  计数器,记录缓冲区的字节数。
  • protected  int mark 流中当前的标记位置。
  • protected  int pos    要从输入流缓冲区中读取的下一个字符的索引。
b、构造函数
ByteArrayInputStream(byte[] buf)//创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。此缓冲区和字段的定义的缓冲区不同,该缓冲区表示要接受的数据源,而字段的缓冲区是流从源中读取的数据。
②ByteArrayOutputStream:其数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
a、字段    
  • protected  byte[] buf 存储数据的缓冲区。。
  • protected  int count  缓冲区中的有效字节数。
b、构造函数
ByteArrayOutputStream()  创建一个新的 byte 数组输出流。其构造函数不许接收目的,原因是其在创建对象时就创建一个byte性数组的缓冲区,即数据目的。
小结:其实字符操作流和字符串操作流同字节数组操作流原理一样。

代码实例如下所示:
package com.huang.stream;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
* @author huangxiang
* @date 创建时间:2015年6月26日上午12:22:13
* @version 1.0
*/
/*
* 用于操作字节数组的流对象。
*
* ByteArrayInputStream :在构造的时候,需要接收数据源,。而且数据源是一个字节数组。
*
* ByteArrayOutputStream: 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字
* 节数组。这就是数据目的地。
*
* 因为这两个流对象都操作的数组,并没有使用系统资源。 所以,不用进行close关闭。
*
* 在流操作规律讲解时:
*
* 源设备, 键盘 System.in,硬盘 FileStream,内存 ArrayStream。 目的设备: 控制台
* System.out,硬盘FileStream,内存 ArrayStream。
*
* 用流的读写思想来操作数据。
*/

public class ByteArrayStream {
public static void main(String[] args) throws IOException {
// 数据源。
ByteArrayInputStream bis = new ByteArrayInputStream(
"ABCDEFD".getBytes());

// 数据目的
ByteArrayOutputStream bos = new ByteArrayOutputStream();

int by = 0;

while ((by = bis.read()) != -1) {
bos.write(by);
}

System.out.println(bos.size());
System.out.println(bos.toString());

bos.writeTo(new FileOutputStream("a.txt"));

}
}

六、字符编码

1、字符码表概述

①含义:是一种可以方便计算机识别的特定字符集,它是将每一个字符和一个唯一的数字对应而形成的一张表。
②常见字符码表
  • ASCII:美国标准信息交换码。用一个字节的7位二进制数表示。
  • ISO8859-1:拉丁码表,兼容ASCII,欧洲码表用一个字节的8位表示。
  • GB2312:中文编码表,兼容ASCII,英文占一个字节,中文占2和字节(2个字节都为负数)。
  • GBK:中文编码表升级,融合了更多的中文文字符号。用两个字节来表示(第一个字节为负数)。
  • Unicode:国际标准码,融合了多种文字。所有文字都用2个字节来表示,Java语言使用的就是unicode
  • UTF-8:Unicode的可变长编码,英文占1字节,中文占3。

2、字符编码和解码

①编码:把字符串变成计算机识别的字节序列。 byte[] getBytes();  byte[] getBytes(String charsetName);
②解码:把字节数组变成字符串。 new String(byte[]); new String(byte[],String charsetName); 当中文用gbk编码,再用ISO8859-1解码会出现乱码,因为ISO8859-1不识别中文,可以继续用ISO8859-1编码再用gbk解码解决。

代码实例如下:
/*
编码:字符串变成字节数组。


解码:字节数组变成字符串。

String-->byte[]; str.getBytes(charsetName);

byte[] -->String: new String(byte[],charsetName);

*/
import java.util.*;
class EncodeDemo
{
public static void main(String[] args)throws Exception
{
String s = "哈哈";

byte[] b1 = s.getBytes("GBK");

System.out.println(Arrays.toString(b1));
String s1 = new String(b1,"utf-8");
System.out.println("s1="+s1);

//对s1进行iso8859-1编码。
byte[] b2 = s1.getBytes("utf-8");
System.out.println(Arrays.toString(b2));

String s2 = new String(b2,"gbk");

System.out.println("s2="+s2);

}
}