1. Prefácio
De fato, desde que comecei a escrever código Java, encontrei inúmeros problemas de transcodificação e transcodificantes, como o código ilegal que ocorre ao ler um arquivo de texto em uma string, código ilegal que ocorre ao obter parâmetros de solicitação HTTP em um servlet, o código iluminado que ocorre quando consultado pelo JDBC, etc. Quando você os encontra, você pode resolvê-los com sucesso pesquisando-os, para não ter um entendimento profundo.
Até dois dias atrás, meu colega de classe conversou comigo sobre um problema de codificação de arquivo de origem Java (esse problema é analisado no último exemplo) e começou com esse problema e começou com uma série de problemas. Então discutimos enquanto pesquisamos as informações. Era tarde da noite que finalmente encontramos uma pista importante em um blog, resolvendo todas as dúvidas e as frases que não tínhamos entendido antes poderiam ser explicadas claramente. Portanto, decidi usar este ensaio para registrar minha compreensão de alguns problemas de codificação e os resultados do experimento.
Alguns dos conceitos a seguir são meu próprio entendimento com base nas condições reais. Se houver algum erro, certifique -se de corrigi -los.
2. Resumo do conceito
Nos primeiros dias, a Internet ainda não havia desenvolvido, e os computadores eram usados apenas para processar alguns dados locais, muitos países e regiões projetavam esquemas de codificação para idiomas locais. Esse tipo de codificação relacionada regional é chamada coletivamente de codificação ANSI (porque são extensões para códigos ANSI-ASCII). No entanto, eles não discutiram com antecedência como serem compatíveis entre si, mas, em vez disso, fizeram o seu próprio, o que colocou a raiz de codificar conflitos. Por exemplo, a codificação GB2312 usada no continente conflita com a codificação BIG5 usada em Taiwan. Os mesmos dois bytes representam caracteres diferentes nos dois esquemas de codificação. Com a ascensão da Internet, um documento geralmente contém vários idiomas, e o computador encontra problemas ao exibi -lo porque não sabe a que codificação desses dois bytes pertencem.
Tais problemas são comuns no mundo, portanto, exige redefinir um conjunto de personagens comum e a numeração unificada de todos os personagens do mundo está aumentando.
Como resultado, o código Unicode surgiu, numerou uniformemente todos os personagens do mundo. Como pode identificar exclusivamente um caractere, a fonte só precisa ser projetada para o código Unicode. No entanto, o padrão Unicode define um conjunto de caracteres, mas não especifica o esquema de codificação, ou seja, define apenas números abstratos e caracteres correspondentes, mas não especifica como armazenar uma sequência de números Unicode. O requisito real é como armazenar UTF-8, UTF-16, UTF-32 e outras soluções. Portanto, as codificações com o início do UTF podem ser diretamente convertidas por meio de cálculos e valores de unicode (pontos CodePoints, pontos de código). Como o nome sugere, o UTF-8 é uma codificação de comprimento de 8 bits, que é uma codificação de comprimento variável, usando 1 a 6 bytes para codificar um caractere (porque é restringido pela faixa de unicode, na verdade são apenas 4 bytes no máximo); O UTF-16 é uma codificação de unidade básica de 16 bits, que também é uma codificação de comprimento variável, 2 bytes ou 4 bytes; O UTF-32 é um comprimento fixo e um bytes fixo de 4 bytes armazena um número Unicode.
Na verdade, eu sempre fui um pouco mal -entendido sobre o Unicode antes. Na minha impressão, o código Unicode pode atingir apenas 0xffff, o que significa que ele pode representar apenas até 2^16 caracteres. Depois de ler cuidadosamente a Wikipedia, percebi que o início do esquema de codificação UCS-2 era realmente assim. UCS-2 usou dois bytes fixamente para codificar um caractere, para que ele possa codificar apenas caracteres dentro do intervalo de BMP (plano multilíngue básico, ou seja, 0x0000-0xffff, que contém os caracteres mais usados no mundo). Para codificar caracteres com unicode maior que 0xffff, as pessoas expandiram a codificação UCS-2 e criaram a codificação UTF-16, que é o comprimento variável. Na faixa BMP, o UTF-16 é exatamente o mesmo que o UCS-2, enquanto o UTF-16 fora do BMP usa 4 bytes para armazenar.
Para facilitar a descrição abaixo, deixe -me explicar o conceito de unidade de código (CodeUnit). O componente básico de uma determinada codificação é chamado de unidade de código. Por exemplo, a unidade de código do UTF-8 é de 1 byte e a unidade de código do UTF-16 é de 2 bytes. É difícil de explicar, mas é fácil de entender.
Para ser compatível com vários idiomas e melhor plataforma cruzada, o Javastring salva o código Unicode para os caracteres. Ele costumava usar o esquema de codificação UCS-2 para armazenar Unicode. Posteriormente, descobriu que os caracteres na faixa de BMP não eram suficientes, mas para considerações de consumo de memória e compatibilidade, não subiu para o UCS-4 (ou seja, UTF-32, codificação de 4 bytes fixada), mas adotou o UTF-16 mencionado acima. O tipo de char pode ser considerado como sua unidade de código. Essa prática causa algum problema. Se todos os caracteres estiverem dentro do intervalo BMP, tudo bem. Se houver caracteres fora do BMP, não é mais uma unidade de código correspondente a um caractere. O método de comprimento retorna o número de unidades de código, não o número de caracteres. O método Charat retorna naturalmente uma unidade de código em vez de um caractere, que se torna problemático ao atravessar. Embora alguns novos métodos de operação sejam fornecidos, ele ainda é inconveniente e não pode ser acessado aleatoriamente.
In addition, I found that Java does not process Unicode literals larger than 0xFFFF when compiling, so if you can't type a non-BMP character, but you know its Unicode code, you have to use a relatively stupid method to let String store it: manually calculate the UTF-16 encoding (four bytes) of the character, and use the first two bytes and the last two bytes as a Unicode number, and then assign the value to String. O código de amostra é o seguinte.
public static void main (string [] args) {// string str = ""; // queremos atribuir esse personagem, assumindo que meu método de entrada não possa ser digitado //, mas eu sei que seu unicode é 0x1d11e // string str = "/u1d11e"; // Isso não será reconhecido // para que possa ser calculado através do UTF-16 que codifica D834 dd1estring str = "/ud834/udd1e"; // então grave System.out.println (str); // saída com sucesso ""}O bloco de notas que vem com o Windows pode ser salvo como codificação Unicode, que realmente se refere à codificação UTF-16. Como mencionado acima, as codificações principais de caracteres usadas estão todas dentro da faixa de BMP e, dentro do intervalo BMP, o valor de codificação UTF-16 de cada caractere é igual ao valor Unicode correspondente, e é provavelmente o motivo pelo qual a Microsoft o chama de unicode. Por exemplo, eu inseri os dois caracteres "Good A" no bloco de notas e o salvei como a codificação Unicode Big Endian (alta prioridade de bits) e abri o arquivo com o Winex. O conteúdo é como mostrado na figura abaixo. Os dois primeiros bytes do arquivo são chamados de marcas de pedidos de bytes (marca de pedidos de bytes), (Fe FF) marca a ordem Endian como prioridade de bit alta e, em seguida, (59 7D) é o código Unicode "bom" e (00 61) é o código "A" Unicode.
Com o código Unicode, o problema não pode ser resolvido imediatamente, porque antes de tudo, há uma grande quantidade de dados de codificação padrão não unicode no mundo, e é impossível descartá-los. Em segundo lugar, a codificação do Unicode geralmente ocupa mais espaço do que a codificação da ANSI; portanto, da perspectiva de salvar recursos, a codificação da ANSI ainda é necessária. Portanto, é necessário estabelecer um mecanismo de conversão para que a codificação do ANSI possa ser convertida em unicode para processamento unificado, ou o Unicode pode ser convertido em codificação da ANSI para atender aos requisitos da plataforma.
O método de conversão é relativamente fácil de dizer. Para séries UTF ou ISO-8859-1, as codificações compatíveis podem ser diretamente convertidas através de valores de cálculo e unicode (na verdade, também pode ser uma pesquisa de tabela). Para a codificação do ANSI sobrando do sistema, isso só pode ser feito olhando a tabela. A Microsoft chama essa tabela de mapeamento (página de código) e classifica e numerada pela codificação. Por exemplo, nosso CP936 comum é a página de código GBK e o CP65001 é a página de código UTF-8. A figura a seguir é a tabela de mapeamento de GBK-> Unicode encontrada no site oficial da Microsoft (visualmente incompleto). Da mesma forma, deve haver uma tabela de mapeamento reversa de unicode-> GBK.
Com uma página de código, você pode executar facilmente várias conversões de codificação. Por exemplo, convertendo do GBK para o UTF-8, você só precisa dividir os dados por caracteres de acordo com as regras de codificação GBK, usar os dados codificados de cada caractere para verificar a página do código GBK, obter seu valor Unicode e, em seguida, usar o Unicode para ver o UTF-8 Código (ou calcular diretamente) e você pode usar o UTF-8. O mesmo vale para o contrário. Nota: UTF-8 é uma implementação padrão do Unicode. Sua página de código contém todos os valores do Unicode; portanto, qualquer codificação é convertida para o UTF-8 e depois convertida de volta, não será perdida. Nesse ponto, podemos tirar uma conclusão de que, para concluir o trabalho de conversão de codificação, o mais importante é converter com êxito ao Unicode; portanto, escolher corretamente o conjunto de caracteres (página de código) é a chave.
Depois de entender a natureza do problema de perda de transcodificação, de repente entendi por que a estrutura JSP usou o ISO-8859-1 para decodificar parâmetros de solicitação HTTP, o que levou ao fato de que tivemos que escrever tais declarações quando obtivemos parâmetros chineses:
Stringparam=newString(s.getBytes("iso-8859-1"),"UTF-8");
Como a estrutura JSP recebe um fluxo de bytes binários codificado por parâmetro, ele não sabe o que é codificando (ou não se importa) e não sabe qual página de código verificar para converter para unicode. Em seguida, escolheu uma solução que nunca causará perda. Ele pressupõe que esses são os dados codificados pelo ISO-8859-1 e, em seguida, pesquise na página Código ISO-8859-1 para obter a sequência Unicode. Como o ISO-8859-1 é codificado por bytes e, diferentemente do ASCII, ele codifica todo o espaço 0 ~ 255, para que qualquer byte possa ser encontrado em sua página de código. Se for virado do Unicode para o fluxo de bytes original, não haverá perda. Dessa forma, para programadores europeus e americanos que não consideram outros idiomas, eles podem decodificar diretamente a sequência com a estrutura JSP. Se eles querem ser compatíveis com outros idiomas, eles precisam retornar apenas ao fluxo de bytes originais e decodificá -lo com a página de código real.
Terminei de explicar os conceitos relacionados de codificação de unicode e caracteres. Em seguida, usarei exemplos de Java para experimentá -lo.
Iii. Exemplo de análise
1.
O método de construção da string é converter vários dados codificados em uma sequência Unicode (armazenada na codificação UTF-16). O seguinte código de teste é usado para mostrar a aplicação do método de construção de javastring. Os caracteres não-BMP estão envolvidos nos exemplos, portanto, os métodos CodePointat não são usados.
public class Test {public static void main (string [] args) lança ioexception {// "hello" gbk codificado byte [] gbkdata = {(byte) 0xc4, (byte) 0xe3, (byte) 0xba, (byte) 0xc3}; // "hello" {(byte) 0XA7, (byte) 0x41, (byte) 0XA6, (byte) 0x6e}; // Construa string e decodifique -o para unicodestring strfromgbk = new string (gbkdata, "gbk"); string strfrombig5 = new String (big5data Showunicode (strfromgbk); showunicode (strfombig5);} public static void showunicode (string str) {for (int i = 0; i <str.Length (); i ++) {System.out.printf ("// u%x", (int) str.charat (i));} System.out.println ();}Os resultados da operação são os seguintes
Pode -se descobrir que, como o código Unicode Masters de String, ele precisa ser convertido para outras codificações!
3. Usando Unicode como uma ponte para realizar a codificação de conversão mútua
Com a base das duas partes acima, é muito simples realizar a codificação e a conversão mútua. Você só precisa usá -los juntos. Primeiro, o Newstring converte os dados codificados originais em uma sequência Unicode e, em seguida, chama GetBytes para transferir para a codificação especificada.
Por exemplo, um código de conversão GBK para BIG5 muito simples é o seguinte
public static void main (string [] args) lança não suportesEnCodingException {// Suponha que este seja os dados lidos no arquivo em um fluxo de bytes (codificação gbk) byte [] gbkdata = {(byte) 0xc4, (byte) 0xe3, (byte) 0xba, (porte) 0xc4, (byte) 0Xe3, (byte) 0xba, porte) String (GBKData, "GBK"); // Converta de Unicode para Big5 que codifica byte [] big5Data = tmp.getbytes ("big5"); // Segunda operações ...}4. Problema de perda de codificação
Conforme explicado acima, a razão pela qual a estrutura JSP usa o caractere ISO-8859-1 definido para decodificá-lo. Primeiro, use um exemplo para simular este processo de restauração, o código é o seguinte
public class Test {public static void main(String[] args) throws UnsupportedEncodingException {//JSP framework receives 6 bytes of data byte[] data = {(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xbd};//Print the original data showbytes (dados); // estrutura jsp pressupõe que é a codificação ISO-8859-1, gera uma string objeto string tmp = new string (dados, "iso-8859-1"); // ******************************* Resultado de decodificação: " + tmp); // Assim, obtenha os 6 bytes originais de dados (procure com reversa a página de código do ISO-8859-1) byte [] utfdata = tmp.getBytes (" ISO-8859-1 "); // imprima os dados restaurados. UTF-8 para reconstruir o objeto String String result = new String (utfdata, "utf-8"); // imprima novamente, está correto! System.out.println ("UTF-8 Resultado de decodificação:" + resultado);} public static void showbytes (byte [] dados) {for (byte b: data) system.out.printf ("0x%x", b); system.out.println ();}}}}}}O resultado em execução é o seguinte. A primeira saída está incorreta porque as regras de decodificação estão incorretas. Também verifiquei a página de código incorretamente e recebi o unicode errado. Então eu descobri que os dados podem ser restaurados perfeitamente através da verificação errada do Unicode da página ISO-8859-1.
Este não é o ponto. Se a chave é substituir a "China" por "China", a compilação será bem -sucedida e o resultado da operação será mostrado na figura abaixo. Além disso, pode -se descobrir ainda que, quando o número de caracteres chineses é estranho, a compilação falha e quando o número é par, ele passa. Por que isso? Vamos analisá -lo em detalhes abaixo.
Como o javstring usa o Unicode internamente, o compilador transcodificará nossos literais de string durante a compilação e converterá da codificação do arquivo de origem para o Unicode (a Wikipedia diz que usa uma codificação ligeiramente diferente do UTF-8). Ao compilar, não especificamos o parâmetro de codificação, para que o compilador o decodifique no GBK por padrão. Se você tiver algum conhecimento de UTF-8 e GBK, deve saber que geralmente um personagem chinês precisa de 3 bytes para usar a codificação UTF-8, enquanto o GBK precisa apenas de 2 bytes. Isso pode explicar por que a paridade do número do personagem afetará o resultado, porque se houver 2 caracteres, a codificação UTF-8 ocupa 6 bytes e a decodificação no GBK pode ser decodificada para 3 caracteres. Se for um caractere, haverá um byte não aplicável, que é o local onde o ponto de interrogação na figura.
Para ser mais específico, a codificação UTF-8 da palavra "China" no arquivo de origem é e4b8ade59bbd. O compilador o decodifica em GBK. Os pares de 3 bytes procuram CP936 para obter 3 valores de unicode, que são 6d93E15E6D57, respectivamente, correspondendo aos três caracteres estranhos no gráfico de resultado. Conforme mostrado na figura abaixo, após a compilação, esses três unicodes são realmente armazenados na codificação do tipo UTF-8 no arquivo .class. Ao executar, o unicode é armazenado na JVM. No entanto, quando a saída final for em saída, ela ainda será codificada e passada para o terminal. A codificação acordada desta vez é a codificação definida pela área do sistema; portanto, se as configurações de codificação do terminal forem alteradas, ela ainda será distorcida. Nosso E15E aqui não define os caracteres correspondentes no padrão Unicode; portanto, o visor será diferente sob fontes diferentes em diferentes plataformas.
Pode-se imaginar que, se o arquivo de origem for armazenado na codificação GBK e, em seguida, enganar o compilador a dizer que é UTF-8, ele basicamente não pode ser compilado e passado, não importa quantos caracteres chineses sejam inseridos, porque a codificação do UTF-8 é muito regular, e os bytes combinados aleatoriamente não cumprirão o UTF-88.
Obviamente, a maneira mais direta de permitir que o compilador converta a codificação para unicode corretamente é dizer honestamente ao compilador qual é a codificação do arquivo de origem.
4. Resumo
Após esta coleção e experimento, aprendi muitos conceitos relacionados à codificação e me familiarizei com o processo específico de conversão de codificação. Essas idéias podem ser generalizadas para várias linguagens de programação, e os princípios de implementação são semelhantes. Então, acho que não vou mais ignorar esse tipo de problema no futuro.
O exposto acima é todo o conteúdo deste artigo sobre exemplos de conceito de codificação, como ANSI, Unicode, BMP, UTF, etc. Espero que seja útil para todos. Amigos interessados podem continuar se referindo a outros tópicos relacionados neste site. Se houver alguma falha, deixe uma mensagem para apontá -la. Obrigado amigos pelo seu apoio para este site!