Java IO vs NIO

1. Oversigt

Håndtering af input og output er almindelige opgaver for Java-programmører. I denne vejledning ser vi på original java.io (IO) biblioteker og de nyere java.nio (NIO) biblioteker og hvordan de adskiller sig, når de kommunikerer på tværs af et netværk.

2. Nøglefunktioner

Lad os starte med at se på nøglefunktionerne i begge pakker.

2.1. IO - java.io

Det java.io pakken blev introduceret i Java 1.0, med Læser introduceret i Java 1.1. Det giver:

  • InputStream og OutputStream - der leverer data en byte ad gangen
  • Læser og Forfatter - bekvemmelighedspakker til vandløbene
  • blokeringstilstand - for at vente på en komplet besked

2.2. NIO - java.nio

Det java.nio pakken blev introduceret i Java 1.4 og opdateret i Java 1.7 (NIO.2) med forbedrede filhandlinger og en ASynchronousSocketChannel. Det giver:

  • Bufferat læse klumper af data ad gangen
  • CharsetDecoder - til kortlægning af rå bytes til / fra læsbare tegn
  • Kanal - til kommunikation med omverdenen
  • Vælger - for at muliggøre multiplexing på en Valgbar kanal og give adgang til enhver Kanals, der er klar til I / O
  • ikke-blokerende tilstand - for at læse hvad der er klar

Lad os nu se på, hvordan vi bruger hver af disse pakker, når vi sender data til en server eller læser dens svar.

3. Konfigurer vores testserver

Her bruger vi WireMock til at simulere en anden server, så vi kan køre vores tests uafhængigt.

Vi konfigurerer det til at lytte efter vores anmodninger og sende os svar ligesom en ægte webserver ville. Vi bruger også en dynamisk port, så vi ikke kommer i konflikt med nogen tjenester på vores lokale maskine.

Lad os tilføje Maven-afhængighed for WireMock med prøve rækkevidde:

 com.github.tomakehurst wiremock-jre8 2.26.3 test 

Lad os i en testklasse definere en JUnit @Herske for at starte WireMock op på en fri port. Vi konfigurerer det derefter til at returnere et HTTP 200-svar, når vi beder om en foruddefineret ressource med meddelelsesteksten som en tekst i JSON-format:

@Rule public WireMockRule wireMockRule = ny WireMockRule (wireMockConfig (). DynamicPort ()); privat streng REQUESTED_RESOURCE = "/ test.json"; @Før offentlig tomrumsopsætning () {stubFor (get (urlEqualTo (REQUESTED_RESOURCE)) .willReturn (aResponse () .withStatus (200) .withBody ("{\" response \ ": \" Det fungerede! \ "}")) ); }

Nu hvor vores mock-server er konfigureret, er vi klar til at køre nogle tests.

4. Blokering af IO - java.io

Lad os se på, hvordan den oprindelige blokerende IO-model fungerer ved at læse nogle data fra et websted. Vi bruger en java.net.Socket for at få adgang til en af ​​operativsystemets porte.

4.1. Send en anmodning

I dette eksempel opretter vi en GET-anmodning for at hente vores ressourcer. Lad os først lave en Stikkontakt for at få adgang til porten at vores WireMock-server lytter til:

Stikkontakt = ny stikkontakt ("localhost", wireMockRule.port ())

For normal HTTP- eller HTTPS-kommunikation ville porten være 80 eller 443. I dette tilfælde bruger vi dog wireMockRule.port () for at få adgang til den dynamiske port, vi oprettede tidligere.

Lad os nu åbne en OutputStream på stikkontakten, pakket ind i en OutputStreamWriter og videregive det til en PrintWriter at skrive vores besked. Og lad os sørge for at skylle bufferen, så vores anmodning sendes:

OutputStream clientOutput = socket.getOutputStream (); PrintWriter-forfatter = ny PrintWriter (ny OutputStreamWriter (clientOutput)); writer.print ("GET" + TEST_JSON + "HTTP / 1.0 \ r \ n \ r \ n"); writer.flush ();

4.2. Vent på svaret

Lad os åbne en InputStreampå stikkontakten for at få adgang til svaret skal du læse strømmen med en BufferedReader, og opbevar den i en StringBuilder:

InputStream serverInput = socket.getInputStream (); BufferedReader reader = ny BufferedReader (ny InputStreamReader (serverInput)); StringBuilder ourStore = ny StringBuilder ();

Lad os bruge reader.readLine () at blokere, venter på en komplet linje, og tilføj derefter linjen til vores butik. Vi fortsætter med at læse, indtil vi får en nul, som angiver slutningen af ​​strømmen:

for (String line; (line = reader.readLine ())! = null;) {ourStore.append (line); ourStore.append (System.lineSeparator ()); }

5. Ikke-blokerende IO - java.nio

Lad os nu se på, hvordan nio pakkens ikke-blokerende IO-model fungerer med det samme eksempel.

Denne gang vil vi lave en java.nio.kanal.SocketChannel for at få adgang til porten på vores server i stedet for en java.net.Socket, og giv det en InetSocketAddress.

5.1. Send en anmodning

Lad os først åbne vores SocketChannel:

InetSocketAddress-adresse = ny InetSocketAddress ("localhost", wireMockRule.port ()); SocketChannel socketChannel = SocketChannel.open (adresse);

Og nu, lad os få en standard UTF-8 Charset at kode og skrive vores besked:

Charset charset = StandardCharsets.UTF_8; socket.write (charset.encode (CharBuffer.wrap ("GET" + REQUESTED_RESOURCE + "HTTP / 1.0 \ r \ n \ r \ n")));

5.2. Læs svaret

Når vi har sendt anmodningen, kan vi læse svaret i ikke-blokerende tilstand ved hjælp af rå buffere.

Da vi behandler tekst, har vi brug for en ByteBuffer for de rå byte og en CharBuffer for de konverterede tegn (hjulpet af a CharsetDecoder):

ByteBuffer byteBuffer = ByteBuffer.allocate (8192); CharsetDecoder charsetDecoder = charset.newDecoder (); CharBuffer charBuffer = CharBuffer.allocate (8192);

Vores CharBuffer har plads tilbage, hvis dataene sendes i et tegnsæt med flere byte.

Bemærk, at hvis vi har brug for særlig hurtig ydeevne, kan vi oprette en MappedByteBuffer i indbygget hukommelse ved hjælp af ByteBuffer.allocateDirect (). I vores tilfælde bruger vi dog tildel () fra standard bunken er hurtig nok.

Når vi beskæftiger os med buffere, skal vi vide, hvor stor bufferen er (kapaciteten), hvor vi er i bufferen (den aktuelle position), og hvor langt vi kan gå (grænsen).

Så lad os læse fra vores SocketChannel, passerer det vores ByteBuffer for at gemme vores data. Vores Læs fra SocketChannel slutter med vores ByteBuffer'S nuværende position indstillet til den næste byte, du vil skrive til (lige efter den sidste byte, der blev skrevet), men med dens grænse uændret:

socketChannel.read (byteBuffer)

Vores SocketChannel.read () returnerer antallet af læst byte der kunne skrives i vores buffer. Dette vil være -1, hvis stikket blev afbrudt.

Når vores buffer ikke har plads tilbage, fordi vi ikke har behandlet alle dens data endnu, så SocketChannel.read () returnerer nul læst byte, men vores buffer.position () vil stadig være større end nul.

For at sikre, at vi begynder at læse fra det rigtige sted i bufferen, bruger vi Buffer.flip() for at indstille vores ByteBuffer'S nuværende position til nul og dens grænse til den sidste byte, der blev skrevet af SocketChannel. Vi gemmer derefter bufferindholdet ved hjælp af vores storeBufferContents metode, som vi vil se på senere. Endelig bruger vi buffer.compact () at komprimere bufferen og indstille den aktuelle position klar til vores næste læsning fra SocketChannel.

Da vores data muligvis kommer i dele, lad os indpakke vores bufferlæsningskode i en sløjfe med opsigelsesbetingelser for at kontrollere, om vores stikkontakt stadig er tilsluttet, eller om vi er blevet afbrudt, men stadig har data tilbage i vores buffer:

mens (socketChannel.read (byteBuffer)! = -1 || byteBuffer.position ()> 0) {byteBuffer.flip (); storeBufferContents (byteBuffer, charBuffer, charsetDecoder, ourStore); byteBuffer.compact (); }

Og lad os ikke glemme at tæt() vores stikkontakt (medmindre vi åbnede den i en prøve-med-ressource-blok):

socketChannel.close ();

5.3. Lagring af data fra vores buffer

Svaret fra serveren vil indeholde overskrifter, som kan få datamængden til at overstige størrelsen på vores buffer. Så vi bruger en StringBuilder at opbygge vores komplette budskab, når det ankommer.

For at gemme vores budskab skal vi først afkode de rå byte til tegn i vores CharBuffer. Derefter vender vi markørerne, så vi kan læse vores karakterdata og føje dem til vores udvidelige StringBuilder. Endelig rydder vi CharBuffer klar til næste skrive / læse cyklus.

Så nu, lad os implementere vores komplette storeBufferContents () metode, der passerer i vores buffere, CharsetDecoderog StringBuilder:

void storeBufferContents (ByteBuffer byteBuffer, CharBuffer charBuffer, CharsetDecoder charsetDecoder, StringBuilder ourStore) {charsetDecoder.decode (byteBuffer, charBuffer, sand); charBuffer.flip (); ourStore.append (charBuffer); charBuffer.clear (); }

6. Konklusion

I denne artikel har vi set, hvordan original java.io modelblokke, venter på en anmodning og bruger Strøms til at manipulere de data, den modtager.

I modsætning, det java.nio biblioteker muliggør ikke-blokerende kommunikation ved brug af Buffers og Kanals og kan give direkte hukommelsesadgang for hurtigere ydeevne. Men med denne hastighed kommer den ekstra kompleksitet ved håndtering af buffere.

Som sædvanlig er koden til denne artikel tilgængelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found