InputStream和OutputStream构成了输入/输出类层次结构的基础。用于按字节进行读写。而与之处在同一等级的Reader/Writer同样作为抽象类定义了用于对字符进行读取的类层次结构,是基于两个字节的Unicode码元的读取。

  inputStream的抽象方法abstract int read(),每次读入一个字节并返回一个int值,在遇到文件尾的时候返回-1.所以可以用其子类FileInputStream进行文件字符读取:

FileInputStream f = new FileInputStream("C:\\Users\\Loki\\Desktop\\io.txt");
int i;
while((i=f.read()) != -1){
System.out.print((char)i);
}

  同样OutputStream的抽象方法abstract void write()每次写入一个字节,比如用其子类FileOutputStream进行写入:

FileOutputStream out = new FileOutputStream("C:\\Users\\Loki\\Desktop\\output.txt");
char[] chars = {'a','b','c','d','e'};    //java中char是两个字节表示。
for(int j = 0; j < chars.length; j++){ //如果用write写入int类型变量,则低8位被写入,高24位被忽略。
out.write(chars[j]);
}

  read和write方法在执行时都将阻塞,直至字节被读入或写出。此外可以通过available()方法得知可读入字节数,也可以以数组的形式进行读取。

  同样的,InputStreamReader类也是从文件中读取字节,并以int的形式返回,遇到文件尾则返回-1。但是它与上面的InputStream有什么不同呢?不同点就在于InputStream没有指定解析字节的编码方式,只是纯粹地读取一个字节的内容并原原本本地返回。而InputStreamReader类的构造器是以输入流为参数并且可以指定编码方式,比如可以以InputStream实例对象为参数并指定“GBK”编码方式,以“GBK”编码方式解析从InputStream输入流返回的字节,解析完了之后再返回对应字符的Unicode对应的代码值(代码点)。java核心技术上说  “InputStreamReader 类将包含字节(用某种字符编码方式表示的字符)的输入流转换为转换为可以产生Unicode码元的读入器”。以下是实例代码和输出,文件中的文字是“小岳岳是河南人”,第一行是InputStream的输出,第二行是InputStreamReader的输出:

FileInputStream f2 = new FileInputStream("C:\\Users\\Loki\\Desktop\\io.txt");
InputStreamReader inputStreamReader = new InputStreamReader(f2,"gbk");
while((i = inputStreamReader.read()) != -1)
System.out.print((char)i); СÔÀÔÀÊǺÓÄÏÈË
小岳岳是河南人
Process finished with exit code 0

根据以上描述,相应的OutpStreamReader类在输出的时候要指定一个输出流,并且可选地选定编码方式。

  当然,如果要是像文件写入文本的话,最方便的还是PrintWriter类,对于这个类的使用是跟System.out是一样的,它把数字、字符、boolean值以“utf-8"的编码格式编码成的字符串的形式输出到指定位置。关于编码格式,可以通过上面的InPuStreamReader验证。以下例程:

 PrintWriter printWriter = new PrintWriter("C:\\Users\\Loki\\Desktop\\output.txt");
printWriter.println();
printWriter.println("今天天气不错");
printWriter.println(24);
printWriter.println(true);
printWriter.println('c');
printWriter.flush(); 文档内容: 今天天气不错
24
true
c

  对应文本的读入之前是用BufferedReader,不过建议用Scanner。在下面的例程中,刚开始没有用System.out把in.nextLine()输出,导致程序一直没有输出。所以以后看Api的时候一定要好好看函数的返回类型:

Scanner in = new Scanner(System.in);
while(in.hasNextLine()) //对应于单个字符有.hasNext()和.next()方法。具体查看Api
{
System.out.println(in.nextLine());
}

字符集中的编码方式可以在java中的unicode字符和编码而成的字节之间进行转换。获取字符集对象的方式为Charset.forName("utf-8");这与反射中通过名字构造类实例一样(Class cl = Class.forName("java.util.Scanner");)。

  下面是如何编码java字符串:

    String str = ...;

    ByteBuffer buffer = Charset.forName("...");

    byte[] bytes = buffer.array();

  解码字节序列:

    byte[] bytes = ...;

    ByteBuffer bbuf = ByteBuffer.wrap(bytes,offset,length);  

    CharBuffer cbuf = cset.decode(bbuf);

    String str = cbuf.toString();

  DataOutput接口定义了一些方法,这些方法以二进制的格式写数组、字符、boolean值。比如writeInt总是把一个整数写出为4字节的二进制整数量。而DataInput在输入的时候需要用一个输入流初始化。

  随机访问文件类RandomAccessFile类的思想与C语言中的文件读取思想是一致的,可以选择只读(r)或可读可写(rw),还可以获得获得文件指针,也能够设置文件指针的位置。它同时实现了DataOutput和DateInput的方法,所以可以用这两个接口中的方法对文件进行读取。

  读取Zip文档的时候要用到ZipInputStream类,以下是两种读取方法,但是注意,Scanner方法要比直接用read方法的效果要好:

ZipInputStream zin = new ZipInputStream(new FileInputStream("C:\\Users\\Loki\\Desktop\\Desktop.zip"));
ZipEntry entry;
while((entry = zin.getNextEntry()) != null){
Scanner scanner = new Scanner(zin);
while (scanner.hasNextLine())
System.out.println(scanner.nextLine());
zin.closeEntry();
} /*
while((entry = zin.getNextEntry()) != null){
int i;
while((i = zin.read()) != -1)
System.out.print((char)i);
zin.closeEntry();
}
*/
zin.close();

