Charsets Encodings Java
Charsets Encodings Java
Introduction
Java can read, write, and convert among various character encodings. But many people don't entirely understand:
the difference between Unicode and UTF-16 how ISO 8859-1, Windows 1252, and ASCII are related how Java translates between character encodings what pitfalls await the unwary, when dealing with databases, web browsers, and data files
Introduction
This talk attempts to demystify the terminology, the technology, and the trials and tribulations associated with handling multiple character sets in Java applications.
Those are worthy topics, but beyond the scope of this presentation
The terms character set and encoding are often used interchangeably.
Most people do not make a distinction between the numbers assigned to characters and the way those numbers are stored in the computer. I think that distinction can be clarifying.
Character Set: A collection of characters Font: A collection of glyphs, usually in a specific style (Arial, Helvetica, Times, Old English, etc.) Coded Character Set: A character set where each character has been assigned a specific numeric value, or code point.
Unfortunately, the term character set is also sometimes used here, leading to confusion. Think of this as a chart on the wall.
Character Layout: My term, used in this presentation as an alternative to Coded Character Set.
I introduce this non-standard term solely to avoid confusion between character set and coded character set. (Terminology is a real problem in this area.)
10
First edition: 1963 Based on ordering of English alphabet Consists of 128 characters, including nonprinting control characters Commonly encoded as 7 bits in an 8-bit byte That's not the only historical encoding, though (as we'll see)
11
Part of ISO/IEC 8859 series of ASCII-based character sets Generally, we treat ISO 8859-1 as an encoding, but for the moment, let's treat it as a character layout. Identical to US-ASCII from 0 to 128. Has more characters (including many with accent marks), from 160 through 255. Codes 128 through 159 are undefined
12
Also called CP 1252, for code page 1252) The normal character set on Windows Again, most people think of it as an encoding. I say it's also a character layout. Identical to US-ASCII from 0 to 128. Identical to ISO 8859-1, from 160 through 255. Codes 128 through 159 are Windowsspecific
13
In the Windows world, sometimes called the ANSI code page Never standardized by ANSI. Name was taken from an early ANSI draft, later modified to become ISO 8859-1. Microsoft says: "The term ANSI as used to signify Windows code pages is a historical reference, but is nowadays a misnomer that continues to persist in the Windows community."
14
A universal character set, maintained by the Unicode Consortium (unicode.org) Provides the basis for processing, storage, and interchange of text data in any language in all modern software and information technology protocols). Unicode Basic FAQ calls it an encoding, but I say it's a character layout. ;-) Characters are also called code points and are denoted by U+xxxx, where xxxx is the hexadecimal character code.
15
Very large, containing all the characters for all the writing systems of the world, ancient and modern Codes 0 through 127 are the same as ASCII Codes 160 through 255 are the same as Windows 1252 and ISO 8859-1
Note, though, that the encodings of these nonASCII characters turn out never to be the same as Windows 1252 or ISO 8859-1
Unicode divides characters into 17 planes, 16 each with 65,536 (i.e., 2 ) characters.
contains characters U+0000 through U+FFFF consists of most of the character assignments so far
If you delve into this stuff at any level, you'll see lots of references to the BMP
17
Common Encodings
Okay, so what's the difference between this odd character layout term and an encoding? Let's start by looking at each of the previously mentioned character layouts.
18
Not the only historical encoding, though. e.g., 1970s-era Control Data Corporation Cyber mainframes:
Word size was 60 bits Native character set was 6 bits (no lower case), packed 10 per word. Not byte-addressable. For interoperability, they had an ASCII encoding: 7-bit ASCII characters in a 12-bit byte (packed 5 per word)
19
An 8-bit encoding of the ISO 8859-1 character layout. There really aren't any other encodings. On US-based Unix systems, the JVM typically assumes the host operating system is using ISO 8859-1, unless told otherwise. Called ISO8859_1 in Java
20
An 8-bit encoding of the Windows 1252 character layout. (There really aren't any other encodings.) On US-based Windows systems, the JVM typically assumes the host operating system is using CP 1252, unless told otherwise. Called Cp1252 in Java
21
Unicode is not an encoding (at least, not the way I define encoding). There are several encodings of Unicode. You can freely convert between them without losing information.
22
Let's examine each of these encodings in more detail, starting with UTF-16.
23
Java uses UTF-16 as its internal character representation. Variable-length word encoding of Unicode Each code point (character) is mapped to a sequence of 16-bit words. The characters in the Basic Multilingual Plane are encoded in one 16-bit word. For characters in other planes, the encoding is a pair of 16-bit words
Identical to UTF-16, except that it doesn't support surrogate pairs Can only encode the Basic Multilingual Plane Fixed 16-bit length Java used UCS-2 initially, but added UTF-16 supplementary character support in J2SE 5.0
25
Requires 16-bit leading byte-order mark (BOM) BOM is encoded version of the Zero-width Non-breaking Space (ZWNBSP) character, U+FEFF 0xFE 0xFF means big-endian 0xFF 0xFE means little-endian Java name for this encoding: UTF16
26
Bytes are in big-endian order No byte-order mark Java name for this encoding: UTF-16BE
27
Bytes are in little-endian order No byte-order mark Java name for this encoding: UTF-16LE
28
A variable-length byte-oriented encoding Encodes each character (code point) in 1 to 4 octets (8-bit bytes) There's a straightforward mapping that dictates how many bytes a character needs.
29
Indistinguishable from ASCII encoding for these characters is one byte in CP 1252 & ISO 8859-1: 0xA9 In UTF-8, is encoded as: 0xC2 0xA9
Three bytes needed for remainder of most common characters Compatibility with ASCII makes UTF-8 popular for web pages, documents, etc.
30
Many Windows programs (e.g., Notepad) add the bytes 0xEF 0xBB 0xBF at the start of files saved as UTF-8
This is the UTF-8 encoding of a Unicode byteorder mark (U+FEFF) A BOM is silly for byte-oriented UTF-8. Ostensibly, it allows code to distinguish UTF-8 files from ISO 8859-1 or Windows 1252 ones. Java does not recognize this byte-order mark. You can't cat two such files together.
31
UTF-32 uses exactly 32 bits for each Unicode code point. All Unicode characters can be encoded in 32 bits.
Highly space-inefficient, so not used very often Like UTF-16, there are three related encodings to handle endian issues Java names: UTF-32, UTF-32BE, UTF32-LE
32
Java stores characters internally as UTF-16 Java uses translation tables to map between external encodings and UTF-16.
Map from external encoding to UTF-16 on input. Map from UTF-16 to external encoding on output.
33
Character Sets, Encodings, Java and Other Headaches 34
It:
opens a stream of bytes from a file, via the FileInputStream wraps the FileInputStream in a Reader that treats those bytes as UTF-8 encoded characters converts the UTF-8 characters to Java's internal UTF-16
35
Java only deals with UTF-16 internally. Inbound characters are converted to UTF16. Outbound characters are converted from UTF-16 to whatever the output encoding is. Unless you're reading and writing UTF-16, all character I/O requires conversion to and from Java's canonical UTF-16 encoding. This is a perfectly reasonable and sound approach.
36
http://java.sun.com/javase/6/docs/technotes/g uides/intl/encoding.doc.html
37
Pitfalls
* Whew *
With that out of the way, let's look at some encoding-related pitfalls.
38
Many people and tools use the term Unicode inappropriately. When Microsoft Office Excel presents you with a Save As Unicode Text option, what does that mean?
Does it mean UTF-8? (This answer seems logical.) UTF-16? UTF-32? (Man, I hope not...)
39
It turns out to mean UTF-16. If you don't know that, you'll have trouble reading a Unicode CSV file produced by Excel.
40
You read a file encoded in Windows 1252 The file has a symbol (0x99 in Windows 1252) Java translates the to U+2122 in UTF-16 You write the data out as ISO 8859-1 Java cannot translate the to ISO 8859-1, because the character does not exist in that encoding Java writes a ? character, instead
41
42
Customer sends you a UTF-8 CSV file. You don't realize it has a UTF-8 byte-order mark. You decode the CSV file into an internal set of rows and columns. You find that the first column of the first row has an unexpected U+FEFF character.
If you write the file back out, those characters get translated to the target encoding (or replaced with ?) The U+FEFF character will screw up numeric parsing
43
Open the file as UTF-8 Wrap your Reader in a PushbackReader Read the first character, which converts it from UTF-8 to UTF-16
If the character is U+FEFF (the BOM), ignore it and move on. If the character is not U+FEFF, push it back and move on.
44
Your Grand Web Application delivers web pages encoded in ISO 8859-1. A user, in Internet Explorer, enters data in one of your forms and includes a symbol (0x99 in Windows 1252). On submission, IE converts the form data from Windows 1252 to ISO 8859-1, due to the document's encoding. ISO 8859-1 has no . IE doesn't map the to a ?. Instead, it leaves it alone.
45
Java, reading the form, converts the incoming data from ISO 8859-1 to UTF-16. When it sees the (illegal) 0x99 character, it doesn't barf or convert it; it just passes the character along as a Unicode U+0099. U+0099 is a control character in Unicode. The user's just became an obscure nonprintable. If you write the data as ISO 8859-1 or CP 1252, it'll go out as a 0x99. If you write it as UTF-8, you get 0xC2 0x99. In ASCII, you'll write a ?.
46
47
Create the database as ISO 8859-1, and VARCHAR2 columns hold ISO 8859-1 characters. Create the database as UTF-8, and those columns hold UTF-8. This approach is nice, because the DDL remains the same. Oracle also supports NCHAR and NVARCHAR2, for column-specific encodings.
48
You could use them to store UTF-8, but you'd have to keep track of conversions yourself. The character sizes would be off, as well.
If you change your DDL to use NVARCHAR and NCHAR, you can store Unicode (encoded as UTF-16).
49
MySQL allows you to specify character sets at the server, database, table, and column level.
Examples:
50
See
http://www.postgresql.org/docs/8.4/s tatic/multibyte.html
Example:
CREATE DATABASE db WITH ENCODING 'UTF8' $ createdb -E UTF8 db
51
I actually had to debug this situation in a former life. It combines many of the previous pitfalls and illustrates
how hairy this stuff can get how you might debug some of the problems
52
Web-based user interface, backed by Java servlets Support for Oracle or SQL Server
53
Nightmare Scenario #1
User entered product data, including a symbol When product data redisplayed on the page, the had been replaced with a ?
What happened, and how do we fix it? First, what's the configuration?
User is on Windows, with Internet Explorer Database is using ISO 8859-1 encoding
54
Nightmare Scenario: #1
55
Nightmare Scenario #1
What actually happened:
IE converted the CP 1252 (0x99) to a UTF-8 symbol Java converted it to a UTF-16 version The JDBC driver used the JDK's encoding tables to convert the to ISO 8859-1, so it was converted to a ? and stored that way in the database.
56
Nightmare Scenario #2
User entered product data, including a symbol When product data redisplayed on the page, the had been replaced with a double quote. User is on Windows, with Internet Explorer Database is using Windows 1252 encoding Different database engine than Customer A
57
Nightmare Scenario #2
What actually happened:
IE converted the CP 1252 (0x99) to a UTF-8 symbol Java converted it to a UTF-16 version (0x2122) The JDBC driver passed the unconverted UTF-16 over the wire to the RDBMS. The RDBMS truncated it to 8 bits, yielding 0x22 (a double quote in Windows 1252). A double quote was stored in the database.
58
Lessons to Learn
This stuff is a complete pain in the ass. But, because there's no one character set that is perfect for all occasions, this stuff is also unavoidable.
Understanding how it all fits together can save valuable time and confusion. Lots of people don't understand this stuff, so with a small amount of effort, you can become the resident expert.
59
If you encounter this kind of problem, get as much information as possible about the environment.
What browser are they using, if applicable? Where are they running Java? (Useful to know the default encoding.) What databases is Java talking to, and how are they encoded? What are the encodings of the files being read? What encoding is used when HTML forms are submitted?
60
Reproducing the problem in an isolated test case is the gold standard. Wire-sniffing tools like tcpdump, Ethereal and WireShark are invaluable: They will show you what's actually going over the wire, so you don't have to guess. You absolutely must have a good hex dump utility. (e.g., on Unix: hexdump -C) Write a small program that converts between encodings. It's easy to do, and it's damned useful to have around.
61
Useful Links
http://www.unicode.org/ http://www.joelonsoftware.com/articles/Unicode.html http://en.wikipedia.com/wiki/Unicode http://en.wikipedia.com/wiki/UTF-16 http://en.wikipedia.com/wiki/UTF-8 Tim Bray's Characters vs. Bytes:
http://www.tbray.org/ongoing/When/200x/2003/04/26/UTF This presentation will be on the Philly JUG site, as well as my web site:
http://www.ardentex.com/publications/charsets-encodings-java.pdf
62
Acknowledgments
The following individuals reviewed the original draft(s) of this presentation and provided valuable feedback:
Mark Chadwick Matt Dymek Tom Hjellming Steve Sapovits Drew Sudell Jon Tulk
63
Thank you!
If you made it this far, and your brain didn't explode, pat yourself on the back. Feel free to drop me an email, if you have questions or comments about this presentation: [email protected]
64