`

Java 之 String 的用法及相关注意

    博客分类:
  • JAVA
阅读更多

一、String方法列表

1、length() 字符串的长度
  例:char chars[]={'a','b'.'c'};
    String s=new String(chars);
    int len=s.length();

2、charAt() 截取一个字符
  例:char ch;
    ch="abc".charAt(1); 返回'b'

3、 getChars() 截取多个字符
  void getChars(int sourceStart,int sourceEnd,char target[],int targetStart)
  sourceStart指定了子串开始字符的下标,sourceEnd指定了子串结束后的下一个字符的下标。因此,子串包含从sourceStart 到sourceEnd-1的 字符。接收字符的数组由target指定,target中开始复制子串的下标值是 targetStart。
  例:String s="this is a demo of the getChars method.";
    char buf[]=new char[20];
    s.getChars(10,14,buf,0);

4、getBytes()
  替代getChars()的一种方法是将字符存储在字节数组中,该方法即getBytes()。

5、toCharArray()

6、equals()和equalsIgnoreCase() 比较两个字符串

7、regionMatches() 用于比较一个字符串中特定区域与另一特定区域,它有一个重载的形式允许在比较中忽略大小写。
  boolean regionMatches(int startIndex,String str2,int str2StartIndex,int numChars)
  boolean regionMatches(boolean ignoreCase,int startIndex,String str2,int str2StartIndex,int numChars)

8、startsWith()和endsWith()
  startsWith()方法决定是否以特定字符串开始,endWith()方法决定是否以特定字符串结束

9、equals()和==
  equals()方法比较字符串对象中的字符,==运算符比较两个对象是否引用同一实例。
  例:String s1="Hello";
    String s2=new String(s1);
    s1.eauals(s2); //true
    s1==s2;//false

10、compareTo()和compareToIgnoreCase() 比较字符串

11、indexOf()和lastIndexOf()
  indexOf() 查找字符或者子串第一次出现的地方。
  lastIndexOf() 查找字符或者子串是后一次出现的地方。

12、substring() 它有两种形式,分别是:
    String substring(int startIndex)
  String substring(int startIndex,int endIndex)

13、concat() 连接两个字符串

14、replace() 替换,它有两种形式:
    第一种形式用一个字符在调用字符串中所有出现某个字符的地方进行替换,形式如下:
  String replace(char original,char replacement)
  例如:String s="Hello".replace('l','w');
  第二种形式是用一个字符序列替换另一个字符序列,形式如下:
  String replace(CharSequence original,CharSequence replacement)

15、trim() 去掉起始和结尾的空格

16、valueOf() 转换为字符串

17、toLowerCase() 转换为小写

18、toUpperCase() 转换为大写

19、StringBuffer构造函数
  StringBuffer定义了三个构造函数:
  StringBuffer()
  StringBuffer(int size)
  StringBuffer(String str)
  StringBuffer(CharSequence chars)
  
  (1)length()和capacity()
    一个StringBuffer当前长度可通过length()方法得到,而整个可分配空间通过capacity()方法得到。
  
  (2)ensureCapacity() 设置缓冲区的大小
    void ensureCapacity(int capacity)

  (3)setLength() 设置缓冲区的长度
    void setLength(int len)

  (4)charAt()和setCharAt()
    char charAt(int where)
    void setCharAt(int where,char ch)

  (5)getChars()
    void getChars(int sourceStart,int sourceEnd,char target[],int targetStart)

  (6)append() 可把任何类型数据的字符串表示连接到调用的StringBuffer对象的末尾。
    例:int a=42;
      StringBuffer sb=new StringBuffer(40);
      String s=sb.append("a=").append(a).append("!").toString();

  (7)insert() 插入字符串
    StringBuffer insert(int index,String str)
    StringBuffer insert(int index,char ch)
    StringBuffer insert(int index,Object obj)
    index指定将字符串插入到StringBuffer对象中的位置的下标。

  (8)reverse() 颠倒StringBuffer对象中的字符
    StringBuffer reverse()

  (9)delete()和deleteCharAt() 删除字符
    StringBuffer delete(int startIndex,int endIndex)
    StringBuffer deleteCharAt(int loc)

  (10)replace() 替换
    StringBuffer replace(int startIndex,int endIndex,String str)

  (11)substring() 截取子串
    String substring(int startIndex)
    String substring(int startIndex,int endIndex)

----------------------Tips1-------------------------------------

Java内存分配:

  1. 寄存器:我们在程序中无法控制;

  2. 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中;

  3. 堆:存放用new产生的数据;

  4. 静态域:存放在对象中用static定义的静态成员;

  5. 常量池:存放常量;

  6. 非RAM存储:硬盘等永久存储空间。

       引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或者对象起的一个 名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。(其实就是Java中的指针)。

  数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是Java比较占内存的原因。(不像C++中那样,临时对象在程序运行到 代码段外时会被立即析构掉,所以,返回临时对象的指针是危险的!)(Java则例外,可以返回临时对象,如果赋值给一个外部变量,则该对象仍然继续存在, 不会被回收,只是其临时引用变量被干掉而已)。

  常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。 虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值。在程序执行的时候,常量池会储存在 Method Area,而不是堆中。

  String常量池可以再运行时填充,需要调用String.intern();存在于.class文件中的常量池,在运行期被JVM装载,并 且可以扩充。当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的 引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;

  如果原先str引用的是堆中的对象:

  str=str.intern();//原先堆中的对象会变成垃圾。

  结论:

  a.栈中用来存放一些原始数据类型的局部变量数据和对象的引用(String,数组.对象等等)但不存放对象内容

  b.堆中存放使用new关键字创建的对象.

  c.字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).有的是编译期就已经创建好,存放在字符串常量池中,而有的是运行时才被创建.使用new关键字(或隐含使用new),存放在堆中。

----------------------Tips2-------------------------------------

Java把内存划分成两种:一种是栈内存,一种是堆内存。 

栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。 
        Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等 指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时 动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 
         栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类 型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。 
         栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义: 
            int a = 3; 
             int b = 3; 
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令a=4;那么编译器 会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这 种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,(实现机制是否同c++ STL中的copy-on-write?)因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

二、深入理解String 类

要理解 java中String的运作方式,必须明确一点:String是一个非可变类(immutable)。什么是非可变类呢?简单说来,非可变类的实例是不 能被修改的,每个实例中包含的信息都必须在该实例创建的时候就提供出来,并且在对象的整个生存周期内固定不变。java为什么要把String设计为非可 变类呢?你可以问问 james Gosling :)。但是非可变类确实有着自身的优势,如状态单一,对象简单,便于维护。其次,该类对象对象本质上是线程安全的,不要求同步。此外用户可以共享非可变对 象,甚至可以共享它们的内部信息。(详见 《Effective java》item 13)。String类在java中被大量运用,甚至在class文件中都有其身影,因此将其设计为简单轻便的非可变类是比较合适的。

1、创建。
    好了,知道String是非可变类以后,我们可以进一步了解String的构造方式了。创建一个Stirng对象,主要就有以下两种方式:
java 代码

String str1 = new String("abc");   
Stirng str2 = "abc";

     虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。对于第一种,jvm会马上在heap中创建一个String对 象,然后将该对象的引用返回给用户。对于第二种,jvm首先会在内部维护的strings pool中通过String的 equals 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象; 如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至strings pool中。注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到strings pool里面的,除非程序调用 String的intern方法。看下面的例子:
java 代码
String str1 = new String("abc"); //jvm 在堆上创建一个String对象

//jvm 在strings pool中找不到值为“abc”的字符串,因此 
//在堆上创建一个String对象,并将该对象的引用加入至strings pool中 
//此时堆上有两个String对象 
Stirng str2 = "abc"; 

if(str1 == str2){ 
       System.out.println("str1 == str2"); 
}else{ 
       System.out.println("str1 != str2"); 
} 
//打印结果是 str1 != str2,因为它们是堆上两个不同的对象 

String str3 = "abc"; 
//此时,jvm发现strings pool中已有“abc”对象了,因为“abc”equals “abc” 
//因此直接返回str2指向的对象给str3,也就是说str2和str3是指向同一个对象的引用 
if(str2 == str3){ 
       System.out.println("str2 == str3"); 
}else{ 
       System.out.println("str2 != str3"); 
} 
//打印结果为 str2 == str3


   再看下面的例子:
java 代码
String str1 = new String("abc"); //jvm 在堆上创建一个String对象 

str1 = str1.intern(); 
//程序显式将str1放到strings pool中,intern运行过程是这样的:首先查看strings pool 
//有没“abc”对象的引用,没有,则在堆中新建一个对象,然后将新对象的引用加入至 
//strings pool中。执行完该语句后,str1原来指向的String对象已经成为垃圾对象了,随时会 
//被GC收集。 

//此时,jvm发现strings pool中已有“abc”对象了,因为“abc”equels “abc” 
//因此直接返回str1指向的对象给str2,也就是说str2和str1引用着同一个对象, 
//此时,堆上的有效对象只有一个。 
Stirng str2 = "abc"; 

if(str1 == str2){ 
       System.out.println("str1 == str2"); 
}else{ 
       System.out.println("str1 != str2"); 
} 
//打印结果是 str1 == str2 

为什么jvm可以这样处理String对象呢?就是因为String的非可变性。既然所引用的对象一旦创建就永不更改,那么多个引用共用一个对象时互不影响。


2、串接(Concatenation)。
     java程序员应该都知道滥用String的串接操作符是会影响程序的性能的。性能问题从何而来呢?归根结底就是String类的非可变性。既然 String对象都是非可变的,也就是对象一旦创建了就不能够改变其内在状态了,但是串接操作明显是要增长字符串的,也就是要改变String的内部状 态,两者出现了矛盾。怎么办呢?要维护String的非可变性,只好在串接完成后新建一个String 对象来表示新产生的字符串了。也就是说,每一次执行串接操作都会导致新对象的产生,如果串接操作执行很频繁,就会导致大量对象的创建,性能问题也就随之而 来了。
    为了解决这个问题,jdk为String类提供了一个可变的配套类,StringBuffer。使用StringBuffer对象,由于该类是可变的,串 接时仅仅时改变了内部数据结构,而不会创建新的对象,因此性能上有很大的提高。针对单线程,jdk 5.0还提供了StringBuilder类,在单线程环境下,由于不用考虑同步问题,使用该类使性能得到进一步的提高。

3、String的长度
   我们可以使用串接操作符得到一个长度更长的字符串,那么,String对象最多能容纳多少字符呢?查看String的源代码我们可以得知类String中 是使用域 count 来记录对象字符的数量,而count 的类型为 int,因此,我们可以推测最长的长度为 2^32,也就是4G。
    不过,我们在编写源代码的时候,如果使用 Sting str = "aaaa";的形式定义一个字符串,那么双引号里面的ASCII字符最多只能有 65534 个。为什么呢?因为在class文件的规范中, CONSTANT_Utf8_info表中使用一个16位的无符号整数来记录字符串的长度的,最多能表示 65536个字节,而java class 文件是使用一种变体UTF-8格式来存放字符的,null值使用两个字节来表示,因此只剩下 65536- 2 = 65534个字节。也正是变体UTF-8的原因,如果字符串中含有中文等非ASCII字符,那么双引号中字符的数量会更少(一个中文字符占用三个字节)。 如果超出这个数量,在编译的时候编译器会报错。

三、String的实现细节

    1. 首先String不属于8种基本数据类型,String是一个对象。因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。

    2. new String()和new String(“”)都是申明一个新的空字符串,是空串不是null;

    3. String str=”kvill”;   String str=new String (“kvill”);的区别:

  在这里,简单引入常量池这个简单的概念。
    常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

看例1:
String s0=”kvill”;
String s1=”kvill”;
String s2=”kv” + “ill”;
System.out.println( s0==s1 );
System.out.println( s0==s2 );
结果为:
true
true
  首先,我们要知道Java会确保一个字符串常量只有一个拷贝
  因为例子中的s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符 串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中 ”kvill”的一个引用。
  所以我们得出s0==s1==s2;
  用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。

看例2:
String s0=”kvill”;
String s1=new String(”kvill”);
String s2=”kv” + new String(“ill”);
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
结果为:
false
false
false
  例2中s0还是常量池中”kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分new String(“ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。

  4. String.intern():
  再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个 方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用, 如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了
例3:
String s0= “kvill”;
String s1=new String(”kvill”);
String s2=new String(“kvill”);
System.out.println( s0==s1 );
System.out.println( “**********” );
s1.intern();
s2=s2.intern(); //把常量池中“kvill”的引用赋给s2
System.out.println( s0==s1);
System.out.println( s0==s1.intern() );
System.out.println( s0==s2 );
结果为:
false
**********
false //虽然执行了s1.intern(),但它的返回值没有赋给s1
true //说明s1.intern()返回的是常量池中”kvill”的引用
true

  最后我再破除一个错误的理解:
  有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的 Unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中“如果我把他说的这 个全局的 String表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:

看例4:
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() );
结果:
false
kvill kvill
true
  在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。
  s1==s1.intern()为false说明原来的“kvill”仍然存在;
  s2现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。

  5. 关于equals()和==:
  这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。

  6. 关于String是不可变的
  这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”;
就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” “ 生成 ”kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的“不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原 因了,因为StringBuffer是可改变的。

四、String和StringBuffer的区别

在java中有3个类来负责字符的操作

1.Character 是进行单个字符操作的,

2.String 对一串字符进行操作。不可变类。

3.StringBuffer 也是对一串字符进行操作,但是可变类。

String:
是对象不是原始类型.
为不可变对象,一旦被创建,就不能修改它的值.
对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.
String 是final类,即不能被继承.

StringBuffer:
是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象
它只能通过构造函数来建立,
StringBuffer sb = new StringBuffer();
note:不能通过付值符号对他进行付值.
sb = "welcome to here!";//error
对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer
中付值的时候可以通过它的append方法.
sb.append("hello");

字符串连接操作中StringBuffer的效率要比String高:

String str = new String("welcome to ");
str += "here";
的处理步骤实际上是通过建立一个StringBuffer,让侯调用append(),最后
再将StringBuffer toSting();
这样的话String的连接操作就比StringBuffer多出了一些附加操作,当然效率上要打折扣.

并且由于String 对象是不可变对象,每次操作Sting 都会重新建立新的对象来保存新的值.
这样原来的对象就没用了,就要被垃圾回收.这也是要影响性能的.

看看以下代码:
将26个英文字母重复加了5000次,

String tempstr = "abcdefghijklmnopqrstuvwxyz";
int times = 5000;
long lstart1 = System.currentTimeMillis();
String str = "";
for (int i = 0; i < times; i++) {
   str += tempstr;
}
long lend1 = System.currentTimeMillis();
long time = (lend1 - lstart1);
System.out.println(time);

可惜我的计算机不是超级计算机,得到的结果每次不一定一样一般为 46687左右。
也就是46秒。
我们再看看以下代码

String tempstr = "abcdefghijklmnopqrstuvwxyz";
int times = 5000;
long lstart2 = System.currentTimeMillis();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < times; i++) {
   sb.append(tempstr);
}
long lend2 = System.currentTimeMillis();
long time2 = (lend2 - lstart2);
System.out.println(time2);

得到的结果为 16 有时还是 0
所以结论很明显,StringBuffer 的速度几乎是String 上万倍。当然这个数据不是很准确。因为循环的次数在100000次的时候,差异更大。不信你试试。

根据上面所说:

str += "here";
的处理步骤实际上是通过建立一个StringBuffer,让侯调用append(),最后
再将StringBuffer toSting();

所以str += "here";可以等同于

StringBuffer sb = new StringBuffer(str);

sb.append("here");

str = sb.toString();

所以上面直接利用"+"来连接String的代码可以基本等同于以下代码

String tempstr = "abcdefghijklmnopqrstuvwxyz";
int times = 5000;
long lstart2 = System.currentTimeMillis();
String str = "";
for (int i = 0; i < times; i++) {
   StringBuffer sb = new StringBuffer(str);
   sb.append(tempstr);
   str = sb.toString();
}
long lend2 = System.currentTimeMillis();
long time2 = (lend2 - lstart2);
System.out.println(time2);

平均执行时间为46922左右,也就是46秒。

总结: 如果在程序中需要对字符串进行频繁的修改连接操作的话.使用StringBuffer性能会更高

五、String.getBytes()编码问题

String的 getBytes()方法是得到一个字串的字节数组,这是众所周知的。但特别要注意的是,本方法将返回该操作系统默认的编码格式的字节数组。如果你在使用 这个方法时不考虑到这一点,你会发现在一个平台上运行良好的系统,放到另外一台机器后会产生意想不到的问题。比如下面的程序:

class TestCharset
            {
            public static void main(String[] args)
            {
            new TestCharset().execute();
            }
            private void execute() {
            String s = "Hello!你好!";
            byte[] bytes = s.getBytes();
            System.out.println("bytes
            lenght is:" + bytes.length);
            }
            }

在一个中文WindowsXP系统下,运行时,结果为:

bytes lenght is:12

但是如果放到了一个英文的UNIX环境下运行:

$ java TestCharset
            bytes lenght is:9

如果你的程序依赖于该结果,将在后续操作中引起问题。为什么在一个系统中结果为12,而在另外一个却变成了9了呢?上面已经提到了,该方法是和平台(编码)相关的。

在中文操作系统中,getBytes方法返回的是一个GBK或者GB2312的中文编码的字节数组,其中中文字符,各占两个字节。而在英文平台中,一般的默认编码是“ISO-8859-1”,每个字符都只取一个字节(而不管是否非拉丁字符)。

Java中的编码支持

Java是支持多国编码的,在Java中,字符都是以Unicode进行存储的,比如,“你”字的Unicode编码是“4f60”,我们可以通过下面的实验代码来验证:

class TestCharset
            {
            public static void main(String[] args)
            {
            char c = '你';
            int i = c;
            System.out.println(c);
            System.out.println(i);
            }
            }

不管你在任何平台上执行,都会有相同的输出:

20320

20320就是Unicode “4f60”的整数值。其实,你可以反编译上面的类,可以发现在生成的.class文件中字符“你”(或者其它任何中文字串)本身就是以Unicode编码进行存储的:

char c = '\u4F60';
            ... ...

即使你知道了编码的编码格式,比如:

javac -encoding GBK TestCharset.java

编译后生成的.class文件中仍然是以Unicode格式存储中文字符或字符串的。使用String.getBytes(String charset)方法

所以,为了避免这种问题,我建议大家都在编码中使用String.getBytes(String charset)方法。下面我们将从字串分别提取ISO-8859-1和GBK两种编码格式的字节数组,看看会有什么结果:

class TestCharset
            {
            public static void main(String[] args)
            {
            new TestCharset().execute();
            }
            private void execute() {
            String s = "Hello!你好!";
            byte[] bytesISO8859 =null;
            byte[] bytesGBK = null;
            try
            {
            bytesISO8859 =
            s.getBytes("iso-8859-1");
            bytesGBK = s.getBytes("GBK");
            }
            catch
            (java.io.UnsupportedEncodingException e)
            {
            e.printStackTrace();
            }
            System.out.println
            ("--------------
            \n 8859 bytes:");
            System.out.println("bytes is:     " + arrayToString(bytesISO8859));
            System.out.println("hex format is:"
            + encodeHex(bytesISO8859));
            System.out.println();
            System.out.println
            ("--------------
            \n GBK bytes:");
            System.out.println("bytes is:
            " + arrayToString(bytesGBK));
            System.out.println("hex format
            is:" + encodeHex(bytesGBK));
            }
            public static final String
            encodeHex (byte[] bytes)
            {
            StringBuffer buff =
            new StringBuffer(bytes.length * 2);
            String b;
            for (int i=0; i<bytes.length ; i++)
            {
            b = Integer.toHexString(bytes[i]);
            // byte是两个字节的,
            而上面的Integer.toHexString会把字节扩展为4个字节
            buff.append(b.length() > 2 ? b.substring(6,8) : b);
            buff.append(" ");
            }
            return buff.toString();
            }
            public static final String
            arrayToString (byte[] bytes)
            {
            StringBuffer buff = new StringBuffer();
            for (int i=0; i<bytes.length ; i++)
            {
            buff.append(bytes[i] + " ");
            }
            return buff.toString();
            }
            }

执行上面程序将打印出:

--------------
            8859 bytes:
            bytes is:     72 101 108 108 111 33 63 63 63
            hex format is:48 65 6c 6c 6f 21 3f 3f 3f
            --------------
            GBK bytes:
            bytes is:     72 101 108 108 111 33
            -60 -29 -70 -61 -93 -95
            hex format is:48 65 6c 6c 6f 21 c4 e3 ba c3 a3 a1

可见,在s中提取的8859-1格式的字节数组长度为9,中文字符都变成了“63”,ASCII码为63的是“?”,一些国外的程序在国内中文环境下运行时,经常出现乱码,上面布满了“?”,就是因为编码没有进行正确处理的结果。

而提取的GBK编码的字节数组中正确得到了中文字符的GBK编码。字符“你”“好”“!”的GBK编码分别是:“c4e3”“bac3”“a3a1”。得到了正确的以GBK编码的字节数组,以后需要还原为中文字串时,可以使用下面方法:

new String(byte[] bytes, String charset)

   mysql 不支持 unicode,所以比较麻烦。
   将 connectionString 设置成 encoding 为 gb2312
   String connectionString
   = "jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=gb2312";
  
  测试代码:
   String str = "汉字";
   PreparedStatement pStmt = conn.prepareStatement("INSERT INTO test VALUES (?)");
   pStmt.setString(1,str);
   pStmt.executeUpdate();
  
  数据库表格:
   create table test (
   name char(10)
   )
  
  连接 Oracle Database Server
  -------------------------------------------------------------------------------
   在把汉字字符串插入数据库前做如下转换操作:
   String(str.getBytes("ISO8859_1"),"gb2312")
  
  测试代码:
   String str = "汉字";
   PreparedStatement pStmt = conn.prepareStatement("INSERT INTO test VALUES (?)");
   pStmt.setString(1,new String(str.getBytes("ISO8859_1"),"gb2312");
   pStmt.executeUpdate();
  
  Servlet
  -------------------------------------------------------------------------------
   在 Servlet 开头加上两句话:
   response.setContentType("text/html;charset=UTF-8");
   request.setCharacterEncoding("UTF-8");

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics