あらためて理解するArrayBuffer - JavaScriptでバイナリデータを扱う方

97

JavaScriptのArrayBufferはWebGLなどでまとまったバイナリデータを扱う際に使用されています。ArrayBufferという用語だけ聞くとピンとこない人もいるかもしれません。開発者が主に目にするのは、Float32ArrayUint16ArrayといったTypedArray(型付き配列)の形式です。こちらであれば見覚えがあるという方もいるでしょう。実はFloat32ArrayUint16Arrayの本体は、ArrayBufferというバイナリデータを扱うためのオブジェクトなのです。

本記事では、ArrayBufferについて深く理解し、正しく使用できるようになることを目指します。そのことで、省メモリーなウェブアプリケーションの開発に役立つはずです。

主な対象読者

  • TypeScriptの型推論でArrayBufferTypedArrayが引数に求められるので、よくわからず参考サイトのコードをコピペしている
  • ArrayTypedArrayとの違いを詳しく知りたい

なぜArrayBufferが必要なのか

コンピューターは、内部的にすべてのデータをバイナリ形式、すなわち「0」と「1」の二進数で表現しています。これは、電気的な信号のオフ(0)とオン(1)を組み合わせて情報を処理するためです。

バイナリ形式は、コンピューターがデータを効率的かつ高速に処理するために最適化されています。また、バイナリ形式はデータサイズを小さく保てるため、保存容量の節約や通信の効率化にもつながります。コンピューターはすべてのデータをバイナリ形式で扱うことで、その高い計算能力を実現しています。

ArrayBuffer登場以前のJavaScriptのデータ型や構造では、バイナリデータを効率的に扱うことが困難でした。しかし、ネットワークの高速化やウェブ技術の進化にともなって、以下のような複雑で大きなデータを処理する必要性が高まりました。

  • メディアデータの操作:画像・動画・音声データの生成・編集・解析
  • ファイル操作:ユーザーがアップロードしたファイルの解析。ファイルの生成、ダウンロードなど
  • ネットワーク通信:データの送受信や解析
  • グラフィックス処理:WebGLを用いた高度なグラフィックス描画

そこで、バイナリデータを効果的に操作するための仕組みとして、ArrayBufferがECMAScript 2015で導入されました。

ArrayBufferTypedArray

ArrayBufferとは、バイナリデータそのものをメモリー上に確保するためのオブジェクトです。1バイト(8ビット)を単位とし、複数のバイト列が並んだデータのかたまりとなっています。

実は、ArrayBufferそれ自体にはバイナリデータを1バイトずつ管理する機能しかなく、データ型の概念もありません。バイナリデータに意味をもってアクセスするにはTypedArrayオブジェクトが必要になります。

// 16バイトのメモリー領域を確保
const array = new ArrayBuffer(16);

// ArrayBufferからデータを読んだり書き込むことはできない
console.log(array[0]); // undefined

TypedArrayArrayBufferのバイナリデータを数バイト単位で特定のデータ型として解釈し、配列風のインターフェイスを提供するための「ビュー」オブジェクトです。実際には、TypedArrayという名前のクラスやAPIはありません。Float32ArrayUint8Arrayといった、型ごとに管理する配列風ビューオブジェクトのAPIの総称です。

次の図版では、同じArrayBufferインスタンスをUint8ArrayUint32Arrayの2通りのTypedArrayで参照した場合のイメージを示しています。

画像:ArrayBufferとTypedArray(Uint8Array)

画像:ArrayBufferとTypedArray(Uint32Array)

TypedArrayはコンストラクターの引数にArrayBufferを指定することで、そのArrayBufferを参照するTypedArrayオブジェクトを作成します。

また、単に配列の要素数を指定してTypedArrayのコンストラクターを呼び出すことでも作成できます。しかし、内部では上記の書き方と同じようにArrayBufferが作られ、管理されます

// 16バイトのメモリー領域を確保
const array = new ArrayBuffer(16);

// ArrayBufferを8ビット符号なし整数で解釈するためのTypedArrayを作成
const uint8A = new Uint8Array(array);
// TypedArrayには配列アクセス演算子[]を使用して各要素に決まった型のデータを読み書きできる
uint8A[0] = 100;

