1. nio
1.1. 说明:在新的i/o系统当中,我们将主要使用channel和buffer来描述我们底层的操作。
1.2. 模型:
1.3. 对channel进行读写:
/**
* @author cenyongh@mails.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 cenyongh@mails.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 cenyongh@mails.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的值。
