Takip et

Java RandomAccessFile Örneği: Dosya İşlemlerinde Güçlü Bir Araç

Java RandomAccessFile Örneği: Dosya İşlemlerinde Güçlü Bir Araç Giriş Java’da dosya işlemleri, genellikle InputStream, OutputStream, Reade

Java RandomAccessFile Örneği: Dosya İşlemlerinde Güçlü Bir Araç

Giriş

Java’da dosya işlemleri, genellikle InputStream, OutputStream, Reader ve Writer gibi akış tabanlı sınıflar aracılığıyla gerçekleştirilir. Bu sınıflar, dosyaları sırayla, yani baştan sona veya sondan başa doğru okuma ve yazma işlemlerinde oldukça etkilidir. Ancak, belirli bir dosyanın ortasındaki bir veri bloğuna doğrudan erişmek, okumak veya güncellemek gerektiğinde, akış tabanlı yaklaşımlar yetersiz kalır. Bu tür senaryolarda, tüm dosyayı belleğe yüklemek veya istenen konuma ulaşana kadar gereksiz yere okuma yapmak gibi verimsiz çözümlere başvurmak gerekebilir.

İşte tam bu noktada Java’nın java.io.RandomAccessFile sınıfı devreye girer. RandomAccessFile, adından da anlaşılacağı gibi, bir dosyanın herhangi bir konumuna rastgele erişim sağlayabilen güçlü bir araçtır. Bu sınıf, bir dosya üzerinde hem okuma hem de yazma işlemlerini destekler ve dosya işaretçisi (file pointer) adı verilen bir mekanizma ile dosya içindeki mevcut konumu yönetmenize olanak tanır. Bu sayede, büyük dosyalarla çalışırken veya veritabanı benzeri yapıları dosya sisteminde tutarken oldukça verimli çözümler sunar.

Bu makalede, RandomAccessFile sınıfının ne olduğunu, neden kullanılması gerektiğini, temel yapıcı metotlarını ve önemli metotlarını detaylı bir şekilde inceleyeceğiz. Çeşitli kullanım örnekleri ile bu sınıfın pratik uygulamalarını gösterecek, dikkat edilmesi gereken noktaları vurgulayacak ve son olarak alternatif teknolojilerle karşılaştırmasını yaparak hangi durumlarda RandomAccessFile‘ın en uygun seçim olduğunu ortaya koyacağız.

RandomAccessFile Nedir?

RandomAccessFile, Java’nın java.io paketinde yer alan, bir dosyanın hem okunmasına hem de yazılmasına izin veren bir sınıftır. Geleneksel akış sınıflarının aksine, RandomAccessFile bir dosyanın belirli bir bayt ofsetine doğrudan atlama yeteneği sunar. Bu özellik, sınıfın “rastgele erişim” yeteneğini tanımlar.

Bu sınıf, DataInput ve DataOutput arayüzlerini implemente eder. Bu, ilkel veri tiplerini (int, double, boolean, String vb.) doğrudan dosyalara yazma ve dosyalardan okuma yeteneğine sahip olduğu anlamına gelir. RandomAccessFile tamamen bayt tabanlı çalışır ve karakter kodlaması gibi konuları doğrudan ele almaz; bu, programcının sorumluluğundadır veya readUTF()/writeUTF() gibi metotlar Java’nın kendi özel UTF formatını kullanır.

RandomAccessFile‘ın en temel özelliği, bir dosya işaretçisi (file pointer) tutmasıdır. Bu işaretçi, dosya içindeki mevcut okuma/yazma konumunu gösterir. seek() metodu ile bu işaretçi istenilen herhangi bir konuma taşınabilir. Okuma veya yazma işlemleri gerçekleştirildiğinde, dosya işaretçisi otomatik olarak okunan veya yazılan bayt sayısı kadar ilerler. Bu mekanizma, dosyanın herhangi bir bölümüne hızlı ve verimli bir şekilde erişim imkanı sağlar.

Neden RandomAccessFile Kullanmalıyız?

