当前位置: 首页 > news >正文

VB.NET,C#字典对象来保存用户数据,支持大小写

用这个保存的,登录时大小写不一样会不会无法识别

根据你提供的 SaveUsersToJson 方法,我注意到你使用了 JSON 序列化来保存用户数据,但没有显式指定字典的比较器。这意味着在反序列化时,默认会使用区分大小写的比较器,导致大小写不同的用户名无法正确匹配。

问题分析

当你保存用户数据时:

Private Sub SaveUsersToJson(users As Dictionary(Of String, UserInfo), filePath As String)Dim jsonContent As String = JsonConvert.SerializeObject(users, Formatting.Indented)File.WriteAllText(filePath, jsonContent)
End Sub

这里的 users 字典虽然在内存中是不区分大小写的(假设你已正确初始化),但 JSON 序列化不会保存比较器信息。因此,当你从 JSON 文件加载数据时:

Private Function LoadUsersFromJson(filePath As String) As Dictionary(Of String, UserInfo)If Not File.Exists(filePath) ThenReturn New Dictionary(Of String, UserInfo)(StringComparer.OrdinalIgnoreCase)End IfDim jsonContent As String = File.ReadAllText(filePath)' 问题:这里反序列化时默认使用区分大小写的比较器!Return JsonConvert.DeserializeObject(Of Dictionary(Of String, UserInfo))(jsonContent)
End Function

反序列化得到的字典会 默认使用区分大小写的比较器,导致登录时大小写不匹配的用户名无法识别。

解决方案

1. 修改加载方法,强制使用不区分大小写的比较器

在加载用户数据后,创建一个新的不区分大小写的字典,并复制所有项:

vb

Private Function LoadUsersFromJson(filePath As String) As Dictionary(Of String, UserInfo)If Not File.Exists(filePath) ThenReturn New Dictionary(Of String, UserInfo)(StringComparer.OrdinalIgnoreCase)End IfDim jsonContent As String = File.ReadAllText(filePath)Dim tempDict = JsonConvert.DeserializeObject(Of Dictionary(Of String, UserInfo))(jsonContent)' 创建不区分大小写的字典,并复制所有项Dim caseInsensitiveDict = New Dictionary(Of String, UserInfo)(StringComparer.OrdinalIgnoreCase)For Each kvp In tempDictcaseInsensitiveDict.Add(kvp.Key, kvp.Value)NextReturn caseInsensitiveDict
End Function
2. 改进方案:使用自定义 ContractResolver

更优雅的方法是通过 JSON.NET 的 ContractResolver 来控制反序列化行为:

vb

Imports Newtonsoft.Json
Imports Newtonsoft.Json.SerializationPublic Class CaseInsensitiveDictionaryConverterInherits JsonConverterPublic Overrides Function CanConvert(objectType As Type) As BooleanReturn objectType = GetType(Dictionary(Of String, UserInfo))End FunctionPublic Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object' 反序列化为普通字典Dim tempDict = serializer.Deserialize(Of Dictionary(Of String, UserInfo))(reader)' 创建不区分大小写的字典,并复制所有项Dim caseInsensitiveDict = New Dictionary(Of String, UserInfo)(StringComparer.OrdinalIgnoreCase)For Each kvp In tempDictcaseInsensitiveDict.Add(kvp.Key, kvp.Value)NextReturn caseInsensitiveDictEnd FunctionPublic Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)' 使用默认序列化逻辑serializer.Serialize(writer, value)End Sub
End Class

然后在加载方法中使用这个转换器:

vb

Private Function LoadUsersFromJson(filePath As String) As Dictionary(Of String, UserInfo)If Not File.Exists(filePath) ThenReturn New Dictionary(Of String, UserInfo)(StringComparer.OrdinalIgnoreCase)End IfDim jsonContent As String = File.ReadAllText(filePath)Dim settings = New JsonSerializerSettings()settings.Converters.Add(New CaseInsensitiveDictionaryConverter())Return JsonConvert.DeserializeObject(Of Dictionary(Of String, UserInfo))(jsonContent, settings)
End Function

完整实现示例

以下是整合后的完整代码:

vb

Imports System.IO
Imports System.Security.Cryptography
Imports System.Text
Imports Newtonsoft.Json
Imports Newtonsoft.Json.SerializationPublic Class UserManagerPrivate users As New Dictionary(Of String, UserInfo)(StringComparer.OrdinalIgnoreCase)Private filePath As String = ""Private lockObject As New Object()Private Class UserInfoPublic Property PasswordHash As StringPublic Property Salt As StringEnd ClassPublic Sub New(Optional filePath As String = "users.json")Me.filePath = filePathLoadUsers()End SubPublic Sub RegisterUser(username As String, password As String, Optional ByRef Errinfo As String = "")SyncLock lockObjectIf users.ContainsKey(username) ThenErrinfo = "用户已存在"ReturnEnd IfDim salt = GenerateSalt()Dim passwordHash = HashPassword(password, salt)users(username) = New UserInfo With {.PasswordHash = passwordHash,.Salt = salt}SaveUsers()End SyncLockEnd SubPublic Function Login(username As String, password As String, Optional ByRef Errinfo As String = "") As BooleanSyncLock lockObjectIf Not users.ContainsKey(username) ThenErrinfo = "用户不存在"Return FalseEnd IfDim user = users(username)Dim hashedInput = HashPassword(password, user.Salt)If hashedInput = user.PasswordHash ThenReturn TrueElseErrinfo = "密码错误"Return FalseEnd IfEnd SyncLockEnd FunctionPrivate Function GenerateSalt() As StringUsing rng As New RNGCryptoServiceProvider()Dim salt(15) As Byterng.GetBytes(salt)Return Convert.ToBase64String(salt)End UsingEnd FunctionPrivate Function HashPassword(password As String, salt As String) As StringDim saltBytes = Convert.FromBase64String(salt)Using pbkdf2 As New Rfc2898DeriveBytes(password, saltBytes, 10000)Dim hashBytes = pbkdf2.GetBytes(20)Return Convert.ToBase64String(hashBytes)End UsingEnd FunctionPrivate Sub SaveUsers()SyncLock lockObjectSaveUsersToJson(users, filePath)End SyncLockEnd SubPrivate Sub LoadUsers()SyncLock lockObjectusers = LoadUsersFromJson(filePath)End SyncLockEnd SubPrivate Sub SaveUsersToJson(users As Dictionary(Of String, UserInfo), filePath As String)Dim jsonContent As String = JsonConvert.SerializeObject(users, Formatting.Indented)File.WriteAllText(filePath, jsonContent)End SubPrivate Function LoadUsersFromJson(filePath As String) As Dictionary(Of String, UserInfo)If Not File.Exists(filePath) ThenReturn New Dictionary(Of String, UserInfo)(StringComparer.OrdinalIgnoreCase)End IfDim jsonContent As String = File.ReadAllText(filePath)Dim tempDict = JsonConvert.DeserializeObject(Of Dictionary(Of String, UserInfo))(jsonContent)' 创建不区分大小写的字典,并复制所有项Dim caseInsensitiveDict = New Dictionary(Of String, UserInfo)(StringComparer.OrdinalIgnoreCase)For Each kvp In tempDictcaseInsensitiveDict.Add(kvp.Key, kvp.Value)NextReturn caseInsensitiveDictEnd Function
End Class

验证测试

vb

Sub Main()Dim manager As New UserManager()' 注册用户(大写)manager.RegisterUser("TESTUSER", "Password123")' 保存并重新加载manager.SaveUsers()manager = New UserManager()' 登录(小写)Dim errorInfo As StringIf manager.Login("testuser", "Password123", errorInfo) ThenConsole.WriteLine("登录成功") ' 应该成功ElseConsole.WriteLine($"登录失败: {errorInfo}")End If
End Sub

关键点总结

  1. JSON 序列化不保存比较器:默认反序列化会创建区分大小写的字典。
  2. 解决方案
    • 手动转换为不区分大小写的字典(简单方案)。
    • 使用自定义转换器(更优雅,推荐)。
  3. 确保一致性
    • 所有操作(注册、登录、加载、保存)都要使用不区分大小写的字典。

通过这些修改,你的用户管理系统将正确处理大小写不敏感的用户名登录,即使数据存储在 JSON 文件中。
 

.NET字典对象大小写都支持的原理是什么?内部存储时全部转成小写?

在 .NET 中,Dictionary<TKey, TValue> 的大小写敏感性是由 比较器(Comparer) 控制的,而不是简单地将所有键转换为小写。理解其原理需要从以下几个方面入手:

一、核心原理:比较器决定键的唯一性

Dictionary 内部使用 哈希表(Hash Table) 实现,其核心机制是:

  1. 通过 GetHashCode() 计算键的哈希值,确定存储位置
  2. 通过 Equals() 判断键是否相等

当你创建字典时,可以指定一个比较器(IEqualityComparer<T>):vb

' 不区分大小写的字典(使用 OrdinalIgnoreCase 比较器)
Dim caseInsensitiveDict = New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)' 区分大小写的字典(默认比较器)
Dim caseSensitiveDict = New Dictionary(Of String, String)()

二、存储机制:键保持原始大小写,但比较时忽略

字典内部不会将键转换为小写存储,而是:

  1. 哈希计算:使用比较器的 GetHashCode() 方法计算哈希值
    • 对于 OrdinalIgnoreCase,会忽略大小写计算哈希(如 "TEST" 和 "test" 生成相同哈希)
  2. 相等判断:使用比较器的 Equals() 方法判断是否相等
    • 对于 OrdinalIgnoreCase,"TEST" 和 "test" 被视为相等

示例说明:vb

Dim dict = New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
dict.Add("TEST", "value1")' 以下操作会覆盖原值,因为被视为同一个键:
dict("test") = "value2"' 字典中实际存储的键仍是 "TEST",但通过 "test" 也能访问
Console.WriteLine(dict("TEST"))  ' 输出: value2

三、比较器实现示例

StringComparer.OrdinalIgnoreCase 的简化实现原理:

vb

Public Class CaseInsensitiveComparerImplements IEqualityComparer(Of String)Public Function GetHashCode(str As String) As Integer _Implements IEqualityComparer(Of String).GetHashCode' 计算哈希值时忽略大小写Return str.ToUpperInvariant().GetHashCode()End FunctionPublic Function Equals(x As String, y As String) As Boolean _Implements IEqualityComparer(Of String).Equals' 比较时忽略大小写Return String.Equals(x, y, StringComparison.OrdinalIgnoreCase)End Function
End Class

四、性能考虑

  1. 哈希冲突:不区分大小写的字典可能有更多哈希冲突(如 "TEST" 和 "test" 哈希相同)
  2. 计算开销:每次比较需要额外处理大小写(但通常可忽略不计)

五、常见误区

  1. 误区 1:键被转换为小写存储
    ❌ 错误。键保持原始大小写,只是比较时忽略大小写。

  2. 误区 2:所有字符串比较都默认不区分大小写
    ❌ 错误。默认比较器(StringComparer.Ordinal)是区分大小写的。

  3. 误区 3:不区分大小写的字典更慢
    ✅ 通常略微慢一点(因额外处理),但在大多数场景下可忽略不计。

六、验证测试

以下代码验证字典内部存储的键保持原始大小写:

vb

Dim dict = New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
dict.Add("TeSt", "Value")' 输出所有键(注意大小写)
For Each key In dict.KeysConsole.WriteLine(key)  ' 输出: TeSt
Next' 通过不同大小写访问
Console.WriteLine(dict("test"))    ' 输出: Value
Console.WriteLine(dict("TEST"))    ' 输出: Value
Console.WriteLine(dict("TeSt"))    ' 输出: Value

总结

.NET 字典的大小写敏感性完全由 比较器 控制:

  • 存储:键保持原始大小写
  • 比较:通过比较器的 GetHashCode() 和 Equals() 方法决定是否相等
  • 优势:既保持原始数据,又提供灵活的查询方式

这种设计使得字典在需要大小写不敏感的场景下表现出色,同时避免了数据被隐式修改的问题。

许内部用2个值的方式更高效?KEY全部转成小写保存,保存原来KEY作为第2个值,区分大小写?

分享

你的想法很有创意!确实,通过维护一个小写版本的键(用于哈希计算和比较)和原始键(用于返回真实数据),可以实现大小写不敏感的查找,同时保留原始大小写信息。这种方案在某些场景下可能更高效,但也有其局限性。

一、自定义实现方案

以下是一个简化的实现示例,展示如何通过维护两个键来实现大小写不敏感的字典:vb

Imports System.Collections
Imports System.Collections.GenericPublic Class CustomCaseInsensitiveDictionary(Of TValue)Implements IDictionary(Of String, TValue)' 存储小写键到原始键的映射Private lowerCaseToOriginal As New Dictionary(Of String, String)(StringComparer.Ordinal)' 存储原始键和值Private originalToValue As New Dictionary(Of String, TValue)(StringComparer.Ordinal)' 获取或设置值Public Default Property Item(key As String) As TValue Implements IDictionary(Of String, TValue).ItemGetDim lowerKey = key.ToLowerInvariant()Dim originalKey As StringIf lowerCaseToOriginal.TryGetValue(lowerKey, originalKey) ThenReturn originalToValue(originalKey)End IfThrow New KeyNotFoundException()End GetSet(value As TValue)Dim lowerKey = key.ToLowerInvariant()Dim originalKey As String' 如果已存在,先移除If lowerCaseToOriginal.TryGetValue(lowerKey, originalKey) ThenoriginalToValue.Remove(originalKey)End If' 添加新项lowerCaseToOriginal(lowerKey) = keyoriginalToValue(key) = valueEnd SetEnd Property' 添加键值对Public Sub Add(key As String, value As TValue) Implements IDictionary(Of String, TValue).AddDim lowerKey = key.ToLowerInvariant()If lowerCaseToOriginal.ContainsKey(lowerKey) ThenThrow New ArgumentException("已存在相同的键(忽略大小写)")End IflowerCaseToOriginal(lowerKey) = keyoriginalToValue(key) = valueEnd Sub' 检查键是否存在Public Function ContainsKey(key As String) As Boolean Implements IDictionary(Of String, TValue).ContainsKeyDim lowerKey = key.ToLowerInvariant()Return lowerCaseToOriginal.ContainsKey(lowerKey)End Function' 其他接口实现...' (为简化代码,此处省略其他方法实现)
End Class

二、这种方案的优缺点

优点
  1. 潜在性能提升

    • 哈希计算只需一次(对小写键)
    • 比较时直接使用 Ordinal 比较器(比 OrdinalIgnoreCase 略快)
  2. 更简单的冲突处理

    • 由于小写键唯一,哈希冲突只可能发生在真正的重复键上
  3. 清晰的键映射关系

    • 明确区分用于查找的键和用于存储的键
缺点
  1. 内存开销

    • 需要额外存储小写键和原始键的映射
  2. 线程安全问题

    • 需要同步维护两个字典,增加复杂性
  3. 文化敏感性

    • ToLowerInvariant() 可能不适合所有文化场景(如土耳其语的 i 特殊处理)
  4. 与框架不一致

    • 与 .NET 标准字典行为不同,可能导致意外行为

三、性能对比测试

以下测试比较三种实现的性能:

vb

Imports System.DiagnosticsSub TestPerformance()Dim count As Integer = 1000000Dim keys As New List(Of String)' 生成测试数据For i As Integer = 0 To count - 1keys.Add($"Key{i}")keys.Add($"key{i}") ' 故意添加大小写不同的重复键Next' 1. 使用 StringComparer.OrdinalIgnoreCaseDim dict1 = New Dictionary(Of String, Integer)(StringComparer.OrdinalIgnoreCase)Dim sw1 = Stopwatch.StartNew()For Each key In keysdict1(key) = 1Nextsw1.Stop()Console.WriteLine($"使用 OrdinalIgnoreCase: {sw1.ElapsedMilliseconds} ms")' 2. 使用自定义双键字典Dim dict2 = New CustomCaseInsensitiveDictionary(Of Integer)()Dim sw2 = Stopwatch.StartNew()For Each key In keysdict2(key) = 1Nextsw2.Stop()Console.WriteLine($"使用自定义双键字典: {sw2.ElapsedMilliseconds} ms")
End Sub

四、实际应用场景

这种双键方案在以下场景可能更合适:

  1. 高频查找且键重复率高

    • 如配置文件解析,键可能频繁重复
  2. 内存不是主要瓶颈

    • 额外的映射表开销可接受
  3. 需要严格控制哈希计算逻辑

    • 例如自定义哈希算法或特殊字符处理

五、.NET 为何不采用这种方案?

  1. 兼容性和一致性

    • .NET 选择通过比较器统一处理大小写问题,保持 API 一致性
  2. 文化敏感性

    • 字符串大小写转换涉及文化问题(如土耳其语的 i 和 I),直接转换可能导致意外结果
  3. 内存优化

    • 对于大多数场景,维护两个字典的额外内存开销并不值得
  4. 现有方案已足够高效

    • StringComparer.OrdinalIgnoreCase 的性能在大多数情况下已足够好

总结

你的想法确实有潜力在特定场景下提供更高效率,但 .NET 框架选择了更通用、更安全的实现方式。在实际开发中,建议优先使用 StringComparer.OrdinalIgnoreCase,除非你有特殊的性能需求并经过严格测试验证。

http://www.lqws.cn/news/541027.html

相关文章:

  • Unreal引擎——Chaos物理引擎(不)详解
  • 官方 Linker Scripts 语法和规则解析(2)
  • 《算力迁徙:WebAssembly如何将C++算法炼成前端》
  • 临床项目范围管理:确保项目聚焦与成功交付
  • Flutter 网络请求指南, 从 iOS 到 Flutter 的 Dio + Retrofit 组合
  • 【组管理】创建组删除组修改文件/目录所属组
  • Windows11系统上安装WM虚拟机及Ubuntu 22.04系统
  • 小型软件开发的三重境界:从混沌编码到结构化设计
  • 用3个字符表示2字节二进制数据
  • 【菜狗的记录】模糊聚类最大树、图神经网络、大模型量化——20250627
  • [论文阅读] 人工智能 | 真实场景下 RAG 系统的工程实践指南
  • 机器学习基础 多层感知机
  • C++动态链接库之非托管封装Invoke,供C#/C++ 等编程语言使用,小白教程——C++动态链接库(一)
  • 【 MyBatis-Plus | 精讲 】
  • MySQL(1)——count()聚合函数
  • 容器和 Kubernetes 中的退出码
  • 【MariaDB】MariaDB Server 11.3.0 Alpha下载、安装、配置
  • 10【认识文件系统】
  • 云计算与人工智能的融合:从弹性算力到智能云的IT新革命
  • c#调用rust的dll怎么处理rust的panic
  • 为什么要设计access_token和refresh_token
  • Netty入门案例:简单Echo服务器(同步)
  • 航天VR赋能,无人机总测实验舱开启高效新篇​
  • 探秘 VR 逃生救援技术的奇妙世界​
  • 自动化保护 AWS ECS Fargate 服务:使用 Prisma Cloud 实现容器安全
  • Linux工作常用命令记录
  • 系统学习 Android 的 进程管理、内存管理、音频管理
  • 【人工智能与机器人研究】基于改进长短焦图像融合技术的轨道交通障碍物检测系统研究
  • 飞凌A40i使用笔记
  • 【RAG面试题】LLMs已经具备了较强能力,存在哪些不足点?