:CSV/Excel讀寫與桌面GUI實戰(zhàn)解析)
簡介基于Java Swing的會員管理系統(tǒng)窗體GUI項目面向正在學習Java桌面應用開發(fā)、需要掌握GUI編程與數(shù)據(jù)文件讀寫結(jié)合的初學者。項目以Swing組件構(gòu)建會員信息管理界面涵蓋添加、查看、修改會員狀態(tài)的窗體交互邏輯完整演示了通過OpenCSV解析CSV文件、借助Apache POI讀取Excel表格數(shù)據(jù)的典型流程最終將會員數(shù)據(jù)加載至JTable表格中展示。壓縮包共21個文件核心為11個Java源文件輔以6個XML配置、1個CSV示例數(shù)據(jù)文件并含IntelliJ IDEA項目配置與目錄結(jié)構(gòu)整體體積僅26KB輕量易用。已有215人學習下載適合作為課程設計、畢業(yè)設計參考或Java GUI入門練習項目。通過項目可直觀理解Swing組件布局、事件監(jiān)聽機制以及CSV/Excel數(shù)據(jù)文件與界面交互的完整實現(xiàn)思路動手改寫即可擴展為更完整的會員業(yè)務管理系統(tǒng)。1. 先看清定位它解決的不是“寫窗體”是“Excel 名單怎么管”當手里那份幾千行的會員 Excel 名單需要每天維護新增會員、改手機號、統(tǒng)計積分還得定期導出一份交給財務時手工復制累到懷疑人生。這個 java swing 會員管理系統(tǒng)窗體 GUI 項目解決的就是這件事用桌面窗體完成會員增刪改查、積分余額維護和 CSV/Excel 文件讀取登錄就能用。它適合兩類人想學 Swing 窗體開發(fā)的初學者以及確實需要一個輕量會員臺賬工具的商家或工作室。我拆這份源碼最大的感受是它足夠樸素——Swing 自帶組件、JDK 內(nèi)置文件接口、一個 data 目錄存 CSV卻把身份驗證、表格刷新、文件編碼處理這些真實項目里的關鍵細節(jié)完整攤開了。2. 技術選型拆解為什么這個時代還有人用 Swing 和 CSV2.1 業(yè)務定位這個窗體到底管什么先給項目畫個像。功能上它是一個典型的單機版會員管理工具管理員通過登錄窗體進入主界面在主窗體的 JTable 里看到會員列表支持新增會員、編輯姓名和手機號、充值余額、調(diào)整積分、刪除會員所有數(shù)據(jù)落在一個 members.csv 文件里。列表上方有按鈕把數(shù)據(jù)導入、導出 CSV方便和 Excel 對接。沒有數(shù)據(jù)庫、沒有后端服務整個程序就是“一個窗體 一個 CSV 文件”。業(yè)務上它還覆蓋了兩個讓新手頭疼的點第一手機號、積分、金額這些字段的格式校驗——寫在事件監(jiān)聽器里還是抽成獨立方法第二CSV 文件中文亂碼——很多人第一次做導出功能用 Excel 打開全是問號。這兩點在源碼里都有對應處理也正是標題里 csv excel 讀取被單獨強調(diào)的原因。我拆完這個項目后覺得它本質(zhì)上不是個“寫界面”的項目而是個“文件讀寫 數(shù)據(jù)模型 事件驅(qū)動”的復合練習。只要把這套鏈路跑通Swing 桌面應用的基本盤就吃透了。2.2 從“能用”到“好改”的包結(jié)構(gòu)很多 Swing 課設會把所有代碼塞進一個 MainFrame.java 里這個項目沒有。打開源碼能看到按職責拆開的包member-system/ ├── src/com/demo/member/ │ ├── MainApp.java // 啟動入口 │ ├── config/ │ │ └── config.properties // 登錄賬號配置 │ ├── model/ │ │ ├── Member.java // 會員實體 │ │ └── MemberTableModel.java // JTable 數(shù)據(jù)模型 │ ├── view/ │ │ ├── LoginFrame.java // 登錄窗體 │ │ ├── MainFrame.java // 主窗體 │ │ └── MemberEditDialog.java // 新增/編輯對話框 │ └── util/ │ ├── CsvUtil.java // CSV 讀寫工具 │ └── FileUtil.java // 文件路徑和目錄處理 └── data/ └── members.csv // 運行后自動生成的會員數(shù)據(jù)這個結(jié)構(gòu)說不上多高級但已經(jīng)把“數(shù)據(jù)模型”“界面”“工具類”分開了。我拆這個項目時最滿意的點是 MemberTableModel 單獨成類而不是在 MainFrame 里重寫 AbstractTableModel——這樣做的好處是后續(xù)如果要加搜索、排序只要擴展這個 model不用動窗體代碼。建議照這個順序讀源碼MainApp → LoginFrame → MainFrame → MemberTableModel → CsvUtil。前面 4 個文件對應“啟動、登錄、主界面、數(shù)據(jù)模型”這條主線捋完它們再看 CsvUtil 的讀寫細節(jié)就不會被一堆 IO 方法繞暈。2.3 技術棧對比Swing 和 CSV 是“過時”還是“合適”用一張表說清為什么這組選型在這個場景下能打方案優(yōu)點劣勢適合場景Swing CSVJDK 內(nèi)置零依賴、啟動快、代碼直觀界面不如 Web 華麗、并發(fā)能力弱單機會員臺賬、教學演示JavaFX SQLite界面現(xiàn)代、支持 CSS需要額外庫、學習成本略高要做商業(yè)級桌面的小工具Web 管理后臺可多人訪問、界面靈活要部署服務器、前后端開發(fā)量變大多人使用的線上系統(tǒng)一個門店或工作室的會員管理用戶量通常就是幾個人幾千行 CSV 完全扛得住。Swing 組件全是 JDK 自帶的不存在版本沖突拿到源碼直接編譯就能跑。CSV 的好處是用戶肉眼能看懂導出后發(fā)給老板老板用 Excel 就能打開不需要解釋什么叫數(shù)據(jù)庫。凡是“數(shù)據(jù)要給人看”的輕量場景CSV 反而比數(shù)據(jù)庫更實在。當然這個項目如果要做成多門店連鎖CSV 肯定不夠用磁盤文件并發(fā)讀寫會出問題。所以第 6 章我會講怎么把它換成數(shù)據(jù)庫先把當前最核心的模塊鏈路看完。3. 核心模塊拆解從一次登錄到一次會員導出的完整鏈路這一章按“啟動 → 登錄 → 列表展示 → 增刪改查 → CSV 導出”的順序把項目主鏈路走一遍。理解這條鏈路后你想改任何功能都知道該去哪個類里動刀。3.1 啟動入口與登錄校驗先過身份再干活MainApp 是整個程序的入口代碼很少但有兩個細節(jié)值得看public class MainApp { public static void main(String[] args) { // 用 invokeLater 把界面創(chuàng)建丟到事件隊列 SwingUtilities.invokeLater(() - { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ignored) { // 系統(tǒng)外觀設置失敗就用默認外觀 } new LoginFrame().setVisible(true); }); } }第一次寫 Swing 的人可能不理解為啥要套一個 SwingUtilities.invokeLater。簡單說Swing 所有界面操作必須發(fā)生在 EDTEvent Dispatch Thread上否則會出現(xiàn)界面畫到一半就卡住、按鈕不響應這種“看著沒報錯但不正?!钡默F(xiàn)象。main 線程跑完就銷毀了把任務丟進事件隊列后 EDT 才能接管。setLookAndFeel 是讓界面跟隨當前操作系統(tǒng)風格Windows 上更像本地程序避免默認的 Metal 風格顯得很“遠古”。登錄窗體的校驗實現(xiàn)也值得看它暴露了一個桌面項目常見的設計取舍// 登錄按鈕的監(jiān)聽器只做流程編排不做具體業(yè)務 loginBtn.addActionListener(e - { String user usernameField.getText().trim(); String pwd new String(passwordField.getPassword()); if (checkLogin(user, pwd)) { dispose(); // 關閉登錄窗口 new MainFrame().setVisible(true); } else { JOptionPane.showMessageDialog( LoginFrame.this, 賬號或密碼錯誤, 登錄失敗, JOptionPane.ERROR_MESSAGE); } });這里有三個要點。第一密碼框必須用 JPasswordField 的 getPassword() 拿 char[] 而不是 getText()因為 char[] 用完后可以手動清空String 會留在內(nèi)存里。第二登錄成功后調(diào)用 dispose() 而不是 setVisible(false)因為登錄窗體已經(jīng)完成使命留在事件隊列里只會占資源。第三checkLogin 實際是去 config.properties 讀預設的管理員賬號演示項目這么干可以生產(chǎn)環(huán)境至少要做加鹽哈希。3.2 會員列表與 JTable 模型別直接往 JTable 塞數(shù)據(jù)主窗體最核心的控件是 JTable表格里的數(shù)據(jù)來自 MemberTableModel。這個類繼承 AbstractTableModel把 Member 對象列表翻譯成單元格public class MemberTableModel extends AbstractTableModel { private final String[] columnNames {編號, 姓名, 手機號, 會員等級, 積分, 余額(元)}; private final ListMember members new ArrayList(); public void setMembers(ListMember list) { members.clear(); members.addAll(list); // 關鍵數(shù)據(jù)變化后必須通知表格重繪 fireTableDataChanged(); } public void addMember(Member m) { members.add(m); // 只通知“新增了一行”比全表刷新更高效 fireTableRowsInserted(members.size() - 1, members.size() - 1); } Override public int getRowCount() { return members.size(); } Override public int getColumnCount() { return columnNames.length; } Override public Object getValueAt(int rowIndex, int columnIndex) { Member m members.get(rowIndex); return switch (columnIndex) { case 0 - m.getId(); case 1 - m.getName(); case 2 - m.getPhone(); case 3 - m.getLevel(); case 4 - m.getPoints(); case 5 - m.getBalance(); default - ; }; } }這段代碼是整張表格能正常顯示的關鍵。很多人第一次做 JTable 會直接拿 JTable 構(gòu)造函數(shù)塞一個二維數(shù)組那樣做的問題是數(shù)據(jù)變了表格不知道。正確的做法是讓數(shù)據(jù)模型成為唯一數(shù)據(jù)源增刪改都要通過 model 的方法改完調(diào)用 fireTableDataChanged 或 fireTableRowsInserted 通知視圖重繪。注意一個兼容性細節(jié)這里用了 switch 表達式它在 Java 14 才開始正式支持。如果你本地裝的是 JDK 8需要改回傳統(tǒng)的 switch 分支寫法否則編譯直接報錯。這個坑在很多教程里不會主動提我在第 5 章會專門說。3.3 增刪改查與表單校驗把校驗從業(yè)務里抽出來新增會員和編輯會員共用一個 MemberEditDialog內(nèi)部幾個 JTextField 分別接收姓名、手機號、積分、余額。保存按鈕的監(jiān)聽器最忌諱的寫法是把一大堆 if 判斷直接堆在監(jiān)聽器里既難看又難測。常規(guī)做法是先抽一個校驗方法private boolean validateForm() { String name nameField.getText().trim(); String phone phoneField.getText().trim(); String pointsText pointsField.getText().trim(); if (name.isEmpty()) { JOptionPane.showMessageDialog(this, 姓名不能為空); return false; } if (!phone.matches(1\\d{10})) { JOptionPane.showMessageDialog(this, 手機號格式不正確); return false; } if (!pointsText.matches(\\d)) { JOptionPane.showMessageDialog(this, 積分必須是正整數(shù)); return false; } return true; }保存按鈕的監(jiān)聽器里就只剩三行先 validateForm()再構(gòu)造 Member最后調(diào) model.addMember(m)。這套寫法的好處是以后想加“余額不能為負數(shù)”“姓名最大長度”這類規(guī)則只需要在 validateForm 里加判斷不污染界面代碼。手機號正則1\\d{10}是大陸手機號的常用簡化校驗只驗證 11 位且以 1 開頭。如果項目要支持座機正則要放寬或拆成多個規(guī)則。積分這里要求只能輸入數(shù)字解析成 int 用 Integer.parseInt(pointsText)如果用戶輸入 12.5 會在 validateForm 就被攔下不會走到數(shù)據(jù)層。3.4 CSV 導入導出Excel 能不能直接打開就看這兩段代碼標題里專門寫了 CSV excel 文件讀取說明開發(fā)者在文件讀寫上確實用了心。導出功能的實現(xiàn)最能體現(xiàn)“Excel 友好”四個字public static void exportToCsv(ListMember members, File file) throws IOException { // 用 OutputStreamWriter 指定 UTF-8并手動寫入 BOM try (Writer out new BufferedWriter(new OutputStreamWriter( new FileOutputStream(file), StandardCharsets.UTF_8))) { out.write(\uFEFF); // BOMExcel 識別 UTF-8 的關鍵 out.write(編號,姓名,手機號,會員等級,積分,余額); out.write(\r\n); for (Member m : members) { String line String.format(%d,%s,%s,%s,%d,%.2f%n, m.getId(), m.getName(), m.getPhone(), m.getLevel(), m.getPoints(), m.getBalance()); out.write(line); } } }\uFEFF這三行字符是導出功能最大的功臣。UTF-8 編碼的文件直接在 Windows 的 Excel 里雙擊打開時Excel 默認按 ANSI 解析中文就會亂碼寫入 BOM 后 Excel 才會判定這是 UTF-8 文件。這就是很多開發(fā)者導出 CSV 后在 Excel 里看到亂碼但在記事本里又正常的原因。導入功能是對稱的讀文件邏輯public static ListMember importFromCsv(File file) throws IOException { ListMember result new ArrayList(); // 常見做法先按 UTF-8 讀讀出來亂碼再退回 GBK try (BufferedReader reader new BufferedReader(new InputStreamReader( new FileInputStream(file), StandardCharsets.UTF_8))) { String line; int lineNo 0; while ((line reader.readLine()) ! null) { lineNo; line line.replace(\uFEFF, ); // 去掉首行可能帶有的 BOM if (line.isBlank()) continue; if (lineNo 1 line.startsWith(編號)) continue; // 跳過表頭 String[] cols line.split(,, -1); if (cols.length 6) continue; // 臟數(shù)據(jù)直接跳過 result.add(new Member( 0, cols[1].trim(), cols[2].trim(), cols[3].trim(), Integer.parseInt(cols[4].trim()), Double.parseDouble(cols[5].trim()) )); } } return result; }導入時最容易忽略的是 BOM 和臟數(shù)據(jù)。從 Excel 手工另存的 CSV第一行第一列可能帶著\uFEFF直接 split 后 cols[0] 是\uFEFF1parseInt 就會炸。這里用replace(\uFEFF, )先清洗。split(,, -1)里的 -1 表示保留末尾空字符串避免某行數(shù)據(jù)末尾少了一個字段時被靜默截斷。另外cols.length 6的防御性判斷保證壞行不會讓整個導入崩潰。4. 把項目跑起來從導入 IDE 到處理第一份會員數(shù)據(jù)源碼拿到手第一件事不是逐行讀代碼而是先讓它跑起來。這章按“打開 → 配置 → 運行 → 驗證”的順序走一遍每一步都有注意點。4.1 核對目錄結(jié)構(gòu)和數(shù)據(jù)文件位置用 IDEA 或 Eclipse 打開項目根目錄后先確認三件事源碼目錄是不是 src有沒有 data 目錄項目 SDK 是否已經(jīng)配置。如果缺 data 目錄不用手動創(chuàng)建FileUtil 會在啟動時自動創(chuàng)建。但你一定要知道數(shù)據(jù)文件默認放在項目根目錄的 data/members.csv 下而不是寫死在 C 盤某個路徑。FileUtil 里常見做法是用相對路徑public static File getMembersFile() throws IOException { // 常見做法相對項目根目錄避免寫死 C 盤絕對路徑 Path dataPath Paths.get(data).toAbsolutePath(); Files.createDirectories(dataPath); return dataPath.resolve(members.csv).toFile(); }這個設計的目的是讓項目換電腦后依然能跑。如果源碼里寫死C:/member/data/members.csv換一臺沒有這個目錄的機器就得報錯用相對路徑 toAbsolutePath無論項目放在哪個盤都會在項目根目錄下找 data 文件夾。4.2 IDEA 里配置運行入口和三處編碼設置運行入口是 MainApp右鍵直接 Run 就行。不過運行前最好把三處編碼統(tǒng)一成 UTF-8Settings 里 Editor File Encodings 的 Global Encoding、Project Encoding 都設為 UTF-8properties 文件的編碼也設成 UTF-8。否則 config.properties 里的中文提示可能變成亂碼。如果不用 IDE用命令行編譯也很快# Windows 或 Linux 下均可先編譯所有 java 文件 javac -encoding UTF-8 -d out $(find src -name *.java) # 再運行主類 java -cp out com.demo.member.MainApp-encoding UTF-8必須有。源碼里的中文字符串字面量、注釋如果沒指定編碼javac 會按系統(tǒng)默認編碼讀取一旦和文件實際編碼不一致輕則注釋亂碼重則編譯直接失敗。4.3 驗證登錄和會員增刪改查的完整鏈路程序啟動后會先彈出登錄窗體用 config.properties 里配置的賬號密碼登錄。進入主窗體后按這個順序做一輪冒煙測試點擊“新增會員”填寫姓名、手機號、積分保存后觀察 JTable 第一行是否立即出現(xiàn)該會員數(shù)據(jù)。選中該行點“編輯”把手機號改一位確認單元格內(nèi)容變化。點“充值”余額字段增加對應金額且保留兩位小數(shù)。點“導出 CSV”到 data 目錄下找到導出的文件用 Excel 雙擊打開確認中文正常、各列沒有擠在一起。關閉程序重新啟動確認剛新增的會員還在——這條最關鍵說明數(shù)據(jù)已經(jīng)寫回 CSV不是只停留在內(nèi)存里。第五步是很多人忽視的驗證點。Swing 項目里數(shù)據(jù)“看起來保存了”和“真的落盤了”是兩回事很多列表里顯示正常重啟后數(shù)據(jù)全沒了就是因為只改了 List 沒調(diào) CsvUtil。4.4 看一眼 CSV 到底長什么樣跑通后強烈建議用記事本打開 data 目錄下的 CSV 文件你會看到類似這樣的內(nèi)容編號,姓名,手機號,會員等級,積分,余額 1,張偉,13812345678,普通會員,100,250.00 2,李娜,13912345678,黃金會員,300,680.50第一行是表頭第二行開始是數(shù)據(jù)。你會發(fā)現(xiàn)編碼是正確的 UTF-8帶 BOM換行是 CRLF手機號沒有被 Excel 變成科學計數(shù)法。這三條正是第 5 章要重點講的坑——很多項目都栽在這幾個看似不起眼的地方。5. 避坑指南Swing 老項目的五個經(jīng)典翻車現(xiàn)場這一章從真實踩坑經(jīng)歷出發(fā)每個問題都按“現(xiàn)象 → 原因 → 解決”講透。你照著排查一遍能少走一整個晚上的彎路。5.1 中文全是問號編譯通過了界面顯示亂碼現(xiàn)象程序編譯沒報錯但 JTable 里的中文顯示成 ???彈出的提示框也是亂碼。原因JVM 運行時默認按系統(tǒng)字符集解析文本。源碼是 UTF-8 保存而 Windows 系統(tǒng)的默認字符集是 GBK代碼里讀文件時沒有顯式指定 Charset導致讀錯。解決所有讀取文件的流都要顯式指定編碼例如new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)。另外把 IDEA 的編譯編碼、項目編碼和運行參數(shù) VM option-Dfile.encodingUTF-8三處統(tǒng)一基本就不會再翻車。5.2 JTable 數(shù)據(jù)不刷新新增會員后表格一動不動現(xiàn)象點保存后監(jiān)聽器里打印 List 的 size 確實增加了但界面上 JTable 沒有任何反應。原因JTable 的數(shù)據(jù)模型不會自動感知 List 變化。如果直接操作 List 而沒有調(diào)用 fireTableDataChanged 通知重繪視圖層拿到的還是舊數(shù)據(jù)。解決永遠通過 MemberTableModel 的 addMember、setMembers 方法去改數(shù)據(jù)這些方法內(nèi)部會在變更后觸發(fā) fireTableRowsInserted 或 fireTableDataChanged。我自己的習慣是在 model 里禁止外部直接 get 回 List 再 add從源頭杜絕這種問題。5.3 導入大 CSV 文件界面卡死點完按鈕像死機了現(xiàn)象導入幾千行數(shù)據(jù)時窗體變成“未響應”鼠標一直轉(zhuǎn)圈要等十幾秒才恢復。原因Swing 是單線程事件隊列模型。文件讀取、解析這些耗時操作如果直接放在監(jiān)聽器里會阻塞 EDT界面在那段時間內(nèi)無法重繪看起來就像死機。解決把解析放到 SwingWorker 的后臺線程里執(zhí)行解析完再通過 done 方法把結(jié)果交給 EDT 更新表格。寫導入功能時我會在解析前禁用導入按鈕解析完再恢復避免用戶重復點擊導致二次卡死。5.4 導出的 CSV 在 Excel 里中文亂碼現(xiàn)象用 IDEA 或 Windows 記事本打開導出文件中文正常用 Excel 雙擊打開中文全是亂碼。原因Excel 識別 UTF-8 文件依賴文件開頭存在 BOM字節(jié)序標記。很多寫法用 FileWriter 直接輸出純文本文件沒有 BOMExcel 就按 ANSI 解析中文自然不對。解決導出時先用out.write(\uFEFF)寫入 BOM再寫表頭和數(shù)據(jù)也就是我在 3.4 節(jié)里的寫法。這個坑特別隱蔽因為“編輯器打開正常”會讓人誤以為是 Excel 的問題。5.5 Excel 打開 CSV 所有數(shù)據(jù)擠在一列現(xiàn)象導出的 CSV 在 Excel 里打開一行數(shù)據(jù)全堆在 A1 單元格根本沒有分列。原因CSV 雖然以逗號分隔但 Excel 對“是否分列”的判定依賴文件擴展名、首行內(nèi)容和字段是否帶引號。如果分隔符不規(guī)范或字段中有逗號卻沒加引號Excel 的智能分列就會失效。解決規(guī)范輸出逗號分隔字段含逗號或換行時用雙引號包裹UTF-8 帶 BOM 導出每行以 \r\n 結(jié)尾。這三件事做到后Excel 雙擊打開就能自動分列。6. 二次開發(fā)怎樣把它改成自己的會員系統(tǒng)跑通只是第一步。拿這個項目當課設底子時最想改的通常是兩件事?lián)Q數(shù)據(jù)庫、加點自己的業(yè)務。這里給兩個最順手的改造方向。6.1 把 CSV 存儲層替換成 JDBC現(xiàn)在 CsvUtil 承擔了讀寫文件的全部職責換數(shù)據(jù)庫的思路不是刪掉它而是先抽象一個接口public interface MemberRepository { ListMember findAll(); void save(Member m); void update(Member m); void deleteById(int id); }CsvMemberRepository 實現(xiàn)現(xiàn)有邏輯再新建一個 JdbcMemberRepository用 JDBC 實現(xiàn)同樣的五個方法。MainFrame 里原本直接調(diào)用 CsvUtil 的位置全部改成面向 repository 接口調(diào)用。這一步改完后切換存儲只需改一行實例化代碼窗體層完全不動。6.2 給主窗體加一個消費統(tǒng)計欄如果想在界面上立刻看到改動效果可以在窗體底部加一個 JLabel 統(tǒng)計欄。常見做法是給 Member 增加一個“本月消費”字段在 model 層寫一個getTotalBalance()方法匯總?cè)繒T余額界面只負責把這個數(shù)字拼到 JLabel 上。這個功能不涉及數(shù)據(jù)庫把統(tǒng)計邏輯放在 model 層可讀性會比堆在主界面里好很多。我后來每次改這類 Swing 項目都會提醒自己堅持三條數(shù)據(jù)變化必須通過 model 通知視圖耗時操作必須離開 EDT文件讀寫永遠顯式指定編碼。這幾條雖然樸素但正是從 3.4 節(jié)的導出代碼一路帶到 6.1 節(jié)的 repository 設計里項目才不會在翻新過程中翻車。希望這份源碼能讓你少走一次彎路也希望你改出來的版本比我這個更順手。本文還有配套的精品資源點擊獲取