RandomAccessFile, belirli senaryolarda diğer dosya I/O sınıflarına göre önemli avantajlar sunar. İşte bu sınıfı kullanmayı gerektiren başlıca nedenler:

Sıralı Erişim vs. Rastgele Erişim

FileInputStream, FileOutputStream, FileReader, FileWriter gibi sınıflar veriyi sıralı bir şekilde işler. Yani, bir dosyayı okumak için baştan başlamanız ve sonuna kadar ilerlemeniz gerekir. Dosyanın ortasındaki bir veri bloğunu değiştirmek istediğinizde, genellikle dosyanın tamamını okuyup, bellekte değişiklik yapıp, sonra tüm dosyayı baştan yazmanız gerekir ki bu büyük dosyalar için son derece verimsizdir. RandomAccessFile ise seek(long pos) metodu sayesinde dosya işaretçisini doğrudan istediğiniz bayt konumuna taşıyarak, bu tür sıralı erişim kısıtlamalarını ortadan kaldırır. Bu, özellikle büyük dosyalarda belirli kayıtları hızlıca bulup güncellemek için hayati öneme sahiptir.

Veritabanı Benzeri Yapılar

RandomAccessFile, sabit boyutlu kayıtların tutulduğu dosyalar üzerinde veritabanı benzeri işlemler gerçekleştirmek için idealdir. Örneğin, her kaydın (bir öğrenci, bir ürün vb.) belirli bir sabit bayt uzunluğuna sahip olduğu bir dosya düşünün. RandomAccessFile ile, N. kayda doğrudan N * RECORD_SIZE konumuna atlayarak erişebilir, okuyabilir veya güncelleyebilirsiniz. Bu, basit indeksleme sistemleri oluşturmak veya mevcut bir dosya içinde belirli bir veriyi arayıp değiştirmek için çok kullanışlıdır.

Bellek Verimliliği

Büyük dosyalarla çalışırken, dosyanın tamamını belleğe yüklemek sistem kaynaklarını aşırı tüketebilir ve OutOfMemoryError hatalarına yol açabilir. RandomAccessFile, sadece üzerinde işlem yaptığınız belirli bir bölümü okuyup yazarak bellek verimliliği sağlar. Bu sayede, gigabaytlarca büyüklüğündeki dosyalarla bile etkin bir şekilde çalışabilirsiniz.

Eş Zamanlı Erişim

Birden fazla iş parçacığının aynı dosyaya eş zamanlı olarak erişmesi gerektiğinde, RandomAccessFile‘ın getChannel() metodu aracılığıyla elde edilen FileChannel nesnesi ve FileLock mekanizması kullanılabilir. Bu, dosyanın belirli bölgelerini kilitleyerek veri bütünlüğünü korumaya ve eş zamanlı erişim sorunlarını yönetmeye yardımcı olur. Ancak, bu konu RandomAccessFile‘ın kendisinin ötesinde, Java NIO (New I/O) API’sine ait ileri düzey bir konudur ve dikkatli bir şekilde ele alınmalıdır.

RandomAccessFile Yapıcı Metotları

RandomAccessFile sınıfını kullanmak için öncelikle bir nesnesini oluşturmanız gerekir. Bu sınıfın iki temel yapıcı metodu bulunur:

* RandomAccessFile(String name, String mode): Dosyanın yolunu bir String olarak ve erişim modunu başka bir String olarak alır.
* RandomAccessFile(File file, String mode): Dosyanın yolunu bir File nesnesi olarak ve erişim modunu bir String olarak alır.

Her iki yapıcı metotta da mode parametresi kritik öneme sahiptir.

Mod Parametresi

mode parametresi, dosyanın hangi erişim izinleriyle açılacağını belirtir:

* "r" (read): Dosya sadece okuma modunda açılır. Eğer dosya mevcut değilse, FileNotFoundException fırlatılır.
* "rw" (read/write): Dosya hem okuma hem de yazma modunda açılır. Eğer dosya mevcut değilse, otomatik olarak oluşturulur.
* "rws" (read/write synchronous metadata): Dosya okuma ve yazma modunda açılır. Ayrıca, dosya içeriğinin ve dosya meta verilerinin (son değiştirilme zamanı, dosya boyutu vb.) diske senkronize olarak yazılmasını sağlar. Bu, kritik veriler için veri bütünlüğünü artırır, ancak performansı düşürebilir.
* "rwd" (read/write synchronous data): Dosya okuma ve yazma modunda açılır. Dosya içeriğinin diske senkronize olarak yazılmasını sağlar, ancak meta veriler için senkronizasyon garantisi vermez. rws‘ye göre biraz daha iyi performans sunabilirken, veri bütünlüğü açısından rws kadar güçlü değildir.

Genellikle "rw" modu çoğu senaryo için yeterlidir. Veri bütünlüğünün kritik olduğu ve sistem çökmesi gibi durumlara karşı dayanıklılığın önemli olduğu durumlarda "rws" veya "rwd" tercih edilebilir.

Temel Metotlar

RandomAccessFile sınıfı, dosya üzerinde çeşitli işlemler yapmanızı sağlayan zengin bir metot setine sahiptir. Bu metotlar DataInput ve DataOutput arayüzlerinden miras alınmıştır.

Dosya İşaretçisi Yönetimi

* seek(long pos): Dosya işaretçisini belirtilen pos (bayt cinsinden) konumuna taşır. Bu metot, rastgele erişimin kalbidir.
* getFilePointer(): Dosya işaretçisinin mevcut konumunu döndürür. Bu, bir sonraki okuma veya yazma işleminin nereden başlayacağını gösterir.
* length(): Dosyanın toplam uzunluğunu (bayt cinsinden) döndürür.
* setLength(long newLength): Dosyanın uzunluğunu ayarlar. Eğer newLength mevcut uzunluktan küçükse, dosya kırpılır. Eğer büyükse, dosya belirtilen uzunluğa kadar genişletilir ve yeni eklenen baytlar genellikle sıfırlarla doldurulur (işletim sistemine bağlı olarak değişebilir).

Okuma Metotları

RandomAccessFile, ilkel veri tiplerini ve bayt dizilerini okumak için çeşitli metotlar sunar:
* read(): Dosyadan bir bayt okur ve onu int olarak döndürür (0-255 arası). Dosya sonuna ulaşılırsa -1 döndürür.
* read(byte[] b): Dosyadan b dizisinin boyutuna kadar bayt okur ve b dizisine depolar. Okunan bayt sayısını döndürür.
* read(byte[] b, int off, int len): Dosyadan len kadar bayt okur ve b dizisinin off ofsetinden itibaren depolar. Okunan bayt sayısını döndürür.
* readBoolean(): Bir boolean değeri okur.
* readByte(): Bir signed byte değeri okur.
* readUnsignedByte(): Bir unsigned byte değeri okur (0-255 arası).
* readChar(): İki baytlık bir char değeri okur.
* readDouble(): Sekiz baytlık bir double değeri okur.
* readFloat(): Dört baytlık bir float değeri okur.
* readInt(): Dört baytlık bir int değeri okur.
* readLong(): Sekiz baytlık bir long değeri okur.
* readShort(): İki baytlık bir signed short değeri okur.
* readUnsignedShort(): İki baytlık bir unsigned short değeri okur (0-65535 arası).
* readUTF(): Java’nın modifiye edilmiş UTF-8 formatında bir String okur. İlk iki bayt String’in bayt uzunluğunu belirtir.

Yazma Metotları

