You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TreeMap排序异常:按词频排序时数字键优先级异常排查

问题分析与解决

问题描述

我尝试在表格中打印单词列表及其出现次数,并按出现次数排序。使用TreeMap+StreamAPI实现排序后,顺序出现异常——数字开头的单词被优先排在前面,尽管我想要的是最终按出现次数排序的效果。

后端代码

@RestController
@Slf4j
@RequestMapping("/application")
public class ApplicationController {

    @GetMapping
    public List<String> fileSelection() {

        List<String> files = new ArrayList<>();

        URL url = getClass().getClassLoader().getResource("placeholder.txt");
        File dir = null;

        try {

            dir = new File(url.toURI()).getParentFile();

        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }

        for (File f : dir.listFiles(path -> path.getName().contains(".txt"))) {

            if (!f.getName().equals("placeholder.txt")) {

                files.add(f.getName());

            }

        }

        return files;

    }

    @GetMapping("/{file}/lines")
    public Map<String, Integer> processFile(@PathVariable("file") String fileName) {

        List<String> lines = new ArrayList<>();
        Map<String, Integer> stringMap = new HashMap<String, Integer>();

        try {

            lines = Files.readAllLines(Paths.get(System.getProperty("user.dir"), "src", "main", "resources", fileName));

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        String string = String.join(" ", lines);
        String[] arr = string.split(" ");

        for (String s : arr) {

            if (stringMap.containsKey(s)) {

                stringMap.put(s, stringMap.get(s) + 1);

            } else {

                stringMap.put(s, 1);

            }

        }

        TreeMap<String, Integer> sortedMap = new TreeMap<>(stringMap);

        Map<String, Integer> resultMap =
                sortedMap.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                                (oldValue, newValue) -> oldValue, LinkedHashMap::new));

        return resultMap;

    }

}

前端代码

window.onload = () => {

    getFiles()

}

function getFiles() {

    fetch(`/application`, {

        method: 'GET'

    }).then(response => response.json())
        .then(data => document.getElementById("fileList").innerHTML = data.map(filename =>
            `<option value="${filename}">${filename}</option>`).join(''));

}

function readLines() {

    let file = document.getElementById("fileList").value;

    fetch(`/application/${file}/lines`, {

        method: 'GET'

    }).then(response => response.json())
        .then(data => {

            console.log(data)

            var keys = Object.keys(data)

                let html = `<tr><th>Word</th><th>Occurrence</th></tr>`;

                keys.forEach(key => {

                    if (key.substring(0, 1) !== "1") {
                        html += `<tr><td>${key}</td><td>${data[key]}</td></tr>`
                    }else{
                        html += `<tr><td>${key}</td><td>${data[key]}</td></tr>`
                    }

                })


                document.getElementById("showTable").innerHTML = html;

        });

}

问题根源与解决方法

核心问题

  1. 后端多余的TreeMap操作:先将HashMap转成TreeMap(按键自然排序)再按value倒序,这一步完全没必要,还会增加额外排序开销,但这不是顺序混乱的主因。
  2. 前端JSON对象的键排序特性:JavaScript中Object.keys()处理包含数字形式字符串的键(如"123")时,会自动按数字大小排序,直接忽略后端LinkedHashMap的原有顺序,这才是数字开头单词被优先排列的根本原因。

修复步骤

步骤1:优化后端代码,返回有序列表

去掉多余的TreeMap操作,将统计结果转成自定义DTO的列表返回,确保排序顺序不会丢失:

@GetMapping("/{file}/lines")
public List<WordCount> processFile(@PathVariable("file") String fileName) {
    // 原有统计单词次数的逻辑保持不变

    // 直接对HashMap的entry排序,转成自定义DTO列表返回
    return stringMap.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
            .map(entry -> new WordCount(entry.getKey(), entry.getValue()))
            .collect(Collectors.toList());
}

// 新增自定义DTO类,可放在Controller同包或单独的model包中
class WordCount {
    private String word;
    private Integer count;

    public WordCount(String word, Integer count) {
        this.word = word;
        this.count = count;
    }

    // 必须生成getter方法,否则JSON序列化会失败
    public String getWord() { return word; }
    public Integer getCount() { return count; }
}

步骤2:修改前端代码,遍历有序数组

后端返回的数组会严格保持排序顺序,直接遍历即可:

function readLines() {
    let file = document.getElementById("fileList").value;

    fetch(`/application/${file}/lines`, {
        method: 'GET'
    }).then(response => response.json())
        .then(data => {
            let html = `<tr><th>Word</th><th>Occurrence</th></tr>`;
            // 直接遍历数组,顺序完全匹配后端排序结果
            data.forEach(item => {
                html += `<tr><td>${item.word}</td><td>${item.count}</td></tr>`;
            });
            document.getElementById("showTable").innerHTML = html;
        });
}

可选:坚持返回Map的替代方案

如果不想修改后端返回类型,前端可改用Object.entries()获取键值对数组遍历(但不推荐,因为部分浏览器可能仍存在顺序问题):

// 前端修改遍历逻辑
Object.entries(data).forEach(([key, value]) => {
    html += `<tr><td>${key}</td><td>${value}</td></tr>`;
});

内容的提问来源于stack exchange,提问作者Gahatma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 17:14:56