Java 中文问题的根源分析及解决 在简体中文 MS Windows 98 + JDK 1.3 下,可以用 System.getProperties() 得到 Java 运行环境的一些基本属性,类 PoorChinese 可以帮助我们得到这些属性。 类 PoorChinese 的源代码: public class PoorChinese { } 执行 java PoorChinese 后,我们会得到: 系统变量 file.encoding 的值为 GBK ,user.language 的值为 zh , user.region 的值为 CN ,这些系统变量的值决定了系统默认的编码方式是 GBK 。 在上述系统中,下面的代码将 GB2312 文件转换成 Big5 文件,它们能够帮助我们理解 Java 中汉字编码的转化: ? import java.io.*; import java.util.*; ? public class gb2big5 { ? static int iCharNum=0; ? public static void main(String[] args) { System.out.println("Input GB2312 file, output Big5 file."); if (args.length!=2) { System.err.println("Usage: jview gb2big5 gbfile big5file"); System.exit(1); String inputString = readInput(args[0]); writeOutput(inputString,args[1]); System.out.println("Number of Characters in file: "+iCharNum+"."); } ? static void writeOutput(String str, String strOutFile) { try { FileOutputStream fos = new FileOutputStream(strOutFile); Writer out = new OutputStreamWriter(fos, "Big5"); out.write(str); out.close(); } catch (IOException e) { e.printStackTrace(); e.printStackTrace(); } } ? static String readInput(String strInFile) { StringBuffer buffer = new StringBuffer(); try { FileInputStream fis = new FileInputStream(strInFile); InputStreamReader isr = new InputStreamReader(fis, "GB2312"); Reader in = new BufferedReader(isr); int ch; while ((ch = in.read()) > -1) { iCharNum += 1; buffer.append((char)ch); } in.close(); return buffer.toString(); } catch (IOException e) { e.printStackTrace(); return null; } } } ? 编码转化的过程如下: GB2312------------------>Unicode------------->Big5 执行 java gb2big5 gb.txt big5.txt ,如果 gb.txt 的内容是“今天星期三”,则得到的文件 big5.txt 中的字符能够正确显示;而如果 gb.txt 的内容是“情人节快乐”,则得到的文件 big5.txt 中对应于“节”和“乐”的字符都是符号“?”(0x3F),可见 sun.io.ByteToCharGB2312 和 sun.io.CharToByteBig5 这两个基本类并没有编好。 正如上例一样, Java 的基本类也可能存在问题。由于国际化的工作并不是在国内完成的,所以在这些基本类发布之前,没有经过严格的测试,所以对中文字符的支持并不像 Java Soft 所声称的那样完美。前不久,我的一位技术上的朋友发信给我说,他终于找到了 Java Servlet 中文问题的根源。两周以来,他一直为 Java Servlet 的中文问题所困扰,因为每面对一个含有中文字符的字符串都必须进行强制转换才能够得到正确的结果(这好象是大家公认的唯一的解决办法)。后来,他确实不想如此继续安分下去了,因为这样的事情确实不应该是高级程序员所要做的工作,他就找出 Servlet 解码的源代码进行分析,因为他怀疑问题就出在解码这部分。经过四个小时的奋斗,他终于找到了问题的根源所在。原来他的怀疑是正确的, Servlet 的解码部分完全没有考虑双字节,直接把 %XX 当作一个字符。(原来 Java Soft 也会犯这幺低级的错误!) 如果你对这个问题有兴趣或者遇到了同样的烦恼的话,你可以按照他的步骤对 Servlet.jar 进行修改: 找到源代码 HttpUtils 中的 static private String parseName ,在返回前将 sb(StringBuffer) 复制成 byte bs[] ,然后 return new String(bs,”GB2312”)。作上述修改后就需要自己解码了: HashTable form=HttpUtils .parseQueryString(request.getQueryString())或者 form=HttpUtils.parsePostData(……) 千万别忘了编译后放到 Servlet.jar 里面。 

解决方案 »

  1.   

    qnzu(站)仁兄:
       谢谢你 !
    但我的问题好像主要是字节流和字符流间的问题,不然为何第一次读double型数据都出那样的问题?请继续指教!
      

  2.   

    readDouble
    public double readDouble()
                      throws IOException
    Reads eight input bytes and returns a double value. It does this by first constructing a long value in exactly the manner of the readlong method, then converting this long value to a double in exactly the manner of the method Double.longBitsToDouble. This method is suitable for reading bytes written by the writeDouble method of interface DataOutput. Returns:
    the double value read. 
    Throws: 
    EOFException - if this stream reaches the end before reading all the bytes. 
    IOException - if an I/O error occurs.所以还是不建议你直接用readDouble(),试试转成String或者byte再进行存取。
      

  3.   

    DataInputStream   <--> DataOutputStream
    保存的就是二进制数据,与平台无关,主要缺点儿就是无法辨认了PrintWriter或者PrintOutputStream提供字符流的写功能,并没有对应的类,必须要使用BufferedReader类来处理
      

  4.   

    teva(静夜狼)、acefr()两位兄台:
       按照你们的提示,好像没有很大的改观,主要问题是我用字符流写入时,可以在invoice.txt中显示正常的文本,但读出就有问题,好像还抛出了异常;而用字节流写入时,在invoice.txt中不能正常显示文本,但读出却没有问题?
       好像与acefr()所说的readDouble()为:“This method is suitable for reading bytes written by the writeDouble method of interface DataOutput”有关,请问如何解决呢?主要读取的问题了!
      

  5.   

    java流在处理上分为字符流和字节流。字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。Java内用Unicode编码存储字符,字符流处理类负责将外部的其他编码的字符流和java内Unicode字符流之间的转换。而类InputStreamReader和OutputStreamWriter处理字符流和字节流的转换。字符流(一次可以处理一个缓冲区)一次操作比字节流(一次一个字节)效率高。对应不同的流,需要不同的流构建器或流过滤实现。java中字节、字符流可以参见:http://www.javaresearch.org/article/showarticle.jsp?column=1&thread=195
      

  6.   

    谢谢acefr() 兄弟提供的说明和网址,看了感觉受益匪浅,但我的读数据问题还是没有解决,能帮我再看看吗?我都折磨一天了。
      

  7.   

    今天快下班了,明天再看看。
    你试试用float而不用double看看。
      

  8.   

    为什么要用DataOutputStream?
    FileOutputStream不是更好使吗?
      

  9.   

    如果你不反对的作这个方法来读写文件,读出你读出后再进行转换成double类型等4)读文件  FileReader       以行为单位读              较好
     BufferedReader in;
     FileReader file; 
    try
     {File f=new File ("F:\\my_bag\\vj_file\\读文件\\","target.htm");
     file=new FileReader(f);
     in=new BufferedReader (file);
     }
     catch(FileNotFoundException e){}
    -----------------
    public void actionPerformed(ActionEvent e)
    {String s;
    if(e.getSource ()==button)
    try
    {while((s=in.readLine ())!=null)
     text.append (s+'\n');
     }
    catch(IOException exp){}
    }
    }
    ///////////////////////////////////////////////////5)写文件  FileWriter          以行为单位写             较好
     BufferedWriter out;
     FileWriter tofile;
     try
      {tofile=new FileWriter ("write_in_here.txt");
       out=new BufferedWriter  (tofile);
      }
      catch(FileNotFoundException e){}
      catch(IOException e){}
      addWindowListener (new WindowAdapter()
    {public void windowClosing(WindowEvent e)
    {setVisible (false);
    System.exit (0);}
    });  
     
     }
     public void actionPerformed(ActionEvent e)
    {String s;
    if(e.getSource ()==button_me)
    try
    {out.write(text.getText (),0,(text.getText()).length ());
     out.flush ();
     }
    catch(IOException exp){text.setText ("have problem.");}
    }
    }
      

  10.   

    郁闷!我看了大家的建议,试了n种方法!还是不行!
       我总结了一下,是否只要是用字节流写入文件的,在文件中除String外大多显示为乱码,但再用字节流读出显示在控制台是正常的;用字符流写入文件的,在文件中可以正常显示!但用字节流读出显示在控制台将为乱码。是否就是说不能在读和写混合使用字符流和字节流,也不能单一使用字节流,不然总有一步显示出问题。那么如何才能实现往一个文件中写入不同类型的数据,再分别读出数据显示在控制台,而且每一步的数据显示都正常呢?
        高手能给一个完整的例子吗?谢谢!!
      

  11.   

    原因如下:首先你第一中写法:
    DataOutputStream,相当于读写二进制文件,都是精确读写的,故你读取的时候可以很顺利的使用readDouble等读出来,
    例如,浮点数19.99 在二进制文件里面是 40 33 FD 70 A3 D7 OA 3D 00 00 00
    int型则占有一个 FFFF,四个字节 所以,你写入这些的话,会在文件中产生这些字符,而很碰巧,一个char恰好是 FF 两个字节,而打开文本显示的时候,就只有英文可以显示,其他都一般是乱麻了
    而第二种写法:
    PrintStream(new FileOutputStream("购买.txt"));这种写法,很自然是把浮点数等任何字符都用普通的字符型写入,当然,文本可以自由读入,可是你会发现,这时候浮点数 19.99已经变成了 31 39 39 2E 39  ,只有5位了,所以,要正常想看文本文件,还是需要写出成字符型,但是此时你就不能用readDouble,readchar之类的定字符长度的操作了.
    俺又不由自主的浪费30分钟,希望对楼主有用
      

  12.   

    我觉得任何事情很难两全,以字符的方式写入可以使文件易读,但是数据类型已经被转化成字符串,不能直接通过二进制读写直接读出原来
    的类型,原则是用什么方式写入就应该用什么方式读出。所以要使数据文件可读,数据使用的便利性上就要有所牺牲。我有两个建议,第一个就是笨一点,写两个文件,一个用来看(相当于log),一个用来存贮数据。另外一个就是用字符的方式读写,但是你需要对读入的串做后处理,提出相应的信息,然后转化成为相应的类型。