第十八章:Java I/O系统

File

File类,文件路径,既代表了一个特定文件的名称,有能代表一个目录下的一组文件的名称(list()方法,返回文件名的字符串数组)。

  1. 目录过滤器 java.io.FilenameFilter
    过滤器包含有accept()方法,返回一个布尔值,表示是否接受该文件。file.list(filenameFilter)获取符合过滤器的文件名。
  2. 目录实用工具
public final class Directory{
  //过滤目录下的文件
  public static File[] local(File dir, final String regex){
    return dir.listFiles(new FilenameFilter(){
      private Pattern pattern = Pattern.compile(regex);
      public boolean accept(File dir, String name){
        return pattern.matcher(new File(name).getName()).matches();
      }
    });
  }
  //重载
  public static File[] local(String path, final String regex){
    return local(new File(path), regex);
  }
}

//
public static class TreeInfo implements Iterable{
  public List files = new ArrayList();
  public List dirs = new ArrayList();
  //默认迭代器是files的迭代器
  public Iterator iterator(){
    return files.iterator();
  }
  
  void addAll(TreeInfo other){
    files.addAll(other.files);
    dirs.addAll(other.dirs);
  }
  
  //walk():递归的查找某个目录下的所有文件
  public static TreeInfo walk(File start, String regex){
      return recurseDirs(start, regex);
  }
  public static TreeInfo walk(File start){
      return recurseDirs(start,".*");
  }
  static TreeInfo recurseDirs(File startDir, String regex){
    TreeInfo result = new TreeInfo();
    for(File item : startDir.listFiles()){
      if(item.isDirectory()){
        result.dirs.add(item);
        result.addAll(recurseDirs(item, regex));
      } else{
         if(item.getName().matches(regex))result.files.add(item);
      }
    }
    return result;
  }
}

File对象有很多作用,还可以创建新的目录和删除文件等等,可以查看api全面了解一下。

输入和输出

输入:自Inputstream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或者字节数组。
输出:自OutputStream或Writer派生而来的类都含有名为write()的基本方法,用于写单个字节或字节数组。
1.InputStream类型
数据源:字节数组、String对象、文件、pipe、由其他种类的流组成的序列,其他数据源,比如Internet连接。不同数据源有相应的子类。

功能 使用
ByteArrayInputStream 允许将内存的缓冲区当做InnputStream使用 缓冲区,字节从中取出。一种数据源,将其与FilterInputStream对象相连以提供有用接口
StringBufferInputStream 将String换成InputStream 字符串,底层实现实际使用StringBuffer。一种数据源,将其与FilterInputStream对象相连以提供有用接口
FileInputStream 用于从文件中读取信息 字符串表示文件名、文件或FileDescriptor对象。一种数据源,将其与FilterInputStream对象相连以提供有用接口
PipedInputStream 产生用于写入相关PipedOutputStream的数据,实现管道化 PipedOutputStream作为多线程中数据源;将其与FilterInputStream对象相连以提供有用接口
SequenceInputStream 将两个或多个InputStream对象转换成单一InputStream 多个Input对象的容器Enumertation;一种数据源,将其与FilterInputStream对象相连以提供有用接口
FilterInputStream 抽象类,作为“装饰器”的接口,装饰器为其他InputStream提供有用功能 --见下表
  1. OutputStream类型
功能 使用
ByteArrayOutputStream 在内存中创建缓冲区。所有送往“流”的数据都要放置在此缓冲器 缓冲区初始化尺寸,用于指定数据的目的地。将其与FilterOutputStream对象相连以提供有用接口
FileOutputStream 用于将信息写入文件 字符串表示文件名、文件或FileDescriptor对象。将其与FilterOutputStream对象相连以提供有用接口
PipedOutputStream 任何写入其中的信息都会自动作为PipedOutputStream的输出,实现管道化 PipedInputStream指定用于多线程数据的目的地;将其与FilterOutputStream对象相连以提供有用接口
FilterOutputStream 抽象类,作为“装饰器”的接口,装饰器为其他OutputStream提供有用功能 --见下表

添加属性和有用的接口

