Fünf Möglichkeiten zur Maximierung von Java NIO und NIO.2

Java NIO - das neue Input / Output-API-Paket - wurde 2002 mit J2SE 1.4 eingeführt. Ziel von Java NIO war es, die Programmierung von E / A-intensiven Aufgaben auf der Java-Plattform zu verbessern. Ein Jahrzehnt später wissen viele Java-Programmierer immer noch nicht, wie sie NIO optimal nutzen können, und noch weniger wissen, dass Java SE 7 mehr neue Eingabe- / Ausgabe-APIs (NIO.2) eingeführt hat. In diesem Tutorial finden Sie fünf einfache Beispiele, die die Vorteile der NIO- und NIO.2-Pakete in gängigen Java-Programmierszenarien demonstrieren.

Der Hauptbeitrag von NIO und NIO.2 zur Java-Plattform besteht darin, die Leistung in einem der Kernbereiche der Java-Anwendungsentwicklung zu verbessern: der Eingabe- / Ausgabeverarbeitung. Weder ist die Arbeit mit dem Paket besonders einfach, noch sind die neuen Eingabe- / Ausgabe-APIs für jedes Java-E / A-Szenario erforderlich. Bei korrekter Verwendung können Java NIO und NIO.2 die für einige allgemeine E / A-Vorgänge erforderliche Zeit verkürzen. Das ist die Supermacht von NIO und NIO.2, und dieser Artikel enthält fünf relativ einfache Möglichkeiten, sie zu nutzen:

  1. Benachrichtiger wechseln (weil jeder einen Listener braucht)
  2. Selektoren helfen beim Multiplexen
  3. Kanäle - Versprechen und Wirklichkeit
  4. Speicherzuordnung - wo es darauf ankommt
  5. Zeichenkodierung und Suche

Der NIO-Kontext

Wie kommt es, dass eine 10 Jahre alte Erweiterung immer noch das neue Eingabe- / Ausgabepaket für Java ist? Der Grund dafür ist, dass für viele arbeitende Java-Programmierer die grundlegenden Java-E / A-Operationen mehr als ausreichend sind. Die meisten Java - Entwickler nicht haben zu NIO für unsere tägliche Arbeit zu lernen. Darüber hinaus ist NIO nicht nur ein Leistungspaket. Stattdessen handelt es sich um eine heterogene Sammlung von Funktionen im Zusammenhang mit Java-E / A.. NIO steigert die Leistung von Java-Anwendungen, indem es dem Metall eines Java-Programms "näher kommt". Dies bedeutet, dass die NIO- und NIO.2-APIs Einstiegspunkte für Betriebssysteme auf niedrigerer Ebene bereitstellen. Der Nachteil von NIO besteht darin, dass wir gleichzeitig mehr Kontrolle über die E / A haben und mehr Sorgfalt walten lassen müssen als bei der grundlegenden E / A-Programmierung. Ein weiterer Aspekt von NIO ist die Aufmerksamkeit für die Ausdruckskraft von Anwendungen, mit der wir in einigen der folgenden Übungen spielen werden.

Beginnen Sie mit NIO und NIO.2

Für NIO stehen viele gute Referenzen zur Verfügung - siehe Ressourcen für einige ausgewählte Links. Für den Einstieg in NIO und NIO.2 sind die Dokumentation zu Java 2 SDK Standard Edition (SE) und die Dokumentation zu Java SE 7 unverzichtbar. Um die Beispiele in diesem Artikel ausführen zu können, müssen Sie mit JDK 7 oder höher arbeiten.

Für viele Entwickler kann die erste Begegnung mit NIO während der Wartungsarbeiten auftreten: Eine Anwendung verfügt über die richtige Funktionalität, reagiert jedoch nur langsam. Daher schlägt jemand vor, NIO zu verwenden, um sie zu beschleunigen. NIO glänzt, wenn es zur Steigerung der Verarbeitungsleistung verwendet wird, aber seine Ergebnisse sind eng mit der zugrunde liegenden Plattform verknüpft. (Beachten Sie, dass NIO plattformabhängig ist.) Wenn Sie NIO zum ersten Mal verwenden, müssen Sie sorgfältig messen. Möglicherweise stellen Sie fest, dass die Fähigkeit von NIO, die Anwendungsleistung zu beschleunigen, nicht nur vom Betriebssystem abhängt, sondern auch von der spezifischen JVM, dem Hostvirtualisierungskontext, den Massenspeichereigenschaften und sogar von Daten. Die Messung kann jedoch schwierig zu verallgemeinern sein. Beachten Sie dies insbesondere dann, wenn eine mobile Bereitstellung zu Ihren Zielen gehört.

