Java: WordCloud

 项目结构:

image

 

/**
 * encoding: utf-8
 * 版权所有 2026 ©涂聚文有限公司 ®
 * 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
 * 描述:
 * Author    : geovindu,Geovin Du 涂聚文.
 * IDE       : IntelliJ IDEA 2024.3.6 Java 21
 * # database  : Oracle21c,MySQL 9.0,SQL Server 2019,PostgreSQL 17.1 Neo4j
 * # OS        : window10
 * Datetime  : 2026 - 2026/9/5 - 7:55
 * User      : geovindu
 * Product   : IntelliJ IDEA
 * Project   : JavaWordCloud
 * File      : ChineseWordCloud.java
 * explain   : 学习  类
 **/

// ============================================================
// 中文词云生成器(Java)— 词云引擎类
// 对标 Python wordcloud 示例(alice 蒙版词云):
//   - 读取中文文本 alice2.txt(UTF-8)
//   - Java 中文分词(com.huaban:jieba-analysis,Python jieba 的 Java 移植)
//   - 停用词过滤(工作/就是/个人/没有/村民委员会/said 等)
//   - 蒙版图片 alice_mask.png(深色人像 = 文字填充区,白色背景 = 禁区)
//   - 自研像素级词云布局(对标 Python wordcloud:
//     中心区域优先、字号按词频缩放、放不下自动减小字号重试)
//   - 自定义字体:方正小篆体
//   - 白色背景 + 钢蓝色轮廓(contour_width=3, contour_color=steelblue)
//   - 输出 chinese_wordcloud2.png
// 本类只含引擎逻辑,程序入口在 Main.java。
// 用法:设置字段后调用 generate(),或在 Main.java 中配置后运行。
// Author : geovindu, Geovin Du 涂聚文
// ============================================================
import com.huaban.analysis.jieba.JiebaSegmenter;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.regex.Pattern;

public class ChineseWordCloud {

    // ==================== 路径配置(默认值,可按需修改) ====================
    // 文本文件(对应 Python 的 alice2.txt)
    public static String textFile = "alice2.txt";
    // 蒙版图片(对应 Python 的 alice_mask.png)
    public static String maskFile = "alice_mask.png";
    // 停用词文件(可选,一行一个词;不存在则用内置停用词)
    public static String stopWordsFile = "stopwords.txt";
    // 方正小篆体字体文件(用户级安装字体,Python 已验证可用的路径)
    // 可通过环境变量 WORDCLOUD_FONT 覆盖,便于测试/切换字体,无需改代码
    public static String fontFile = System.getenv().getOrDefault("WORDCLOUD_FONT",
            "C:\\Users\\geovindu\\AppData\\Local\\Microsoft\\Windows\\Fonts\\方正小篆体.ttf");
    // 输出图片(对应 Python 的 chinese_wordcloud2.png)
    public static String outputFile = "chinese_wordcloud2.png";

    // ==================== 布局参数(对应 Python wordcloud 的关键参数) ====================
    public static int maxWords = 2000;        // max_words=2000
    public static double preferHorizon = 0.9; // prefer_horizontal=0.9:约 10% 文字竖向
    public static double minFontSize = 10.0;  // 最小字号
    public static double maxFontRatio = 0.25; // 最大字号 = 画布短边 * 该比例
    public static int tryPerRound = 80;       // 每个字号尝试的随机落点数量
    public static double fontDecay = 0.93;    // 放不下时字号衰减系数(对标 python 逐像素减小)

    // 钢蓝色,对应 Python contour_color='steelblue'
    public static Color steelBlue = new Color(70, 130, 180);

    // 只保留中英文/数字,过滤纯标点
    static final Pattern validWordRegex = Pattern.compile("[\\x{4e00}-\\x{9fff}A-Za-z0-9]+");

    // 已放置词数(输出用)
    static int placedCount = 0;

    /** 词与频次 */
    static class WordFreq {
        String word;
        int count;

        WordFreq(String word, int count) {
            this.word = word;
            this.count = count;
        }
    }

    /** 蒙版网格:可绘制标记 + 尺寸 */
    static class MaskGrid {
        boolean[][] drawable;
        int width;
        int height;

        MaskGrid(boolean[][] drawable, int width, int height) {
            this.drawable = drawable;
            this.width = width;
            this.height = height;
        }
    }

    /** 文字栅格:像素偏移列表 + 宽高 */
    static class TextRaster {
        int[] ox; // 像素 x 偏移
        int[] oy; // 像素 y 偏移
        int n;    // 像素数
        int bw;   // 位图宽
        int bh;   // 位图高

        TextRaster(int[] ox, int[] oy, int n, int bw, int bh) {
            this.ox = ox;
            this.oy = oy;
            this.n = n;
            this.bw = bw;
            this.bh = bh;
        }

        /** 顺时针旋转 90°,实现竖排 */
        void rotate() {
            for (int i = 0; i < n; i++) {
                int nx = bh - 1 - oy[i];
                int ny = ox[i];
                ox[i] = nx;
                oy[i] = ny;
            }
            int t = bw;
            bw = bh;
            bh = t;
        }
    }

    // ============================================================
    // 公开入口
    // ============================================================

    /** 使用类字段配置生成词云(推荐在 Main.java 里设置字段后调用本方法) */
    public static void generate() throws Exception {
        // ========== 1. 读取中文文本(对应 Python: open(..., encoding='utf-8').read()) ==========
        File textFileObj = new File(textFile);
        if (!textFileObj.isFile()) {
            throw new RuntimeException("[错误] 找不到文本文件:" + textFile
                    + "\n请确认当前工作目录下存在该文本文件,"
                    + "或通过命令行参数指定正确路径:java Main <文本文件> <蒙版> <字体> <输出>");
        }
        String rawText = new String(Files.readAllBytes(textFileObj.toPath()), StandardCharsets.UTF_8);

        // ========== 2. Java 中文分词(对应 Python: jieba.lcut(text)) ==========
        JiebaSegmenter segmenter = new JiebaSegmenter();
        List<String> tokens = segmenter.sentenceProcess(rawText);

        // ========== 3. 停用词集合 ==========
        Set<String> stopwords = loadStopWords();

        // ========== 4. 统计词频 ==========
        Map<String, Integer> freq = new HashMap<String, Integer>();
        for (String token : tokens) {
            String w = token.trim();
            if (w.isEmpty()) {
                continue;
            }
            if (!validWordRegex.matcher(w).find()) {
                continue; // 纯标点符号
            }
            if (stopwords.contains(w)) {
                continue; // 停用词
            }
            Integer old = freq.get(w);
            freq.put(w, old == null ? 1 : old + 1);
        }
        if (freq.isEmpty()) {
            throw new RuntimeException("[错误] 词频统计为空,请检查文本内容与停用词。");
        }

        // 词频降序排列,取前 maxWords
        List<WordFreq> words = sortedWords(freq, maxWords);

        // ========== 5. 加载蒙版 → 可绘制网格(深色人像=可绘制,白色背景=禁区) ==========
        File maskFileObj = new File(maskFile);
        if (!maskFileObj.isFile()) {
            throw new RuntimeException("[错误] 找不到蒙版图片:" + maskFile
                    + "\n请确认当前工作目录下存在该图片文件。");
        }
        MaskGrid mask = loadMaskGrid(maskFile);
        if (mask.width == 0 || mask.height == 0) {
            throw new RuntimeException("[错误] 蒙版图片尺寸无效。");
        }

        // ========== 6. 加载字体(方正小篆体) ==========
        File fontFileObj = new File(fontFile);
        if (!fontFileObj.exists()) {
            throw new RuntimeException("[错误] 找不到字体文件:" + fontFile
                    + "\n请设置 ChineseWordCloud.fontFile 或环境变量 WORDCLOUD_FONT 为你的方正小篆体真实路径。");
        }
        Font baseFont = Font.createFont(Font.TRUETYPE_FONT, fontFileObj);

        // ========== 7. 生成词云(自研布局,对标 Python wordcloud) ==========
        BufferedImage img = drawWordCloud(words, mask.drawable, mask.width, mask.height, baseFont);

        // ========== 8. 叠加钢蓝色轮廓 ==========
        drawContour(img, maskFile, steelBlue, 3);

        // ========== 9. 保存 PNG ==========
        ImageIO.write(img, "png", new File(outputFile));
        System.out.println("[完成] 词云已保存:" + new File(outputFile).getAbsolutePath() + "(共放置 " + placedCount + " 个词)");
    }

