Java IO vs NIO

1. Áttekintés

A bemenet és a kimenet kezelése a Java programozók számára általános feladat. Ebben az oktatóanyagban megnézzük a eredeti java.io (IO) könyvtárak és az újabbak java.nio (NIO) könyvtárak és hogyan különböznek egymástól a hálózaton keresztüli kommunikáció során.

2. Főbb jellemzők

Kezdjük azzal, hogy megvizsgáljuk mindkét csomag főbb jellemzőit.

2.1. IO - java.io

A java.io csomagot a Java 1.0-ban vezették be, val vel Olvasó bevezetett Java 1.1. Ez biztosítja:

  • InputStream és OutputStream - amelyek egyenként bájtot szolgáltatnak
  • Olvasó és Író - kényelmi csomagolók a patakokhoz
  • blokkolási mód - a teljes üzenet megvárására

2.2. NIO - java.nio

A java.nio csomagot a Java 1.4-ben vezették be és frissítve Java 1.7-ben (NIO.2) továbbfejlesztett fájlműveletekkel és egy ASynchronousSocketChannel. Ez biztosítja:

  • Pufferhogy egyszerre olvashasson adatrészleteket
  • CharsetDecoder - nyers bájtok olvasható karakterekhez / leképezéséhez
  • Csatorna - a külvilággal való kommunikációért
  • Választó - a multiplexelés engedélyezése a VálaszthatóChannel és hozzáférést biztosítanak bármelyikhez Csatornaamelyek készen állnak az I / O-ra
  • nem blokkoló mód - a készenlét elolvasása

Most nézzük meg, hogyan használjuk ezeket a csomagokat, amikor adatokat küldünk egy szervernek, vagy elolvassuk a válaszát.

3. Konfigurálja a tesztkiszolgálónkat

Itt a WireMock segítségével fogjuk szimulálni egy másik szervert, hogy tesztjeinket függetlenül futtathassuk.

Beállítjuk úgy, hogy meghallgassa a kéréseinket és válaszokat küldjön nekünk, akárcsak egy igazi webszerver. Dinamikus portot is használunk, hogy ne ütközzünk a helyi gépünk egyetlen szolgáltatásával sem.

Tegyük hozzá a WireMock Maven-függőségét teszt hatókör:

 com.github.tomakehurst wiremock-jre8 2.26.3 teszt 

Egy teszt osztályban definiáljunk egy JUnit-et @Szabály hogy elindítsa a WireMock-ot egy szabad porton. Ezután úgy konfiguráljuk, hogy HTTP 200 választ küldjön nekünk, amikor egy előre definiált erőforrást kérünk, az üzenet törzsével pedig JSON formátumú szövegként:

@Rule public WireMockRule wireMockRule = new WireMockRule (wireMockConfig (). DynamicPort ()); privát karakterlánc REQUESTED_RESOURCE = "/test.json"; @A nyilvános void beállítása előtt () {stubFor (get (urlEqualTo (REQUESTED_RESOURCE)) .willReturn (aResponse () .withStatus (200) .withBody ("{\" response \ ": \" Működött! \ "}")) ); }

Most, hogy beállítottuk az álkiszolgálónkat, készen állunk néhány teszt futtatására.

4. Az IO blokkolása - java.io

Nézzük meg, hogyan működik az eredeti blokkoló IO modell, ha elolvasunk néhány adatot egy webhelyről. Használjuk a java.net.Socket hogy hozzáférjen az operációs rendszer egyik portjához.

4.1. Küldjön egy kérést

Ebben a példában létrehozunk egy GET kérést az erőforrásaink lekérésére. Először nézzük hozzon létre egy Foglalat hogy elérje a kikötőt hogy WireMock szerverünk hallgatja:

Socket socket = új Socket ("localhost", wireMockRule.port ())

A normál HTTP vagy HTTPS kommunikációhoz a port 80 vagy 443 lenne. Ebben az esetben azonban ezt használjuk wireMockRule.port () hogy elérjük a korábban beállított dinamikus portot.

Most nézzük nyit egy OutputStream az aljzaton, csomagolva egy OutputStreamWriter és adja át a PrintWriter hogy megírjuk az üzenetünket. És ügyeljünk arra, hogy öblítsük le a puffert úgy, hogy kérésünket elküldjük:

OutputStream clientOutput = socket.getOutputStream (); PrintWriter író = new PrintWriter (új OutputStreamWriter (clientOutput)); író.nyomtatás ("GET" + TESZT_JSON + "HTTP / 1.0 \ r \ n \ r \ n"); író.öblítés ();

4.2. Várja meg a választ

Nézzük nyit egy InputStreamaz aljzaton a válasz eléréséhez olvassa el az adatfolyamot a-val BufferedReader, és tárolja a StringBuilder:

InputStream serverInput = socket.getInputStream (); BufferedReader olvasó = new BufferedReader (új InputStreamReader (serverInput)); StringBuilder ourStore = új StringBuilder ();

Használjuk reader.readLine () blokkolni, várva a teljes sort, majd csatolja a sort üzletünkhöz. Addig fogjuk olvasni, amíg meg nem kapjuk a nulla, amely a folyam végét jelzi:

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

5. Nem blokkoló IO - java.nio

Most nézzük meg, hogyan nio a csomag nem blokkoló IO modellje ugyanazzal a példával működik.

Ezúttal megtesszük hozzon létre egy java.nio.channel.SocketChannel hogy elérje a kikötőt a szerverünkön a helyett java.net.Socket, és adja át InetSocketAddress.

5.1. Küldjön egy kérést

Először nyissuk meg SocketChannel:

InetSocketAddress address = new InetSocketAddress ("localhost", wireMockRule.port ()); SocketChannel socketChannel = SocketChannel.open (cím);

Most pedig szerezzünk be egy szabványos UTF-8-at Charset az üzenetünk kódolásához és megírásához:

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

5.2. Olvassa el a választ

Miután elküldtük a kérést, olvashatjuk a választ nem blokkoló módban, nyers pufferek segítségével.

Mivel szöveget fogunk feldolgozni, szükségünk lesz egy ByteBuffer a nyers bájtokra és a CharBuffer az átalakított karakterekhez (a CharsetDecoder):

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

A mi CharBuffer akkor marad helye, ha az adatokat több bájtos karakterkészletben küldi el.

Vegye figyelembe, hogy ha különösen gyors teljesítményre van szükségünk, létrehozhatunk egy MappedByteBuffer natív memóriában használ ByteBuffer.allocateDirect (). Esetünkben azonban a kioszt() a standard kupacból elég gyors.

A pufferekkel való foglalkozáskor tudnunk kell, hogy mekkora a puffer (a kapacitás), ahol a pufferben vagyunk (az aktuális pozíció), és meddig mehetünk el (a határ).

Tehát, nézzük olvassa el a mi SocketChannel, átadva a mi ByteBuffer hogy tároljuk adatainkat. A mi olvas tól SocketChannel befejezi a mi ByteBuffer’S az aktuális pozíció beállítása a következő bájtra, ahová írni akar (közvetlenül az utolsó írt bájt után), de korlátja változatlan:

socketChannel.read (byteBuffer)

A mi SocketChannel.read () visszaadja az olvasott bájtok számát amit be lehet írni a pufferünkbe. Ez -1 lesz, ha a foglalatot leválasztják.

Amikor a pufferünknek nincs helye, mert akkor még nem dolgoztuk fel az összes adatot SocketChannel.read () nulla olvasott bájtot ad vissza, de a mi buffer.position () továbbra is nagyobb lesz, mint nulla.

Annak biztosítására, hogy a puffer megfelelő helyéről kezdjünk olvasni, használjuk Buffer.flip() beállítani a mi ByteBufferAktuális pozíciója nulla, és annak határa az utolsó bájthoz, amelyet a SocketChannel. Ezután a puffer tartalmát a mi storeBufferContents módszer, amelyet később megvizsgálunk. Végül felhasználjuk buffer.compact () tömöríteni a puffert, és készen állítani az aktuális helyzetet a következő olvasáshoz SocketChannel.

Mivel adataink részenként érkezhetnek, tekerjük a pufferolvasó kódot egy végződési feltételekkel rendelkező körbe annak ellenőrzésére, hogy a foglalatunk továbbra is csatlakozik-e, vagy nem csatlakoztak-e, de még mindig vannak adatok a pufferben:

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

És ne felejtsük el Bezárás() a foglalatunk (hacsak nem egy try-with-resources blokkban nyitottuk meg):

socketChannel.close ();

5.3. Adatok tárolása pufferünkből

A szerver válasza fejléceket tartalmaz, amelyek miatt az adatmennyiség meghaladja a puffer méretét. Tehát használjuk a StringBuilder hogy elkészítsük teljes üzenetünket, amint megérkezik.

Az üzenetünk tárolásához először dekódolja a nyers bájtokat karakterekre a mi CharBuffer. Ezután megfordítjuk a mutatókat, hogy elolvashassuk a karakter adatainkat, és hozzáfűzzük a kibontható elemekhez StringBuilder. Végül megtisztítjuk a CharBuffer készen áll a következő írási / olvasási ciklusra.

Tehát most hajtsuk végre a teljes storeBufferContents () puffereinkben átadott módszer, CharsetDecoder, és StringBuilder:

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

6. Következtetés

Ebben a cikkben láttuk, hogy a eredeti java.io modellblokkok, várja a kérést és felhasználja Folyams manipulálni a kapott adatokat.

Ellentétben, a java.nio a könyvtárak lehetővé teszik a nem blokkoló kommunikációt felhasználásával Puffers és Csatornas közvetlen hozzáférést biztosít a memóriához a gyorsabb teljesítmény érdekében. Ezzel a sebességgel azonban a pufferek kezelése további összetettséggel jár.

Szokás szerint a cikk kódja elérhető a GitHubon.


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