代码改变世界

《Java编程思想》之I/O系统

2012-02-05 19:40  java线程例子  阅读(704)  评论(0编辑  收藏  举报

1、Java中“流“类库让人迷惑的主要原因:创建单一的结果流,却需要创建多个对象。

2、使用层叠的数个对象为单个对象动态地、透明地添加职责的方式,称作“修饰器“模式。修饰器必须与其所修饰的对象具有相同的接口,这使得修饰器的基本应用具有透明性——我们可以想修饰过或没有修饰过的对象发送相同的消息。

3、为什么使用修饰器

         在直接使用扩展子类的方法时,如果导致产生了大量的、用以满足所需的各种可能的组合的子类,这是通常就会使用修饰器——处理太多的子类已经不太实际

4、修饰器的缺点:增加代码的复杂性。

5、JavaI/O类库操作不便的原因在于:我们必须创建许多类——“核心”I/O类型加上所有的修饰器,才能得到我们所希望的单个I/O对象。

6、FilterInputStream和FilterOutputStream是用来提供修饰器类接口以控制特定输入流和输出流的俩个类。它们分别继承自I/O类库中的基类InputStream和OutputStream。

7、DataInputStream是FilterInputStream的直接子类,它允许我们读取不用的基本类型数据以及String对象。

8、InputStream和OutputStream是面向字节的,而Reader和Writer是面向字符与兼容Unicode

9、在InputStream和OutputStream的继承层次结构仅支持8位的字节流,并且不能很好地处理16位的Unicode。由于Unicode用用于字符国际化,所以添加Reader和Write继承结构就是为了在所有的I/O操作中都支持Unicode。另外添加了Reader和Write的I/O流类库使得它的操作比旧类库更快。

10、但是,Reader和Write并不是用来取代nputStream和OutputStream的,因为InputStream和OutputStream在面向字节形式的I/O中仍然提供了极有价值的功能。

11、有时,我们需要把来自于“字节”层次结构的类和“字符”层次结构的类结合起来使用。这时,需要使用“适配器(adapter)”类:InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer。

12、当我们使用DataOutputStream时,写字符串并且让DataInputStream能够恢复它的唯一可靠的做法就是使用UTF-8编码。UTF-8是Unicode的变体,后者把所有字符都存储成两个字节的形式。而我们使用的只是ASCII或者几乎是ASCII字符(只占7位),这么做就显得极其浪费空间和宽带,所以UTF-8将ASCII字符编码成单一字节的形式,而非ASCII字符则编码成两到三个字节的形式。另外,字符串的长度存储在两个字节中

13、System.in是一个没有被加工过的InputStream,所以在读取System.in之前必须对其进行加工。

14、标准I/O重定向:如果我们突然开始在显示器上创建大量输出,而这些输出滚动得太快以至于无法阅读是,重定向输出就显得极为有用。重定向有一下方法:

SetIn(InputStream)
SetOut(InputStream)
SetErr(InputStream)

注意:重定向操作的是字节流(InputStream、OutputStream),而不是字符流。

15、JDK1.4的java.nio.*包引入了新的JavaI/O类库,其目的在于提高速度。速度的提高来自于所使用的结构更接近操作系统的I/O的方式:通道和缓冲器。

         我们可以把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互;只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。

         唯一直接与通道交互的缓冲器是ByteBuffer——也就是说,可以存储未加工字节的缓冲器。ByteBuffer是一个相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用以原始的字节形式或基本数据类型输出和读取数据。

16、FileInputStream、FileOutputStreaam以及用于既读又写的RandomAccessFile被使用nio重新实现过,都有一个getChannel()方法返回一个FileChannel(通道)。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。Reader和Writer这种字符模式类不能产生通道;但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Wirter。

以下

17、转换数据:缓冲器容纳的是普通的字节,为了把它们转化成字符,我们要么在输入它们的时候对其进行编码,要么在将其从缓冲器输出时对它们进行解码。请参看下面例子:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
 
public class GetChannel{
   public static void main(String[] args) throws Exception{
      //写入data.txt
      FileChannel fc = new FileOutputStream("data.txt").getChannel();
      fc.write(ByteBuffer.wrap("Some text".getBytes()));
      fc.close();
      //读取data.txt
      fc = new FileInputStream("data.txt").getChannel();
      ByteBuffer buff = ByteBuffer.allocate(1024);
      fc.read(buff);
      buff.flip();
     
      System.out.println(buff.asCharBuffer());
      //使用系统默认的字符集进行解码(输出时对其进行解码)
      buff.rewind();/返回数据开始的部分
      String encoding = System.getProperty("file.encoding");
      System.out.println("Decodedusing " + encoding + ":"
           + Charset.forName(encoding).decode(buff));
      //再次写入(输入时对其进行编码)
      fc = new FileOutputStream("data.txt").getChannel();
      fc.write(ByteBuffer.wrap("Some text".getBytes("UTF-16BE")));
      fc.close();
      //再次读取
      fc = new FileInputStream("data.txt").getChannel();
      buff = ByteBuffer.allocate(1024);
      fc.read(buff);
      buff.flip();
      System.out.println(buff.asCharBuffer());
      //再次写入(输入时对其进行编码)
      fc = new FileOutputStream("data.txt").getChannel();
      buff = ByteBuffer.allocate(24);
      buff.asCharBuffer().put("Some text");
      fc.write(buff);
      fc.close();
      //再次读取
      fc = new FileInputStream("data.txt").getChannel();
      buff.clear();
      fc.read(buff);
      buff.flip();
      System.out.println(buff.asCharBuffer());
   }
}

运行结果:


18、获取基本类型:尽管ByteBuffer只能保存字节类型的数据,但是它具有从其所容纳的字节中产生出各种不同的类型值的方法。

import java.nio.ByteBuffer;

public class GetData{
   private final static int BSIZE = 1024;
   public static void main(String[] args){
  
      ByteBuffer bb = ByteBuffer.allocate(BSIZE);
      int i = 0;
      while(i++ < bb.limit()){
        if(bb.get() != 0){
           System.out.println("nonzero");
        }
      }
      System.out.println("i = " + i);
      bb.rewind();
      //char
      bb.asCharBuffer().put("Howdy");
      char c;
      while((c = bb.getChar()) != 0){
        System.out.print(c + " ");
      }
      System.out.println();
      bb.rewind();
      //short。是一个特例,必须加上(short)进行类型转换
      bb.asShortBuffer().put((short)471142);
      System.out.println(bb.getShort());
      bb.rewind();
      //int
      bb.asIntBuffer().put(99471142);
      System.out.println(bb.getInt());
      bb.rewind();
      //long
      bb.asLongBuffer().put(99471142);
      System.out.println(bb.getLong());
      bb.rewind();
      //float
      bb.asFloatBuffer().put(99471142);
      System.out.println(bb.getFloat());
      bb.rewind();
      //double
      bb.asDoubleBuffer().put(99471142);
      System.out.println(bb.getDouble());
      bb.rewind();
   }
}

运行结果:


19、视图缓冲器:可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。

ByteBuffer bb = ByteBuffer.allocate(BSIZE);
//asIntBuffer() 创建此字节缓冲区的视图,作为 int 缓冲区。
IntBuffer ib = bb.asIntBuffer();

20、不同机器可能会有不同的字节排序方法来存储数据。默认的ByteBuffer是以高位优先的顺序存储数据的。考虑下面两个字节:


如果我们以short(ByteBuffer.asShortBuffer)高位优先读出的是97(0000000001100001),若更改ByteBuffer更改为地位优先,则读出的是24832(0110000100000000)。再看下面例子:

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
 
public class Endians{
   public static void main(String[] args){
      ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
      bb.asCharBuffer().put("abcdef");
      System.out.println(Arrays.toString(bb.array()));
     
      bb.rewind();
      bb.order(ByteOrder.BIG_ENDIAN);//设置为高位排序
      bb.asCharBuffer().put("abcdef");
      //array()只能对有数组支持的缓冲器调用
      System.out.println(Arrays.toString(bb.array()));
     
      bb.rewind();
      bb.order(ByteOrder.LITTLE_ENDIAN);//设置为低位排序
      bb.asCharBuffer().put("abcdef");
      System.out.println(Arrays.toString(bb.array()));
   }
}

运行结果:


21、如果想把一个字节数组写到文件中去,那么应该使用ByteBuffer.wrap()方法把字节数组包装起来,然后用getChannel()方法在FileOutputStream上打开一个通道,接着将来自于ByteBuffer的数据写到FileChannel中去。

22、存储器映射文件:允许我们创建和修改那些因为太大而不能放入内存的文件。有了存储器映射文件,我们就可以假定整个文件都在内存中,而且可以完全把它当作非常大的数组来访问。

    尽管“旧”的I/O在用nio实现后性能有所提高,但是“映射文件访问”往往可以更加显著地加快速度

23、文件加锁机制:允许我们同步访问某个作为共享资源的文件。文件锁对其它操作系统进程是可见的,因为Java的文件加锁之间映射到本地操作系统的加锁工具。另外,利用对映射文件的部分加锁,可以对巨大的文件进行部分加锁,以便其他进程可以对修改文件中未被加锁的部分

24、压缩:Java压缩类库是按字节方式的,它们继承自InputStream和OutputStream。

1).GZIP:如果只想对单个数据流(而不是一系列互异数据)进行压缩,那么它是比较适合的选择。下面是对单个文件进行压缩的例子:

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
 
