spark 1.1.1发布

2014年11月30日 由 acefarmer 没有评论 »

http://spark.apache.org/releases/spark-release-1-1-1.html

Java NIO 系列教程

2014年5月9日 由 acefarmer 没有评论 »
Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。本系列教程将有助于你学习和理解Java NIO。感谢并发编程网的翻译和投递。

(关注ITeye官微,随时随地查看最新开发资讯、技术文章。)

Java NIO提供了与标准IO不同的IO工作方式:

  • Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
  • Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
  • Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。

下面就来详细介绍Java NIO的相关知识。

Java NIO 概述Top

(本部分原文链接,作者:Jakob Jenkov, 译者:airu,校对:丁一)
Java NIO 由以下几个核心部分组成:

  • Channels
  • Buffers
  • Selectors

虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的API。其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类。因此,在概述中我将集中在这三个组件上。其它组件会在单独的章节中讲到。

Channel 和 Buffer

基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:

Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。

与这些类一起的有一些有趣的接口,但为简单起见,我尽量在概述中不提到它们。本教程其它章节与它们相关的地方我会进行解释。

以下是Java NIO里关键的Buffer实现:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。

Java NIO 还有个 Mappedyteuffer,用于表示内存映射文件, 我也不打算在概述中说明。

Selector

Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。

这是在一个单线程中使用一个Selector处理3个Channel的图示:

要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

Java NIO vs. IOTop

(本部分原文地址,作者:Jakob Jenkov,译者:郭蕾,校对:方腾飞)
当学习了Java NIO和IO的API后,一个问题马上涌入脑海:

引用
我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异、它们的使用场景,以及它们如何影响您的代码设计。

Java NIO和IO的主要区别

下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异。

IO NIO
Stream oriented Buffer oriented
Blocking IO Non blocking IO
Selectors

面向流与面向缓冲

Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

阻塞与非阻塞IO

Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

选择器(Selectors)

Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

NIO和IO如何影响应用程序的设计

无论您选择IO或NIO工具箱,可能会影响您应用程序设计的以下几个方面:

  • 对NIO或IO类的API调用。
  • 数据处理。
  • 用来处理数据的线程数。

API调用

当然,使用NIO的API调用时看起来与使用IO时有所不同,但这并不意外,因为并不是仅从一个InputStream逐字节读取,而是数据必须先读入缓冲区再处理。

数据处理

使用纯粹的NIO设计相较IO设计,数据处理也受到影响。

在IO设计中,我们从InputStream或 Reader逐字节读取数据。假设你正在处理一基于行的文本数据流,例如:

代码
  1. Name: Anna
  2. Age: 25
  3. Email: anna@mailserver.com
  4. Phone: 1234567890

该文本行的流可以这样处理:

Java代码
  1. InputStream input = … ; // get the InputStream from the client socket
  2. BufferedReader reader = new BufferedReader(new InputStreamReader(input));
  3. String nameLine   = reader.readLine();
  4. String ageLine    = reader.readLine();
  5. String emailLine  = reader.readLine();
  6. String phoneLine  = reader.readLine();

请注意处理状态由程序执行多久决定。换句话说,一旦reader.readLine()方法返回,你就知道肯定文本行就已读完, readline()阻塞直到整行读完,这就是原因。你也知道此行包含名称;同样,第二个readline()调用返回的时候,你知道这行包含年龄等。 正如你可以看到,该处理程序仅在有新数据读入时运行,并知道每步的数据是什么。一旦正在运行的线程已处理过读入的某些数据,该线程不会再回退数据(大多如此)。下图也说明了这条原则:


从一个阻塞的流中读数据

而一个NIO的实现会有所不同,下面是一个简单的例子:

Java代码
  1. ByteBuffer buffer = ByteBuffer.allocate(48);
  2. int bytesRead = inChannel.read(buffer);

注意第二行,从通道读取字节到ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节,这使得处理有点困难。
假设第一次 read(buffer)调用后,读入缓冲区的数据只有半行,例如,“Name:An”,你能处理数据吗?显然不能,需要等待,直到整行数据读入缓存,在此之前,对数据的任何处理毫无意义。

所以,你怎么知道是否该缓冲区包含足够的数据可以处理呢?好了,你不知道。发现的方法只能查看缓冲区中的数据。其结果是,在你知道所有数据都在缓冲区里之前,你必须检查几次缓冲区的数据。这不仅效率低下,而且可以使程序设计方案杂乱不堪。例如:

Java代码
  1. ByteBuffer buffer = ByteBuffer.allocate(48);
  2. int bytesRead = inChannel.read(buffer);
  3. while(! bufferFull(bytesRead) ) {
  4. bytesRead = inChannel.read(buffer);
  5. }

bufferFull()方法必须跟踪有多少数据读入缓冲区,并返回真或假,这取决于缓冲区是否已满。换句话说,如果缓冲区准备好被处理,那么表示缓冲区满了。

bufferFull()方法扫描缓冲区,但必须保持在bufferFull()方法被调用之前状态相同。如果没有,下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的,但却是需要注意的又一问题。

如果缓冲区已满,它可以被处理。如果它不满,并且在你的实际案例中有意义,你或许能处理其中的部分数据。但是许多情况下并非如此。下图展示了“缓冲区数据循环就绪”:


从一个通道里读数据,直到所有的数据都读到缓冲区里

总结

NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。

如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。同样,如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势。一个线程多个连接的设计方案如下图所示:


单线程管理多个连接

如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合。下图说明了一个典型的IO服务器设计:


一个典型的IO服务器设计:一个连接通过一个线程处理

通道(Channel)Top

(本部分原文链接,作者:Jakob Jenkov,译者:airu,校对:丁一)
Java NIO的通道类似流,但又有些不同:

  • 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
  • 通道可以异步地读写。
  • 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。如下图所示:

Channel的实现

这些是Java NIO中最重要的通道的实现:

  • FileChannel:从文件中读写数据。
  • DatagramChannel:能通过UDP读写网络中的数据。
  • SocketChannel:能通过TCP读写网络中的数据。
  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

基本的 Channel 示例

下面是一个使用FileChannel读取数据到Buffer中的示例:

Java代码
  1. RandomAccessFile aFile = new RandomAccessFile(“data/nio-data.txt”, ”rw”);
  2. FileChannel inChannel = aFile.getChannel();
  3. ByteBuffer buf = ByteBuffer.allocate(48);
  4. int bytesRead = inChannel.read(buf);
  5. while (bytesRead != -1) {
  6. System.out.println(“Read ” + bytesRead);
  7. buf.flip();
  8. while(buf.hasRemaining()){
  9. System.out.print((char) buf.get());
  10. }
  11. buf.clear();
  12. bytesRead = inChannel.read(buf);
  13. }
  14. aFile.close();

注意 buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。下一节会深入讲解Buffer的更多细节。

缓冲区(Buffer)Top

(本部分原文链接,作者:Jakob Jenkov,译者:airu,校对:丁一)
Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

Buffer的基本用法

使用Buffer读写数据一般遵循以下四个步骤:

  • 写入数据到Buffer
  • 调用flip()方法
  • 从Buffer中读取数据
  • 调用clear()方法或者compact()方法

当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

下面是一个使用Buffer的例子:

Java代码
  1. RandomAccessFile aFile = new RandomAccessFile(“data/nio-data.txt”, ”rw”);
  2. FileChannel inChannel = aFile.getChannel();
  3. //create buffer with capacity of 48 bytes
  4. ByteBuffer buf = ByteBuffer.allocate(48);
  5. int bytesRead = inChannel.read(buf); //read into buffer.
  6. while (bytesRead != -1) {
  7.   buf.flip();  //make buffer ready for read
  8.   while(buf.hasRemaining()){
  9.       System.out.print((char) buf.get()); // read 1 byte at a time
  10.   }
  11.   buf.clear(); //make buffer ready for writing
  12.   bytesRead = inChannel.read(buf);
  13. }
  14. aFile.close();

Buffer的capacity,position和limit

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

为了理解Buffer的工作原理,需要熟悉它的三个属性:

  • capacity
  • position
  • limit

position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。

这里有一个关于capacity,position和limit在读写模式中的说明,详细的解释在插图后面。

capacity

作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

position

当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1。

当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

limit

在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。

当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

Buffer的类型

Java NIO 有以下Buffer类型:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

如你所见,这些Buffer类型代表了不同的数据类型。换句话说,就是可以通过char,short,int,long,float 或 double类型来操作缓冲区中的字节。

MappedByteBuffer 有些特别,在涉及它的专门章节中再讲。

Buffer的分配

要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。下面是一个分配48字节capacity的ByteBuffer的例子。

Java代码
  1. ByteBuffer buf = ByteBuffer.allocate(48);

这是分配一个可存储1024个字符的CharBuffer:

Java代码
  1. CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中写数据

写数据到Buffer有两种方式:

  • 从Channel写到Buffer。
  • 通过Buffer的put()方法写到Buffer里。

从Channel写到Buffer的例子

Java代码
  1. int bytesRead = inChannel.read(buf); //read into buffer.

通过put方法写Buffer的例子:

Java代码
  1. buf.put(127);

put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。例如, 写到一个指定的位置,或者把一个字节数组写入到Buffer。 更多Buffer实现的细节参考JavaDoc。

flip()方法

flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。

从Buffer中读取数据

从Buffer中读取数据有两种方式:

  • 从Buffer读取数据到Channel。
  • 使用get()方法从Buffer中读取数据。

从Buffer读取数据到Channel的例子:

Java代码
  1. //read from buffer into channel.
  2. int bytesWritten = inChannel.write(buf);

使用get()方法从Buffer中读取数据的例子

Java代码
  1. byte aByte = buf.get();

get方法有很多版本,允许你以不同的方式从Buffer中读取数据。例如,从指定position读取,或者从Buffer中读取数据到字节数组。更多Buffer实现的细节参考JavaDoc。

rewind()方法

Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。

clear()与compact()方法

一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。

compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

mark()与reset()方法

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:

Java代码
  1. buffer.mark();
  2. //call buffer.get() a couple of times, e.g. during parsing.
  3. buffer.reset();  //set position back to mark.

equals()与compareTo()方法

可以使用equals()和compareTo()方法两个Buffer。

equals()

当满足下列条件时,表示两个Buffer相等:

  • 有相同的类型(byte、char、int等)。
  • Buffer中剩余的byte、char等的个数相等。
  • Buffer中所有剩余的byte、char等都相同。

如你所见,equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。

compareTo()方法

compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:

  • 第一个不相等的元素小于另一个Buffer中对应的元素。
  • 所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。

(译注:剩余元素是从 position到limit之间的元素)

分散(Scatter)/聚集(Gather)Top

(本部分原文地址,作者:Jakob Jenkov   译者:郭蕾)

Java NIO开始支持scatter/gather,scatter/gather用于描述从Channel(译者注:Channel在中文经常翻译为通道)中读取或者写入到Channel的操作。

分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。

聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。

scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。

Scattering Reads

Scattering Reads是指数据从一个channel读取到多个buffer中。如下图描述:

代码示例如下:

Java代码
  1. ByteBuffer header = ByteBuffer.allocate(128);
  2. ByteBuffer body   = ByteBuffer.allocate(1024);
  3. ByteBuffer[] bufferArray = { header, body };
  4. channel.read(bufferArray);

注意buffer首先被插入到数组,然后再将数组作为channel.read() 的输入参数。read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。

Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。

Gathering Writes

Gathering Writes是指数据从多个buffer写入到同一个channel。如下图描述:

代码示例如下:

Java代码
  1. ByteBuffer header = ByteBuffer.allocate(128);
  2. ByteBuffer body   = ByteBuffer.allocate(1024);
  3. //write data into buffers
  4. ByteBuffer[] bufferArray = { header, body };
  5. channel.write(bufferArray);

buffers数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。

通道之间的数据传输Top

(本部分原文地址,作者:Jakob Jenkov,译者:郭蕾,校对:周泰)
在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel。

transferFrom()

FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中(译者注:这个方法在JDK文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个简单的例子:

Java代码
  1. RandomAccessFile fromFile = new RandomAccessFile(“fromFile.txt”, ”rw”);
  2. FileChannel      fromChannel = fromFile.getChannel();
  3. RandomAccessFile toFile = new RandomAccessFile(“toFile.txt”, ”rw”);
  4. FileChannel      toChannel = toFile.getChannel();
  5. long position = 0;
  6. long count = fromChannel.size();
  7. toChannel.transferFrom(position, count, fromChannel);

方法的输入参数position表示从position处开始向目标文件写入数据,count表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。

此外要注意,在SoketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足count字节)。因此,SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。

transferTo()

transferTo()方法将数据从FileChannel传输到其他的channel中。下面是一个简单的例子:

Java代码
  1. RandomAccessFile fromFile = new RandomAccessFile(“fromFile.txt”, ”rw”);
  2. FileChannel      fromChannel = fromFile.getChannel();
  3. RandomAccessFile toFile = new RandomAccessFile(“toFile.txt”, ”rw”);
  4. FileChannel      toChannel = toFile.getChannel();
  5. long position = 0;
  6. long count = fromChannel.size();
  7. fromChannel.transferTo(position, count, toChannel);

是不是发现这个例子和前面那个例子特别相似?除了调用方法的FileChannel对象不一样外,其他的都一样。

上面所说的关于SocketChannel的问题在transferTo()方法中同样存在。SocketChannel会一直传输数据直到目标buffer被填满。

选择器(Selector)Top

(本部分原文链接,作者:Jakob Jenkov,译者:浪迹v,校对:丁一)
Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

(1)  为什么使用Selector?

仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。

但是,需要记住,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。实际上,如果一个CPU有多个内核,不使用多任务可能是在浪费CPU能力。不管怎么说,关于那种设计的讨论应该放在另一篇不同的文章中。在这里,只要知道使用Selector能够处理多个通道就足够了。

下面是单线程使用一个Selector处理3个channel的示例图:

(2)  Selector的创建

通过调用Selector.open()方法创建一个Selector,如下:

Java代码
  1. Selector selector = Selector.open();

(3) 向Selector注册通道

为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现,如下:

Java代码
  1. channel.configureBlocking(false);
  2. SelectionKey key = channel.register(selector,
  3.     Selectionkey.OP_READ);

与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。

注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:

  • Connect
  • Accept
  • Read
  • Write

通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

这四种事件用SelectionKey的四个常量来表示:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:

Java代码
  1. int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

在下面还会继续提到interest集合。

(4)  SelectionKey

在上一小节中,当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些你感兴趣的属性:

  • interest集合
  • ready集合
  • Channel
  • Selector
  • 附加的对象(可选)

下面我会描述这些属性。

interest集合

就像向Selector注册通道一节中所描述的,interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合,像这样:

Java代码
  1. int interestSet = selectionKey.interestOps();
  2. boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
  3. boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
  4. boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
  5. boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;

可以看到,用“位与”操作interest 集合和给定的SelectionKey常量,可以确定某个确定的事件是否在interest 集合中。

ready集合

ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready set。Selection将在下一小节进行解释。可以这样访问ready集合:

int readySet = selectionKey.readyOps();

可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型:

Java代码
  1. selectionKey.isAcceptable();
  2. selectionKey.isConnectable();
  3. selectionKey.isReadable();
  4. selectionKey.isWritable();

Channel + Selector

从SelectionKey访问Channel和Selector很简单。如下:

Java代码
  1. Channel  channel  = selectionKey.channel();
  2. Selector selector = selectionKey.selector();

附加的对象

可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:

Java代码
  1. selectionKey.attach(theObject);
  2. Object attachedObj = selectionKey.attachment();

还可以在用register()方法向Selector注册Channel的时候附加对象。如:

Java代码
  1. SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

(5)  通过Selector选择通道

一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。

下面是select()方法:

  • int select()
  • int select(long timeout)
  • int selectNow()

select()阻塞到至少有一个通道在你注册的事件上就绪了。

select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。

selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。

select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。

selectedKeys()

一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。如下所示:

Java代码
  1. Set selectedKeys = selector.selectedKeys();

当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。

可以遍历这个已选择的键集合来访问就绪的通道。如下:

Java代码
  1. Set selectedKeys = selector.selectedKeys();
  2. Iterator keyIterator = selectedKeys.iterator();
  3. while(keyIterator.hasNext()) {
  4.     SelectionKey key = keyIterator.next();
  5.     if(key.isAcceptable()) {
  6.         // a connection was accepted by a ServerSocketChannel.
  7.     } else if (key.isConnectable()) {
  8.         // a connection was established with a remote server.
  9.     } else if (key.isReadable()) {
  10.         // a channel is ready for reading
  11.     } else if (key.isWritable()) {
  12.         // a channel is ready for writing
  13.     }
  14.     keyIterator.<tuihighlight class=”tuihighlight”><a href=”javascript:;” style=”display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;” onclick=”return false;”>remove</a></tuihighlight>();
  15. }

这个循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件。

注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。

SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。

(6)  wakeUp()

某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。

如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。

(7)  close()

用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

(8)  完整的示例

这里有一个完整的示例,打开一个Selector,注册一个通道注册到这个Selector上(通道的初始化过程略去),然后持续监控这个Selector的四种事件(接受,连接,读,写)是否就绪。

Java代码
  1. Selector selector = Selector.open();
  2. channel.configureBlocking(false);
  3. SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
  4. while(true) {
  5.   int readyChannels = selector.select();
  6.   if(readyChannels == 0) continue;
  7.   Set selectedKeys = selector.selectedKeys();
  8.   Iterator keyIterator = selectedKeys.iterator();
  9.   while(keyIterator.hasNext()) {
  10.     SelectionKey key = keyIterator.next();
  11.     if(key.isAcceptable()) {
  12.         // a connection was accepted by a ServerSocketChannel.
  13.     } else if (key.isConnectable()) {
  14.         // a connection was established with a remote server.
  15.     } else if (key.isReadable()) {
  16.         // a channel is ready for reading
  17.     } else if (key.isWritable()) {
  18.         // a channel is ready for writing
  19.     }
  20.     keyIterator.<tuihighlight class=”tuihighlight”><a href=”javascript:;” style=”display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;” onclick=”return false;”>remove</a></tuihighlight>();
  21.   }
  22. }

文件通道Top

(本部分原文链接,作者:Jakob Jenkov,译者:周泰,校对:丁一)
Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。

FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

打开FileChannel

在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。下面是通过RandomAccessFile打开FileChannel的示例:

Java代码
  1. RandomAccessFile aFile = new RandomAccessFile(“data/nio-data.txt”, ”rw”);
  2. FileChannel inChannel = aFile.getChannel();

从FileChannel读取数据

调用多个read()方法之一从FileChannel中读取数据。如:

Java代码
  1. ByteBuffer buf = ByteBuffer.allocate(48);
  2. int bytesRead = inChannel.read(buf);

首先,分配一个Buffer。从FileChannel中读取的数据将被读到Buffer中。

然后,调用FileChannel.read()方法。该方法将数据从FileChannel读取到Buffer中。read()方法返回的int值表示了有多少字节被读到了Buffer中。如果返回-1,表示到了文件末尾。

向FileChannel写数据

使用FileChannel.write()方法向FileChannel写数据,该方法的参数是一个Buffer。如:

Java代码
  1. String newData = ”New String to write to file…” + System.currentTimeMillis();
  2. ByteBuffer buf = ByteBuffer.allocate(48);
  3. buf.clear();
  4. buf.put(newData.getBytes());
  5. buf.flip();
  6. while(buf.hasRemaining()) {
  7.     channel.write(buf);
  8. }

注意FileChannel.write()是在while循环中调用的。因为无法保证write()方法一次能向FileChannel写入多少字节,因此需要重复调用write()方法,直到Buffer中已经没有尚未写入通道的字节。

关闭FileChannel

用完FileChannel后必须将其关闭。如:

Java代码
  1. channel.close();

FileChannel的position方法

有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取FileChannel的当前位置。

也可以通过调用position(long pos)方法设置FileChannel的当前位置。

这里有两个例子:

Java代码
  1. long pos = channel.position();
  2. channel.position(pos +123);

如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志。

如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。

FileChannel的size方法

FileChannel实例的size()方法将返回该实例所关联文件的大小。如:

Java代码
  1. long fileSize = channel.size();

FileChannel的truncate方法

可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:

Java代码
  1. channel.truncate(1024);

这个例子截取文件的前1024个字节。

FileChannel的force方法

FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。

force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。

下面的例子同时将文件数据和元数据强制写到磁盘上:

Java代码
  1. channel.force(true);

Socket 通道Top

(本部分原文链接,作者:Jakob Jenkov,译者:郑玉婷,校对:丁一)
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:

  • 打开一个SocketChannel并连接到互联网上的某台服务器。
  • 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。

打开 SocketChannel

下面是SocketChannel的打开方式:

Java代码
  1. SocketChannel socketChannel = SocketChannel.open();
  2. socketChannel.connect(new InetSocketAddress(“http://jenkov.com”, 80));

关闭 SocketChannel

当用完SocketChannel之后调用SocketChannel.close()关闭SocketChannel:

Java代码
  1. socketChannel.close();

从 SocketChannel 读取数据

要从SocketChannel中读取数据,调用一个read()的方法之一。以下是例子:

Java代码
  1. ByteBuffer buf = ByteBuffer.allocate(48);
  2. int bytesRead = socketChannel.read(buf);

首先,分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中。

然后,调用SocketChannel.read()。该方法将数据从SocketChannel 读到Buffer中。read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)。

写入 SocketChannel

写数据到SocketChannel用的是SocketChannel.write()方法,该方法以一个Buffer作为参数。示例如下:

Java代码
  1. String newData = ”New String to write to file…” + System.currentTimeMillis();
  2. ByteBuffer buf = ByteBuffer.allocate(48);
  3. buf.clear();
  4. buf.put(newData.getBytes());
  5. buf.flip();
  6. while(buf.hasRemaining()) {
  7.     channel.write(buf);
  8. }

注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。

非阻塞模式

可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。

connect()

如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。像这样:

Java代码
  1. socketChannel.configureBlocking(false);
  2. socketChannel.connect(new InetSocketAddress(“http://jenkov.com”, 80));
  3. while(! socketChannel.finishConnect() ){
  4.     //wait, or do something else…
  5. }

write()

非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。

read()

非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。

非阻塞模式与选择器

非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等。Selector与SocketChannel的搭配使用会在后面详讲。

ServerSocket 通道Top

(本部分原文链接,作者:Jakob Jenkov,译者:郑玉婷,校对:丁一)
Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。

这里有个例子:

Java代码
  1. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  2. serverSocketChannel.socket().bind(new InetSocketAddress(9999));
  3. while(true){
  4.     SocketChannel socketChannel =
  5.             serverSocketChannel.accept();
  6.     //do something with socketChannel…
  7. }

打开 ServerSocketChannel

通过调用 ServerSocketChannel.open() 方法来打开ServerSocketChannel.如:

Java代码
  1. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

关闭 ServerSocketChannel

通过调用ServerSocketChannel.close() 方法来关闭ServerSocketChannel. 如:

Java代码
  1. serverSocketChannel.close();

监听新进来的连接

通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此,accept()方法会一直阻塞到有新连接到达。

通常不会仅仅只监听一个连接,在while循环中调用 accept()方法. 如下面的例子:

Java代码
  1. while(true){
  2.     SocketChannel socketChannel =
  3.             serverSocketChannel.accept();
  4.     //do something with socketChannel…
  5. }

当然,也可以在while循环中使用除了true以外的其它退出准则。

非阻塞模式

ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null。如:

Java代码
  1. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  2. serverSocketChannel.socket().bind(new InetSocketAddress(9999));
  3. serverSocketChannel.configureBlocking(false);
  4. while(true){
  5.     SocketChannel socketChannel =
  6.             serverSocketChannel.accept();
  7.     if(socketChannel != null){
  8.         //do something with socketChannel…
  9.     }
  10. }

Datagram 通道Top

(本部分原文链接,作者:Jakob Jenkov,译者:郑玉婷,校对:丁一)
Java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。

打开 DatagramChannel

下面是 DatagramChannel 的打开方式:

Java代码
  1. DatagramChannel channel = DatagramChannel.open();
  2. channel.socket().bind(new InetSocketAddress(9999));

这个例子打开的 DatagramChannel可以在UDP端口9999上接收数据包。

接收数据

通过receive()方法从DatagramChannel接收数据,如:

Java代码
  1. ByteBuffer buf = ByteBuffer.allocate(48);
  2. buf.clear();
  3. channel.receive(buf);

receive()方法会将接收到的数据包内容复制到指定的Buffer. 如果Buffer容不下收到的数据,多出的数据将被丢弃。

发送数据

通过send()方法从DatagramChannel发送数据,如:

Java代码
  1. String newData = ”New String to write to file…” + System.currentTimeMillis();
  2. ByteBuffer buf = ByteBuffer.allocate(48);
  3. buf.clear();
  4. buf.put(newData.getBytes());
  5. buf.flip();
  6. int bytesSent = channel.send(buf, new InetSocketAddress(“jenkov.com”, 80));

这个例子发送一串字符到”jenkov.com”服务器的UDP端口80。 因为服务端并没有监控这个端口,所以什么也不会发生。也不会通知你发出的数据包是否已收到,因为UDP在数据传送方面没有任何保证。

连接到特定的地址

可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据。

这里有个例子:

Java代码
  1. channel.connect(new InetSocketAddress(“jenkov.com”, 80));

当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。只是在数据传送方面没有任何保证。这里有几个例子:

Java代码
  1. int bytesRead = channel.read(buf);
  2. int bytesWritten = channel.write(but);

管道(Pipe)Top

(本部分原文链接,作者:Jakob Jenkov,译者:黄忠,校对:丁一)
Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。

这里是Pipe原理的图示:

创建管道

通过Pipe.open()方法打开管道。例如:

Java代码
  1. Pipe pipe = Pipe.open();

向管道写数据

要向管道写数据,需要访问sink通道。像这样:

Java代码
  1. Pipe.SinkChannel sinkChannel = pipe.sink();

通过调用SinkChannel的write()方法,将数据写入SinkChannel,像这样:

Java代码
  1. String newData = ”New String to write to file…” + System.currentTimeMillis();
  2. ByteBuffer buf = ByteBuffer.allocate(48);
  3. buf.clear();
  4. buf.put(newData.getBytes());
  5. buf.flip();
  6. while(buf.hasRemaining()) {
  7.     <b>sinkChannel.write(buf);</b>
  8. }

从管道读取数据

从读取管道的数据,需要访问source通道,像这样:

Java代码
  1. Pipe.SourceChannel sourceChannel = pipe.source();

调用source通道的read()方法来读取数据,像这样:

Java代码
  1. ByteBuffer buf = ByteBuffer.allocate(48);
  2. int bytesRead = inChannel.read(buf);

read()方法返回的int值会告诉我们多少字节被读进了缓冲区。

分享到:  
评论 共 22 条

22 楼 feihangchen 2014-05-09 14:35 引用
特别好
21 楼 tianyi1 2014-05-06 17:13 引用
20 楼 ytzhsh 2014-05-05 15:50 引用
lzzzl 写道
全文比较长,想打个比方归纳一下。
原文中说了最重要的3个概念,

Channel 通道

Buffer 缓冲区

Selector 选择器

其中Channel对应以前的流,Buffer不是什么新东西,Selector是因为nio可以使用异步的非堵塞模式才加入的东西。

以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于水管没有阀门,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上。

nio的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,都可以得到妥善接纳,这个关键之处就是增加了一个接水工,也就是Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到一定程度的时候,就切换一下:临时关上当前水龙头,试着打开另一个水龙头(看看有没有水)。

当其他人需要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其他人虽然也可能要等,但不会在现场等,而是回家等,可以做其它事去,水接满了,接水工会通知他们。

这其实也是非常接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个并行处理,虽然那样是最简单的,但也是最浪费资源的方式。

总结的很好。

19 楼 freerambo 2014-05-05 13:18 引用
  
18 楼 摇摆d枫 2014-05-04 17:52 引用
mark一下
17 楼 风筝-Fly 2014-05-04 10:44 引用
16 楼 young7 2014-05-03 19:03 引用
不错的入门文章,记录一下先
15 楼 kennethhs 2014-05-02 16:37 引用
好东西,收藏
14 楼 lzzzl 2014-05-02 12:08 引用
全文比较长,想打个比方归纳一下。
原文中说了最重要的3个概念,

Channel 通道

Buffer 缓冲区

Selector 选择器

其中Channel对应以前的流,Buffer不是什么新东西,Selector是因为nio可以使用异步的非堵塞模式才加入的东西。

以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于水管没有阀门,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上。

nio的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,都可以得到妥善接纳,这个关键之处就是增加了一个接水工,也就是Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到一定程度的时候,就切换一下:临时关上当前水龙头,试着打开另一个水龙头(看看有没有水)。

当其他人需要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其他人虽然也可能要等,但不会在现场等,而是回家等,可以做其它事去,水接满了,接水工会通知他们。

这其实也是非常接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个并行处理,虽然那样是最简单的,但也是最浪费资源的方式。

http://www.iteye.com/magazines/132-Java-NIO

高性能数据库引擎 CoolHash 产品宣言

2014年5月9日 由 acefarmer 没有评论 »

Fourinone4.0版新特性:一个高性能的数据库引擎CoolHash(酷哈嘻)

一、前言:如何写一个数据库

如果将操作系统和业务应用之间的软件都统称中间件的话,那么最重要的软件无疑是数据库,它比web应用服务器市场更大,几乎所有的业务系统都需要数据库,所有的企业都会购买数据库。无论是早期的商业智能也好,数据分析挖掘也好,近年的分布式存储也好,大数据也好…围绕数据变着花样的新理念新技术再多,都是“乱花渐欲迷人眼,浅草才能没马蹄”,其实最核心的还是数据库技术。

就像华为想进军软件市场已经很久了,我脑海里一直惦记着数据库技术,几年来不断收集数据库实现技术,但是一直不得要领,几欲放弃,没有碰到一个白胡子老头传授秘籍,国内也几乎没有这方面的书籍,找了几本国外的教材,要么翻译的不好,要么英文啃起来费劲,书里更多是讲数据库相关的基础知识,但是不会告诉你怎么做,好比是你想学变脸,但是总在教你唱戏,其实你只想知道变脸是用线拉的就行了。行业技术机密都是避而不谈的,感觉从书本上是学不到的。

去年给一个银行技术老总讲解大数据方案,他突然问到,你们能不能不用别人的,自己写个ORACLE这样的数据库出来,我们每年花在license和服务费非常昂贵,升级也很痛苦,但是也没有办法。我承诺可以抽业余时间研究一下,不过他马上又说,就算有也不会马上用,可以先开源出来,可以看出他无比纠结的心态,也许他对太多架构师说过这样的话,可能现在都忘了,但是这件事再次给了我触动,言语之间能感受到客户寄托出的一种期望,希望中国企业能成长为ORACLE、IBM这样的角色。

要实现一个数据库,首先不能不谈起数据库引擎。数据库引擎和数据库产品的关系,就像汽车发动机和汽车的关系,有了发动机,剩下的只是装配工作。知名的数据库引擎有“ISAM、MyISAM、InnoDB、PostgreSQL、BerkeleyDB等”,另外也有些产品模糊在“数据库引擎、数据库server、数据库管理系统”之间的,如近几年的redis,还有JDK6.0起自带一个java编写的只有2m大的关系数据库Derby(由IBM捐献),另外值得一提的还有SQLite,一个很轻量级嵌入式的数据库(3万c代码,250k大小),但是功能齐全,实现了ACID和SQL标准,目前广泛应用于苹果的Mac操作系统和Android移动操作系统中,缺点是多用户高并发承受能力较弱。

世界上大部分的数据库产品都是围绕部分数据库引擎扩充出来的,比如大家熟悉的Mysql,它的数据库引擎叫MyISAM,MyISAM是在ISAM发展而来,ISAM也是一个知名的数据库引擎,最初被IBM开发,它读的性能大于写,但是索引功能和事务处理缺乏,MyISAM相对于ISAM做了很多改进,优化了表锁和并发操作,但是由于继承的原因,仍然倾向于多读少写操作。ISAM系列引擎的大致设计原理:采用B树设计,分成表元数据、表数据、表索引3部分存储,读的快是因为维持大量的索引结构指向数据存储位置,但是由于删除更新容易导致大量数据碎片和空间浪费,常常需要执行“OPTIMIZE TABLE”,从而又导致索引常常需要重新计算。Mysql5.5以后默认采用InnoDB引擎,还可以使用BerkeleyDB,InnoDB和BerkeleyDB包括了对事务处理和外键的支持,这是ISAM系列引擎所没有的特性,另外InnoDB的锁设计的要精明一点,锁到少数行的数据块上,而不是整表锁。

关于ISAM系列的发展有很多,IBM开发了VSAM代替ISAM,VSAM被IBM一个数据库产品所使用 ,就是大名鼎鼎的DB2。虽然MySql的数据库引擎换成了InnoDB,但是MySql的作者迈克尔(Michael Widenius)从MySql旧版本发展了另一个分支,并以自己的小女儿名字命名为MariaDB(玛丽亚),MariaDB的数据库引擎叫做Aria,但实际上还是MyISAM,只是增加了些key缓存改进(Segmented Key Cache),MariaDB数据库得到了google等企业的大力支持,普通的观点认为oralce收购Mysql的最终目的是想其慢慢死亡,而不是想着如何把它发展更好。

PostgreSQL和BerkeleyDB都是来源于加州伯克利分校,一个面向关系数据,一个面向k/v数据,虽然知名和流行程度不如Mysql,但是也各有优势,PostgreSQL的关系数据库功能实现的比较完整,包括了很多高级特性,并且采用BSD/MIT开源协议,因此比较适合用来封装成数据库产品,这种协议允许任何人使用修改代码,但是要保留版权声明,BerkeleyDB提供了一个高并发访问的k/v数据库引擎,但不是一个数据库server,不提供网络访问,不支持sql,但可以支持函数式操作,数据库产品HyperTable和MemcacheDB内部都使用了BerkeleyDB。

综上所述,我们可以看出:

  1. 大部分数据库产品都是都是近亲结婚,由少数数据库引擎(如ISAM、PostgreSQL、BerkeleyDB等)发展而来。(但是oracle和sqlserver的数据库引擎是内置的,鲜有人知)
  2. 你会惊奇的发现,MyISAM、BerkeleyDB、InnoDB等大部分数据库引擎的vendor都是oracle,oracle公司一直处心积虑的收购着市场上有竞争力的数据库引擎,用来捍卫自己的垄断地位,
  3. 几乎所有的数据库引擎都是GPL协议,擅自闭源用于商业将承担法律责任。

因此,要写一个数据库,首先要从实现数据库引擎入手,掌握数据库引擎技术有重要意义,因为无论是“关系数据库还是K/V数据库,SQL数据库还是NOSQL数据库,分布式数据库还是并行数据库,列数据库还是对象数据库…”,存储引擎部分都有着相似的实现原理,掌握了数据库引擎技术后,只要公司愿意投资,就可以长足拓展到任何数据库领域,一切只是工作量问题,数据库引擎一经研发成功,就值得长期放养,哪怕是5年10年。同时对个人职业意义来说,对一个玩了一辈子软件技术的架构师,没有写过数据库是一种遗憾,就跟喜欢女优的工程师没有见过真人一样。

二、CoolHash是个什么样的数据库

1、CoolHash是一个数据库引擎

CoolHash只做数据库最基础核心的引擎部分,支持大部分数据类型的“插入、获取、更新、删除、批量插入、批量获取、批量更新、批量删除、高效查询(精确查、模糊查、按树节点查、按key查、按value查)、分页,排序、and操作、or操作、事务处理、key指针插入和查询、缓存持久互转”等操作和远程操作。

其他的“监控、管理、安全、备份、命令行操作、运维工具”等外围特性都剥离出去不做,开发者可以根据自己需求扩充这些功能,CoolHash也不做自动扩容,CoolHash认为分布式集群特性也可以在外围通过“分库分表”或者“分布式扩容”等中间件技术去做,目前国内很多企业都具备基于开源软件做外围中间件的能力,这样,CoolHash只维持一个高性能又轻量级的最小存储引擎。

CoolHash高度产品化,易用性强,容易嵌入使用和复制传播(200k大小),采用apache2.0开源协议,使用java实现(jdk1.7),对外提供java接口,同时支持windows和linux(unix-like),由于依赖底层操作系统,windows和linux的实现稍有不同。

2、CoolHash是一个k/v数据库

实现数据库存储结构索引有多种方法,有比较平衡减少深入的b树、b+树系列,结合内存再合并的LSM-tree系列(bigTable),借鉴字典索引技术的Trie树(前缀树、三叉树)等,不过对于key/value结构,感觉最合适的还是Hash,不过java里的Hash算法是实现在内存组件里,无法持久化,只能快速读写,但是无法模糊查询,传统的Hash不是一个“cool”的Hash,需要进行改进。

CoolHash改进后的Hash算法是一个完整的key树结构,我们知道传统Hash的key和key之间没有关系,互相独立,但是在CoolHash里,key可以表示为“user.001.name”的用“.”分开的树层次结构,如“user.001.age”和“user.001.name”都属于“user.001.*”分支,既可独立获取,也可以从父节点查询,还可以“user.*.name”方式只查询所有子节点。

提出key指针的概念:CoolHash的key可以是一个指针,指向其他树的key,这样能将两棵key树连起来,这样的设计能避免大量join操作,如果两棵key树没有直接关系,需要动态join将会非常耗时,但有了key指针可以很好的描述数据之间“1对1、1对多、多对多”的关联关系。key指针也不同于数据库外健,它可以模糊指,比如只模糊指向一个key前缀“user.001”,再补充需要获取的属性形成一个完整key;还能连续指,比如一个key指针指向另一个key指针再指向其他key指针,连续指可以将更多的树联系起来形成一个大的数据森林。CoolHash能很好的控制key指针最终值返回、key指针死循环、读写死锁等问题。

我们知道关系数据库的数据表是一个行列的二维矩阵的数据结构,表和表之间没有层次感,一个表不会是另一个表的父子节点,这就导致需要大量关联,从一个表获取另一个表的数据需要通过join操作,同时由于表和表之间彼此独立,又导致数据量大后join性能不好,多个矩阵需要动态的“求并”“求或”获取主健,再返回所有属性。很多朋友是开发业务系统出身,复杂一点的业务逻辑通常需要关联7-8个数据库表甚至更多,是相当头疼的事情。

这种矩阵行列式结构还有个不好,就是一加全加,一减全减,一行加了一列属性,所有行都要跟着加,假如有个“子女”属性,有的人有1个子女,有的人有2-3个子女,这个属性应该有时是1列,有时是3列,有时没有才好。CoolHash的父节点下的key值可以任意添加和减少,或者再任意向下扩充叶子结点,也可以任意查询,能够非常灵活,比如“子女”属性可以这样表示第一个小孩:“user.001.children.001”。

关系数据库还容易产生数据碎片,需要经常执行“OPTIMIZE TABLE”或者“压缩数据库”等操作回收空间,CoolHash对此做了设计和算法改进,对于大量数据频繁写入和删除能很好重复利用存储空间。

key和value的数据格式:CoolHash的key只能是字符串,默认最大长度为256字节。value能支持非常广泛的数据类型,基本数据类型“String(变长字符)、short(短整形)、int(整型)、long(长整形)、 double(双精度浮点型)、 float(浮点型)、Date(日期型)”,高级数据类型的大部分的java集合都能支持(List、Map、Set等),以及任意可序列化的自定义java类型,底层数据类型也可以支持二进制型。基本数据类型的value可支持按内容查询,高级数据类型和二进制类型不支持按内容查询。所有类型的value默认最大长度为2M,默认配置保证一对key/value长度不会太大,能够容易加载到内存进行处理,但是key和value的最大长度、以及region大小都是可以根据计算机性能进行配置。

CoolHash还支持HashMap缓存和持久化的统一和互相转换,并共用一套API,可以将数据放入CoolHashMap,也可以放入持久化,可以将CoolHashMap对象转成持久化,也可以从持久化数据里直接获取CoolHashMap对象。

3、CoolHash是一个并行数据库(mpp)

前几年有篇文章,国外的数据库大牛猛烈抨击map/reduce,让重复造轮子的人应该去学习一下关系数据库几十年的理论和实践积累,一个借助蛮力而不善于设计索引的数据库系统是愚蠢和低效的。这一方面说明了map/reduce技术已经侵犯到了传统数据库领域的核心利益,另一方面也暴露了分布式存储技术的某些不足。

这里的蛮力就是并行计算,在CoolHash的底层,会维持一个数据工人进程组,根据计算机性能少则几个多则上百个,对于数据库的每种操作,数据工人们像演奏交响乐一样,时而独奏,时而合奏,统一调度,紧密协作的完成任务,CoolHash从一开始就是高度并行化的设计,数据库引擎本质就是在寻求cpu、内存、硬盘的充分利用和均衡利用,“一力胜十巧”,在大量数据的读写和查询中并行计算的效果犹为明显。

同时一个好的数据库引擎应该是蛮力和技巧的结合,Hbase的一个重要启示,就是可以灵活设计它的key达到近似索引的效果,CoolHash将此特性发挥的更深入,树型结构的key本身就是最好的索引,除外CoolHash几乎不需要另外再建立索引,只需要按照业务数据特点设计好你的key。并行计算和索引的结合会得到一个很好的互补,让你的索引结构不需要维持的太精准而节省开销,举个例子:我们从一个城市找一个人,一种方法是我们有精确的索引,知道他在哪个区哪个楼那个房间,另一种是我们只大致知道他在哪栋楼,但是我们有几百个工人可以每间房同时去找,这样也能很快找到。

4、CoolHash是一个nosql数据库

nosql不等于没有sql的功能,关系数据库的sql仍然是非常方便的交互语言,而且有专门的标准,CoolHash通过函数方式实现大部分sql的功能,如果需要扩充sql支持,在外围用正则表达式做一个sql解析,然后调用CoolHash的函数支持即可。

由于CoolHash没有关系数据库的“db、table、row、col”等概念,使用树型key取代了,所以sql create语句功能就不需要了。

  • “put()函数”对应于“sql insert、update”语句
  • “remove()函数”对应于“sql delete…where”语句
  • “put(map)函数”对应于“sql insert/update into…select…”语句
  • “get()函数”对应于“sql select…where id=?”语句
  • “get(map)”函数对应于“sql select *…”语句
  • “find(*,filter)”函数对于于“sql select * where col=%x%”语句
  • “and()函数”对应于“sql and”语句
  • “or()函数”对应于“sql or”语句
  • “sort(comp)函数”对应于“sql desc/group”语句

5、CoolHash实现了事务处理

CoolHash实现了ACID事务属性,对写入、更新、删除的基本操作提供事务处理,在程序里调用begin()、commit()、rollback()等事务方法。

  • 原子性(Atomic):事务内操作要么提交全部生效,要么回滚全部撤消。
  • 一致性(Consistent):事务操作前后的数据状态保持一致,一致性跟原子性密切相关,比如银行转账前后,两个账户累加总和保持一致。
  • 隔离性(Isolated):多个事务操作时,互相不能有影响,保持隔离。
  • 持续性(Durable) :事务提交后需要持久化生效。

另外,对于多个事务并发操作数据的情况,JDBC规范归纳出“脏读(dirty read)、不可重复读(non-repeatable read) 、幻读(phantom read) ”三种问题,并提出了4种事务隔离级让软件制造商去实现,按照不同等级去容忍这三个问题,其实这种逐级容忍大部分都不实用,一个事务操作未提交时,按照事务隔离级,其他访问应该是读取到该事务开始前的数据,而不应该把事情搞复杂,CoolHash实现的是“TRANSACTION_SERIALIZABLE”,禁止容忍三种读取问题。

6、CoolHash是一个数据库Server

CoolHash支持远程网络访问,服务端发布一个IP和监听端口,客户端连接该IP端口即可进行远程操作,CoolHash可承受多用户高并发的网络连接访问,来源于服务端设计的相似之处,大家知道Apache HTTP Server是一个多进程模型+共享内存方式实现,在前面讲到的CoolHash会维持一个数据工人的进程组,数据工人不仅是并行计算的执行者,同时也是网络请求的响应者,数据工人身兼多职能很好的将服务端设计统一起来,避免重复设计,因此CoolHash也是一个很好的网络Server.

7、CoolHash的测试性能

有句话叫做“人生如白驹过隙”,用来形容性能就是一瞬间,数据库性能最好能接近缓存,或者直接可以当作缓存来用,能在瞬间完成读写和各种查询,这个瞬间就是秒。只有在几秒内完成操作才能做到实时交互没有等待感,否则就要离线交互。

  • CoolHash的单条写入和读取速度都在毫秒级别,写和读差别不大,读略快于写。
  • CoolHash的批量写入和读取速度都控制在秒级别,100万数据写入基准测试,普通台式机或者笔记本(4核4g)需要5-6秒,标准pc server(24核256g)需要2-3秒;批量读、批量删除和批量写入的速度差不多。
  • CoolHash写入缓存和写入持久的速度差别不大,100万数据写入缓存基准测试,普通台式机或者笔记本(4核4g)需要5秒左右,标准pc server(24核256g)需要2秒左右。如果是10万级别的数据读写,缓存和持久的速度大致接近等同。
  • CoolHash的查询速度控制在秒级别,100万数据的模糊查询(如like%str%)在没有构建索引情况下,普通台式机或者笔记本(4核4g)需要2-3秒,标准pc server(24核256g)需要1-2秒;如果是重复查询,由于CoolHash内部做了数据内存映射,第二次以后只需要毫秒级完成。
  • 高并发多客户端的吞吐量总体速度要快于单客户端,但是受服务器cpu、内存、io等性能限制,会倾向于一个平衡值。

数据库引擎的性能通常也会受“key/value数据大小、数据类型、工人数量、硬件配置(内存大小、cpu核数、硬盘io、网络耗用)”等等因素影响。

  • 比如同样数量但是单条key/value数据很大,整体速度要慢一些;
  • 基本数据类型的读写速度要快过高级数据类型(如java集合类);
  • 工人数量也有影响,对于单条写入读取单工人和多工人差不多,但是批量操作和查询多工人要好过单工人,普通台式机或者笔记本(4核4g)维持在8-10个工人可以打满cpu,标准pc server(24核256g)最大可以维持到100个左右工人,但是工人数量就算能打满cpu,也受限于后端硬盘io,到一定程度速度不再增长;
  • 硬件配置对于性能的影响很大,普通笔记本的测试效果明显不如标准pc server,笔记本内存较小,硬盘io弱,不适合做数据库服务器。内存大的服务器测试效果好,加载数据和jvm垃圾回收速度会更快,目前测试采用的都是传统SAS/SATA硬盘,如果采用固态硬盘进行硬件升级,随机IO性能会得到进一步提升;
  • 另外,由于存在网络耗用和序列化传送,远程网络操作的速度要比本地速度慢,但是局域网内速度会接近本地速度,这是因为远程网络操作涉及带宽接入限制和线路共享等复杂消耗,而局域网内主要取决于物理设备速度。

关于CoolHash性能的更多体验,欢迎有兴趣的朋友根据demo在各自的机器环境下,模拟各种极端条件下去压测。

三、如何使用CoolHash

ch追求极简的编程体验,不需要安排配置,服务端启动CoolHashServer,指定好ip和端口,客户端大致编程步骤如下:

代码
  1. CoolHashClient chc = BeanContext.getCoolHashClient(“localhost”,2014);//连接CoolHashServer
  2. chc.put(“user.001.name”,”zhang”);//写入字符
  3. chc.put(“user.001.age”,20);//写入整数
  4. chc.put(“user.001.weight”,50.55f);//写入浮点数
  5. chc.put(“user.001.pet”,new ArrayList());//写入集合对象
  6. String name = (String)chc.get(“user.001.name”);//读取字符
  7. int age = (int)chc.get(“user.001.age”);//读取整数
  8. float weight = (float)chc.get(“user.001.weight”);//读取浮点数
  9. ArrayList pet = (ArrayList)chc.get(“user.001.pet”);//读取集合对象
  10. chc.put(“user.002.name”,”Li”);
  11. chc.put(“user.002.age”,25);
  12. chc.put(“user.002.weight”,60.55f);
  13. CoolKeyResult keyresult = chc.findKey(“user.001.*”);//查找用户001的所有属性
  14. CoolKeySet ks = keyresult.nextBatchKey(4);//分页获取前4条结果
  15. System.out.println(ks);//输出[user.001.weight, user.001.age, user.001.name, user.001.pet]
  16. CoolHashResult mapresult = chc.find(“user.*.age”, ValueFilter.greater(18));//查找年龄大于18岁的用户
  17. CoolHashMap hm = mapresult.nextBatch(10);
  18. System.out.println(hm);//输出[user.001.age=20, user.002.age=25]
  19. ……

更多的功能使用请去参考开发包里的demo

四、Fourinone到底是什么?

Fourinone1.0是一个并行计算框架,2.0是一个分布式文件系统(Fttp),4.0是一个数据库引擎(CoolHash)…那么Fourinone到底是什么?

我们把Fourinone打开,其实就是纯java程序,除外什么都没有,通篇展示如何用最基础的java实现上面的功能。

其实不用关注Fourinone的产品定位究竟是什么,Fourinone就是四不象,虽然功能众多,跨度很大,但是仍然长自同一个身体,由同一个脑袋指挥,只要各项机理运行正常健康就好。

Fourinone就是俄罗斯套娃里最小的一个,2.0是1.0的应用,3.0是2.0的应用,4.0是3.0的应用,层层扩充没有止境,体现软件搭建精髓思想。

Fourinone只收集最核心的技术,hadoop的开发者去使用spark需要重新学习,反之亦然,因为他永远只是一个使用者,但是Fourinone可以从一个简单MQ演变成一个复杂数据库,只有掌握核心技术,才具备强大的变通能力。

Fourinone4.0虽然新增加了一个完整的数据库引擎,但是依然保持着苗条身材,整体大小只有220k,用不到1万行java代码实现,所有的设计都是原创和创新。

除外,4.0版本还增加了以下特性:

  • 多进程多线程的无缝融合,同一套接口,改改参数,从多进程变为多线程,开发者无需改写程序逻辑;
  • 提供高容错任务分配算法API:doTaskCompete(m工人,n任务),将n个任务分给m个工人并行完成,根据任务大小设置工人数量,工人间能者多劳,性能好的工人机器争抢干更多的任务,同时跟现实工作一样,如果有工人生病请假(故障),那么他的任务活由其余工人代干,除非所有工人出故障,否则就算只剩一个工人也应该加班把其他所有工人的活干完,对整体计算来说,部分工人故障对计算结果来说不受影响,只是计算时间会延长。

五、结束语:将技术做酷是一种生活态度

就像摇滚乐手骑行哈雷是一种生活态度一样,把技术和产品做酷也是一种生活态度。

  • 用很多代码体积臃肿实现出来也可以,但是不够酷,用很少代码实现功能更强才酷;
  • 用很多人花很多时间做出一个产品也可以,但是不够酷,用很少人花很少时间做出来才酷;
  • 全靠整合依赖太多也可以,但是不够酷,没有任何依赖才酷;
  • 最核心的组件都是用别人的也可以,但是不够酷,自己做的更好才酷;
  • 产品只有专业人士会用也可以,但是不够酷,没文化的人都会用才酷;
  • 跳水自由落水也可以,但是不够酷,翻几个跟头不冒一点水花才酷;
  • 演死尸直接倒下也可以,但是不够酷,多晃悠几下再死才酷;
  • 不玩创新也可以,那样的人生不够酷,玩创新挨整才酷。

基础软件也能像互联网产品余额宝、微信那样做到很酷,追求傻瓜式的客户体验和黏性,把草根程序员当做客户群体,做他们踮踮脚就能够的到的软件。基础软件相对于业务创新产品来说有一定技术门槛,不容易被复制,观察我们身边的互联网产品,“从网游到偷菜到手游到微博到微信…”或者是“从b2b到c2c到b2c到团购到o2o…”总在不断的进行业务模式创新,保鲜期不超过2年,而且容易遭到山寨,但是微软的操作系统和office、oracle的数据库卖了20多年,不需要什么创新好像也没有竞争对手。

很多毕业生踏入这个行业时最初的梦想也是想写一个很酷的操作系统或者数据库,但是无数次的现实磨灭了他的梦想,很多人最后变成一个在职场政治和项目扯皮中寻求着人生价值,并且总是把“技术不是问题”挂在嘴边,是否还记的你曾经的梦想,把技术做酷。

Fourinone4.0(CoolHash)开源地址:http://code.google.com/p/fourinone/
svn地址(浏览器可以直接下载):http://fourinone.googlecode.com/svn/trunk/

关于Fourinone的所有架构、指南、demo,可以参考《大规模分布式系统架构与设计实战》一书

Java 8 简明教程

2014年3月29日 由 acefarmer 没有评论 »
欢迎阅读我编写的Java 8介绍。本教程将带领你一步一步地认识这门语言的新特性。通过简单明了的代码示例,你将会学习到如何使用默认接口方法,Lambda表达式,方法引用和重复注解。看完这篇教程后,你还将对最新推出的API有一定的了解,例如:流控制,函数式接口,map扩展和新的时间日期API等等。

允许在接口中有默认方法实现

Java 8 允许我们使用default关键字,为接口声明添加非抽象的方法实现。这个特性又被称为扩展方法。下面是我们的第一个例子:

Java代码
  1. interface Formula {
  2.     double calculate(int a);
  3.     default double sqrt(int a) {
  4.         return Math.sqrt(a);
  5.     }
  6. }

在接口Formula中,除了抽象方法caculate以外,还定义了一个默认方法sqrt。Formula的实现类只需要实现抽象方法caculate就可以了。默认方法sqrt可以直接使用。

Java代码
  1. Formula formula = new Formula() {
  2.     @Override
  3.     public double calculate(int a) {
  4.         return sqrt(a * 100);
  5.     }
  6. };
  7. formula.calculate(100);     // 100.0
  8. formula.sqrt(16);           // 4.0

formula对象以匿名对象的形式实现了Formula接口。代码很啰嗦:用了6行代码才实现了一个简单的计算功能:a*100开平方根。我们在下一节会看到,Java 8 还有一种更加优美的方法,能够实现包含单个函数的对象。

Lambda表达式

让我们从最简单的例子开始,来学习如何对一个string列表进行排序。我们首先使用Java 8之前的方法来实现:

Java代码
  1. List<String> names = Arrays.asList(“peter”, ”anna”, ”mike”, ”xenia”);
  2. Collections.sort(names, new Comparator<String>() {
  3.     @Override
  4.     public int compare(String a, String b) {
  5.         return b.compareTo(a);
  6.     }
  7. });

静态工具方法Collections.sort接受一个list,和一个Comparator接口作为输入参数,Comparator的实现类可以对输入的list中的元素进行比较。通常情况下,你可以直接用创建匿名Comparator对象,并把它作为参数传递给sort方法。

除了创建匿名对象以外,Java 8 还提供了一种更简洁的方式,Lambda表达式。

Java代码
  1. Collections.sort(names, (String a, String b) -> {
  2.     return b.compareTo(a);
  3. });

你可以看到,这段代码就比之前的更加简短和易读。但是,它还可以更加简短:

Java代码
  1. Collections.sort(names, (String a, String b) -> b.compareTo(a));

只要一行代码,包含了方法体。你甚至可以连大括号对{}和return关键字都省略不要。不过这还不是最短的写法:

Java代码
  1. Collections.sort(names, (a, b) -> b.compareTo(a));

Java编译器能够自动识别参数的类型,所以你就可以省略掉类型不写。让我们再深入地研究一下lambda表达式的威力吧。

函数式接口

Lambda表达式如何匹配Java的类型系统?每一个lambda都能够通过一个特定的接口,与一个给定的类型进行匹配。一个所谓的函数式接口必须要有且仅有一个抽象方法声明。每个与之对应的lambda表达式必须要与抽象方法的声明相匹配。由于默认方法不是抽象的,因此你可以在你的函数式接口里任意添加默认方法。任意只包含一个抽象方法的接口,我们都可以用来做成lambda表达式。为了让你定义的接口满足要求,你应当在接口前加上@FunctionalInterface 标注。编译器会注意到这个标注,如果你的接口中定义了第二个抽象方法的话,编译器会抛出异常。举例:

Java代码
  1. @FunctionalInterface
  2. interface Converter<F, T> {
  3.     T convert(F from);
  4. }
  5. Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
  6. Integer converted = converter.convert(“123″);
  7. System.out.println(converted);    // 123

注意,如果你不写@FunctionalInterface 标注,程序也是正确的。

方法和构造函数引用

上面的代码实例可以通过静态方法引用,使之更加简洁:

Java代码
  1. Converter<String, Integer> converter = Integer::valueOf;
  2. Integer converted = converter.convert(“123″);
  3. System.out.println(converted);   // 123

Java 8 允许你通过::关键字获取方法或者构造函数的的引用。上面的例子就演示了如何引用一个静态方法。而且,我们还可以对一个对象的方法进行引用:

Java代码
  1. class Something {
  2.     String startsWith(String s) {
  3.         return String.valueOf(s.charAt(0));
  4.     }
  5. }
  6. Something something = new Something();
  7. Converter<String, String> converter = something::startsWith;
  8. String converted = converter.convert(“Java”);
  9. System.out.println(converted);    // ”J”

让我们看看如何使用::关键字引用构造函数。首先我们定义一个示例bean,包含不同的构造方法:

Java代码
  1. class Person {
  2.     String firstName;
  3.     String lastName;
  4.     Person() {}
  5.     Person(String firstName, String lastName) {
  6.         this.firstName = firstName;
  7.         this.lastName = lastName;
  8.     }
  9. }

接下来,我们定义一个person工厂接口,用来创建新的person对象:

Java代码
  1. interface PersonFactory<P extends Person> {
  2.     P create(String firstName, String lastName);
  3. }

然后我们通过构造函数引用来把所有东西拼到一起,而不是像以前一样,通过手动实现一个工厂来这么做。

Java代码
  1. PersonFactory<Person> personFactory = Person::new;
  2. Person person = personFactory.create(“Peter”, ”Parker”);

我们通过Person::new来创建一个Person类构造函数的引用。Java编译器会自动地选择合适的构造函数来匹配PersonFactory.create函数的签名,并选择正确的构造函数形式。

Lambda的范围

对于lambdab表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。你能够访问局部对应的外部区域的局部final变量,以及成员变量和静态变量。访问局部变量我们可以访问lambda表达式外部的final局部变量:

Java代码
  1. final int num = 1;
  2. Converter<Integer, String> stringConverter =
  3.         (from) -> String.valueOf(from + num);
  4. stringConverter.convert(2);     // 3

但是与匿名对象不同的是,变量num并不需要一定是final。下面的代码依然是合法的:

Java代码
  1. int num = 1;
  2. Converter<Integer, String> stringConverter =
  3.         (from) -> String.valueOf(from + num);
  4. stringConverter.convert(2);     // 3

然而,num在编译的时候被隐式地当做final变量来处理。下面的代码就不合法:

Java代码
  1. int num = 1;
  2. Converter<Integer, String> stringConverter =
  3.         (from) -> String.valueOf(from + num);
  4. num = 3;

在lambda表达式内部企图改变num的值也是不允许的。

访问成员变量和静态变量

与局部变量不同,我们在lambda表达式的内部能获取到对成员变量或静态变量的读写权。这种访问行为在匿名对象里是非常典型的。

Java代码
  1. class Lambda4 {
  2.     static int outerStaticNum;
  3.     int outerNum;
  4.     void testScopes() {
  5.         Converter<Integer, String> stringConverter1 = (from) -> {
  6.             outerNum = 23;
  7.             return String.valueOf(from);
  8.         };
  9.         Converter<Integer, String> stringConverter2 = (from) -> {
  10.             outerStaticNum = 72;
  11.             return String.valueOf(from);
  12.         };
  13.     }
  14. }

访问默认接口方法

还记得第一节里面formula的那个例子么? 接口Formula定义了一个默认的方法sqrt,该方法能够访问formula所有的对象实例,包括匿名对象。这个对lambda表达式来讲则无效。

默认方法无法在lambda表达式内部被访问。因此下面的代码是无法通过编译的:

Java代码
  1. Formula formula = (a) -> sqrt( a * 100);

内置函数式接口

JDK 1.8 API中包含了很多内置的函数式接口。有些是在以前版本的Java中大家耳熟能详的,例如Comparator接口,或者Runnable接口。对这些现成的接口进行实现,可以通过@FunctionalInterface 标注来启用Lambda功能支持。此外,Java 8 API 还提供了很多新的函数式接口,来降低程序员的工作负担。有些新的接口已经在Google Guava库中很有名了。如果你对这些库很熟的话,你甚至闭上眼睛都能够想到,这些接口在类库的实现过程中起了多么大的作用。Predicates

Predicate是一个布尔类型的函数,该函数只有一个输入参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or,negate):

Java代码
  1. Predicate<String> predicate = (s) -> s.length() > 0;
  2. predicate.test(“foo”);              // true
  3. predicate.negate().test(“foo”);     // false
  4. Predicate<Boolean> nonNull = Objects::nonNull;
  5. Predicate<Boolean> isNull = Objects::isNull;
  6. Predicate<String> isEmpty = String::isEmpty;
  7. Predicate<String> isNotEmpty = isEmpty.negate();

Functions

Function接口接收一个参数,并返回单一的结果。默认方法可以将多个函数串在一起(compse, andThen):

Java代码
  1. Function<String, Integer> toInteger = Integer::valueOf;
  2. Function<String, String> backToString = toInteger.andThen(String::valueOf);
  3. backToString.apply(“123″);     // ”123″

Suppliers

Supplier接口产生一个给定类型的结果。与Function不同的是,Supplier没有输入参数。

Java代码
  1. Supplier<Person> personSupplier = Person::new;
  2. personSupplier.get();   // new Person

Consumers

Consumer代表了在一个输入参数上需要进行的操作。

Java代码
  1. Consumer<Person> greeter = (p) -> System.out.println(“Hello, ” + p.firstName);
  2. greeter.accept(new Person(“Luke”, ”Skywalker”));

Comparators

Comparator接口在早期的Java版本中非常著名。Java 8 为这个接口添加了不同的默认方法。

Java代码
  1. Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
  2. Person p1 = new Person(“John”, ”Doe”);
  3. Person p2 = new Person(“Alice”, ”Wonderland”);
  4. comparator.compare(p1, p2);             // > 0
  5. comparator.reversed().compare(p1, p2);  // < 0

Optionals

Optional不是一个函数式接口,而是一个精巧的工具接口,用来防止NullPointerEception产生。这个概念在下一节会显得很重要,所以我们在这里快速地浏览一下Optional的工作原理。

Optional是一个简单的值容器,这个值可以是null,也可以是non-null。考虑到一个方法可能会返回一个non-null的值,也可能返回一个空值。为了不直接返回null,我们在Java 8中就返回一个Optional。

Java代码
  1. Optional<String> optional = Optional.of(“bam”);
  2. optional.isPresent();           // true
  3. optional.get();                 // ”bam”
  4. optional.orElse(“fallback”);    /%2

lsof 命令

2014年3月3日 由 acefarmer 没有评论 »

1, 使用 lsof 命令行列出所有打开的文件

# lsof 

这可是一个很长的列表,包括打开的文件和网络

lsof1

上述屏幕截图中包含很多列,例如 PID、user、FD 和 TYPE 等等。

FD - File descriptor

FD 列包含这样一些值

cwd - Current working directory
txt - Text file
mem - Memory Mapped file
mmap - Memory Mapped device
Number - It represent the actual file descriptor. For example, 0u, 1w and 3r

r 是读的意思,w 是写,u 代表读写

Type 代表文件类型,例如:

>REG - Regular file 
>DIR - Directory
>CHR - Character special file
>FIFO - First in first out

2, 列出某个用户打开的文件

# lsof -u user_name 

Example:

# lsof -u crybit
COMMAND   PID   USER   FD   TYPE             DEVICE SIZE/OFF       NODE NAME
sshd    29609 crybit  cwd    DIR            144,233     4096  117711421 /
sshd    29609 crybit  rtd    DIR            144,233     4096  117711421 /
sshd    29609 crybit  txt    REG            144,233   409488  119020186 /usr/sbin/sshd
sshd    29609 crybit  mem    REG            144,241          2443001619 (deleted)/dev/zero (stat: No such file or directory)
sshd    29609 crybit  mem    REG               8,37           119021850 /lib64/libnss_dns-2.5.so (path dev=144,233)
sshd    29609 crybit  mem    REG               8,37           119021984 /lib64/security/pam_succeed_if.so (path dev=144,233)
sshd    29609 crybit  mem    REG               8,37           119022000 /lib64/security/pam_limits.so (path dev=144,233)
sshd    29609 crybit  mem    REG               8,37           119021960 /lib64/security/pam_keyinit.so (path dev=144,233)
sshd    29609 crybit  mem    REG               8,37           119021972 /lib64/security/pam_cracklib.so (path dev=144,233)
sshd    29609 crybit  mem    REG               8,37           119021987 /lib64/security/pam_nologin.so (path dev=144,233)
sshd    29609 crybit  mem    REG               8,37           119021988 /lib64/security/pam_deny.so (path dev=144,233)
sshd    29609 crybit  mem    REG               8,37           119019223 /usr/lib64/libcrack.so.2.8.0 (path dev=144,233)
.....
.....

3, 列出在某个端口运行的进程

# lsof -i :port_number 

Example:

# lsof -i :22
COMMAND   PID   USER   FD   TYPE     DEVICE SIZE/OFF NODE NAME
sshd      769   root    3u  IPv6 2281738844      0t0  TCP *:ssh (LISTEN)
sshd      769   root    4u  IPv4 2281738846      0t0  TCP *:ssh (LISTEN)
# lsof -i :3306
COMMAND   PID  USER   FD   TYPE     DEVICE SIZE/OFF NODE NAME
mysqld  11106 mysql   10u  IPv4 2340975114      0t0  TCP *:mysql (LISTEN)

4, 只列出使用 IPv4 的打开文件

# lsof -i 4 - For IPv4 

Example:

# lsof -i 4
COMMAND     PID   USER   FD   TYPE     DEVICE SIZE/OFF NODE NAME
sshd        769   root    4u  IPv4 2281738846      0t0  TCP *:ssh (LISTEN)
named      8362  named   20u  IPv4 2334751017      0t0  TCP localhost.localdomain:domain (LISTEN)
named      8362  named   21u  IPv4 2334751019      0t0  TCP crybit.com:domain (LISTEN)
named      8362  named   22u  IPv4 2334751021      0t0  TCP localhost.localdomain:rndc (LISTEN)
named      8362  named  512u  IPv4 2334751016      0t0  UDP localhost.localdomain:domain 
named      8362  named  513u  IPv4 2334751018      0t0  UDP crybit.com:domain 
tcpserver  9975   root    3u  IPv4 2335487959      0t0  TCP *:pop3 (LISTEN)
tcpserver  9978   root    3u  IPv4 2335487967      0t0  TCP *:pop3s (LISTEN)
tcpserver  9983   root    3u  IPv4 2335487997      0t0  TCP *:imap (LISTEN)
tcpserver  9987   root    3u  IPv4 2335488014      0t0  TCP *:imaps (LISTEN)
xinetd    10413   root    5u  IPv4 2336070983      0t0  TCP *:ftp (LISTEN)
xinetd    10413   root    6u  IPv4 2336070984      0t0  TCP *:smtp (LISTEN)
mysqld    11106  mysql   10u  IPv4 2340975114      0t0  TCP *:mysql (LISTEN)
# lsof -i 6 

Example:

# lsof -i 6
COMMAND   PID   USER   FD   TYPE     DEVICE SIZE/OFF NODE NAME
sshd      769   root    3u  IPv6 2281738844      0t0  TCP *:ssh (LISTEN)
named    8362  named   23u  IPv6 2334751024      0t0  TCP localhost.localdomain:rndc (LISTEN)
httpd   29241   root    4u  IPv6 2439777206      0t0  TCP *:http (LISTEN)
httpd   29241   root    6u  IPv6 2439777211      0t0  TCP *:https (LISTEN)
httpd   29243 apache    4u  IPv6 2439777206      0t0  TCP *:http (LISTEN)
httpd   29243 apache    6u  IPv6 2439777211      0t0  TCP *:https (LISTEN)
httpd   29244 apache    4u  IPv6 2439777206      0t0  TCP *:http (LISTEN)
httpd   29244 apache    6u  IPv6 2439777211      0t0  TCP *:https (LISTEN)
httpd   29245 apache    4u  IPv6 2439777206      0t0  TCP *:http (LISTEN)
httpd   29245 apache    6u  IPv6 2439777211      0t0  TCP *:https (LISTEN)
httpd   29246 apache    4u  IPv6 2439777206      0t0  TCP *:http (LISTEN)

5, 列出端口在 1-1024 之间的所有进程

# lsof -i :1-1024 

Example:

# lsof -i :1-1024
COMMAND     PID   USER   FD   TYPE     DEVICE SIZE/OFF NODE NAME
sshd        769   root    3u  IPv6 2281738844      0t0  TCP *:ssh (LISTEN)
sshd        769   root    4u  IPv4 2281738846      0t0  TCP *:ssh (LISTEN)
named      8362  named   20u  IPv4 2334751017      0t0  TCP localhost.localdomain:domain (LISTEN)
named      8362  named   21u  IPv4 2334751019      0t0  TCP crybit.com:domain (LISTEN)
named      8362  named   22u  IPv4 2334751021      0t0  TCP localhost.localdomain:rndc (LISTEN)
named      8362  named   23u  IPv6 2334751024      0t0  TCP localhost.localdomain:rndc (LISTEN)
tcpserver  9975   root    3u  IPv4 2335487959      0t0  TCP *:pop3 (LISTEN)
tcpserver  9978   root    3u  IPv4 2335487967      0t0  TCP *:pop3s (LISTEN)
tcpserver  9983   root    3u  IPv4 2335487997      0t0  TCP *:imap (LISTEN)
tcpserver  9987   root    3u  IPv4 2335488014      0t0  TCP *:imaps (LISTEN)
xinetd    10413   root    5u  IPv4 2336070983      0t0  TCP *:ftp (LISTEN)
xinetd    10413   root    6u  IPv4 2336070984      0t0  TCP *:smtp (LISTEN)
httpd     29241   root    4u  IPv6 2439777206      0t0  TCP *:http (LISTEN)
httpd     29241   root    6u  IPv6 2439777211      0t0  TCP *:https (LISTEN)
httpd     29243 apache    4u  IPv6 2439777206      0t0  TCP *:http (LISTEN)
....
....

6, 根据进程id来列出打开的文件

# lsof -p PID

Example:

# lsof -p 11106
COMMAND   PID  USER   FD   TYPE             DEVICE SIZE/OFF       NODE NAME
mysqld  11106 mysql  cwd    DIR            144,233     4096  119025114 /var/lib/mysql
mysqld  11106 mysql  rtd    DIR            144,233     4096  117711421 /
mysqld  11106 mysql  txt    REG            144,233  9484782  119025094 /usr/libexec/mysqld
mysqld  11106 mysql  mem    REG               8,37           119025094 /usr/libexec/mysqld (path dev=144,233)
mysqld  11106 mysql  mem    REG               8,37           119021850 /lib64/libnss_dns-2.5.so (path dev=144,233)
mysqld  11106 mysql  mem    REG               8,37           119021830 /lib64/libnss_files-2.5.so (path dev=144,233)
mysqld  11106 mysql  mem    REG               8,37           119021841 /lib64/libsepol.so.1 (path dev=144,233)
mysqld  11106 mysql  mem    REG               8,37           119021801 /lib64/libselinux.so.1 (path dev=144,233)
mysqld  11106 mysql  mem    REG               8,37           119021785 /lib64/libresolv-2.5.so (path dev=144,233)
mysqld  11106 mysql  mem    REG               8,37           119021920 /lib64/libkeyutils-1.2.so (path dev=144,233)
mysqld  11106 mysql  mem    REG               8,37           119017006 /usr/lib64/libkrb5support.so.0.1 (path dev=144,233)
....
....

7, 杀掉某个用户的所有活动进程

# killall -9 `lsof -t -u username` 

8, 列出某个目录中被打开的文件

# lsof +D path_of_the_directory 

Example:

# lsof +D /var/log/
COMMAND     PID   USER   FD   TYPE  DEVICE SIZE/OFF      NODE NAME
syslogd    9729   root    1w   REG 144,233        0 119019158 /var/log/kernel
syslogd    9729   root    2w   REG 144,233   350722 119021699 /var/log/messages
syslogd    9729   root    3w   REG 144,233   591577 119019159 /var/log/secure
syslogd    9729   root    4w   REG 144,233   591577 119019159 /var/log/secure

9, 根据进程名称列出打开的文件

# lsof -c process_name 

Example:

# lsof -c ssh
COMMAND     PID USER   FD   TYPE     DEVICE SIZE/OFF    NODE NAME
sshd        483 root  cwd    DIR        8,9     4096       2 /
sshd        483 root  rtd    DIR        8,9     4096       2 /
sshd        483 root  txt    REG        8,9   523488 1193409 /usr/sbin/sshd

10, 列出所有网络连接

# lsof -i 

该命令列出所有侦听和已建立的网络连接
Example:

# lsof -i
COMMAND     PID   USER   FD   TYPE     DEVICE SIZE/OFF NODE NAME
sshd        769   root    3u  IPv6 2281738844      0t0  TCP *:ssh (LISTEN)
sshd        769   root    4u  IPv4 2281738846      0t0  TCP *:ssh (LISTEN)
named      8362  named   20u  IPv4 2334751017      0t0  TCP localhost.localdomain:domain (LISTEN)
named      8362  named   21u  IPv4 2334751019      0t0  TCP crybit.com:domain (LISTEN)
named      8362  named   22u  IPv4 2334751021      0t0  TCP localhost.localdomain:rndc (LISTEN)
named      8362  named   23u  IPv6 2334751024      0t0  TCP localhost.localdomain:rndc (LISTEN)
named      8362  named  512u  IPv4 2334751016      0t0  UDP localhost.localdomain:domain



转自 http://flycars001.iteye.com/blog/2024783

Java中何时定义方法为静态的

2014年3月1日 由 acefarmer 没有评论 »

One rule-of-thumb: ask yourself “does it make sense to call this method, even if no Obj has been constructed yet?” If so, it should definitely be static.

So in a class Car you might have a method double convertMpgToKpl(double mpg) which would be static, because one might want to know what 35mpg converts to, even if nobody has ever built a Car. But void setMileage(double mpg) (which sets the efficiency of one particular Car) can’t be static since it’s inconceivable to call the method before any Car has been constructed.

(Btw, the converse isn’t always true: you might sometimes have a method which involves two Car objects, and still want it to be static. E.g. Car theMoreEfficientOf( Car c1, Car c2 ). Although this could be converted to a non-static version, some would argue that since there isn’t a “privileged” choice of which Car is more important, you shouldn’t force a caller to choose one Car as the object you’ll invoke the method on. This situation accounts for a fairly small fraction of all static methods, though.)

 

 

A few good examples here. I would add, however, that “static” is often valuable when you know something is not going to change across instances. If this is the case, I would really consider the “Single Responsability Principle”, which implies a class should have one responsability and thus only one reason to change. I feel one should consider moving the “ConvertMpgToKpl(double mpg)” function, and similar methods, to their own class. The purpose of a car object is to allow instantiation of cars, not provide a comparison between them. Those should be external to the class.

 

Define static methods in the following scenarios only:

  1. If you are writing utility classes and they are not supposed to be changed.
  2. If the method is not using any instance variable.
  3. If any operation is not dependent on instance creation.
  4. If there is some code that can easily be shared by all the instance methods, extract that code into a static method.
  5. If you are sure that the definition of the method will never be changed or overridden. As static methods can not be overridden.

 

After reading Misko’s articles I believe that static methods are bad from a testing point of view. You should havefactories instead(maybe using a dependency injection tool like Guice).

how do I ensure that I only have one of something

only have one of something The problem of “how do I ensure that I only have one of something” is nicely sidestepped. You instantiate only a single ApplicationFactory in your main, and as a result, you only instantiate a single instance of all of your singletons.

The basic issue with static methods is they are procedural code

The basic issue with static methods is they are procedural code. I have no idea how to unit-test procedural code. Unit-testing assumes that I can instantiate a piece of my application in isolation. During the instantiation I wire the dependencies with mocks/friendlies which replace the real dependencies. With procedural programing there is nothing to “wire” since there are no objects, the code and data are separate.

There are some valid reasons to use static methods:

  • Performance: if you want some code to be run, and don’t want to instantiate an extra object to do so, shove it into a static method. The JVM also can optimize static methods a lot (I read once James Gosling declaring that you don’t need custom instructions in the JVM, since static methods will be just as fast, but couldn’t find the source). Yes, it is micro-optimization, and probably unneeded. And we programmers never do unneeded things just because they are cool, right?
  • Practicality: instead of calling new Util().method(arg), call Util.method(arg), or method(arg)with static imports. Easier, shorter.
  • Adding methods: you really wanted the class String to have a removeSpecialChars() instance method, but it’s not there (and it shouldn’t, since your project’s special characters may be different from the other project’s), and you can’t add it (since Java is minimally sane), so you create an utility class, and call removeSpecialChars(s) instead of s.removeSpecialChars(). Sweet.
  • Purity: taking some precautions, your static method will be a pure function, that is, the only thing it depends on is its parameters. Data in, data out. This is easier to read and debug, since you don’t have inheritance quirks to worry about. You can do it with instance methods too, but the compiler will help you a little more with static methods (by not allowing references to instance attributes, overriding methods, etc.).

You’ll also have to create a static method if you want to make a singleton, but… don’t. I mean, think twice.

Now, more importantly, why you wouldn’t want to create a static method? Basically, polymorphism goes out of the window. You’ll not be able to override the method, nor declare it in an interface. It takes a lot of flexibility out from your design. Also, if you need state, you’ll end up with lots of concurrency bugs and/or bottlenecks if you are not careful.

An even more interesting discussion is about static inner classes, but that is for another time :)

恭贺spark成为apache顶级项目

2014年3月1日 由 acefarmer 没有评论 »

 

Apache软件基金会今天宣布,Spark成为Apache软件基金会的一个顶级项目

 

http://spark.apache.org/

localhost与127.0.0.1区别

2014年2月17日 由 acefarmer 没有评论 »

1: localhost也叫local ,正确的解释是:本地服务器,localhot(local)是不经网卡传输!这点很重要,它不受网络防火墙和网卡相关的的限制。

2: 127.0.0.1在windows等系统的正确解释是:本机地址(本机服务器)    127.0.0.1是通过网卡传输,依赖网卡,并受到网络防火墙和网卡相关的限制。   一般设置程序时本地服务用localhost是最好的,localhost不会解析成ip,也不会占用网卡、网络资源。   有时候用localhost可以,但用127.0.0.1就不可以的情况就是在于此。猜想localhost访问时,系统带的本机当前用户的权限去访问,而用ip的时候,等于本机是通过网络再去访问本机,可能涉及到网络用户的权限。

大陆开源软件镜像服务站点列表

2014年1月1日 由 acefarmer 没有评论 »

七牛云存储同步工具qrsync

2013年12月22日 由 acefarmer 没有评论 »

详细文档见其官方网站http://docs.qiniu.com/tools/v6/qrsync.html

这里只是做个示范,

bucket,是你在七牛注册的空间名

{
“access_key”:”your key”,
“secret_key”:”your key”,
“bucket”:”acefarmer”,
“sync_dir”:”F:/website/acefarmer.com/”,
“ignore_patterns”:”*.php”,
“debug_level”:1
}

 

cd 到qrsync.exe目录

 

qrsync.exe conf.json

 

java中的NULL

2013年11月24日 由 acefarmer 没有评论 »

一、null是代表不确定的对象

Java中,null是一个关键字,用来标识一个不确定的对象。因此可以将null赋给引用类型变量,但不可以将null赋给基本类型变量。

比如:int a = null;是错误的。Ojbect o = null是正确的。

Java中,变量的适用都遵循一个原则,先定义,并且初始化后,才可以使用。我们不能int a后,不给a指定值,就去打印a的值。这条对对于引用类型变量也是适用的。

有时候,我们定义一个引用类型变量,在刚开始的时候,无法给出一个确定的值,但是不指定值,程序可能会在try语句块中初始化值。这时候,我们下面使用变量的时候就会报错。这时候,可以先给变量指定一个null值,问题就解决了。例如:

Connection conn = null;
try {
conn = DriverManager.getConnection(“url”, “user”, “password”);
} catch (SQLException e) {
e.printStackTrace();
}

String catalog = conn.getCatalog();
如果刚开始的时候不指定conn = null,则最后一句就会报错。

二、null本身不是对象,也不是Objcet的实例

null本身虽然能代表一个不确定的对象,但就null本身来说,它不是对象,也不知道什么类型,也不是java.lang.Object的实例。
可以做一个简单的例子:

//null是对象吗? 属于Object类型吗?
if (null instanceof java.lang.Object) {
System.out.println(“null属于java.lang.Object类型”);
} else {
System.out.println(“null不属于java.lang.Object类型”);
}
结果会输出:null不属于java.lang.Object类型

三、Java默认给变量赋值

在定义变量的时候,如果定义后没有给变量赋值,则Java在运行时会自动给变量赋值。赋值原则是整数类型int、byte、short、long的自动赋值为0,带小数点的float、double自动赋值为0.0,boolean的自动赋值为false,其他各供引用类型变量自动赋值为null。
这个具体可以通过调试来看。

四、容器类型与null

List:允许重复元素,可以加入任意多个null。

Set:不允许重复元素,最多可以加入一个null。

Map:Map的key最多可以加入一个null,value字段没有限制。

数组:基本类型数组,定义后,如果不给定初始值,则java运行时会自动给定值。引用类型数组,不给定初始值,则所有的元素值为null。

五、null的其他作用

1、判断一个引用类型数据是否null。 用==来判断。

2、释放内存,让一个非null的引用类型变量指向null。这样这个对象就不再被任何对象应用了。等待JVM垃圾回收机制去回收。

 

jmap jhat

2013年11月20日 由 acefarmer 没有评论 »

good tools to use

自定义网页字体

2013年10月9日 由 acefarmer 没有评论 »

在自定义这个博客主题过程中使用了某些特定字体,这些字体并非操作系统的内置字体,这样用户在浏览页面的时就有可能看不到设计时效果,导致页面显示很丑陋,一般的解决办法是把文字体成图片。

但是有点麻烦, 不适合我的样的懒人,而且做成图片也不方便修改.还有一种是通过CSS样式定义,我选择了这种, 因为它简单易用.CSS中使用@font-face属性来实现在网页中嵌入任意字体,稍稍复杂一点的问题是每种浏览器都有自己的字体格式要求。

.TTF或.OTF 适用于Firefox 3.5、Safari、Opera
.EOT 适用于Internet Explorer 4.0+
.SVG 适用于Chrome、IPhone

那么为了主流的浏览器们都能正常显示必须将字体转换为以上这些格式才行,

但是这样就违背了”懒人原则”.

[当girl手中的一支棒棒糖就快被抢走时,一只hero出现了! "放开那个girl!".. ]

恩, 我们的hero也没有迟到 http://www.fontsquirrel.com/fontface

该网站有很多别人制作完成的字体(适用于主流浏览器们的),

选择任意一款下载后你会得到一年压缩包,

解压后通常会得到*.eot,*.svg,*.ttf,*.woff和stylesheet.css等文件,

其中*.eot,*.svg,*.ttf,*.woff为字体文件,

放到自己站点的域空间内。

stylesheet.css为样式定义(也就是自定义字体部分),

@font-face {

font-family: ‘fontName’; /* 字体名称,可自己定义 */

src: url(‘fontName.eot’);

src: local(‘fontName Regular’),

local(‘fontName’),

url(‘fontName.woff’) format(‘woff’),

url(‘fontName.ttf’) format(‘truetype’),

url(‘fontName.svg#fontName’) format(‘svg’);

}

将其内容复制到自己的样式表中就可以了, (注意URL部分与自己域空间内字体文件路径相匹配)

如果要制作自己的字体也可以, http://www.fontsquirrel.com/fontface/generator

但貌似是收费的.. -_-!

http://www.cnblogs.com/mix-up/archive/2009/12/30/1636265.html

jquery的checked以及disabled

2013年10月9日 由 acefarmer 没有评论 »

下面只提到checked,其实disabled在jquery里的用法和checked是一模一样的

下边两种写法没有任何区别 只是少了些代码而已…

———————————————————–
<input id=”cb1″ type=”checkbox” checked />
<input id=”cb2″ type=”checkbox” checked=”checked” />

————————————————————–

jquery判断checked的三种方法:

.attr(‘checked’):   //看版本1.6+返回:”checked”或”undefined” ;1.5-返回:true或false
.prop(‘checked’): //16+:true/false
.is(‘:checked’):    //所有版本:true/false//别忘记冒号哦

jquery赋值checked的几种写法:

所有的jquery版本都可以这样赋值:

// $(“#cb1″).attr(“checked”,”checked”);
// $(“#cb1″).attr(“checked”,true);

jquery1.6+:prop的4种赋值:

// $(“#cb1″).prop(“checked”,true);//很简单就不说了哦
// $(“#cb1″).prop({checked:true}); //map键值对
// $(“#cb1″).prop(“checked”,function(){
return true;//函数返回true或false
});

//记得还有这种哦:$(“#cb1″).prop(“checked”,”checked”);

更多参考:http://api.jquery.com/prop/

上代码 大家可以随便测试:(你是懒人么-_-)

jquery1.6以后才支持prop的哦

新建一个text复制内容进去  后缀名改成html

<html>
    <head>
<title>测试</title>
<style type="text/css">

</style>
<!--1.62可以修改1.42 1.52 1.7来测试-->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
//判断checked
       // var a=$("#cb1").attr('checked'); //看版本1.6+返回:"checked"或"undefined" ;1.5-返回:true或false
       // var b=$("#cb1").prop('checked'); //1.6+:true/false
       var c=$("#cb1").is(':checked'); //所有版本:true/false
       // alert(a);
       // alert(b);
alert(c);

//赋值 前两个所有的jquery版本都支持 prop只有jquery1.6+支持
       // $("#cb1").attr("checked","checked");//1.5-
       // $("#cb1").attr("checked",true);//1.5-
       //   $("#cb1").prop("checked","checked");//1.6+(整理的时候把这个忘记啦)
       //    $("#cb1").prop("checked",true);//1.6+
       // $("#cb1").prop({checked:true});//1.6+
      // $("#cb1").prop("checked",function(){
       // return true;//1.6+
       // });
})();

</script>
</head>
<body>
<!--赋值的时候记得去掉checked-->
<input id="cb1" type="checkbox" checked />
<input id="cb2" type="checkbox" checked="checked"/>
</body>
</html>


from : http://www.cnblogs.com/0banana0/archive/2011/11/16/2251855.html


heritrix crawler-beans.cxml

2013年8月25日 由 acefarmer 没有评论 »

 1. bean id=simpleOverrides

.springframework.beans.factory.config.PropertyOverrideConfigurer

覆盖全局

2. bean id=metadata

.archive.modules.CrawlMetadata

设置抓取信息

3. bean id=seeds

class=org.archive.modules.seeds.TextSeedModule

种子配置,可以从文件中读取种子,也可以直接设置种子

4. bean id=scope

.archive.modules.deciderules.DecideRuleSequence

URL规则控制,可以决定哪些URL要抓取,这里能够控制单站抓取

5. bean id=candidateScoper

class=org.archive.crawler.prefetch.CandidateScoper

URL范围控制,通过该范围的URL Heritrix才处理

6. bean id=preparer

.archive.crawler.prefetch.FrontierPreparer

url预处理,如设置URL的抓取深度,队列,成本控制等

 7. bean id=candidateProcessors

class=org.archive.modules.CandidateChain

处理器,引用candidateScoper去控制URL是否可以成为CrawlURI,preparer去设置深度,队列,成本控制等