因为Java I/O类库需要多种不同功能的组合,所以需要使用装饰器模式。装饰器必须具有和它所装饰对象相同的接口。FilterXXXX两个输入输出流就是装饰器的必要条件。

  1. FIlterInputStream类型(FilterReader):
功能 使用
DataInputStream 读取不同的基本类型数据以及String对象 所有方法都以“read”开头,如readByte()。。。
BufferedInputStream 代表使用缓冲区,防止每次读取时都得进行实际写操作 可以指定缓冲区大小,本质上不提供接口,只不过是向进程中添加缓冲区所必须的,与接口对象搭配
LineNumberInputStream(已弃用)(LineNumberReader) 跟踪输入流的行号 调用getLineNumber()和setLineNumber()
PushbackInputStream 具有“能弹出一个字节的缓冲区”,因此可以将督导的最后一个字符回退 通常作为编译器的扫描器,java编译器需要
  1. FilterOutPutStream(FilterWriter抽象类,没有子类)向OutputStream写入
功能 使用
DataOutputStream 与DataInputStream搭配使用,写入基本类型数据,处理数据的存储 writeByte()、writeFloat()
PrintStream 用于产生格式化输出。处理显示 print()和println()。捕捉所有IOExceptions,所以必须使用checkErrors()->true有错,自行测试错误状态。可以使用布尔值指示是否每次换行时清空缓冲区
BufferedOutputStream 避免每次发送数据都要进行写操作,可以使用flush()清空缓冲区 可指定缓冲区大小。本质上并不提供接口,只不过是向进程中添加缓冲区所必需的,与接口对象搭配

Reader和Writer

Stream面向字节,Reader和Writer面向兼容Unicode的字符I/O,国际化。有时,使用适配器类:InputStreamReader将InputStream转换为Reader,OutputStreamWriter将OutoutStream转换为Writer。一般先用Reader和Writer,不行的话再用stream。

来源与去处:java 1.0类 相应的Java1.1类
FileInputStream FileReader
FileOutputStream FileWriter
StrngBufferInputStream(已弃用) StringReader
ByteArrayInputStream CharArrayReader
ByteArrayOutputStream CharArrayWriter
PipedInputStream PipedReader
PipedOutputStream PipedWriter

更改流的行为:

过滤器:Java 1.0类 相应的Java 1.1类
FilterInputStream FilterReader
FilterOutputStream FilterWriter(抽象类,没有子类)
BufferedInputStream BufferedReader(也有readLine())
BufferedOutputStream BufferedWriter
DataInputStream 使用DataInputStream,除了当使用readLine()时之外,这时应该使用BufferedReader
PrintStream PrintWriter,接口一样,接受OutputStream和Writer的构造器
LineNumberInputStream(已弃用) LineNumberReader
StreamTokenizer StreamTokenizer(使用接受Reader的构造器)
PushbackInputStream PushbackReader

自我独立的类:RandomAccessFile

RandomAccessFile继承了DataInput、DataOutput这两个接口,提供的对文件内容的访问,它既可以读文件,也可以写文件,并且RandomAccessFile支持随机访问文件,可以指定位置进行访问。
与File的区别最根本的是在Java中Class File代表的是“文件/目录”本身,可以想象成是一个文件句柄、标识,这个类本身只提供对文件的打开,关闭,删除,属性访问等等;而RandomAccessFile类则是文件访问的类,从类名可以看出它是一种文件访问方法:随机访问文件。这样就很好理解,比如在vi或者notepad中你的光标随意游走,改变一些内容,然后保存,关闭等等,这些是它的功能。
作用:
1.断点续传;2.RandomAccessFile可以提高读取的速度。根据文件的hashcode生成一个位置存入文件,取得时候再反过来根据这个固定的位置直接就能找到文件。
jdk1.4中大多数功能被nio存储映射取代。

I/O流的使用方式

  1. 缓冲输入文件
public class BufferedInputFile{

public static String read(String filename) throws IOException{
  //reading input by lines
  BufferedReader in = new BufferedReader(new FileReader(filename));
  String s;
  StringBuffer sb = new StringBuffer();
  while((s = in.readLine() ) != null){
    sb.append(s + "\n");
  }
  in.close();
  return sb.toString();
}

}
  1. 从内存输入
    用上面的BufferedInputFile.read()读入的String结果被用来创建一个StringReader。然后调用read()每次读入一个字符,发送至控制台