public class GZIPcompress{
   public static void main(String[] args) throws IOException{
      //InputStreamReader中设置字符编码为GBK
      BufferedReader in = new BufferedReader(new InputStreamReader(new
           FileInputStream("test.txt"), "GBK"));
      BufferedOutputStream out = new BufferedOutputStream((
           new GZIPOutputStream(new FileOutputStream("test.txt.gz"))));
      System.out.println("Writingfile");
      int c;
      while((c = in.read()) != -1){
        //使用GBK字符编码将此 String编码到 out中
        out.write(String.valueOf((char) c).getBytes("GBK"));
      }
      in.close();
      out.close();
      System.out.println("ReadingFile");
      BufferedReader in2 = new BufferedReader((new InputStreamReader
           (new GZIPInputStream(new FileInputStream("test.txt.gz")))));
      String s;
      while((s = in2.readLine()) != null){
        System.out.println(s);
      }
   }
}

2).用Zip进行多文件压缩:对于每一个要加入压缩档案的文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry对象。

25、对象的序列化:将那些实现Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。利用它可以实现“轻量持久性”。

1).持久化”:意味一个对象的生存周期并不取决于程序是否正在执行。通过将一个序列化对象写入磁盘,然后再重新调用程序时恢复该对象,就能够实现持久性的效果。