8. bean id=preselector

.archive.crawler.prefetch.Preselector

预先选择器,这里会过滤掉一部分URL.如blockByRegex为拒绝正则,allowByRegex为允许正则

 9. bean id=preconditions

class=org.archive.crawler.prefetch.PreconditionEnforcer

先决条件设置,如设置IP有效期,爬虫协议文件robots.txt有效期

 10. bean id=fetchDns

class=org.archive.modules.fetcher.FetchDNS

解析DNS,获得IP

11. bean id=fetchHttp

class=org.archive.modules.fetcher.FetchHTTP

核心模块,获取URL内容,设置状态

12. bean id=extractorHttp

.archive.modules.extractor.ExtractorHTTP

核心模块,抽取URL,抽取出新的URL再次运行,如此爬虫才可以一直爬下去

  13. bean id=extractorHtml

class=org.archive.modules.extractor.ExtractorHTML

抽取HTML,包含JSP,ASP等,这里也会抽取JS,CSS等

14. bean id=extractorCss

class=org.archive.modules.extractor.ExtractorCSS

抽取CSS,无需单独配置,ExtractorHTML会调用

15. bean id=extractorJs

class=org.archive.modules.extractor.ExtractorJS

抽取JS,无需单独配置,ExtractorHTML会调用

16. warcWriter

