正常文本中使用最少的分隔符字符 < ASCII 128

108

由于编码原因(我感到非常尴尬,不敢说出来),我需要将多个文本项目存储在单个字符串中。

我将使用一个字符来分隔它们。

哪个字符最适合用于此,即哪个字符最不可能出现在文本中?必须是可打印的,并且可能小于128的ASCII以避免区域设置问题。


69
请不要难为情。你应该忽略那些说“哦,那是一种糟糕的方式,做这个替代品更好”的人。回答者的责任是回答如何做,而不是质疑为什么要这样做。我不在乎你为什么会陷入这种境地,我自己也经历过几次。祝你好运! - Iain Holder
2
我曾经遇到过同样的问题...在查找或堆栈溢出之前,我选择了PIPE...因为我喜欢它看起来像一个瘦人。 - user656925
1
这取决于文本的类型。某些类型的文本很少使用制表符,所以我经常采用这种方式。但其他类型的文本,包括源代码,通常会使用它。你不能对源文本进行一些统计吗?你不能在源文本中添加转义字符,从而使用任何你喜欢的分隔符吗? - hippietrail
1
不去问,不去尝试,比因为任何问题而感到尴尬要糟糕得多。我在这里寻找同样的答案,我为自己能有其他人与我分享同样的问题感到自豪 :) - Teoman shipahi
1
对于那些可能在文本中有 | 的人,我实际上遇到过这样的情况,我需要尽可能地减少字符数量。由于大多数字段都是带有有趣文本的字符串,CSV 由于转义过多而无法使用。我们的字段分隔符是 /|。斜杠只是适度常见,但与管道配对使用时永远不会遇到它。我一直在使用一个引擎,每天都会传递大量数据。这从未出现过问题,我从未需要封装单个字符串或转义特殊字符。平均而言,这种机制为我们节省了一些文本百分比。 - RLH
17个回答

67

我会选择ASCII码的“单元分隔符”——ASCII 31(0x1F)。

在非常古老的时代,大多数事情都是串行完成的,没有随机访问。这意味着一些控制代码被嵌入到ASCII中。

ASCII 28 (0x1C) File Separator - Used to indicate separation between files on a data input stream.
ASCII 29 (0x1D) Group Separator - Used to indicate separation between tables on a data input stream (called groups back then).
ASCII 30 (0x1E) Record Separator - Used to indicate separation between records within a table (within a group).  These roughly map to a tuple in modern nomenclature.
ASCII 31 (0x1F) Unit Separator - Used to indicate separation between units within a record.  The roughly map to fields in modern nomenclature.

单元分隔符是ASCII中的一个字符,Unicode支持显示它(通常在相同字形中显示作为"us"),但许多字体无法正确显示它。

如果您必须显示它,建议将其解析为字段后在应用程序中进行显示。


6
哇,谢谢。这正是我正在寻找的东西。 - Theunis
我正在做一个控制台仪表板,其中有一些类似CSV的处理管道,这就像奇迹般地拯救了我!谢谢! - João Ciocca
这真是太好了。这基本上意味着可以在ASCII中编写格式良好的4D表格,而不必转义任何内容,也不必担心用户数据是否包含分隔符。用户数据不会包含 0x1C-0x1F,对吧? - Eric Duminil
1
如果您将用户数据清理为仅包含可打印字符,则 0x1c-0x1F 将被排除在外(以及许多其他项目)。 没有某种形式的数据净化,即使我怀疑他们通常不会这样做,有创意的用户也可能混入那些字符。 它将是SQL注入攻击的文本等效物。 请注意,您的4个数据维度并不意味着维度是均匀的。 可以有一个记录比之前的记录具有更多的单位,或者更少的单位。 - Edwin Buck

39
假设由于某些尴尬的原因,您不能使用CSV格式,那么建议考虑数据。获取一些样本数据,并对每个值进行简单的字符计数,范围为0-127。选择其中一个不出现的字符作为分隔符。如果选择太多,请获取更大的数据集。编写此程序不需要花费太多时间,您将得到最适合自己的答案。
答案因问题域的不同而异,因此在shell脚本中常用|(管道符),在数学公式中常用^(乘方符号),其他大多数字符应该也是如此。
我个人认为,如果可以选择的话,我会选择|(管道符),但使用真实数据是最安全的。
无论您采取何种方法,请确保已经制定了转义方案!

我不会在这里嘲笑。在Magento 2产品导出中,它们将许多属性合并到名为“additional_attributes”的CSV单列中。 - tread
1
为什么不直接将文本中的所有制表符替换为四个空格,并使用制表符\t作为分隔符呢? - Elie G.
使用其他东西而不是CSV有很好的理由:由于引用字段,CSV文件不易与像cut和awk这样的Unix工具兼容。一个单独的字符,它在其他地方没有出现并且可以键入,很容易成为首选。认识到这一点并不会让人感到“尴尬”。 - Chris L. Barnes
谢谢提醒,我有一个关于分隔符的问题,找到了我正在寻找的理想转义字符,用于我正在实现的自定义协议。 - Luctins

25

在使用不同语言时,这个符号:¬

被证明是最好的。不过我还在测试中。


1
我喜欢这个想法,但我很好奇你是否能够使用cut命令来处理包含字符串“Billy”、“Car”、“Red”、“Garage”、“3”的文件(即$ cut -d“¬”-f1 myfile.delim)。 - blehman
我将此问题添加到了Stack Overflow上:https://dev59.com/Y3jZa4cB1Zd3GeqPitfi - blehman
not sign 的 charcode 172 不是 ASCII,而是 CP-1252。 - milahu

22

可能是 | 或 ^ 或 ~ ,您也可以组合两个字符


12
使用两次相同的符号可以避免任何误解,例如 || 或 ##。 - roel

17
你说“可打印字符”,但这可能包括制表符(0x09)或换页符(0x0c)等字符。对于分隔文件,我几乎总是选择选项卡而不是逗号,因为逗号有时会出现在文本中。
(有趣的是,ASCII表中有组、记录和单元分隔符的GS(0x1D)、RS(0x1E)和US(0x1F)字符,无论它们是/曾经是什么。)
如果你所说的“可打印”的字符是用户可以识别并轻松输入的字符,我会首选管道|符号,还有其他一些奇怪的字符(@~^\ 或反引号,我似乎无法在这里输入)。这些字符+=! $%&*() -'“:;<>,.? / 看起来更可能出现在用户输入中。至于下划线_和井号#以及括号{}[],我不知道。

14
标准ASCII码表中确实包含了四个专门设计用于此目的的控制码,就像Jason S上面提到的一样。它们是:28 FS文件分隔符,29 GS组分隔符,30 RS记录分隔符,31 US单元分隔符。不幸的是,几乎没有人使用它们,尽管这正是它们的初衷。我个人讨厌CSV格式文件,因为很多人不考虑周全,弄得我们程序员必须处理他们的文件格式混乱。 - deegee
3
@deegee 这可能是这里最好的答案了。除非数据包含二进制或非标准的ASCII/Unicode,否则这种方法在任何语言中都适用。你应该将其转化为一个普通回答。 - dhj
@rahul,你有标记此为已接受答案的权限吗?在处理充斥着垃圾用户输入数据时非常有用。提醒其他人:在Windows中使用ALT+31可以得到美国字符(0x1F)。 - golfalot

16

你考虑使用CSV格式吧?在标准的CSV格式中,字符可以被转义,并且已经有很多解析器可用。


我喜欢这个比我的想法更好。+1。 - Iain Holder
我认为在普通文本中逗号算作普通字符。如果只是使用CSV文件那么就没有必要提出这个问题了…… - Jay
CSV处理逗号以及其他一些问题,因此在文本中已经存在逗号并不重要。如果我没记错,CSV将用引号括起文本并转义引号。 - Jeremy French
@Jeremy:完全正确。这里有一篇维基百科文章提到了转义方案的工作原理:http://en.wikipedia.org/wiki/Comma-separated_values - rmeador
1
直白地说:CVS 将处理你没有想到的所有问题,并确保你不必每两周修复你的“解决方案”,因为它会因某些未预料到的输入而出现故障。 - Aaron Digulla
我假设(也许是错误的)数据没有被转义,并且由于某种原因,数据源的控制不足以确保它将被正确转义。否则,当然最好使用现有的库。 - Jay

9

我使用以下方法来进行快速转义:假设您想要连接str1、str2和str3,我所做的是:

delimitedStr=str1.Replace("@","@a").Replace("|","@p")+"|"+str2.Replace("@","@a").Replace("|","@p")+"|"+str3.Replace("@","@a").Replace("|","@p");

然后要检索原始数据,请使用:

splitStr=delimitedStr.Split("|".ToCharArray());
str1=splitStr[0].Replace("@p","|").Replace("@a","@");
str2=splitStr[1].Replace("@p","|").Replace("@a","@");
str3=splitStr[2].Replace("@p","|").Replace("@a","@");

注意:替换的顺序很重要。
它是不可破坏的且易于实现。

3
在我的看法中,这确实是最好的答案,也是唯一正确的答案。它是唯一一个不能被破解的答案。所有其他答案只能降低输入破坏格式的概率,但这是非常不好的方法。所选择的答案正确地提到了使用像这样的转义方案-但一旦你这样做,分隔符的选择基本上是无关紧要的。 - Alfie
分隔符并不是完全无关紧要的。如果你选择一个常见字符——比如空格或字母“e”——你的转义字符串会变得相当长,而且很难读懂。最好选择一个不常见的字符,这就是为什么我仍然更喜欢使用管道符号来处理这种情况。 - fool4jesus

9

你能使用管道符号吗?在逗号或制表符分隔的字符串之后,这通常是下一个最常用的分隔符。大多数文本不太可能包含管道符号,ord('|')对我返回124,因此它似乎符合您的要求。


3
管道大胜利!|

3

我们使用 ASCII 0x7f,这是一种伪可打印字符,在正常使用中几乎不会出现。


1
为什么不选择这个,这显然是最好的答案。 - user1034912

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