質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.34%

  • JavaScript

    17604questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

fromCharCode.applyの引数の制限を回避する方法

受付中

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 1,130

terater

score 2

前提・実現したいこと

元々がカンマ区切りのデータを引数にとって
String.fromCharCode.applyで処理をしている箇所があります。
ファイルのデータが少ない際には問題ないのですが、
一定の容量になるとエラーになります。

原因としてはapplyの引数に渡せる個数に制限があるため、
一定容量を超えると使えなくなるようでした。

この制限を回避する方法はないでしょうか。

試したこと

考えた事として制限としてかかっている数に分割して、
後に結合すればよいと単純に思いました。
ただしそのような処理をして正常な状態に戻るのかという疑問と、
バイナリデータ操作する方法が根本的によく分かりませんでした。

発生している問題・エラーメッセージ

SCRIPT XX: スタック領域が不足しています。

該当のソースコード

var input = String.fromCharCode.apply(null, this.sampledata)

補足情報(言語/FW/ツール等のバージョンなど)

ブラウザIE11

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • asahina1979

    2017/09/22 07:06

    引数がおおいんじゃなくデータ量が多いのだけどね(メモリ不足)

    キャンセル

  • think49

    2017/09/22 08:51

    asahina1979さん、あまり詳しくないのですが、スタックオーバーフローはスタック領域を超過した際に起きるものと理解しています。ざっくり、ググった限りでは、スタック領域はプログラム側で定めた固定サイズで確保するように読めました。fromCharCodeに限っては、空きメモリ容量に応じたスタック領域を動的に確保するように実装されているのでしょうか。 http://www.kab-studio.biz/Programing/JavaA2Z/Word/00000987.html

    キャンセル

  • Lhankor_Mhy

    2017/09/22 14:58

    手元のFirefoxで確認しましたら500000が上限でした。2^nでもない不自然にキリのいい数字なので、実装上の制限のような気がしますね。

    キャンセル

回答 4

+2

環境にもよるのかもしれませんが、ArrayBufferBlobとして、FileReaderAPIを使ったらエラーになりませんでした。

var sampledata= Array(1000000).fill(97);
//String.fromCharCode.apply(null, sampledata); ←これはエラー。
var blob = new Blob( [new Uint8Array(sampledata).buffer] );
var reader = new FileReader();
reader.onload = function(e){
    console.log(reader.result); 
};
reader.readAsText(blob);


でも、ウチのFirefoxさんは10万字に耐えたので現実的にはfromCharCodeで十分な気も……

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/23 13:58

    調べてみると、型付き配列(Typed Array)はバイナリデータを扱うために生まれたもののようでした。
    実装法としては、この回答がマッチしているのかも知れないですね。
    https://www.html5rocks.com/ja/tutorials/webgl/typed_arrays/

    キャンセル

+1

多少内容と用途がちがいますが、こちらで紹介されているような分割が現実的かもしれません

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/22 13:09

    あとはどのような実装をしており、どうやってデータを取得しているかをもう少し詳しく例示されたほうがいいと思います

    キャンセル

+1

この現象には詳しくありませんが、スタックオーバーフローと呼ばれる現象だと思います。
引数の数が多過ぎでスタック領域を使い尽くすケースは見つかりませんでしたが、それが理由であれば、一つずつ繰り返し処理をすれば、回避できると思います。

var input = this.sampledata.map(String.fromCharCode).join('');

Re: terater さん

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

引数の数の制限についてStack Overflowに質問があったので紹介します。

Is there a max number of arguments JavaScript functions can accept?

上記質問の各回答によると、仕様としては制限はありません(ES5.1で述べていますが、最新のES8の6.2.1The List and Record Specification Typesも同じ文言です)。しかし、各ブラウザ毎に実装上の制限は存在するようで、その制限値もブラウザによって異なります。また、スタック不足のエラーメッセージとなっていますが、単なるスタックオーバーフローであるとか、そう単純な話でも無いようです。

とりあえず、制限はあります。32767以下でないと安全とは言えないでしょう。では、バラバラに処理して、文字列を結合させて問題ないかというと問題ありません。下記のテスト用コードを実行してみてください。

"use strict";
function stringToCharCodeList(str) {
  return Array.prototype.map.call(str, function(c) {
    return c.charCodeAt(0);
  });
}

function charCodeListToString(list) {
  return String.fromCharCode.apply(null, list);
}

function charCodeListToStringSplit(list) {
  return list.reduce(function(str, code) {
    return str + String.fromCharCode(code);
  }, "");
}

function charCodeListToStringSplitFor(list) {
  var str = "";
  for (var i = 0, len = list.length; i < len; i++) {
    str += String.fromCharCode(list[i]);
  }
  return str;
}

function charCodeListToStringSlice(list, sliceSize) {
  var str = "";
  for (var i = 0, len = list.length; i < len; i += sliceSize) {
    var slicedList = list.slice(i, i + sliceSize);
    str += String.fromCharCode.apply(null, slicedList);
  }
  return str;
}