public static String read(String filename) throws IOException{
  StringReader in=new StringReader(BufferedInputFile(filename));
  int c;
  while((c=in.read())!= -1){
      System.put.print((char) c);
  }
}
  1. 格式化内存输入
    要读取格式化数据,可以使用DataInputStream。
public static void main throws IOException{
  try{
    DataInputStream in = new DataInputStream( 
      new ByteArrayInputStream( //必须为ByteArrayInputStream提供字节数组
      new BufferedInputFile("xxxx.java").getBytes()));
    while(true)
      print((char)in.readByte());
   } catch(EOFException e){//一个IO异常的子类,名字也是END OF FILE的缩写,表示流的末尾
      print("End of Stream");
   }
}

使用DataInputStream用readByte()读取字节,任何字节都是合法结果,返回值不能用来检测输入是否结束。可以使用available()查看还有多少可供存取的字符。注意,available()会随着所读取的媒介类型不同而不同,字面意思是没有阻塞的情况下所能读取的字节数,对于文件,可能意味着整个文件,对其他可能不同。

public static void main throws IOException{
  DataInputStream in = new DataInputStream(
    new BufferedInputStream(
      new FileInputStream("xxx.java")));
  while(in.available() != 0)
    print((char)in.readByte());
}
  1. 基本文件输出
    FileWriter可以向文件写入数据,为了提供格式化机制,被装饰城PrintWriter。这种方式创建的文件可作为普通文本文件读取。
public static void main throws IOException{
  BufferReader in = new BufferReader(
    new StringReader(
      BufferedInputFile.read("xxx.java")));
  PrintWriter out = new PrintWriter(
    new BufferedWriter( new FileWriter("xxxx.out")));
  //PrintWriter添加了辅助构造器,可以直接创建文本,并缓冲写入
  // PrintWriter out = new PrinterWriter("xxxx.out");
  int lineCount = 1;
  String s;
  while((s = in.readLine() )!= null)
    out.println(lineCount++ + ": " + s);
  out.close();

  //显示存入的文件
  print(new BufferedInputFile("xxxx.out"));
}
  1. 存储和恢复数据
    当使用DataOutputStream写入数据,java保证可以使用DataInputStream准确读取数据。做法就是使用UTF-8编码,writeUTF()和readUTF()。java使用的是UTF-8辩题,非java程序读取需要编写一些特殊代码才能读取字符串。但如果用writeDouble()将double类型写入流,需要用readDouble()恢复。但可能被作为其他类型读入。因此,要么为文件中的数据采用固定的格式,要么将额外信息保存如文件中,以便能够解析以确定数据的存放文职。因此,对象序列化和XML可能是更容易的存储和读取复杂数据结构的方式。
public static void main throws IOException{
  DataOutputStream out = new DataOutputStream(
    new BufferedOutputStream(
      new FIleOutputStream("Data.txt")));
  out.writeDouble(3.14159);
  out.writeUTF("that wa pi");
  out.writeDouble(1.41413);
  out.writeUTF("Square root of 2");
  out.close();

  DataInputStream in = new DataInputStream(
    new BufferedInputStream(
      new FileInputStream("Data.txt"))):
  print(in.readDouble());
  //注意读入方式
  print(in.readUTF());
  print(in.readDouble());
  print(in.readUTF());
}
  1. 读写随机访问文件
    RandomAccessFile组合了DataInput和DataOutput接口,并可以用seek()在文件中到处移动,并修改文件中的值。不支持装饰。
static String file = "rtest.dat";
static void display() throws IOException{
  RandomAccessFile rf = new RandomAccessFile(file, "r");
  for(int i=0; i<7; i++)
    println("Value " + i + ": " + rf.readDouble());
  println(rf.readUTF());
  rf.close();
}

