2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Beschreibt die Grundkenntnisse von NIO auf Ava-Ebene zur grundlegenden Überprüfung
In Java ist NIO (Non-blocking I/O oder New I/O) ein neuer Satz von Eingabe-/Ausgabeoperations-APIs, die in Java SE 1.4 und nachfolgenden Versionen eingeführt wurden.
Im Vergleich zum herkömmlichen E/A-Modell bietet es eine höhere Effizienz und bessere Möglichkeiten zur gleichzeitigen Verarbeitung. Das Hauptmerkmal von NIO ist seine nicht blockierende Funktion, die es einem einzelnen Thread ermöglicht, mehrere E/A-Kanäle zu verwalten und dadurch die Anwendungsleistung in Szenarien mit hoher Parallelität erheblich zu verbessern.
Das Einsatzszenario von Java NIO eignet sich besonders für Situationen, in denen eine große Anzahl gleichzeitiger Verbindungen verarbeitet werden muss. In einem Netzwerkserver kann beispielsweise ein Thread Tausende von Client-Verbindungen verwalten, ohne dass jeder Verbindung ein unabhängiger Thread zugewiesen werden muss . Kann den Ressourcenverbrauch des Systems erheblich reduzieren und die Verarbeitungsfähigkeiten verbessern
Kanal ist das Medium des Datenflusses in Java NIO. Er ist bidirektional und kann zum Lesen oder Schreiben von Daten verwendet werden. Der Hauptvorteil von Kanälen besteht darin, dass sie nicht blockierend sind, was bedeutet, dass ein Thread mehrere Kanäle verwalten kann. Wenn auf einem Kanal keine Ereignisse auftreten, wird der Thread nicht blockiert und kann stattdessen andere Aufgaben verarbeiten. Zu den wichtigsten Kanaltypen gehören:
FileChannel
: Wird zum Lesen und Schreiben von Dateien verwendet. Es kann zum Schreiben von Daten aus dem Puffer in die Datei oder zum Lesen von Daten aus der Datei in den Puffer verwendet werden.SocketChannel
: Wird für TCP-Verbindungen in der Netzwerkkommunikation verwendet und kann zum Lesen und Schreiben von Daten verwendet werden.ServerSocketChannel
: Wird zum Akzeptieren neuer SocketChannel-Verbindungen verwendet, ähnlich wie beim herkömmlichen ServerSocket.DatagramChannel
: Wird für die UDP-Kommunikation verwendet, die Datagramme senden und empfangen kann.Puffer ist ein Objekt zum Speichern von Daten in NIO. Es handelt sich um ein Byte-Array, das Daten schreiben und daraus lesen kann. Ein Puffer hat eine bestimmte Kapazität und zwei wichtige Eigenschaften: Position und Grenze.
Die wichtigsten Arten von Puffern sindByteBuffer
、CharBuffer
、ShortBuffer
、IntBuffer
、LongBuffer
、FloatBuffer
UndDoubleBuffer
, jeder Typ entspricht einem primitiven Datentyp.
Ein Selektor ist ein Multiplexer in NIO, der es einem einzelnen Thread ermöglicht, Ereignisse von mehreren Kanälen zu überwachen, z. B. Lese-, Schreib-, Verbindungs- und Empfangsereignisse. Der Selektor benachrichtigt die Anwendung, wenn einer der Kanäle für E/A-Vorgänge bereit ist. Die Verwendung von Selektoren verbessert die Parallelitätsverarbeitungsfähigkeiten von Netzwerkanwendungen erheblich, da nicht für jede Verbindung ein Thread erstellt werden muss.
Zu den wichtigsten Auswahlmethoden gehören:
select()
: Blockiert, bis mindestens ein Kanal für E/A-Vorgänge bereit ist.selectedKeys()
: Gibt ein Set zurück, das die SelectionKey-Objekte aller vorbereiteten Kanäle enthält.wakeup()
: Interrupt blockiertselect()
überweisen.SelectionKey
Es handelt sich um die Zuordnung zwischen Selektoren und Kanälen. Es stellt den Registrierungsstatus eines Kanals auf dem Selektor dar, einschließlich Kanälen, Selektoren, interessierten Ereignissammlungen und bereiten Ereignissammlungen.
Dies ist das traditionellste E/A-Modell. In Java basieren die traditionellen Socket- und ServerSocket-APIs auf dem Blockieren von E/A. Wenn in diesem Modus ein Thread einen Lese- oder Schreibvorgang initiiert, wird der Thread blockiert, bis der E/A-Vorgang abgeschlossen ist. Wenn für den Lesevorgang keine Daten zum Lesen vorhanden sind oder der Schreibvorgang nicht sofort abgeschlossen werden kann, wartet der Thread, bis der Vorgang abgeschlossen ist.
Merkmale:
Nicht blockierendes I/O ist Teil des Java NIO (New I/O)-Frameworks, das es Threads ermöglicht, Lese- und Schreibvorgänge zu initiieren, ohne blockiert zu werden. Wenn für einen Lesevorgang keine Daten zum Lesen vorhanden sind oder der Schreibvorgang nicht sofort abgeschlossen werden kann, wird der Thread nicht angehalten und kann weiterhin andere Aufgaben ausführen.
Merkmale:
Beim Multiplexen werden mehrere Dateideskriptoren gleichzeitig über einen Thread überwacht und ein Deskriptor nur dann bearbeitet, wenn er bereit ist (normalerweise bedeutet dies, dass die Daten lesbar sind oder der Schreibpuffer beschreibbar ist). Selektoren werden in Java verwendet, um Multiplexing zu implementieren.
Merkmale:
Beachten:
In praktischen Anwendungen werden nicht blockierende E/A häufig in Verbindung mit Multiplexing verwendet. Beispielsweise kann ein Server einen Selector verwenden, um mehrere SocketChannels zu überwachen. Wenn ein SocketChannel über Daten verfügt, die gelesen oder geschrieben werden können, benachrichtigt der Selector den Server und der Server verarbeitet diesen spezifischen SocketChannel auf nicht blockierende Weise.
In Java sind Stream und Channel zwei verschiedene Methoden zur Verarbeitung von Datenströmen. Sie gehören zur Standard-IO-Bibliothek bzw. zur NIO-Bibliothek. Hier ein detaillierter Vergleich der beiden Konzepte:
Stream ist Teil des Standard-IO-Modells (Blocking IO) von Java, das eine Möglichkeit bietet, Daten sequenziell zu lesen und zu schreiben. Stream ist in zwei Typen unterteilt: InputStream und OutputStream, die zum Lesen bzw. Schreiben von Daten verwendet werden.Diese Streams können Byte-Streams sein (z. BInputStream
, OutputStream
) oder ein Zeichenstrom (z. BReader
, Writer
)。
Merkmale:
close()
Methode zur Freigabe von Ressourcen.Ab Java 7 kann die try-with-resources-Anweisung die Implementierung automatisch schließenAutoCloseable
Schnittstellenressourcen, einschließlich Stream. Channel ist Teil des Java NIO-Modells (New IO), das eine höhere Abstraktionsebene als Stream bietet und so eine effizientere Datenverarbeitung ermöglicht. Kanäle können bidirektional sein, d. h. sie können zum Lesen und Schreiben von Daten verwendet werden.Zu den wichtigsten Kanaltypen gehören:FileChannel
、SocketChannel
、ServerSocketChannel
UndDatagramChannel
。
Merkmale:
Zusammenfassen
In Java können E/A-Operationsmodelle anhand der beiden Dimensionen synchron/asynchron und blockierend/nicht blockierend klassifiziert werden.
Definition : Synchrones Blockieren von E/A ist das traditionellste E/A-Modell. Wenn ein Thread eine E/A-Operation aufruft (z. B. Lesen oder Schreiben), wird der Thread blockiert, bis die Operation abgeschlossen ist. Dies bedeutet, dass der Thread keine anderen Aufgaben ausführen kann, bis der Vorgang abgeschlossen ist.
Merkmale:
InputStream
、OutputStream
、Socket
UndServerSocket
Szene.Beispiel:
Verwenden Sie traditionellInputStream
UndOutputStream
So lesen und schreiben Sie Dateien:
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();
}
}
}
Definition : Bei synchroner nicht blockierender E/A wird der Thread nach dem Aufruf der E/A-Operation nicht blockiert. Wenn der Vorgang nicht sofort abgeschlossen werden kann, kehrt die Methode sofort zurück und gibt normalerweise einen Sonderwert (z. B. -1 oder 0) zurück oder löst eine Ausnahme aus, um anzuzeigen, dass der Vorgang nicht abgeschlossen ist.
Merkmale:
Channels
UndBuffers
, durch AnrufconfigureBlocking(false)
Stellen Sie den Kanal auf den nicht blockierenden Modus ein.Beispiel:
Mit NIOFileChannel
Für blockierungsfreies Lesen und Schreiben:
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();
}
}
}
Definition: Verwendung des synchronen Multiplex-E/A-ModellsSelector
(Auswahl), um mehrere zu überwachenChannel
Ereignis.Wenn der Thread anruftSelector.select()
, wird es blockiert, bis mindestens einer vorhanden istChannel
Es treten Ereignisse (z. B. lesbar, beschreibbar, Verbindungsanfrage usw.) auf.
Merkmale:
Channel
, verbessert die Parallelität.Selector
UndSelectionKey
Mechanismus, der eine große Anzahl gleichzeitiger Verbindungen effizient verarbeiten kann.Beispiel:
Mit NIOFileChannel
Für blockierungsfreies Lesen und Schreiben:
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();
}
}
}
}
}
Definition : Im asynchronen nicht blockierenden E/A-Modell kehrt der Thread sofort nach dem Initiieren des E/A-Vorgangs zurück, ohne auf den Abschluss des Vorgangs zu warten. Wenn der Vorgang abgeschlossen ist, wird der Thread asynchron über eine Rückruffunktion oder eine Ereignisbenachrichtigung benachrichtigt.
Merkmale:
AsynchronousChannel
Schnittstelle und ihre Unterklassenimplementierung, wie zAsynchronousFileChannel
UndAsynchronousSocketChannel
。Beispiel:
Mit NIOAsynchronousFileChannel
Führen Sie asynchrones Lesen und Schreiben von Dateien durch:
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();
}
}
Hinweis: Das asynchrone Modell erfordert Unterstützung durch das zugrunde liegende Betriebssystem (Kernel).
Definition : Theoretisch existiert dieses Modell nicht, da „asynchron“ bedeutet, dass der Vorgang im Hintergrund ausgeführt wird und der Thread nicht blockiert ist. Daher ist asynchrones Blockieren von E/A ein widersprüchliches Konzept und wird in der Praxis nicht vorkommen.
Die Wahl des geeigneten I/O-Modells ist für die Leistung und das Ressourcenmanagement von entscheidender Bedeutung. In Szenarien mit hoher Parallelität sind synchrone, nicht blockierende E/A- oder synchrone, gemultiplexte E/A-Modelle die häufigste Wahl. In Szenarien, die extreme Leistung und Reaktionsgeschwindigkeit erfordern, ist das asynchrone, nicht blockierende E/A-Modell die erste Wahl.
Zero-Copy-Konzept
Zero-Copy-Technologie bedeutet, dass während des Prozesses der Datenübertragung von einem Ort zum anderen keine Daten zwischen User-Space und Kernel-Space kopiert werden müssen oder zumindest die Anzahl solcher Kopien reduziert wird, wodurch die Systemeffizienz verbessert wird. Bei herkömmlichen E/A-Vorgängen werden Daten beim Lesen aus dem Netzwerk oder der Festplatte zunächst in den Puffer im Kernel-Bereich und dann vom Kernel-Bereich in den Puffer im Benutzerbereich kopiert. Im Gegensatz dazu können bei der Nullkopie Daten direkt im Kernelraum verarbeitet oder direkt vom Kernelraum auf das Netzwerkgerät übertragen werden, wodurch der CPU-Kopiervorgang reduziert, der Systemaufwand verringert und die Effizienz der Datenübertragung verbessert wird.
Quelle der Nullkopie
Das Konzept der Nullkopie tauchte erstmals im Betriebssystemdesign auf und zielte darauf ab, den Leistungsengpass zu lösen, der durch das Kopieren von Daten bei herkömmlichen E/A-Vorgängen verursacht wird. In frühen Computersystemen erforderten alle E/A-Vorgänge mehrere Datenkopien im Benutzer- und Kernelbereich. Dies wurde allmählich zu einem Leistungsengpass, nachdem Hochgeschwindigkeitsnetzwerke und Festplatten mit großer Kapazität üblich wurden.
Wichtige technische Punkte
Implementierung in Java:
Java unterstützt die Zero-Copy-Technologie durch das NIO-Framework (New I/O). NIO eingeführtFileChannel
UndSocketChannel
und andere Klassen, die effizientere I/O-Operationen ermöglichen. Speziell,FileChannel.transferTo()
UndFileChannel.transferFrom()
Methoden können Daten direkt von einem Dateikanal zu einem Socket-Kanal oder umgekehrt übertragen, ohne die Daten in einen Puffer zu laden, wodurch eine Kopie ohne Null erreicht wird.
Angenommen, Sie müssen den Inhalt einer großen Datei an das Netzwerk senden. Der herkömmliche Ansatz besteht darin, zuerst den Dateiinhalt in einen Puffer zu lesen und dann den Pufferinhalt in das Netzwerk zu schreiben. Dies umfasst zwei Kopiervorgänge: einen von der Festplatte in den Puffer und einen weiteren vom Puffer in das Netzwerk.während dem BenutzentransferTo()
Bei Verwendung dieser Methode können Daten direkt von der Festplatte in das Netzwerk übertragen werden, ohne dass ein Zwischenpuffer erforderlich ist, wodurch die Anzahl der Kopien reduziert und eine Nullkopie erreicht wird.
Ein Beispiel mit ByteBuffer:
ByteBuffer
und andereBuffer
Klasse (wie zCharBuffer
,ShortBuffer
usw.) stellen Puffer bereit, die gefüllt oder geleert werden können, ohne dass direkt eine Kopie zwischen Benutzerraum und Kernelraum erforderlich ist.ByteBuffer
Direkt oder indirekt in der Zero-Copy-Technologie einsetzbar:
ByteBuffer.allocateDirect(size)
ErstelltByteBuffer
Instanzen werden unter Umgehung des Java-Heaps direkt dem physischen Speicher zugeordnet.Wenn ein solcher Puffer verglichen wird mitFileChannel
oderSocketChannel
Bei gemeinsamer Verwendung können Daten direkt zwischen physischem Speicher und Hardwaregeräten übertragen werden, ohne dass zusätzliche Kopien über den Java-Heap erforderlich sind. Dies ermöglicht auf einigen Plattformen echtes Zero-Copy.FileChannel
vontransferTo()
UndtransferFrom()
: Mit diesen Methoden können Daten direkt gespeichert werdenFileChannel
UndSocketChannel
zwischen übertragenByteBuffer
als Vermittler. Das bedeutet, dass Daten direkt von der Festplatte ins Netzwerk oder umgekehrt übertragen werden können, ohne dass sie zwischen Userspace und Kernelspace kopiert werden müssen.ByteBuffer
vonwrap()
Methode:wrap()
Mit der Methode können Sie ein vorhandenes Byte-Array oder einen anderen Puffer in einen einbindenByteBuffer
, sodass die Daten nicht in einen neuen Puffer kopiert werden müssen. Dies ist nützlich, um unnötiges Kopieren von Daten zu vermeiden.ByteBuffer
vonslice()
Methode:slice()
Die Methode erstellt eine Ansicht des Puffers, ohne die zugrunde liegenden Daten zu kopieren.Dies bedeutet, dass eine großeByteBuffer
Aufteilen in mehrere kleinere Puffer, ohne Daten zu kopieren.Verwenden Sie ByteBuffer mit 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();
}
}
}
Beachten:
Wir sollten beachten, dass es tatsächlich keine vollständige Nullkopie gibt. Die hier erwähnte Nullkopie gilt nur für unsere Anwendung selbst, die keine Kopie auf Benutzerebene hat. Aber selbst auf Benutzerebene ist es unmöglich, überhaupt keine Kopiervorgänge durchzuführen, sondern die Kopien so weit wie möglich zu reduzieren. Daher können wir verstehen, dass sich der Begriff Nullkopie tatsächlich auf die Technologie zur Reduzierung der Anzahl der Datenkopien bezieht. und das bedeutet nicht, dass es keinen echten Kopiervorgang gibt.