二进制I/O

一、文件传输基础

1.1 基本概念

  ① bit位只能是1或者0

  ② 一个byte字节是8位:1 byte = 8 bits。字节是计算机表示的基本单位 

  ③ 1MB=1024KB=2(20)B; //括号里的表示2的几次方
    1GB=1024MB=2(30)B。 
    1TB=1024GB=2(40)B 
    1PB=1024TB=2(50)B 
    1EB=1024PB=2(60)B

  ④ 二进制、十进制、八进制、16进制

  ⑤ 字符是文字与符号的总称,包括文字、图形符号、数学符号。一组抽象字符的集合就是字符集。字符集包括:ASCII字符集、GB2312字符集、GBK字符集、BIG5字符集、GB18003字符集、Unicode字符集

  ⑥ byte可以表示2^8 = 256个字符

  ⑦ 以补码表示的二进制编码:补码 = 反码 + 1

1.2 编码

 为什么要编码?

 -- 计算机中存储信息的最小单位是一个字节,所能表示的字符范围是0~255个。

 -- 人类要表示的符号太多,无法用一个字节完全表示。

   -- 要解决这个矛盾,需要一个新的数据结构char,从char到byte必须编码。

编码方式

  ① ASCII码

   ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。

   ② ISO-8859-1

   ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。

  ③ GB2312

  全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

   ④ GBK

  全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。GBK编码中中问占用2个字节,英文占用1个字节

  ⑤ UTF-16

  UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。中英文都占用两个字节。

  ⑥ UTF-8

  UTF-16 统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。UTF-8编码中,中文占用3个字节,英文占用1个字节。

UTF-8 有以下编码规则:

  * 如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。

  * 如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。

  * 如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节

关于编码方式,注意:

  · 当你的字节序列是某种编码的时候,这个时候想把字节序列变成字符串,也需要这种编码方式,否则会出现乱码。

  · 文本文件就是字节序列,可以是任意编码的字节序列,如果在中文机器上,直接创建文本文件,那么该文件只认识ansi编码。

联通乱码

  在win下新建一个记事本文件,输入“联通”,保存后打开,出现乱码。这是因为GBK编码与UTF-8编码产生了编码冲突。

1.3 文本I/O与二进制I/O

  计算机并不区分二进制文件与文本文件,所有的文件都是以二进制形式来存储的,因此本质上讲,所有文件都是二进制文件在文本I/O中自动进行编码和解码:在写入一个字符时,java虚拟机会将统一码转换成文件指定的编码,而在读取字符的时候,将文件指定的编码转化成统一码。

  二进制不需要转化。如果使用二进制I/O向文件写入一个数值,就是将内存中的确切值复制到文件中。使用二进制I/O读取一个字节时,就会从输入流中读取一个字节的数值。

  一般来说,对于文本编辑器或文本输出程序创建的文件,应该使用文本输入来读取,对于java二进制输出程序创建的文件,应该使用二进制输入来读取。

  二进制I/O不需要编码和解码,所以比文本I/O效率高。二进制文件与主机的编码方案无关,因此是可移植的。也就是任何机器都可以读取java程序所创建的二进制文件。

1.4 二进制I/O类  

Java I/O主要包括如下几个层次:

  1. File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。  

  2. InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。

  3. OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。

  4. Reader(文件格式操作):抽象类,基于字符的输入操作。

  5. Writer(文件格式操作):抽象类,基于字符的输出操作。

  6. RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作

字节流:一次读入或读出是8位二进制

字符流:一次读入或读出是16位二进制

字节流和字符流的原理是相同的,只不过处理的单位不同而已。后缀是Stream是字节流,而后缀是Reader,Writer是字符流

节点流:直接与数据源相连,读入或读出。

处理流:与节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。

 

抽象InputStream类定义的字节输入流方法:

public abstract int read() throws IOException    -- 读取输入流中的下一个字节。返回字节的值是一个在0到255之间的整数值。如果到达这个流的末尾而无字节可用,则返回-1.

public int read(byte[] b) throws IOException     -- 从输入流中读取大到b.length字节并存储到缓冲区数组b中,返回读取的字节数,遇到文件结尾返回-1。

public int read(byte[] b, int off, int len) throws IOException  --从输入流中读取len个字节并写入b中,位置从off开始,末尾为off + len - 1。返回写的字节数。

public int available() throws IOException      -- 返回流中可用的字节数。

public void close() throws IOException        -- 关闭流并释放与流相关的系统资源。用户使用完输入流时,调用这个方法。

public void mark(int readlimit) throws IOException  --输入流中标志当前位置。

public boolean markSupported() throws IOException  --测试流是否支持标志和复位。

public void reset() throws IOException      -- 重定位到上次输入流中调用的位置。

public long skip(long n) throws IOException  -- 跳过输入流中n个字节,返回跳过的字节数,遇到文件结尾返回-1。

抽象OutputStream类定义的字节输出流方法:

  与InputStream是相对应,OutputStream提供了3个write方法来做数据的输出:
  * public void write(byte b[ ]) throws IOException:将参数b中的字节写到输出流。 
  * public void write(byte b[ ], int off, int len) throws IOException:将参数b的从偏移量off开始的len个字节写到输出流。 
  * public abstract void write(int b) throws IOException:先将int转换为byte类型,把低字节写入到输出流中。 
  * public void flush( ) throws IOException: 将数据缓冲区中数据全部输出,并清空缓冲区。 
  * public void close( ) throws IOException: 关闭输出流并释放与流相关的系统资源。 

  二进制I/O类中的所有方法都声明为抛出java.io.IOException或java.io.IOException的子类。

1.4 FileInputStream类和FileOutputStream类

  FileInputStream类和FileOutputStream类:向文件读取/写入字节,其所有的方法都是从InputStream类和OutputStream类继承的,并没有新引入的方法。

  FileInputStream类的构造方法:

  FileInputStream(file: File)  从一个File对象创建一个FileInputStream

    FileInputStream(filename : String)  从一个文件名创建一个FileInputStream

  如果试图为一个不存在的文件创建从一个File对象创建一个FileInputStream对象,将会发生java.io.File NotFoundException。

  FileOutputStream类的构造方法:

  FileOutputStream(file:File)  从一个Flie对象创建一个FileOutputStream

  FileOutputStream(filename:String)  从一个文件名创建一个FileOutputStream

  FileOutputStream(file:File, append:boolean)  如果append为true,追加数据到现有的文件

  FileOutputStream(filename:String, append:boolean)  如果append为true,追加数据到现有的文件

  如果这个文件不存在,就会创建一个新的文件。如果这个文件已经存在,前面两个构造方法将会删除文件的当前内容。

  几乎所有的I/O类中的方法都会抛出java.io.IOException。因此在方法中声明会抛出java.io.IOException异常,或者将代码放到try- catch块中。

 1 //在方法中声明异常
 2 public static void main(String[] args) throws IOException{
 3       ......
 4 }
 5 
 6 
 7 //使用try-catch块
 8 public static void main(String[] args){
 9    try{
10     ......
11    }
12    catch(IOException ex){
13     ex.printStackTrace();
14    }
15 }
 1 package demo;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileOutputStream;
 5 import java.io.IOException;
 6 
 7 /**
 8  * Created by luts on 2015/12/6.
 9  */
10 public class TestFileStream {
11     public static void main(String[] args) throws IOException{
12 
13         //创建一个FileOutputStream对象
14         FileOutputStream outputStream = new FileOutputStream("temp.dat");  //temp.dat是二进制文件
15 
16         for (int i = 1; i <= 10; i++)
17             outputStream.write(i);
18 
19         outputStream.close(); //关闭文件
20 
21         //创建一个FileInputStream
22         FileInputStream inputStream = new FileInputStream("temp.dat");
23 
24         //读取数据
25         int value;
26         while ((value = inputStream.read()) != -1)
27             System.out.print(value + " ");
28 
29         inputStream.close();
30     }
31 }
1 2 3 4 5 6 7 8 9 10 

  当不需要流时,使用close()方法将其关闭。不关闭流可能会在输出文件中造成数据受损,或者导致其他的程序设计错误。

  FileInputStream类的实例可以作为参数去构造一个Scanner对象,而FileOutputStream类的实例可以作为参数构建一个PrinterWriter对象。可以使用 new PrinterWriter(new FileOutputStream("temp.txt", true)); 创建一个PrinterWriter对象向文件追加文本。

1.5 FileInputStream类和FileOutputStream类

  过滤数据流(filter stream)是为某种目的过滤字节的数据流。基本字节输入流提供的读取方法read只能用来读取字节。使用过滤器类就可以读取整数值、双精度值或字符串,而不是字节或字符。FileInputStream类和FileOutputStream类是过滤数据的基类。可以使用DataInputStream类和DataOutputStream类来过滤字节。

 FileInputStream的各种类型

class   功能

构造函数引数

如何使用它

DataInputStream 和DataInputStream协同运用,你可以因此以一种可携的方式,从stream读取基本型别数据(int/char/long等)

InputStream

含有一份完整接口,让得以读取各种基本型别的数据

BufferedInputStream 运用它便可避免“每次想要取得数据时都得进行实际读取动作”。它所代表的意义正是“使用缓冲区”

InputStream

可指定缓冲区大小就其本身而言,并不提供额外接口,只是用来申请一份可用的缓冲区。

LineNumberInputStream 它会记录input stream内的行数,然后可以调用getLineNumber()和setLineNumber(int)

InputStream

为数据加上编号

PushbackInputStream 具备一个“one byte pushback buffer”,所以可以将读到的最后一个byte回送回去。

InputStream

通常用于编译器的扫描器上。它之所以会被放进来,可能是因为编译器需要。你可能永远都不会用到它。

DataInputStream类

  DataInputStream从数据流中读取字节,并且将它们转换成正确的基本数据类型值或字符串。

  DataInputStream扩展于FileInputStream类,并实现DataInput接口。

构造方法:DataInputStream(InputStream in);

主要方法:

  int read(byte[] b);//从输入流中读取一定的字节,存放到缓冲数组b中。返回缓冲区中的总字节数。

  int read(byte[] buf,int off,int len);//从输入流中一次读入len个字节存放在字节数组中的偏移off个字节及后面位置。

  String readUTF();//读入一个已使用UTF-8修改版格式编码的字符串

  String readLine(); //从输入读取一行字符

  boolean readBoolean; //从输入流读取一个布尔值

  int readShort();  //从输入流读取一个short值

  int readInt();  //从输入流读取一个int值

  int readLong();  //从输入流读取一个long值

  int readFloat();  //从输入流读取一个float值

  int readDouble();  //从输入流读取一个double值

  byte readByte(); //从输入流读取一个字节

  char readChar();  //从输入流读取一个字符

 

DataOutputStream类

  DataOutputStream将基本数据类中的值转换成字节,并将字节输出到数据流。

  DataOutputStream扩展于FileOutputStream类,并实现DataOutput接口

 

构造函数:DataOutputStream(OutputStream out);//创建一个将数据写入指定输出流out的数据输出流。

字段摘要:int written;//到目前为止写入数据流的字节数。

主要方法:

  void write(byte[] b,int off,int len);//将byte数组off角标开始的len个字节写到OutputStream 输出流对象中。

  void write(int b);//将指定字节的最低8位写入基础输出流。

  void writeBoolean(boolean b);//将一个boolean值以1-byte形式写入基本输出流。

  void writeByte(int v);//将一个byte值以1-byte值形式写入到基本输出流中。

  void writeBytes(String s);//将字符串s中每个字符统一码中的低字节写到输出流中

  void writeChar(int v);//将一个char值以2-byte形式写入到基本输出流中。先写入高字节。

  void writeInt(int v);//将一个int值以4-byte值形式写入到输出流中先写高字节。

  void writeUTF(String str);//以机器无关的的方式用UTF-8修改版将一个字符串写到基本输出流。该方法先用writeShort写入两个字节表示后面的字节数。

  int size();//返回written的当前值。

 1 package demo;
 2 
 3 import java.io.*;
 4 
 5 /**
 6  * Created by luts on 2015/12/7.
 7  */
 8 public class TestDataStream {
 9     public static void main(String[]args) throws IOException{
10 
11         DataOutputStream output = new DataOutputStream(new FileOutputStream("temp.dat"));   //为文件temp.dat创建一个DataOutputStream对象
12 
13         //写入学生姓名跟分数
14         output.writeUTF("Jhon");
15         output.writeDouble(88.5);
16         output.writeUTF("Lucy");
17         output.writeDouble(90.2);
18         output.writeUTF("Jack");
19         output.writeDouble(93.4);
20 
21         output.close(); //关闭输出流
22 
23         DataInputStream input = new DataInputStream(new FileInputStream("temp.dat"));   //创建一个DataInputStream对象
24         System.out.println(input.readUTF() + " " + input.readDouble()); //读取学生姓名跟分数
25         System.out.println(input.readUTF() + " " + input.readDouble());
26         System.out.println(input.readUTF() + " " + input.readDouble());
27     }
28 }
Jhon 88.5
Lucy 90.2
Jack 93.4

    应该按存储的顺序和格式读取文件中的数据。上例中的学生姓名是用writeUTF方法以UTF-8格式写入的,那么读取时就应该使用readUTF()。

  如果到达InputStream的末尾之后还从数据中读取数据,会发生EOFException异常,这个异常可以用来检测是否到达文件的末尾。

 1 package demo;
 2 
 3 import java.io.*;
 4 
 5 /**
 6  * Created by luts on 2015/12/8.
 7  */
 8 public class TestDataStream {
 9     public static void main(String[]args){
10         try {
11 
12             DataOutputStream output = new DataOutputStream(new FileOutputStream("temp.dat"));   //为文件temp.dat创建一个DataOutputStream对象
13 
14             output.writeDouble(88.5);
15             output.writeDouble(90.2);
16             output.writeDouble(93.4);
17 
18             output.close(); //关闭输出流
19 
20             DataInputStream input = new DataInputStream(new FileInputStream("temp.dat"));   //创建一个DataInputStream对象
21             while (true) {
22                 System.out.println(input.readDouble());
23             }
24         }
25         catch (EOFException ex){
26             System.out.println("All data have been read");
27         }
28         catch (IOException ex){
29             ex.printStackTrace();
30         }
31     }
32 }
88.5
90.2
93.4
All data have been read

1.6 BufferedInputStream类和BufferedOutputStream类

   BufferedInputStream类和BufferedOutputStream类:通过减少读写次数来提高输入和输出的速度。

   BufferedInputStream类和BufferedOutputStream类所有的方法都是从InputStream类和OutputStream类继承来的。

   BufferedInputStream类和BufferedOutputStream类为存储自己在流中添加一个缓冲区,以提高处理效率。

   

(1)BufferedInputStream 

   BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如, 在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。

   

java.io.BufferedInputStream类继承于FilterInputStream-->InputStream,声明:

  public class BufferedInputStream  extends FilterInputStream

为什么需要缓冲呢?--->缓冲中的数据实际上是保存在内存中,而原始数据可能是保存在硬盘或NandFlash等存储介质中;而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。 


那为什么不干脆一次性将全部数据都读取到缓冲中呢?--->第一,读取全部的数据所需要的时间可能会很长。第二,内存价格很贵,容量不像硬盘那么大。

BufferedInputStream 构造函数:

  BufferedInputStream(InputStream in)  ---> 从一个InputStream对象创建一个BufferedInputStream。

  BufferedInputStream(InputStream in, int size) --->从一个带缓冲区大小的InputStream对象创建一个BufferedInputStream

 

BufferedInputStream 的方法:

  int available() --->从这个输入流中可通过一个方法的下一次调用阻塞该输入流返回可以读取(或跳过)的字节数的估计值。
  void close() --->此方法关闭此输入流并释放与该流关联的所有系统资源。
  synchronized void mark(int readlimit)
  boolean markSupported() --->此输入流是否支持mark和reset方法的方法测试。
  int read() --->此方法读取从输入流中的下一个数据字节。
  int read(byte[] buffer, int offset, int byteCount) --->从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。
  void reset() --->此方法重新定位这个流,以当时的mark方法最后调用这个输入流中的位置
    long skip(long byteCount) ---> 此方法跳过并丢弃n个字节从此输入流中的数据。

 

 

(2)BufferedOutputStream

  BufferedOutputStream类实现一个缓冲输出流。通过建立这样一个输出流,应用程序可以写字节到底层输出流,而不必然导致调用底层的系统写入的每个字节。BufferedOutputStream类方法继承:-->FilterOutputStream--->OutputStream

Java.io.BufferedOutputStream类的声明:public class BufferedOutputStream extends FilterOutputStream

构造函数:

构造函数  描述
BufferedOutputStream(OutputStream out) 
这将创建一个新的缓冲输出流将数据写入到指定的基础输出流。
BufferedOutputStream(OutputStream out, int size) 
这将创建一个新的缓冲输出流的数据与指定的缓冲区大小写入指定的基础输出流。
 

 类方法:

  void flush()--->刷新缓存区

  void write(byte[]b, int offset, int length)---->从指定的字节数组开始在这个缓存的输出流关闭写入length字节。

  void write(int b)--->写入指定的字节到此缓存的输出流。

(3)复制文件

  

 1 package CopyTest;
 2 
 3 import java.io.*;
 4 
 5 /**
 6  * 文件复制测试
 7  * Created by luts on 2015/12/8.
 8  */
 9 public class CopyTest {
10     public static void main(String[]args) throws IOException{
11         if (args.length != 2){
12             System.out.println("Usage: java CopyTest sourceFile targeFile");
13             System.exit(0);
14         }
15 
16         File sourceFile = new File(args[0]);
17         if (!sourceFile.exists()){
18             System.out.println("Source file " + sourceFile + "not exist");
19             System.exit(0);
20         }
21 
22         File targeFile = new File(args[1]);
23         if (targeFile.exists()){
24             System.out.println("Targe file" + targeFile + " already exist");
25             System.exit(0);
26         }
27 
28         BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(sourceFile));
29 
30         BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(targeFile));
31 
32         int r ;
33         int numberOfBytesCopied = 0;
34         while ((r = inputStream.read()) != -1){
35             outputStream.write((byte) r);
36             numberOfBytesCopied++;
37         }
38 
39         inputStream.close();
40         outputStream.close();
41 
42         System.out.println(numberOfBytesCopied + "bytes copied");
43     }
44 }

 在以命令行的方式运行java程序的时候,发现一个问题:

声明了包的类Java命令找不到主类?

  一个声明了包的类,在包目录(包含.class文件)下使用java命令为什么会出现“找不到主类”的错误?Java虚拟机在加载一个类时不是会在当前目录下寻找吗?还有就是,为什么在包文件夹所在目录下使用“java 包名.类名 ”命令却可以正常执行。
 
 

 1.7 对象的输入/输出

  DataInputStream类和DataOutputStream类可以实现基本数据类型与字符串的输入和输出。ObjectInputStream类和ObjectOutputStream类不仅可以实现基本数据类型与字符串的输入和输出外,还可以实现对象的输入或输出。后者包含了前者所有的功能,所有完全可以使用ObjectInputStream类和ObjectOutputStream类替代DataInputStream类和DataOutputStream类。

  ObjectInputStream类扩展于InputStream类,实现接口ObjectInput和ObjectInputStreamConstants. ObjectInput是DataInput的子接口。ObjectInputStreamConstants包含支持ObjectInputStream类和ObjectOutputStream类所用的常量。

  ObjectInputStream类读取对象实例:

1 ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
2 MyClass object = (MyClass) input.readObject(); //etc.
3 input.close();

在这个例子中,你读取的对象必须是MyClass的一个实例,并且必须事先通过ObjectOutputStream序列化到 “object.data”文件中。

  ObjectOutputStream扩展于OutputStream类,并实现ObjectOutput与ObjectOutputStreamConstants类。ObjectOutput是DataOutput的子接口。

ObjectOutputStream能够让你把对象写入到输出流中,而不需要每次写入一个字节。你可以把OutputStream包装到ObjectOutputStream中,然后就可以把对象写入到该输出流中了。代码如下:

ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("object.data"));
MyClass object = new MyClass();  
output.writeObject(object); //etc.
output.close();

1.8 序列化接口Serializable

  并不是所有的对象都可以写到输出流的。可以写到输出流中的对象称为可序列化的(serializable),并实现了可序列化serializable接口。serializable接口是一个非标记性的接口

  如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(注意:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而反序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。

       对象序列化过程不仅仅保存单个对象,还能追踪对象内所包含的所有引用,并保存那些对象(这些对象也需实现了Serializable接口)。

  · 试图存储一个不支持Serializable接口的对象会会引起一个NotSerializableException异常

  · 当存储一个可序列化的对象的时候,会对该对象进行编码。编码包括类名、类的签名、对象实例变量的值以及从初始对象引用的任何其他对象的闭包,但是不存储对象静态变量的值。

  · 如果一个对手是Serializable的实例,但是其包含的都是非序列化的数据域,那么这个对象也是不可序列化的。但是可以给数据域添加一个transient关键字,告诉java虚拟机将对象写入对象流时忽略这些数据域。

  · 如果一个对象不止一次写入对象流,对象流不会存储多个对象副本。第一次写入一个对象的时候,就好为它创建一个序列号,java虚拟机将对象的所有内容和序列号一起写入对象流中。如果写入相同的对象,就只存储序列号

1.9  随机访问文件

  RandomAccessFile类:允许在文件内的随机位置上进行读写,该类实现了DataInput和DataOutput接口。使用RandomAccessFile类,可以跳转到文件的任意位置读写数据。程序可以在随机文件中插入数据,而不会破坏该文件的其他数据。此外,程序也可以更新或删除先前存储的数据,而不用重写整个文件。

  RandomAccessFile类是Object类的直接子类,包含两个主要的构造方法用来创 建RandomAccessFile 的对象:

  

构造方法功能描述
public RandomAccessFile(String name, String mode) 指定随机文件流对象所对应的文件名,以 mode 表示对文件的访问模式
public RandomAccessFile (File file, String mode) 以 file 指定随机文件流对象所对应的文件名,以 mode 表示访问模式

  需要注意的是,mode 表示所创建的随机读写文件的操作状态,其取值包括:

  • r:表示以只读方式打开文件。
  • rw:表示以读写方式打开文件,使用该模式只用一个对象即可同时实现读写操作。
1 RandomAccessFile raf = new RandomAccessFile ("test.dat", "rw");    //如果这个文件test.dat存在,则创建raf以便访问这个文件;如果test.dat不存在,则创建一个名为test.dat的新文件,在创建创建raf来访问这个文件

  

  随机访问文件是由字节序列组成。文件指针(file pointer):定位这些字节中某个字节的位置。文件的读写操作就是在文件指针所指的位置进行的。打开文件时,文件指针置于文件的开始位置。进行读取操作后,文件指针会向前移动到下一个数据项。如readInt()方法读取一个int数据,文件指针向前移动4个字节。seek()方法将文件指针移动到指定的位置

  RandowAccessFile 的常用方法:

RandowAccessFile 的常用方法
方法功能描述
long length() 返回文件长度
void seek(long pos) 移动文件位置指示器,pos 指定从文件开头的偏离字节数
int skipBytes(int n) 跳过 n 个字节,返回数为实际跳过的字节数
int read() 从文件中读取一个字节,字节的高 24 位为 0,若遇到文件结尾,返回-1
final byte readByte() 从文件中读取带符号的字节值
final char readChar() 从文件中读取一个 Unicode 字符
final void writeChar(inte c)

写入一个字符,两个字节

void close()

关闭流并释放和这个流相关的资源

long getFilePointer()

返回偏移量,以字节为单位,从文件开始位置到下一个read或write的地方

void setLength(long newLength)

设置文件的新长度

 1 package demo;
 2 
 3 import java.io.IOException;
 4 import java.io.RandomAccessFile;
 5 
 6 /**
 7  * 测试RandomAccessFile
 8  * Created by luts on 2015/12/9.
 9  */
10 public class TestRandomAccessFile {
11     public static void main(String[] args) throws IOException{
12         RandomAccessFile inout = new RandomAccessFile("inout.dat", "rw");
13 
14         inout.setLength(0);
15 
16         for (int i = 0; i < 200; i++){
17             inout.writeInt(i);
18         }
19 
20         System.out.println("Current file length is: " + inout.length());
21 
22         inout.seek(0);
23         System.out.println("The first number is " + inout.readInt());
24 
25         inout.seek(1*4);
26         System.out.println("The second number is " + inout.readInt());
27 
28         inout.seek(9*4);
29         System.out.println("The tenth number is " + inout.readInt());
30 
31         inout.writeInt(555); //修改第11个元素
32         
33 
34         inout.seek(inout.length()); //文件指针移到文件结尾处
35         inout.writeInt(999);
36 
37         inout.seek(10*4);
38         System.out.println("The eleventh number is " + inout.readInt());
39 
40         System.out.println("New file length is: " + inout.length());
41 
42         inout.close();
43     }
44 }
Current file length is: 800
The first number is 0
The second number is 1
The tenth number is 9
The eleventh number is 555
New file length is: 804

小结:

  · I/O 类可以分为文本I/O和二进制I/O。文本I/O将数据解释成字符序列,二进制I/O将数据解释成原始的二进制数值。文本在文件中如何存储依赖于文件的编码方式。java自动完成对文本I/O的编码和解码。

  · InputStream类和OutputStream类是所有二进制I/O类的根类。FileInputStream类和FileOutputStream类用于对文件实现二进制输入和输出。BufferInputStream类和BufferOutputStream类可以包装任何一个二进制输入/输出流以提高其性能。DataInputStream类和DataOutputStream类可以用来读写基本数据和字符串。

  · ObjectInputStream类和ObjectOutputStream类除了可以读写基本类型数据值和字符串外,还可以读写对象。为实现对象的可序列化,对象的定义类必须实现java.io.Serializable标记性接口。

  · RandowAccessFile类允许对文件进行随机读写。可以打开模式为“r”(只读)或“rw”(读写)的文件。RandowAccessFile类实现了DataInput和DataOutput接口,所以RandowAccessFile中的许多方法与DataInputStream类和DataOutputStream类中的方法一样。

http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/#authorN1001C

http://www.ibm.com/developerworks/cn/java/j-lo-javaio/

http://blog.csdn.net/hguisu/article/details/7418161

原文地址:https://www.cnblogs.com/luts/p/5032282.html