定义采用哪种方式写文件到本地,默认是org.archive.modules.writer.WARCWriterProcesso

可以修改为org.archive.modules.writer.MirrorWriterProcessor,这样就可以抓取镜像了

Heritrix 3.1.1在windows上运行

2013年8月25日 由 acefarmer 没有评论 »

1. 下载dist: http://builds.archive.org:8080/maven2/org/archive/heritrix/heritrix/3.1.1/

2.解压到一个目录,如D盘,

3.调出你的cmd,cd到bin目录,如:D:\heritrix-3.1.1\bin

4直接敲入 heritrix -a admin:admin1234 (你登录web界面的用户名密码) 完事。前提是你jdk与环境变量啥的都设置好。

5.打开浏览器直接键入 https://localhost:8443/ ,此时会输入上面的用户名与密码

6.一会儿会多出一个cmd窗口蹦出来,不管他,有ssl错误异常啥的也不管他。

7. 在add job 处输入job名字,如test,点击create,如图

创建heritrix job

 

8. 你可以加入多个job(本文讲一个),在

Job Directories 出就回看到你的job

9 点击 job名字,会跳转到job页面,如:https://localhost:8443/engine/job/test

此时在最顶端 有 configuration: .\jobs\test\crawler-beans.cxml [edit] 字样

 