2).轻量级”:因为它不能用某种“persistent”(持久)关键字来简单定一个对象,并让系统自动维护其他细节问题。相反,对象必须在程序中显示地序列化和反序列化还原。

26、对象序列化的概念加入到语言中是为了支持两种主要特性:

1).Java的“远程方法调用”(Remote Method Invocation,RMI)。

2).Java Beans。

27、

1).序列化:创建OutputStream对象,封装到ObjectOutputStream,再调用writeObject()

2).反序列化:创建InputStream对象,封装到ObjectInputStream,再调用readObject()

3).Serializable的对象在还原的过程中,没有调用任何构造器

28、对象序列化是面向字节的,因此采用InputStream和OutputStream层次结构。

29、序列化的控制

1).实现Externalizable接口,接口继承自Serializable,添加了writeExternal()和readExternal()两个方法,它们会在序列化和反序列化还原的过程中被自动调用。

2).反序列过程中,对于Serializable对象,对象完全以它存储的二进制位为基础来构造,而不是调用构造器。而Externalizeble对象,普通的缺省构造函数会被调用

3).transient(瞬时)关键字:只能和Serializable对象一起使用。

private transient String password;

password将不会被保存到磁盘中。

30、序列化/反序列化static成员:显示调用serializeStaticState()和deserializeStaticState()

31、Preferences:用于存储和读取用户的偏好(preferences)以及程序配置项的设置。只能用于小的、受限的数据集合——我们只能保存基本数据类型和字符串,并且每个字符串的存储类型长度不能超过8K(不是很小,单我们也不想用它来创建任何重要的东西)。它是一个键-值集合(类似映射),存储在一个节点层次结构中。

import java.util.Arrays;
import java.util.Iterator;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
 
public class PreferencesDemo{
   public static void main(String[] args) throws BackingStoreException{
      Preferences prefs = Preferences.userNodeForPackage(PreferencesDemo.class);
      prefs.put("日期", "2012年02月05日");
      prefs.put("天气", "晴");
      prefs.putInt("放假多少天了?", 24);
      prefs.putBoolean("现在在家?", true);
     
      int usageCount = prefs.getInt("寒假还剩多少天?", 16);
      usageCount--;
      prefs.putInt("寒假还剩多少天?", usageCount);
     
      Iterator<String> it = Arrays.asList(prefs.keys()).iterator();
      while(it.hasNext()){
        String key = it.next().toString();
        //null作为缺省
        System.out.println(key + ":" + prefs.get(key, null));
      }
      System.out.println("放假多少天了?" + prefs.getInt("放假多少天了?", 0));
   }
}

注意:每次运行,usageCount的值没都减少1。即每次打印出“寒假还剩多少天?”后面的数字都减少1。然而,在程序第一次运行之后,并没有任何本地文件出现。Preferences API利用合适的系统资源完成了这个任务,并且这些资源会随操作系统的不同而不同。例如在windows里,就使用注册表。

32、正则表达式

1). 在Java ,“\\”意味“我正在插入一个正则表达式反斜杠”,那么随后的字符具有特殊意义。若想插入一个字面意义上的反斜杠,得这样子表示“\\\\”。

2).量词:

·贪婪的:竟可能的模式发现尽可能的匹配。

·勉强的:用问号来指定,匹配满足模式所需的最少字符数。

·占有的:只有在Java语言中才可用,并且它也更高效,常常用于防止正则表达式失控,因此可以是正则表达式执行起来更有效。

3).注意abc+与(abc)+的不同。

4).在java中,正则表达式是通过java.util.regex包里面的Pattern和Matcher类来实现的。

·Pattern对象表示一个正则表达式的编译版本。静态的complie()方法将一个正则表达式字符串编译成Pattern对象。

·Matcher对象有matcher()方法和输入字符串编译过的Pattern对象中产生Matcher对象。

5).split()分裂操作将输入字符串断开成字符串对象数组。