昨天 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(
"目前沒有已儲存的記帳資料。"
);
}
}
}
實際測試
第一次執行程式時新增:
然後選擇:
顯示:
記帳資料已儲存!
這時 expenses.txt:
早餐,60,飲食
午餐,150,飲食
捷運,30,交通
接著把 Java 程式完全關閉。
再重新執行。
程式一開始就會執行:
loadExpenses();
顯示:
記帳資料讀取成功!
現在選擇:
就可以再次看到:
===== 消費紀錄 =====
成功!
這次就算重新啟動 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
這些東西組合起來,就完成了檔案讀取。
一個目前版本的小問題
現在資料必須手動選:
才會存進檔案。
所以如果今天新增:
晚餐|180 元|飲食
卻直接選:
沒有先儲存,那麼「晚餐」這筆資料還是會消失。
這代表目前:
新增資料
↓
一定要記得按儲存
↓
才會寫進檔案
使用上還不是很方便。
下一步可以讓程式自動儲存。
Day 22 學習心得
今天完成了昨天 FileWriter 的另一半,學會使用 File 和 Scanner 把 expenses.txt 裡面的資料重新讀回程式。
一開始我以為把檔案讀進來就完成了,但實際上還需要利用 split() 把每一行拆成項目、金額和分類,再利用 Integer.parseInt() 把金額從 String 轉成 int,最後重新建立 Expense Object 並放回 ArrayList。
做到這一步之後,我覺得記帳系統有一個很明顯的進步。之前的資料只存在程式執行期間,現在即使關閉程式再重新開啟,消費紀錄還是可以重新載入。
也讓我第一次比較清楚感受到「程式中的資料」和「真正儲存在檔案中的資料」是不一樣的。