IO流(四)
IO 中的其他内容
一.对象的序列化
ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream out)
将对象持久化,保存到硬盘,它必须实现Serializable接口。Serializable接口中没有方法。这样的接口叫做标记接口。其原理就是给实现这个接口的类加一个ID值。这个ID值是根据成员生成的。
如果,我们在将对象写入硬盘后,因为某些原因修改了这个类,此时再去读硬盘上的对象,就会报异常。这就是因为类发生了改变,从而ID值发生了改变。
将对象写在本地硬盘中
public static void writeObj() throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt")); Person p = new Person("lisi",32); oos.writeObject(p); System.out.println("写入对象完成..."); oos.close(); }
读取本地硬盘中的对象
public static void readObj() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt")); Person p1 = (Person)ois.readObject(); System.out.println("读取对象完成..."); System.out.println(p1); ois.close(); }
其中Person类的源码:
import java.io.*; class Person implements Serializable {
//public static final long serialVersionUID = 42L
private String name;//人名 private Integer age;//年龄 //测试ID是否改变 //private String contry; public Person(String name,Integer age) { this.name = name; this.age = age; } public String toString() { return name+":"+age; } }
我们可以测试其ID值是否改变,以及改变对读写对象的影响。
首先,将ID值写死,我们将对象写入文本中,然后改变Person类,并且编译它,再去读去文本中的对象,此时不会报异常。
然后,当我们把写死的ID值注释掉,再进行重复操作。此时,就会报异常。(InvalidClassException)
注:静态的成员是不能被序列化的。(原因:它只能序列化堆内存中的数据,而不能序列化其他的)
注:如果成员被 transient 修饰,也不会被序列化。
二. 管道流
PipedInputStream
PipedOutputStream
管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream
对象读取,并由其他线程将其写入到相应的 PipedOutputStream
。不建议对这两个对象尝试使用 单个线程,因为这样可能死锁线程。
构造函数:PipedInputStream();
PipedInputStream(PipedOutputStream out);
PipedOutputStream();
PipedOutputStream(PipedInputStream in);
如果使用了空构造函数,那么我们要使用connect()方法,将管道输入流和管道输出流连接起来。
示例:
public static void demo2() throws Exception { PipedInputStream pis = new PipedInputStream(); PipedOutputStream pos = new PipedOutputStream(); pis.connect(pos); WritePip wp = new WritePip(pos); ReadPip rp = new ReadPip(pis); new Thread(wp).start(); new Thread(rp).start(); }
WritePip类:
class WritePip implements Runnable { private PipedOutputStream pos; public WritePip(PipedOutputStream pos) { this.pos = pos; } public void run() { try{ System.out.println("开始写入数据..."); Thread.sleep(5000); pos.write("管道已经建立...".getBytes()); System.out.println("数据写入完毕..."); pos.close(); }catch(Exception e){ throw new RuntimeException("数据写入失败..."); } } }
ReadPip 类
class ReadPip implements Runnable { private PipedInputStream pis; public ReadPip(PipedInputStream pis) { this.pis = pis; } public void run() { try{ byte[] buff = new byte[1024]; System.out.println("开始读取数据..."); int len = pis.read(buff); System.out.println("读取数据完毕..."); System.out.println("=================="); System.out.println(new String(buff,0,len)); pis.close(); }catch(Exception e){ throw new RuntimeException("读取输入失败..."); } } }
三.RandomAccessFile
虽然这个也是IO流,但是这个很特殊,它是一个自成派系的,继承于Object,并且它可以写文件,也可以读文件。
随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。
3.1 创建对象。
构造函数:RandomAccessFile(String filename,String mode)
RandomAccessFile(FIle filename,String mode)
注:这里就有一个缺陷,那就是只能操作文件。
mode: 访问文件的模式
"r" 只读。
"rw" 读写。
"rws" 读写,并且文件的内容或元数据同步更新到底层设备。
"rwd" 读写,并且文件的内容同步更新到底层设备。
注:如果,mode 的值为 "r " 时,却调用write方法,就会报异常。
注:当mode 的值为 "rw" ,调用write方法,如果文件不存在就会创建一个文件,如果文件存在,就不会覆盖掉原来的方法。写入数据:
public static void write() throws IOException { RandomAccessFile raf = new RandomAccessFile("raf.txt","rw"); raf.write("历史".getBytes()); raf.close(); }
读取数据:
public static void read() throws IOException { RandomAccessFile raf = new RandomAccessFile("raf.txt","r"); byte[] buff = new byte[4]; int len = raf.read(buff); System.out.println("len = "+len); System.out.println(new String(buff,0,len)); }
注: write(int i) 这个方法,是将 i 的最后8位写进去了,而不是,将整个32位写入数据了。如果要将整个32 位写入数据中,需要使用 writeInt(int i) 方法。
3.2 其中的特殊方法。
设置指针。从什么位置开始写入数据或读取数据。
seek(long n);
skipBytes(int n)
写入数据示例:
public static void write_2() throws IOException
{
RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
raf.seek(4);
raf.write("哈哈".getBytes());
raf.close();
}
读取示例:
public static void read_2() throws IOException { RandomAccessFile raf = new RandomAccessFile("raf.txt","r"); raf.seek(4); byte[] buff = new byte[4]; int len = raf.read(buff); System.out.println(new String(buff,0,len)); }
设置了指针的偏移量之后,就是从偏移后的位置开始读写。但是指针可以随意设置。而另外一个方法skipBytes(int n),只能往后设置。
作用:一般的下载软件的多线程下载原理就是这样的。
四.DataStream (操作基本数据类型的流)
有可以写入或读取基本数据的方法。
特殊:writeUTF(String str); 这个方法是使用修改版的UTF编码。用这个方法写入的数据,只能用与之对应的readUTF 方法读取。
五.ByteArrayStream(操作数组的流)
ByteArrayInputStream包含一个内部缓冲区,在构造的时候需要一个数据源,而且数据源是一个数组,
ByteArrayOutputStream 在构造的时候不需要定义数据目的地,因为其内部封装了一个可变长度的数组。
这两个流都是操作的数组,没有操作系统底层资源的,所以不用 close,就算close了,也可以继续操作这个流。
补充:
源设备: 键盘:System.in 硬盘:FileStream 内存:ArrayStream
目的设备 控制台:System.out 键盘:FileStream 内存:ArrayStream
可以使用 toByteArray() ,toString() 获取数据。
示例:
public static void demo() { ByteArrayInputStream bais = new ByteArrayInputStream("哈哈哈".getBytes()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = 0; while((len=bais.read())!=-1) { baos.write(len); } System.out.println(baos.size()); System.out.println(baos.toString()); }