// 8ビット符号なし整数を16個格納できるTypedArrayを作成
// 内部では16バイトのArrayBufferが自動的に作られる
// この作成方法は上の例とほぼ同じ動きをする
const uint8B = new Uint8Array(16);

TypedArrayに格納できる代表的なデータ型には下記のような種類があります。TypedArrayには他にもたくさんの種類があります。詳しくは『TypedArray オブジェクト | MDN』を参照ください。

TypedArray 格納できるデータ型 値の範囲 バイト数
Uint8Array 8ビット符号なし整数 0 〜 255 1
Uint8ClampedArray 8ビット符号なし整数 0 〜 255(*1) 1
Float32Array 32ビット浮動小数点数 およそ-3.4x10^38 〜 3.4x10^38 4
Float64Array(*2) 64ビット浮動小数点数 およそ-1.8x10^308 〜 1.8x10^308 8

(*1)範囲外のデータを設定すると、この範囲に補正される。

(*2)JavaScriptにおける数値型はNumberしかありません。Numberは内部的には64ビット浮動小数点数で、整数も小数もすべてNumber型で扱います。Numberと同じ精度のTypedArrayを使いたい場合は、Float64Arrayを選択します。

大事な点として、TypedArrayArrayBufferを解釈するためのビューに過ぎないことをもう一度強調しておきます。100MBのUint8Arrayを作成したところで、実際にメモリーを確保しているのは中身のArrayBufferです。

コラム:任意のデータ型でArrayBufferにアクセスできるDataView

ArrayBufferにアクセス可能な「ビュー」として、TypedArrayの他にDataViewがあります。DataViewArrayBufferに任意のデータ型としてアクセス可能です。また、複数バイトにアクセスするデータのエンディアンを実行環境にかかわらず指定できます。

※エンディアンとは、メモリー上で複数バイトを解釈する際のバイトの並び順です。

コラム:TypedArrayの新しいデータ型

基本的なデータ型はECMAScript 2015で追加されましたが、その後もTypedArrayの種類が追加されているので紹介します。

BigInt64Array

BigInt64Arrayは64ビット符号付き整数を扱えるデータ型です。JavaScriptのNumberでは表現しきれない大きな整数を正確に扱えます。BigInt64ArrayはECMAScript 2020でBigIntとあわせて追加されました。BigInt64Arrayから得られる数値はBigIntですので、そのままではNumberとの演算は行えないことに注意が必要です。

参考記事:JavaScriptのモダンな書き方- ES2020のオプショナルチェーン、null合体演算子、動的import、globalThis等を解説

Float16Array

Float16Arrayは16ビット浮動小数点数を扱えるデータ型です。16ビット浮動小数点数は、ディープラーニングなどのAIの計算でよく使われます。Float32Arrayと比べると精度が落ちるものの、データ量が半分です。そのため、使用するメモリー容量や帯域が半分で済み、演算も高速に実行可能です。

Float16ArrayはChrome 135、Edge 135(2025年4月)、Safari 18.2(2024年12月)、Firefox 129(2024年8月)で利用可能です。ただし、2025年4月執筆時点では、ECMAScriptの仕様になっておらず、TC39でStage 3の段階です。

TypedArrayの作成方法

TypedArrayについて、作成方法と注意点を詳しく説明します。方法によって内部のArrayBufferの状態が異なります。とくに、ArrayBuffer新しく作成するのか、しないのかについて焦点をあてています。

ArrayBufferが新しく作成されると、メモリーの容量がサイズ分確保されます。不要にメモリーを消費しないよう注意が必要です。また、元のArrayBufferおよびTypedArrayの変更の影響を受けるかどうかにもかかわってきます。正しくデータ処理を行う上でハマりやすいポイントですので、意識しましょう。

new演算子(コンストラクター)

コンストラクターからnew演算子でTypedArrayを作成する際、引数によってArrayBufferを新しく作成するのか、しないのかが異なります

A:引数に要素数を指定する

対応したデータ型のバイト数を要素数分確保したArrayBuffer内部で自動生成されます。

B:引数に配列を指定する

対応したデータ型のバイト数を指定した配列の要素数分確保したArrayBuffer内部で自動生成されます。

C:引数にArrayBufferを指定する

