20

所以我正在尝试将二进制文件转换为字符串。这段代码:

t = [{<<71,0,69,0,84,0>>}]
String.from_char_list(t)

但是当我尝试这种转换时,我得到了这个:

** (ArgumentError) argument error
    (stdlib) :unicode.characters_to_binary([{<<70, 0, 73, 0, 78, 0>>}])
    (elixir) lib/string.ex:1161: String.from_char_list/1

我假设 <<70、0 等可能是一个字形列表(它是 API 调用的返回,并且 API 没有完全记录)但是我需要以某种方式指定编码吗?

我知道我可能会遗漏一些明显的东西(也许这不是正确使用的功能?)但我似乎无法弄清楚该怎么做。


编辑:

值得一提的是,上面的二进制文件是 Erlang ODBC 调用的返回值。经过一番挖掘后,我发现有问题的二进制文件实际上是“编码为 UTF16 little endian 的 Unicode 二进制文件”(参见此处:http : //www.erlang.org/doc/apps/odbc/odbc.pdf 第 9 页re: SQL_WVARCHAR) 并没有真正改变问题,但它确实增加了一些上下文。

4

7 回答 7

27

这里有几件事:

1.) 你有一个包含一个元素的元组列表,一个二进制。您可能可以只提取二进制文件并拥有您的字符串。将当前数据结构传递给to_string是行不通的。

2.) 您在示例中使用的二进制文件包含0一个不可打印的字符。在 shell 中,这将无法正确打印为字符串,因为当表示字符串的二进制文件包含不可打印的字符时,Elixir 无法区分二进制文件和表示字符串的二进制文件。

3.) 您可以使用模式匹配将二进制文件转换为特定类型。例如:

iex> raw = <<71,32,69,32,84,32>>
...> Enum.join(for <<c::utf8 <- raw>>, do: <<c::utf8>>)
"G E T "
...> <<c::utf8, _::binary>> = raw
"G"

此外,如果您从网络连接获取二进制数据,您可能想要使用:erlang.iolist_to_binary,因为数据将是 iolist,而不是 charlist。不同之处在于 iolist 可以包含二进制文件、嵌套列表,也可以是整数列表。Charlists 总是只是一个扁平的整数列表。如果您to_string在 iolist 上调用 , 它将失败。

于 2014-03-19T22:27:53.420 回答
6

我做了一个函数将二进制转换为字符串

def raw_binary_to_string(raw) do
   codepoints = String.codepoints(raw)  
      val = Enum.reduce(codepoints, 
                        fn(w, result) ->  
                            cond do 
                                String.valid?(w) -> 
                                    result <> w 
                                true ->
                                    << parsed :: 8>> = w 
                                    result <>   << parsed :: utf8 >>
                            end
                        end)

  end

在 iex 控制台上执行

iex(6)>raw=<<65, 241, 111, 32, 100, 101, 32, 70, 97, 99, 116, 117, 114, 97, 99, 105, 111, 110, 32, 65, 99, 116, 117, 97, 108>>
iex(6)>raw_binary_to_string(raw)
iex(6)>"Año de Facturacion Actual"
于 2017-03-11T07:01:22.783 回答
4

不知道 OP 是否已经解决了他的问题,但是关于他关于他的二进制文件的评论utf16-le:特别是那个编码,我发现最快的(对于那些更有经验的 Elixir,可能是hacky)方法是使用Enum.reduce

# coercing it into utf8 gives us ["D", <<0>>, "e", <<0>>, "v", <<0>>, "a", <<0>>, "s", <<0>>, "t", <<0>>, "a", <<0>>, "t", <<0>>, "o", <<0>>, "r", <<0>>]
<<68, 0, 101, 0, 118, 0, 97, 0, 115, 0, 116, 0, 97, 0, 116, 0, 111, 0, 114, 0>>  
|> String.codepoints()
|> Enum.reduce("", fn(codepoint, result) ->
                     << parsed :: 8>> = codepoint
                     if parsed == 0, do: result, else: result <> <<parsed>>
                   end)

# "Devastator"
|> IO.puts()

假设:

  • utf16-le编码

  • 代码点向后兼容,utf8即它们仅使用 1 个字节

因为我还在学习 Elixir,所以我花了一段时间才找到这个解决方案。我查看了人们制作的其他库,甚至使用iconv了 bash 级别的库。

于 2016-09-20T18:25:48.217 回答
2

最后一点确实改变了问题,并解释了它。Elixir 使用二进制文件作为字符串,但假定并要求它们是 UTF8 编码的,而不是 UTF16。

于 2014-03-21T14:13:33.273 回答
2

参考http://erlang.org/pipermail/erlang-questions/2010-December/054885.html

您也可以:unicode.characters_to_list(binary_string, {:utf16, :little})用来验证结果和存储

IEX 评估

iex(1)> y                                                
<<115, 0, 121, 0, 115, 0>>
iex(2)> :unicode.characters_to_list(y, {:utf16, :little})
'sys'

注意:打印的值sys<<115, 0, 121, 0, 115, 0>>

于 2018-10-26T18:10:49.100 回答
2

Ecto.UUID.load/1将二进制转换为字符串并返回一个元组:

binary = Ecto.UUID.bingenerate()
<<99, 148, 189, 126, 144, 154, 71, 236, 160, 110, 149, 143, 67, 162, 177, 192>>

Ecto.UUID.load(binary)
{:ok, "6394bd7e-909a-47ec-a06e-958f43a2b1c0"}

信用: https ://stackoverflow.com/a/43530427/2091331

于 2021-10-11T19:42:33.020 回答
1

你可以使用理解

    defmodule TestModule do
      def convert(binary) do
        for c <- binary, into: "", do: <<c>>
      end
    end
    TestModule.convert([71,32,69,32,84,32]) |> IO.puts
于 2020-01-20T00:16:46.903 回答