Okuma metotlarına benzer şekilde, RandomAccessFile ilkel veri tiplerini ve bayt dizilerini dosyaya yazmak için de metotlar sunar:
* write(int b): Bir bayt yazar. Sadece int değerinin en düşük 8 biti kullanılır.
* write(byte[] b): b dizisindeki tüm baytları dosyaya yazar.
* write(byte[] b, int off, int len): b dizisinin off ofsetinden başlayarak len kadar baytı dosyaya yazar.
* writeBoolean(boolean v): Bir boolean değeri yazar.
* writeByte(int v): Bir byte değeri yazar.
* writeBytes(String s): String’in her karakterinin düşük 8 bitini bayt olarak yazar. Karakterler 0-255 aralığında olmalıdır.
* writeChar(int v): İki baytlık bir char değeri yazar.
* writeChars(String s): String’in her karakterini iki baytlık char olarak yazar. Bu, sabit boyutlu String alanları için writeUTF()‘e göre daha uygun olabilir.
* writeDouble(double v): Sekiz baytlık bir double değeri yazar.
* writeFloat(float v): Dört baytlık bir float değeri yazar.
* writeInt(int v): Dört baytlık bir int değeri yazar.
* writeLong(long v): Sekiz baytlık bir long değeri yazar.
* writeShort(int v): İki baytlık bir short değeri yazar.
* writeUTF(String str): Java’nın modifiye edilmiş UTF-8 formatında bir String yazar. İlk iki bayt String’in bayt uzunluğunu içerir, bu nedenle yazılan String’in uzunluğu değişkendir.

close() Metodu

Tüm dosya işlemleri tamamlandığında, close() metodu çağrılarak dosya kaynağının serbest bırakılması çok önemlidir. Bu, bellek sızıntılarını ve dosya kilitlenme sorunlarını önler. Java 7 ve sonraki sürümlerde try-with-resources yapısı, RandomAccessFile gibi AutoCloseable arayüzünü implemente eden sınıflar için otomatik kapanmayı sağlar ve hata yönetimini basitleştirir.

RandomAccessFile Kullanım Örnekleri

RandomAccessFile‘ın yeteneklerini daha iyi anlamak için çeşitli pratik örneklere göz atalım.

Örnek 1: Dosyaya Rastgele Veri Yazma ve Okuma

Bu örnek, dosyaya farklı türde verileri nasıl yazacağımızı ve ardından belirli bir konuma atlayarak bu verileri nasıl okuyacağımızı gösterir.

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class BasicRandomAccessFileExample {
    private static final String FILE_NAME = "test_data.dat";

    public static void main(String[] args) {
        try (RandomAccessFile raf = new RandomAccessFile(FILE_NAME, "rw")) {
            // Dosyaya veri yazma
            raf.writeUTF("Merhaba Dünya!"); // UTF String yazar
            raf.writeInt(12345);            // Integer yazar
            raf.writeDouble(3.14159);       // Double yazar

            System.out.println("Veriler dosyaya yazıldı.");
            System.out.println("Dosya işaretçisi konumu (yazma sonrası): " + raf.getFilePointer());

            // Dosya işaretçisini başa taşıma
            raf.seek(0);
            System.out.println("Dosya işaretçisi başa alındı: " + raf.getFilePointer());

            // Dosyadan veri okuma
            String message = raf.readUTF();
            int number = raf.readInt();
            double pi = raf.readDouble();

            System.out.println("Okunan mesaj: " + message);
            System.out.println("Okunan sayı: " + number);
            System.out.println("Okunan Pi değeri: " + pi);
            System.out.println("Dosya işaretçisi konumu (okuma sonrası): " + raf.getFilePointer());

            // Belirli bir konuma atlayıp sadece int değerini okuma
            // writeUTF() metodu, string'in başında 2 baytlık bir uzunluk bilgisi içerir.
            // "Merhaba Dünya!" string'i (14 karakter) için UTF-8'de 14 bayt yer kaplar.
            // Yani writeUTF("Merhaba Dünya!") toplam 2 (uzunluk) + 14 (veri) = 16 bayt yer kaplar.
            // int değeri ise 4 bayt yer kaplar.
            // Dolayısıyla, int değerinin başlangıç konumu 16. bayttır.
            raf.seek(16); // Mesajı atla, int değerinin başlangıcına git
            int onlyNumber = raf.readInt();
            System.out.println("Sadece int değerini okuduk (seek sonrası): " + onlyNumber);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // Dosyayı temizle
            File file = new File(FILE_NAME);
            if (file.exists()) {
                file.delete();
                System.out.println("Test dosyası silindi.");
            }
        }
    }
}

Bu örnekte, writeUTF(), writeInt(), writeDouble() gibi metotlarla verileri dosyaya yazıp, ardından seek() metoduyla dosya işaretçisini sıfırlayarak veya belirli bir konuma taşıyarak bu verileri readUTF(), readInt(), readDouble() ile okuduk. writeUTF()‘in değişken uzunluklu yapısı nedeniyle, belirli bir veri türüne atlamak için yazılan her veri türünün bayt uzunluğunu bilmek gerektiğini unutmayın.

Örnek 2: Sabit Boyutlu Kayıtlarla Çalışma

RandomAccessFile‘ın en güçlü kullanım alanlarından biri, sabit boyutlu kayıtları depolayan dosyalardır. Bu sayede, N. kayda doğrudan erişilebilir. Aşağıdaki örnekte, her biri id, name ve age alanlarına sahip Person nesnelerini bir dosyaya yazıp okuyacağız. name alanı için sabit bir uzunluk belirleyerek kayıt boyutunu sabitleyeceğiz.

“`java
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;

class Person {
int id;
String name;
int age;

// Her Person kaydının boyutu (byte cinsinden)
// int (4 byte) + 15 chars (her char 2 byte) + int (4 byte) = 4 + 30 + 4 = 38 byte
public static final int RECORD_SIZE = 4 + (15 * 2) + 4;
private static final int NAME_LENGTH = 15;

public Person(int id, String name, int age) {
this.id = id;
// İsim sabit uzunlukta tutulacak. Fazla karakterler kesilir, eksik karakterler boşlukla doldurulur.
this.name = String.format(“%-” + NAME_LENGTH + “s”, name).substring(0, NAME_LENGTH);
this.age = age;
}

@Override
public String toString() {
return “Person{” +
“id=” + id +
“, name='” + name.trim() + ‘\” + // Okurken boşlukları temizlemek için trim()
“, age=” + age +
‘}’;
}
}

public class FixedRecordRandomAccessFileExample {
private static final String FILE_NAME = “people_data.dat”;

public static void main(String[] args) {
try (RandomAccessFile raf = new RandomAccessFile(FILE_NAME, “rw”)) {
raf.setLength(0); // Önceki verileri temizle

// Kayıtları dosyaya yazma
Person p1 = new Person(1, “Alice”, 30);
Person p2 = new Person(2, “Bob Smith”, 25);
Person p3 = new Person(3, “Charlie Brown”, 35);

writePerson(raf, p1); // Konum 0
writePerson(raf, p2); // Konum 38
writePerson(raf, p3); // Konum 76

System.out.println(“Kayıtlar dosyaya yazıldı.”);
System.out.println(“Dosya uzunluğu: ” + raf.length() + ” byte.”);

// Belirli bir kaydı okuma (örneğin 2. kayıt – Bob Smith)
// 2. kayıtın başlangıç konumu: (2 – 1) RECORD_SIZE = 1 38 = 38
raf.seek(Person.RECORD_SIZE);
Person readP2 = readPerson(raf);
System.out.println(“Okunan 2. kayıt: ” + readP2);

// Belirli bir kaydı güncelleme (örneğin 1. kayıt – Alice’in yaşını 31 yap)
raf.seek(0); // 1. kaydın başlangıcı
Person updateP1 = readPerson(raf); // Önce oku
updateP1.age = 31; // Yaşı güncelle
raf.seek(0); // Tekrar 1. kaydın başına dön (yazmak için)
writePerson(raf, updateP1); // Güncellenmiş kaydı yaz

System.out.println(“1. kayıt güncellendi.”);

// Tüm kayıtları tekrar oku ve göster
System.out.println(“\nTüm kayıtlar:”);
raf.seek(0); // Başa dön
while (raf.getFilePointer() < raf.length()) { System.out.println(readPerson(raf)); } } catch (IOException e) { e.printStackTrace(); } finally { // Dosyayı temizle File file = new File(FILE_NAME); if (file.exists()) { file.delete(); System.out.println("Test dosyası silindi."); } } } // Person nesnesini dosyaya yazan yardımcı metot private static void writePerson(RandomAccessFile raf, Person p) throws IOException { raf.writeInt(p.id); raf.writeChars(p.name); // 15 karakter * 2 byte = 30 byte raf.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık