UndefinedConversionError
와 같은 Ruby 예외를 보았을 가능성이 큽니다. 또는 IncompatibleCharacterEncodings
. 예외가 무엇을 의미하는지 이해했을 가능성이 적습니다. 이 기사가 도움이 될 것입니다. 문자 인코딩이 작동하는 방식과 Ruby에서 구현되는 방식을 배우게 됩니다. 결국에는 이러한 오류를 훨씬 더 쉽게 이해하고 수정할 수 있습니다.
그렇다면 "문자 인코딩"이란 무엇입니까?
모든 프로그래밍 언어에서 문자열로 작업합니다. 때로는 입력으로 처리하고 때로는 출력으로 표시합니다. 그러나 당신의 컴퓨터는 "문자열"을 이해하지 못합니다. 1과 0의 비트만 이해합니다. 문자열을 비트로 변환하는 프로세스를 문자 인코딩이라고 합니다.
그러나 문자 인코딩은 컴퓨터 시대에만 해당되는 것은 아닙니다. 우리는 컴퓨터가 있기 전에 더 간단한 과정인 모스 부호를 통해 배울 수 있습니다.
모스 부호
모스 부호는 그 정의가 매우 간단합니다. 신호를 생성하는 두 가지 기호 또는 방법(짧은 것과 긴 것)이 있습니다. 이 두 기호로 간단한 영어 알파벳을 나타냅니다. 예:
- A는 .-(짧은 표시 하나와 긴 표시 하나)
- E는 . (짧은 표시 하나)
- O는 ---(긴 기호 3개)
이 시스템은 1837년경에 발명되었으며 두 개의 기호 또는 신호만으로 전체 알파벳을 인코딩할 수 있었습니다.
여기에서 한 명의 번역가와 온라인으로 플레이할 수 있습니다.
이미지에서 메시지 인코딩 및 디코딩을 담당하는 "인코더"를 볼 수 있습니다. 이것은 곧 컴퓨터의 등장으로 바뀔 것입니다.
수동 인코딩에서 자동 인코딩으로
메시지를 인코딩하려면 모스 부호 알고리즘에 따라 문자를 수동으로 기호로 번역하는 사람이 필요합니다.
모스 부호와 유사하게 컴퓨터는 1과 0의 두 가지 "기호"만 사용합니다. 이러한 기호의 시퀀스만 컴퓨터에 저장할 수 있으며, 읽을 때 사용자가 이해할 수 있는 방식으로 해석해야 합니다.
프로세스는 두 경우 모두 다음과 같이 작동합니다.
Message -> Encoding -> Store/Send -> Decoding -> Message
모스 부호의 SOS는 다음과 같습니다.
SOS -> Encode('SOS') -> ...---... -> Decode('...---...') -> SOS
----------------------- --------------------------
Sender Receiver
컴퓨터 및 기타 기술의 큰 변화는 인코딩 및 디코딩 프로세스가 자동화되어 더 이상 정보를 번역할 사람이 필요하지 않다는 것입니다.
컴퓨터가 발명되었을 때 문자를 1과 0으로 자동 변환하기 위해 만들어진 초기 표준 중 하나는 ASCII였습니다.
ASCII는 정보 교환을 위한 미국 표준 코드(American Standard Code for Information Interchange)의 약자입니다. "미국인" 부분은 한동안 컴퓨터가 정보를 다루는 방식에서 중요한 역할을 했습니다. 다음 섹션에서 그 이유를 알아보겠습니다.
아스키(1963)
모스 부호와 초창기 컴퓨터와 같은 전신 부호에 대한 지식을 바탕으로 컴퓨터의 문자 인코딩 및 디코딩 표준은 1963년경에 만들어졌습니다. 이 시스템은 처음에는 영어 알파벳과 추가 기호를 포함하여 127자만 다루었기 때문에 비교적 간단했습니다.
ASCII는 각 문자를 이진 코드로 변환할 수 있는 십진수와 연결하여 작동했습니다. 예를 들어 보겠습니다.
"A"는 ASCII에서 65이므로 65를 이진 코드로 변환해야 합니다.
작동 방식을 모르는 경우 빠른 방법이 있습니다. :65를 2로 나누기 시작하여 0이 될 때까지 계속합니다. 나누기가 정확하지 않으면 나머지로 1을 더합니다.
<미리>65 / 2 = 32 + 1
32 / 2 = 16 + 0
16 / 2 = 8 + 0
8 / 2 = 4 + 0
4 / 2 = 2 + 0
2 / 2 = 1 + 0
1 / 2 = 0 + 1
이제 나머지를 취하여 역순으로 넣습니다.
1000001
따라서 "A"를 현재 US-ASCII로 알려진 원래 ASCII 인코딩으로 "1000001"로 저장합니다. 요즘은 8비트 컴퓨터가 일반적이므로 01000001(8비트 =1바이트)이 됩니다.
각 문자에 대해 동일한 프로세스를 따르므로 7비트로 최대 2^7 문자 =127을 저장할 수 있습니다.
전체 표는 다음과 같습니다.
(출처:https://www.plcdev.com/ascii_chart)
ASCII 문제
프랑스어 ç 또는 일본어 문자 大와 같은 다른 문자를 추가하려는 경우 어떻게 됩니까?
예, 문제가 있습니다.
ASCII 이후 사람들은 자신만의 인코딩 시스템을 만들어 이 문제를 해결하려고 했습니다. 그들은 더 많은 비트를 사용했지만 이것은 결국 또 다른 문제를 일으켰습니다.
주요 문제는 파일을 읽을 때 특정 인코딩 시스템이 있는지 알 수 없다는 것이었습니다. 잘못된 인코딩으로 해석하려고 하면 "��' 또는 "Ã,ÂÂÃ'Â"와 같은 말이 나왔습니다.
이러한 인코딩 시스템의 발전은 크고 광범위했습니다. 언어에 따라 다른 시스템이 있었습니다. 중국어와 같이 문자가 더 많은 언어는 알파벳을 인코딩하기 위해 더 복잡한 시스템을 개발해야 했습니다.
수년 동안 이것과 씨름한 끝에 유니코드라는 새로운 표준이 만들어졌습니다. 이 표준은 현대 컴퓨터가 정보를 인코딩하고 디코딩하는 방식을 정의했습니다.
유니코드(1988)
유니코드의 목표는 매우 간단합니다. 공식 사이트에 따르면 "플랫폼, 프로그램 또는 언어에 관계없이 모든 캐릭터에 고유한 번호를 제공합니다."
따라서 언어의 각 문자에는 코드 포인트라고도 하는 고유한 코드가 할당됩니다. 현재 137,000자 이상입니다.
유니코드 표준의 일부로 이러한 값이나 코드 포인트를 인코딩하는 다양한 방법이 있지만 UTF-8이 가장 광범위합니다.
Go 프로그래밍 언어를 만든 Rob Pike와 Ken Thompson도 UTF-8을 만들었습니다. 그 숫자를 인코딩하는 방법이 효율적이고 영리하기 때문에 성공했습니다. 그 이유를 정확히 알아보겠습니다.
UTF-8:유니코드 변환 형식(1993)
UTF-8은 이제 웹사이트에 대한 사실상의 인코딩입니다(웹사이트의 94% 이상이 해당 인코딩을 사용합니다). 또한 많은 프로그래밍 언어 및 파일의 기본 인코딩입니다. 그렇다면 왜 그렇게 성공적이었고 어떻게 작동합니까?
UTF-8은 다른 인코딩 시스템과 마찬가지로 유니코드로 정의된 숫자를 바이너리로 변환하여 컴퓨터에 저장합니다.
UTF-8에는 두 가지 매우 중요한 측면이 있습니다. - 문자가 1~4바이트를 차지할 수 있기 때문에 비트를 저장할 때 효율적입니다.- 유니코드와 동적 바이트 양을 사용하여 처음 127개 문자는 1바이트를 차지합니다. 즉, ASCII 파일을 UTF-8로 열 수 있습니다.
UTF-8이 작동하는 방식을 분석해 보겠습니다.
UTF-8 1바이트
유니코드 테이블의 값에 따라 UTF-8은 다른 수의 문자를 사용합니다.
처음 127에서는 다음 템플릿을 사용합니다. Rust1
0_______
따라서 0은 항상 거기에 있고 그 뒤에 유니코드(ASCII도 됨) 값을 나타내는 이진수가 옵니다. 예:A =65 =1000001.
String:
의 unpack 메소드를 사용하여 Ruby로 이를 확인해보자.'A'.unpack('B*').first
# 01000001
B는 최상위 비트가 먼저 있는 이진 형식을 원한다는 것을 의미합니다. 이 문맥에서, 그것은 가장 높은 값을 가진 비트를 의미합니다. 별표는 더 이상 비트가 없을 때까지 루비에게 계속하도록 지시합니다. 대신 숫자를 사용하면 해당 숫자까지만 비트를 얻을 수 있습니다.
'A'.unpack('B4').first
# 01000
2바이트의 UTF-8
유니코드의 값 또는 코드 포인트가 127을 초과하여 최대 2047인 문자가 있는 경우 다음 템플릿과 함께 2바이트를 사용합니다.
110_____ 10______
따라서 유니코드 값에 대해 11개의 빈 비트가 있습니다. 예를 들어 보겠습니다.
À는 유니코드에서 192이므로 이진법으로 8비트를 사용하는 11000000입니다. 첫 번째 템플릿에는 맞지 않으므로 두 번째 템플릿을 사용합니다.
110_____ 10______
오른쪽에서 왼쪽으로 공백을 채우기 시작합니다.
110___11 10000000
거기에 있는 빈 비트는 어떻게 됩니까? 0을 입력했으므로 최종 결과는 11000011 10000000입니다.
여기서 패턴을 볼 수 있습니다. 왼쪽에서 오른쪽으로 읽기 시작하면 8비트의 첫 번째 그룹은 처음에 2개의 1을 갖습니다. 이것은 문자가 2바이트를 차지한다는 것을 의미합니다.
11000011 10000000
--
다시 Ruby로 확인할 수 있습니다.
'À'.unpack('B*').first
# 1100001110000000
여기서 약간의 팁은 다음을 사용하여 출력 형식을 더 잘 지정할 수 있다는 것입니다.
'À'.unpack('B8 B8').join(' ')
# 11000011 10000000
'À'.unpack('B8 B8')
에서 배열을 얻습니다. 그런 다음 요소를 공백으로 결합하여 문자열을 얻습니다. unpack 매개변수의 8은 Ruby에게 2개 그룹으로 8비트를 가져오도록 지시합니다.
3바이트의 UTF-8
문자에 대한 유니코드 값이 이전 템플릿에서 사용 가능한 11비트에 맞지 않으면 추가 바이트가 필요합니다.
1110____ 10______ 10______
다시 한 번, 템플릿 시작 부분에 있는 3개의 1은 3바이트 문자를 읽을 것임을 알려줍니다.
이 템플릿에도 동일한 프로세스가 적용됩니다. 유니코드 값을 바이너리로 변환하고 슬롯을 오른쪽에서 왼쪽으로 채우기 시작합니다. 그 뒤에 빈 공간이 있으면 0으로 채우십시오.
4바이트의 UTF-8
일부 값은 이전 템플릿에서 사용했던 11개의 빈 비트보다 더 많이 사용합니다. 유니코드의 경우 "a" 또는 "大"와 같은 문자로도 볼 수 있는 이모티콘 🙂이 있는 예를 살펴보겠습니다.
"🙂"의 유니코드 값 또는 코드 포인트는 128578입니다. 이진수의 해당 숫자는 11111011001000010, 17비트입니다. 이것은 우리가 16개의 빈 슬롯을 가지고 있기 때문에 3바이트 템플릿에 맞지 않는다는 것을 의미하므로 메모리에서 4바이트를 차지하는 새 템플릿을 사용해야 합니다.
11110___ 10______ 10______ 10______
이진수로 숫자를 채우는 것으로 다시 시작합니다.Rust1
11110___ 10_11111 10011001 10000010
이제 나머지는 0으로 채웁니다. Rust1
1111000 10011111 10011001 10000010
이것이 Ruby에서 어떻게 보이는지 봅시다.
4바이트가 필요하다는 것을 이미 알고 있으므로 출력에서 더 나은 가독성을 위해 최적화할 수 있습니다.
'🙂'.unpack('B8 B8 B8 B8').join(' ')
# 11110000 10011111 10011001 10000010
하지만 그렇지 않은 경우 다음을 사용할 수 있습니다.
'🙂'.unpack('B*')
바이트를 배열로 추출하기 위해 "bytes" 문자열 방법을 사용할 수도 있습니다.
"🙂".bytes
# [240, 159, 153, 130]
그런 다음 다음을 사용하여 요소를 바이너리로 매핑할 수 있습니다.
"🙂".bytes.map {|e| e.to_s 2}
# ["11110000", "10011111", "10011001", "10000010"]
문자열을 원하면 조인을 사용할 수 있습니다.
"🙂".bytes.map {|e| e.to_s 2}.join(' ')
# 11110000 10011111 10011001 10000010
UTF-8에는 유니코드에 필요한 것보다 더 많은 공간이 있습니다.
UTF-8의 또 다른 중요한 측면은 모든 유니코드 값(또는 코드 포인트)을 포함할 수 있다는 것입니다. 현재 존재하는 값뿐 아니라 미래에 존재할 값도 포함됩니다.
이는 4바이트 템플릿을 사용하는 UTF-8에서 채울 슬롯이 21개이기 때문입니다. 즉, 최대 2^21(=2,097,152) 값을 저장할 수 있으며, 이는 표준으로 사용할 수 있는 최대 유니코드 값인 약 110만 개보다 훨씬 많습니다.
이것은 우리가 새로운 문자나 언어를 할당하기 위해 미래에 다른 인코딩 시스템으로 전환할 필요가 없다는 확신을 가지고 UTF-8을 사용할 수 있다는 것을 의미합니다.
Ruby에서 다양한 인코딩 작업
Ruby에서는 다음을 수행하여 주어진 문자열의 인코딩을 즉시 확인할 수 있습니다.
'Hello'.encoding.name
# "UTF-8"
다른 인코딩 시스템으로 문자열을 인코딩할 수도 있습니다. 예:
encoded_string = 'hello, how are you?'.encode("ISO-8859-1", "UTF-8")
encoded_string.encoding.name
# ISO-8859-1
변환이 호환되지 않으면 기본적으로 오류가 발생합니다. "hello 🙂"를 UTF-8에서 ASCII로 변환하고 싶다고 가정해 봅시다. 이모티콘 "🙂"은 ASCII에 맞지 않으므로 할 수 없습니다. Ruby는 이 경우 오류를 발생시킵니다.
"hello 🙂".encode("ASCII", "UTF-8")
# Encoding::UndefinedConversionError (U+1F642 from UTF-8 to US-ASCII)
그러나 Ruby는 문자를 인코딩할 수 없는 경우 "?"로 대체할 수 있는 예외를 허용합니다.
"hello 🙂".encode("ASCII", "UTF-8", undef: :replace)
# hello ?
또한 특정 문자를 새 인코딩의 유효한 문자로 바꾸는 옵션도 있습니다.
"hello 🙂".encode("ASCII", "UTF-8", fallback: {"🙂" => ":)"})
# hello :)
Ruby에서 스크립트의 스크립트 인코딩 검사
작업 중인 스크립트 파일인 ".rb" 파일의 인코딩을 보려면 다음을 수행하십시오.
__ENCODING__
# This will show "#<Encoding:UTF-8>" in my case.
Ruby 2.0부터 Ruby 스크립트의 기본 인코딩은 UTF-8이지만 첫 번째 줄의 주석으로 변경할 수 있습니다.
# encoding: ASCII
__ENCODING__
# #<Encoding:US-ASCII>
하지만 UTF-8 표준을 변경할 이유가 없는 한 UTF-8 표준을 고수하는 것이 좋습니다.
Ruby에서 인코딩 작업을 위한 몇 가지 팁
Encoding.name_list
를 사용하여 Ruby에서 지원되는 전체 인코딩 목록을 볼 수 있습니다. . 이것은 큰 배열을 반환합니다:
["ASCII-8BIT", "UTF-8", "US-ASCII", "UTF-16BE", "UTF-16LE", "UTF-32BE", "UTF-32LE", "UTF-16", "UTF-32", "UTF8-MAC"...
영어 이외의 문자로 작업할 때 다른 중요한 측면은 Ruby 2.4 이전에는 upcase
또는 reverse
예상대로 작동하지 않았습니다. 예를 들어 Ruby 2.3에서는 upcase가 생각대로 작동하지 않습니다.
# Ruby 2.3
'öıüëâñà'.upcase
# 'öıüëâñà'
해결 방법은 Rails의 ActiveSupport 또는 다른 외부 gem을 사용하는 것이지만 Ruby 2.4부터 완전한 유니코드 대소문자 매핑이 있습니다.
# From Ruby 2.4 and up
'öıüëâñà'.upcase
# 'ÖIÜËÂÑÀ'
이모티콘으로 즐거운 시간
유니코드와 루비에서 이모티콘이 어떻게 작동하는지 봅시다:
'🖖'.chars
# ["🖖"]
이것은 "Vulcan Salute" 이모티콘으로도 알려진 "중지와 약지 사이에 부분이 있는 들린 손"입니다. 같은 이모티콘이 있지만 기본값이 아닌 다른 피부색을 사용하면 흥미로운 일이 발생합니다.
'🖖🏾'.chars
# ["🖖", "🏾"]
따라서 하나의 캐릭터가 아닌 하나의 단일 이모티콘에 두 가지가 있습니다.
거기서 무슨 일이 있었 니?
음, 유니코드의 일부 문자는 여러 문자의 조합으로 정의됩니다. 이 경우 컴퓨터에서 이 두 문자를 함께 보면 피부색이 적용된 한 문자만 표시됩니다.
플래그로 볼 수 있는 또 다른 재미있는 예가 있습니다.
'🇦🇺'.chars
# ["🇦", "🇺"]
유니코드에서 플래그 이모티콘은 🇦 또는 🇿와 같은 "지역 표시기 기호"라는 추상 유니코드 문자로 내부적으로 표현됩니다. 일반적으로 플래그 외부에서 사용되지 않으며 컴퓨터가 두 기호를 함께 볼 때 해당 조합에 대한 플래그가 있는 경우 플래그를 표시합니다.
직접 보려면 다음을 복사하여 텍스트 편집기나 필드에서 쉼표를 제거하십시오.
🇦,🇺
결론
유니코드와 UTF-8의 작동 방식, Ruby 및 잠재적 오류와의 관계에 대한 이 리뷰가 도움이 되었기를 바랍니다.
가장 중요한 교훈은 모든 종류의 텍스트로 작업할 때 연결된 인코딩 시스템이 있으며 저장하거나 변경할 때 현재 상태를 유지하는 것이 중요하다는 것을 기억하는 것입니다. 가능하면 UTF-8과 같은 최신 인코딩 시스템을 사용하여 나중에 변경할 필요가 없습니다.
Ruby 릴리스에 대한 참고 사항
이 기사의 모든 예제에 Ruby 2.6.5를 사용했습니다. 터미널로 이동하여 irb
를 실행하여 온라인 REPL에서 또는 로컬로 시도할 수 있습니다. Ruby가 설치되어 있는 경우.
유니코드 지원이 지난 릴리스에서 개선되었기 때문에 이 기사가 관련성을 유지하기 위해 최신 버전을 사용하기로 결정했습니다. 어쨌든 Ruby 2.4 이상에서는 모든 예제가 여기에 표시된 대로 작동해야 합니다.