CodeGym /Cursos /C# SELF /Principais tipos de codificação:

Principais tipos de codificação: UTF-8, UTF-16, ASCII

C# SELF
Nível 37 , Lição 1
Disponível

1. Introdução

A gente já entendeu que, por mais inteligentes que sejam os computadores, eles não entendem por si só o que é a letra "A" ou o símbolo "⌘". Eles entendem zeros e uns, e para decodificar símbolos legíveis por humanos precisam de um tradutor — uma codificação.

A história das codificações é uma história de compromissos e evolução. No começo tudo era simples, depois ficou mais complicado, e finalmente surgiu um padrão mais ou menos universal. Vamos percorrer essa cronologia.

Começo da história

Vamos começar do início. Na aula anterior já lembramos do antecessor das codificações de texto — ASCII (lê-se "aski"). Lembre que isso se expande como American Standard Code for Information Interchange, ou seja, o código padrão americano para troca de informação. Pelo próprio nome já dá pra entender para quem foi feito e por que é "americano".

ASCII foi desenvolvida nos anos 1960 e se tornou o primeiro padrão amplamente usado para codificação de caracteres. Ela consiste em um conjunto de 128 caracteres:

  • Letras latinas (maiúsculas e minúsculas): A-Z, a-z
  • Números: 0-9
  • Sinais de pontuação: .,!?"' etc.
  • Alguns caracteres de controle: quebra de linha, tabulação, etc.

Cada um desses 128 caracteres era codificado em um byte, usando apenas 7 bits dos 8 disponíveis (o bit mais significativo normalmente ficava livre ou era usado para checagem). Isso é bem compacto e eficiente para inglês.


Exemplo:
O caractere 'A' em ASCII é codificado como o byte 0x41 (em binário 01000001)
O caractere '!' em ASCII é codificado como o byte 0x21 (em binário 00100001)

Limitações:
A principal limitação do ASCII é óbvia: ele foi pensado para inglês. Se você quiser escrever algo em russo ("Привет"), alemão ("Grüße") ou chinês, o ASCII não vai ajudar — esses caracteres não existem na sua tabela. Isso levou ao surgimento de várias code pages (páginas de código) que tentavam estender os 128 caracteres do ASCII para 256, usando o oitavo bit. Por exemplo, para russo existiam páginas de código como CP1251 (Windows Cyrillic), KOI8-R e outras. O problema é que essas páginas de código eram incompatíveis entre si: o mesmo byte em páginas diferentes podia representar caracteres totalmente distintos! Uma verdadeira Torre de Babel.

Uso prático hoje:
O formato puro ASCII é raro hoje em dia para arquivos de texto gerais, exceto para usos muito específicos ou em sistemas legados. No entanto, seu legado vive: muitas codificações modernas, como veremos, são compatíveis retroativamente com ASCII.

Vamos tentar escrever e ler algo em ASCII, e depois vamos adicionar letras russas para ver o que acontece.

Crie um novo projeto de console no JetBrains Rider e chame, por exemplo, FileEncodingExplorer.

using System;
using System.IO;
using System.Text;

string file = "ascii.txt";
string asciiText = "Hello, world!";
string cyrillicText = "Olá, mundo!";

// Gravação em ASCII
using var writer = new StreamWriter(file, false, Encoding.ASCII);
writer.WriteLine(asciiText);
writer.WriteLine(cyrillicText);

// Leitura em ASCII
using var reader = new StreamReader(file, Encoding.ASCII);
string content = reader.ReadToEnd();
Console.WriteLine("Conteúdo do arquivo:");
Console.WriteLine(content);

Console.WriteLine("\nOs caracteres cirílicos foram substituídos por '?', porque ASCII não suporta cirílico!");

Ao executar esse código você verá que a parte em inglês é lida normalmente, mas o texto em russo vira pontos de interrogação (?) ou outros símbolos "desconhecidos". Isso acontece porque Encoding.ASCII não sabe converter os caracteres cirílicos em bytes, e simplesmente os substitui por algo "seguro" (normalmente ?), ou então bytes que correspondem a letras russas em alguma outra codificação são interpretados como outros símbolos pelo ASCII. No nosso caso, o StreamWriter força a substituição dos caracteres que não existem no ASCII por ? durante a escrita. Isso mostra por que é importante usar a codificação correta!

2. UTF-8: O rei da internet e da flexibilidade

Chegamos a uma das codificações mais importantes e provavelmente a mais popular hoje — UTF-8. É a codificação usada pela maior parte da internet, sistemas Linux e a maioria das aplicações modernas.

