iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
自我挑戰組

Java 程式設計與實作:30 天打造個人記帳系統系列 第 22 篇

File 檔案讀取:重新開啟程式也能看到以前的記帳紀錄

  • 分享至 

  • xImage
  •  

昨天 Day 21 第一次使用 FileWriter,成功把記帳資料保存到:

expenses.txt

例如:

早餐,60,飲食
午餐,150,飲食
捷運,30,交通
電影,300,娛樂

但是昨天還有一個問題:

把資料存進檔案 ✓
重新啟動程式
讀取以前的資料 ✕

所以今天要完成另外一半:把 expenses.txt 裡面的資料重新讀回 Java 程式。

完成之後,即使把程式關掉再重新開啟,以前的記帳紀錄也不會消失。

今天要完成的流程

昨天完成的是:

ArrayList
↓
FileWriter
↓
expenses.txt

今天要反過來:

expenses.txt
↓
Scanner 讀取檔案
↓
取得每一筆資料
↓
建立 Expense Object
↓
加入 ArrayList

最後就會形成完整流程:

    儲存

ArrayList ──────→ expenses.txt
↑ │
│ │
└─────────────────┘
讀取

這樣資料才算真正保存下來。

第一步:認識 File

今天需要加入:

import java.io.File;

File 可以代表電腦中的一個檔案。

例如:

File file = new File("expenses.txt");

代表程式準備操作:

expenses.txt

第二步:使用 Scanner 讀取檔案

之前一直使用 Scanner 讀取鍵盤輸入:

Scanner scanner = new Scanner(System.in);

其實 Scanner 也可以讀取檔案。

例如:

File file = new File("expenses.txt");
Scanner fileScanner = new Scanner(file);

這裡我使用另一個名稱:

fileScanner

因為原本已經有:

scanner

負責接收使用者輸入。

所以現在:

scanner
→ 讀取使用者鍵盤輸入
fileScanner
→ 讀取 expenses.txt

第三步:一行一行讀取資料

假設檔案裡:

早餐,60,飲食
午餐,150,飲食
捷運,30,交通

可以使用:

while (fileScanner.hasNextLine()) {
String line = fileScanner.nextLine();
}

hasNextLine() 的意思可以簡單理解成:

檔案還有下一行嗎?

如果有,就繼續讀。

所以流程是:

早餐,60,飲食
↓
還有下一行嗎?有
↓
午餐,150,飲食
↓
還有下一行嗎?有
↓
捷運,30,交通
↓
還有下一行嗎?沒有
↓
停止

這裡又重新使用了之前學過的 while。

第四步:把一行資料拆開

現在:

String line = fileScanner.nextLine();

假設讀到:

早餐,60,飲食

但是我們需要的是三個不同資料:

item → 早餐
amount → 60
category → 飲食

這時候可以使用:

split()

寫成:

String[] data = line.split(",");

原本:

早餐,60,飲食

經過:

line.split(",");

就會變成:

data[0] → 早餐
data[1] → 60
data[2] → 飲食

這裡又用到了之前 Day 8 學過的 Array。

第五步:把 String 轉成 int

有一個小問題。

從文字檔讀取:

60

Java 會把它當成:

String

但是 Expense 的 amount 是:

int

所以不能直接:

int amount = data[1];

需要使用:

Integer.parseInt()

例如:

int amount = Integer.parseInt(data[1]);

這樣:

"60"
↓
Integer.parseInt()
↓
60

就從文字 "60" 變成真正可以計算的數字 60。

第六步:重新建立 Expense Object

現在已經取得:

String item = data[0];
int amount =
Integer.parseInt(data[1]);
String category = data[2];

接著就可以:

Expense expense =
new Expense(item, amount, category);

最後:

expenses.add(expense);

這樣資料就重新回到 ArrayList。

建立 loadExpenses() Method

把今天的功能整理成:

public static void loadExpenses() {
try {
File file = new File("expenses.txt");
Scanner fileScanner =
new Scanner(file);
while (fileScanner.hasNextLine()) {
String line =
fileScanner.nextLine();
String[] data =
line.split(",");
String item = data[0];
int amount =
Integer.parseInt(data[1]);
String category = data[2];
Expense expense =
new Expense(
item,
amount,
category
);
expenses.add(expense);
}
fileScanner.close();
System.out.println("記帳資料讀取成功!");
} catch (Exception e) {
System.out.println("目前沒有已儲存的記帳資料。");
}
}

什麼時候執行 loadExpenses()?

這個地方很重要。

如果希望:

一打開程式就自動載入以前的資料

那就應該在 main() 一開始呼叫:

loadExpenses();

例如:

public static void main(String[] args) {
loadExpenses();
boolean running = true;
while (running) {
// 主選單
}
}

這樣流程就變成:

啟動程式
↓
loadExpenses()
↓
讀取 expenses.txt
↓
建立 Expense Object
↓
加入 ArrayList
↓
顯示主選單

Day 22 完整程式

今天把「讀取檔案」加入昨天的記帳系統:

import java.util.Scanner;
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
class Expense {
private String item;
private int amount;
private String category;
Expense(String item, int amount, String category) {
this.item = item;
this.amount = amount;
this.category = category;
}
public String getItem() {
return item;
}
public int getAmount() {
return amount;
}
public String getCategory() {
return category;
}
public void setItem(String item) {
this.item = item;
}
public void setAmount(int amount) {
if (amount >= 0) {
this.amount = amount;
}
}
public void setCategory(String category) {
this.category = category;
}
}
public class Main {
static Scanner scanner = new Scanner(System.in);
static ArrayList expenses =
new ArrayList<>();
static int budget = 0;
public static void main(String[] args) {
loadExpenses();
boolean running = true;
while (running) {
showMenu();
try {
System.out.print("請選擇功能:");
int choice = scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
addExpense();
break;
case 2:
showExpenses();
break;
case 3:
calculateTotal();
break;
case 4:
categoryStatistics();
break;
case 5:
deleteExpense();
break;
case 6:
editExpense();
break;
case 7:
setBudget();
break;
case 8:
checkBudget();
break;
case 9:
saveExpenses();
break;
case 10:
running = false;
System.out.println(
"已離開個人記帳系統!"
);
break;
default:
System.out.println(
"沒有這個選項!"
);
}
} catch (InputMismatchException e) {
System.out.println(
"輸入錯誤,請輸入數字!"
);
scanner.nextLine();
}
}
scanner.close();
}
public static void showMenu() {
System.out.println();
System.out.println(
"===== 個人記帳系統 ====="
);
System.out.println("1. 新增消費");
System.out.println("2. 查看紀錄");
System.out.println("3. 查看總支出");
System.out.println("4. 分類支出統計");
System.out.println("5. 刪除消費紀錄");
System.out.println("6. 修改消費紀錄");
System.out.println("7. 設定預算");
System.out.println("8. 查看預算狀況");
System.out.println("9. 儲存記帳資料");
System.out.println("10. 離開");
}
public static void addExpense() {
System.out.println();
System.out.println(
"===== 新增消費 ====="
);
System.out.print(
"請輸入消費項目:"
);
String item =
scanner.nextLine();
System.out.print(
"請輸入消費金額:"
);
int amount =
scanner.nextInt();
String category =
chooseCategory();
Expense expense =
new Expense(
item,
amount,
category
);
expenses.add(expense);
System.out.println(
"記帳成功!"
);
}
public static String chooseCategory() {
System.out.println();
System.out.println(
"請選擇消費分類:"
);
System.out.println("1. 飲食");
System.out.println("2. 交通");
System.out.println("3. 購物");
System.out.println("4. 娛樂");
System.out.print(
"請輸入選項:"
);
int choice =
scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
return "飲食";
case 2:
return "交通";
case 3:
return "購物";
case 4:
return "娛樂";
default:
return "其他";
}
}
public static void showExpenses() {
System.out.println();
System.out.println(
"===== 消費紀錄 ====="
);
if (expenses.size() == 0) {
System.out.println(
"目前沒有消費紀錄。"
);
return;
}
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
System.out.println(
(i + 1) + ". "
+ expense.getItem()
+ "|"
+ expense.getAmount()
+ " 元|"
+ expense.getCategory()
);
}
}
public static void calculateTotal() {
int total = 0;
for (int i = 0;
i < expenses.size();
i++) {
total +=
expenses
.get(i)
.getAmount();
}
System.out.println();
System.out.println(
"===== 支出統計 ====="
);
System.out.println(
"目前共有 "
+ expenses.size()
+ " 筆消費"
);
System.out.println(
"總支出:"
+ total
+ " 元"
);
}
public static void categoryStatistics() {
int foodTotal = 0;
int transportTotal = 0;
int shoppingTotal = 0;
int entertainmentTotal = 0;
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
switch (
expense.getCategory()
) {
case "飲食":
foodTotal +=
expense.getAmount();
break;
case "交通":
transportTotal +=
expense.getAmount();
break;
case "購物":
shoppingTotal +=
expense.getAmount();
break;
case "娛樂":
entertainmentTotal +=
expense.getAmount();
break;
}
}
System.out.println();
System.out.println(
"===== 分類支出統計 ====="
);
System.out.println(
"飲食:" + foodTotal + " 元"
);
System.out.println(
"交通:" + transportTotal + " 元"
);
System.out.println(
"購物:" + shoppingTotal + " 元"
);
System.out.println(
"娛樂:"
+ entertainmentTotal
+ " 元"
);
}
public static void deleteExpense() {
if (expenses.size() == 0) {
System.out.println(
"目前沒有可以刪除的紀錄。"
);
return;
}
showExpenses();
System.out.print(
"請輸入要刪除的紀錄編號:"
);
int number =
scanner.nextInt();
scanner.nextLine();
if (number >= 1
&& number <= expenses.size()) {
Expense deleted =
expenses.remove(number - 1);
System.out.println(
"已刪除:"
+ deleted.getItem()
);
} else {
System.out.println(
"沒有這筆紀錄!"
);
}
}
public static void editExpense() {
if (expenses.size() == 0) {
System.out.println(
"目前沒有可以修改的紀錄。"
);
return;
}
showExpenses();
System.out.print(
"請輸入要修改的紀錄編號:"
);
int number =
scanner.nextInt();
scanner.nextLine();
if (number >= 1
&& number <= expenses.size()) {
Expense expense =
expenses.get(number - 1);
System.out.print(
"請輸入新的消費項目:"
);
String item =
scanner.nextLine();
System.out.print(
"請輸入新的消費金額:"
);
int amount =
scanner.nextInt();
String category =
chooseCategory();
expense.setItem(item);
expense.setAmount(amount);
expense.setCategory(category);
System.out.println(
"修改成功!"
);
} else {
System.out.println(
"沒有這筆紀錄!"
);
}
}
public static void setBudget() {
System.out.println();
System.out.println(
"===== 設定預算 ====="
);
System.out.print(
"請輸入預算金額:"
);
budget =
scanner.nextInt();
scanner.nextLine();
System.out.println(
"預算設定成功:"
+ budget
+ " 元"
);
}
public static void checkBudget() {
System.out.println();
System.out.println(
"===== 預算狀況 ====="
);
if (budget <= 0) {
System.out.println(
"尚未設定預算。"
);
return;
}
int total = 0;
for (int i = 0;
i < expenses.size();
i++) {
total +=
expenses
.get(i)
.getAmount();
}
System.out.println(
"目前預算:"
+ budget
+ " 元"
);
System.out.println(
"目前支出:"
+ total
+ " 元"
);
if (total > budget) {
int over =
total - budget;
System.out.println(
"已超出預算 "
+ over
+ " 元!"
);
} else {
int remaining =
budget - total;
System.out.println(
"剩餘預算:"
+ remaining
+ " 元"
);
}
}
public static void saveExpenses() {
try {
FileWriter writer =
new FileWriter(
"expenses.txt"
);
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
writer.write(
expense.getItem()
+ ","
+ expense.getAmount()
+ ","
+ expense.getCategory()
+ "\n"
);
}
writer.close();
System.out.println(
"記帳資料已儲存!"
);
} catch (IOException e) {
System.out.println(
"檔案儲存失敗!"
);
}
}
public static void loadExpenses() {
try {
File file =
new File("expenses.txt");
Scanner fileScanner =
new Scanner(file);
while (
fileScanner.hasNextLine()
) {
String line =
fileScanner.nextLine();
String[] data =
line.split(",");
String item =
data[0];
int amount =
Integer.parseInt(
data[1]
);
String category =
data[2];
Expense expense =
new Expense(
item,
amount,
category
);
expenses.add(expense);
}
fileScanner.close();
System.out.println(
"記帳資料讀取成功!"
);
} catch (Exception e) {
System.out.println(
"目前沒有已儲存的記帳資料。"
);
}
}
}

