java nio 类介绍

在JDK 1.4以前,Java的IO操作集中在java.io这个包中,是基于流的同步(blocking)API。对于大多数应用来说,这样的API使用很方便,然而,一些对性能要求较高的应用,尤其是服务端应用,往往需要一个更为有效的方式来处理IO。从JDK 1.4起,NIO API作为一个基于缓冲区,并能提供异步(non-blocking)IO操作的API被引入。本文对其进行深入的介绍。

NIO API主要集中在java.nio和它的subpackages中:

java.nio

定义了Buffer及其数据类型相关的子类。其中被java.nio.channels中的类用来进行IO操作的ByteBuffer的作用非常重要。

java.nio.channels

定义了一系列处理IO的Channel接口以及这些接口在文件系统和网络通讯上的实现。通过Selector这个类,还提供了进行异步IO操作的办法。这个包可以说是NIO API的核心。

java.nio.channels.spi

定义了可用来实现channel和selector API的抽象类。

java.nio.charset

定义了处理字符编码和解码的类。

java.nio.charset.spi

定义了可用来实现charset API的抽象类。

java.nio.channels.spi和java.nio.charset.spi这两个包主要被用来对现有NIO API进行扩展,在实际的使用中,我们一般只和另外的3个包打交道。下面将对这3个包一一介绍。

Package java.nio

这个包主要定义了Buffer及其子类。Buffer定义了一个线性存放primitive type数据的容器接口。对于除boolean以外的其他primitive type,都有一个相应的Buffer子类,ByteBuffer是其中最重要的一个子类。

下面这张UML类图描述了java.nio中的类的关系:

Buffer

定义了一个可以线性存放primitive type数据的容器接口。Buffer主要包含了与类型(byte, char…)无关的功能。值得注意的是Buffer及其子类都不是线程安全的。

每个Buffer都有以下的属性:

capacity

这个Buffer最多能放多少数据。capacity一般在buffer被创建的时候指定。

limit

在Buffer上进行的读写操作都不能越过这个下标。当写数据到buffer中时,limit一般和capacity相等,当读数据时,limit代表buffer中有效数据的长度。

position

读/写操作的当前下标。当使用buffer的相对位置进行读/写操作时,读/写会从这个下标进行,并在操作完成后,buffer会更新下标的值。

mark

一个临时存放的位置下标。调用mark()会将mark设为当前的position的值,以后调用reset()会将position属性设置为mark的值。mark的值总是小于等于position的值,如果将position的值设的比mark小,当前的mark值会被抛弃掉。

这些属性总是满足以下条件:

0 <= mark <= position <= limit <= capacity

limit和position的值除了通过limit()和position()函数来设置,也可以通过下面这些函数来改变:

Buffer clear()

把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。

Buffer flip()

把limit设为当前position,把position设为0,一般在从Buffer读出数据前调用。

Buffer rewind()

把position设为0,limit不变,一般在把数据重写入Buffer前调用。

Buffer对象有可能是只读的,这时,任何对该对象的写操作都会触发一个ReadOnlyBufferException。isReadOnly()方法可以用来判断一个Buffer是否只读。

ByteBuffer

在Buffer的子类中,ByteBuffer是一个地位较为特殊的类,因为在java.io.channels中定义的各种channel的IO操作基本上都是围绕ByteBuffer展开的。

ByteBuffer定义了4个static方法来做创建工作:

ByteBuffer allocate(int capacity)

创建一个指定capacity的ByteBuffer。

ByteBuffer allocateDirect(int capacity)

创建一个direct的ByteBuffer,这样的ByteBuffer在参与IO操作时性能会更好(很有可能是在底层的实现使用了DMA技术),相应的,创建和回收direct的ByteBuffer的代价也会高一些。isDirect()方法可以检查一个buffer是否是direct的。

ByteBuffer wrap(byte [] array)

ByteBuffer wrap(byte [] array, int offset, int length)

把一个byte数组或byte数组的一部分包装成ByteBuffer。

ByteBuffer定义了一系列get和put操作来从中读写byte数据,如下面几个:

byte get()

ByteBuffer get(byte [] dst)

byte get(int index)

ByteBuffer put(byte b)

ByteBuffer put(byte [] src)

ByteBuffer put(int index, byte b)

这些操作可分为绝对定位和相对定为两种,相对定位的读写操作依靠position来定位Buffer中的位置,并在操作完成后会更新position的值。

在其它类型的buffer中,也定义了相同的函数来读写数据,唯一不同的就是一些参数和返回值的类型。

除了读写byte类型数据的函数,ByteBuffer的一个特别之处是它还定义了读写其它primitive数据的方法,如:

int getInt()

从ByteBuffer中读出一个int值。

ByteBuffer putInt(int value)

写入一个int值到ByteBuffer中。

读写其它类型的数据牵涉到字节序问题,ByteBuffer会按其字节序(大字节序或小字节序)写入或读出一个其它类型的数据(int,long…)。字节序可以用order方法来取得和设置:

ByteOrder order()

返回ByteBuffer的字节序。

ByteBuffer order(ByteOrder bo)

设置ByteBuffer的字节序。

ByteBuffer另一个特别的地方是可以在它的基础上得到其它类型的buffer。如:

CharBuffer asCharBuffer()

为当前的ByteBuffer创建一个CharBuffer的视图。在该视图buffer中的读写操作会按照ByteBuffer的字节序作用到ByteBuffer中的数据上。

用这类方法创建出来的buffer会从ByteBuffer的position位置开始到limit位置结束,可以看作是这段数据的视图。视图buffer的readOnly属性和direct属性与ByteBuffer的一致,而且也只有通过这种方法,才可以得到其他数据类型的direct buffer。

ByteOrder

用来表示ByteBuffer字节序的类,可将其看成java中的enum类型。主要定义了下面几个static方法和属性:

ByteOrder BIG_ENDIAN

代表大字节序的ByteOrder。

ByteOrder LITTLE_ENDIAN

代表小字节序的ByteOrder。

ByteOrder nativeOrder()

返回当前硬件平台的字节序。

MappedByteBuffer

ByteBuffer的子类,是文件内容在内存中的映射。这个类的实例需要通过FileChannel的map()方法来创建。

接下来看看一个使用ByteBuffer的例子,这个例子从标准输入不停地读入字符,当读满一行后,将收集的字符写到标准输出:

public static void main(String [] args)

throws IOException

{

// 创建一个capacity为256的ByteBuffer

ByteBuffer buf = ByteBuffer.allocate(256);

while (true) {

// 从标准输入流读入一个字符

int c = System.in.read();

// 当读到输入流结束时,退出循环

if (c == -1)

break;

// 把读入的字符写入ByteBuffer中

buf.put((byte) c);

// 当读完一行时,输出收集的字符

if (c == 'n') {

// 调用flip()使limit变为当前的position的值,position变为0,

// 为接下来从ByteBuffer读取做准备

buf.flip();

// 构建一个byte数组

byte [] content = new byte[buf.limit()];

// 从ByteBuffer中读取数据到byte数组中

buf.get(content);

// 把byte数组的内容写到标准输出

System.out.print(new String(content));

// 调用clear()使position变为0,limit变为capacity的值,

// 为接下来写入数据到ByteBuffer中做准备

buf.clear();

}

}

}

Package java.nio.channels

这个包定义了Channel的概念,Channel表现了一个可以进行IO操作的通道(比如,通过FileChannel,我们可以对文件进行读写操作)。java.nio.channels包含了文件系统和网络通讯相关的channel类。这个包通过Selector和SelectableChannel这两个类,还定义了一个进行异步(non-blocking)IO操作的API,这对需要高性能IO的应用非常重要。

下面这张UML类图描述了java.nio.channels中interface的关系:

Channel

Channel表现了一个可以进行IO操作的通道,该interface定义了以下方法:

boolean isOpen()

该Channel是否是打开的。

void close()

关闭这个Channel,相关的资源会被释放。

ReadableByteChannel

定义了一个可从中读取byte数据的channel interface。

int read(ByteBuffer dst)

从channel中读取byte数据并写到ByteBuffer中。返回读取的byte数。

WritableByteChannel

定义了一个可向其写byte数据的channel interface。

int write(ByteBuffer src)

从ByteBuffer中读取byte数据并写到channel中。返回写出的byte数。

ByteChannel

ByteChannel并没有定义新的方法,它的作用只是把ReadableByteChannel和WritableByteChannel合并在一起。

ScatteringByteChannel

继承了ReadableByteChannel并提供了同时往几个ByteBuffer中写数据的能力。

GatheringByteChannel

继承了WritableByteChannel并提供了同时从几个ByteBuffer中读数据的能力。

InterruptibleChannel

用来表现一个可以被异步关闭的Channel。这表现在两方面:

1. 当一个InterruptibleChannel的close()方法被调用时,其它block在这个InterruptibleChannel的IO操作上的线程会接收到一个AsynchronousCloseException。

2. 当一个线程block在InterruptibleChannel的IO操作上时,另一个线程调用该线程的interrupt()方法会导致channel被关闭,该线程收到一个ClosedByInterruptException,同时线程的interrupt状态会被设置。

接下来的这张UML类图描述了java.nio.channels中类的关系:

异步IO

异步IO的支持可以算是NIO API中最重要的功能,异步IO允许应用程序同时监控多个channel以提高性能,这一功能是通过Selector,SelectableChannel和SelectionKey这3个类来实现的。

SelectableChannel代表了可以支持异步IO操作的channel,可以将其注册在Selector上,这种注册的关系由SelectionKey这个类来表现(见UML图)。Selector这个类通过select()函数,给应用程序提供了一个可以同时监控多个IO channel的方法:

应用程序通过调用select()函数,让Selector监控注册在其上的多个SelectableChannel,当有channel的IO操作可以进行时,select()方法就会返回以让应用程序检查channel的状态,并作相应的处理。

下面是JDK 1.4中异步IO的一个例子,这段code使用了异步IO实现了一个time server:

private static void acceptConnections(int port) throws Exception {

// 打开一个Selector

Selector acceptSelector =

SelectorProvider.provider().openSelector();

// 创建一个ServerSocketChannel,这是一个SelectableChannel的子类

ServerSocketChannel ssc = ServerSocketChannel.open();

// 将其设为non-blocking状态,这样才能进行异步IO操作

ssc.configureBlocking(false);

// 给ServerSocketChannel对应的socket绑定IP和端口

InetAddress lh = InetAddress.getLocalHost();

InetSocketAddress isa = new InetSocketAddress(lh, port);

ssc.socket().bind(isa);

// 将ServerSocketChannel注册到Selector上,返回对应的SelectionKey

SelectionKey acceptKey =

ssc.register(acceptSelector, SelectionKey.OP_ACCEPT);

int keysAdded = 0;

// 用select()函数来监控注册在Selector上的SelectableChannel

// 返回值代表了有多少channel可以进行IO操作 (ready for IO)

while ((keysAdded = acceptSelector.select()) > 0) {

// selectedKeys()返回一个SelectionKey的集合,

// 其中每个SelectionKey代表了一个可以进行IO操作的channel。

// 一个ServerSocketChannel可以进行IO操作意味着有新的TCP连接连入了

Set readyKeys = acceptSelector.selectedKeys();

Iterator i = readyKeys.iterator();

while (i.hasNext()) {

SelectionKey sk = (SelectionKey) i.next();

// 需要将处理过的key从selectedKeys这个集合中删除

i.remove();

// 从SelectionKey得到对应的channel

ServerSocketChannel nextReady =

(ServerSocketChannel) sk.channel();

// 接受新的TCP连接

Socket s = nextReady.accept().socket();

// 把当前的时间写到这个新的TCP连接中

PrintWriter out =

new PrintWriter(s.getOutputStream(), true);

Date now = new Date();

out.println(now);

// 关闭连接

out.close();

}

}

}

这是个纯粹用于演示的例子,因为只有一个ServerSocketChannel需要监控,所以其实并不真的需要使用到异步IO。不过正因为它的简单,可以很容易地看清楚异步IO是如何工作的。

SelectableChannel

这个抽象类是所有支持异步IO操作的channel(如DatagramChannel、SocketChannel)的父类。SelectableChannel可以注册到一个或多个Selector上以进行异步IO操作。

SelectableChannel可以是blocking和non-blocking模式(所有channel创建的时候都是blocking模式),只有non-blocking的SelectableChannel才可以参与异步IO操作。

SelectableChannel configureBlocking(boolean block)

设置blocking模式。

boolean isBlocking()

返回blocking模式。

通过register()方法,SelectableChannel可以注册到Selector上。

int validOps()

返回一个bit mask,表示这个channel上支持的IO操作。当前在SelectionKey中,用静态常量定义了4种IO操作的bit值:OP_ACCEPT,OP_CONNECT,OP_READ和OP_WRITE。

SelectionKey register(Selector sel, int ops)

将当前channel注册到一个Selector上并返回对应的SelectionKey。在这以后,通过调用Selector的select()函数就可以监控这个channel。ops这个参数是一个bit mask,代表了需要监控的IO操作。

SelectionKey register(Selector sel, int ops, Object att)

这个函数和上一个的意义一样,多出来的att参数会作为attachment被存放在返回的SelectionKey中,这在需要存放一些session state的时候非常有用。

boolean isRegistered()

该channel是否已注册在一个或多个Selector上。

SelectableChannel还提供了得到对应SelectionKey的方法:

SelectionKey keyFor(Selector sel)

返回该channe在Selector上的注册关系所对应的SelectionKey。若无注册关系,返回null。

Selector

Selector可以同时监控多个SelectableChannel的IO状况,是异步IO的核心。

Selector open()

Selector的一个静态方法,用于创建实例。

在一个Selector中,有3个SelectionKey的集合:

1. key set代表了所有注册在这个Selector上的channel,这个集合可以通过keys()方法拿到。

2. Selected-key set代表了所有通过select()方法监测到可以进行IO操作的channel,这个集合可以通过selectedKeys()拿到。

3. Cancelled-key set代表了已经cancel了注册关系的channel,在下一个select()操作中,这些channel对应的SelectionKey会从key set和cancelled-key set中移走。这个集合无法直接访问。

以下是select()相关方法的说明:

int select()

监控所有注册的channel,当其中有注册的IO操作可以进行时,该函数返回,并将对应的SelectionKey加入selected-key set。

int select(long timeout)

可以设置超时的select()操作。

int selectNow()

进行一个立即返回的select()操作。

Selector wakeup()

使一个还未返回的select()操作立刻返回。

SelectionKey

代表了Selector和SelectableChannel的注册关系。

Selector定义了4个静态常量来表示4种IO操作,这些常量可以进行位操作组合成一个bit mask。

int OP_ACCEPT

有新的网络连接可以accept,ServerSocketChannel支持这一异步IO。

int OP_CONNECT

代表连接已经建立(或出错),SocketChannel支持这一异步IO。

int OP_READ

int OP_WRITE

代表了读、写操作。

以下是其主要方法:

Object attachment()

返回SelectionKey的attachment,attachment可以在注册channel的时候指定。

Object attach(Object ob)

设置SelectionKey的attachment。

SelectableChannel channel()

返回该SelectionKey对应的channel。

Selector selector()

返回该SelectionKey对应的Selector。

void cancel()

cancel这个SelectionKey所对应的注册关系。

int interestOps()

返回代表需要Selector监控的IO操作的bit mask。

SelectionKey interestOps(int ops)

设置interestOps。

int readyOps()

返回一个bit mask,代表在相应channel上可以进行的IO操作。

ServerSocketChannel

支持异步操作,对应于java.net.ServerSocket这个类,提供了TCP协议IO接口,支持OP_ACCEPT操作。

ServerSocket socket()

返回对应的ServerSocket对象。

SocketChannel accept()

接受一个连接,返回代表这个连接的SocketChannel对象。

SocketChannel

支持异步操作,对应于java.net.Socket这个类,提供了TCP协议IO接口,支持OP_CONNECT,OP_READ和OP_WRITE操作。这个类还实现了ByteChannel,ScatteringByteChannel和GatheringByteChannel接口。

DatagramChannel和这个类比较相似,其对应于java.net.DatagramSocket,提供了UDP协议IO接口。

Socket socket()

返回对应的Socket对象。

boolean connect(SocketAddress remote)

boolean finishConnect()

connect()进行一个连接操作。如果当前SocketChannel是blocking模式,这个函数会等到连接操作完成或错误发生才返回。如果当前SocketChannel是non-blocking模式,函数在连接能立刻被建立时返回true,否则函数返回false,应用程序需要在以后用finishConnect()方法来完成连接操作。

Pipe

包含了一个读和一个写的channel(Pipe.SourceChannel和Pipe.SinkChannel),这对channel可以用于进程中的通讯。

FileChannel

用于对文件的读、写、映射、锁定等操作。和映射操作相关的类有FileChannel.MapMode,和锁定操作相关的类有FileLock。值得注意的是FileChannel并不支持异步操作。

Channels

这个类提供了一系列static方法来支持stream类和channel类之间的互操作。这些方法可以将channel类包装为stream类,比如,将ReadableByteChannel包装为InputStream或Reader;也可以将stream类包装为channel类,比如,将OutputStream包装为WritableByteChannel。

Package java.nio.charset

这个包定义了Charset及相应的encoder和decoder。下面这张UML类图描述了这个包中类的关系,可以将其中Charset,CharsetDecoder和CharsetEncoder理解成一个Abstract Factory模式的实现:

Charset

代表了一个字符集,同时提供了factory method来构建相应的CharsetDecoder和CharsetEncoder。

Charset提供了以下static的方法:

SortedMap availableCharsets()

返回当前系统支持的所有Charset对象,用charset的名字作为set的key。