内部で新しくArrayBufferは作成されず、コンストラクターで渡したArrayBufferを解釈するTypedArrayを作成します。

// A:32ビット浮動小数点数の配列を4要素確保。内部では16バイトのArrayBufferが作成される
const float32A = new Float32Array(4);

// B:32ビット浮動小数点数の配列を4要素、値を指定して確保
// 内部では16バイトのArrayBufferが作成される
const float32B = new Float32Array([0.25, 0.5, 0.75, 1.0]);

// 16バイトのメモリー領域を確保
const array = new ArrayBuffer(16);
// C:渡した16バイトのArraybufferを32ビット浮動小数点数の配列として解釈
// float32Cは4要素の配列になる
const float32C = new Float32Array(array);

同じArrayBufferから複数のTypedArrayを作成することも可能です。このとき、データの本体であるArrayBufferは共有しています。そのため、ひとつのTypedArrayを変更すると、他のTypedArrayに影響します。

// 16バイトのメモリー領域を確保
const array = new ArrayBuffer(16);

// ArraybufferからFloat32Arrayを作成
const float32A = new Float32Array(array);
// 同じArraybufferから別のFloat32Arrayを作成
const float32B = new Float32Array(array);

// すべてのArrayBufferは同一
console.log(float32A.buffer === array); // true
console.log(float32B.buffer === float32A.buffer); // true

// ArrayBufferを共有している場合、一方の変更は他方にも反映される
float32A[0] = 0.25;
console.log(float32B[0]); // 0.25

slice()メソッド

作成済みのTypedArrayインスタンスからslice()メソッドで新しいTypedArrayを作成できます。引数で切り出す要素の範囲を指定します。このとき、作成元のArrayBufferは共有せず、新しいArrayBufferが作成されます

subarray()メソッド

作成済みのTypedArrayインスタンスからsubarray()メソッドで新しいTypedArrayを作成できます。引数で切り出す要素の範囲を指定します。このとき、作成元のArrayBufferを共有するため、新しいArrayBufferは作成されません

以上をまとめると下記のようになります。TypedArrayを作成する際にArrayBufferが内部で新しく作られるか、元のArrayBufferを共有するかはメモリー効率の大事なポイントです。

TypedArrayの作成方法 ArrayBufferの作成・共有 ArrayBufferの中身
new TypedArray(要素数) 新規作成される 初期値で埋められる
new TypedArray(配列) 新規作成される 引数の配列が要素に代入される
new TypedArray(ArrayBuffer) 引数のArrayBufferを共有する 引数のArrayBufferを引き継ぐ(変更されない)
slice(start, end) 新規作成される 元のArrayBufferの値が複製され代入される
subarray(begin, end) 作成元のArrayBufferを共有する 元のArrayBufferを引き継ぐ(変更されない)

配列(Array)との比較

JavaScriptにはTypedArrayとは別に配列(Array)の違いを説明します。

ArrayTypedArrayと比べると柔軟で汎用性が高いです。しかし、Arrayは内部で動的なメモリー管理を行うため、データ処理や転送を高い頻度で行う場合にはオーバーヘッドが発生する可能性があります。

TypedArrayは長さが固定であり、バイト単位のデータ格納が可能なため、メモリーアクセスが効率的に行われます。数値計算においてもバイナリデータを直接操作できるので、高いパフォーマンスが期待できます。また、メモリー領域が連続しているので高速なデータの転送が可能です。

大量のデータを処理する数値計算の場合にはTypedArrayで管理するとデータのまとめた取り回しが楽になります。逆にデータの規模が小さい時は汎用的なArrayが良いでしょう。

TypedArrayは決まった型の数値しか格納できない

Arrayは数値、文字列、オブジェクトなど、任意のデータを混在させて格納できます。

// 配列は数値や文字列、オブジェクトを混在できる
const array = [10, "hello", { key: "value" }];

TypedArrayは決まった型の数値しか格納できません。

// 32ビット浮動小数点数が10個格納できるメモリー領域を確保
const float32 = new Float32Array(10);

// Float32Arrayに32ビット浮動小数点数を格納
float32[0] = 0.5;

// 数値として表現できないオブジェクトを代入するとNaNが格納される
float32[1] = "文字列"; // a[1] = NaN

TypedArrayはサイズを変更できない

