Daha önce, sınıfları çoğunlukla Java'daki akışlarla çalışmak için olan IO API'sini (Giriş/Çıkış Uygulama Programlama Arayüzü) ve java.io paketini tanımıştık . Buradaki anahtar, akış kavramıdır .

Bugün NIO API'sini (Yeni Girdi/Çıktı) ele almaya başlayacağız .

G/Ç'ye yönelik iki yaklaşım arasındaki temel fark, G/Ç API'sinin akış yönelimli, NIO API'sinin ise arabellek yönelimli olmasıdır. Dolayısıyla anlaşılması gereken ana kavramlar arabellekler ve kanallardır .

Tampon nedir ve kanal nedir?

Kanal , verilerin içeri ve dışarı hareket ettiği mantıksal bir portaldır, arabellek ise iletilen bu verilerin kaynağı veya hedefidir . Çıktı sırasında, göndermek istediğiniz veriler bir arabelleğe alınır ve arabellek, verileri kanala iletir. Giriş sırasında, kanaldan gelen veriler arabelleğe alınır.

Başka bir deyişle:

  • arabellek , basitçe içine bilgi yazabileceğimiz ve bilgileri okuyabileceğimiz bir bellek bloğudur,
  • kanal, dosyalar veya yuvalar gibi G/Ç aygıtlarına erişim sağlayan bir ağ geçididir.

Kanallar, java.io paketindeki akışlara çok benzer. Herhangi bir yere giden (veya herhangi bir yerden gelen) tüm veriler bir kanal nesnesinden geçmelidir. Genel olarak, NIO sistemini kullanmak için, bir G/Ç varlığına giden bir kanala ve verileri depolamak için bir ara belleğe sahip olursunuz. Ardından, gerektiğinde verileri girerek veya çıkararak arabellekle çalışırsınız.

Bir tamponda ileri ve geri hareket edebilirsiniz, yani tamponda "yürüyebilirsiniz", bu akışlarda yapamayacağınız bir şeydir. Bu, verileri işlerken daha fazla esneklik sağlar. Standart kitaplıkta, arabellekler soyut Buffer sınıfı ve birkaç alt sınıfı tarafından temsil edilir:

  • ByteBuffer
  • CharBuffer
  • Kısa Tampon
  • IntBuffer
  • FloatBuffer
  • DoubleBuffer
  • Uzun Tampon

Alt sınıflar arasındaki temel fark, depoladıkları veri tipidir - bytes , ints , longs ve diğer ilkel veri tipleri.

Tampon özellikleri

Bir tamponun dört ana özelliği vardır. Bunlar kapasite, limit, konum ve işarettir.

Kapasite , arabellekte depolanabilecek maksimum veri/bayt miktarıdır. Bir tamponun kapasitesi değiştirilemez . Bir arabellek dolduğunda, daha fazla yazmadan önce temizlenmesi gerekir.

Yazma modunda, ara belleğin sınırı kapasitesiyle aynıdır ve ara belleğe yazılabilecek maksimum veri miktarını gösterir. Okuma modunda, bir arabelleğin sınırı, arabellekten okunabilen maksimum veri miktarını ifade eder.

Konum , imlecin arabellekteki geçerli konumunu gösterir. Başlangıçta, arabellek oluşturulduğunda 0 olarak ayarlanır. Diğer bir deyişle okunacak veya yazılacak bir sonraki elemanın indeksidir.

İşaret , bir imleç konumunu kaydetmek için kullanılır. Bir tamponu manipüle ederken, imlecin konumu sürekli olarak değişir, ancak onu her zaman önceden işaretlenmiş konuma geri getirebiliriz.

Bir tamponla çalışma yöntemleri

Şimdi, kanallara ve kanallardan veri okumak ve yazmak için arabelleğimizle (bellek bloğu) çalışmamıza izin veren ana yöntem setine bakalım.

  1. allocate(int kapasite) — bu yöntem, belirtilen kapasiteye sahip yeni bir arabellek ayırmak için kullanılır. Aktarılan kapasite negatif bir tamsayıysa, allocate () yöntemi bir IllegalArgumentException atar .

  2. kapasite(), geçerli arabelleğin kapasitesini döndürür .

  3. position(), geçerli imleç konumunu döndürür. Okuma ve yazma işlemleri imleci tamponun sonuna taşır. Dönen değer her zaman sınırdan küçük veya sınıra eşittir.

  4. limit() geçerli tamponun limitini döndürür.

  5. mark(), mevcut imleç konumunu işaretlemek (kaydetmek) için kullanılır.

  6. reset(), imleci önceden işaretlenmiş (kaydedilmiş) konuma döndürür.

  7. clear() konumu sıfıra ayarlar ve limiti kapasiteye ayarlar. Bu yöntem arabellekteki verileri temizlemez . Yalnızca konumu, limiti ve işareti yeniden başlatır.

  8. flip(), tamponu yazma modundan okuma moduna geçirir. Ayrıca, mevcut konumun sınırını ayarlar ve ardından konumu tekrar sıfıra getirir.

  9. read() — Kanalın read yöntemi, kanaldan arabelleğe veri yazmak için kullanılırken, tamponun put() yöntemi, arabelleğe veri yazmak için kullanılır.

  10. write() — Kanalın yazma yöntemi, arabellekten kanala veri yazmak için kullanılırken, arabelleğe ait get() yöntemi, arabellekten veri okumak için kullanılır.

  11. rewind() arabelleği geri sarar. Bu yöntem, tamponu yeniden okumanız gerektiğinde kullanılır — konumu sıfıra ayarlar ve limiti değiştirmez.

Ve şimdi kanallar hakkında birkaç söz.

Java NIO'daki en önemli kanal uygulamaları aşağıdaki sınıflardır:

  1. FileChannel — Bir dosyadan/dosyaya veri okumak ve yazmak için bir kanal.

  2. DatagramChannel — Bu sınıf, UDP (Kullanıcı Datagram Protokolü) aracılığıyla ağ üzerinden veri okur ve yazar.

  3. SocketChannel — TCP (İletim Kontrol Protokolü) aracılığıyla ağ üzerinden veri okumak ve yazmak için kullanılan bir kanal.

  4. ServerSocketChannel — Tıpkı bir web sunucusunun yaptığı gibi, TCP bağlantıları üzerinden veri okumak ve yazmak için kullanılan bir kanal. Gelen her bağlantı için bir SocketChannel oluşturulur.

Pratik

Birkaç satır kod yazmanın zamanı geldi. Önce dosyayı okuyup içeriğini konsolda gösterelim ve ardından dosyaya bir dizi dize yazalım.

Kod çok sayıda yorum içeriyor — Umarım her şeyin nasıl çalıştığını anlamanıza yardımcı olurlar:


// Create a RandomAccessFile object, passing in the file path
// and a string that says the file will be opened for reading and writing
try (RandomAccessFile randomAccessFile = new RandomAccessFile("text.txt", "rw");
    // Get an instance of the FileChannel class
    FileChannel channel = randomAccessFile.getChannel();
) {
// Our file is small, so we'll read it in one go   
// Create a buffer of the required size based on the size of our channel
   ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
   // Read data will be put into a StringBuilder
   StringBuilder builder = new StringBuilder();
   // Write data from the channel to the buffer
   channel.read(byteBuffer);
   // Switch the buffer from write mode to read mode
   byteBuffer.flip();
   // In a loop, write data from the buffer to the StringBuilder
   while (byteBuffer.hasRemaining()) {
       builder.append((char) byteBuffer.get());
   }
   // Display the contents of the StringBuilder on the console
   System.out.println(builder);
 
   // Now let's continue our program and write data from a string to the file
   // Create a string with arbitrary text
   String someText = "Hello, Amigo!!!!!";
   // Create a new buffer for writing,
   // but let the channel remain the same, because we're going to the same file
   // In other words, we can use one channel for both reading and writing to a file
   // Create a buffer specifically for our string — convert the string into an array and get its length
   ByteBuffer byteBuffer2 = ByteBuffer.allocate(someText.getBytes().length);
   // Write our string to the buffer
   byteBuffer2.put(someText.getBytes());
   // Switch the buffer from write mode to read mode
   // so that the channel can read from the buffer and write our string to the file
   byteBuffer2.flip();
   // The channel reads the information from the buffer and writes it to our file
   channel.write(byteBuffer2);
} catch (FileNotFoundException e) {
   e.printStackTrace();
} catch (IOException e) {
   e.printStackTrace();
}

NIO API'yi deneyin — buna bayılacaksınız!