public stativ void main(String[] args) throws IOException{
  RandomAccessFile rf = new RandomAccessFile(file, "rw");
  for(int i=0; i<7; i++)
    rf.writeDouble(i*1.414);
  rf.writeUTF("the end of the file");
  rf.close();
  dispaly();

  rf = new RandomAccessFile(file, "rw");
  rf.seek(5*8);//double8字节长
  rf.writeDouble(47.001);
  rf.close();
  dispaly();
}
}
  1. 管道流
    PipedInputSream等管道流用于任务之间的通信,在多线程用处很大。

标准I/O

标准I/O参照Unix中“程序所使用的单一信息流”这一概念,程序的所有输入都可以来自标准输入,所有输出也可以发送到标准输出,所有错位信息都可以发送到标准错误。意义在于,方便将程序串联起来,像管道一样。
java提供System.in, System.out, System.err。

  1. 从标准输入中读取
    为了使用readLine(),BufferedReader要求使用InputStreamReader把System.in转换成Reader。
public stativ void main(String[] args) throws IOException{
  BufferedReader stdin = new BufferedReader(
    new InputSreamReader(System.in));
  String s;
  while( (s = stdin.readLine()) != null && s.length!=0)
    System.out.println(s);
  //空行或者Ctrl-z终止程序
}
  1. 将System.out转换成PrintWriter
    System.out是个PrintStream,PrintStream是一个OutputStream。PrintWriter有一个接受OutputSream作为参数的构造器。
PrintWriter out = new PrintWriter(System.out, true);
  1. 标准I/O重定向
    System类提供静态方法,以将标准输入输出重定向:
    · setIn(InputStream)
    · setOut(PrintStream)
    · setErr(PrintStream)

进程控制

若要在java内部执行其他操作系统的程序,并且要控制这些程序的输入和输出,则需要进程控制。
比如运行程序,将输出发送到控制台。可以使用java.lang.ProcessBuilder。在使用时,可能会产生两种类型的错误,普通的导致异常的错误——抛出运行时异常,还有从进程自身的执行过程中产生的错误,可以用单独的异常报告来记录。

public static void command(String command){ //命令字符串
try{
  boolean err = false;
  Process process = new ProcessBuilder(command.split(" ")).start();
  BufferReader results = new BufferReader(
    new InputStreamReader(process.getInputStream()));
  String s;
  while((s = results.readLine()) != null)
    println(s);
  BufferedReader errors = new BufferedReader(
    new InputStreamReader(process.getErrorStream()));
  while((s = errors.readLine()) != null){
    println(s);
    err = true;
  }
 } catch(Exception e){
    throw new RuntimeException(e);
 }

NIO

实际上,java旧的I/O包已经使用nio重新实现过,以提高速度。速度的提高来自于所使用的结构更接近与操作系统执行I/O的方式:通道和缓冲器。直接与缓冲器交互,将缓冲器派送到通道。
唯一直接与通道交互的缓冲器是ByteBuffer——存储未加工字节(8字节)。将字节存放于ByteBuffer的方法:一是“put”直接填充一个或多个字节或基本数据类型的值。二是warp()将已存在的字节数组包装进ByteBuffer中。
FileInputStream、FileOutputStream、RandomAccessFile可以使用getChannel()产生FileChannel( 一个读,写,映射,操作文件的通道)。

public class GetChannal {
  private static final int BSIZE = 1024;
  
  public stativ void main(String[] args) throws IOException{
    //写文件
    FileChannel fc =new FileOutputStream("data.txt").getChannel();
    fc.write(ByteBuffer.wrap("some text".getBytes()));
    fc.close();
    //将内容添加到文件尾部
    fc = new RandomAccessFile("data.txt").getChannel();
    fc.position(fc.size());
    fc.write(ByteBuffer.wrap("Some more".getBytes()));
    fc.close();
    //读文件
    fc = new FileInputStream("data.txt").getChannel();
    ByteBuffer buff = ByteBuffer.allocate(BSIZE);//allocateDirect()更快,资源消耗更多
    fc.read(buff);// FileChannel 向ByteBuffer存储字节
    buff.flip();//must 准备
    while(buff.hasRemaining())
      print((char)bugg.get());
  }
}

读写例子:

.....
FileChannel in = new FileInputStream("in.txt").getChannel(),
  out = new FileInputStream("out.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1){
    buffer.flip(); //准备写
    out.write(buffer); //操作之后,信息仍在缓冲器
    buffer.clear(); //清理准备
}
....

使用transferTo()和transferFrom()能将两个通道相连。

...
in.transferTo(0, in.size(), out);
//或
out.transferFrom(in, 0, in.size());

转换数据:缓冲器放的是字节,要转换成字符,一是可以在输入的时候对其进行编码,二是从缓冲器输出是对其进行解码。可以使用java.nio.charset.Charset实现。

//第一种方法
String encoding = System.getProperty("file.encoding");
System.out.println("Decoded using " + encoding + ": "
      + Charset.forName(encoding).decode(buff));
//第二种
fc = new FileOutputStream("data2.txt").getChannel();
fc.write(ByteBuffer.wrap(
      "Some text".getBytes("UTF-16BE")));
fc.close();
// Now try reading again:
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
// 使用CharBuffer 写
fc = new FileOutputStream("data2.txt").getChannel();
buff = ByteBuffer.allocate(24); // More than needed
buff.asCharBuffer().put("Some text");
fc.write(buff);
fc.close();
// 再来读
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());

获取基本类型:

ByteBuffer bb = ByteBuffer.allocate(BSIZE);
// Allocation automatically zeroes the ByteBuffer:
int i = 0;
while(i++ < bb.limit())
if(bb.get() != 0)
    print("nonzero");
print("i = " + i);
bb.rewind();
// Store and read a char array:
bb.asCharBuffer().put("Howdy!");
char c;
while((c = bb.getChar()) != 0)
   printnb(c + " ");
print();
bb.rewind();
// Store and read a short:
bb.asShortBuffer().put((short)471142);//需要类型转换
print(bb.getShort());
bb.rewind();
// Store and read an int:
bb.asIntBuffer().put(99471142);
//bb.asIntBuffer().put(new int[]{1,2,4,5,});
//bb.asIntBuffer().put(1,3,5);
print(bb.getInt());
bb.rewind();
// Store and read a long:
bb.asLongBuffer().put(99471142);
print(bb.getLong());
bb.rewind();
// Store and read a float:
bb.asFloatBuffer().put(99471142);
print(bb.getFloat());
bb.rewind();
// Store and read a double:
bb.asDoubleBuffer().put(99471142);
print(bb.getDouble());
bb.rewind();

除了short都不用类型转换。asXXBuffer()获取的是视图缓冲器XXBuffer。等底层的ByteBuffer通过视图缓冲器填满了基本类型是,就 直接写到通道中了。
缓冲器有四个索引操作:mark、positon、limit和capacity。

方法 功能
capacity() 返回缓冲区容量
clear() 清空缓冲区,position=0,limit=capacity,可以覆写缓冲区
flip() limi=positon, position=0,准备从缓冲器读取写入数据
limit() 返回limit值
limit(int lim) 设置limit值
mark() mark=position
position() 返回position值
position(int pos) 设置position值
remaining() 返回limit-position值
hasRemaining() 若有介于position和limit之间的元素,则返回true
reset() position=mark值
rewind() position=0,mark值不明确,在写完读之前用

内存映射文件:允许创建和修改因太大而不能放入内存的文件,假定整个文件都在内存中,当做非常大的数组来访问。BufferXXX对大文件力不从心,而MapedByteBuffer就很适合。

static int length = 0x8FFFFFF; // 128 MB
public static void main(String[] args) throws Exception {
    MappedByteBuffer out = //ByteBuffer的子类
      new RandomAccessFile("test.dat", "rw").getChannel()
      .map(FileChannel.MapMode.READ_WRITE, 0, length);//初始位置和映射区域长度
    for(int i = 0; i < length; i++)
      out.put((byte)'x');
    print("Finished writing");
    for(int i = length/2; i < length/2 + 6; i++)
      printnb((char)out.get(i));
  }

文件加锁:
使用FileChannel调用tryLock()(非阻塞,若不能获取,则返回方法调用)或lock()(阻塞直至锁获得)就能获取整个文件的FileLock。如果返回值不为空,则可以进行操作。

