1. Visão Geral
Na API BIO, a entrada e a saída são realizadas por meio de dois fluxos, InputStream e outPutStream. NIO usa um canal de comunicação bidirecional em vez deles. O canal (Canal) deve contar com o buffer (Buffer) para alcançar a comunicação.
O fluxo de comparação do pipeline tem mais recursos, como mapeamento de memória fora do heap, sem bloqueio e cópia zero.
2. Definição de buffer
O pipeline depende do buffer, portanto, vamos primeiro apresentar o conceito e o uso do buffer.
Uma matriz é mantida dentro do buffer para armazenar dados. O buffer não suporta nenhum tipo de dado armazenado e só pode armazenar alguns tipos de dados básicos.
Tem as funções de leitura e escrita e esvaziamento. Sem a segurança da linha, você precisa controlar a segurança da linha por si mesmo.
Dê a todos o benefício de receber um mapa de habilidades de arquitetura Java gratuito. Observe que é gratuito
、
Livre para receber o + V necessário para receber
3. Estrutura interna do buffer
Tome ByteBuffer como exemplo, é uma subclasse de Buffer, usada para armazenar dados do tipo Byte, outros buffers são semelhantes, mas os tipos de dados armazenados são diferentes.
Uma matriz de bytes é mantida internamente para armazenar dados.
A classe pai Buffer tem 4 atributos importantes:
-
capacidade
Indica o tamanho da matriz
-
limite
Limita o intervalo legível e gravável do Buffer, o padrão é igual à capacidade
-
posição
A posição atual de leitura e gravação, o padrão é 0, toda vez que um bit é lido ou um bit é escrito, +1.
-
marca
Faça uma marca para reiniciar para definir a posição para esta posição. O padrão é -1
A relação de posição dos quatro: marca <= posição <= limite <= capacidade
4. Operações de buffer comumente usadas
4.1 alocar
Aloque o espaço de armazenamento do buffer e inicialize todos os atributos. Por exemplo:
Os resultados obtidos são os seguintes:
4.2 embrulhar
Empacote um buffer com base em uma matriz, a posição é 0, o limite é o valor da capacidade
O código de teste é o seguinte:
Os resultados do buffer são os seguintes:
4,3 colocar
Grave no buffer, o código de teste é o seguinte:
No processo de colocação, a posição será alterada, ou seja, toda vez que um número for escrito, a posição será +1. Na 7ª escrita, a posição excederá o limite, e uma exceção BufferOverflowExeception será lançada neste momento
4,4 flip
Prepare-se para a leitura. Depois de colocar, execute o flip para redefinir a posição e defina-a para 0.
A operação de leitura subsequente começa na posição.
O código de teste é o seguinte:
O resultado do buffer após a execução de put 5 é o seguinte:
Em seguida, execute o flip, os resultados são os seguintes:
De acordo com os resultados, você pode ver a função do flip, defina a posição como 0
E o limite é definido para a posição original, o limite neste momento limita o escopo da leitura.
4,5 obter
Leia o conteúdo do buffer, cada vez que você ler um, mova uma posição após a posição
O código de teste é o seguinte:
Quando o quinto é lido, o conteúdo do buffer neste momento é o seguinte:
Pode-se descobrir que a posição neste momento atingiu o limite do limite. Se você ler novamente, ele relatará um erro, exceção de leitura fora dos limites BufferUnderflowException
Marca 4,6
Defina o bit de marca e registre a posição atual
O código de teste é o seguinte:
Após executar a marca, defina a propriedade da marca para a posição, ou seja, faça uma marca
4.7 reset
A função de redefinir é definir o valor da posição a ser marcada para modificar uma parte dos dados no buffer. Ou leia um pedaço de dados no buffer repetidamente.
O código de teste é o seguinte:
Depois de executar o reset, os resultados do buffer são os seguintes
Depois de registrar a marca, dois números foram lidos e os dois números foram modificados. Após a reinicialização, a posição retorna para a posição registrada pela marca. Nesse momento, se você escrever novamente, poderá modificar os dois números no buffer. O resultado é o seguinte
4,8 claro
Reinicialize os atributos do buffer, mas não apagará os dados do buffer
Defina a posição da posição para 0, marque para -1. O limite é definido para a capacidade
O código de teste é o seguinte:
Depois de executar a limpeza, o resultado do buffer é o seguinte:
4,9 retroceder
Prepare-se para reler. Defina a posição como 0 e limite inalterado. a marca é definida como -1
O código de teste é o seguinte: Depois de
executar o retrocesso, os resultados do buffer são os seguintes:
4,10 restantes
Quantos intervalos legíveis são retornados
Qual é a posição limite
O código de teste é o seguinte:
Neste momento, o limite é igual a 5. Depois de ler duas vezes, o método restante é executado e o resultado retornado é 3, indicando o conteúdo legível restante
5. chanel
Pipes são usados para conectar arquivos, sockets de rede, etc. Ele pode ler e gravar duas operações de E / S ao mesmo tempo. É chamado de tubo bidirecional. Tem dois estados: conectado e fechado. Ele está aberto quando o tubo é criado. Depois de fechado, ele informa quando a operação de E / S é chamada. ClosedChannelException
. isOpen
Pode-se avaliar se está no estado aberto pelo método do pipeline .
5.1 Pipeline de arquivo FileChannel
O nome sólido sugere que ele é usado para manipular arquivos. Além das operações normais, ele também oferece suporte aos seguintes recursos:
-
Suporte para ler e gravar na área especificada do arquivo
-
Mapeamento de memória out-of-heap, ao ler e gravar arquivos grandes, pode ser mapeado diretamente da memória declarada da JVM para melhorar a eficiência de leitura e gravação.
-
Tecnologia Zero-cópia, através
transferFrom
outransferTo
directamente a transmissão de dados para um canal, melhora muito o desempenho. -
Bloqueie a área designada do arquivo para evitar que outros programadores o acessem
Abrir FileChannel só pode ser aberto por meio do fluxo no momento, como inputStream.getChannel () e outputStream.getChannel (), o pipeline aberto por meio do fluxo de entrada só pode ser acessado e o outputStream só pode ser aberto para gravação. Caso contrário, NonWritableChannelException e NonReadableChannelException serão lançados separadamente.
Se você deseja que o pipe suporte leitura e gravação, você deve usar o modo de RandomAccessFile
leitura e gravação.
O código de teste a seguir é o uso básico do pipeline de arquivos
//1. 打开文件管道
FileChannel channel = new RandomAccessFile(file_name,"rw").getChannel();
ByteBuffer buffer=ByteBuffer.allocate(1024); // 声明1024个空间
// 从文件中 读取数据并写入管道 再写入缓冲 channel.read(buffer); buffer.flip();//上面学的,写完之后,需要将position归0,为了后面的读取做准备 byte[] bytes= new byte[buffer.remaining()]; int i =0; while (buffer.hasRemaining()){ bytes[i++]= buffer.get(); } System.out.println(new String(bytes)); // 把缓冲区数据写入到管道 channel.write(ByteBuffer.wrap("森林大帅哥".getBytes())); channel.close();
5.2 DatagramChannel socket UDP pipe
UDP é um protocolo sem conexão e DatagramChannel fornece serviços para este protocolo para receber mensagens de clientes.
O uso básico do DatagramChannel é o seguinte:
public void test1() throws IOException { DatagramChannel channel=DatagramChannel.open(); // 绑定端口 channel.bind(new InetSocketAddress(8080)); ByteBuffer buffer=ByteBuffer.allocate(8192); while (true){ buffer.clear(); // 清空还原 channel.receive(buffer); // 阻塞 buffer.flip(); byte[] bytes=new byte[buffer.remaining()]; buffer.get(bytes); System.out.println(new String(bytes)); } }
Use o comando nc-uv 127.0.0.1 8080 para enviar udp para o número da porta IP especificada
O console de ideias terá a seguinte saída:
5.3 tubo de soquete TCP
TCP é um protocolo de conexão e a comunicação só pode ser feita após o estabelecimento de uma conexão. Isso requer os seguintes dois pipelines:
-
** ServerSocketChannel: ** Usado para estabelecer uma conexão com o cliente
-
** SocketChannel: ** Usado para ler e escrever mensagens com o cliente
O código de teste é o seguinte:
@Test
public void test1() throws IOException { // 用于与客户端建立连接 ServerSocketChannel channel = ServerSocketChannel.open(); channel.bind(new InetSocketAddress(8080)); while (true) {//循环接收请求,分配子线程去执行请求 // 用于和客户端进行消息读写 SocketChannel socketChannel = channel.accept(); handle(socketChannel); } } public void handle(final SocketChannel socketChannel) throws IOException { // 2.通信 Thread thread = new Thread(new Runnable() { @Override public void run() { ByteBuffer buffer = ByteBuffer.allocate(8192); while (true) { try { buffer.clear(); socketChannel.read(buffer); // 从buffer 当中读出来 buffer.flip(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); String message = new String(bytes); System.out.println(message); // 写回去 buffer.rewind(); socketChannel.write(buffer); if (message.trim().equals("exit")) { break; } } catch (Exception e) { e.printStackTrace(); } } try { socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }); thread.start(); }
Pode ser testado pelo comando de serviço TCP telnet 127.0.0.1 8080