将List<String>编码为普通字符串并解码回来的最简单方法是什么?

6

我认为我已经遇到过这种需求十几次了。但我从来没有找到令人满意的解决方案。例如,我有一组字符串,我想通过一个只允许纯字符串的通道(如磁盘或网络)进行序列化。

我几乎总是使用“split”和“join”,并使用类似于“:::==--==:::”这样荒谬的分隔符。

public static string encode(System.Collections.Generic.List<string> data)
{
    return string.Join(" :::==--==::: ", data.ToArray());
}
public static string[] decode(string encoded)
{
    return encoded.Split(new string[] { " :::==--==::: " }, StringSplitOptions.None);
}

但是这个简单的解决方案显然有一些缺陷。该字符串不能包含分隔符字符串。因此,编码后的字符串无法再次重新编码。
据我所知,全面的解决方案应该涉及在编码时转义分隔符,在解码时取消转义。虽然问题听起来很简单,但我相信完整的解决方案可能需要大量的代码。我想知道是否有任何技巧可以让我用非常少的代码构建编码器和解码器?
12个回答

6

添加一个对System.Web的引用和using,然后:

public static string Encode(IEnumerable<string> strings)
{
    return string.Join("&", strings.Select(s => HttpUtility.UrlEncode(s)).ToArray());
}

public static IEnumerable<string> Decode(string list)
{
    return list.Split('&').Select(s => HttpUtility.UrlDecode(s));
}

大多数编程语言都有一对实用函数,可以进行Url“百分比”编码,这在此类情况下非常理想,可以重复使用。


嗯,也许我可以原谅某个人重新发明轮子,但是重新发明字符串转义已经太过分了! - Daniel Earwicker
已投票。顺便说一句,UrlEncode 绝非我喜欢的解决方案。它会将我的本地字符编码为 %NN。 - Sake
谢谢。当然可以,但这取决于你想要什么。如果你想要一个可读性强的数据流来表示一些特定的字符集,那么你需要编写自己的编码/解码程序。如果你想要快速解决问题,而不关心数据流的外观,那么可以使用URL编码。 - Daniel Earwicker
加一 - 比我之前做的 XML 变体更简单。 - ShuggyCoUk

5
你可以在List<>上使用.ToArray属性,然后将数组序列化 - 然后可以将其转储到磁盘或网络,并在另一端进行反序列化以重新构建。这样做的代码不会太多,并且您可以使用已在.net框架中测试和编码的序列化技术。

很遗憾,该通道只允许“字符串”。而我总是希望输出是“可读的”。 - Sake
2
.Net 序列化将转为字符串。 - Joel Coehoorn

4

您可能需要了解CSV文件的格式。

  • 转义字符串中所有分隔符,例如 "
  • 将列表中的每个项目用 "item" 包含起来
  • 使用简单的分隔符(如 ,)进行连接

我认为这个问题没有完美的解决方案。


3

这里是一种老派的技术,可能很适用 -

通过在每行的开头存储字符串数组的宽度作为固定宽度前缀来序列化。

因此

 string[0]="abc"
 string[1]="defg"
 string[2]=" :::==--==::: "

变得

0003abc0004defg0014 :::==--==::: 

...其中前缀的大小足够大,以适应字符串的最大长度


为什么我以前想不出这个解决方案!好棒的解决方案! - Sake
1
我喜欢这个,你可以稍微修改它,使其是大小加分隔符,这样你就可以处理任何大小的前缀,例如:3|abc4|defg6|--==--。 - Jacob Stanley
你也可以在字符串后面添加另一个分隔符,即使它不是解析所必需的,只是为了更容易阅读。例如3abc4defg6--==--或者3:abc:4:defg:6:--==--。最后一个看起来像php的serialize()函数 :) a:1:{s:5:"Hello";s:5:"World";} - Jacob Stanley

