Java Nio: Effektiv input/output

Javas nye input/output-klasser, Nio, er den største forskel imellem den seneste version 1.4 og forgængeren. De nye i/o-biblioteker giver væsentlig bedre ydelse og gør det muligt at skabe kode, hvor programudførslen ikke hænger, mens data-bufferne fyldes op. I denne artikel ser vi på anvendelsen af Nio i forbindelse med filer, og det er slet ikke så svært at udnytte de nye biblioteker endda.

Hvorfor Nio

Blandt de største nyskabelser i Java 1.4, som kom på gaden for et år siden, er input/output-klasserne Nio, som på trods af at de er et år gamle stadig betyder New input-output.

Det nye i bibliotekerne består i to forhold: En implementering af læsning og skrivning, som ligger tættere på den måde, styresystemer typisk implementerer i/o på, samt muligheden for at læse og skrive, uden at programafviklingen stopper, indtil en buffer er fyldt op.

Det har ikke så stor betydning ved læsning og skrivning til disk, hvor overførselshastigheden er høj, men ved dataoverførsel via netværk er programmets afviklingshastighed som regel mange gange større end netværkets hastighed.

Her kan man opleve, at en programdel blot står og venter på nye data i bufferen, mens programmet egentligt kunne foretage sig noget mere fornuftigt.

De fordele, som Nio har, kommer selvfølgelig med en pris. Nio-klasserne er mere system-nære end de gamle input/output-klasser, og derfor er der også flere detaljer at holde styr på.

Hit med gaffeltrucken
Nio til brug med filer er en del nemmere at gå til end Nio til netværksbrug, så det er det, vi kigger nærmere på i denne artikel.

Nios grundlæggende virkemåde er heldigvis simpel. De to centrale begreber er Channels og Buffers. Channels, kanaler, repræsenterer data. Humlen er, at man ikke kan tilgå kanalerne direkte: man skal i stedet benytte en buffer. Bufferen benyttes at skrive til eller læse fra kanalen.

Man kan sammenligne med et lager og en gaffeltruck. Varerne på lagret repræsenterer data, og gaffeltrucken er bufferen.

Programmøren må pænt vente ved porten til lagret, og benytte instruktioner til gaffeltrucken som en måde at hente og gemme data til og fra lageret.

Den buffer, som snakker direkte med kanalerne, er objektet java.nio.ByteBuffer. Det er ByteBufferen, som kommunikerer med kanalen, ved at transportere data frem og tilbage i mellem kanalen og programmørens kode.

Kanalerne, som befinder sig i pakken java.nio.channels, tilgås ved hjælp af nye metoder i de gamle input- og outputstream-biblioteker.

Læs og skriv med buffere

Lad os se på et gennemkommenteret eksempel på læsning og skrivning fra en fil ved hjælp af Nio.

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class NioEksempel1 {
  private static final int BUFFER_SIZE = 1024;

  public static void main(String[] args) throws Exception {
    // Nio skriver:
    // En channel hentes fra en FileOutputStream-instans:
    FileChannel filechannel =
      new FileOutputStream("test.txt").getChannel();

    // Vi tager en streng, og omskaber den til et byte-array
    byte[] stringAsByte = "Vi skriver med Nio. ".getBytes();

    // Vi skaber en ByteBuffer-instans, og fylder
    // byte-arrayet ind i bufferen    
    ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);    
    buffer.put(stringAsByte);
          
    // Efter at bufferen er blevet fyldt med vores bytes,
    // skal bufferen "klargøres" til skriving med metoden flip
    buffer.flip();
    
    // Derefter skrives bufferen til kanalen
    filechannel.write(buffer);
    filechannel.close();
    
    
    // Nio læser:
    // Her får vi filechannel-instansen fra en
    // FileInputStream-instans
    filechannel =
      new FileInputStream("test.txt").getChannel();
    filechannel.read(buffer);

    // Efter at bufferen er blevet fyldt op af
    // read-metoden i forrige linie, skal bufferen "klargøres"
    // til læsning med metoden flip
    buffer.flip();

    // Her udskriver vi de enkelte bytes i bufferen
    // ved at caste dem om til char's
    while (buffer.hasRemaining())
      System.out.print((char) buffer.get());
  }
}

Eksemplet er forhåbentlig ikke så slemt at se på. Det eneste, som kan virke lidt mystisk, er ByteBufferens metode flip.

Flip sætter ByteBufferets øvre grænse (limit) til den aktuelle værdi af den interne pegepind (position), og dernæst sættes pegepinden til nul. Næste gang, ByteBufferen anvendes, læses der altså fra starten, og til og med den sidste byte, som blev puttet ind i bufferen.

Metoden flip skal anvendes efter at put-metoden eller læsning af kanalen er anvendt, og før get-metoden eller skrivning til kanalen anvendes.

ByteBuffer har, udover flip-metoden, en række andre metoder til at manipulere de interne pegepinde, som ByteBufferen anvender.

I stedet for at instantiere ByteByfferen med den statiske metode allocate(BUFFER_SIZE), kan man også benytte den statiske metode wrap(byte[] bytearray), som tager et bytearray som argument, og derefter benytter dette bytearray som det underliggende lager for de enkelte bytes i ByteBufferen.

En hel fil

Ovenstående eksempel viser kun, hvorledes bufferen skrives til og læses fra kanalen. Hvis man skal læse en hel fil eller overføre data fra en strøm til en anden strøm, skal der lidt flere boller på suppen.

Læsningen af en hel fil er nemt nok. FileChannel-metoden read kaldes successivt, indtil metoden returnerer -1, som markerer slutning på strømmen.

Det kan se sådan ud:

public class NioEksempel2 {
  private static final int BUFFER_SIZE = 1024;

  public static void main(String[] args) throws Exception {
    FileChannel filechannel =
      new FileInputStream("C:\\data.txt").getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

    while (filechannel.read(buffer) != -1) {
      buffer.flip();
      while (buffer.hasRemaining())
        System.out.print((char) buffer.get());
    }
  }
}

Hvis man skal læse fra en inputstrøm og skrive til outputstrøm, kan man selvfølgelig bare læse en stak bytes i en ByteBuffer fra den ene strøm, og så skrive ByteBufferen til output-strømmen, men Nio har en indbygget metode til at overføre data direkte fra en kanal til en anden kanal.

Det kan se sådan ud:

public class NioEksempel3 {
  private static final int BUFFER_SIZE = 1024;

  public static void main(String[] args) throws Exception {
    FileChannel inFilechannel =
      new FileInputStream("data.txt").getChannel();
    FileChannel outFilechannel =
      new FileOutputStream("kopi.txt").getChannel();
    
    outFilechannel.transferFrom(
      inFilechannel,
      0,
      inFilechannel.size());
  }
}

Her overføres data imellem to fil-kanaler ved hjælp af FileChannel-metoden transferFrom, men operationen kan udføres mellem alle kanaler som er læsbare, til alle kanaler som er skrivbare.

Som argumenter tager transferFrom-metoden først den kanal, som der skal læses fra, og derefter den start- og slut-position, som den skal læse, målt i bytes. I eksemplet ovenfor læses hele filen.

Kodning af data

Kodning af data
I disse eksempler benytter vi tekstfiler, og den går egentlig ikke. Man kan nemlig ikke regne med, at konverteringen imellem char, String-metoden getBytes, og byte går problemfrit. Tekst er jo indkodet, som Unicode, ASCII, eller noget helt andet.

De gamle input/output-klasser til formålet - InputStreamReader og OutputStreamWriter - kan det med indkodning, og Nio har tilsvarende klassen CharBuffer, som er en søster-klasse til ByteBuffer. Man skal dog selv holde styr på indkodningen. Nio har pakken java.nio.charset til at oversætte imellem bytes og bestemte tegntabeller.

Tegn er ikke det eneste, som skal kodes på en eller anden facon. Der er buffere til andre primitive typer, som int, double, og så videre.

Views
Men det er kun ByteBuffer, som kan benyttes til at læse eller skrive fra en kanal med, og derfor skal CharBuffer, IntBuffer og de andre type-specifikke buffere svejses sammen med et ByteBuffer. Det gøres ved at benytte ByteBuffer-metoderne asCharBuffer, asIntBuffer med videre, som returnerer en CharBuffer- eller IntBuffer-instans, eller hvad det nu er for en type, det drejer sig om.

Denne CharBuffer benyttes så som et såkaldt view, hvilket vil sige, at operationer på instansen forplanter sig til det underliggende ByteBuffer, som holder styr på de underliggende bytes, der repræsenterer data. Man kan så skrive int-variabler til IntBufferen, og den instans omkoder den bagvedliggende repræsentation af int-variablen til bytes, som placeres i ByteBufferen, som så kan skrives til kanalen.

Endian
Endnu en slem ting, som man ikke altid kan ignorere, er det underliggende styresystems måde at gemme data på. Nogle systemer, "big endian", gemmer data med den mest signifikante byte på den laveste tilgængelige hukommelsesadresse. Andre systemer benytter den højeste hukommelsesadresse. Det sidste kaldes "little endian". Ved transmission over netværk benyttes altid big endian.

Man kan sætte et ByteBuffer til at være big endian eller little endian ved hjælp af hjælpe-klassen java.nio.ByteOrder, og man kan også benytte en statisk metode i objektet, ByteOrder.nativeOrder(), som fortæller hvilken orden, det aktuelle system anvender.

Læs mere
Et godt sted at læse mere om anvendelsen af Nio til filer er Java-guruen Bruce Eckels bog, Thinking in Java, 3. udgave, nærmere bestemt kapitel 12. Den kan, ganske generøst, downloades gratis fra Eckels hjemmeside i et læsevenligt HTML-format.




Brancheguiden
Brancheguide logo
Opdateres dagligt:
Den største og
mest komplette
oversigt
over danske
it-virksomheder
Hvad kan de? Hvor store er de? Hvor bor de?
Ed A/S
Salg af hard- og software.

Nøgletal og mere info om virksomheden
Skal din virksomhed med i Guiden? Klik her

Kommende events
Send dine legacysystemer på pension og invitér standardløsninger indenfor

Legacysystemer er rygraden i mange organisationers it-infrastruktur, men før eller siden er det tid til at sige farvel og skifte til en eller flere standardløsninger. Vi udforsker scenarier og muligheder, der gør det muligt at rykke videre. Hvad er businesscasen? Hvilke krav stiller skiftet til din forretning og jeres processer? Hvordan

08. oktober 2024 | Læs mere


Dynamics 365 & Business Central - AI og branchemoduler

Udforsk, hvordan du kommer godt i gang med Business Central, får hjælp til at tilpasse platformen til dine behov og får mest ud af din ERP-løsning med begrænsede ressourcer.

23. oktober 2024 | Læs mere


Årets CISO 2024

Vær med når Computerworld, Dansk Erhverv og Rådet for Digital Sikkerhed tager temperaturen på trusselslandskabet lige nu, og giver dig overblikket over de nyeste trusler, de mest aktuelle tendenser og de bedste løsninger og værktøjer til at sikre effektiv drift og høj compliance.

24. oktober 2024 | Læs mere