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

Flutter中如何比较编码不同但内容相同的日文字符串?

字符串匹配问题与Flutter代码优化

问题背景

我在Flutter中编写了一个函数,接收两个字符串参数,从CSV文件中返回对应行列的数据——第一个字符串位于CSV首行,第二个位于首列,通过索引定位目标数据。函数代码如下:

Future<String> getData(String ward, String trashType) async {
  final csvString =
      await rootBundle.loadString('assets/data.csv');
  final csvData = CsvToListConverter().convert(csvString);

  final wardIndex = csvData[0].indexOf(ward);

  final trashRow = csvData.firstWhere((row) => row[0] == trashType);

  final data = trashRow[wardIndex];

  return data;
}

排查firstWhere报错时发现,视觉上完全相同的字符串编码存在差异:

trashType
UTF-8-decoded:少量の木くず、葉・Twigs,Leaves
UTF-8-encoded: \xE5\xB0\x91\xE9\x87\x8F\xE3\x81\xAE\xE6\x9C\xA8\xE3\x81\x8F\xE3\x81\x9A\xE3\x80\x81\xE8\x91\x89\xE3\x83\xBB\x54\x77\x69\x67\x73\x2C\x4C\x65\x61\x76\x65\x73

String in row[0]
UTF-8-decoded:少量の木くず、葉・Twigs,Leaves
UTF-8-encoded: \xE5\xB0\x91\xE9\x87\x8F\xE3\x81\xAE\xE6\x9C\xA8\xE3\x81\x8F\xE3\x81\x99\xE3\x82\x99\xE3\x80\x81\xE8\x91\x89\xE3\x83\xBB\x54\x77\x69\x67\x73\x2C\x4C\x65\x61\x76\x65\x73

需要解决这类“视觉相同、编码不同”的字符串匹配问题,同时希望得到代码优化建议。


解决字符串匹配问题:Unicode归一化

Dart的String类自带normalize()方法(需导入dart:convert),可将不同编码形式的同视觉字符转换为标准形式,常用归一化形式有四种:

  • NFC:标准等价组合(最常用,将分解字符合并为单个字符)
  • NFD:标准等价分解
  • NFKC:兼容等价组合
  • NFKD:兼容等价分解

只需将两个字符串转换为相同归一化形式后再比较即可,示例如下:

归一化实现步骤

  1. 导入依赖
import 'dart:convert';
  1. 修改匹配逻辑
// 替换原wardIndex获取逻辑
final wardIndex = csvData[0].indexWhere(
  (item) => item.normalize(NFC) == ward.normalize(NFC)
);

// 替换原trashRow获取逻辑
final trashRow = csvData.firstWhere(
  (row) => row[0].normalize(NFC) == trashType.normalize(NFC)
);

代码优化建议

1. 增加错误处理

当前代码未处理ward或trashType不存在的情况,firstWhere找不到匹配会直接报错,indexOf返回-1后取数据也会触发异常。建议添加明确的错误处理:

final wardIndex = csvData[0].indexWhere(
  (item) => item.normalize(NFC) == ward.normalize(NFC)
);
if (wardIndex == -1) {
  throw ArgumentError('未找到指定区域:$ward');
}

final trashRow = csvData.firstWhere(
  (row) => row[0].normalize(NFC) == trashType.normalize(NFC),
  orElse: () => throw ArgumentError('未找到指定垃圾类型:$trashType'),
);

2. 缓存CSV解析结果

若多次调用getData,重复加载解析CSV会浪费资源,可将解析后的数据缓存:

List<List<dynamic>>? _cachedCsvData;

Future<String> getData(String ward, String trashType) async {
  final csvData = _cachedCsvData ?? await _loadCsvData();
  _cachedCsvData = csvData;

  // 后续匹配逻辑...
}

Future<List<List<dynamic>>> _loadCsvData() async {
  final csvString = await rootBundle.loadString('assets/data.csv');
  return CsvToListConverter().convert(csvString);
}

3. 提升类型安全性

CSV解析结果为List<dynamic>,可根据实际数据类型做强制转换,避免运行时类型错误:

final wardIndex = csvData[0].indexWhere(
  (item) => (item as String).normalize(NFC) == ward.normalize(NFC)
);

final trashRow = csvData.firstWhere(
  (row) => (row[0] as String).normalize(NFC) == trashType.normalize(NFC),
  orElse: () => throw ArgumentError('未找到指定垃圾类型:$trashType'),
);

final data = trashRow[wardIndex] as String;

4. 明确文件编码

加载CSV时可明确指定编码(默认UTF-8,若文件编码特殊需调整):

final csvString = await rootBundle.loadString('assets/data.csv', encoding: utf8);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 10:42:48