2
你可以使用XmlDocument来处理序列化。这将为您处理编码。
public static string encode(System.Collections.Generic.List<string> data)
{
    var xml = new XmlDocument();
    xml.AppendChild(xml.CreateElement("data"));
    foreach (var item in data)
    {
        var xmlItem = (XmlElement)xml.DocumentElement.AppendChild(xml.CreateElement("item"));
        xmlItem.InnerText = item;
    }
    return xml.OuterXml;
}

public static string[] decode(string encoded)
{
    var items = new System.Collections.Generic.List<string>();
    var xml = new XmlDocument();
    xml.LoadXml(encoded);
    foreach (XmlElement xmlItem in xml.SelectNodes("/data/item"))
        items.Add(xmlItem.InnerText);
    return items.ToArray();
}

有时候我会按照你建议的去做。但在大多数情况下,这是过度杀伤力的。 - Sake

2

Json.NET 是一种非常简单的序列化任何对象的方式。JSON 使事物紧凑,并且比 XML 更快。

List<string> foo = new List<string>() { "1", "2" };
string output = JsonConvert.SerializeObject(foo);
List<string> fooToo = (List<string>)JsonConvert.DeserializeObject(output, typeof(List<string>));

2
如果您愿意使用两个字符长的分隔符,那么这个操作可以更简单地完成:
在Java代码中:
StringBuilder builder = new StringBuilder();
    
for(String s : list) {
  if(builder.length() != 0) {
    builder.append("||");
  }
  builder.append(s.replace("|", "|p"));
}

并且回到原处:

for(String item : encodedList.split("||")) {
  list.add(item.replace("|p", "|"));
}

2
我会在每个字符串前加上它的长度和一个指示长度结束的终止符。请注意保留HTML标签。
abc
defg
hijk
xyz
546
4.X
变成

3: abc 4: defg 4: hijk 3: xyz 3: 546 3: 4.X

这样做没有限制或限制,并且非常简单易行。

1

你不应该手动完成这个任务。正如其他答案所指出的那样,有很多内置或其他方式可以进行序列化/反序列化。

然而,如果你决定自己完成这项工作,它并不需要太多代码:

public static string CreateDelimitedString(IEnumerable<string> items)
{
    StringBuilder sb = new StringBuilder();

    foreach (string item in items)
    {
        sb.Append(item.Replace("\\", "\\\\").Replace(",", "\\,"));
        sb.Append(",");
    }

    return (sb.Length > 0) ? sb.ToString(0, sb.Length - 1) : string.Empty;
}

这将用逗号 (,) 分隔项目。任何现有的逗号都将用反斜杠 (\) 转义,任何现有的反斜杠也将被转义。

public static IEnumerable<string> GetItemsFromDelimitedString(string s)
{
    bool escaped = false;
    StringBuilder sb = new StringBuilder();

    foreach (char c in s)
    {
        if ((c == '\\') && !escaped)
        {
            escaped = true;
        }
        else if ((c == ',') && !escaped)
        {
            yield return sb.ToString();
            sb.Length = 0;
        }
        else
        {
            sb.Append(c);
            escaped = false;
        }
    }

    yield return sb.ToString();
}

@Sake,我并不是立刻就写出来的,虽然也没花太长时间。(实际上,我几天前就写好了,作为对另一个SO问题的回答。) - LukeH
@Luke,但你更喜欢Earwicker的解决方案吗?为什么? - Sake
@Sake,正如我在答案开头所说的,现在你不需要手动序列化/反序列化。URL编码是经过验证的,并且(大多数情况下)易于阅读。在大多数现代语言/框架中,只需要一两行代码即可完成。 - LukeH
我认为可以更简单地实现这个。只需替换一个字符(例如将 | 替换为 |s),然后使用 || 作为分隔符。在拆分时,只需按任何找到的 || 进行拆分。之后,通过将 |s 替换为 | 来取消转义拆分字符串。 - john16384

0

为什么不使用Xstream进行序列化,而要重新发明自己的序列化格式呢?

这很简单:

new XStream().toXML(yourobject)

没问题。这个问题不涉及具体的编程语言。我个人更偏向使用 Python 作答。我使用 C# 来吸引更多的读者。 - Sake

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接