O que é?
UTF-8 (Unicode Transformation Format - 8-bit) é mais uma codificação do Unicode que resolve a ineficiência do UTF-16 para textos em inglês. UTF-8 é uma codificação de comprimento variável, mas com uma sacada inteligente:

  • Caracteres que são ASCII (códigos de 0 a 127) são codificados em um byte. E o melhor: esses bytes são idênticos à representação em ASCII! Isso significa que UTF-8 é retrocompatível com ASCII.
  • Outros símbolos são codificados em 2 a 4 bytes:
    • Cirílico — normalmente 2 bytes.
    • Muitos símbolos europeus com diacríticos, árabe, hebraico, grego — 2 bytes.
    • Ideogramas chinês/japonês/coreano — frequentemente 3 bytes.
    • Símbolos raros e alguns emojis — 4 bytes.

Exemplos da representação em bytes no UTF-8:

  • Caractere 'A' (ASCII): 01000001 (1 byte)
  • Caractere 'я' (russo): 11010001 10111111 (2 bytes)
  • Caractere '€' (Euro): 11100010 10000010 10101100 (3 bytes)
  • Caractere '😂' (emoji): 11110000 10011111 10011000 10000010 (4 bytes)

Por que UTF-8 é o rei?

  1. Eficiência: É bem compacto para textos com muitos caracteres ASCII (como inglês, código fonte, arquivos de configuração).
  2. Compatibilidade retroativa com ASCII: Se você lê um arquivo UTF-8 que contém apenas ASCII, pode lê-lo como ASCII que tudo funciona.
  3. Normalmente sem BOM: Ao contrário do UTF-16, o UTF-8 geralmente não usa BOM. Se houver um BOM (por exemplo EF BB BF), é opcional e às vezes causa problemas (por exemplo, ao parsear certos formatos ou em scripts Linux).

Desvantagens:

  • O comprimento variável pode complicar algumas operações (por exemplo, pular para o N-ésimo caractere sem escanear), mas em C# isso não é um problema: string já trabalha com pontos de código Unicode independentemente da codificação do arquivo.

Uso prático:

  • Páginas web (HTML, CSS, JavaScript),
  • APIs (JSON, XML),
  • Arquivos de configuração,
  • Código-fonte da maioria das linguagens,
  • Sistemas Linux/Unix.

Vamos escrever e ler um arquivo em UTF-8.

string file = "utf8.txt";
string text = "Hello, mundo! 😀 €";

// Gravação em UTF-8 (por padrão sem BOM)
File.WriteAllText(file, text, Encoding.UTF8);

// Leitura em UTF-8
string readText = File.ReadAllText(file, Encoding.UTF8);
Console.WriteLine(readText); // Tudo é lido corretamente!

Ao executar esse código e comparar o tamanho dos arquivos, você verá que o utf8.txt para texto misto costuma ser menor que o arquivo em UTF-16, e se o texto fosse só em inglês ele teria tamanho parecido com o ASCII.

3. UTF-16: Unicode para quase tudo

O problema da "Torre de Babel" das páginas de código virou uma dor de cabeça para desenvolvedores, especialmente quando programas ficaram globais. Precisávamos de uma solução universal. E ela surgiu — Unicode. Unicode não é uma codificação por si só, é uma grande tabela que associa a cada símbolo conhecido um código numérico único (code point).

O que é?
UTF-16 (Unicode Transformation Format - 16-bit) é uma codificação que originalmente supunha que todos os pontos de código do Unicode seriam codificados em dois bytes (16 bits).

  • A maioria dos caracteres (BMP, até 65535) é codificada em 2 bytes.
  • Para pontos fora do BMP são usadas pares substitutos — 4 bytes. Ou seja, UTF-16 também é de comprimento variável, mas frequentemente é visto como 2 bytes por caractere.

Ordem de bytes (Endianness) e BOM:

  • Big-Endian (BE): o byte mais significativo vem primeiro.
  • Little-Endian (LE): o byte menos significativo vem primeiro.
  • Para o leitor entender a ordem, frequentemente colocam-se no começo do arquivo um BOM (Byte Order Mark):
    • Para UTF-16 LE: FF FE
    • Para UTF-16 BE: FE FF
    No C# e no Windows por padrão usa-se UTF-16 LE.

Vantagens:

  • Suporta a grande maioria dos símbolos do mundo.
  • Fácil trabalhar com caracteres dentro do BMP (comprimento fixo de 2 bytes).

Desvantagens:

  • Ineficiente para textos em inglês: cada caractere ASCII ocupa 2 bytes.
  • A presença do BOM pode causar problemas se o leitor não esperar por ele.

Uso prático:
UTF-16 é amplamente usado internamente no Windows e, por exemplo, no Java — para a representação interna de strings. Arquivos de texto do Notepad com caracteres cirílicos frequentemente são salvos como UTF-16 LE com BOM.

string file = "utf16.txt";
string text = "Hello, mundo! 👋";

// Gravação em UTF-16 (por padrão Little-Endian, com BOM)
File.WriteAllText(file, text, Encoding.Unicode);

// Leitura em UTF-16
string readText = File.ReadAllText(file, Encoding.Unicode);
Console.WriteLine(readText); // Tudo é lido corretamente!

Console.WriteLine($"Tamanho do arquivo: {new FileInfo(file).Length} bytes");

Após executar você verá que todos os caracteres aparecem corretamente. Arquivos com texto em inglês em UTF-16 ocupam aproximadamente o dobro do espaço que ocupariam em ASCII ou UTF-8 (no intervalo ASCII).

4. Tabela resumida de comparação de codificações

Para sistematizar, vamos colocar as características principais em uma tabela.

Codificação Mínimo de bytes por caractere Máximo de bytes por caractere Compatibilidade com ASCII (direta) Usa BOM (por padrão no .NET) Exemplos de uso
ASCII 1 1 Completa Não Sistemas antigos, dados de texto muito simples, protocolos internos
UTF-16 2 4 Não Sim (Encoding.Unicode) Representação interna de strings no Windows, Java; arquivos de texto do Windows
UTF-8 1 4 Completa Não (Encoding.UTF8 no .NET 5+); Sim (Encoding.UTF8 no .NET Framework) Web (HTML, JSON), arquivos de configuração, código-fonte, Linux/Unix

Uma observação sobre Encoding.UTF8 no .NET:
Historicamente, no .NET Framework o Encoding.UTF8 adicionava um BOM por padrão. No .NET moderno (Core/5+) o comportamento mudou: por padrão o BOM não é adicionado. Se você precisa do BOM, use new UTF8Encoding(true).

5. Como especificar a codificação em C#

Como você já viu nos exemplos, para dizer ao StreamReader ou StreamWriter qual "dicionário" usar, passamos um objeto do tipo System.Text.Encoding.

System.Text.Encoding fornece opções prontas:

  • Encoding.ASCII: para trabalhar com ASCII.
  • Encoding.Unicode: UTF-16 LE (com BOM).
  • Encoding.UTF8: UTF-8 (sem BOM por padrão no .NET moderno).

Outras codificações estão disponíveis via Encoding.GetEncoding (por exemplo, "windows-1251", "koi8-r"), mas o foco aqui é Unicode.

// Gravação em UTF-8
using var writer = new StreamWriter("my_file.txt", false, Encoding.UTF8);
writer.WriteLine("Algum texto.");

// Leitura em UTF-16
using var reader = new StreamReader("another_file.txt", Encoding.Unicode);
string content = reader.ReadToEnd();
Console.WriteLine(content);

É isso! StreamReader e StreamWriter cuidam de converter caracteres em bytes e bytes em caracteres, seguindo as regras da codificação escolhida.

6. Problemas com codificações: "caracteres estranhos" (кракозябры)

Já vimos "caracteres estranhos" quando tentamos gravar texto russo em ASCII. Mas e se você gravou um arquivo em uma codificação e tentou ler em outra? Aí começa a diversão de verdade!

Imagine que uma mensagem em russo foi salva em UTF-8, mas seu cliente tenta lê-la como CP1251. As sequências de bytes serão interpretadas de forma errada, e em vez de "Привет, мир!" você vai ver "caracteres estranhos" (em inglês mojibake).

A razão é simples: incompatibilidade entre codificação na escrita e na leitura. Sempre use a mesma codificação nas duas etapas, a menos que você esteja intencionalmente convertendo.

string file = "mismatch.txt";
string russianText = "Olá, mundo!";

// Gravação em UTF-8 (correto!)
File.WriteAllText(file, russianText, Encoding.UTF8);

// Leitura incorreta: tentamos ler um arquivo UTF-8 como ASCII
string readAsAscii = File.ReadAllText(file, Encoding.ASCII);

Console.WriteLine($"Original: {russianText}");
Console.WriteLine($"Leitura como ASCII: {readAsAscii}"); // É aí que aparecem os 'caracteres estranhos' (mojibake)!

Execute o programa e você verá como o cirílico vira pontos de interrogação ou símbolos sem sentido. Na próxima aula vamos ver como trabalhar com codificações de forma mais flexível e evitar esses problemas, além de como detectar qual codificação um arquivo usa.

2
Tarefa
C# SELF, nível 37, lição 1
Bloqueado
Comparando tamanhos de arquivos para diferentes codificações
Comparando tamanhos de arquivos para diferentes codificações
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION