
প্রোগ্রাম বন্ধ করলেই সব তথ্য হারিয়ে যায়, এই সমস্যাটি নতুন প্রোগ্রামারদের প্রথম বড় হোঁচট। ভ্যারিয়েবলে রাখা মান কম্পিউটারের র্যামে থাকে, আর প্রোগ্রাম শেষ হওয়ামাত্র সেই জায়গা মুছে যায়। তথ্য ধরে রাখতে হলে সেটিকে ডিস্কে, অর্থাৎ একটি ফাইলে লিখে রাখতে হয়।
এই টিউনে আমরা একটি ছোট নোট অ্যাপ বানাব যা লেখা নোটগুলো ফাইলে জমা রাখবে এবং প্রোগ্রাম আবার চালু করলে সেগুলো ফিরিয়ে আনবে। পুরোনো পদ্ধতি BufferedWriter ও BufferedReader দিয়ে শুরু করব, তারপর দেখব আধুনিক Files ক্লাস দিয়ে একই কাজ কত কম লাইনে করা যায়। বাংলা লেখা ফাইলে সঠিকভাবে রাখার কৌশলটিও থাকবে, যেটি না জানলে বাংলা অক্ষর প্রশ্নবোধক চিহ্ন হয়ে যায়।
অ্যাপটি চালু হলে একটি ছোট মেনু দেখাবে। ব্যবহারকারী নতুন নোট লিখতে পারবেন, আগের সব নোট দেখতে পারবেন, অথবা বের হয়ে যেতে পারবেন। প্রতিটি নোট notes.txt নামের একটি ফাইলে নতুন লাইন হিসেবে যোগ হবে।
কোড লেখা শুরু করার আগে পুরো কাজটি কোন ধাপে কীভাবে এগোবে তা একবার দেখে নেওয়া ভালো। নিচের চিত্রে ফাইলে নোট লেখা থেকে শুরু করে সেটি আবার পর্দায় দেখানো পর্যন্ত ছয়টি ধাপ সাজিয়ে দেওয়া হয়েছে।

ফাইল নিয়ে কাজ করার ব্যাপারটি প্রথমে একটু ভয় লাগতে পারে, কারণ এখানে প্রোগ্রামের বাইরের জগতের সাথে যোগাযোগ করতে হয়। কিন্তু মূল ধারণাটি খুব সহজ। লেখার সময় জাভা একটি পথ খুলে নেয়, সেই পথ দিয়ে অক্ষরগুলো ফাইলে পাঠায়, তারপর পথটি বন্ধ করে দেয়। পড়ার সময়ও ঠিক একই কাজ উল্টো দিকে হয়।
এই পথটিকেই বলা হয় স্ট্রিম। স্ট্রিম খোলা থাকলে সেটি কম্পিউটারের একটি সম্পদ দখল করে রাখে, তাই কাজ শেষ হলে সেটি বন্ধ করা খুব দরকারি। বন্ধ না করলে দুইটি সমস্যা হতে পারে। এক, লেখা তথ্য ফাইলে না পৌঁছে মাঝপথে আটকে থাকতে পারে। দুই, ফাইলটি আটকে থাকায় অন্য প্রোগ্রাম সেটি ব্যবহার করতে পারে না।
এই টিউনে আমরা প্রথমে সাধারণ উপায়ে কাজটি করব, তারপর দেখব কীভাবে আরও কম লাইনে একই কাজ করা যায়। নিচের কথাগুলো মাথায় রাখলে পুরো ধাপগুলো বুঝতে সুবিধা হবে।
ফাইলে লেখার জন্য FileWriter যথেষ্ট, তবে তার উপরে BufferedWriter মুড়ে দিলে বারবার ডিস্কে না গিয়ে একসাথে লেখা হয়, ফলে গতি বাড়ে। নিচের কোডে দুটি গুরুত্বপূর্ণ জিনিস আছে, খেয়াল করুন।
static void saveNote(String note) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("notes.txt", StandardCharsets.UTF_8, true))) {
writer.write(note);
writer.newLine();
System.out.println("নোট সেভ হয়েছে");
} catch (IOException e) {
System.out.println("সেভ করা যায়নি, কারণ " + e.getMessage());
}
}
শুধু নাম দিলে ফাইলটি প্রোগ্রামের ওয়ার্কিং ডিরেক্টরিতে তৈরি হয়, যা সাধারণত প্রজেক্ট ফোল্ডার। কোথায় হলো তা নিশ্চিত হতে System.getProperty দিয়ে user.dir ছাপিয়ে দেখুন, তাহলে আর খুঁজে বেড়াতে হবে না।
পড়ার সময় আমরা একটি লুপ চালাব যা প্রতিটি লাইন এক এক করে আনবে। readLine মেথডটি ফাইলের শেষে পৌঁছালে null ফেরত দেয়, সেটিই আমাদের থামার সংকেত।
static void showNotes() {
try (BufferedReader reader = new BufferedReader(new FileReader("notes.txt", StandardCharsets.UTF_8))) {
String line;
int count = 1;
while ((line = reader.readLine()) != null) {
System.out.println(count + ". " + line);
count++;
}
} catch (IOException e) {
System.out.println("এখনো কোনো নোট নেই");
}
}
একই কাজ জাভাতে দুইভাবে করা যায়। কোনটি কখন বেছে নেবেন তা নিচের তুলনামূলক চিত্র দেখে সহজে বুঝতে পারবেন।

স্ট্রিম দিয়ে কাজ করার সবচেয়ে বড় সুবিধা হলো ফাইলটি যত বড়ই হোক, একবারে পুরোটা মেমোরিতে তোলার দরকার হয় না। এক লাইন পড়া হয়, সেই লাইনের কাজ শেষ হয়, তারপর পরের লাইন পড়া হয়। কয়েক লাখ লাইনের একটি লগ ফাইলেও এই পদ্ধতি নির্ভয়ে ব্যবহার করা যায়, কারণ মেমোরির চাপ প্রায় একই থাকে।
অন্যদিকে Files ক্লাস কাজটিকে অনেক ছোট করে দেয়। পুরো ফাইলের সব লাইন একটি তালিকায় নিয়ে আসা যায় মাত্র এক লাইনের কোডে, আবার একটি লেখা পুরোটা ফাইলে বসিয়ে দেওয়াও এক লাইনেই সম্ভব। তবে এখানে একটি শর্ত আছে, পুরো ফাইলটি একবারে মেমোরিতে ওঠে। তাই ফাইলটি যদি খুব বড় হয় তবে মেমোরির উপর চাপ পড়ে।
আমাদের নোট অ্যাপে ফাইলটি ছোট থাকবে, হয়তো কয়েকশো লাইন। তাই এখানে Files ক্লাস ব্যবহার করলে কোড অনেক পরিষ্কার হয়। তবু স্ট্রিমের পদ্ধতিটি শিখে রাখা দরকার, কারণ বাস্তব কাজে বড় ফাইলের মুখোমুখি হতেই হবে। সহজ নিয়মটি নিচে দেওয়া হলো।
জাভার আধুনিক Files ক্লাস ব্যবহার করলে উপরের কাজগুলো মাত্র দুই লাইনে হয়ে যায়। ছোট ফাইলের জন্য এটিই এখন সবচেয়ে পরিষ্কার পদ্ধতি।
Path path = Path.of("notes.txt");
Files.writeString(path, "নতুন একটি নোট" + System.lineSeparator(), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
String all = Files.readString(path, StandardCharsets.UTF_8);
System.out.println(all);
এবার সব অংশ একসাথে জুড়ে দিচ্ছি। Scanner দিয়ে ব্যবহারকারীর পছন্দ নেব, while লুপ দিয়ে বারবার মেনু দেখাব, আর switch দিয়ে কাজ ভাগ করব।
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class NoteApp {
private static final String FILE = "notes.txt";
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
while (running) {
System.out.println();
System.out.println("1. নতুন নোট লিখুন");
System.out.println("2. সব নোট দেখুন");
System.out.println("3. বের হয়ে যান");
System.out.print("আপনার পছন্দ: ");
String choice = scanner.nextLine().trim();
switch (choice) {
case "1":
System.out.print("নোট লিখুন: ");
saveNote(scanner.nextLine());
break;
case "2":
showNotes();
break;
case "3":
running = false;
System.out.println("ধন্যবাদ");
break;
default:
System.out.println("1, 2 অথবা 3 লিখুন");
}
}
scanner.close();
}
static void saveNote(String note) {
if (note.isBlank()) {
System.out.println("খালি নোট সেভ করা যাবে না");
return;
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE, StandardCharsets.UTF_8, true))) {
writer.write(note);
writer.newLine();
System.out.println("নোট সেভ হয়েছে");
} catch (IOException e) {
System.out.println("সেভ করা যায়নি, কারণ " + e.getMessage());
}
}
static void showNotes() {
try (BufferedReader reader = new BufferedReader(new FileReader(FILE, StandardCharsets.UTF_8))) {
String line;
int count = 1;
while ((line = reader.readLine()) != null) {
System.out.println(count + ". " + line);
count++;
}
if (count == 1) {
System.out.println("তালিকা খালি");
}
} catch (IOException e) {
System.out.println("এখনো কোনো নোট নেই");
}
}
}
ফাইল নিয়ে কাজ করতে গেলে কিছু এরর প্রায় সবার ক্ষেত্রেই আসে। নিচের ছকে সেই সমস্যাগুলো, তার কারণ এবং সমাধান একসাথে সাজিয়ে দেওয়া হলো, যাতে এরর দেখলে সাথে সাথে কী করতে হবে বুঝতে পারেন।

এররগুলোর মধ্যে সবচেয়ে বেশি ভোগায় দুইটি জিনিস। প্রথমটি হলো পাথ সংক্রান্ত ভুল, আর দ্বিতীয়টি হলো স্ট্রিম বন্ধ না করা। এই দুইটি বিষয় একবার ভালো করে বুঝে নিলে ফাইল নিয়ে কাজ করা অনেক নিরাপদ হয়ে যায়। তাই এই দুইটি নিয়ে আলাদা করে নিচে আলোচনা করা হলো।
নতুন অবস্থায় সবচেয়ে বেশি বিভ্রান্তি হয় ফাইলটি আসলে কোথায় তৈরি হলো তা নিয়ে। কোডে যদি শুধু ফাইলের নাম লেখা হয়, তবে জাভা সেটি খোঁজে প্রোগ্রাম যেখান থেকে চালু হয়েছে সেই ফোল্ডারে। এই ফোল্ডারটিকে বলা হয় ওয়ার্কিং ডিরেক্টরি। ইন্টেলিজে চালালে সাধারণত সেটি প্রজেক্ট ফোল্ডার হয়, কিন্তু টার্মিনাল থেকে চালালে যে ফোল্ডারে আছেন সেটিই হয়।
এই কারণেই অনেকে বলেন ফাইলটি তৈরি হয়েছে বলে মনে হচ্ছে অথচ খুঁজে পাচ্ছি না। আসলে ফাইলটি ঠিকই তৈরি হয়েছে, শুধু অন্য ফোল্ডারে। সমস্যাটি এড়ানোর সহজ উপায় হলো প্রোগ্রামের শুরুতে একবার ওয়ার্কিং ডিরেক্টরিটি ছাপিয়ে দেখে নেওয়া। তখন আর অনুমান করতে হয় না।
উইন্ডোজে পাথ লেখার সময় আরেকটি ঝামেলা হয়। উইন্ডোজ ফোল্ডার আলাদা করতে ব্যাকস্ল্যাশ ব্যবহার করে, কিন্তু জাভার স্ট্রিং এ ব্যাকস্ল্যাশের আলাদা অর্থ আছে। তাই সরাসরি কপি করে বসালে কোড কাজ করে না। এর সমাধান হলো ফরওয়ার্ড স্ল্যাশ ব্যবহার করা, যা উইন্ডোজেও ঠিকভাবে চলে।
স্ট্রিম খোলার পর সেটি বন্ধ করা যে দরকার তা আগেই বলা হয়েছে। কিন্তু সমস্যা হলো, মাঝপথে যদি কোনো এরর হয়ে যায় তবে বন্ধ করার লাইনটি পর্যন্ত প্রোগ্রাম পৌঁছাতেই পারে না। ফলে স্ট্রিমটি খোলা থেকে যায়। আগে এই সমস্যা এড়াতে আলাদা একটি ব্লকে বন্ধ করার কোড লিখতে হতো, যা বেশ লম্বা আর ভুল হওয়ার সম্ভাবনাও বেশি ছিল।
জাভা এই ঝামেলা দূর করার জন্য একটি সুন্দর ব্যবস্থা দিয়েছে। স্ট্রিম তৈরির কাজটি যদি বন্ধনীর ভিতরে লেখা হয়, তবে ব্লকের কাজ শেষ হওয়ার সাথে সাথে জাভা নিজেই সেটি বন্ধ করে দেয়। এমনকি ভিতরে এরর হলেও বন্ধ করার কাজটি হবে। এতে কোড ছোট হয়, আর ভুলে যাওয়ার ঝুঁকিও থাকে না।
একটি বিষয় মনে রাখা দরকার, এই সুবিধা সব জিনিসের জন্য নয়। যে ক্লাসগুলো বন্ধ করা যায় এমন হিসেবে তৈরি, শুধু সেগুলোই এভাবে ব্যবহার করা যায়। ভাগ্যক্রমে ফাইল পড়া ও লেখার প্রায় সব ক্লাসই এই তালিকায় পড়ে, তাই ফাইল নিয়ে কাজ করার সময় সব সময় এই পদ্ধতিটিই ব্যবহার করা উচিত।
আমাদের নোট অ্যাপে অনেকেই বাংলায় নোট লিখতে চাইবেন। এখানেই একটি সমস্যা প্রায়ই দেখা যায়, ফাইলে লেখার পর সেটি খুললে বাংলা অক্ষরের বদলে অদ্ভুত চিহ্ন দেখা যায়। এর কারণ কোডের ভুল নয়, বরং ক্যারেক্টার সেটের অমিল।
কম্পিউটার আসলে অক্ষর জমা রাখে না, জমা রাখে সংখ্যা। কোন সংখ্যা কোন অক্ষরকে বোঝাবে তার একটি নিয়ম থাকে, সেই নিয়মটিকেই বলা হয় ক্যারেক্টার সেট। লেখার সময় এক নিয়মে লেখা হলো আর পড়ার সময় অন্য নিয়মে পড়া হলো, তখনই লেখা ভেঙে যায়। বাংলার জন্য সবচেয়ে নির্ভরযোগ্য নিয়মটি হলো ইউটিএফ আট।
সমাধান খুব সহজ। রিডার আর রাইটার দুইটি তৈরি করার সময় স্পষ্ট করে বলে দিতে হবে যে ইউটিএফ আট ব্যবহার হবে। এতে কম্পিউটারের নিজের সেটিং যা হোক না কেন, আপনার প্রোগ্রাম সব সময় একই নিয়মে লিখবে ও পড়বে। ফলে যে কম্পিউটারে চালান, বাংলা ঠিকভাবেই দেখা যাবে।
এটি এনকোডিংয়ের সমস্যা। FileWriter ও FileReader দুই জায়গাতেই StandardCharsets.UTF 8 দিতে হবে, একটিতে দিলে অন্যটিতে ভুল হবে। যে টেক্সট এডিটরে ফাইলটি খুলে দেখছেন সেটিও যেন UTF 8 এ পড়ে সেদিকে খেয়াল রাখুন।
FileWriter এর দ্বিতীয় বা তৃতীয় প্যারামিটারে true দিতে ভুলে গেছেন। ওই মানটিই ঠিক করে দেয় নতুন লেখা আগেরটির উপরে বসবে নাকি শেষে যোগ হবে।
Scanner এর সাথে nextInt ও nextLine মিশিয়ে ব্যবহার করলে এই সমস্যা হয়, কারণ nextInt লাইনের শেষের অংশটি রেখে দেয়। সবচেয়ে নিরাপদ উপায় হলো সব ইনপুট nextLine দিয়ে নেওয়া, তারপর প্রয়োজনে সংখ্যায় রূপ দেওয়া, যা আমরা এই প্রোগ্রামে করেছি।
মূল অ্যাপটি কাজ করার পর সেটিকে আরও কাজের করে তোলা যায় ছোট ছোট কিছু যোগ করে। এই কাজগুলো নতুন কোনো কঠিন ধারণা নয়, বরং যা শিখেছেন তারই একটু বাড়ানো রূপ। ধাপে ধাপে যোগ করলে প্রতিটি অংশ কীভাবে কাজ করছে তাও পরিষ্কার হয়ে যাবে।
প্রথম যোগ করা যেতে পারে সময়ের তথ্য। প্রতিটি নোটের সাথে সেটি কখন লেখা হলো তা বসিয়ে দিলে পরে খুঁজে বের করা সহজ হয়। এরপর করা যেতে পারে খোঁজার ব্যবস্থা, অর্থাৎ ব্যবহারকারী একটি শব্দ লিখলে যে নোটগুলোতে সেই শব্দ আছে শুধু সেগুলো দেখানো হবে। এই কাজটি লাইন ধরে পড়ার সময় একটি শর্ত মিলিয়ে নিলেই হয়ে যায়।
আরেকটি দরকারি জিনিস হলো নোট মুছে ফেলার সুবিধা। ফাইল থেকে সরাসরি একটি লাইন মুছে ফেলা যায় না, তাই কাজটি করতে হয় ঘুরিয়ে। পুরো ফাইল পড়ে নিয়ে যে লাইনটি রাখতে চান না সেটি বাদ দিয়ে বাকিগুলো আবার ফাইলে লিখে দিতে হয়। এই কৌশলটি বুঝে গেলে ফাইল নিয়ে অনেক কাজই সহজ মনে হবে।
নোট সেভ করার কাজটি ফাইল দিয়ে সহজেই হয়ে গেল। তাহলে প্রশ্ন আসে, ডেটাবেজের দরকার কী। উত্তরটি নির্ভর করে তথ্যের পরিমাণ আর তথ্য নিয়ে কী কাজ করবেন তার উপর। ছোট আর সরল কাজের জন্য ফাইলই যথেষ্ট, কিন্তু কাজ বাড়তে থাকলে ফাইলের সীমা টের পাওয়া যায়।
ফাইলের সবচেয়ে বড় সুবিধা হলো এতে বাড়তি কিছু বসানোর দরকার নেই। জাভা আর একটি টেক্সট ফাইল থাকলেই কাজ চলে যায়। কিন্তু ধরুন হাজার হাজার নোটের মধ্যে একটি নির্দিষ্ট শব্দ খুঁজতে হবে, বা একই সময়ে কয়েকজন মিলে নোট যোগ করবে। তখন ফাইল দিয়ে কাজটি ধীর আর ঝুঁকিপূর্ণ হয়ে যায়, কারণ প্রতিবার পুরো ফাইল পড়তে হয়।
ডেটাবেজ ঠিক এই জায়গাগুলোতেই কাজে আসে। এতে খোঁজার কাজ অনেক দ্রুত হয়, একসাথে অনেকে কাজ করলেও তথ্য নষ্ট হয় না, আর কোনো কাজ অর্ধেক হয়ে থেমে গেলে সেটি বাতিল করার ব্যবস্থাও থাকে। তবে শেখার শুরুতে ফাইল দিয়েই অভ্যাস করা ভালো, কারণ তথ্য জমা রাখার মূল ধারণাগুলো এখানেই পরিষ্কার হয়।
ফাইল নিয়ে কাজ শেখার সবচেয়ে ভালো উপায় হলো নিজে কয়েকবার চালিয়ে দেখা। নিচের কাজগুলো ছোট, কিন্তু প্রতিটি করলে একটি নির্দিষ্ট ধারণা পরিষ্কার হবে। বিশেষ করে ভুল করে দেখার কাজগুলো বাদ দেবেন না, কারণ এররগুলো নিজের চোখে দেখলে মনে অনেক দিন থাকে।
ফাইল পড়া ও লেখা শিখে ফেলা মানে আপনার প্রোগ্রাম আর ক্ষণস্থায়ী নয়। একই কৌশলে আপনি ছাত্রছাত্রীর নম্বর, খরচের হিসাব বা ছোট ইনভেন্টরি, সবই সংরক্ষণ করতে পারবেন। ডেটাবেজ শেখার আগে এই ধাপটি পার হওয়া খুব জরুরি, কারণ ধারণাগুলো একই থাকে।
অ্যাপটি চালিয়ে দেখুন, তারপর নিজে একটি সুবিধা যোগ করুন, যেমন নির্দিষ্ট শব্দ দিয়ে নোট খোঁজার ব্যবস্থা। কোন জায়গায় আটকে যাচ্ছেন টিউমেন্টে লিখে জানান, আলোচনা করলে সমাধান দ্রুত আসে।
আমি নাছিরুল হক। বিশ্বের সর্ববৃহৎ বিজ্ঞান ও প্রযুক্তির সৌশল নেটওয়ার্ক - টেকটিউনস এ আমি 1 সপ্তাহ 5 দিন যাবৎ যুক্ত আছি। টেকটিউনস আমি এ পর্যন্ত 24 টি টিউন ও 3 টি টিউমেন্ট করেছি। টেকটিউনসে আমার 0 ফলোয়ার আছে এবং আমি টেকটিউনসে 2 টিউনারকে ফলো করি।
আমি নাছিরুল হক। প্রোগ্রামিং আর প্রযুক্তি নিয়ে ঘাঁটাঘাঁটি করতে ভালোবাসি। টেকটিউনসে মূলত Java প্রোগ্রামিং নিয়ে ধাপে ধাপে হাতে কলমে টিউটোরিয়াল লিখি — JDK ইনস্টল ও এনভায়রনমেন্ট সেটআপ থেকে শুরু করে IntelliJ IDEA, Exception Handling, OOP, Java Swing ও ফাইল হ্যান্ডলিং পর্যন্ত। এছাড়া স্মার্টফোন, ব্যাটারি ও নেটওয়ার্ক নিয়ে সহজ ভাষায় ব্যাখ্যামূলক...