به نام خدا.

از زمانی که برنامه نویسی رو شروع کردم و به سراغ جاوا رفتم همیشه برای کار با فایل از گوگل و سایت استک آورفلو کمک میگرفتم تا چندخطی که هیچوقت نمیفهمیدمش رو انجام بدم. اما بعد از چندین سال تصمیم گرفتم برم ببینم داستانش چیه؟

توی این پست تصمیم دارم که هر آنچه که ازش فهمیدم رو با شما درمیون بذارم شاید درک مطلب از زبان من کمی برای شما ساده تر از خوندن از منابع دیگه باشه.

اولین مفهومی که باید باهاش آشنا بشیم اسمش InputStream هست که یک جریان از بایت های ورودی هست. این که الان گفتم معنیش چیه؟ جریانی از دیتا وجود داره؟ وقتی شما یک فایلی رو باز میکنید اون فایل از روی دیسک از همون آدرسی که شما دادید باز میشه و یک جریانی ایجاد میشه که شما بتونید از اون فایل بخونید. نه فقط فایل بلکه هرجریان ورودی دیگه ای مثل جریان ورودی از کیبورد که توی جاوا از طریق System.in در دسترسه و دقیقا یک InputStream هست که شما میتونید بخونیدش.

نحوه کار با InputStream و OutputStream:

برای باز کردن یک فایل و خوندنش میتونید از کلاس FileInputStream استفاده کنید. این کلاس مشابه به کلاس InputStream هست و برای خوندن فایل استفاده میشه و به راحتی ادرس رو میگیره و بعد میتونید بخونید.

شما میتونید یک فایل رو اینجوری بازش کنید.

FileInputStream in = new FileInputStream("~/Downloads/family.txt");

و بعد هم با تابع read موجود در کلاس های InputStream که FileInputStream رو هم شامل میشه دیتا رو بخونید اما این دیتا بایت هست.

کلاس InputStream و FileInputStream دیتا رو به صورت Byte به Byte دریافت میکنه (حتی تبدیل به کاراکتر هم نمیکنه) ولی کلاس هایی هستن که میتونید ازشون استفاده کنید تا همین Byte به Byte رو به Char تغییر بدید تا کاراکتر بخونید.

همونطور که این کلاس رو مثال زدیم دقیقا در طرف مقابل کلاسی با نام OutputStream و FileOutputStream وجود داره که میتونید دیتا رو درون یک فایلی ذخیره کنید فرایند دقیقا برعکس همین کاری هست که انجام دادیم و اونجا باید با استفاده از تابع write میتونید دیتا رو توی اون استریم بنویسید.

 

کار با InputStreamReader و OutputStreamWriter:

همونطور که گفتیم توابع InputStream و FileInputStream همچنین OutputStream و FileOutputStream با بایت کار میکردن اما اگه میخواید با کاراکتر ها کار کنید از این کلاس ها به همون سبک قبل استفاد کنید اینبار بهتون کاراکتر میده.

FileReader fr = new FileReader("file.txt");
        int content;
        while ((content = fr.read()) != -1) {
            System.out.print((char) content);
        }

کد بالا کل متن داخل فایل file.txt رو میخونه و چاپ میکنه.

اما در همه این حالت ها وقتی هربار که read اجرا میشه سیستم ما میره یه کوئری میزنه و یه کاراکتر (یا بایت) از فایل میخونه میاره به ما میده و این سرعت رو به شدت پایین میاره.

بهترین کار اینه که به جای کاراکتر به کاراکتر بریم خط به خط یا حتی بیشتر بیاریم.

برای اینکار باید از یک کلاس که wrapper این کلاس ها میشه استفاده کنیم و اونم چیزی نیست جز BufferedInputStream و یا BufferedReader.

 

کار با BufferedInputStream و BufferedReader:

این کلاس ها همونطوری که گفتم دیتایی که قراره بیاد رو بافر میکنن یعنی به جای کاراکتر به کاراکتر اونا رو مثلا خط به خط میارن (چانک بای چانک) میارنشون.

اینم مثل قبلیا جفت هست یعنی BufferedOutputStream و BufferedWriter هم موجوده که کار مشابه ولی در عمل نوشتن انجام میده. 

مثال بالا رو با استفاده از این کلاس بازنویسی میکنیم.

BufferedReader br = new BufferedReader(new FileReader("file.txt"));
        String content;
        while ((content = br.readLine()) != null) {
            System.out.print(content);
        }

میبینید که کلاس BufferedReader دور کلاس FileReader پیچیده شد و دیتای اون رو بافر میکنه همچنین تابع readLine در این کلاس وجود داره که به راحتی ازش استفاده کردیم.

 

کار با DataInputStream:

این کلاس هم یه wrapper مثل BufferedInputStream هست و کارش به این شکله که مثل کلاس بافر که بافر میکرد و به ما تابع readLine رو میداد اینبار ما دیتایی که داره میاد رو میتونیم براساس تایپ های اصلی زبان جاوا یعنی Int, Char, String و... بخونیم. (ترتیب خیلی مهمه یعنی مثلا اگه readInt کردید دارید میگید که ۴ بایت پیش رو به عنوان Int بخون و بعد اگه Char زدید یعنی از اینجا یک بایت رو به char تبدیل کن و... پس ترتیب خیلییی مهمه).

DataInputStream din = new DataInputStream(new FileInputStream("file.dat"));

// Illustrating readDouble() method
double a = din.readDouble();

// Illustrating readInt() method
int b = din.readInt();

// Illustrating readBoolean() method
boolean c = din.readBoolean();

// Illustrating readChar() method
char d = din.readChar();

// Print the values
System.out.println("Values: " + a + " " + b + " " + c + " " + d);

پس میتونید دیتاتون رو اینجوری ذخیره کنید و بخونید.

یه مثال از نوشتن هم میزنم که پست تکمیل تر باشه.

            DataOutputStream dout = new DataOutputStream(new FileOutputStream("file.dat"));
            dout.writeDouble(1.1);
            dout.writeInt(55);
            dout.writeBoolean(true);
            dout.writeChar('4');

این دقیقا فایل مثال بالا رو میسازه.

 

کار با ObjectInputStream:

این آخرین کلاسی هست که توضیح میدم. کار با این کلاس خیلی ساده است. این کلاس آبجکتی که بهش میدید رو ذخیره میکنه (و حتی آبجکت های داخلی رو هم ذخیره میکنه) و هرموقع نیاز داشتید میتونید لود کنید و همون instance ها براتون ساخته میشه (جالبیش اینه که اگه یه instance توی آبجکتی که دارید ذخیره میکنید توی چندتا متغییر داشته باشید این هم فقط یه بار اون رو ذخیره میکنه و وقتی لود میکنید همون یه دونه رو به اون متغییر ها میده)

نکته: این نکته خیلی مهمه که کلاس حتما باید Serializable باشه یعنی این interface رو پیاده کنه. مگرنه کار نمیکنه.

class Dog implements Serializable {

    String name;
    String breed;

    public Dog(String name, String breed) {
        this.name = name;
        this.breed = breed;
    }
}

class Main {
    public static void main(String[] args) {

        // Creates an object of Dog class
        Dog dog = new Dog("Tyson", "Labrador");

        try {
            FileOutputStream file = new FileOutputStream("file.txt");

            // Creates an ObjectOutputStream
            ObjectOutputStream output = new ObjectOutputStream(file);

            // Writes objects to the output stream
            output.writeObject(dog);

            FileInputStream fileStream = new FileInputStream("file.txt");

            // Creates an ObjectInputStream
            ObjectInputStream input = new ObjectInputStream(fileStream);

            // Reads the objects
            Dog newDog = (Dog) input.readObject();

            System.out.println("Dog Name: " + newDog.name);
            System.out.println("Dog Breed: " + newDog.breed);

            output.close();
            input.close();
        }

        catch (Exception e) {
            e.getStackTrace();
        }
    }
}