1.        NIO
1.1.       说明:在新的I/O系统当中,我们将主要使用Channel和Buffer来描述我们底层的操作。
1.2.       模型:
1.3.       对Channel进行读写:
/**
 * author cenyonghmails.gscas.ac.cn
 */
public class CopyFile {
     public static void main(String[] args) throws Exception {
         String in = args[0];
         String out = args[1];
         FileInputStream fis = new FileInputStream(in);
         FileOutputStream fos = new FileOutputStream(out);
         FileChannel inc = fis.getChannel();
         FileChannel outc = fos.getChannel();
         ByteBuffer bb = ByteBuffer.allocate(1024);
         while (true) {
              int ret = inc.read(bb);
              if (ret == -1) {
                   break;
              }
              bb.flip();
              outc.write(bb);
              bb.clear();
         }
     }
}
注:我们并没有直接对Channel进行读写,而是通过Buffer来对Channel进行间接操作。这里有两个地方要注意,就是我们在拷贝的过程当中调用了flip()和clear()方法,这两个方法的作用,将在后面讲解。
 
 
 
1.4.       手工填充Buffer
/**
 * author cenyonghmails.gscas.ac.cn
 */
public class WriteFile {
     public static void main(String[] args) throws Exception {
         String out = args[0];
         String in = args[0];
         FileInputStream fin = new FileInputStream(in);
         FileOutputStream fout = new FileOutputStream(out);
         FileChannel inc = fin.getChannel();
         FileChannel outc = fout.getChannel();
         ByteBuffer bb = ByteBuffer.allocate(256);
         for (int i = 0; i < 256; i++)
              bb.put((byte) i);
         bb.flip();
         outc.write(bb);
 
 
 
         bb.clear();
         inc.read(bb);
         bb.flip();
         for (int i = 0; i < bb.limit(); i++) {
              System.out.println(bb.get());
         }
     }
}
注:通过调用Buffer上的put()和get()方法,我们可以手工的往Buffer当中填充数据。
 
 
 
1.5.       Buffer的状态量。
Buffer主要使用三个状态量position,limit,capacity来标记底层的状态。其中capacity表征Buffer的最大容量,这个值在Buffer被分配时设定,一般不会随着操作改变。position表征Buffer的当前读写位置,不管是读操作还是写操作,都会导致position的增加。limit表征Buffer的最大可读写位置,limit总是小于或等于capacity。
1.5.1.      结构图:
1.5.2.      flip()和clear()操作
flip(){
     limit = position;
     postion = 0;
}
clear(){
     limit = capacity;
     position = 0;
}
1.5.3.      例子:
/**
 * author cenyonghmails.gscas.ac.cn
 */
 
 
 
public class CopyFile {
 
 
 
     public static void main(String[] args) throws Exception {
         String in = args[0];
         String out = args[1];
         FileInputStream fis = new FileInputStream(in);
         FileOutputStream fos = new FileOutputStream(out);
         FileChannel inc = fis.getChannel();
         FileChannel outc = fos.getChannel();
         ByteBuffer bb = ByteBuffer.allocate(1024);
 
 
 
         inc.read(bb);
         show(bb, "After read");
         bb.flip();
         show(bb, "After flip");
         outc.write(bb);
         show(bb, "After write");
         bb.clear();
         show(bb, "After clear");
     }
     public static void show(ByteBuffer bb, String msg) {
         System.out.println(msg + " p:" + bb.position() + " l:" + bb.limit()
                   + " c:" + bb.capacity());
     }
}
输出:   After read p:1024 l:1024 c:1024
After flip p:0 l:1024 c:1024
After write p:1024 l:1024 c:1024
After clear p:0 l:1024 c:1024
     注:在进行read()操作时,程序将尽量的填充从position到limit之间的空间。在进行write()操作时,
程序将读出从position到limit之间的空间。所以,在调用完read()操作以后,要进行其他操作以前,
我们必须要调用flip()操作,使得position的位置回指到开头;而当调用完write()操作以后,应调
用clear()操作,这一方面使得position回指到开头,同时使得limit到达Buffer最大的容量处。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1.6.       子Buffer
当在Buffer上面调用slice()操作时,将单独划出在[position,limit)之间的一段Buffer作为子Buffer,子Buffer与父Buffer使用相同的空间,但维护各自的状态量。
 
 
 
1.6.1.      结构图:
1.6.2.      例子:
ByteBuffer original = ByteBuffer.allocate( 8 );
original.position( 2 );
original.limit( 6 );
ByteBuffer slice = original.slice();
 
 
 
1.7.       其他类型的Buffer
我们可以把最基本的ByteBuffer包装成其他的CharBuffer,FloatBuffer等。
1.7.1.      结构图:
1.7.2.      例子:
ByteBuffer buffer = ByteBuffer.allocate( size );
FloatBuffer floatBuffer = buffer.asFloatBuffer();
 
 
 
1.8.       在ByteBuffer上的多格式读取
在对ByteBuffer进行读取时,除了可以按照固定间隔的读取方式以外,我们也可以按照变长的方式读取。
1.8.1.      例子:
fch.read( bb );
bb.flip();
byte b0 = bb.get();
short s0 = bb.getShort();
byte b1 = bb.get();
float f0 = bb.getFloat();
1.9.       非阻塞I/O
在实现基于TCP/UDP的聊天服务器时,为了节省资源我们可以使用轮训技术。而为了让服务器不永远阻塞在accept()方法上,我们可以设置一个等待超时值。而通过使用Selector类,我们可以让以上方法更容易,更高效的得到实现。
public class ServerStub implements Runnable{
   private Selector selector = null;
   private int port = 0;  
…
   public void run() {
        started = true;
        try {
            selector = Selector.open();
            ServerSocketChannel schannel = ServerSocketChannel.open();
            schannel.configureBlocking(false);
            ServerSocket ssocket = schannel.socket();
            ssocket.bind(new InetSocketAddress("127.0.0.1", port));
            schannel.register(selector, SelectionKey.OP_ACCEPT);
            Set<SelectionKey> sks = null;
            int keys = 0;
            while (started) {
                 keys = selector.select();
                 if (keys > 0) {
                      sks = selector.selectedKeys();
                      Iterator<SelectionKey> it = sks.iterator();
                      while (it.hasNext()) {
                          SelectionKey key = it.next();
                          it.remove();
                          if (key.isReadable()) {
                               sender = (SocketChannel) key.channel();
                               String msg = receive(sender);                  
                          } else if (key.isAcceptable()) {
                               SocketChannel sc = schannel.accept();
                               sc.configureBlocking(false);
                               sc.register(selector, SelectionKey.OP_READ);
                          } else {
                               emit("Something Abnormal");
                          }
                      }
                 }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
   }
   …
}
注:Selector的使用,使得服务器能以一种事件响应的方式对客户端的连接进行监听。通过
SelectionKey提供的常量,管道可以注册他说感兴趣的事件,对于ServerSocketChannel他
只能注册OP_ACCEPT事件。当用户调用selector.select()方法时,线程将会被阻塞,直到某
些事件发生了。然后用户判断发生的事件类型并进行对应的操作。这里有几点需要注意,第一
是需要使用Selector的Channel需要设置为非阻塞模式(configureBlocking(false)),第二
是用户需要手工的把已处理的SelectionKey,从集合中移除。
 
 
 
public class ClientStub implements Runnable {
   private Selector selector = null;
   private SocketChannel channel = null;
   private boolean started = false;
…
   public void run() {
        started = true;
        try {
            selector = Selector.open();
            Selector sel = Selector.open();
            channel = SocketChannel.open();
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_READ
                      | SelectionKey.OP_CONNECT);
            channel.connect(addr);
            Set<SelectionKey> sks = null;
            int keys = 0;
            while (started) {
                 keys = selector.select();
                 if (keys > 0) {
                      sks = selector.selectedKeys();
                      Iterator<SelectionKey> it = sks.iterator();
                      while (it.hasNext()) {
                          SelectionKey key = it.next();
                          it.remove();
                          if (key.isReadable()) {
                               String msg = receive(channel);                 
                          } else if (key.isConnectable()) {
                               channel.finishConnect();
                               key.interestOps(SelectionKey.OP_READ);
                          } else {
                               emit("Something Abnormal");
                          }
                      }
                 }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
   }
   …
}
注:客户端程序的实现与服务器端的基本相似。唯一的一点区别就是,客户端一开始注册了两个事件类型OP_READ和OP_CONNECT,而当客户端连接上服务器以后,他将会收到一个isConnectable的SelectionKey。在这里我们需要先调用finishConnect()方法,然后由于我们不再需要监听连接事件,因此我们需要修改Channel在Selector上的监听事件类型,这需要调用interestOps()操作来完成,其中方法的参数就是我们所需要的新的事件类型,这一步骤非常重要。
 
 
 
1.10.    编码与解码
J2SDK 1.4提供了专门用于进行编/解码的类,CharsetEncoder和CharstDecoder。
public CharBuffer decode(ByteBuffer bb){
Charset c = Charset.forName("gb2312");
CharsetDecoder cd = c.newDecoder();
CharBuffer cb = cd.decode(bb);
return cb;
}
注:编码(CharsetEncoder)的方法与解码的类似。
 
 
 
2.        Image I/O
J2SDK 1.4提供了专门用于图片读写的类。ImageIO。如果我们只是想简单的读取或输出图片的话,那么我们可以直接使用ImageIO提供的static方法。而如果我们想对图片的读/写进行更多的控制的话,我们可以使用ImageReader和ImageWriter,以及与图片读写相关的一系列Listener。
public Image readImage(String filename){
     BufferedImage bi = ImageIO.read(new File(filename));
     return bi;
}
 
 
 
public void writeImage(){
     BufferedImage bi = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
     Graphics2D g2 = bi.createGraphics();
     //   绘图操作
     ImageIO.write(bi, "jpeg",new File("pic.jpg"));
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3.        Log
J2SDK 1.4提供了专门用于书写日志的类,Logger及其相关的Handler,Filter和Formatter。
3.1.       结构图:
在Logger系统当中,我们需要先获取一个Logger实例,然后通过调用Logger上的日志方法,我们将产生一个LogRecord实例,该实例将会被传送到在Logger上注册的所有Handler内,然后Handler使用他内部的Filter对象,以判断是否要处理该LogRecord记录,如果要处理的话,则把LogRecord传递给Formatter,让他对输出格式进行格式化。
public class Test{
     public static void main(String[] args){
         Logger log = Logger.getLogger("Test");
         StreamHandler sh = new StreamHandler(System.out, new SimpleFormatter());
         log.addHandler(sh);
         log.info("Hello World");
     }
}
输出:   2005-3-12 1:06:25 nick.log.Test main
信息: Hello World
2005-3-12 1:06:25 nick.log.Test main
信息: Hello World
          说明:由于Logger机制会递归的调用父类的Logger,因此,这里输出了两份日志记录。
 
 
 
3.2.       自定义Handler,Filter,Formatter
public class TestFormatter extends Formatter {
     public String format(LogRecord record) {
         return "INFO MESSAGE:" + record.getMessage();
     }
}
 
 
 
public class TestFilter implements Filter {
     public boolean isLoggable(LogRecord record) {
         if (record.getLevel() == Level.INFO)
              return true;
         else
              return false;
     }
}
 
 
 
public class TestHandler extends Handler {
     public void publish(LogRecord r) {
         if (!isLoggable(r))
              return;
         System.out.println(getFormatter().format(r));
     }
     public void close() throws SecurityException {}
     public void flush() {}
}
 
 
 
public class Test {
     public static void main(String[] args) {
         Logger log = Logger.getLogger("Test");
         log.setLevel(Level.ALL);         
         log.setUseParentHandlers(false);
         TestHandler th = new TestHandler();
         th.setFilter(new TestFilter());
         th.setFormatter(new TestFormatter());
         log.addHandler(th);
         log.info("info");
         log.fine("fine");
     }
}
 
 
 
输出:INFO MESSAGE:info
说明:在主程序里面,我们调用了setUseParentHandlers(false)方法,这样做是为了禁止当前
的Logger调用其父类Logger,默认情况下该值为true。
 
 
 
3.3.       默认Handler及其配置
Log系统提供了五个默认Handler的实现:FileHandler,ConsoleHandler,MemoryHandler,SocketHandler,StreamHandler。通过配置文件,我们可以设定其默认属性。而通过在System.setProperty()方法里面设定“java.util.loggin.config.file”的值,可以指定配置文件的位置,默认情况下系统使用/jre/lib/logging.properties作为配置文件。
 
 
 
FileHandler
ConsoleHandler
MemoryHandler
SocketHandler
StreamHandler
level
y
y
y
Y
Y
filter
y
y
y
Y
Y
formatter
y
y
y
Y
 
 
 
encoding
y
y
y
Y
 
 
 
limit
y
 
 
 
 
 
 
 
 
 
 
 
 
count
y
 
 
 
 
 
 
 
 
 
 
 
 
pattern
y
 
 
 
 
 
 
 
 
 
 
 
 
append
y
 
 
 
 
 
 
 
 
 
 
 
 
size
 
 
 
 
 
 
y
 
 
 
 
 
 
push
 
 
 
 
 
 
y
 
 
 
 
 
 
target
 
 
 
 
 
 
y
 
 
 
 
 
 
host
 
 
 
 
 
 
 
 
 
Y
 
 
 
port
 
 
 
 
 
 
 
 
 
Y
 
 
 
     logging.properties的内容:
nick.log.level = WARNING
     
public class Test {
     public static void main(String[] args) {
         System.setProperty("java.util.logging.config.file",
                   "./logging.properties");
         Logger log = Logger.getLogger("nick.log");
         System.out.println(log.getLevel());       
         log.setUseParentHandlers(false);
         StreamHandler sh = new StreamHandler(System.out, new SimpleFormatter());
         log.addHandler(sh);
         log.warning("warning");
         log.info("info");
         log.fine("fine");
     }
}
输出:   WARNING
2005-3-12 1:05:44 nick.log.Test main
警告: warning
 
 
 
4.        正则表达式
J2SDK 1.4引入了对正则表达式的支持。这主要包括Pattern和Matcher类。
public class Test {
     public static void main(String[] args) {
         Pattern p = Pattern.compile("\ +\ ");
         String inputString = "well, hey there feller";
         Matcher matcher = p.matcher(inputString);
         while (matcher.find()) {
              int start = matcher.start();
              int end = matcher.end();
              String matched = inputString.substring(start, end);
              System.out.println(matched);
         }
         System.out.println("===== Using Group: =====");
         matcher.reset();
         while (matcher.find()) {
              String matched = matcher.group();
              System.out.println(matched);
         }
     }
}
输出:   well, 
hey 
there 
===== Using Group: =====
well, 
hey 
there 
         说明:Pattern对需要进行识别的模式进行编译,这可以提高之后的识别速度。在使用Pattern
时有一点要特别注意,就是正则表达式单中,大量的使用以“\”开头的符号,所以为了在Pattern
中表示“ ”我们需要写成“\ ”。而当中的加号并不是表示连接,而是表示“1此或多次”
上述程序演示了如何使用一个模式去识别一个字符串,并提取每一个匹配的串。
 
 
 
4.1.       捕获组(Capturing Group)
在Pattern当中的正则表达当中,通过使用括号,我们可以在原来的表达式当中定义子表达式,或者称为Capturing Group。通过Matcher,我们还可以直接提取某一个Capturing Group的内容。
public class Test {
     public static void main(String[] args) {
         Pattern p = Pattern.compile("\ +\ +(\ +)\ +\ +");
         String inputString = "hey there feller";
         Matcher matcher = p.matcher(inputString);
         while (matcher.find()) {
              int start = matcher.start(1);
              int end = matcher.end(1);
              String matched = inputString.substring(start, end);
              System.out.println(matched);
         }
         System.out.println("===== Using Group: =====");
         matcher.reset();
         while (matcher.find()) {
              String matched = matcher.group(1);
              System.out.println(matched);
         }
     }
}
输出:   there
===== Using Group: =====
there
说明:Capturing Group的编号是从1开始的。组号为0的组表示整个串。
 
 
 
4.2.       替换
Matcher提供用于替换的方法。一种是简单进行查找替换,使用replaceAll()方法。第二种更加灵活的方式,在使用的时候可以结合Capturing Group。
public class Test {
   public static void main(String[] args) {
        Pattern p = Pattern.compile("\ +\ ");
        String inputString = "hey there feller";
        Matcher matcher = p.matcher(inputString);
        String ns = matcher.replaceAll("Hello ");
        System.out.println(ns);
   }
}
输出:Hello Hello feller
 
 
 
public class Test {
   public static void main(String[] args) {
        Pattern pattern = Pattern.compile("\\(((\\w|\ )*)\\)");
        String inputString = "These should be (square brackets).(hello)";
        StringBuffer sb = new StringBuffer();
        Matcher matcher = pattern.matcher(inputString);
        while (matcher.find()) {
            matcher.appendReplacement(sb, "[$1]");
        }
        matcher.appendTail(sb);
        String newString = sb.toString();
        System.out.println(newString);
   }
}
输出:These should be [square brackets].[hello]
说明:这种方式的替换,由于加入了Capturing Group。所以比之前的方法更加灵活。在appendReplacement()方法中,我们使用第二个参数的内容,替换匹配的部分。而$X则是用于引用对应的Capturing Group的值。
                   
J2SDK 1.4中的新功能类
                    80酷酷网    80kuku.com 
       
  
 
 
  