實際測試

第一次執行程式時新增:

  1. 早餐|60 元|飲食
  2. 午餐|150 元|飲食
  3. 捷運|30 元|交通

然後選擇:

  1. 儲存記帳資料

顯示:

記帳資料已儲存!

這時 expenses.txt:

早餐,60,飲食
午餐,150,飲食
捷運,30,交通

接著把 Java 程式完全關閉。

再重新執行。

程式一開始就會執行:

loadExpenses();

顯示:

記帳資料讀取成功!

現在選擇:

  1. 查看紀錄

就可以再次看到:

===== 消費紀錄 =====

  1. 早餐|60 元|飲食
  2. 午餐|150 元|飲食
  3. 捷運|30 元|交通

成功!

這次就算重新啟動 Java,資料也還存在。

今天的新語法整理

今天主要多了四個重要觀念。

File:

File file =
new File("expenses.txt");

代表指定要操作的檔案。

hasNextLine():

fileScanner.hasNextLine()

檢查檔案還有沒有下一行。

split():

line.split(",");

把:

早餐,60,飲食

拆成:

早餐
60
飲食

Integer.parseInt():

Integer.parseInt("60")

把:

String "60"

轉換成:

int 60

這些東西組合起來,就完成了檔案讀取。

一個目前版本的小問題

現在資料必須手動選:

  1. 儲存記帳資料

才會存進檔案。

所以如果今天新增:

晚餐|180 元|飲食

卻直接選:

  1. 離開

沒有先儲存,那麼「晚餐」這筆資料還是會消失。

這代表目前:

新增資料
↓
一定要記得按儲存
↓
才會寫進檔案

使用上還不是很方便。

下一步可以讓程式自動儲存。

Day 22 學習心得

今天完成了昨天 FileWriter 的另一半,學會使用 File 和 Scanner 把 expenses.txt 裡面的資料重新讀回程式。

一開始我以為把檔案讀進來就完成了,但實際上還需要利用 split() 把每一行拆成項目、金額和分類,再利用 Integer.parseInt() 把金額從 String 轉成 int,最後重新建立 Expense Object 並放回 ArrayList。

做到這一步之後,我覺得記帳系統有一個很明顯的進步。之前的資料只存在程式執行期間,現在即使關閉程式再重新開啟,消費紀錄還是可以重新載入。

也讓我第一次比較清楚感受到「程式中的資料」和「真正儲存在檔案中的資料」是不一樣的。


上一篇
File 檔案寫入:讓記帳紀錄可以真正保存
下一篇
自動儲存:新增、修改、刪除後直接保存資料
系列文
Java 程式設計與實作:30 天打造個人記帳系統 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言