JNI中文处理惩罚问题小结
当前位置:以往代写 > JAVA 教程 >JNI中文处理惩罚问题小结
2019-06-14

JNI中文处理惩罚问题小结

JNI中文处理惩罚问题小结

副标题#e#

由于事情干系,需要操作JNI在C++与Java措施之间举办要领挪用和数据通报,但以前老是在英文情况下事情,对中文(其他语言编码同理)问题反倒没有太存眷,最近抽了点时间研究了一下,将本身的体会整理如下,供各人接头或参考。

在进一步接头之前,有几点基本常识需要说明:

在Java内部,所有的字符串编码回收的是Unicode即UCS-2。Unicode是用两个字节暗示每个字符的字符编码方案。Unicode有一个特性:它包罗了世界上所有的字符字形。所以,各个地域的语言都可以成立与Unicode的映射干系,而Java正是操作了这一点以到达异种语言之间的转换;

UTF-8是另一种差异于UCS-2/UCS-4的编码方案,个中UTF代表UCS Transformation Format,它回收变长的方法举办编码,编码长度可以是1~3(听说理论上最长可以到6,不懂)。

由于UCS-2/UCS-4编码定长的原因,编码发生的字符串会包括一些非凡的字符,如\0(即0x0,所有0~256的字符Unicode编码的第一个字节),这在有些环境下(如传输或理会时)会给我们带来一些贫苦,并且对付一般的英文字母挥霍了太多的空间,另外,听说UTF-8尚有Unicode所没有的纠错本领(不懂!),因此,Unicode往往只是被用作一种中间码,用于逻辑暗示。关于Unicode/UTF-8的更多信息,见参考1;

Java中文乱码问题在许多环境下都大概产生:差异应用间,差异平台间等等,但以上问题已有大量优秀的文章接头过,这里不作深入探讨,详见参考2、3、4、5。下面扼要总结一下:

当我们利用默认编码方法生存源文件时,文件内容实际上是凭据我们的系统设定举办编码生存的,这个设定值即file.encoding可以通过下面的措施得到:

public class Encoding {
  public static void main(String[] args) {
    System.out.println(System.getProperty("file.encoding"));
  }
}
javac在不指定encoding参数时,假如区域设定不正确,则大概造成编/解码错误,这个问题在编译一个以后外情况传过来的文件时大概产生;

2、固然在Java内部(即运行期间,Runtime)字符串是以Unicode形式存在的,但在class文件中信息是以UTF-8形式存储的(Unicode仅被用作逻辑暗示中间码) ;

对付Web应用,以Tomcat为例,JSP/Servlet引擎提供的JSP转换东西(jspc)搜索JSP文件顶用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>指定的charset。假如在JSP文件中未指定<Jsp-charset>,则取系统默认的file.encoding(这个值在中文平台上是GBK),可通过节制面板的Regional Options举办修改;jspc用相当于“javac –encoding <Jsp-charset>”的呼吁表明JSP文件中呈现的所有字符,包罗中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF-8名目,存为JAVA文件。

我曾经偶尔将jsp文件存成UTF-8,而在文件内部利用的charset却是GB2312,功效运行时老是无法正常显示中文,厥后转存为默认编码方法才正常。只要文件存储名目与JSP开头的charset配置一致,就都可以正常显示(不外将文件生存成UTF-16的环境下我还没有试验乐成);

在XML文件中,encoding暗示的是文件自己的编码方法,假如这个参数设定与文件自己实际的编码方法纷歧致的话,则大概解码失败,所以应该老是将encoding配置成与文件编码方法一致的值;而JSP/HTML的charset则暗示凭据何种字符集来解码从文件中读取出来的字符串(在领略中文问题时应该把字符串领略成一个二进制或16进制的串,凭据差异的charset大概映射成差异的字符)。

我曾经在网上就encoding的详细寄义跟别人接头过:假如encoding指的是文件自己的编码方法,那么读取该文件的应用措施在不知道encoding配置的环境下如何正确解读该文件呢?

按照接头及小我私家领略,处理惩罚措施(如jspc)老是按ISO8859-1来读取输入文件,然后查抄文件开始的几个字节(即Byte Order Mark,BOM,详细如何判定,可以参考Tomcat源码$SOURCE_DIR\jasper\jasper2\src\share\org\apache\jasper\xmlparser\XMLEncodingDetector.java的getEncodingName要领,在JSP Specification的Page Character Encoding一节也有具体阐述)以探测文件是以何种名目生存的,当理会到encoding选项时,若encoding配置与文件实际生存名目纷歧致,会实验举办转换,但这种转换大概在文件实际以ISO8859-1/UTF-8等单字节编码而encoding被配置成Unicode、UTF-16等双字节编码时产生错误。


#p#副标题#e#

下面重点接头JNI中在C++措施与Java措施间举办数据通报时需要留意的问题。

在JNI中jstring回收的是UCS-2编码,与Java中String的编码方法一致。可是在C++中,字符串是用char(8位)可能wchar_t(16位,Unicode编码与jchar一致,但并非所有开拓平台上都是Unicode编码,详见参考6),下面的措施证明白这一点(编译情况:VC6):

#p#分页标题#e#

#include <iostream>
using namespace std;
int main()
{
  locale loc( "Chinese-simplified" );
  //locale loc( "chs" );
  //locale loc( "ZHI" );
  //locale loc( ".936" );
  wcout.imbue( loc );
  wcout << L"中文" << endl; //若没有L,会出问题
  wchar_t wch[] = {0x4E2D, 0x6587, 0x0}; //"中文"二字的Unicode编码
  wcout << wch << endl;
  return 0;
}

JNI提供了几个要领来实现jstring与char/wchar_t之间的转换。jsize GetStringLength(jstring str)
const jchar *GetStringChars(jstring str, jboolean *isCopy)
void ReleaseStringChars(jstring str, const jchar *chars)
另外,为了便于以UTF-8方法举办传输、存储,JNI还提供了几个操纵UTF名目标要领:jsize GetStringUTFLength(jstring str)
const char* GetStringUTFChars(jstring str, jboolean *isCopy)
void ReleaseStringUTFChars(jstring str, const char* chars)
GetStringChars返回的是Unicode名目标编码串,而GetStringUTFChars返回的是UTF-8名目标编码串。要建设一个jstring,可以用如下方法:jstring NewJString( JNIEnv * env, LPCTSTR str )
{
  if (!env || !str)
    return 0;
  int slen = strlen(str);
  jchar * buffer = new jchar[slen];
  int len = MultiByteToWideChar(CP_ACP, 0, str, strlen(str), buffer, slen);
  if (len > 0 && len < slen)
    buffer[len] = 0;
  jstring js = env->NewString(buffer, len);
  delete [] buffer;
  return js;
}
而要将一个jstring工具转为一个char字符串数组,可以:int JStringToChar( JNIEnv * env, jstring str, LPTSTR desc, int desc_len )
{
  int len = 0;
  if (desc == NULL || str == NULL)
    return -1;
  // Check buffer size
  if (env->GetStringLength(str) * 2 + 1 > desc_len)
  {
    return -2;
  }
  memset(desc, 0, desc_len);
  const wchar_t * w_buffer = env->GetStringChars(str, 0);
  len = WideCharToMultiByte(CP_ACP, 0, w_buffer, wcslen(w_buffer) + 1, desc, desc_len, NULL, NULL);
  env->ReleaseStringChars(str, w_buffer);
  if (len > 0 && len < desc_len)
    desc[len] = 0;
  return strlen(desc);
}
  虽然,凭据上面的阐明,你也可以直接将GetStringChars的返回功效作为wchar_t串来举办操纵。可能,假如你愿意,你也可以将GetStringUTFChars的功效通过MultiByteToWideChar转换为UCS2编码串,再通过WideCharToMultiByte转换为多字节串。const char* pstr = env->GetStringUTFChars(str, false);
int nLen = MultiByteToWideChar( CP_UTF8, 0, pstr, -1, NULL, NULL );//获得UTF-8编码的字符串长度
LPWSTR lpwsz = new WCHAR[nLen];
MultiByteToWideChar( CP_UTF8, 0, pstr, -1, lpwsz, nLen );//转换的功效是UCS2名目标编码串
int nLen1 = WideCharToMultiByte( CP_ACP, 0, lpwsz, nLen, NULL, NULL, NULL, NULL );
LPSTR lpsz = new CHAR[nLen1];
WideCharToMultiByte( CP_ACP, 0, lpwsz, nLen, lpsz, nLen1, NULL, NULL );//将UCS2名目标编码串转换为多字节
cout << "Out:" << lpsz << endl;
delete [] lpwsz; delete [] lpsz;

#p#副标题#e#

虽然,我相信很少有人想要可能需要这么做。这里需要留意一点,GetStringChars的返回值是jchar,而GetStringUTFChars的返回值是const char*。除了上面的步伐外,当需要常常在jstring和char*之间举办转换时我们尚有一个选择,那就是下面的这个类。这个类原来是一个叫Roger S. Reynolds的老外提供的,想法很是棒,但用起来却不太灵光,因为作者将思量的重心放在UTF名目串上,但在实际操纵中,我们往往利用的却是ACP(ANSI code page)串。下面是原作者的措施:class UTFString {
private:
  UTFString (); // Default ctor - disallowed
public:
  // Create a new instance from the specified jstring
  UTFString(JNIEnv* env, const jstring& str) :
    mEnv (env),
    mJstr (str),
    mUtfChars ((char* )mEnv->GetStringUTFChars (mJstr, 0)),
    mString (mUtfChars) { }
  // Create a new instance from the specified string
  UTFString(JNIEnv* env, const string& str) :
    mEnv (env),
    mString (str),
    mJstr (env->NewStringUTF (str.c_str ())),
    mUtfChars ((char* )mEnv->GetStringUTFChars (mJstr, 0)) { }
  // Create a new instance as a copy of the specified UTFString
  UTFString(const UTFString& rhs) :
    mEnv (rhs.mEnv),
    mJstr (mEnv->NewStringUTF (rhs.mUtfChars)),
    mUtfChars ((char* )mEnv->GetStringUTFChars (mJstr, 0)),
    mString (mUtfChars) { }
  // Delete the instance and release allocated storage
  ~UTFString() { mEnv->ReleaseStringUTFChars (mJstr, mUtfChars); }
  // assign a new value to this instance from the given string
  UTFString & operator =(const string& rhs) {
    mEnv->ReleaseStringUTFChars (mJstr, mUtfChars);
    mJstr = mEnv->NewStringUTF (rhs.c_str ());
    mUtfChars = (char* )mEnv->GetStringUTFChars (mJstr, 0);
    mString = mUtfChars;
    return *this;
  }
  // assign a new value to this instance from the given char*
  UTFString & operator =(const char* ptr) {
    mEnv->ReleaseStringUTFChars (mJstr, mUtfChars);
    mJstr = mEnv->NewStringUTF (ptr);
    mUtfChars = (char* )mEnv->GetStringUTFChars (mJstr, 0);
    mString = mUtfChars;
    return *this;
  }
  // Supply operator methods for converting the UTFString to a string
  // or char*, making it easy to pass UTFString arguments to functions
  // that require string or char* parameters.
  string & GetString() { return mString; }
  operator string() { return mString; }
  operator const char* () { return mString.c_str (); }
  operator jstring() { return mJstr; }
private:
  JNIEnv* mEnv;  // The enviroment pointer for this native method.
  jstring mJstr;  // A copy of the jstring object that this UTFString represents
  char* mUtfChars; // Pointer to the data returned by GetStringUTFChars
  string mString; // string buffer for holding the "value" of this instance
};
我将它改了改:class JNIString {
private:
  JNIString (); // Default ctor - disallowed
public:
  // Create a new instance from the specified jstring
  JNIString(JNIEnv* env, const jstring& str) :
    mEnv (env) {
    const jchar* w_buffer = env->GetStringChars (str, 0);
    mJstr = env->NewString (w_buffer,
                wcslen (w_buffer)); // Deep Copy, in usual case we only need
            // Shallow Copy as we just need this class to
            // provide some convenience for handling jstring
    mChars = new char[wcslen (w_buffer) * 2 + 1];
    WideCharToMultiByte (CP_ACP, 0, w_buffer, wcslen (w_buffer) + 1, mChars, wcslen (w_buffer) * 2 + 1,
               NULL,  NULL);
    env->ReleaseStringChars (str, w_buffer);
    mString = mChars;
  }
  // Create a new instance from the specified string
  JNIString(JNIEnv* env, const string& str) :
    mEnv (env) {
    int slen = str.length ();
    jchar* buffer = new jchar[slen];
    int len = MultiByteToWideChar (CP_ACP, 0, str.c_str (), str.length (), buffer, slen);
    if (len > 0 && len < slen)
      buffer[len] = 0;
    mJstr = env->NewString (buffer, len);
    delete [] buffer;
    mChars = new char[str.length () + 1];
    strcpy (mChars, str.c_str ());
    mString.empty ();
    mString = str.c_str ();
  }
  // Create a new instance as a copy of the specified JNIString
  JNIString(const JNIString& rhs) :
    mEnv (rhs.mEnv) {
    const jchar* wstr = mEnv->GetStringChars (rhs.mJstr, 0);
    mJstr = mEnv->NewString (wstr, wcslen (wstr));
    mEnv->ReleaseStringChars (rhs.mJstr, wstr);
    mChars = new char[strlen (rhs.mChars) + 1];
    strcpy (mChars, rhs.mChars);
    mString = rhs.mString.c_str ();
  }
  // Delete the instance and release allocated storage
  ~JNIString() { delete [] mChars; }
  // assign a new value to this instance from the given string
  JNIString & operator =(const string& rhs) {
    delete [] mChars;
    int slen = rhs.length ();
    jchar* buffer = new jchar[slen];
    int len = MultiByteToWideChar (CP_ACP, 0, rhs.c_str (), rhs.length (), buffer, slen);
    if (len > 0 && len < slen)
      buffer[len] = 0;
    mJstr = mEnv->NewString (buffer, len);
    delete [] buffer;
    mChars = new char[rhs.length () + 1];
    strcpy (mChars, rhs.c_str ());
    mString = rhs.c_str ();
    return *this;
  }
  // Supply operator methods for converting the JNIString to a string
  // or char*, making it easy to pass JNIString arguments to functions
  // that require string or char* parameters.
  string & GetString() { return mString; }
  operator string() { return mString; }
  operator const char* () { return mString.c_str (); }
  operator jstring() { return mJstr; }
private:
  JNIEnv* mEnv;  // The enviroment pointer for this native method.
  jstring mJstr; // A copy of the jstring object that this JNIString represents
  char* mChars;  // Pointer to a ANSI code page char array
  string mString; // string buffer for holding the "value" of this instance (ANSI code page)
};

#p#副标题#e#

#p#分页标题#e#

后者除了将面向UTF编码改成了面向ANSI编码外,还去掉了operator =(const char* ptr)的界说,因为 operator =(const string& rhs)可以在需要的时候替代前者而无需任何特别编码。(因为凭据C++类型,const reference可以自动转换,详见本人另一文章“关于 const reference 的几点说明”)

#p#分页标题#e#

假如你愿意,给JNIString再加个JNIString(JNIEnv* env, const wstring& str)和一个operator =(const wstring& rhs)操纵符重载就较量完美了,:),很简朴,留给用获得的伴侣本身加吧。

下面是一个利用该类的例子(真正跟用于演示的code很少,大部门都是些routine code,:)):

#include <iostream>
#include <string>
#include <assert.h>
#include <jni.h>
using namespace std;
int main() {
  int res;
  JavaVM* jvm;
  JNIEnv* env;
  JavaVMInitArgs vm_args;
  JavaVMOption options[3];
  options[0].optionString = "-Djava.compiler=NONE";
  options[1].optionString = "-Djava.class.path=.;.."; // .. is specially for this project
  options[2].optionString = "-verbose:jni";
  vm_args.version = JNI_VERSION_1_4;
  vm_args.nOptions = 3;
  vm_args.options = options;
  vm_args.ignoreUnrecognized = JNI_TRUE;
  res = JNI_CreateJavaVM (& jvm, (void* * )& env, & vm_args);
  if (res < 0) {
    fprintf (stderr, "Can''t create Java VM\n");
    return 1;
  }
  jclass cls = env->FindClass ("jni/test/Demo");
  assert (0 != cls);
  jmethodID mid = env->GetMethodID (cls, "", "(Ljava/lang/String;)V");
  assert (0 != mid);
  wchar_t* p = L"中国";
  jobject obj = env->NewObject (cls, mid, env->NewString (reinterpret_cast (p), wcslen (p)));
  assert (0 != obj);
  mid = env->GetMethodID (cls, "getMessage", "()Ljava/lang/String;");
  assert (0 != mid);
  jstring str = (jstring)env->CallObjectMethod (obj, mid);
  // use JNIString for easier handling.
  JNIString jnistr (env, str);
  cout << "JNIString:" << jnistr.GetString () << endl;
  jnistr = "中文";
  cout << jnistr.GetString () << endl;
  jvm->DestroyJavaVM ();
  fprintf (stdout, "Java VM destory.\n");
  return 0;
}

    关键字:

在线提交作业