Scala.js - 将Uint8Array转换为Array[Byte]

3
我应该如何在Scala.js中实现以下方法?
import scala.scalajs.js

def toScalaArray(input: js.typedarray.Uint8Array): Array[Byte] =
  // code in question
  

你到目前为止尝试了什么? - Gaël J
使用 toByte 调用的 map 似乎正在工作,但可能不是最优的。 - R A
请更新您的帖子,附上您尝试过的代码,并解释您正在寻找另一种解决方案。此外,最优解是基于哪些标准? - Gaël J
我将其发布为答案。我所说的优化是指性能。另外,我不太明白为什么需要toByte调用。 - R A
你需要一个 Array 吗?或者一个类似于 Seq 的足够类序列结构是否就足够了呢?因为可能有可能编写一个包装器类,实现 Seq 而不需要拷贝数组。 - Silvio Mayolo
显示剩余2条评论
3个回答

2
根据请求修改:简而言之
input.view.map(_.toByte).toArray

原始答案

我对Scala-js并不是非常熟悉,但我可以详细解释一些评论中出现的问题,并改进你的自我回答。

另外,我不太明白为什么需要 toByte 调用

class Uint8Array extends Object with TypedArray[Short, Uint8Array] 

Scala将Uint8Array视为Short的集合,而您期望它是一个Byte的集合。

Uint8Array的toArray方法说明:

该成员是通过在scala.scalajs.js.LowestPrioAnyImplicits中执行iterableOps方法由Uint8Array转换为IterableOps [Short]而添加的隐式转换。

因此,该方法返回一个Array [Short],然后您可以使用.mapShort转换为Byte

在您的答案中,您发布了

 input.toArray.map(_.toByte)

这种写法在技术上是正确的,但是它的缺点是会分配一个 Shorts 的中间数组。为了避免这种分配,你可以在数组的 .view 上执行 .map 操作,然后在视图上调用 .toArray

Scala (以及 Scala.js) 中的 Views 是轻量级对象,它们引用原始集合加上某种转换/过滤函数,可以像任何其他集合一样迭代。你可以组合许多转换/过滤器在一个 view 上而不必分配中间集合来表示结果。有关更多信息,请参见链接的文档页面。

input.view.map(_.toByte).toArray

根据您打算如何传递结果值,您甚至可能不需要调用.toArray。例如,如果您只需要稍后迭代元素,则可以将视图作为Iterable[Byte]传递而无需分配单独的数组。


谢谢您的解释。我会把实际代码答案放在顶部,以便更容易找到。 - R A
我进行了一些不科学的测试,发现使用视图比不使用视图慢大约1.8倍(至少在Node.js上)。 - R A

1

所有当前的答案都需要在用户空间中迭代数组。

Scala.js具有优化器支持的类型数组转换(事实上,Array[Byte]在现代配置中就是类型数组)。通过这样做,您可能会获得更好的性能:

import scala.scalajs.js.typedarray._

def toScalaArray(input: Uint8Array): Array[Byte] = {
  // Create a view as Int8 on the same underlying data.
  new Int8Array(input.buffer, input.byteOffset, input.length).toArray
}

需要额外的 new Int8Array 来将底层缓冲区重新解释为有符号的(Byte 类型是有符号的)。只有这样,Scala.js 才会提供内置的转换到 Array[Byte]

当查看生成的代码时,你会发现不需要用户空间循环:内置的 slice 方法用于复制 TypedArray。在性能方面,几乎肯定无法通过任何用户空间循环来超越它。

$c_Lhelloworld_HelloWorld$.prototype.toScalaArray__sjs_js_typedarray_Uint8Array__AB = (function(input) {
  var array = new Int8Array(input.buffer, $uI(input.byteOffset), $uI(input.length));
  return new $ac_B(array.slice())
});

如果我们将其与当前接受的答案进行比较(input.view.map(_.toByte).toArray),我们会发现有很大的区别(我的评论):

$c_Lhelloworld_HelloWorld$.prototype.toScalaArray__sjs_js_typedarray_Uint8Array__AB = (function(input) {
  var this$2 = new $c_sjs_js_IterableOps(input);
  var this$5 = new $c_sc_IterableLike$$anon$1(this$2);
  // We need a function
  var f = new $c_sjsr_AnonFunction1(((x$1$2) => {
    var x$1 = $uS(x$1$2);
    return ((x$1 << 24) >> 24)
  }));
  new $c_sc_IterableView$$anon$1();
  // Here's the view: So indeed no intermediate allocations.
  var this$8 = new $c_sc_IterableViewLike$$anon$6(this$5, f);
  var len = $f_sc_TraversableOnce__size__I(this$8);
  var result = new $ac_B(len);
  // This function actually will traverse.
  $f_sc_TraversableOnce__copyToArray__O__I__V(this$8, result, 0);
  return result
});

对我来说似乎不起作用(手动源数组值分配)scastie - R A
是的 :-/ 字节数组视图的参数设置错误了。现在已经修复了。 - gzm0
我已经进行了一些测试,并发现与我的其他方案相比,性能没有差异。两者都比“view”变体快约1.8倍。 - R A

0
import scala.scalajs.js

def toScalaArray(input: js.typedarray.Uint8Array): Array[Byte] =
  input.toArray.map(_.toByte)

这只是一个猜测,我实际上并没有尝试测试它。符号等是否都能正常工作?Short可以表示+255,但有符号字节不能(难道符号问题不是他们最初将其设为“Iterable [Short]”的原因吗?) - Andrey Tyukin
@AndreyTyukin 我只测试了-127这个数字的双向转换,结果很好。 - R A
Uint8Array 的可能值在 0 和 255 之间。数字 128-255 将被转换为负字节数字,但不会丢失数据(例如:255 映射到 -1)。 - R A

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