Arrayは配列サイズの変更が容易に可能です。

const array = [10, "hello", { key: "value" }];
console.log(array.length); // 3

// 配列はサイズの変更が簡単にできる
array.push("newValue");
console.log(array.length); // 4

一方、TypedArrayは配列のサイズの変更ができません。これは、TypedArrayが先にメモリー領域をまとめて一箇所に確保し、効率的に処理するための制約です。配列の長さが変わる操作はできないため、concat()メソッドやsplice()メソッドもありません。

// 32ビット浮動小数点数が10個格納できるメモリー領域を確保
const float32 = new Float32Array(10);
// push()やpop()など、配列のサイズが変更されるメソッドは持たない
// float32.push(0.5); // ✗

逆に、Arrayは中身がメモリー上で連続していない可能性があります。サイズも可変で配列内にどのような大きさのオブジェクトでも格納できるので、必要なメモリー領域があらかじめわからないためです。

Arrayは操作を柔軟にできる反面、配列の中身をまるまる複製したり、転送するためにはメモリーに散らばったデータを集める必要があります。転送のような操作を行うときは、TypedArrayと比べると効率がよくありません。

画像:配列のメモリー配置イメージ

コラム:サイズを変更できるTypedArray

ECMAScript 2024でArrayBufferのサイズを変えられるresize()メソッドが追加されました。ArrayBufferを作るときに最大の想定バイトサイズを指定しておくと、後からそのサイズまで拡張できます。

// 8バイトのArrayBufferを作成(最大16バイトまで拡張可能)
const array = new ArrayBuffer(8, { maxByteLength: 16 });
console.log(array.byteLength); // 8

// サイズを拡張
buffer.resize(12);
console.log(array.byteLength); // 12

サイズ変更は可能になるものの、最初からバイト数を見積もっておかなくてはならないため、やはり柔軟性は高くありません。

TypedArrayの配列操作

TypedArrayは、Arrayにあるmap()メソッドやforEach()メソッド、filter()メソッドなどの配列操作が可能です。しかし、TypedArrayならではの注意点があります。

たとえば、map()メソッドを使用する場合、返却される新しい配列は元のTypedArrayと同じ型になります。また、中のArrayBufferは新しく作られます

大きなデータに対してmap()操作を行うと、内部的には同じサイズの新たなメモリー領域が確保されることになります。知らずにメモリーを消費していることがあるため、注意が必要です。

// 要素を指定してFloat32Arrayを作成
const float32 = new Float32Array([1, 4, 9, 16]);
// map()操作が可能
const newFloat32 = float32.map(value => value + 1);

console.log(newFloat32); // Float32Array, [2, 5, 10, 17]
// 元のTypedArrayはそのまま
console.log(float32); // Float32Array, [1, 4, 9, 16]
// TypedArrayが参照するArrayBufferは新しく作られる
console.log(float32.buffer === newFloat32.buffer); // false

sort()reverse()などは元のArrayBufferを操作するため、新しいArrayBufferは作られません。新しいArrayBufferが作られるかどうかは、Arrayの同名の配列操作メソッドが元の配列を操作するかどうかに対応しています。

// 要素を指定してFloat32Arrayを作成
const float32 = new Float32Array([9, 4, 16, 1]);
// sort()操作が可能
float32.sort();

// 元のArrayBufferが変更される
console.log(float32); // Float32Array, [1, 4, 9, 16]

ArrayBufferおよびTypedArrayを使用するAPI

ArrayBufferは大きなデータを編集したり転送するAPIで使用されます。ArrayBufferTypedArrayについて、ウェブ開発のどのような場面で利用するか具体的にみていきましょう。

2D Canvas

TypedArrayが利用される実用的な例として2D Canvas(CanvasRenderingContext2D)があります。2D CanvasはHTMLの<canvas>要素に自由に図形や文字、画像などを描画できるAPIです。線や図形などのベクターグラフィックスも描画できますが、最終的にはピクセルごとにRGBAのデータを持ったビットマップに変換されます。

CanvasRenderingContext2DgetImageData()メソッドは、描画されたCanvasの指定領域のピクセルデータをImageDataとして取得します。ImageDataは画素のRGBAデータ(Uint8ClampedArray)と領域のサイズ(幅と高さ)を持ったオブジェクトです。

