Java String charAt(int index)
How does Java String’s charAt method work? How does it find a character at a particular index? Computers deal with binary values, which is a sequence of 0s and 1s, so it means the characters are represented and manipulated as bits internally.
There must be some kind of mapping between the characters and binary values to have computers process human language characters or other symbols. Unicode is a character encoding standard, and java uses its UTF-16.
The official Oracle website says:
A
Stringrepresents a string in the UTF-16 format in which supplementary characters are represented by surrogate pairs (see the section Unicode Character Representations in theCharacterclass for more information). Index values refer tocharcode units, so a supplementary character uses two positions in aString.
The charAt(int index) method from String class and StringUTF16 class:
StringUTF16 class’ getChar method is called for the actual work.
byte[] val is a byte array representation of the string value, and index is the location of the character to be returned. When trying to get the character at index 4 of string “ᲶᲶᲶᲶᲶ”, val will be the byte array version of “ᲶᲶᲶᲶᲶ” and the index will be 4.
Get J’s stories in your inbox
Join Medium for free to get updates from this writer.
Then what will happen when new String(“ᲶᲶᲶᲶᲶ”).charAt(4) is executed?
The below code is a simplified version of what happens.
String inputString = "ᲶᲶᲶᲶᲶ";
byte[] bytesLE =inputString.getBytes("UTF-16LE");
for(byte by:bytesLE){
System.out.print(by +" ");
}
System.out.println("bytes size is "+bytesLE.length);
// -74 28 -74 28 -74 28 -74 28 -74 28 bytes size is 10
byte[] bytes =inputString.getBytes("UTF-16");
for(byte by:bytes){
System.out.print(by +" ");
}
System.out.println("bytes2 size is "+bytes.length);
// -2 -1 28 -74 28 -74 28 -74 28 -74 28 -74 bytes2 size is 12
byte[] bytesBE =inputString.getBytes("UTF-16BE");
for(byte by:bytesBE){
System.out.print(by +" ");
}
System.out.println("bytes3 size is "+bytesBE.length);
// 28 -74 28 -74 28 -74 28 -74 28 -74 bytes3 size is 10Note the byte order mark is added at the beginning when LE or BE is not given to the getBytes method.
//UTF16 big endian
//char s = new String("ᲶᲶᲶᲶᲶ").charAt(4);
int index= 4;
index<<=1;
System.out.println("index "+index);
byte msbs = bytesBE[index]; // prints 8
System.out.println(msbs); // prints 28
int msbsPos= bytesBE[index] & 0xff; //byte*int results in int.
//0xff is = 0000...11111111
System.out.println(msbsPos);
msbsPos<<=8; //big endian
index++;
byte lsbs = bytesBE[index];
System.out.println(lsbs); //prints -74
int lsbPos= bytesBE[index] & 0xff; //byte*int results in int.
//0xff is =0000... 11111111
System.out.println(lsbPos); //prints 182
lsbPos<<=0; //big endian
char returningChar=(char) (msbsPos|lsbPos);
System.out.println(returningChar);
//prints ᲶLet’s take a look at the getChar method again.
index<< =1; is needed because each character will be represented by 2 bytes.
&0ff (& operation with 0000…1111 1111)happens next. Why is this done?
(val[index++] &0xff)<<8 | (val[index] &0xff)<<0
for LSBs, when OR opration happens with MSBs, binary numeric promotion is performed and LSBs part gets extra 1s at the front, not 0s.
10110110 becomes 11111111111111111111111110110110. Java uses two’s complement representation. 10110110 means this number will be 0 when its two’s complement 01001010 is added. 11111111111111111111111110110110 will become 0 as well when the same number (01001010) is added, so they are considered the same number in the system. 1 at the beginning of a binary representation means a negative number. Java’s byte ranges from -128 to 127(inclusive). In Ჶ’s byte array representation, LSBs decimal value is 182, and negative range has to be used to represent the value due to overflow.
To do bitwise OR with MSBs, unsigned byte value is needed. &0xff removes added 1s at the front and the negative number turns positive.
System.out.println("lsbs "+Integer
.toBinaryString(lsbs)
.substring(Integer.SIZE - Byte.SIZE));
//prints lsbs 10110110
System.out.println("lsbs "+Integer.toBinaryString(lsbs));
//prints lsbs 11111111111111111111111110110110
// &0ff will remove all the added 1s!for MSBs, 0xff prevents data loss due to bit shifting.
when MSBs are shifted back to its original place to concatenate the two bytes to return a char value. For big endian, HI_BYTE_SHIFT is 8 , and LO_BYTE_SHIFT is 0. <<8 means multiplying by 2⁸, and <<0 means multiplying by 2⁰(nothing happens).
However, MSBs value 00011100 shifted 8 times to the left will be 0. The bits will be lost because the bits go out of range. To keep the bits, it is necessary to do & operation with 0xff.
System.out.println("msbs in int after shifting "
+Integer.toBinaryString((msbs& 0xff)<<8));
// msbs in int after shifting 1110000000000
msbs &= 0xff; // this will not upcast. bits are lost.
msbs<<=8;
System.out.println("msbs in bytes after shifting "
+Integer.toBinaryString((msbs)));
//msbs in bytes after shifting 0







