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; }); }
问题根源与解决方法
核心问题
- 后端多余的TreeMap操作:先将HashMap转成TreeMap(按键自然排序)再按value倒序,这一步完全没必要,还会增加额外排序开销,但这不是顺序混乱的主因。
- 前端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
相关产品推荐
相关产品推荐