点击Edit配置你的job

a) metadata.operatorContactUrl=ENTER_AN_URL_WITH_YOUR_CONTACT_INFO_HERE_FOR_WEBMASTERS_AFFECTED_BY_YOUR_CRAWL

此处输入你控制爬出的地址,一般是本地,那就http://127.0.0.1

metadata.jobName=basic,job 名字,如test

metadata.description=Basic crawl starting with useful defaults,这个是job的描述,随便你改不改,

 

b)配置种子,

# URLS HERE

http://example.example/example 表示抓取的地址.比如

http://news.baidu.com

 

c) 配置模拟浏览器,查找 userAgentTemplate(原本被注解掉,打开)

<property name=”userAgentTemplate”  value=”Mozilla/5.0 (compatible; heritrix/@VERSION@ +@OPERATOR_CONTACT_URL@)”/>

 

将 @VERSION@修改为你heritrix版本,这里是3.1.1,@OPERATOR_CONTACT_URL@保持不变(不然会报错,也许是bug,3.1.1,3.1.2都有这问题)

修改后的情况应该是:

<property name=”userAgentTemplate”  value=”Mozilla/5.0 (compatible; heritrix/3.1.2 +@OPERATOR_CONTACT_URL@)”/>

而其他几个直接注释掉

<!– <property name=”operatorContactUrl” value=”[see override above]“/>
<property name=”jobName” value=”[see override above]“/>
<property name=”description” value=”[see override above]“/>–>

将operaterFrom 打开,填入你的email,模拟浏览器,跟以前1.X的设置差不多,以前是分开的,以防被拦截。

<property name=”operatorFrom” value=”acefarmer@163.com”/>

点击save changes,然后回到 https://localhost:8443/engine

点击进入你想抓取的job,并点击launch 开始抓取

如果状态不为running,点击unpause,

开始运行后你会在你job文件夹下生成你job名字,然后又个以时间结尾的文件夹,

G:\heritrix-3.1.2-SNAPSHOT\bin\jobs\guos\20130825022631

该文件夹下有个warcs文件夹就是你抓取的内容,日后如果采用mirrorWriter的话就会是一个个网页了

本文只是基本全网抓取,后面讲限定网站,镜像抓取等。

 

office 2007,2010无法打开doc,提示文件已损坏的解决办法

2013年8月13日 由 acefarmer 没有评论 »

如果在Word 2010中打开从网络上下载的文档doc,docx,将默认是处于受保护状态。当你双击打开时,会提示文件已经损坏无法打开。

 

临时解决办法:修改文件属性
选择需要打开的文件,点右键属性里面选择”解除锁定”,然后确定后。即可正常打开了。

 
彻底解决办法:修改选项配置
进入文件菜单中的选项->信任中心->点信任中心设置 然后点受保护的视图,把右边的所有钩上的内容都不钩,最后保存退出即可

没Android手机,电脑一样当手机玩

2013年8月10日 由 acefarmer 没有评论 »

只需要下载一个Android模拟器(bluestacks),然后任何apk都可以在pc上玩了,再也不觉得屏幕小啦,想耍啥游戏就耍啥。再牛逼的手机,应该没你的pc拽吧!

http://www.bluestacks.net.cn/

访问google.com而不是.hk的最有效方法

2013年8月10日 由 acefarmer 没有评论 »

这个很简单,

直接访问: http://www.google.com/ncr

就不再是google.com.hk啦,你想怎么玩就怎么玩

WordPress缓存超级组合Hyper Cache+DB Cache Reloaded Fix

2013年8月4日 由 acefarmer 没有评论 »

要想你的小站飞快,页面缓存与数据库缓存那是必不可少的,

强烈推荐使用Hyper Cache

http://wordpress.org/extend/plugins/hyper-cache/

 

与 DB Cache Reloaded Fix

http://wordpress.org/plugins/db-cache-reloaded-fix/

 

这个真的不错,关键是小而精悍