function checkConvert(str) {
  var list = stringToCharCodeList(str);
  var convertedStr = charCodeListToString(list);
  var splitConvertedStr = charCodeListToStringSplit(list);
  var splitForConvertedStr = charCodeListToStringSplitFor(list);
  var sliceConvertedStr = charCodeListToStringSlice(list, 8);
  console.log(str +
              "\n  list:  [" + list + "]" +
              "\n  bulk:  " + convertedStr +
              "\n  split: " + splitConvertedStr +
              "\n  spfor: " + splitForConvertedStr +
              "\n  slice: " + sliceConvertedStr);
}

checkConvert("Hello, World!");
checkConvert("こんにちは、世界!");
checkConvert("昨日、近所の𠮷野家行ったんです。𠮷野家。");
checkConvert("🏠帰ったら📞くれ");

charCodeListToStringSplit()が一個一個処理するバージョン、charCodeListToStringSplitForforを使った別実装、charCodeListToStringSlice()は指定のサイズにまとめて処理するバージョンです。どちらも正常に動くことが確認できると思います。なお、"𠮷"や"🏠"のようなU+10000以上になるためサロゲートペアで構成される文字が含まれる場合、途中不正な文字列が生成されることに注意してください。その後結合すれば問題はありません。

では、パフォーマンスはどうなのかです。ということでベンチマークテストを作りました。Benchmark.jsを入れてから試して見てください。

"use strict";
var Benchmark = require('benchmark')

function charCodeListToString(list) {
  return String.fromCharCode.apply(null, list);
}

function charCodeListToStringSplit(list) {
  return list.reduce(function(str, code) {
    return str + String.fromCharCode(code);
  }, "");
}

function charCodeListToStringSplitFor(list) {
  var str = "";
  for (var i = 0, len = list.length; i < len; i++) {
    str += String.fromCharCode(list[i]);
  }
  return str;
}

function charCodeListToStringSlice(list, sliceSize) {
  var str = "";
  for (var i = 0, len = list.length; i < len; i += sliceSize) {
    var slicedList = list.slice(i, i + sliceSize);
    str += String.fromCharCode.apply(null, slicedList);
  }
  return str;
}

var maxCharCode = 65535 // 2**16 - 1
var listSize = 32767;
var list = [];
for (var i = 0; i < listSize; i++) {
  list.push(Math.floor(Math.random() * (maxCharCode + 1)));
}

var suite = new Benchmark.Suite;
suite
  .add("Bulk", function() {
    charCodeListToString(list);
  })
  .add("Split", function() {
    charCodeListToStringSplit(list);
  })
  .add("Split For", function() {
    charCodeListToStringSplitFor(list);
  })
  .add("Slice 1", function() {
    charCodeListToStringSlice(list, 1);
  })
  .add("Slice 8", function() {
    charCodeListToStringSlice(list, 8);
  })
  .add("Slice 64", function() {
    charCodeListToStringSlice(list, 64);
  })
  .add("Slice 256", function() {
    charCodeListToStringSlice(list, 256);
  })
  .add("Slice 1024", function() {
    charCodeListToStringSlice(list, 1024);
  })
  .add("Slice 4096", function() {
    charCodeListToStringSlice(list, 4096);
  })
  .add("Slice 16384", function() {
    charCodeListToStringSlice(list, 16384);
  })
  .add("Slice 32767", function() {
    charCodeListToStringSlice(list, 32767);
  })
  .on('cycle', function(event) {
    console.log(String(event.target));
  })
  .run({ 'async': true });

手元のNode.jsでは次の結果になりました。

Bulk x 7,640 ops/sec ±3.31% (79 runs sampled)
Split x 505 ops/sec ±2.49% (76 runs sampled)
Split For x 502 ops/sec ±2.55% (75 runs sampled)
Slice 1 x 119 ops/sec ±2.77% (65 runs sampled)
Slice 8 x 740 ops/sec ±2.92% (76 runs sampled)
Slice 64 x 2,301 ops/sec ±2.49% (79 runs sampled)
Slice 256 x 3,166 ops/sec ±2.06% (80 runs sampled)
Slice 1024 x 3,461 ops/sec ±2.14% (80 runs sampled)
Slice 4096 x 3,455 ops/sec ±2.13% (79 runs sampled)
Slice 16384 x 3,392 ops/sec ±2.57% (77 runs sampled)
Slice 32767 x 3,398 ops/sec ±1.97% (78 runs sampled)

ops/secの値が大きいほど高速です。一個一個処理する場合は、もともと1/10以下になるなどかなり低速です。また、まとめるサイズが大きいほど、速くなっていきますが、1024をこえたあたりからあまり変わらなくなります。安全面を見て、1024,2048,4096あたりにするのが良いのかも知れません。ただ、他のブラウザでは最適なところが異なる場合がありますので、ご注意ください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • JavaScript

    17604questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。