逆に、putImageData()メソッドでImageDataをCanvasに描画できます。簡単な画像処理であればUint8ClampedArrayの画素データを通してJavaScriptのみで実装できます。

以下のサンプルでは、ネットワークから読み込んだ画像をCanvasに描画したあとgetImageData()で各画素を取得し、モノクロ風に加工します。

const canvas = document.querySelector("canvas");
// CanvasからCanvasRenderingContext2Dを取得
const ctx = canvas.getContext("2d");

// Canvasの画素のImageDataをgetImageData()で取得
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 画素のデータを参照できるビュー配列、Uint8ClampedArrayを取得
const data = imageData.data;

// 1ピクセルにつき4バイト(4要素)のUint8ClampedArrayデータが返却される。
const pixelLength = data.length / 4;
// すべてのピクセルについて処理
for (let i = 0; i < pixelLength; i += 1) {
  // 注目ピクセルの最初の要素(R)のTypedArray内の位置
  const pixelId = i * 4;
  // モノクロ化には様々な方法があるが、今回は単純に平均をとる
  const average = (data[pixelId] + data[pixelId + 1] + data[pixelId + 2]) / 3;
  data[pixelId] = average; // R
  data[pixelId + 1] = average; // G
  data[pixelId + 2] = average; // B
  // data[i + 3]はアルファのデータのためそのまま変更しない
}

// 変更したピクセルのデータをCanvasにセット
ctx.putImageData(imageData, 0, 0);

ImageDatagetImageData()で取得する方法の他に、Uint8ClampedArrayから作成もできます。以下のサンプルでは、自分で作成したランダムな色の画素データをputImageData()でCanvasに設定します。

WebGL

WebGLではJavaScript(CPU)とシェーダー(GPU)でデータのやり取りをするときに、TypedArrayがよく出てきます。一例として、bufferData()メソッドを紹介します。

bufferData()メソッドはGPU上に確保した領域(バッファー)に頂点ごとの座標や色のデータを送ります。下記のソースコードでsrcDataにはFloat32ArrayなどのTypedArray、もしくはArrayBufferを指定します。TypedArrayを指定しても結局は参照するArrayBufferの中身がまとめて転送されます。

// CanvasからWebGLRenderingContextを取得
const gl = document.querySelector("canvas").getContext("webgl");

// バッファーを作成
const vertexBuffer = gl.createBuffer(); 
// バッファーをバインドする
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// バッファーにデータを転送
// 引数のsrcDataには、転送したいデータをTypedArrayやArrayBufferの形式で指定する
gl.bufferData(gl.ARRAY_BUFFER, srcData, gl.STATIC_DRAW);

bufferData()に転送する頂点データを定義して保持するのに、ArrayTypedArrayの2通りの方法があります。Arrayで定義する方法では、データ転送時にTypedArrayに変換します。

// CanvasからWebGLRenderingContextを取得
const gl = document.querySelector("canvas").getContext("webgl");

// 方法① Arrayでデータ管理する
const positionArr = [];
// 配列に座標データを設定(省略)
positionArr[0] = 0.1; // x座標
// 配列をTypedArrayに変換しつつバッファーにデータを転送
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positionArr), gl.STATIC_DRAW);

// 方法② 最初から頂点数分のTypedArrayを作成しておく
const positionData = new Float32Array(100 * 3);
// positionDataに座標データを設定(省略)
positionData[0] = 0.1; // x座標
// バッファーにデータを転送
gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);

サイズ(=頂点数)を容易に変更できるメリットがあるため一長一短ですが、JavaScript側の頂点データを最初からTypedArrayで管理することも考えてみましょう。

fetch

皆さんがよく使うfetch()メソッドにもArrayBufferが登場します。たとえば、普段JSONファイルを読み込むとき、fetch()後にresponse.json()を呼び出していると思います。Responseオブジェクトには、読み込んだデータをバイナリデータとして変換するresponse.arrayBuffer()が用意されています。外部ファイルの画像やバイナリデータを読み込んで、ArrayBufferとして処理する場合に使用します。

// JSONファイルをロード
const response = await fetch("setting.json");
// レスポンスをJSONとして読み込み
const json = await response.json();

// レスポンスをバイナリとして読み込み
const jsonBinary = await response.arrayBuffer();
// 元がJSONファイルなので、バイナリにはテキストの文字コードの羅列が入る
console.log(new Uint8Array(jsonBinary));

Web Audio

Web AudioはJavaScript上で音声に柔軟な操作を行えるAPIです。読み込んだ音声ファイルからAudioBufferを作成する際に、ファイルのバイナリ形式であるArrayBufferを渡します。AudioBufferはWeb Audioで短い音声データを扱う際に使用するオブジェクトです。

また、AudioBufferからは音声チャンネルごとのPCMデータを取得したり加工できます。取得するオブジェクトの形式はFloat32Arrayとなっており、ここでもArrayBufferが登場します。

// 新しいAudioContextを作成
const audioContext = new AudioContext();

// 音声ファイルを読み込んでArrayBufferとして取得
const response = await fetch("audioFile.mp3");
const arrayBuffer = await response.arrayBuffer();

// ArrayBufferをWeb Audioで扱えるAudioBufferデータ形式に変換
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);

// AudioBufferから指定したチャンネルのPCMデータを取得できる
// 取得したデータはFloat32Array形式で、各要素に-1.0から1.0の間の数値が入っている
const channel0Data = audioBuffer.getChannelData(0);
console.log(channel0Data); // Float32Array [-0.47540074586868286, ...

参考記事:音を操るWeb技術 - Web Audio API入門

Web Workers

Web Workersは、ページ表示に使用しているメインスレッド以外のスレッドでJavaScriptを実行できるAPIです。Web Workersではデータをメインスレッドからワーカー側に渡すと、通常はデータのコピーが発生します。大量のデータをワーカー側に処理させたい場合には、コピーが発生するとパフォーマンスが低下します。

Web Workersには委譲可能オブジェクト(Transferable objects)という仕組みが用意されています。ArrayBufferも委譲可能オブジェクトの一種です。委譲可能オブジェクトはメインスレッドでのアクセス権を失う代わりに、コピーすることなくワーカースレッドで使用できるようになります。ArrayBufferを委譲可能オブジェクトとしてワーカーに転送すれば、大量のデータをコピーコストなしにワーカー側で処理できます。

// プログラムファイルからワーカーインスタンスを作成
const worker = new Worker("worker.js");

// 大量のデータ例
const uint8 = new Uint8Array(1024 * 1024 * 100);

// ワーカーインスタンスにArrayBufferを移譲可能オブジェクトとして渡す
worker.postMessage(uint8.buffer, [uint8.buffer]);

WebAssembly

WebAssemblyは、JavaScriptより高速の、ネイティブに近いパフォーマンスでプログラムを実行できる仕組みです。JavaScriptからWebAssemblyのプログラムを呼び出す際に、処理するデータのメモリー領域としてWebAssembly.Memoryオブジェクトを渡せます。

このメモリー領域も正体はArrayBufferなので、JavaScriptからデータの読み書きをする際にはTypedArray形式にしてアクセス可能です。

// WebAssemblyに渡すメモリーを作成
const memory = new WebAssembly.Memory({ initial: 10, maximum: 100 });

// メモリーのバッファーにJavaScriptからアクセス
const uint8 = new Uint8Array(memory.buffer);
uint8[0] = 100;

// WebAssemblyプログラムをインスタンス化する
const response = await fetch("program.wasm");
const programBinary = await response.arrayBuffer();
// JavaScriptでデータ設定済みのWebAssembly.Memoryを渡す
WebAssembly.instantiate(programBinary, {js: { memory }});

まとめ

TypedArrayと、その内部で使われているArrayBufferの仕組みを正しく理解しておくことは、今後低レベルなデータを扱うAPIを利用する際に役立ちます。とくに、TypedArrayの操作が求められる場面でも、スムーズに対応できるようになるでしょう。

TypedArrayは内部のArrayBufferの変更の影響を受けるなど、一見すると不可解な挙動に感じられることもあります。TypedArrayの特殊な挙動の理解に、本記事が役立てば幸いです。

川勝 研太郎

インタラクティブディベロッパー。ゲーム技術、GPUとその周辺技術について日々勉強中。自宅周辺の移動手段は自転車。

この担当の記事一覧