le mie informazioni di contatto
Posta[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Descrive la conoscenza di base di NIO a livello ava, per una revisione di base
In Java, NIO (Non-blocking I/O o New I/O) è un nuovo set di API per operazioni di input/output introdotte in Java SE 1.4 e versioni successive.
Rispetto al modello IO tradizionale, offre maggiore efficienza e migliori capacità di elaborazione simultanea. La caratteristica principale di NIO è la sua funzionalità non bloccante, che consente a un singolo thread di gestire più canali I/O, migliorando così notevolmente le prestazioni dell'applicazione in scenari ad alta concorrenza.
Lo scenario di utilizzo di Java NIO è particolarmente adatto per situazioni in cui è necessario elaborare un gran numero di connessioni simultanee. Ad esempio, in un server di rete, un thread può gestire migliaia di connessioni client senza la necessità di allocare un thread indipendente per ciascuna connessione Può ridurre significativamente il consumo delle risorse di sistema e migliorare le capacità di elaborazione
Il canale è il mezzo del flusso di dati in Java NIO. È bidirezionale e può essere utilizzato per leggere o scrivere dati. Il vantaggio principale dei canali è che non sono bloccanti, il che significa che un thread può gestire più canali. Quando non si verificano eventi su un canale, il thread non verrà bloccato e potrà invece gestire altre attività. I principali tipi di canale includono:
FileChannel
: Utilizzato per operazioni di lettura e scrittura di file. Può essere utilizzato per scrivere dati dal buffer al file o per leggere dati dal file nel buffer.SocketChannel
: utilizzato per le connessioni TCP nelle comunicazioni di rete e può essere utilizzato per leggere e scrivere dati.ServerSocketChannel
: Utilizzato per accettare nuove connessioni SocketChannel, simili al tradizionale ServerSocket.DatagramChannel
: Utilizzato per la comunicazione UDP, che può inviare e ricevere datagrammi.Il buffer è un oggetto utilizzato per archiviare dati in NIO. È un array di byte che può scrivere e leggere dati da esso. Un buffer ha una capacità specifica e ha due proprietà importanti: posizione e limite.
I principali tipi di buffer sonoByteBuffer
、CharBuffer
、ShortBuffer
、IntBuffer
、LongBuffer
、FloatBuffer
EDoubleBuffer
, ogni tipo corrisponde a un tipo di dati primitivo.
Un selettore è un multiplexer in NIO che consente a un singolo thread di monitorare eventi da più canali, ad esempio lettura, scrittura, connessione e ricezione di eventi. Il selettore notifica all'applicazione quando uno dei canali è pronto per le operazioni di I/O. L'utilizzo dei selettori migliora notevolmente le capacità di elaborazione della concorrenza delle applicazioni di rete poiché non è necessario creare un thread per ogni connessione.
I principali metodi di selezione includono:
select()
: si blocca finché almeno un canale non è pronto per le operazioni di I/O.selectedKeys()
: Restituisce un Set contenente gli oggetti SelectionKey di tutti i canali preparati.wakeup()
: Interrupt bloccatoselect()
trasferimento.SelectionKey
È l'associazione tra selettori e canali. Rappresenta lo stato di registrazione di un canale sul selettore, inclusi canali, selettori, raccolte di eventi interessati e raccolte di eventi pronti.
Questo è il modello I/O più tradizionale. In Java, le tradizionali API Socket e ServerSocket si basano sul blocco dell'I/O. In questa modalità, quando un thread avvia un'operazione di lettura o scrittura, il thread viene bloccato fino al completamento dell'operazione di I/O. Se non sono presenti dati da leggere per l'operazione di lettura o l'operazione di scrittura non può essere completata immediatamente, il thread attenderà fino al completamento dell'operazione.
Caratteristiche:
L'I/O non bloccante fa parte del framework Java NIO (New I/O), che consente ai thread di avviare operazioni di lettura e scrittura senza essere bloccati. Se non sono presenti dati da leggere per un'operazione di lettura o l'operazione di scrittura non può essere completata immediatamente, il thread non verrà sospeso e potrà continuare a eseguire altre attività.
Caratteristiche:
Il multiplexing consiste nel monitorare più descrittori di file contemporaneamente attraverso un thread e operare su un descrittore solo quando è pronto (di solito significa che i dati sono leggibili o il buffer di scrittura è scrivibile). I selettori vengono utilizzati in Java per implementare il multiplexing.
Caratteristiche:
Avviso:
Nelle applicazioni pratiche, l'I/O non bloccante viene spesso utilizzato insieme al multiplexing. Ad esempio, un server può utilizzare un selettore per monitorare più SocketChannel. Quando un SocketChannel contiene dati che possono essere letti o scritti, il selettore avviserà il server e il server elaborerà questo SocketChannel specifico in modo non bloccante.
In Java, Stream e Channel sono due modi diversi di elaborare i flussi di dati. Appartengono rispettivamente alla libreria IO standard e alla libreria NIO. Ecco un confronto dettagliato tra i due concetti:
Stream fa parte del modello IO (blocking IO) standard di Java, che fornisce un modo per leggere e scrivere i dati in sequenza. Il flusso è diviso in due tipi: InputStream e OutputStream, che vengono utilizzati rispettivamente per leggere e scrivere i dati.Questi flussi possono essere flussi di byte (comeInputStream
, OutputStream
) o un flusso di caratteri (comeReader
, Writer
)。
Caratteristiche:
close()
metodo per liberare risorse.A partire da Java 7, l'istruzione try-with-resources può chiudere automaticamente l'implementazioneAutoCloseable
Risorse dell'interfaccia, incluso Stream. Channel fa parte del modello Java NIO (New IO), che fornisce un livello di astrazione più elevato rispetto a Stream, consentendo un'elaborazione dei dati più efficiente. I canali possono essere bidirezionali, ovvero possono essere utilizzati per leggere e scrivere dati.I principali tipi di canali includonoFileChannel
、SocketChannel
、ServerSocketChannel
EDatagramChannel
。
Caratteristiche:
Riassumere
In Java, i modelli operativi di I/O possono essere classificati in base alle due dimensioni sincrono/asincrono e bloccante/non bloccante.
definizione : L'I/O con blocco sincrono è il modello di I/O più tradizionale. Quando un thread chiama un'operazione di I/O (come la lettura o la scrittura), il thread verrà bloccato fino al completamento dell'operazione. Ciò significa che il thread non può eseguire altre attività finché l'operazione non viene completata.
Caratteristiche:
InputStream
、OutputStream
、Socket
EServerSocket
scena.Esempio:
Usa tradizionaleInputStream
EOutputStream
Per leggere e scrivere file:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class SyncBlockingIOExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
definizione : nell'I/O sincrono non bloccante, il thread non verrà bloccato dopo aver chiamato l'operazione I/O. Se l'operazione non può essere completata immediatamente, il metodo restituirà immediatamente un risultato, solitamente restituendo un valore speciale (come -1 o 0) o lanciando un'eccezione per indicare che l'operazione non è stata completata.
Caratteristiche:
Channels
EBuffers
, a chiamataconfigureBlocking(false)
Imposta il canale in modalità non bloccante.Esempio:
Utilizzando NIOFileChannel
Per la lettura e la scrittura non bloccanti:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
public class SyncNonBlockingIOExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
inChannel.configureBlocking(false);
while (inChannel.read(buffer) > 0) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
definizione: Utilizzo del modello I/O multiplex sincronoSelector
(selettore) per monitorare piùChannel
evento.Quando il thread chiamaSelector.select()
, si bloccherà finché non ce ne sarà almeno unoChannel
Si verificano eventi (come ad esempio leggibili, scrivibili, richieste di connessione, ecc.).
Caratteristiche:
Channel
, migliora la concorrenza.Selector
ESelectionKey
Meccanismo in grado di gestire in modo efficiente un gran numero di connessioni simultanee.Esempio:
Utilizzando NIOFileChannel
Per la lettura e la scrittura non bloccanti:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;
public class SyncMultiplexingIOExample {
public static void main(String[] args) throws IOException {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = ssc.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
}
}
keys.clear();
}
}
}
}
}
definizione : nel modello I/O asincrono non bloccante, il thread ritorna immediatamente dopo aver avviato l'operazione di I/O senza attendere il completamento dell'operazione. Una volta completata l'operazione, il thread verrà avvisato in modo asincrono tramite una funzione di callback o una notifica di evento.
Caratteristiche:
AsynchronousChannel
Interfaccia e la sua implementazione della sottoclasse, come ad esempioAsynchronousFileChannel
EAsynchronousSocketChannel
。Esempio:
Utilizzando NIOAsynchronousFileChannel
Eseguire la lettura e la scrittura asincrona dei file:
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
public class AsyncNonBlockingIOExample {
public static void main(String[] args) throws IOException, InterruptedException {
AsynchronousFileChannel inChannel = AsynchronousFileChannel.open(Paths.get("input.txt"), StandardOpenOption.READ);
AsynchronousFileChannel outChannel = AsynchronousFileChannel.open(Paths.get("output.txt"), StandardOpenOption.WRITE);
ByteBuffer buffer = ByteBuffer.allocate(1024);
CountDownLatch latch = new CountDownLatch(1);
inChannel.read(buffer, 0, buffer, 0, (channel, result) -> {
buffer.flip();
outChannel.write(buffer, 0, buffer, 0, null);
latch.countDown();
});
latch.await();
inChannel.close();
outChannel.close();
}
}
Nota: il modello asincrono richiede il supporto del sistema operativo sottostante (Kernel)
definizione : In teoria questo modello non esiste, perché "asincrono" significa che l'operazione viene eseguita in background e il thread non è bloccato. Pertanto, il blocco asincrono degli I/O è un concetto contraddittorio e non apparirà nella pratica.
La scelta del modello I/O appropriato è fondamentale per le prestazioni e la gestione delle risorse. Negli scenari ad alta concorrenza, i modelli I/O sincrono non bloccante o I/O multiplex sincrono sono scelte comuni. Negli scenari che richiedono prestazioni e velocità di risposta estreme, il modello I/O asincrono non bloccante è la prima scelta.
Concetto di copia zero
La tecnologia zero-copy significa che durante il processo di trasmissione dei dati da un luogo a un altro, i dati non devono essere copiati tra lo spazio utente e lo spazio kernel, o almeno riduce il numero di tali copie, migliorando così l'efficienza del sistema. Nelle operazioni di I/O tradizionali, quando i dati vengono letti dalla rete o dal disco, vengono prima copiati nel buffer nello spazio del kernel e quindi copiati dallo spazio del kernel al buffer nello spazio utente. Al contrario, in zero-copy, i dati possono essere elaborati direttamente nello spazio del kernel o trasferiti direttamente dallo spazio del kernel al dispositivo di rete, riducendo così l'operazione di copia della CPU, riducendo il sovraccarico del sistema e migliorando l'efficienza della trasmissione dei dati.
Origine della copia zero
Il concetto di zero copy è apparso per la prima volta nella progettazione del sistema operativo, con l'obiettivo di risolvere il collo di bottiglia prestazionale causato dalla copia dei dati nelle tradizionali operazioni di I/O. Nei primi sistemi informatici, tutte le operazioni di I/O richiedevano più copie di dati tra lo spazio utente e lo spazio kernel. Ciò divenne gradualmente un collo di bottiglia delle prestazioni dopo che le reti ad alta velocità e i dischi di grande capacità divennero comuni.
Punti tecnici chiave
Implementazione in Java:
Java supporta la tecnologia zero-copy tramite il framework NIO (New I/O). Presentato NIOFileChannel
ESocketChannel
e altre classi, che forniscono operazioni di I/O più efficienti. Nello specifico,FileChannel.transferTo()
EFileChannel.transferFrom()
I metodi possono trasferire i dati direttamente da un canale file a un canale socket o viceversa senza caricare i dati in un buffer, ottenendo così zero copie.
Ad esempio, supponiamo di dover inviare il contenuto di un file di grandi dimensioni alla rete. L'approccio tradizionale consiste nel leggere prima il contenuto del file in un buffer e quindi scriverlo nella rete. Ciò comporta due operazioni di copia: una dal disco al buffer e un'altra dal buffer alla rete.durante l'utilizzotransferTo()
Quando si utilizza questo metodo, i dati possono essere trasferiti direttamente dal disco alla rete senza la necessità di un buffer intermedio, riducendo così il numero di copie e raggiungendo zero copie.
Un esempio utilizzando ByteBuffer:
ByteBuffer
e altroBuffer
classe (comeCharBuffer
,ShortBuffer
ecc.) forniscono buffer che possono essere riempiti o svuotati senza coinvolgere direttamente una copia tra lo spazio utente e lo spazio kernel.ByteBuffer
Può essere utilizzato direttamente o indirettamente nella tecnologia zero-copy:
ByteBuffer.allocateDirect(size)
CreatoByteBuffer
Le istanze vengono mappate direttamente sulla memoria fisica, ignorando l'heap Java.Quando tale buffer viene confrontato conFileChannel
OSocketChannel
Se utilizzati insieme, i dati possono essere trasferiti direttamente tra la memoria fisica e i dispositivi hardware senza la necessità di copie aggiuntive tramite l'heap Java. Ciò consente la vera copia zero su alcune piattaforme.FileChannel
DitransferTo()
EtransferFrom()
: Questi metodi consentono di archiviare i dati direttamente nelFileChannel
ESocketChannel
trasmesso traByteBuffer
come intermediario. Ciò significa che i dati possono essere trasferiti direttamente dal disco alla rete o viceversa senza dover essere copiati tra lo spazio utente e lo spazio kernel.ByteBuffer
Diwrap()
metodo:wrap()
Il metodo consente di racchiudere un array di byte esistente o un altro buffer in un fileByteBuffer
, quindi non è necessario copiare i dati in un nuovo buffer. Ciò è utile per evitare copie inutili dei dati.ByteBuffer
Dislice()
metodo:slice()
Il metodo crea una visualizzazione del buffer senza copiare i dati sottostanti.Ciò significa che un grandeByteBuffer
Suddiviso in più buffer più piccoli senza copiare i dati.Combina ByteBuffer con FileChannel:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class ZeroCopyExample {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
long transferred = fileChannel.transferTo(0, fileChannel.size(), System.out);
System.out.println("Transferred bytes: " + transferred);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Avviso:
Dovremmo notare che in realtà non esiste una copia zero completa. La copia zero menzionata qui è solo per la nostra stessa applicazione, che non ha una copia a livello di utente. Ma anche a livello di utente, è impossibile non effettuare alcuna operazione di copia, ma ridurre il più possibile le copie. Pertanto, possiamo capire che il termine copia zero si riferisce in realtà alla tecnologia di riduzione del numero di copie dei dati. e ciò non significa che non esista una vera e propria operazione di copia.