压缩

压缩使用直接将输出流封装成GZIPOutputStream或ZipOutputStream,输入流封装成GZIPInputStream。。即可。批量的话有ZipEntry对每个entry操作。

对象序列化

java的对象序列化将实现了Serializable接口的对象转换成字节序列,并能通过字节序列恢复对象,可以利用这个实现轻量级持久性。这主要为了支持java的RMI和java Beans。
要序列化一个对象,首先OutputStream对象,然后将其封装再ObjectOutputStream对象内,调用writeObject()对象序列化,并将其发送到OutputStream。对象序列化是基于字节的。
若有特殊需要,可以实现Externalizable接口(继承了Serializable接口,添加了两个方法,writeExternal()和readExternal())。Externalizable对象默认不保存任何字段。
一般序列化默认无参构造器。若有参数,则需要实现Externalizable接口

public class Blip3 implements Externalizable {
  private int i;
  private String s; // No initialization
  public Blip3() {
    print("Blip3 Constructor");
    // s, i not initialized
  }
  public Blip3(String x, int a) {
    print("Blip3(String x, int a)");
    s = x;
    i = a;
    // s & i initialized only in non-default constructor.
  }
  public String toString() { return s + i; }
  public void writeExternal(ObjectOutput out)
  throws IOException {
    print("Blip3.writeExternal");
    // You must do this:
    out.writeObject(s);
    out.writeInt(i);
  }
  public void readExternal(ObjectInput in)
  throws IOException, ClassNotFoundException {
    print("Blip3.readExternal");
    // You must do this:
    s = (String)in.readObject();
    i = in.readInt();
  }
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    print("Constructing objects:");
    Blip3 b3 = new Blip3("A String ", 47);
    print(b3);
    ObjectOutputStream o = new ObjectOutputStream(
      new FileOutputStream("Blip3.out"));
    print("Saving object:");
    o.writeObject(b3);
    o.close();
    // Now get it back:
    ObjectInputStream in = new ObjectInputStream(
      new FileInputStream("Blip3.out"));
    print("Recovering b3:");
    b3 = (Blip3)in.readObject();
    print(b3);
  }
} 

transient关键字:若不希望对某个子对象序列化。

Externalizable替代方法

可以实现Serializable接口,并添加名为writeObject()和readObject()的私有方法。

private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectOutputStream stream) throws IOException, ClassNotFoundException;

在方法内部,可以调用defaultWriteObjec()或者defaultReadObject()。非transient字段一定要在之前调用defaultXXX(), transient字段一定要明确保存和恢复。

public class SerialCtl implements Serializable {
  private String a;
  private transient String b;
  public SerialCtl(String aa, String bb) {
    a = "Not Transient: " + aa;
    b = "Transient: " + bb;
  }
  public String toString() { return a + "\n" + b; }
  private void writeObject(ObjectOutputStream stream)
  throws IOException {
    stream.defaultWriteObject();
    stream.writeObject(b);
  }
  private void readObject(ObjectInputStream stream)
  throws IOException, ClassNotFoundException {
    stream.defaultReadObject();
    b = (String)stream.readObject();
  }
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    SerialCtl sc = new SerialCtl("Test1", "Test2");
    System.out.println("Before:\n" + sc);
    ByteArrayOutputStream buf= new ByteArrayOutputStream();
    ObjectOutputStream o = new ObjectOutputStream(buf);
    o.writeObject(sc);
    // Now get it back:
    ObjectInputStream in = new ObjectInputStream(
      new ByteArrayInputStream(buf.toByteArray()));
    SerialCtl sc2 = (SerialCtl)in.readObject();
    System.out.println("After:\n" + sc2);
  }
} /* Output:
Before:
Not Transient: Test1
Transient: Test2
After:
Not Transient: Test1
Transient: Test2
*///:~

XML

XOM类库可以实现对象xml化。还有个Serializer类将XML转换为可读性的格式。

Preferences

Preference API用于存储和读取用户的偏好以及程序配置项的设置,是一个键-值集合,简单好上手。
补充资料

你可能感兴趣的:(第十八章:Java I/O系统)