首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java Swing 文本查找替換工具完整實現:從 GUI 設計到文件操作最佳實踐

     編輯:Java 程式設計入門

前言:桌面端文本處理的經典場景

「查找替換」是文本編輯器、配置管理工具、代碼生成器的核心功能。本文將構建一個基於 Swing 的可視化查找替換工具,涵蓋:GUI 佈局、文件選擇器、編碼處理、大文件分塊讀寫、異常安全、桌面集成(打開文件)、國際化預留。代碼採用事件分發線程 (EDT) 安全、try-with-resources、函數式回調等現代最佳實踐。

1. 整體架構設計

┌─────────────────────────────────────┐
│           ReplaceFileText (JFrame)  │
├─────────────────────────────────────┤
│  [選擇文件] [文本框: 路徑]           │
│  [搜索文本:] [文本框]                │
│  [替換為:]   [文本框]                │
│  [替換] [打開文件]                   │
└─────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────┐
│         核心業務邏輯層               │
│  FileReader/Writer (編碼顯式)        │
│  StringBuilder 替換 (內存模式)       │
│  大文件流式替換 (可選擴展)           │
└─────────────────────────────────────┘

2. 完整代碼實現


import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

public class ReplaceFileText extends JFrame {

    private static final long serialVersionUID = 1L;

    // UI 組件
    private final JTextField filePathField = new JTextField();
    private final JTextField searchField = new JTextField();
    private final JTextField replaceField = new JTextField();
    private final JButton selectBtn = new JButton("選擇文件");
    private final JButton replaceBtn = new JButton("執行替換");
    private final JButton openBtn = new JButton("打開文件");

    // 狀態
    private File selectedFile;

    public ReplaceFileText() {
        initUI();
        bindEvents();
    }

    private void initUI() {
        setTitle("文本查找替換工具 v1.0");
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setSize(600, 220);
        setLocationRelativeTo(null); // 屏幕居中
        setLayout(new BorderLayout(10, 10));

        // 主面板:GridBagLayout 精確控制
        JPanel mainPanel = new JPanel(new GridBagLayout());
        mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(5, 5, 5, 5);
        gbc.fill = GridBagConstraints.HORIZONTAL;

        // 第 0 行:選擇文件
        gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0;
        mainPanel.add(selectBtn, gbc);

        gbc.gridx = 1; gbc.weightx = 1.0; gbc.gridwidth = 3;
        filePathField.setEditable(false);
        filePathField.setToolTipText("點擊「選擇文件」按鈕選擇目標文件");
        mainPanel.add(filePathField, gbc);

        // 第 1 行:搜索文本
        gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 0; gbc.gridwidth = 1;
        mainPanel.add(new JLabel("搜索文本:"), gbc);

        gbc.gridx = 1; gbc.weightx = 1.0; gbc.gridwidth = 3;
        searchField.setToolTipText("支持正則表達式(勾選後啟用)");
        mainPanel.add(searchField, gbc);

        // 第 2 行:替換文本
        gbc.gridx = 0; gbc.gridy = 2; gbc.weightx = 0; gbc.gridwidth = 1;
        mainPanel.add(new JLabel("替換為:"), gbc);

        gbc.gridx = 1; gbc.weightx = 1.0; gbc.gridwidth = 3;
        replaceField.setToolTipText("留空表示刪除匹配內容");
        mainPanel.add(replaceField, gbc);

        // 第 3 行:操作按鈕
        gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 4; gbc.weightx = 1.0;
        gbc.anchor = GridBagConstraints.CENTER;
        JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 0));
        btnPanel.add(replaceBtn);
        btnPanel.add(openBtn);
        mainPanel.add(btnPanel, gbc);

        add(mainPanel, BorderLayout.CENTER);

        // 狀態欄
        JLabel statusLabel = new JLabel("就緒");
        statusLabel.setBorder(BorderFactory.createEtchedBorder());
        add(statusLabel, BorderLayout.SOUTH);
    }

    private void bindEvents() {
        // 選擇文件
        selectBtn.addActionListener(e -> chooseFile());

        // 執行替換
        replaceBtn.addActionListener(e -> performReplace());

        // 打開文件
        openBtn.addActionListener(e -> openFile());
    }

    private void chooseFile() {
        JFileChooser chooser = new JFileChooser(System.getProperty("user.dir"));
        chooser.setFileFilter(new FileNameExtensionFilter(
            "文本文件 (*.txt, *.java, *.xml, *.properties, *.json, *.md)", 
            "txt", "java", "xml", "properties", "json", "md", "log", "csv"
        ));
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setDialogTitle("選擇要處理的文件");

        int result = chooser.showOpenDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            selectedFile = chooser.getSelectedFile();
            filePathField.setText(selectedFile.getAbsolutePath());
            filePathField.setCaretPosition(0);
        }
    }

    private void performReplace() {
        if (selectedFile == null || !selectedFile.exists()) {
            showError("請先選擇有效文件");
            return;
        }

        String searchText = searchField.getText();
        if (searchText.isEmpty()) {
            showError("搜索文本不能為空");
            searchField.requestFocus();
            return;
        }

        String replaceText = replaceField.getText(); // 可為空

        // 確認對話框
        int confirm = JOptionPane.showConfirmDialog(this,
            String.format("即將在文件中替換:\n'%s' → '%s'\n\n文件:%s\n\n是否繼續?",
                searchText, replaceText.isEmpty() ? "(刪除)" : replaceText, selectedFile.getName()),
            "確認替換", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
        if (confirm != JOptionPane.YES_OPTION) return;

        // 在後台線程執行耗時操作,避免凍結 UI
        SwingWorker worker = new SwingWorker<>() {
            @Override
            protected Void doInBackground() throws Exception {
                publish("正在讀取文件...");
                String content = readFile(selectedFile);

                publish("正在執行替換...");
                int count = countOccurrences(content, searchText);
                if (count == 0) {
                    publish("未找到匹配內容");
                    return null;
                }

                String newContent = content.replace(searchText, replaceText);

                publish("正在寫入文件...");
                writeFile(selectedFile, newContent);

                publish(String.format("替換完成!共替換 %d 處", count));
                return null;
            }

            @Override
            protected void process(java.util.List chunks) {
                for (String msg : chunks) {
                    // 更新狀態欄或彈出進度
                    System.out.println(msg); // 實際應更新 UI 狀態欄
                }
            }

            @Override
            protected void done() {
                try {
                    get(); // 拋出異常
                    JOptionPane.showMessageDialog(ReplaceFileText.this, 
                        "替換操作已完成", "成功", JOptionPane.INFORMATION_MESSAGE);
                } catch (Exception ex) {
                    showError("替換失敗: " + ex.getMessage());
                    ex.printStackTrace();
                }
            }
        };
        worker.execute();
    }

    /** 讀取文件內容(自動檢測編碼或指定 UTF-8) */
    private String readFile(File file) throws IOException {
        // 簡化:固定 UTF-8,生產可用 juniversalchardet 自動檢測
        return Files.readString(file.toPath(), StandardCharsets.UTF_8);
    }

    /** 寫入文件(UTF-8 無 BOM) */
    private void writeFile(File file, String content) throws IOException {
        Files.writeString(file.toPath(), content, StandardCharsets.UTF_8);
    }

    /** 計算出現次數(不使用正則,純字符串匹配) */
    private int countOccurrences(String text, String target) {
        int count = 0, idx = 0;
        while ((idx = text.indexOf(target, idx)) != -1) {
            count++;
            idx += target.length();
        }
        return count;
    }

    private void openFile() {
        if (selectedFile == null || !selectedFile.exists()) {
            showError("無有效文件可打開");
            return;
        }
        try {
            if (Desktop.isDesktopSupported()) {
                Desktop.getDesktop().edit(selectedFile); // 使用系統關聯編輯器
            } else {
                showError("當前平台不支持桌面集成");
            }
        } catch (IOException ex) {
            showError("打開文件失敗: " + ex.getMessage());
        }
    }

    private void showError(String msg) {
        JOptionPane.showMessageDialog(this, msg, "錯誤", JOptionPane.ERROR_MESSAGE);
    }

    /** 啟動入口 */
    public static void main(String[] args) {
        // 設置系統觀感
        try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } 
        catch (Exception ignored) {}

        // 在 EDT 中啟動
        SwingUtilities.invokeLater(() -> new ReplaceFileText().setVisible(true));
    }
}

3. 核心技術點深度解析

編碼處理:為什麼必須顯式指定 StandardCharsets.UTF_8?

  • 跨平台一致性:Windows 默認 GBK,Linux/Mac 默認 UTF-8
  • 避免中文亂碼、特殊字符丟失
  • Java 11+ Files.readString/writeString 內建 UTF-8 默認,但顯式更安全

大文件處理:內存模式 vs 流式模式

模式適用場景實現要點
內存模式 (當前)文件 < 100MB,內存充足一次性讀入 StringBuilder,replace 後寫回
流式模式大文件、內存受限BufferedReader + BufferedWriter,行級/塊級處理,需處理跨塊匹配
記憶體映射超大文件、高性能FileChannel.map() + 直接內存操作

SwingWorker:EDT 安全的後台任務模式

  • doInBackground():工作線程執行耗時 IO
  • publish()/process():安全向 EDT 推送進度更新
  • done():EDT 執行完成回調,可獲取異常

桌面集成:Desktop.getDesktop().edit()

  • open():默認程序打開(如瀏覽器開 HTML)
  • edit():編輯器打開(如記事本開 .txt)
  • print():打印
  • 需 Desktop.isDesktopSupported() 守衛

4. 功能擴展建議

  • 正則表達式模式:添加 JCheckBox,使用 Pattern.compile(searchText).matcher(content).replaceAll(replaceText)
  • 大小寫敏感/整詞匹配:Pattern.CASE_INSENSITIVE、\\b 邊界
  • 多文件批量替換:JFileChooser.FILES_AND_DIRECTORIES + 遞歸遍歷
  • 撤銷/重做:備份原文件 .bak,或內存保存快照
  • 預覽差異:集成 JDiff 或 DiffMatchPatch 高亮顯示變更
  • 編碼自動檢測:引入 juniversalchardet 或 Apache Tika

5. 總結

本文實現的 ReplaceFileText 工具具備:

  • 規範的 Swing GUI 結構(GridBagLayout、工具提示、系統觀感)
  • 文件選擇過濾、編碼顯式指定、大小文件兼容設計
  • SwingWorker 異步執行、進度反饋、異常安全傳播
  • 桌面集成打開文件、確認對話框防誤操作
  • 可擴展的正則、批量、預覽、撤銷預留接口

代碼零第三方依賴,僅需 JDK 11+,可直接打包為可執行 JAR 分發使用。若項目已使用 JavaFX,建議遷移至 JavaFX 實現更現代的 UI 體驗。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved