¿DirectByteBuffer es más rápido?
Se ha lanzado una nueva biblioteca de clases de E/S en J2SE1.4 y superiores. Este artículo presentará brevemente algunas características nuevas proporcionadas por la biblioteca NIO a través de algunos ejemplos: E/S sin bloqueo, conversión de caracteres, almacenamiento en búfer y canales.
1. Introducción a NIO
El paquete NIO (java.nio.*) presenta cuatro tipos de datos abstractos clave, que juntos resuelven algunas preguntas de las clases de E/S tradicionales. .
1. Búfer: es una estructura de tabla lineal que contiene datos y se utiliza para lectura y escritura. También proporciona una clase especial para operaciones de E/S en archivos asignados en memoria.
2. Charset: proporciona operaciones para mapear cadenas Unicode a secuencias de bytes y mapeo inverso.
3. Canales: Contiene tres tipos de tuberías: socket, file y pipe. En realidad, es un canal de comunicación bidireccional.
4. Selector: concentra múltiples operaciones de E/S asincrónicas en uno o más subprocesos (puede considerarse como la versión orientada a objetos de la función select() en Unix o la función WaitForSingleEvent() en Win32).
2. Revisión de la tradición
Antes de presentar NIO, es necesario comprender el método de operación de E/S tradicional. Tomando como ejemplo las aplicaciones de red, el método tradicional requiere monitorear un ServerSocket, aceptar la conexión solicitada y brindarle servicios (los servicios generalmente incluyen procesar solicitudes y enviar respuestas). La Figura 1 es el diagrama del ciclo de vida del servidor, en el que se encuentran las partes. marcados con líneas negras gruesas indican que se producirá un bloqueo de E/S.
Figura 1
Puedes analizar cada paso específico de la creación de un servidor. Primero cree ServerSocket
ServerSocket server=new ServerSocket(10000);
Luego acepte la nueva solicitud de conexión
Socket newConnection=server.accept(); p>
p>
La llamada al método de aceptación provocará el bloqueo hasta que ServerSocket reciba una solicitud de conexión. Una vez que se acepta la solicitud de conexión, el servidor puede leer la solicitud desde el socket del cliente.
InputStream in = newConnection.getInputStream();
Lector InputStreamReader = nuevo InputStreamReader(in);
BufferedReader buffer = nuevo BufferedReader(lector); p>
p>
Solicitud de solicitud = nueva Solicitud();
while(!request.isComplete()) {
Línea de cadena = buffer.readLine() ;
request.addLine(line);
}
Hay dos problemas con esta operación. Primero, el método readLine() de la clase BufferedReader. causa El hilo se bloquea y el método regresará solo cuando una cierta cantidad de datos llene el búfer o el cliente cierre el socket. En segundo lugar, genera mucha basura. BufferedReader crea un búfer para leer datos del socket del cliente, pero también crea algunas cadenas para almacenar los datos. Aunque BufferedReader proporciona StringBuffer internamente para manejar este problema, todas las cadenas rápidamente se convierten en basura y deben reciclarse.
El mismo problema también existe al enviar códigos de respuesta
Response Response = request.generateResponse();
OutputStream out = newConnection.getOutputStream();
p>
InputStream in = respuesta.getInputStream();
int ch;
mientras(-1 != (ch = in.read())) {< / p>
out.write(ch);
}
newConnection.close();
Del mismo modo, las operaciones de lectura y escritura están bloqueadas y la escritura un carácter a la vez en la secuencia es ineficiente, por lo que se debe usar un búfer, pero una vez que se usa el búfer, la secuencia generará más basura.
Soluciones tradicionales
Los subprocesos (una gran cantidad de subprocesos) generalmente se usan para manejar el bloqueo de E/S en Java. Generalmente, se implementa un grupo de subprocesos para manejar solicitudes, como se muestra en la Figura 2
Figura 2
Los subprocesos permiten que el servidor maneje múltiples conexiones, pero también causan muchos problemas. Cada subproceso tiene su propio espacio de pila y consume algo de tiempo de CPU, lo cual es muy costoso y se pierde mucho tiempo en operaciones de E/S bloqueadas sin un uso efectivo de la CPU.
3. Nueva E/S
1. Buffer
La E/S tradicional desperdicia continuamente recursos de objetos (normalmente cadenas). La nueva E/S evita el desperdicio de recursos al utilizar Buffer para leer y escribir datos. Un objeto Buffer es una colección de datos lineal y ordenada que contiene solo tipos de datos únicos según su categoría.
Descripción de la clase java.nio.Buffer
java.nio.ByteBuffer contiene tipos de bytes. Puede leer desde ReadableByteChannel y escribir en WritableByteChannel
java.nio.MappedByteBuffer contiene tipos de bytes y se asigna directamente en un área determinada de la memoria
java.nio.CharBuffer contiene caracteres tipo y no se puede escribir en el canal
java.nio.DoubleBuffer contiene tipo doble y no se puede escribir en el canal
java.nio.FloatBuffer contiene tipo flotante
java. nio.IntBuffer Contiene tipo int
java.nio.LongBuffer contiene tipo largo
java.nio.ShortBuffer contiene tipo corto
Puedes llamar a allocate(int capacidad) o el método allocateDirect (capacidad int) asigna un búfer. En particular, puede crear un MappedBytesBuffer llamando a FileChannel.map (modo int, posición larga, tamaño int). Un búfer directo asigna un bloque contiguo en la memoria y utiliza métodos de acceso local para leer y escribir datos. Los buffers no directos leen y escriben datos usando código de acceso a matrices en Java. A veces es necesario utilizar almacenamiento en búfer indirecto, como cualquier método de ajuste (como ByteBuffer.wrap(byte[])) para crear un búfer basado en una matriz Java.
2. Codificación de caracteres
Almacenar datos en ByteBuffer implica dos cuestiones: orden de bytes y conversión de caracteres. ByteBuffer maneja internamente problemas de orden de bytes a través de la clase ByteOrder, pero no maneja la conversión de caracteres.
De hecho, ByteBuffer no proporciona métodos para leer y escribir cadenas.
Java.nio.charset.Charset maneja problemas de conversión de caracteres. Convierte secuencias de caracteres en bytes y realiza la conversión inversa mediante la construcción de CharsetEncoder y CharsetDecoder.
3. Canal
Puede notar que ninguna de las clases java.io existentes puede leer y escribir tipos de búfer, por lo que NIO proporciona la clase Canal para leer y escribir búferes. Se puede considerar un canal como una conexión, ya sea a un dispositivo, programa o red específica. El diagrama de jerarquía de clases del canal es el siguiente
Figura 3
En la figura, ReadableByteChannel y WritableByteChannel se utilizan para lectura y escritura respectivamente.
GatheringByteChannel se puede utilizar para escribir datos de varios búferes en canales a la vez. Por el contrario, ScatteringByteChannel se puede utilizar para leer datos de canales en varios búferes a la vez. También puede configurar un canal para servir operaciones de E/S con o sin bloqueo.
Para que el canal sea compatible con las clases de E/S tradicionales, la clase Channel proporciona métodos estáticos para crear Stream o Reader
4. Selector
En el pasado, al bloquear E/S, generalmente sabíamos cuándo podíamos leer o escribir en la secuencia porque la llamada al método no regresaba hasta que la secuencia estaba lista. Pero con los canales sin bloqueo, necesitamos alguna forma de saber cuándo el canal está listo. En el paquete NIO, Selector está diseñado para este propósito. SelectableChannel puede registrarse para eventos específicos. En lugar de notificar a la aplicación cuando ocurre el evento, el canal rastrea el evento. Luego, cuando la aplicación llama a cualquier método de selección en el Selector, verifica los canales registrados para ver si ha ocurrido algún evento interesante. La figura 4 es un ejemplo de un selector y dos canales registrados.
Figura 4
No todos los canales admiten todas las operaciones. La clase SelectionKey define todos los bits de operación posibles, que se utilizarán dos veces. Primero, cuando la aplicación llama al método SelectableChannel.register(Selector sel, int op) para registrar un canal, pasa la operación requerida al método como segundo parámetro. Luego, una vez que se selecciona SelectionKey, el método readyOps() de SelectionKey devuelve la suma de los dígitos de todos los canales que admiten operaciones. El método validOps de SelectableChannel devuelve las operaciones permitidas para cada canal. Las operaciones no admitidas por el canal registrado generarán una excepción IllegalArgumentException. La siguiente tabla enumera las operaciones admitidas por las subclases de SelectableChannel.
ServerSocketChannel OP_ACCEPT
SocketChannel OP_CONNECT, OP_READ, OP_WRITE
DatagramChannel OP_READ, OP_WRITE
Pipe.SourceChannel OP_READ
Pipe.SinkChannel OP_WRITE
4 Ejemplos
1. Descarga sencilla de contenido de una página web
Este ejemplo es muy sencillo. La clase SocketChannelReader utiliza SocketChannel para descargar el contenido HTML de una página web específica.
ejemplos de paquetes.nio;
importar java.nio.ByteBuffer;
importar java.nio.channels.SocketChannel;
importar java.nio.charset.Charset;
importar java.net.InetSocketAddress;
importar java.io.IOException;
clase pública SocketChannelReader{
private Charset charset=Charset.forName("UTF-8");//Crear juego de caracteres UTF-8
canal SocketChannel privado;
public void getHTMLContent() {
intentar{
conectar();
enviarRequest();
readResponse();
}catch(IOException e){
System.err.println(e.toString());
}finalmente{
if(channel!=null ){
prueba{
channel.close();
}catch(IOException e){}
}
}
}
private void connect() lanza IOException{//Conectar a CSDN
InetSocketAddress socketAddress=
new InetSocketAddress("",80/);
channel=SocketChannel.open(socketAddress);
//Utilice el método de fábrica open para crear un canal y conectarlo al especificado dirección
//Equivalente a SocketChannel.open().connect(socketAddress); call
}
private void sendRequest()throws IOException{
channel.write(charset.encode("GET "
+"/document"
+"\r\n\r\n"));// Envíe la solicitud GET al Centro de documentación de CSDN
//Utilice el método channel.write, que requiere parámetros de tipo CharByte, y utilice el método
//Charset.encode(String) para convertir la cuerda.
}
private void readResponse() lanza IOException{//Leer respuesta
ByteBuffer buffer=ByteBuffer.allocate(1024);//Crear sección de 1024 palabras almacenamiento en búfer
while(channel.read(buffer)!=-1){
buffer.flip();// El método flip se llama antes de leer los bytes del búfer.
System.out.println(charset.decode(buffer));
//Utilice el método Charset.decode para convertir bytes en cadenas
buffer. clear();//Borrar el búfer
}
}
public static void main(String [] args){
nuevo SocketChannelReader().getHTMLContent();
}
2. Suma simple servidor y cliente
Código de servidor
ejemplos de paquete.nio;
importar java.nio.ByteBuffer;
importar java. nio.IntBuffer;
importar java.nio.channels.ServerSocketChannel;
importar java.nio.channels.SocketChannel;
importar java.net.
importar java.io.IOException;
/**
* SumServer.java
*
*
* Creado: jueves 06 de noviembre 11:41:52 2003
*
* @author starchu1981
* @ versión 1.0
*/
clase pública SumServer {
ByteBuffer privado _buffer=ByteBuffer.allocate(8);
IntBuffer privado _intBuffer =_buffer .asIntBuffer();
SocketChannel privado _clientChannel=null;
ServerSocketChannel privado _serverChannel=null;
inicio vacío público(){
intenta{
openChannel();
waitForConnection();
}catch(IOException e){
System err. println(e.toString());
}
}
private void openChannel() lanza IOException{
_serverChannel =ServerSocketChannel .open();
_serverChannel.socket().bind(new InetSocketAddress(10000));
System.out.println("El canal del servidor ha sido abierto");
}
espera privada vacía WaitForConnection() lanza IOException{
mientras(true){
_clientChannel=_serverChannel.accept();
if(_clientChannel!=null){
System.ou
t.println("Nueva conexión agregada");
processRequest();
_clientChannel.close();
}
}
}
Proceso de anulación privadaRequest() lanza IOException{
_buffer.clear();
_clientChannel.read(_buffer ) ;
int resultado=_intBuffer.get(0)+_intBuffer.get(1);
_buffer.flip();
_buffer.clear() ;
_intBuffer.put(0,resultado);
_clientChannel.write(_buffer);
}
público estático vacío principal (String [] args){
new SumServer().start();
}
} // SumServer
Cliente código
ejemplos de paquete.nio;
importar java.nio.ByteBuffer;
importar java.nio.IntBuffer;
importar java .nio.channels.SocketChannel;
importar java.net.InetSocketAddress;
importar java.io.IOException;
/**
* SumClient.java
*
*
* Creado: jueves 06 de noviembre 11:26:06 2003
*
* @autor starchu1981
* @versión 1.0
*/
clase pública SumClient {
ByteBuffer privado _buffer=ByteBuffer.allocate(8);
IntBuffer privado _intBuffer;
SocketChannel privado _channel;
público SumClient() {
_intBuffer=_buffer.asIntBuffer();
} // constructor SumClient
public int getSum(int primero,int segundo){
int resultado=0;
intenta{
_channel=connect();
sendSumRequest(primero,segundo);
resultado=receiveResponse(); /p>
}catch(IOException e){System.err.println(e.toString());
}finalmente{
if(_channel!=null ) {
prueba{
_channel.close();
}catch(IOException e){}
}
}
p>
resultado devuelto;
}
conexión de SocketChannel privado() lanza IOException{
InetSocketAddress socketAddress=
nueva InetSocketAddress ("localhost",10000);
return SocketChannel.open(socketAddress);
}
private void sendSumRequest(int first,int second)lanza IOException {
_buffer.clear();
_intBuffer.put(0,primero);
_intBuffer.put(1,segundo);
_channel.write(_buffer);
System.out.println("Enviar solicitud adicional"+primero+"+"+segundo);
}
privado int recibirResponse() lanza IOException{
_buffer.clear();
_channel.read(_buffer);
return _intBuffer.get(0 );
}
public static void main(String [] args){
SumClient sumClient=new SumClient();
System.out.println("El resultado de la suma es: "+sumClient.getSum(100,324));
}
} // SumClient
3. Servidor adicional sin bloqueo
Primero agregue una declaración al método openChannel
_serverChannel.configureBlocking(false);//Establezca en modo sin bloqueo
Reescribir WaitForConnection El código del método es el siguiente, usando el modo sin bloqueo
private void waitForConnection()throws IOException{
Selector AcceptSelector = SelectorProvider.provider().openSelector();
/*Registre el selector en el socket del servidor y configúrelo para aceptar notificaciones del método de aceptación.
Esto le dice al Selector que el socket desea ser colocado en la lista lista cuando ocurre la operación de aceptación
, permitiendo así que ocurran múltiples E/S sin bloqueo. */
SelectionKey AcceptKey = ssc.register(acceptSelector,
SelectionKey.OP_ACCEPT);
int clavesAgregadas = 0;