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:兼容等价分解
只需将两个字符串转换为相同归一化形式后再比较即可,示例如下:
归一化实现步骤
- 导入依赖
import 'dart:convert';
- 修改匹配逻辑
// 替换原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
相关产品推荐
相关产品推荐

