Java IO vs NIO

1. Oversikt

Håndtering av input og output er vanlige oppgaver for Java-programmerere. I denne opplæringen vil vi se på opprinnelig java.io (IO) biblioteker og de nyere java.nio (NIO) biblioteker og hvordan de er forskjellige når de kommuniserer på tvers av et nettverk.

2. Nøkkelfunksjoner

La oss starte med å se på de viktigste funksjonene i begge pakkene.

2.1. IO - java.io

De java.io pakken ble introdusert i Java 1.0, med Leser introdusert i Java 1.1. Det gir:

  • InputStream og OutputStream - som gir data en byte om gangen
  • Leser og Forfatter - bekvemmelighetspakker for bekkene
  • blokkeringsmodus - for å vente på en fullstendig melding

2.2. NIO - java.nio

De java.nio pakken ble introdusert i Java 1.4 og oppdatert i Java 1.7 (NIO.2) med forbedrede filoperasjoner og en ASynchronousSocketChannel. Det gir:

  • Bufferå lese biter av data om gangen
  • CharsetDecoder - for å kartlegge råbyte til / fra lesbare tegn
  • Kanal - for å kommunisere med omverdenen
  • Velger - for å aktivere multipleksing på en Valgbar kanal og gir tilgang til alle Kanals som er klare for I / O
  • ikke-blokkerende modus - for å lese hva som er klart

La oss nå se på hvordan vi bruker hver av disse pakkene når vi sender data til en server eller leser svaret.

3. Konfigurer testserveren vår

Her bruker vi WireMock til å simulere en annen server slik at vi kan kjøre testene våre uavhengig.

Vi konfigurerer den til å lytte etter forespørslene våre og sende oss svar akkurat som en ekte webserver ville gjort. Vi bruker også en dynamisk port slik at vi ikke kommer i konflikt med noen tjenester på vår lokale maskin.

La oss legge til Maven-avhengigheten for WireMock med test omfang:

 com.github.tomakehurst wiremock-jre8 2.26.3 test 

La oss i en testklasse definere en JUnit @Regel for å starte WireMock opp på en gratis port. Vi konfigurerer den for å gi oss et HTTP 200-svar når vi ber om en forhåndsdefinert ressurs, med meldingsteksten som litt tekst i JSON-format:

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

Nå som vi har konfigurert mock-serveren vår, er vi klare til å kjøre noen tester.

4. Blokkering av IO - java.io

La oss se på hvordan den opprinnelige blokkerende IO-modellen fungerer ved å lese noen data fra et nettsted. Vi bruker en java.net. stikkontakt for å få tilgang til en av operativsystemets porter.

4.1. Send en forespørsel

I dette eksemplet vil vi lage en GET-forespørsel for å hente ressursene våre. Først, la oss lage en Stikkontakt for å få tilgang til porten at WireMock-serveren vår hører på:

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

For normal HTTP- eller HTTPS-kommunikasjon vil porten være 80 eller 443. I dette tilfellet bruker vi imidlertid wireMockRule.port () for å få tilgang til den dynamiske porten vi satte opp tidligere.

La oss nå åpne en OutputStream på stikkontakten, innpakket i en OutputStreamWriter og gi den til en PrintWriter å skrive meldingen vår. Og la oss sørge for at vi skyller bufferen slik at forespørselen vår blir sendt:

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

La oss åpne en InputStreampå stikkontakten for å få tilgang til svaret, les strømmen med en BufferedReader, og lagre den i en StringBuilder:

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

La oss bruke reader.readLine () for å blokkere, venter på en komplett linje, og legg deretter linjen til butikken vår. Vi fortsetter å lese til vi får en null, som indikerer slutten av strømmen:

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

5. Ikke-blokkerende IO - java.nio

La oss nå se på hvordan nio pakkens ikke-blokkerende IO-modell fungerer med det samme eksemplet.

Denne gangen gjør vi det lage en java.nio.channel.SocketChannel for å få tilgang til porten på serveren vår i stedet for en java.net. stikkontakt, og gi den en InetSocketAddress.

5.1. Send en forespørsel

La oss først åpne vår SocketChannel:

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

Og nå, la oss få en standard UTF-8 Charset å kode og skrive meldingen vår:

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

5.2. Les svaret

Etter at vi har sendt forespørselen, kan vi lese svaret i ikke-blokkerende modus ved hjelp av rå buffere.

Siden vi skal behandle tekst, trenger vi en ByteBuffer for råbyte og a CharBuffer for de konverterte karakterene (hjulpet av a CharsetDecoder):

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

Våre CharBuffer vil ha plass til overs hvis dataene sendes i et tegnsett med flere byte.

Merk at hvis vi trenger spesielt rask ytelse, kan vi lage en MappedByteBuffer i innfødt minne ved hjelp av ByteBuffer.allocateDirect (). Imidlertid, i vårt tilfelle, bruker tildele() fra standard haugen er rask nok.

Når vi arbeider med buffere, må vi vite hvor stor bufferen er (kapasiteten), der vi er i bufferen (gjeldende posisjon), og hvor langt vi kan komme (grensen).

Så la oss lese fra vår SocketChannel, passerer den vår ByteBuffer for å lagre dataene våre. Våre lese fra SocketChannel blir ferdig med vår ByteBuffer‘S gjeldende posisjon satt til neste byte å skrive til (like etter den siste byten som ble skrevet), men med grensen uendret:

socketChannel.read (byteBuffer)

Våre SocketChannel.read () returnerer antall byte som er lest som kan skrives inn i bufferen vår. Dette vil være -1 hvis kontakten ble koblet fra.

Når bufferen ikke har plass igjen fordi vi ikke har behandlet alle dataene ennå, da SocketChannel.read () vil returnere null byte lest men vår buffer.posisjon () vil fortsatt være større enn null.

For å sikre at vi begynner å lese fra riktig sted i bufferen, bruker vi Buffer.flip() for å sette vår ByteBuffer'S nåværende posisjon til null og grensen til den siste byten som ble skrevet av SocketChannel. Vi lagrer deretter bufferinnholdet ved hjelp av vårt storeBufferContents metode, som vi vil se på senere. Til slutt bruker vi buffer.compact () for å komprimere bufferen og sette gjeldende posisjon klar for neste lesing fra SocketChannel.

Siden dataene våre kan komme i deler, la oss pakke inn bufferavlesningskoden vår i en sløyfe med avslutningsbetingelser for å sjekke om kontakten vår fortsatt er koblet til, eller om vi har blitt koblet fra, men fortsatt har data igjen i bufferen:

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

Og la oss ikke glemme å Lukk() kontakten vår (med mindre vi åpnet den i en prøve-med-ressurs-blokk):

socketChannel.close ();

5.3. Lagring av data fra bufferen vår

Svaret fra serveren vil inneholde overskrifter, noe som kan gjøre at datamengden overstiger størrelsen på bufferen vår. Så vi bruker en StringBuilder å bygge vårt komplette budskap når det kommer.

For å lagre budskapet vårt, først dekode råbyte til tegn i vår CharBuffer. Så snur vi pekepinnene slik at vi kan lese karakterdataene våre, og legge dem til våre utvidbare StringBuilder. Til slutt vil vi fjerne CharBuffer klar for neste skrive / lese syklus.

Så nå, la oss implementere vår komplette storeBufferContents () metode som passerer i bufferne våre, CharsetDecoder, og StringBuilder:

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

6. Konklusjon

I denne artikkelen har vi sett hvordan opprinnelig java.io modellblokker, venter på en forespørsel og bruker Strøms for å manipulere dataene den mottar.

I motsetning, de java.nio biblioteker tillater ikke-blokkerende kommunikasjon ved hjelp av Buffers og Kanals og kan gi direkte minnetilgang for raskere ytelse. Imidlertid, med denne hastigheten kommer den ekstra kompleksiteten ved håndtering av buffere.

Som vanlig er koden for denne artikkelen tilgjengelig på GitHub.


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