boolean isSupported(String charsetName)

判断该名字对应的字符集是否被当前系统支持。

Charset forName(String charsetName)

返回该名字对应的Charset对象。

Charset中比较重要的方法有:

返回该字符集的规范名。

Set aliases()

返回该字符集的所有别名。

CharsetDecoder newDecoder()

创建一个对应于这个Charset的decoder。

CharsetEncoder newEncoder()

创建一个对应于这个Charset的encoder。

CharsetDecoder

将按某种字符集编码的字节流解码为unicode字符数据的引擎。

CharsetDecoder的输入是ByteBuffer,输出是CharBuffer。进行decode操作时一般按如下步骤进行:

1. 调用CharsetDecoder的reset()方法。(第一次使用时可不调用)

2. 调用decode()方法0到n次,将endOfInput参数设为false,告诉decoder有可能还有新的数据送入。

3. 调用decode()方法最后一次,将endOfInput参数设为true,告诉decoder所有数据都已经送入。

4. 调用decoder的flush()方法。让decoder有机会把一些内部状态写到输出的CharBuffer中。

CharsetDecoder reset()

重置decoder,并清除decoder中的一些内部状态。

CoderResult decode(ByteBuffer in, CharBuffer out, boolean endOfInput)

从ByteBuffer类型的输入中decode尽可能多的字节,并将结果写到CharBuffer类型的输出中。根据decode的结果,可能返回3种CoderResult:CoderResult.UNDERFLOW表示已经没有输入可以decode;CoderResult.OVERFLOW表示输出已满;其它的CoderResult表示decode过程中有错误发生。根据返回的结果,应用程序可以采取相应的措施,比如,增加输入,清除输出等等,然后再次调用decode()方法。

CoderResult flush(CharBuffer out)

有些decoder会在decode的过程中保留一些内部状态,调用这个方法让这些decoder有机会将这些内部状态写到输出的CharBuffer中。调用成功返回CoderResult.UNDERFLOW。如果输出的空间不够,该函数返回CoderResult.OVERFLOW,这时应用程序应该扩大输出CharBuffer的空间,然后再次调用该方法。

CharBuffer decode(ByteBuffer in)

一个便捷的方法把ByteBuffer中的内容decode到一个新创建的CharBuffer中。在这个方法中包括了前面提到的4个步骤,所以不能和前3个函数一起使用。

decode过程中的错误有两种:malformed-input CoderResult表示输入中数据有误;unmappable-character CoderResult表示输入中有数据无法被解码成unicode的字符。如何处理decode过程中的错误取决于decoder的设置。对于这两种错误,decoder可以通过CodingErrorAction设置成:

1. 忽略错误

2. 报告错误。(这会导致错误发生时,decode()方法返回一个表示该错误的CoderResult。)

3. 替换错误,用decoder中的替换字串替换掉有错误的部分。

CodingErrorAction malformedInputAction()

返回malformed-input的出错处理。

CharsetDecoder onMalformedInput(CodingErrorAction newAction)

设置malformed-input的出错处理。

CodingErrorAction unmappableCharacterAction()

返回unmappable-character的出错处理。

CharsetDecoder onUnmappableCharacter(CodingErrorAction newAction)

设置unmappable-character的出错处理。

String replacement()

返回decoder的替换字串。

CharsetDecoder replaceWith(String newReplacement)

设置decoder的替换字串。

CharsetEncoder

将unicode字符数据编码为特定字符集的字节流的引擎。其接口和CharsetDecoder相类似。

CoderResult

描述encode/decode操作结果的类。

CodeResult包含两个static成员:

CoderResult OVERFLOW

表示输出已满

CoderResult UNDERFLOW

表示输入已无数据可用。

其主要的成员函数有:

boolean isError()

boolean isMalformed()

boolean isUnmappable()

boolean isOverflow()

boolean isUnderflow()

用于判断该CoderResult描述的错误。

int length()

返回错误的长度,比如,无法被转换成unicode的字节长度。

void throwException()

抛出一个和这个CoderResult相对应的exception。

CodingErrorAction

表示encoder/decoder中错误处理方法的类。可将其看成一个enum类型。有以下static属性:

CodingErrorAction IGNORE

忽略错误。

CodingErrorAction REPLACE

用替换字串替换有错误的部分。

CodingErrorAction REPORT

报告错误,对于不同的函数,有可能是返回一个和错误有关的CoderResult,也有可能是抛出一个CharacterCodingException。

手机开发框架

1:Sencha

在几天前,著名的JavaScript框架ExtJS宣布,将现有ExtJS整合JQTouchRaphaël库,推出适用于最前沿Touch Web的Sencha Touch框架,该框架是世界上第一个基于HTML5的Mobile App框架。同时,ExtJS也正式更名为Sencha

Sencha Touch的目的是开发复杂的网络应用,使之兼容手机和触摸屏设备。使用Android和iOS操作系统的产品都属于这类设备。

2:Titanium

Titanium 是一个跟手机平台无关的开发框架,用来开发具有本地应用效果的Web应用。当前主要支持 iPhone 和 Android 手机。

主要提供的API包括:

  • 2D/3D animations
  • Geo-location, compass, and maps
  • Augmented reality features
  • SOAP or REST API calls
  • Audio, video, and image capture and playback
  • Taps into local filesystem and SQL lite databases
  • Accesses photo gallery or address data

3:iPfaces

iPfaces 是一个易于开发移动应用的框架,如iPhone。几乎支持所有主流的服务平台,如Java, PHP 和ASP.NET。
iPfaces有2个版本:社区版和商业版。其中社区版基于GNU General 3许可,可供免费下载。商业版本提供更多的专业支持,培训和咨询服务。

Hello World示例

使用PHP iPFaces类库,只需要包含"ipfaces-php-lib-1.1.php"文件,构建component tree,在component form上调用 "render()" 方法。

  1. require "path/to/ipfaces/library/ipfaces-php-lib-1.1.php"; 
  2. $ipf_form = new IPFForm(); 
  3. $ipf_screen = $ipf_form->addScreen("screen", "Hello World Application"); 
  4. $ipf_screen->addLabel("label", "Hello World!"); 
  5. $ipf_form->render();

新浪出品sap

地址:http://sae.sina.com.cn/
支持:php+mysql
貌似还不错,起码比google app稳定的多了,可以试用一下

【测试】CruiseControl笔记

(1)
背景:
测试是项目生命周期中很重要的一部分。若人工手动的对项目代码进行测试,则比较费时,特别是当项目比较大,升级比较频繁的情况下,人工手动测试显得效率非常低。本案例介绍CruiseControl实现对项目工程进行持续集成测试,从而使项目测试实现自动化,高效率。
 
简介:
CruiseControl(有时我们简称CC)是使用java语言编写的一个持续集成工具,是 一个持续测试(CI Continuous Integration)的服务器CI 服务器。
在CC中,我们可以实现对项目的源码控制,编译,打包,发布及各种测试,如findBugs,pmd,checkStyle,junit,selenium等。而且CC提供了一个web界面使我们更加方便的查看构建项目的当前以及历史状态。
虽然CruiseControl使用java语言编写,但他并不限制你只能构建JAVA项目,你可以通过ant等脚本构建各种语言的持续集成环境。
 
(2)
CruiseControl安装:
2.1 在CuriseControl安装之前,须确保已经安装了Java,SVN,Ant。
安装java,ant,svn,且配置环境变量:
1)
JAVA配置环境变量
须配置JAVA_HOME,及到path中添加%JAVA_HOME%bin;
2)
ANT配置环境变量:
ANT_HOME,,及到path中添加%ANT_HOME%bin;
注意:
必须指向CruiseControl中的ant目录.或系统用的ant的版本要大于等于CC自带ANT版本.
若系统中以前已安装的ant版本低于CC自天带的ANT版本,则CC启动时会出错.
若环境变量中的上面的"用户变量"中定义了path,则系统变量中的path会失效.

3)
安装SVN:
在windows下,可以安装此版本:TortoiseSVN-1.6.2.16344-win32-svn-1.6.2.msi
安装命令行模式下可使用的SVN:Setup-Subversion-1.6.6.msi.
注意:
1.安装的命令行版本,要跟windows大版本相同,如上面都是1.6版本的。否则会出错。
2.须确保在命令行下,svn可以使用.因为CC启动后,会通过svn -update去配置库上更新CC中的项目代码.
  可以用svn --version测试是否在命令行模式下可使用.
2.2
安装CruiseControl
CruiseControl可以在Linux和Windows环境下安装,CruiseControl也是绿色软件,也可下载解压缩后就可以使用。
在windows环境下载,安装CruiseControl.exe方式:
1)
首先安装你的CruiseControl(简称CC),你可以选择exe的文件下载,直接安装就可以.
2)
启动:
第一种方式:
在CC的安装根目录下,双击CruiseControl.bat来启动.或命令行模式到CC根目录,运行bat文件也可.当出现:
wait for next time to build(第一个启动时) 

BuildQueue    - BuildQueue started时表示CC已经启动成功。
注意:
可以在此CruiseControl.bat文件中,配置CC的最大内存,以及启动占用的各个端口。
第二种方式:
安装完成后,我们打开
“开始菜单”->“程序”-> "CruiseControl" 就可以打开CruiseControl的服务。
“开始菜单”->“程序”-> “ReportingApp”是CruiseControl的浏览界面。
3)
查看
打开浏览器在浏览器中输入:
http://localhost:8080/dashboard即可看到当前构建工程(build 项目)总的结果,包括构建成功与失败的工程数量。同时,可以对项目进行编译,build.
http://localhost:8080/cruisecontrol/ 页面中显示了工程构建的列表,构建时间及现在的状态,并且可以对某工程强制重新构建。单击工程名可以看到工程构建的详细信息,包括构建过程中的错误与警告,单元测试的结果以及各种代码检测工具(findBugs,pmd,checkStyle,javancss等)检测结果等
(3)
CruiseControl目录及文件介绍:
apache-ant-1.7.0:CC自带的ant1.7.0.
logs下面包括日志信息,可以通过在config.xml中指定日志路径和名称;
projects:下面放的是需要进行持续集成的项目;
lib目录中放有cruisecontrol.jar和其他运行需要的jar(如运行findBugs,PMD等插件的jar包);
artifacts目录::输出目录,集成后生成的jar就保存在这里。(安装后不存在此目录,第一次运行后才会生成此目录)
webapps:下是cruisecontrol build结果的网站,
可以通过http://127.0.0.1:8080/cruisecontrol/访问;
可以通过http://127.0.0.1:8080/dashboard来进行对你的项目进行编译发布到你指定的web容器上.
(4)
CruiseControl(以下简称 CC) 主要有两个配置文件:
一个是config.xml,是CC初始化、调度等任务参数的配置;
一个是build.xml,ant执行的配置文件,CC借助ant完成指定的任务,如clean,compile,war,junit,findbug,pmd,checkstyle等。
(4.1)
CruiseControl配置文件config.xml分析
cruisecontrol的config文件,CC启动的时候会自动寻找此文件.
所有要集成到CC的项目,都要在config.xml中定义:只须在<cruisecontrol>中添加<project>及定义其子元素即可.
config.xml分析:
<cruisecontrol>    //cc的固有标签,cc中可以有多个project 
<!--这个地方的项目名称要和你的projects目录下的项目名称一样-->    
//监听器,会将当前项目的build状态,记录于status.txt中。 
     <listeners> 
     </listeners> 
//<bootstrappers>用于从svn源码控制程序更新本地版本 
      <bootstrappers> 
           //svn 
           //向ant提供当前信息 
        </bootstrappers> 
//用于检查各个源码控制系统(项目源码)中是否发生变化,如果有,则在web页面显示变化过的文件名。<modificationset>的属性quietperiod(单位为秒)定义了一个时间值。如果CC检查到了变化,会自检查到变化的源码控制系统的最后一次check in 的时间开始等待,等待时间由quietperiod决定,等待结束之后才触发创建(build)过程,主要是防止有人在check in的过程当中就触发创建过程(可能check in只做了一半,这个时候触发创建显然是不正确的).
check in:即是commit,即是将本地数据提交到配置库上;
check out即是将配置库上的数据更新到本地. 
        <modificationset quietperiod="30"
        </modificationset> 
//<schedule >定义build时间间隔为86400秒。若下面的ant标签中定义了time,则此处的时间将会失效。
//<ant>指定ant 的相关信息。buildfile定义build所需要的build.xml文件,time指定build目标的运行时间(这里定义为每天的23点59分执行) 
说明: 
interval="86400": 即是3600秒*24小时=86400秒.说明每隔86400秒,CC便会自动执行当前项目的ant命令. 
time: 若在<ant>中添加time,则上面的interval将会失效.CC会根据time设置的时间来执行ANT命令. time="2359",表示每天的23点59分执行ANT. 
        <schedule interval="86400"
        </schedule> 
//log标签的dir属性指定日志目录。merge标签的dir属性指定需要被合并文件的路径,指定路径下的文件将会与日志文件合并,一般需要合并的文件是测试结果文件,这需要注意的地方是指定路径下的文件都要被合并到日志文件中,也就是说,为了不影响cc的日志文件的准确性,在生成每次的测试结果之前需要先把上次的测试结果删除。 
         <log> 
         </log> 
// publishers的功能主要是发布build结果,我们主要用到的功能是artifactspublisher所定义的功能,也就是cc在build过程中产生的文件发布。在merge标签中我们已经知道,测试日志是cc在build过程中产生的文件,而且我们每次我们都要删除上次的测试结果,这里cc提供了一种机制让你保存测试结果,就是利用artifactspublisher标签。 
Dest定义目标目录,dir定义文件存储的起始目录。所有的文件会被cc从dir目录copy到dest目录。被copy到dest目录的文件会放在以当前时间命名的文件夹中。这里dir定义的是测试日志文件的所在目录。 
          <publishers>    //发布版本 
                <onsuccess> 
                 </onsuccess> 
           </publishers> 
        </project> 
也可以添加失败处理到发布中:如: 
     <onfailure>//失败发送邮件到响应的人员 
         <always address="接收邮件方的邮箱" /> 
     </onfailure>
(5)
CruiseControl项目配置文件build.xml
搭建CruiseControl持续集成测试平台,最主要的工作是配置好config.xml及build.xml。. config.xml:为CruiseControl的配置文件,定义了CC的何时,如何进行项目测试。
Build.xml:即是持续集成测试的项目工程中ant的配置文件。当CC执行项目测试时,将会利用ant来执行所有的逻辑处理。
因此,项目要实现的各种测试,将须到build.xml中进行定义。
5.1
clean操作,如果build之前不执行此操作,build检查到原有的class文件就不再编译。
     <delete file="${CLASS_DIR}/longcon-framework.jar" />
     <delete dir="target" quiet="true" />
  <delete dir="${CLASS_DIR}/source/classes" />
 </target>
(6)
CruiseControl集成各种测试工具:
CruiseControl持续集成测试,常用的测试工具有:findBugs,PMD,checkStyle。
集成时,只须在目标项目的build.xml配置文件中定义findBugs,PMD,checkStyle的实现过程。具体实现及findBugs,PMD,checkStyle相关知识,请参照我以下案例分析
 
(7)
当cc正在build时,不能点击http://localhost:9999/cruisecontrol/中正在build的项目,否则会后台出错,且影响本次编译的结果(有些类编译出错).
 
具体ant的用法,请参考另一个文章:《ant用法》
(8)
每次build目标项目时,在控制台都可以看到SVN更新的信息,如:(D:delete,A:add,U:update)
[cc]一月-29 16:15:07 VNBootstrapper- D    testlibcheckstyle.jar
[cc]一月-29 16:15:10 VNBootstrapper- A    testlibcheckstyle-all-5.0.jar
[cc]一月-29 16:15:10 VNBootstrapper- A    testlibsun_checks.xml
[cc]一月-29 16:15:11 VNBootstrapper- A    testlibcheckstyle-5.0.jar
[cc]一月-29 16:15:15 VNBootstrapper- A    testlibfindbugs.jar
[cc]一月-29 16:15:15 VNBootstrapper- A    testlibcheckstyle-frames.xsl
[cc]一月-29 16:15:22 VNBootstrapper- U    javacomcommonutilEncoderByMd5.java
[cc]一月-29 16:15:22 VNBootstrapper- U    build.xml
[cc]一月-29 16:15:24 VNBootstrapper- 更新到版本 244。
(9)
http://localhost:9999/cruisecontrol/index中,通过Label项可以看到当前项目被build的次数.若build 次数过多,其log会占用硬盘过多空间.可以到CruiseControllogs中,删除对应的项目或进入对应项目,删除以前的log文件.
(10)
在CC的findBug页面:FindBugs was not run against this project. ---说明在CC运行到findbug时,findbug 运行不成功所致.
(11)
JSP页面显示不了报告.-->可以了.config.xml文件中的<log>中没进行配置.

my published paper

@conference{wenchao2009modified,
  title={{ "{{A modified approach to keyword extraction based on word-similarity" }}}},
  booktitle={Intelligent Computing and Intelligent Systems, 2009. ICIS 2009. IEEE International Conference on},
  volume={3},
  pages={388--392},
  year={2009},
  organization={IEEE}
}
@conference{wenchao2010comparative,
  title={{ "{{A comparative study on Chinese word segmentation using statistical models" }}}},
  booktitle={Software Engineering and Service Sciences (ICSESS), 2010 IEEE International Conference on},
  pages={482--486},
  year={2010},
  organization={IEEE}
}

海量数据处理算法

大数据量的问题是很多面试笔试中经常出现的问题,比如baidu google 腾讯
这样的一些涉及到海量数据的公司经常会问到。

下面的方法是我对海量数据的处理方法进行了一个一般性的总结,当然这些方法可能并不能完全覆盖所有的问题,但是这样的一些方法也基本可以处理绝大多数遇到的问题。下面的一些问题基本直接来源于公司的面试笔试题目,方法不一定最优,如果你有更好的处理方法,欢迎与我讨论。

1.Bloom filter

适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集

基本原理及要点:


于原理来说很简单,位数组+k个独立hash函数。将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明存在,很明显这
个过程并不保证查找的结果是100%正确的。同时也不支持删除一个已经插入的关键字,因为该关键字对应的位会牵动到其他的关键字。所以一个简单的改进就是
counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了。

还有一个比较重要的 问题,如
何根据输入元素个数n,确定位数组m的大小及hash函数个数。当hash函数个数k=(ln2)*(m/n)时错误率最小。在错误率不大于E的情况
下,m至少要等于n*lg(1/E)才能表示任意n个元素的集合。但m还应该更大些,因为还要保证bit数组里至少一半为0,则m应
该>=nlg(1/E)*lge
大概就是nlg(1/E)1.44倍(lg表示以2为底的对数)。

举个例子我们假设错误率为0.01,则此时m应大概是n的13倍。这样k大概是8个。

注意这里m与n的单位不同,m是bit为单位,而n则是以元素个数为单位(准确的说是不同元素的个数)。通常单个元素的长度都是有很多bit的。所以使用bloom
filter内存上通常都是节省的。

扩展:

Bloom
filter将集合中的元素映射到位数组中,用k(k为哈希函数个数)个映射位是否全1表示元素在不在这个集合中。Counting
bloom filter(CBF)将位数组中的每一位扩展为一个counter,从而支持了元素的删除操作。Spectral Bloom
Filter(SBF)将其与集合元素的出现次数关联。SBF采用counter中的最小值来近似表示元素的出现频率。

问题实例:给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A,B文件共同的URL。如果是三个乃至n个文件呢?


据这个问题我们来计算下内存的占用,4G=2^32大概是40亿*8大概是340亿,n=50亿,如果按出错率0.01算需要的大概是650亿个bit。
现在可用的是340亿,相差并不多,这样可能会使出错率上升些。另外如果这些urlip是一一对应的,就可以转换成ip,则大大简单了。

2.Hashing

适用范围:快速查找,删除的基本数据结构,通常需要总数据量可以放入内存

基本原理及要点:

hash函数选择,针对字符串,整数,排列,具体相应的hash方法。

碰撞处理,一种是open hashing,也称为拉链法;另一种就是closed hashing,也称开地址法,opened
addressing。

扩展:

d-left hashing中的d是多个的意思,我们先简化这个问题,看一看2-left hashing。2-left
hashing指的是将一个哈希表分成长度相等的两半,分别叫做T1和T2,给T1和T2分别配备一个哈希函数,h1和h2。在存储一个新的key时,同
时用两个哈希函数进行计算,得出两个地址h1[key]和h2[key]。这时需要检查T1中的h1[key]位置和T2中的h2[key]位置,哪一个
位置已经存储的(有碰撞的)key比较多,然后将新key存储在负载少的位置。如果两边一样多,比如两个位置都为空或者都存储了一个key,就把新key
存储在左边的T1子表中,2-left也由此而来。在查找一个key时,必须进行两次hash,同时查找两个位置。

问题实例:

1).海量日志数据,提取出某日访问百度次数最多的那个IP。

IP的数目还是有限的,最多2^32个,所以可以考虑使用hash将ip直接存入内存,然后进行统计。

3.bit-map

适用范围:可进行数据的快速查找,判重,删除,一般来说数据范围是int的10倍以下

基本原理及要点:使用bit数组来表示某些元素是否存在,比如8位电话号码

扩展:bloom filter可以看做是对bit-map的扩展

问题实例:

1)已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。

8位最多99 999 999,大概需要99m个bit,大概10几m字节的内存即可。

2)2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。

将bit-map扩展一下,用2bit表示一个数即可,0表示未出现,1表示出现一次,2表示出现2次及以上。或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map。

4.堆

适用范围:海量数据前n大,并且n比较小,堆可以放入内存


本原理及要点:最大堆求前n小,最小堆求前n大。方法,比如求前n小,我们比较当前元素与最大堆里的最大元素,如果它小于最大元素,则应该替换那个最大元
素。这样最后得到的n个元素就是最小的n个。适合大数据量,求前n小,n的大小比较小的情况,这样可以扫描一遍即可得到所有的前n元素,效率很高。

扩展:双堆,一个最大堆与一个最小堆结合,可以用来维护中位数。

问题实例:

1)100w个数中找最大的前100个数。

用一个100个元素大小的最小堆即可。

5.双层桶划分

适用范围:第k大,中位数,不重复或重复的数字

基本原理及要点:因为元素范围很大,不能利用直接寻址表,所以通过多次划分,逐步确定范围,然后最后在一个可以接受的范围内进行。可以通过多次缩小,双层只是一个例子。

扩展:

问题实例:

1).2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。

有点像鸽巢原理,整数个数为2^32,也就是,我们可以将这2^32个数,划分为2^8个区域(比如用单个文件代表一个区域),然后将数据分离到不同的区域,然后不同的区域在利用bitmap就可以直接解决了。也就是说只要有足够的磁盘空间,就可以很方便的解决。

2).5亿个int找它们的中位数。

这个例子比上面那个更明显。首先我们将int划分为2^16个区域,然后读取数据统计落到各个区域里的数的个数,之后我们根据统计结果就可以判断中位数落到那个区域,同时知道这个区域中的第几大数刚好是中位数。然后第二次扫描我们只统计落在这个区域中的那些数就可以了。


际上,如果不是int是int64,我们可以经过3次这样的划分即可降低到可以接受的程度。即可以先将int64分成2^24个区域,然后确定区域的第几
大数,在将该区域分成2^20个子区域,然后确定是子区域的第几大数,然后子区域里的数的个数只有2^20,就可以直接利用direct
addr table进行统计了。

6.数据库索引

适用范围:大数据量的增删改查

基本原理及要点:利用数据的设计实现方法,对海量数据的增删改查进行处理。

扩展:

问题实例:

7.倒排索引(Inverted index)

适用范围:搜索引擎,关键字查询

基本原理及要点:为何叫倒排索引?一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。

以英文为例,下面是要被索引的文本:

T0 = "it is what it is"

T1 = "what is it"

T2 = "it is a banana"

我们就能得到下面的反向文件索引:

"a":      {2}
"banana": {2}

"is":    
{0, 1, 2}

"it":    
{0, 1, 2}

"what":   {0, 1}

检索的条件"what", "is" 和 "it" 将对应集合的交集。


向索引开发出来用来存储每个文档的单词的列表。正向索引的查询往往满足每个文档有序频繁的全文查询和每个单词在校验文档中的验证这样的查询。在正向索引
中,文档占据了中心的位置,每个文档指向了一个它所包含的索引项的序列。也就是说文档指向了它包含的那些单词,而反向索引则是单词指向了包含它的文档,很
容易看到这个反向的关系。

扩展:

问题实例:文档检索系统,查询那些文件包含了某单词,比如常见的学术论文的关键字搜索。

8.外排序

适用范围:大数据的排序,去重

基本原理及要点:外排序的归并方法,置换选择 败者树原理,最优归并树

扩展:

问题实例:

1).有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16个字节,内存限制大小是1M。返回频数最高的100个词。

这个数据具有很明显的特点,词的大小为16个字节,但是内存只有1m做hash有些不够,所以可以用来排序。内存可以当输入缓冲区使用。

9.trie树

适用范围:数据量大,重复多,但是数据种类小可以放入内存

基本原理及要点:实现方式,节点孩子的表示方式

扩展:压缩实现。

问题实例:

1).有10个文件,每个文件1G,
每个文件的每一行都存放的是用户的query,每个文件的query都可能重复。要你按照query的频度排序 。

2).1000万字符串,其中有些是相同的(重复),需要把重复的全部去掉,保留没有重复的字符串。请问怎么设计和实现?

3).寻找热门查询:查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个,每个不超过255字节。

10.分布式处理 mapreduce

适用范围:数据量大,但是数据种类小可以放入内存

基本原理及要点:将数据交给不同的机器去处理,数据划分,结果归约。

扩展:

问题实例:

1).The canonical example application of MapReduce is a process to
count the appearances of

each different word in a set of documents:

  // document: document
contents

  for each word w in
document:

    EmitIntermediate(w,
1);

  

void reduce(String word, Iterator partialCounts):

  // key: a word

  // values: a list of aggregated
partial counts

  int result = 0;

  for each v in
partialCounts:

    result
+= ParseInt(v);

  Emit(result);

Here, each document is split in words, and each word is counted
initially with a "1" value by

the Map function, using the word as the result key. The framework
puts together all the pairs

with the same key and feeds them to the same call to Reduce, thus
this function just needs to

sum all of its input values to find the total appearances of that
word.

2).海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10。

3).一共有N个机器,每个机器上有N个数。每个机器最多存O(N)个数并对它们操作。如何找到N^2个数的中数(median)?

经典问题分析

上千万or亿数据(有重复),统计其中出现次数最多的前N个数据,分两种情况:可一次读入内存,不可一次读入。

可用思路:trie树+堆,数据库索引,划分子集分别统计,hash,分布式计算,近似统计,外排序

所 谓的是否能一次读入内存,实际上应该指去除重复后的数据量。如果去重后数据可以放入内存,我们可以为数据建立字典,比如通过
map,hashmap,trie,然后直接进行统计即可。当然在更新每条数据的出现次数的时候,我们可以利用一个堆来维护出现次数最多的前N个数据,当
然这样导致维护次数增加,不如完全统计后在求前N大效率高。

如果数据无法放入内存。一方面我们可以考虑上面的字典方法能否被改进以适应这种情形,可以做的改变就是将字典存放到硬盘上,而不是内存,这可以参考数据库的存储方法。


然还有更好的方法,就是可以采用分布式计算,基本上就是map-reduce过程,首先可以根据数据值或者把数据hash(md5)后的值,将数据按照范
围划分到不同的机子,最好可以让数据划分后可以一次读入内存,这样不同的机子负责处理各种的数值范围,实际上就是map。得到结果后,各个机子只需拿出各
自的出现次数最多的前N个数据,然后汇总,选出所有的数据中出现次数最多的前N个数据,这实际上就是reduce过程。

实际 上可能想直
接将数据均分到不同的机子上进行处理,这样是无法得到正确的解的。因为一个数据可能被均分到不同的机子上,而另一个则可能完全聚集到一个机子上,同时还可
能存在具有相同数目的数据。比如我们要找出现次数最多的前100个,我们将1000万的数据分布到10台机器上,找到每台出现次数最多的前
100个,归并之后这样不能保证找到真正的第100个,因为比如出现次数最多的第100个可能有1万个,但是它被分到了10台机子,这样在每台上只有1千
个,假设这些机子排名在1000个之前的那些都是单独分布在一台机子上的,比如有1001个,这样本来具有1万个的这个就会被淘汰,即使我们让每台机子选
出出现次数最多的1000个再归并,仍然会出错,因为可能存在大量个数为1001个的发生聚集。因此不能将数据随便均分到不同机子上,而是要根据hash
后的值将它们映射到不同的机子上处理,让不同的机器处理一个数值范围。

而外排序的方法会消耗大量的IO,效率不会很高。而上面的分布式方法,也可以用于单机版本,也就是将总的数据根据值的范围,划分成多个不同的子文件,然后逐个处理。处理完毕之后再对这些单词的及其出现频率进行一个归并。实际上就可以利用一个外排序的归并过程。

另外还可以考虑近似计算,也就是我们可以通过结合自然语言属性,只将那些真正实际中出现最多的那些词作为一个字典,使得这个规模可以放入内存。