Lassen Sie uns nun ohne weiteres fünf wichtige Einrichtungen von NIO und NIO.2 erkunden.

1. Benachrichtiger wechseln (weil jeder einen Listener braucht)

Die Leistung von Java-Anwendungen ist die häufigste Attraktion für Entwickler, die an NIO oder NIO.2 interessiert sind. Nach meiner Erfahrung ist der Dateiänderungsbenachrichtiger von NIO.2 jedoch die überzeugendste (wenn auch nicht gesungene) Funktion der neuen Eingabe- / Ausgabe-APIs.

Viele Anwendungen der Enterprise-Klasse müssen bestimmte Maßnahmen ergreifen, wenn:

  • Eine Datei wird in einen FTP-Ordner hochgeladen
  • Eine Konfigurationsdefinition wird geändert
  • Ein Dokumententwurf wird aktualisiert
  • Ein weiteres Dateisystemereignis tritt auf

Dies sind alles Beispiele für Änderungsbenachrichtigungen oder Änderungsantworten. In früheren Versionen von Java (und anderen Sprachen) war das Abrufen normalerweise der beste Weg, um Änderungsereignisse zu erkennen. Polling ist eine besondere Art von Endlosschleife: Überprüfen Sie das Dateisystem oder ein anderes Objekt, vergleichen Sie es mit seinem letzten bekannten Status, und überprüfen Sie es nach einem kurzen Intervall, z. B. hundert Millisekunden oder zehn Sekunden, erneut, wenn sich nichts ändert . Setzen Sie die Schleife auf unbestimmte Zeit fort.

Mit NIO.2 können wir die Änderungserkennung besser ausdrücken. Listing 1 ist ein einfaches Beispiel.

Listing 1. Änderungsbenachrichtigung in NIO.2

import java.nio.file.attribute.*; import java.io.*; import java.util.*; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.List; public class Watcher { public static void main(String[] args) { Path this_dir = Paths.get("."); System.out.println("Now watching the current directory ..."); try { WatchService watcher = this_dir.getFileSystem().newWatchService(); this_dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE); WatchKey watckKey = watcher.take(); List
    
      events = watckKey.pollEvents(); for (WatchEvent event : events) { System.out.println("Someone just created the file '" + event.context().toString() + "'."); } } catch (Exception e) { System.out.println("Error: " + e.toString()); } } }
    

Kompilieren Sie diese Quelle und starten Sie die ausführbare Befehlszeilendatei. Erstellen Sie im selben Verzeichnis eine neue Datei. Sie könnten zum Beispiel touch example1oder sogar copy Watcher.class example1. Die folgende Änderungsbenachrichtigung sollte angezeigt werden:

Someone just create the file 'example1'.

Dieses einfache Beispiel zeigt, wie Sie mit dem Zugriff auf die Sprachfunktionen von NIO in Java beginnen. Außerdem wird die WatcherKlasse von NIO.2 eingeführt, die für die Änderungsbenachrichtigung wesentlich einfacher und benutzerfreundlicher ist als die herkömmliche E / A-Lösung, die auf Abfragen basiert.

Achten Sie auf Tippfehler!

Seien Sie vorsichtig, wenn Sie die Quelle aus diesem Artikel kopieren. Beachten Sie beispielsweise, dass das StandardWatchEventKindsObjekt in Listing 1 als Plural geschrieben ist. Sogar die Java.net-Dokumentation hat das übersehen!

Trinkgeld

Die Benachrichtiger von NIO sind so viel einfacher zu verwenden als die alten Abrufschleifen, dass es verlockend ist, die Anforderungsanalyse zu vernachlässigen. Sie sollten diese Semantik jedoch durchdenken, wenn Sie zum ersten Mal einen Listener verwenden. Zu wissen, wann eine Dateimodifikation endet, ist beispielsweise nützlicher als zu wissen, wann sie beginnt. Diese Art der Analyse erfordert einige Sorgfalt, insbesondere in einem häufigen Fall wie dem FTP-Ablageordner. NIO ist ein leistungsstarkes Paket mit einigen subtilen "Gotchas". es kann einen zufälligen Besucher bestrafen.

2. Selektoren und asynchrone E / A: Selektoren helfen beim Multiplexen

Neulinge bei NIO assoziieren es manchmal mit "nicht blockierendem Input / Output". NIO ist mehr als nicht blockierende E / A, aber der Fehler ist sinnvoll: Grundlegende E / A in Java blockiert - was bedeutet, dass es wartet, bis ein Vorgang abgeschlossen werden kann - während nicht blockierende oder asynchrone E / A. eine der am häufigsten genutzten NIO-Einrichtungen.

NIO's non-blocking I/O is event-based, as demonstrated by the file-system listener in Listing 1. This means that a selector (or callback or listener) is defined for an I/O channel, then processing continues. When an event occurs on the selector -- when a line of input arrives, for instance -- the selector "wakes up" and executes. All of this is achieved within a single thread, which is a significant contrast to typical Java I/O.

Listing 2 demonstrates the use of NIO selectors in a multi-port networking echo-er, a program slightly modified from one created by Greg Travis in 2003 (see Resources). Unix and Unix-like operating systems have long had efficient implementations of selectors, so this sort of networking program is a model of good performance for a Java-coded networking program.

Listing 2. NIO selectors

import java.io.*; import java.net.*; import java.nio.*; import java.nio.channels.*; import java.util.*; public class MultiPortEcho { private int ports[]; private ByteBuffer echoBuffer = ByteBuffer.allocate( 1024 ); public MultiPortEcho( int ports[] ) throws IOException { this.ports = ports; configure_selector(); } private void configure_selector() throws IOException { // Create a new selector Selector selector = Selector.open(); // Open a listener on each port, and register each one // with the selector for (int i=0; i
    

Compile this source, then launch it from the command-line with an invocation such as java MultiPortEcho 8005 8006. Once the MultiPortEchoer is running, start up a simple telnet or other terminal emulator running against ports 8005 and 8006. You will see that the program echoes back characters it receives -- and does it in a single Java thread!

More NIO on JavaWorld

See the following JavaWorld articles for more background on the java.nio package APIs.

  • "Master Merlin's new I/O classes" (Michael T. Nygard, JavaWorld, September 2001)
  • "Use select for high-speed networking" (Greg Travis, JavaWorld, April 2003)

3. Channels: Promise and reality

In NIO, a channel can be any object that reads or writes. Its job is to abstract files and sockets. NIO channels support a consistent collection of methods, so it's possible to program without having special cases depending on whether stdout, a network connection, or some other channel is actually in use. Channels share this characteristic of the streams of Java's basic I/O. Streams provide blocking I/O; channels support asynchronous I/O.

While NIO is often promoted for its performance advantages, it's more precise to say it is highly responsive. In some cases NIO actually performs worse than basic Java I/O. For simple sequential reads and writes of small files, for instance, a straightforward streams implementation might be two or three times faster than the corresponding event-oriented channel-based coding. Also, non-multiplexed channels -- that is, channels in separate threads -- can be much slower than channels that register their selectors in a single thread.

The next time you need to define a programming problem in terms of dimensions pertinent to streams or channels, try asking the following questions:

  • How many I/O objects must you read and write?
  • Is there a natural sequence between different I/O objects or might they all need to happen simultaneously?
  • Do your I/O objects only last for a short interval or are they likely to persist during the lifetime of your process?
  • Is it more natural to do your I/O in a single thread or several distinct ones?
  • Is network traffic likely to look the same as local I/O or do the two have different patterns?

This sort of analysis is good practice for deciding when to use streams or channels. Remember: NIO and NIO.2 don't replace basic I/O; they just supplement it.

4. Memory mapping -- where it counts

The most consistently dramatic performance improvement in the use of NIO involves memory mapping. Memory mapping is an OS-level service that makes segments of a file appear for programming purposes like areas of memory.