最近搞redis存儲對象出了點問題,大概說一下背景,項目原有的東東之前存的是redis,存儲的直接是對象模型,沒有問題,這裏存儲對象存儲任何信息事都沒有問題的。可是如今調整爲存儲序列化的json字符串,此時獲取對象信息發生了問題,不是報錯就是有亂碼似的東東,一開始覺得是編碼問題,其實不許確,如今來一步步看一看究竟是什麼問題(這裏的測試只是爲了簡單,命名等都不規範,你們湊活着看了解問題就行)html
public class test { public string Name { get; set; } public string View { get; set; } public IList<AAA> list { get; set; } } public class AAA { public string Name { get; set; } public string View { get; set; } } public class CartController : Controller { public void Index2() { test tes = new test(); tes.Name = "z中文"; tes.View = null; tes.list = new List<AAA>(); AAA d = new AAA(); d.Name = "123"; d.View = "asd"; AAA b = new AAA(); b.Name = "我是特殊符號~!^?*$#<>\\"; b.View = "我是單引號\""; tes.list.Add(d); tes.list.Add(b); //var aa = Newtonsoft.Json.JsonConvert.SerializeObject(tes); RedisManager.Execute(redis => redis.Set("test", tes)); var ggg = RedisManager.Execute(redis => redis.Get<test>("test")); } }
直接存儲對象是沒有問題的,看看其中的set和get吧,redis
public bool Set<T>(string key, T value) { byte[] numArray = (object) value as byte[]; if (numArray != null) { base.Set(key, numArray); return true; } else { string str = JsonSerializer.SerializeToString<T>(value); this.SetEntry(key, str); return true; } } public static string SerializeToString<T>(T value) { if ((object) value == null) return (string) null; if (typeof (T) == typeof (object) || typeof (T).IsAbstract || typeof (T).IsInterface) { if (typeof (T).IsAbstract || typeof (T).IsInterface) JsState.IsWritingDynamic = true; string str = JsonSerializer.SerializeToString((object) value, value.GetType()); if (typeof (T).IsAbstract || typeof (T).IsInterface) JsState.IsWritingDynamic = false; return str; } else { StringBuilder sb = new StringBuilder(); using (StringWriter stringWriter = new StringWriter(sb, (IFormatProvider) CultureInfo.InvariantCulture)) { if (typeof (T) == typeof (string)) JsonUtils.WriteString((TextWriter) stringWriter, (object) value as string); else JsonWriter<T>.WriteObject((TextWriter) stringWriter, (object) value); } return ((object) sb).ToString(); } } public void SetEntry(string key, string value) { byte[] numArray = value != null ? ServiceStack.Text.StringExtensions.ToUtf8Bytes(value) : (byte[]) null; this.Set<byte[]>(key, numArray); }
恩恩,看樣子應該是,存儲的時候序列化了,而且使用utf8編碼,那好吧,get確定也就是utf8編碼反序列化成對象取出來的,因此泛型的存取數據並無跟編碼有什麼關係,那問題出在哪裏呢。json
public T Get<T>(string key) { if (!(typeof (T) == typeof (byte[]))) return JsonSerializer.DeserializeFromString<T>(this.GetValue(key)); else return (T) base.Get(key); } public byte[] Get(string key) { return this.GetBytes(key); } public byte[] GetBytes(string key) { if (key == null) throw new ArgumentNullException("key"); return this.SendExpectData(Commands.Get, StringExtensions.ToUtf8Bytes(key)); }
上面代碼解釋了爲何,對象怎麼存儲都沒有問題,再來看看string類型的信息。ide
public void Index2() { test tes = new test(); tes.Name = "z中文"; tes.View = null; tes.list = new List<AAA>(); AAA d = new AAA(); d.Name = "123"; d.View = "asd"; AAA b = new AAA(); //b.Name = "我是特殊符號~!^?*$#<>\\"; b.Name = "2"; //b.View = "\""; b.View = "\\"; tes.list.Add(d); tes.list.Add(b); var aa = Newtonsoft.Json.JsonConvert.SerializeObject(tes); RedisManager.Execute(redis => redis.Set("niutaotao_cart", aa)); var ggg = RedisManager.Execute(redis => redis.Get<byte[]>("niutaotao_cart")); var ii = RedisManager.Execute(redis => redis.Get<string>("niutaotao_cart")); var json="{\"Name\":\"z中文\",\"View\":null,\"list\":[{\"Name\":\"123\",\"View\":\"asd\"},{\"Name\":\"2\",\"View\":\"\\\"}]}"; RedisManager.Execute(redis => redis.Set("ddd", json)); var o = RedisManager.Execute(redis => redis.Get<byte[]>("ddd")); var pp = RedisManager.Execute(redis => redis.Get<string>("ddd")); }
能夠看看監視的結果,很少說直接上圖。大概能看出點區別了。學習
問題來了,1.json編碼後首先轉移符並無特殊處理,而是直接寫進了json格式字符串中測試
2. 重現向上看取數據的時候 1.雙引號有問題 ,貌似都變爲了轉移符+雙引號ui
2.轉移符有問題,具體規律也看不大出來,貌似就是以前都加了兩個轉移符this
3。中文編碼也有問題(這尼瑪很奇怪啊,從上面代碼來看,應該跟編碼不要緊纔對)編碼
咱們繼續看代碼,看看set存儲數據的時候有什麼特別的地方。咱們能夠看到對象和字符串的處理是不一樣的,嗯,估計問題就應該在這裏了,看代碼。3d
{"Name":"z中文","View":null,"list":[{"Name":"123","View":"asd"},{"Name":"2","View":"\\"}]} public static void WriteString(TextWriter writer, string value) { if (value == null) writer.Write("null"); else if (!JsonUtils.HasAnyEscapeChars(value)) { writer.Write('"'); writer.Write(value); writer.Write('"'); } else { char[] chArray = new char[4]; writer.Write('"'); int length = value.Length; for (int index = 0; index < length; ++index) { switch (value[index]) { case '\b': writer.Write("\\b"); break; case '\t': writer.Write("\\t"); break; case '\n': writer.Write("\\n"); break; case '\f': writer.Write("\\f"); break; case '\r': writer.Write("\\r"); break; case '"': case '\\': writer.Write('\\'); writer.Write(value[index]); break; default: if ((int) value[index] >= 32 && (int) value[index] <= 126) { writer.Write(value[index]); break; } else if ((int) value[index] < 55296 || (int) value[index] > 57343) { JsonUtils.IntToHex((int) value[index], chArray); writer.Write("\\u"); writer.Write(chArray); break; } else break; } } writer.Write('"'); } }
好了基本上找到緣由了,問題就出在方法中列出的」\,」」等特殊符號的問題,並且看((int) value[index] < 55296 || (int) value[index] > 57343)句話應該是對這個範圍以外的符號進行了轉碼,具體什麼轉碼方式小弟不清楚,
因此呢,解決辦法就來了,咱們是否是能夠在存儲以前將這些被視爲特殊符號,特殊處理的字符進行處理呢,而後區出來以後再解碼是否是就能夠了。
好了試一把。咱們就用UrlEncode試一下吧
System.Web.HttpUtility.UrlEncode( 「「, Encoding.UTF8); System.Web.HttpUtility. UrlDecode ( 「「, Encoding.UTF8); var aa = Newtonsoft.Json.JsonConvert.SerializeObject(tes); var jj = System.Web.HttpUtility.UrlEncode(aa, Encoding.UTF8); RedisManager.Execute(redis => redis.Set("niutaotao_cart", jj)); var ggg = RedisManager.Execute(redis => redis.Get<byte[]>("niutaotao_cart")); var ii = RedisManager.Execute(redis => redis.Get<string>("niutaotao_cart")); var zz = System.Web.HttpUtility.UrlDecode(ii,Encoding.UTF8);
看看結果
呵呵,尼瑪能夠了。這裏解決問題就能夠了,關於redis的存儲結構和實現,能夠學習下下面兩篇文章。
關於redis的存儲結構你們能夠看看(http://www.cnblogs.com/shanyou/archive/2012/09/04/2670972.html)
關於redis的實現能夠看看(http://www.searchtb.com/2011/05/redis-storage.html)
redis官網(http://www.redis.cn/)