    // ============================================================
    // 自研词云布局(对标 Python wordcloud 算法)
    // ============================================================

    /** 按词频降序返回前 n 个词 */
    static List<WordFreq> sortedWords(Map<String, Integer> freq, int n) {
        List<WordFreq> list = new ArrayList<WordFreq>();
        for (Map.Entry<String, Integer> e : freq.entrySet()) {
            list.add(new WordFreq(e.getKey(), e.getValue()));
        }
        list.sort(new java.util.Comparator<WordFreq>() {
            public int compare(WordFreq a, WordFreq b) {
                return Integer.compare(b.count, a.count);
            }
        });
        if (list.size() > n) {
            return list.subList(0, n);
        }
        return list;
    }

    /** 在可绘制区域内按词频布局文字,返回白底画布 */
    static BufferedImage drawWordCloud(List<WordFreq> words, boolean[][] drawable,
                                       int width, int height, Font baseFont) {
        // 画布:白色背景
        BufferedImage canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = canvas.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.dispose();

        // 占用网格
        boolean[][] occupied = new boolean[height][width];

        // 可绘制区域(人像)的包围盒与质心,用于随机落点采样
        int minX = width, minY = height, maxX = 0, maxY = 0;
        long cxSum = 0, cySum = 0, cnt = 0;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                if (drawable[y][x]) {
                    if (x < minX) minX = x;
                    if (x > maxX) maxX = x;
                    if (y < minY) minY = y;
                    if (y > maxY) maxY = y;
                    cxSum += x;
                    cySum += y;
                    cnt++;
                }
            }
        }
        if (cnt == 0) {
            throw new RuntimeException("[错误] 蒙版可绘制区域为空,请检查蒙版图(深色人像)。");
        }
        double centroidX = (double) cxSum / cnt;
        double centroidY = (double) cySum / cnt;
        double bboxW = maxX - minX;
        double bboxH = maxY - minY;

        Random rng = new Random(42L); // 固定随机种子,可复现(对应 Python random_state)
        double maxFontSize = Math.min(width, height) * maxFontRatio;
        int maxCount = words.get(0).count;

        // 用于测量/渲染文字的共享 Graphics(FontMetrics 需要 Graphics 上下文)
        BufferedImage probe = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
        Graphics2D probeG = probe.createGraphics();
        probeG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 逐词布局
        int missStreak = 0;
        for (WordFreq wf : words) {
            if (wf.count <= 0) {
                continue;
            }
            // 字号 = 线性映射(对标 python: min + (max-min)*count/maxCount)
            double size = minFontSize + (maxFontSize - minFontSize) * (double) wf.count / maxCount;
            boolean placed = false;

            while (size >= minFontSize && !placed) {
                Font face = baseFont.deriveFont((float) Math.round(size));
                TextRaster tr = rasterizeWord(probeG, face, wf.word);
                if (tr.n == 0) {
                    break;
                }
                // 对标 prefer_horizontal=0.9:约 10% 的词语旋转 90° 竖排
                if (rng.nextDouble() > preferHorizon) {
                    tr.rotate();
                }
                placed = tryPlace(rng, canvas, occupied, drawable, tr,
                        width, height, centroidX, centroidY, bboxW, bboxH,
                        randomColor(rng));
                if (!placed) {
                    size *= fontDecay;
                }
            }

            if (placed) {
                placedCount++;
                missStreak = 0;
            } else {
                missStreak++;
                // 连续多个词都放不下 → 画布已满,提前结束(避免无谓耗时)
                if (missStreak > 60) {
                    break;
                }
            }
        }
        probeG.dispose();
        return canvas;
    }

    /** 把文字按当前字体渲染成小图,返回非透明像素偏移列表与宽高 */
    static TextRaster rasterizeWord(Graphics2D probeG, Font font, String word) {
        FontMetrics fm = probeG.getFontMetrics(font);
        int bw = fm.stringWidth(word);
        int bh = fm.getHeight();
        if (bw <= 0 || bh <= 0) {
            return new TextRaster(new int[0], new int[0], 0, bw, bh);
        }

        BufferedImage img = new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = img.createGraphics();
        g.setFont(font);
        g.setColor(Color.BLACK);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.drawString(word, 0, fm.getAscent());
        g.dispose();

        int[] px = img.getRGB(0, 0, bw, bh, null, 0, bw);
        List<Integer> oxList = new ArrayList<Integer>();
        List<Integer> oyList = new ArrayList<Integer>();
        for (int y = 0; y < bh; y++) {
            for (int x = 0; x < bw; x++) {
                int argb = px[y * bw + x];
                int alpha = (argb >> 24) & 0xFF;
                if (alpha > 0) {
                    oxList.add(x);
                    oyList.add(y);
                }
            }
        }
        int n = oxList.size();
        int[] ox = new int[n];
        int[] oy = new int[n];
        for (int i = 0; i < n; i++) {
            ox[i] = oxList.get(i);
            oy[i] = oyList.get(i);
        }
        return new TextRaster(ox, oy, n, bw, bh);
    }

    /** 尝试在随机落点放置词;成功则画到画布并标记占用 */
    static boolean tryPlace(Random rng, BufferedImage canvas, boolean[][] occupied, boolean[][] drawable,
                            TextRaster tr, int width, int height,
                            double centroidX, double centroidY, double bboxW, double bboxH,
                            Color col) {
        if (tr.bw > width || tr.bh > height) {
            return false;
        }
        // 以人像质心为中心的高斯采样,让大词优先落在中间(对标 python 中心螺旋)
        double sigmaX = Math.max(bboxW / 4.0, 8);
        double sigmaY = Math.max(bboxH / 4.0, 8);

        for (int attempt = 0; attempt < tryPerRound; attempt++) {
            int cx = (int) Math.round(centroidX + rng.nextGaussian() * sigmaX);
            int cy = (int) Math.round(centroidY + rng.nextGaussian() * sigmaY);
            cx = clamp(cx - tr.bw / 2, 0, width - tr.bw);
            cy = clamp(cy - tr.bh / 2, 0, height - tr.bh);

            // 碰撞检测:所有文字像素必须在可绘制区且未占用
            boolean ok = true;
            for (int i = 0; i < tr.n; i++) {
                int px = cx + tr.ox[i];
                int py = cy + tr.oy[i];
                if (px < 0 || py < 0 || px >= width || py >= height
                        || !drawable[py][px] || occupied[py][px]) {
                    ok = false;
                    break;
                }
            }
            if (!ok) {
                continue;
            }

            // 放置:标记占用 + 上色
            int rgb = col.getRGB();
            for (int i = 0; i < tr.n; i++) {
                int px = cx + tr.ox[i];
                int py = cy + tr.oy[i];
                occupied[py][px] = true;
                canvas.setRGB(px, py, rgb);
            }
            return true;
        }
        return false;
    }

    static int clamp(int v, int lo, int hi) {
        if (v < lo) return lo;
        if (v > hi) return hi;
        return v;
    }

    /** 随机彩色(对应 Python: "hsl(随机色相, 80%, 50%)") */
    static Color randomColor(Random rng) {
        return hslToRgb(rng.nextDouble() * 360, 0.8, 0.5);
    }

    // ============================================================
    // 蒙版、轮廓、颜色、停用词
    // ============================================================

    /** 读取蒙版,返回可绘制网格(深色人像=可绘制) */
    static MaskGrid loadMaskGrid(String path) throws Exception {
        File f = new File(path);
        if (!f.exists()) {
            throw new RuntimeException("[错误] 找不到蒙版图片:" + path);
        }
        BufferedImage img = ImageIO.read(f);
        if (img == null) {
            throw new RuntimeException("[错误] 蒙版图片解码失败:" + path);
        }
        int w = img.getWidth();
        int h = img.getHeight();
        boolean[][] grid = new boolean[h][w];
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                int rgb = img.getRGB(x, y);
                int r = (rgb >> 16) & 0xFF;
                int gg = (rgb >> 8) & 0xFF;
                int b = rgb & 0xFF;
                double lum = 0.299 * r + 0.587 * gg + 0.114 * b;
                grid[y][x] = lum < 127.5; // 深色 = 人像 = 可绘制
            }
        }
        return new MaskGrid(grid, w, h);
    }

    /** 沿蒙版“深色人像”边界绘制钢蓝色轮廓 */
    static void drawContour(BufferedImage dst, String maskPath, Color contourColor, int contourWidth) {
        try {
            File f = new File(maskPath);
            if (!f.exists()) {
                return;
            }
            BufferedImage maskImg = ImageIO.read(f);
            if (maskImg == null) {
                return;
            }
            int w = maskImg.getWidth();
            int h = maskImg.getHeight();
            double[][] lum = new double[h][w];
            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    int rgb = maskImg.getRGB(x, y);
                    int r = (rgb >> 16) & 0xFF;
                    int g = (rgb >> 8) & 0xFF;
                    int b = rgb & 0xFF;
                    lum[y][x] = 0.299 * r + 0.587 * g + 0.114 * b;
                }
            }
            int half = contourWidth / 2;
            int rgb = contourColor.getRGB();
            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    if (lum[y][x] > 127.5) {
                        continue;
                    }
                    // 深色人像边界像素:本身为暗(人像),四邻接中有亮(背景)像素
                    boolean edge = (x > 0 && lum[y][x - 1] > 127.5)
                            || (x < w - 1 && lum[y][x + 1] > 127.5)
                            || (y > 0 && lum[y - 1][x] > 127.5)
                            || (y < h - 1 && lum[y + 1][x] > 127.5);
                    if (edge) {
                        for (int dy = -half; dy <= half; dy++) {
                            for (int dx = -half; dx <= half; dx++) {
                                int nx = x + dx;
                                int ny = y + dy;
                                if (nx >= 0 && ny >= 0 && nx < w && ny < h) {
                                    dst.setRGB(nx, ny, rgb);
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception ignored) {
            // 轮廓绘制失败不阻断主流程
        }
    }

    /** 加载停用词:内置 + stopwords.txt(一行一个词) */
    static Set<String> loadStopWords() {
        Set<String> stop = new HashSet<String>();
        String[] builtin = {
                // Python 示例中 stopwords.update([...]) 显式加入的词
                "工作", "就是", "个人", "没有", "村民委员会", "said",
                // 常用英文停用词(对应 Python: wordcloud.STOPWORDS 的主要子集)
                "a", "an", "the", "and", "or", "but", "if", "of", "to", "in", "on", "for",
                "with", "as", "at", "by", "from", "up", "about", "into", "over", "after",
                "is", "are", "was", "were", "be", "been", "being", "have", "has", "had",
                "do", "does", "did", "will", "would", "can", "could", "should", "may",
                "not", "no", "so", "too", "very", "just", "only", "then", "there",
                "it", "its", "this", "that", "these", "those", "i", "you", "he", "she",
                "we", "they", "them", "his", "her", "our", "your", "their", "my", "me",
                "said", "say", "one", "two", "get", "got", "like", "make", "new", "now",
                // 常用中文停用词
                "的", "了", "是", "和", "在", "有", "就", "都", "而", "及", "与",
                "一个", "我们", "你们", "他们", "这个", "那个", "什么", "怎么", "可以",
        };
        for (String w : builtin) {
            stop.add(w);
        }
        try {
            List<String> lines = Files.readAllLines(new File(stopWordsFile).toPath(), StandardCharsets.UTF_8);
            for (String line : lines) {
                String w = line.trim();
                if (!w.isEmpty()) {
                    stop.add(w);
                }
            }
        } catch (Exception ignored) {
            // 停用词文件不存在时使用内置
        }
        return stop;
    }

    /** HSL 转 RGB(S=0.8, L=0.5 时得到鲜艳彩色) */
    static Color hslToRgb(double h, double s, double l) {
        h = h % 360.0 / 360.0;
        double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        double p = 2 * l - q;
        double r = hue2rgb(p, q, h + 1.0 / 3.0);
        double g = hue2rgb(p, q, h);
        double b = hue2rgb(p, q, h - 1.0 / 3.0);
        return new Color((int) Math.round(r * 255), (int) Math.round(g * 255), (int) Math.round(b * 255));
    }

    static double hue2rgb(double p, double q, double t) {
        if (t < 0) t += 1;
        if (t > 1) t -= 1;
        if (t < 1.0 / 6.0) return p + (q - p) * 6 * t;
        if (t < 0.5) return q;
        if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6;
        return p;
    }
}

 

调用:

/**
 * encoding: utf-8
 * 版权所有 2026 ©涂聚文有限公司 ®
 * 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
 * 描述:词云
 * Author    : geovindu,Geovin Du 涂聚文.
 * IDE       : IntelliJ IDEA 2024.3.6 Java 17
 * # database  : Oracle21c,MySQL 9.0,SQL Server 2019,PostgreSQL 17.1 Neo4j
 * # OS        : window10
 * Datetime  : 2026 - 2026/7/5 - 11:05
 * User      : geovindu
 * Product   : IntelliJ IDEA
 * Project   : javaWordCloud
 * File      : main.java
 * explain   : 学习  类
 * */
// ============================================================
// 中文词云生成器(Java)— 程序入口
// 配置词云引擎字段后调用 ChineseWordCloud.generate()。
// 支持命令行参数(可选):java Main [文本] [蒙版] [字体] [输出]
// 示例:
//   java Main
//   java Main alice2.txt alice_mask.png C:\\Fonts\\方正小篆体.ttf out.png
// 也可用环境变量 WORDCLOUD_FONT 指定字体,优先级低于命令行参数。
// Author : geovindu, Geovin Du 涂聚文
// ============================================================
public class Main {

    public static void main(String[] args) {
        // ========== 可选:从命令行参数覆盖默认配置 ==========
        // 顺序:文本文件、蒙版图片、字体文件、输出文件
        if (args.length > 0) {
            // 防护:误把目录名(如项目文件夹)当文本文件传入
            if (new java.io.File(args[0]).isDirectory()) {
                System.err.println("[提示] 第一个参数应是【文本文件】,而不是目录:" + args[0]);
                System.err.println("正确用法:java Main [文本文件] [蒙版图片] [字体文件] [输出文件]");
                System.err.println("示例    :java Main alice2.txt alice_mask.png \"C:\\\\Fonts\\\\方正小篆体.ttf\" out.png");
                System.exit(1);
            }
            ChineseWordCloud.textFile = args[0];
        }
        if (args.length > 1) {
            ChineseWordCloud.maskFile = args[1];
        }
        if (args.length > 2) {
            ChineseWordCloud.fontFile = args[2];
        }
        if (args.length > 3) {
            ChineseWordCloud.outputFile = args[3];
        }

        System.out.println("========== 中文词云生成器 ==========");
        System.out.println("文本文件 : " + ChineseWordCloud.textFile);
        System.out.println("蒙版图片 : " + ChineseWordCloud.maskFile);
        System.out.println("字体文件 : " + ChineseWordCloud.fontFile);
        System.out.println("输出文件 : " + ChineseWordCloud.outputFile);
        System.out.println("===================================");

        try {
            ChineseWordCloud.generate();
            System.out.println("生成完成。");
        } catch (Exception e) {
            System.err.println("生成失败:" + e.getMessage());
            // 用 -v/--verbose 或调试时再展开完整堆栈;默认只打印一行错误,便于定位
            if (System.getProperty("wordcloud.debug") != null) {
                e.printStackTrace();
            }
            System.exit(1);
        }
    }
}

  

输出

chinese_wordcloud2

 

posted @ 2026-09-05 08:19  ®Geovin Du Dream Park™  阅读(7)  评论(0)    收藏  举报