Uint8Array를 Javascript의 문자열로


122

Javascript의 Uint8Array 요소 범위에있는 UTF-8 인코딩 데이터가 있습니다. 이것을 일반 자바 스크립트 문자열로 디코딩하는 효율적인 방법이 있습니까 (Javascript가 16 비트 유니 코드를 사용한다고 생각합니다)? 문자열 결합이 CPU를 많이 사용하므로 한 번에 한 문자를 추가하고 싶지 않습니다.


작동하는지 확실하지 않지만 u8array.toString()을 호출 할 때 Uint8Array 객체를 노출하는 BrowserFS에서 파일을 읽을 때 사용합니다 fs.readFile.
— jcubic

1
@jcubic for me, toStringon Uint8Array은 "91,50,48,49,57,45"(Chrome 79) 와 같이 쉼표로 구분 된 숫자 를 반환합니다.
— kolen

답변:


171

TextEncoder및 TextDecoder로부터 인코딩 표준 에 의해 polyfilled된다 stringencoding 라이브러리 현과 ArrayBuffers간에 변환 :

var uint8array = new TextEncoder("utf-8").encode("¢");
var string = new TextDecoder("utf-8").decode(uint8array);

40
나처럼 게으른 사람이라면 npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. 고맙지 만 사양 할게.
— 에반 후

16
npm 텍스트 인코딩 라이브러리에주의하십시오. webpack 번들 분석기는 라이브러리가 거대하다는 것을 보여줍니다.
— wayofthefuture

3
@VincentScheib Browsers는 utf-8. 따라서 TextEncoder논쟁은 불필요합니다!
— tripulse

1
예제의 nodejs.org/api/string_decoder.html : const {StringDecoder} = require ( 'string_decoder'); const 디코더 = new StringDecoder ( 'utf8'); const cent = Buffer.from ([0xC2, 0xA2]); console.log (decoder.write (cent));
— curist 19

4
Node.js 는 v11에 TextEncoder/ TextDecoderAPI를 추가 했으므로 현재 Node 버전 만 대상으로하는 경우 추가 패키지를 설치할 필요가 없습니다.
— Loilo

42

이것은 작동합니다.

// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt

/* utf.js - UTF-8 <=> UTF-16 convertion
 *
 * Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
 * Version: 1.0
 * LastModified: Dec 25 1999
 * This library is free.  You can redistribute it and/or modify it.
 */

function Utf8ArrayToStr(array) {
    var out, i, len, c;
    var char2, char3;

    out = "";
    len = array.length;
    i = 0;
    while(i < len) {
    c = array[i++];
    switch(c >> 4)
    { 
      case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
        // 0xxxxxxx
        out += String.fromCharCode(c);
        break;
      case 12: case 13:
        // 110x xxxx   10xx xxxx
        char2 = array[i++];
        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
        break;
      case 14:
        // 1110 xxxx  10xx xxxx  10xx xxxx
        char2 = array[i++];
        char3 = array[i++];
        out += String.fromCharCode(((c & 0x0F) << 12) |
                       ((char2 & 0x3F) << 6) |
                       ((char3 & 0x3F) << 0));
        break;
    }
    }

    return out;
}

해킹을 사용하지 않고 브라우저 JS 기능에 의존하지 않기 때문에 다른 솔루션보다 다소 깨끗합니다. 예를 들어 다른 JS 환경에서도 작동합니다.

JSFiddle 데모를 확인하십시오 .

관련 질문도 참조하십시오 : 여기 및 여기


6
이것은 다소 느린 것 같습니다. 그러나 내가 찾은 우주에서 유일한 스 니펫은 작동합니다. 좋은 찾기 + 입양!
— Redsandro

6
왜 이것이 더 많은 찬성표가 없는지 이해할 수 없습니다. 작은 스 니펫에 대해 UTF-8 규칙을 통과하는 것이 현명하게 보입니다. Async Blob + Filereader는 다른 사람들이 지적했듯이 큰 텍스트에 적합합니다.
— DanHorner

2
문제는 문자열 연결하지 않고이 작업을 수행하는 방법이었다
— 잭 Wester은

5
4 바이트 이상의 시퀀스를 처리하지 않는다는 점을 제외하면 훌륭하게 작동합니다. 예를 들어 fromUTF8Array([240,159,154,133])비어 있음 (while fromUTF8Array([226,152,131])→"☃")
— unhammer

1
사례 8, 9, 10 및 11이 제외되는 이유는 무엇입니까? 특별한 이유가 있습니까? 케이스 15도 가능 하죠? 15 (1111)은 4 바이트가 사용되었음을 나타냅니다.
— RaR

31

내가 사용하는 것은 다음과 같습니다.

var str = String.fromCharCode.apply(null, uint8Arr);

7
로부터 문서 , 이것은 디코딩 UTF8하지 않는 것.
— 알버트

29
이것은 RangeError더 큰 텍스트 를 던질 것 입니다. "최대 호출 스택 크기 초과"
— Redsandro

1
큰 Uint8Array를 이진 문자열로 변환하고 RangeError가 발생하는 경우 stackoverflow.com/a/12713326/471341 에서 Uint8ToString 함수를 참조하세요 .
— yonran

IE 11은 SCRIPT28: Out of stack space300 + k 문자를 공급하거나 RangeErrorChrome 39의 경우 발생합니다. Firefox 33은 괜찮습니다. 100 + k는 세 가지 모두에서 정상적으로 실행됩니다.
— Sheepy 2014

이것은 en.wikipedia.org/wiki/UTF-8 의 예제 유니 코드 문자에서 올바른 결과를 생성하지 않습니다 . 예를 들어, String.fromCharCode.apply (null, new Uint8Array ([0xc2, 0xa2]))는 ¢을 생성하지 않습니다.
— Vincent Scheib

16

Chrome 샘플 애플리케이션 중 하나에서 찾을 수 있지만 이는 비동기 변환에 문제가없는 더 큰 데이터 블록을위한 것입니다.

/**
 * Converts an array buffer to a string
 *
 * @private
 * @param {ArrayBuffer} buf The buffer to convert
 * @param {Function} callback The function to call when conversion is complete
 */
function _arrayBufferToString(buf, callback) {
  var bb = new Blob([new Uint8Array(buf)]);
  var f = new FileReader();
  f.onload = function(e) {
    callback(e.target.result);
  };
  f.readAsText(bb);
}

2
당신이 말했듯이, 이것은 변환 할 버퍼가 정말로 크지 않으면 끔찍하게 수행 될 것입니다. 예를 들어 V8에서 구현 된 간단한 문자열 (예 : 10-40 바이트)의 wchar에 대한 동기식 UTF-8 변환은 마이크로 초보다 훨씬 작아야하지만 코드에는 수백 배가 필요하다고 생각합니다. 모두 똑같이 감사합니다.
— Jack Wester 2013 년

15

노드에서 " Buffer인스턴스도 Uint8Array인스턴스 " buf.toString()이므로이 경우에 작동합니다.


나를 위해 잘 작동합니다. 그리고 너무 간단합니다! 그러나 실제로 Uint8Array 에는 toString () 메서드가 있습니다.
— doom

단순하고 우아한, Buffer몰랐던 Uint8Array도 있습니다. 감사!
— LeOn-Han Li

1
@doom 브라우저 측에서 Uint8Array.toString ()은 utf-8 문자열을 컴파일하지 않고 배열의 숫자 값을 나열합니다. 따라서 가지고있는 것이 버퍼가 아닌 다른 소스의 Uint8Array 인 경우 마법을 수행하기 위해 하나를 만들어야합니다.Buffer.from(uint8array).toString('utf-8')
— Joachim Lous 19

12

Albert가 제공 한 솔루션은 제공된 함수가 자주 호출되지 않고 적당한 크기의 배열에만 사용되는 한 잘 작동합니다. 그렇지 않으면 매우 비효율적입니다. 다음은 Node와 브라우저 모두에서 작동하며 다음과 같은 장점이있는 향상된 바닐라 JavaScript 솔루션입니다.

• 모든 옥텟 배열 크기에 대해 효율적으로 작동

• 중간에 던져 버리는 문자열을 생성하지 않습니다.

• 최신 JS 엔진에서 4 바이트 문자 지원 (그렇지 않으면 "?"로 대체 됨)

var utf8ArrayToStr = (function () {
    var charCache = new Array(128);  // Preallocate the cache for the common single byte chars
    var charFromCodePt = String.fromCodePoint || String.fromCharCode;
    var result = [];

    return function (array) {
        var codePt, byte1;
        var buffLen = array.length;

        result.length = 0;

        for (var i = 0; i < buffLen;) {
            byte1 = array[i++];

            if (byte1 <= 0x7F) {
                codePt = byte1;
            } else if (byte1 <= 0xDF) {
                codePt = ((byte1 & 0x1F) << 6) | (array[i++] & 0x3F);
            } else if (byte1 <= 0xEF) {
                codePt = ((byte1 & 0x0F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F);
            } else if (String.fromCodePoint) {
                codePt = ((byte1 & 0x07) << 18) | ((array[i++] & 0x3F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F);
            } else {
                codePt = 63;    // Cannot convert four byte code points, so use "?" instead
                i += 3;
            }

            result.push(charCache[codePt] || (charCache[codePt] = charFromCodePt(codePt)));
        }

        return result.join('');
    };
})();

2
4 바이트 문자 (예 : 이모 지)도 처리하므로 여기에서 최고의 솔루션입니다. 감사합니다!
— fiffy

1
그리고 이것의 역은 무엇입니까?
— simbo1905

6

@Sudhir가 말한 것을 한 다음 쉼표로 구분 된 숫자 목록에서 문자열을 얻으려면 다음을 사용하십시오.

for (var i=0; i<unitArr.byteLength; i++) {
            myString += String.fromCharCode(unitArr[i])
        }

여전히 관련이있는 경우 원하는 문자열을 제공합니다.


죄송합니다. 한 번에 하나의 문자를 추가하고 싶지 않다고 말한 마지막 감각을 찾지 못했습니다. 그러나 이것이 CPU 사용에 문제가없는 다른 사람들에게 도움이되기를 바랍니다.
— shuki

14
이것은 UTF8 디코딩을 수행하지 않습니다.
— 알버트

더 짧게 : String.fromCharCode.apply(null, unitArr);. 언급했듯이 UTF8 인코딩을 처리하지 않지만 ASCII 지원 만 필요하지만 TextEncoder / TextDecoder에 대한 액세스 권한이없는 경우이 방법이 충분히 간단합니다.
— Ravenstine

대답에는 @Sudhir가 언급되어 있지만 페이지를 검색 한 결과 그런 대답을 찾았습니다. 그래서 그가 말한 것을 인라인하는 것이 더 나을 것입니다
— Joakim

이것은 긴 문자열에서 끔찍한 성능을 발휘합니다. 문자열에 + 연산자를 사용하지 마십시오.
— Max

3

당신은 사용할 수없는 경우 TextDecoder API를 하기 때문에 그것이 IE에서 지원되지 않습니다 :

  1. Mozilla 개발자 네트워크 웹 사이트 에서 권장 하는 FastestSmallestTextEncoderDecoder 폴리 필을 사용할 수 있습니다 .
  2. MDN 웹 사이트 에서도 제공되는이 기능을 사용할 수 있습니다 .

function utf8ArrayToString(aBytes) {
    var sView = "";
    
    for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) {
        nPart = aBytes[nIdx];
        
        sView += String.fromCharCode(
            nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */
                /* (nPart - 252 << 30) may be not so safe in ECMAScript! So...: */
                (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
            : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */
                (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
            : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */
                (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
            : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */
                (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
            : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */
                (nPart - 192 << 6) + aBytes[++nIdx] - 128
            : /* nPart < 127 ? */ /* one byte */
                nPart
        );
    }
    
    return sView;
}

let str = utf8ArrayToString([50,72,226,130,130,32,43,32,79,226,130,130,32,226,135,140,32,50,72,226,130,130,79]);

// Must show 2H₂ + O₂ ⇌ 2H₂O
console.log(str);


2

이 기능을 시도해보십시오.

var JsonToArray = function(json)
{
    var str = JSON.stringify(json, null, 0);
    var ret = new Uint8Array(str.length);
    for (var i = 0; i < str.length; i++) {
        ret[i] = str.charCodeAt(i);
    }
    return ret
};

var binArrayToJson = function(binArray)
{
    var str = "";
    for (var i = 0; i < binArray.length; i++) {
        str += String.fromCharCode(parseInt(binArray[i]));
    }
    return JSON.parse(str)
}

출처 : https://gist.github.com/tomfa/706d10fed78c497731ac , Tomfa에 대한 찬사


2

사람들이 두 가지 방법을 모두 보여주지 않거나 사소한 UTF8 문자열에서 작동하지 않는다는 것을 보여주지 않아서 실망했습니다. 잘 작동하는 코드가있는 codereview.stackexchange.com 에서 게시물을 찾았 습니다 . 고대 룬을 바이트로 바꾸고, 바이트에 대한 크라이 포를 테스트 한 다음, 다시 문자열로 변환하는 데 사용했습니다. 작업 코드는 여기 github에 있습니다 . 명확성을 위해 메서드 이름을 변경했습니다.

// https://codereview.stackexchange.com/a/3589/75693
function bytesToSring(bytes) {
    var chars = [];
    for(var i = 0, n = bytes.length; i < n;) {
        chars.push(((bytes[i++] & 0xff) << 8) | (bytes[i++] & 0xff));
    }
    return String.fromCharCode.apply(null, chars);
}

// https://codereview.stackexchange.com/a/3589/75693
function stringToBytes(str) {
    var bytes = [];
    for(var i = 0, n = str.length; i < n; i++) {
        var char = str.charCodeAt(i);
        bytes.push(char >>> 8, char & 0xFF);
    }
    return bytes;
}

단위 테스트는 다음 UTF-8 문자열을 사용합니다.

    // http://kermitproject.org/utf8.html
    // From the Anglo-Saxon Rune Poem (Rune version) 
    const secretUtf8 = `ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ
ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ
ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬`;

문자열 길이는 117 자이지만 인코딩 된 경우 바이트 길이는 234입니다.

console.log 줄의 주석 처리를 제거하면 디코딩 된 문자열이 인코딩 된 문자열과 동일하다는 것을 알 수 있습니다 (Shamir의 비밀 공유 알고리즘을 통해 전달 된 바이트 포함!).

인코딩 및 디코딩을 시연하는 단위 테스트


String.fromCharCode.apply(null, chars)chars너무 크면 오류가 발생합니다 .
— Marc J. Schmidt

1

NodeJS에는 사용할 수있는 버퍼가 있으며 문자열 변환이 정말 쉽습니다. 더 나은 것은 Uint8Array를 버퍼로 변환하는 것이 쉽습니다. 이 코드를 사용해보십시오, 기본적으로 Uint8Arrays와 관련된 모든 변환을 위해 Node에서 저에게 효과적입니다.

let str = Buffer.from(uint8arr.buffer).toString();

Uint8Array에서 ArrayBuffer를 추출한 다음 적절한 NodeJS 버퍼로 변환합니다. 그런 다음 버퍼를 문자열로 변환합니다 (원하는 경우 16 진수 또는 base64 인코딩을 사용할 수 있음).

문자열에서 Uint8Array로 다시 변환하려면 다음을 수행합니다.

let uint8arr = new Uint8Array(Buffer.from(str));

문자열로 변환 할 때 base64와 같은 인코딩을 선언 한 경우 base64를 사용 Buffer.from(str, "base64")했거나 사용한 다른 인코딩을 사용해야합니다.

모듈이없는 브라우저에서는 작동하지 않습니다! NodeJS 버퍼는 브라우저에 존재하지 않으므로 브라우저에 버퍼 기능을 추가하지 않으면이 방법이 작동하지 않습니다. 즉, 생각해야 할 아주 쉽게 사실이다, 다만 같은 모듈 사용 이 작고 빠른 둘 다!


0
class UTF8{
static encode(str:string){return new UTF8().encode(str)}
static decode(data:Uint8Array){return new UTF8().decode(data)}

private EOF_byte:number = -1;
private EOF_code_point:number = -1;
private encoderError(code_point) {
    console.error("UTF8 encoderError",code_point)
}
private decoderError(fatal, opt_code_point?):number {
    if (fatal) console.error("UTF8 decoderError",opt_code_point)
    return opt_code_point || 0xFFFD;
}
private inRange(a:number, min:number, max:number) {
    return min <= a && a <= max;
}
private div(n:number, d:number) {
    return Math.floor(n / d);
}
private stringToCodePoints(string:string) {
    /** @type {Array.<number>} */
    let cps = [];
    // Based on http://www.w3.org/TR/WebIDL/#idl-DOMString
    let i = 0, n = string.length;
    while (i < string.length) {
        let c = string.charCodeAt(i);
        if (!this.inRange(c, 0xD800, 0xDFFF)) {
            cps.push(c);
        } else if (this.inRange(c, 0xDC00, 0xDFFF)) {
            cps.push(0xFFFD);
        } else { // (inRange(c, 0xD800, 0xDBFF))
            if (i == n - 1) {
                cps.push(0xFFFD);
            } else {
                let d = string.charCodeAt(i + 1);
                if (this.inRange(d, 0xDC00, 0xDFFF)) {
                    let a = c & 0x3FF;
                    let b = d & 0x3FF;
                    i += 1;
                    cps.push(0x10000 + (a << 10) + b);
                } else {
                    cps.push(0xFFFD);
                }
            }
        }
        i += 1;
    }
    return cps;
}

private encode(str:string):Uint8Array {
    let pos:number = 0;
    let codePoints = this.stringToCodePoints(str);
    let outputBytes = [];

    while (codePoints.length > pos) {
        let code_point:number = codePoints[pos++];

        if (this.inRange(code_point, 0xD800, 0xDFFF)) {
            this.encoderError(code_point);
        }
        else if (this.inRange(code_point, 0x0000, 0x007f)) {
            outputBytes.push(code_point);
        } else {
            let count = 0, offset = 0;
            if (this.inRange(code_point, 0x0080, 0x07FF)) {
                count = 1;
                offset = 0xC0;
            } else if (this.inRange(code_point, 0x0800, 0xFFFF)) {
                count = 2;
                offset = 0xE0;
            } else if (this.inRange(code_point, 0x10000, 0x10FFFF)) {
                count = 3;
                offset = 0xF0;
            }

            outputBytes.push(this.div(code_point, Math.pow(64, count)) + offset);

            while (count > 0) {
                let temp = this.div(code_point, Math.pow(64, count - 1));
                outputBytes.push(0x80 + (temp % 64));
                count -= 1;
            }
        }
    }
    return new Uint8Array(outputBytes);
}

private decode(data:Uint8Array):string {
    let fatal:boolean = false;
    let pos:number = 0;
    let result:string = "";
    let code_point:number;
    let utf8_code_point = 0;
    let utf8_bytes_needed = 0;
    let utf8_bytes_seen = 0;
    let utf8_lower_boundary = 0;

    while (data.length > pos) {
        let _byte = data[pos++];

        if (_byte == this.EOF_byte) {
            if (utf8_bytes_needed != 0) {
                code_point = this.decoderError(fatal);
            } else {
                code_point = this.EOF_code_point;
            }
        } else {
            if (utf8_bytes_needed == 0) {
                if (this.inRange(_byte, 0x00, 0x7F)) {
                    code_point = _byte;
                } else {
                    if (this.inRange(_byte, 0xC2, 0xDF)) {
                        utf8_bytes_needed = 1;
                        utf8_lower_boundary = 0x80;
                        utf8_code_point = _byte - 0xC0;
                    } else if (this.inRange(_byte, 0xE0, 0xEF)) {
                        utf8_bytes_needed = 2;
                        utf8_lower_boundary = 0x800;
                        utf8_code_point = _byte - 0xE0;
                    } else if (this.inRange(_byte, 0xF0, 0xF4)) {
                        utf8_bytes_needed = 3;
                        utf8_lower_boundary = 0x10000;
                        utf8_code_point = _byte - 0xF0;
                    } else {
                        this.decoderError(fatal);
                    }
                    utf8_code_point = utf8_code_point * Math.pow(64, utf8_bytes_needed);
                    code_point = null;
                }
            } else if (!this.inRange(_byte, 0x80, 0xBF)) {
                utf8_code_point = 0;
                utf8_bytes_needed = 0;
                utf8_bytes_seen = 0;
                utf8_lower_boundary = 0;
                pos--;
                code_point = this.decoderError(fatal, _byte);
            } else {
                utf8_bytes_seen += 1;
                utf8_code_point = utf8_code_point + (_byte - 0x80) * Math.pow(64, utf8_bytes_needed - utf8_bytes_seen);

                if (utf8_bytes_seen !== utf8_bytes_needed) {
                    code_point = null;
                } else {
                    let cp = utf8_code_point;
                    let lower_boundary = utf8_lower_boundary;
                    utf8_code_point = 0;
                    utf8_bytes_needed = 0;
                    utf8_bytes_seen = 0;
                    utf8_lower_boundary = 0;
                    if (this.inRange(cp, lower_boundary, 0x10FFFF) && !this.inRange(cp, 0xD800, 0xDFFF)) {
                        code_point = cp;
                    } else {
                        code_point = this.decoderError(fatal, _byte);
                    }
                }

            }
        }
        //Decode string
        if (code_point !== null && code_point !== this.EOF_code_point) {
            if (code_point <= 0xFFFF) {
                if (code_point > 0)result += String.fromCharCode(code_point);
            } else {
                code_point -= 0x10000;
                result += String.fromCharCode(0xD800 + ((code_point >> 10) & 0x3ff));
                result += String.fromCharCode(0xDC00 + (code_point & 0x3ff));
            }
        }
    }
    return result;
}

`


답변에 대한 설명을 추가하십시오. @terran
— Rohit Poudel

-3

이 Typescript 스 니펫을 사용하고 있습니다.

function UInt8ArrayToString(uInt8Array: Uint8Array): string
{
    var s: string = "[";
    for(var i: number = 0; i < uInt8Array.byteLength; i++)
    {
        if( i > 0 )
            s += ", ";
        s += uInt8Array[i];
    }
    s += "]";
    return s;
}

JavaScript 버전이 필요한 경우 유형 주석을 제거하십시오. 도움이 되었기를 바랍니다!


3
OP는 한 번에 하나의 문자를 추가하지 않도록 요청했습니다. 또한 그는 그것을 목록의 문자열 표현으로 표시하지 않고 문자열로 표시하기를 원합니다. 또한 이것은 문자를 문자열로 변환하지 않지만 숫자를 표시합니다.
— 알버트
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.