序列化:

  序列化是一种传递对象的方式,以序列号代替内存地址标记不同的对象。为什么要用序列号而不是内存地址呢,因为虚拟机会对内存机进行整理从而改变对象的内存地址,所以用内存地址标记一个对象是不可靠的。

  先对序列化有一个直观的认识。序列化是将对象写出到流中、之后再将其读回。为了写出和读回,我们需要用到ObjectInputStream和ObjectOutputStream类,顾名思义,对象输入和输出流。如下程序所示:

public static void main(String[] args) throws IOException,ClassNotFoundException{

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Loki\\Desktop\\io.txt"));
Student li = new Student("LiSi", 24,2);
Student zhang = new Student("ZhangSan", 24,3);
out.writeObject(li);
out.writeObject(zhang);
out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("C:\\Users\\Loki\\Desktop\\io.txt"));
Student copyLi = (Student)in.readObject(); //要抛出ClassNoteFountException
Student copyZhang = (Student)in.readObject(); System.out.println(copyLi.getName()); //注意这里面的copyLi和li是数据域相同的不同的对象。
System.out.println(copyZhang.getName()); }
} class Student implements Serializable{
private String name;
private int age;
private int grade; public Student(){ }
public Student(String aName, int aAge, int aGrade){
name = aName;
age = aAge;
grade = aGrade;
}
...
...
...
}

需要注意的有两点,其一:要抛出ClassNotFountException异常;其二:要实现Serializable接口,这是一个标记接口,没有任何方法。

也可以序列化基本类型,用到的方法是writeInt这些方法,因为ObjectInputStream和ObjectOutputStream类实现了DataInput和DataOutput接口。

因为每个对象都是用序列号保存的,所以这种机制被称为序列化,以下是其算法:

  1. 遇到的每一个对象都关联一个序列号;

  2. 对于每一个对象,第一次遇到时将其对象保存到流中(保存的是域以及类信息);

  3. 如果遇到的对象已经被保存过,那么只写"与之前保存过的序列号为x的对象相同”;

  4. 读取对象的时候过程相反。

  如果类中有不可序列化的域,则应该标记为transient,在序列化时会被跳过。为了存储这些不可序列化的域,可序列化的类可以定义具有以下签名的私有方法:

    

private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException;
private void writeObject(ObjectOutputStream out) throws IOException;

之后数据域就不会再被自动序列化,而是被用反射机制调用来序列化对象,在这种私有方法的内部实现类似于下面所示:

private void writeObject(ObjectOutputStream out){
out.defaultWriteObject();
out.writeDouble(x);
out.writeDouble(y);
}

  其中通过defaultWriteObject方法序列化可被序列化的域,然后用标砖的DataOutput调用写出不可被序列化的域。

外部化(Externalizable):

  除了使用序列化机制来保存和恢复对象数据之外,类还可以定义自己的机制,那就是外部化。外部话可以弥补序列化不能记录超类中数据的缺点。使用外部化时类要实现Exteinalizable接口,并实现readExternal和writeExternal方法, 这两个方法是公共方法。

void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

在方法的具体实现中,可以通过调用DataOutput和DataInput的标准方法来输入和输出数据。

读写文件:

  Files类可以使得对普通的文件的读写变得快捷。这个类主要用于处理中等长度文本文件,在读取的时候它可以全部按字节读取或者当作行序列读取,有如下方法:

byte[] bytes = Files.readAllBytes(path);
String content = new String(bytes, charset); //将上面读出的byte以特定的编码方式转换为字符串
//也可以把文件当作行序列读出
List<String> lines = Files.readAllLines(path, charset); //写出到文件
Files.write(path, content.getBytes(charset));
//追加
Files.write(path, content.getBytes(charset), StandardOpenOption.APPEND);
//将一个行的集合写入到文件中
Files.write(path, lines);

  对于长文本文件和二进制文件,还是需要用传统的读入器/写出器,Files类的静态方法提供一些方法来获取读入/写出器,避免了上述的繁琐方法:

InputStream in = Files.newInputStream(path);
OutputStream out = Files.newOutputStream(path); Reader in = Files.newBufferedReader(path, charset);
Writer out = Files.newBufferedWriter(path, charset);

  Files类提供了移动、删除、复制文件的一些方法。

参考:

  http://blog.163.com/fan_yishan/blog/static/47692213200821595727205/

  java核心技术卷二

最新文章

  1. Android 生成LayoutInflater的三种方式
  2. jQuery中find和filter的区别
  3. EmguCV+Win7+Visual C# 2012 配置
  4. 二十一、【.Net开源框架】EFW框架Web前端开发之目录结构和使用FireBug调试方法
  5. 解决Unable to locate Kerberos realm
  6. INDY idhttp Post用法
  7. .Net webservice动态调用
  8. C语言碰到的一元二次方程
  9. android软件开发之webView.addJavascriptInterface循环渐进【一】
  10. 创建maven项目出现的问题
  11. MATLAB 单变量函数一阶及N阶求导
  12. Tomcat localhost 8080打不开
  13. 关于loadrunner使用web_add_header添加HTTP信息头(比如Content-Type,token等)和使用
  14. js 快速将字符串数组 转化为 数字数组(互换)
  15. LeetCode刷题:第七题 整数翻转 第九题 回文数
  16. Unity打开外部程序exe/Bat文件方案
  17. python 保存对象文件
  18. Java知多少(16)StringBuffer与StringBuider
  19. 让vim不要自动添加新的注释行
  20. Xshell设置网络设备自动登录

热门文章

  1. Django学习(一)---基本配置及创建项目、应用
  2. JavaScript一个猜数字游戏
  3. jsp,2016.11.28
  4. Carbondata源码系列(一)文件生成过程
  5. POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
  6. js获取客户端MAC地址
  7. Mybaits简诉
  8. linux服务器部署jar包以及shell脚本的书写
  9. 【css】盒子模型 之 弹性盒模型
  10. Android学习笔记-绘制圆形ImageView实例