網絡字節序

什麼是Big Endian和Little Endian?

來源:http://blog.ednchina.com/qinyonglyz/194674/message.aspx

1.故事的起源

「endian」這個詞出自《格列佛遊記》。小人國的內戰就源於吃雞蛋時是究竟從大頭(Big-Endian)敲開仍是從小頭(Little-Endian)敲開,由此曾發生過六次叛亂,其中一個皇帝送了命,另外一個丟了王位。

咱們通常將endian翻譯成「字節序」,將big endian和little endian稱做「大尾」和「小尾」。

2.什麼是Big Endian和Little Endian?

在設計計算機系統的時候,有兩種處理內存中數據的方法。一種叫爲little-endian,存放在內存中最低位的數值是來自數據的最右邊部分(也就是數據的最低位部分)。好比一個16進制數字0x12345678,在內存存放的方式以下:
值   
0111,1000   
0101,0110   
0011,0100   
0001,0010

地址   
100   
101   
102   
103


            另外一種稱爲big-endian,正好相反,存放在內存中最低位的數值是來自數據的最左邊邊部分(也就是數據的最高爲部分)。好比一個16進制數字0x12345678,在內存存放的方式以下:
值   
0001,0010   
0011,0100   
0101,0110   
0111,1000

地址   
100   
101   
102   
103


好比某些文件須要在不一樣平臺處理,或者經過Socket通訊。這方面咱們能夠藉助ntohl(), ntohs(), htonl(), and htons()函數進行格式轉換。

3.如何判斷系統是Big Endian仍是Little Endian?

在/usr/include/中(包括子目錄)查找字符串BYTE_ORDER(或_BYTE_ORDER, __BYTE_ORDER),肯定其值。這個值通常在endian.h或machine/endian.h文件中能夠找到,有時在feature.h中,不一樣的操做系統可能有所不一樣。通常來講,Little Endian系統BYTE_ORDER(或_BYTE_ORDER,__BYTE_ORDER)爲1234,Big Endian系統爲4321。大部分用戶的操做系統(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。本質上說,Little Endian仍是Big Endian與操做系統和芯片類型都有關係。

======================================================================

ext3 文件系統在硬盤分區上的數據是按照 Intel 的 Little-endian 格式存放的,若是是在 PC 之外的平臺上開發 ext3 相關的程序,要特別注意這一點。

談到字節序的問題,必然牽涉到兩大 CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列採用big endian方式存儲數據,而x86系列則採用little endian方式存儲數據。那麼究竟什麼是big endian,什麼又是little endian呢?

     其實big endian是指低地址存放最高有效字節(MSB),而little endian則是低地址存放最低有效字節(LSB)。

     用文字說明可能比較抽象,下面用圖像加以說明。好比數字0x12345678在兩種不一樣字節序CPU中的存儲順序以下所示:

Big Endian

   低地址                                            高地址
   ----------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     12     |      34    |     56      |     78    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian

   低地址                                            高地址
   ----------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     78     |      56    |     34      |     12    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

     從上面兩圖能夠看出,採用big endian方式存儲數據是符合咱們人類的思惟習慣的。而little endian,!@#$%^&*,見鬼去吧 -_-|||

     爲何要注意字節序的問題呢?你可能這麼問。固然,若是你寫的程序只在單機環境下面運行,而且不和別人的程序打交道,那麼你徹底能夠忽略字節序的存在。可是,若是你的程序要跟別人的程序產生交互呢?在這裏我想說說兩種語言。C/C++語言編寫的程序裏數據存儲順序是跟編譯平臺所在的CPU相關的,而 JAVA編寫的程序則惟一採用big endian方式來存儲數據。試想,若是你用C/C++語言在x86平臺下編寫的程序跟別人的JAVA程序互通時會產生什麼結果?就拿上面的 0x12345678來講,你的程序傳遞給別人的一個數據,將指向0x12345678的指針傳給了JAVA程序,因爲JAVA採起big endian方式存儲數據,很天然的它會將你的數據翻譯爲0x78563412。什麼?居然變成另一個數字了?是的,就是這種後果。所以,在你的C程序傳給JAVA程序以前有必要進行字節序的轉換工做。

     無獨有偶,全部網絡協議也都是採用big endian的方式來傳輸數據的。因此有時咱們也會把big endian方式稱之爲網絡字節序。當兩臺採用不一樣字節序的主機通訊時,在發送數據以前都必須通過字節序的轉換成爲網絡字節序後再進行傳輸。


 
網絡字節序與主機字節序


來源:http://www.cnblogs.com/jacktu/archive/2008/11/24/1339789.html
不一樣的CPU有不一樣的字節序類型 這些字節序是指整數在內存中保存的順序 這個叫作主機序
最多見的有兩種
1. Little endian:將低序字節存儲在起始地址
2. Big endian:將高序字節存儲在起始地址

LE little-endian
最符合人的思惟的字節序
地址低位存儲值的低位
地址高位存儲值的高位
怎麼講是最符合人的思惟的字節序,是由於從人的第一觀感來講
低位值小,就應該放在內存地址小的地方,也即內存地址低位
反之,高位值就應該放在內存地址大的地方,也即內存地址高位

BE big-endian
最直觀的字節序
地址低位存儲值的高位
地址高位存儲值的低位
爲何說直觀,不要考慮對應關係
只須要把內存地址從左到右按照由低到高的順序寫出
把值按照一般的高位到低位的順序寫出
二者對照,一個字節一個字節的填充進去

例子:在內存中雙字0x01020304(DWORD)的存儲方式

內存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04

例子:若是咱們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲
      big-endian  little-endian
0x0000  0x12      0xcd
0x0001  0x23      0xab
0x0002  0xab      0x34
0x0003  0xcd      0x12
x86系列CPU都是little-endian的字節序.

網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操做系統等無關,從而能夠保證數據在不一樣主機之間傳輸時可以被正確解釋。網絡字節順序採用big endian排序方式。

爲了進行轉換 bsd socket提供了轉換的函數 有下面四個
htons 把unsigned short類型從主機序轉換到網絡序
htonl 把unsigned long類型從主機序轉換到網絡序
ntohs 把unsigned short類型從網絡序轉換到主機序
ntohl 把unsigned long類型從網絡序轉換到主機序

在使用little endian的系統中 這些函數會把字節序進行轉換
在使用big endian類型的系統中 這些函數會定義成空宏

一樣 在網絡程序開發時 或是跨平臺開發時 也應該注意保證只用一種字節序 否則兩方的解釋不同就會產生bug.

注:
一、網絡與主機字節轉換函數:htons ntohs htonl ntohl (s 就是short l是long h是host n是network)
二、不一樣的CPU上運行不一樣的操做系統,字節序也是不一樣的,參見下表。
處理器    操做系統    字節排序
Alpha    所有    Little endian
HP-PA    NT    Little endian
HP-PA    UNIX    Big endian
Intelx86    所有    Little endian <-----x86系統是小端字節序系統
Motorola680x()    所有    Big endian
MIPS    NT    Little endian
MIPS    UNIX    Big endian
PowerPC    NT    Little endian
PowerPC    非NT    Big endian  <-----PPC系統是大端字節序系統
RS/6000    UNIX    Big endian
SPARC    UNIX    Big endian
IXP1200 ARM核心    所有    Little endian
=============================================
字節序轉換類:

/**
* 通訊格式轉換
*
* Java和一些windows編程語言如c、c++、delphi所寫的網絡程序進行通信時,須要進行相應的轉換
* 高、低字節之間的轉換
* windows的字節序爲低字節開頭
* linux,unix的字節序爲高字節開頭
* java則不管平臺變化,都是高字節開頭
*/

public class FormatTransfer {
/**
  * 將int轉爲低字節在前,高字節在後的byte數組
  * @param n int
  * @return byte[]
  */
public static byte[] toLH(int n) {
  byte[] b = new byte[4];
  b[0] = (byte) (n & 0xff);
  b[1] = (byte) (n >> 8 & 0xff);
  b[2] = (byte) (n >> 16 & 0xff);
  b[3] = (byte) (n >> 24 & 0xff);
  return b;
}

/**
  * 將int轉爲高字節在前,低字節在後的byte數組
  * @param n int
  * @return byte[]
  */
public static byte[] toHH(int n) {
  byte[] b = new byte[4];
  b[3] = (byte) (n & 0xff);
  b[2] = (byte) (n >> 8 & 0xff);
  b[1] = (byte) (n >> 16 & 0xff);
  b[0] = (byte) (n >> 24 & 0xff);
  return b;
}

/**
  * 將short轉爲低字節在前,高字節在後的byte數組
  * @param n short
  * @return byte[]
  */
public static byte[] toLH(short n) {
  byte[] b = new byte[2];
  b[0] = (byte) (n & 0xff);
  b[1] = (byte) (n >> 8 & 0xff);
  return b;
}

/**
  * 將short轉爲高字節在前,低字節在後的byte數組
  * @param n short
  * @return byte[]
  */
public static byte[] toHH(short n) {
  byte[] b = new byte[2];
  b[1] = (byte) (n & 0xff);
  b[0] = (byte) (n >> 8 & 0xff);
  return b;
}

 

/**
  * 將將int轉爲高字節在前,低字節在後的byte數組

public static byte[] toHH(int number) {
  int temp = number;
  byte[] b = new byte[4];
  for (int i = b.length - 1; i > -1; i--) {
    b = new Integer(temp & 0xff).byteValue();
    temp = temp >> 8;
  }
  return b;
}

public static byte[] IntToByteArray(int i) {
    byte[] abyte0 = new byte[4];
    abyte0[3] = (byte) (0xff & i);
    abyte0[2] = (byte) ((0xff00 & i) >> 8);
    abyte0[1] = (byte) ((0xff0000 & i) >> 16);
    abyte0[0] = (byte) ((0xff000000 & i) >> 24);
    return abyte0;
}


*/

/**
  * 將float轉爲低字節在前,高字節在後的byte數組
  */
public static byte[] toLH(float f) {
  return toLH(Float.floatToRawIntBits(f));
}

/**
  * 將float轉爲高字節在前,低字節在後的byte數組
  */
public static byte[] toHH(float f) {
  return toHH(Float.floatToRawIntBits(f));
}

/**
  * 將String轉爲byte數組
  */
public static byte[] stringToBytes(String s, int length) {
  while (s.getBytes().length < length) {
    s += " ";
  }
  return s.getBytes();
}


/**
  * 將字節數組轉換爲String
  * @param b byte[]
  * @return String
  */
public static String bytesToString(byte[] b) {
  StringBuffer result = new StringBuffer("");
  int length = b.length;
  for (int i=0; i<length; i++) {
    result.append((char)(b & 0xff));
  }
  return result.toString();
}

/**
  * 將字符串轉換爲byte數組
  * @param s String
  * @return byte[]
  */
public static byte[] stringToBytes(String s) {
  return s.getBytes();
}

/**
  * 將高字節數組轉換爲int
  * @param b byte[]
  * @return int
  */
public static int hBytesToInt(byte[] b) {
  int s = 0;
  for (int i = 0; i < 3; i++) {
    if (b >= 0) {
    s = s + b;
    } else {
    s = s + 256 + b;
    }
    s = s * 256;
  }
  if (b[3] >= 0) {
    s = s + b[3];
  } else {
    s = s + 256 + b[3];
  }
  return s;
}

/**
  * 將低字節數組轉換爲int
  * @param b byte[]
  * @return int
  */
public static int lBytesToInt(byte[] b) {
  int s = 0;
  for (int i = 0; i < 3; i++) {
    if (b[3-i] >= 0) {
    s = s + b[3-i];
    } else {
    s = s + 256 + b[3-i];
    }
    s = s * 256;
  }
  if (b[0] >= 0) {
    s = s + b[0];
  } else {
    s = s + 256 + b[0];
  }
  return s;
}


/**
  * 高字節數組到short的轉換
  * @param b byte[]
  * @return short
  */
public static short hBytesToShort(byte[] b) {
  int s = 0;
  if (b[0] >= 0) {
    s = s + b[0];
    } else {
    s = s + 256 + b[0];
    }
    s = s * 256;
  if (b[1] >= 0) {
    s = s + b[1];
  } else {
    s = s + 256 + b[1];
  }
  short result = (short)s;
  return result;
}

/**
  * 低字節數組到short的轉換
  * @param b byte[]
  * @return short
  */
public static short lBytesToShort(byte[] b) {
  int s = 0;
  if (b[1] >= 0) {
    s = s + b[1];
    } else {
    s = s + 256 + b[1];
    }
    s = s * 256;
  if (b[0] >= 0) {
    s = s + b[0];
  } else {
    s = s + 256 + b[0];
  }
  short result = (short)s;
  return result;
}

/**
  * 高字節數組轉換爲float
  * @param b byte[]
  * @return float
  */
public static float hBytesToFloat(byte[] b) {
  int i = 0;
  Float F = new Float(0.0);
  i = ((((b[0]&0xff)<<8 | (b[1]&0xff))<<8) | (b[2]&0xff))<<8 | (b[3]&0xff);
  return F.intBitsToFloat(i);
}

/**
  * 低字節數組轉換爲float
  * @param b byte[]
  * @return float
  */
public static float lBytesToFloat(byte[] b) {
  int i = 0;
  Float F = new Float(0.0);
  i = ((((b[3]&0xff)<<8 | (b[2]&0xff))<<8) | (b[1]&0xff))<<8 | (b[0]&0xff);
  return F.intBitsToFloat(i);
}

/**
  * 將byte數組中的元素倒序排列
  */
public static byte[] bytesReverseOrder(byte[] b) {
  int length = b.length;
  byte[] result = new byte[length];
  for(int i=0; i<length; i++) {
    result[length-i-1] = b;
  }
  return result;
}

/**
  * 打印byte數組
  */
public static void printBytes(byte[] bb) {
  int length = bb.length;
  for (int i=0; i<length; i++) {
    System.out.print(bb + " ");
  }
  System.out.println("");
}

public static void logBytes(byte[] bb) {
  int length = bb.length;
  String ut = "";
  for (int i=0; i<length; i++) {
    ut = out + bb + " ";
  }

}

/**
  * 將int類型的值轉換爲字節序顛倒過來對應的int值
  * @param i int
  * @return int
  */
public static int reverseInt(int i) {
  int result = FormatTransfer.hBytesToInt(FormatTransfer.toLH(i));
  return result;
}

/**
  * 將short類型的值轉換爲字節序顛倒過來對應的short值
  * @param s short
  * @return short
  */
public static short reverseShort(short s) {
  short result = FormatTransfer.hBytesToShort(FormatTransfer.toLH(s));
  return result;
}

/**
  * 將float類型的值轉換爲字節序顛倒過來對應的float值
  * @param f float
  * @return float
  */
public static float reverseFloat(float f) {
  float result = FormatTransfer.hBytesToFloat(FormatTransfer.toLH(f));
  return result;
}

}html

 

程序間的通訊,說到底即是發送和接收數據流。咱們通常把字節(byte)看做是數據的最小單位。固然,其實一個字節中還包含8位(即bit位)。32位的處理器中「字長」爲32個bit,也就是4個byte。在這樣的CPU中,老是以4字節對齊的方式來讀取或寫入內存,那麼一樣這4個字節的數據是以什麼順序保存在內存中的呢? 這就是字節序的問題。

 

    1、字節序

    顧名思義字節的順序,再多說兩句就是大於一個字節類型的數據在內存中的存放順序(一個字節的數據固然就無需談順序的問題了)。

    下面羅列常見的數據類型及其長度對照表:

            Wtypes.h 中的非託管類型  非託管C 語言類型     .net託管類名           長度
            HANDLE                        void*                   System.IntPtr        32 bit位
            BYTE                            unsigned char       System.Byte          8   bit位(每一個字節Byte=2的4次方 * 2的4次方, 即0x6a 表示。)
            SHORT                         short                    System.Int16        16 bit位(也就是4Byte,即日常說的4字節。)
            WORD                          unsigned short      System.UInt16       16 位
            INT                               int                       System.Int32        32 位
            UINT                             unsigned int         System.UInt32       32 位
            LONG                            long                    System.Int32          32 位
            BOOL                            long                    System.Int32          32 位
            DWORD                        unsigned long       System.UInt32        32 位
            ULONG                          unsigned long      System.UInt32        32 位
            CHAR                            char                    System.Byte           8 位。 
            FLOAT                           Float                   System.Single         32 位
            DOUBLE                        Double                 System.Double        64 bit位(也就是8Byte,即日常說的8字節)

 

注意:

    是基於linux的程序的話,c/c++數據類型爲long、long double和pointer時,其在32位和64位長度是不一樣的。如:long 在 x64和amd64下分別對應的4Byte和8Byte。
       若是是windows的程序的話,c++數據類型只有pointer在32位和64位長度是不一樣的。

 

    2、字節序分類

    有三種:Big-Endian、Little-Endian以及Middle-Endian。常見的主要是Big-Endian和Little-Endian,中文叫高字節序和低字節序(或者是大字節序和小字節序)。引用標準的Big-Endian和Little-Endian的定義以下:
    a) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
    b) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。

    同時引進主機字節序和網絡字節序概念。網絡字節序確定是Big-Endian高字節序,而主機字節序基本上是Little-Endian低字節序,可是主機字節序跟處理器即CPU類型有關,CISC架構的CPU如:X86(包括大部分的intel、AMD等PC處理器)是低字節序,而在嵌入式普遍應用的RISC架構的CPU如:ARM, PowerPC, Alpha, SPARC V9, MIPS等是高字節序的。

 

    3、字節序說明

    以c中定義一個16進制的變量爲例:unsigned int value = 0x6a7b8c9d,根據上述類型對照表得知,c的unsigned int的value變量爲32bit即4個byte。這裏的value也等價於:

        unsigned char buf[4]= { 0x6a,0x7b,0x8c,0x9d}://語法有問題,就是至關於這麼個初始化

    如今分別按轉高字節序和低字節序解釋下,加入這個buf變量的內存初始地址是a:
   a) Little-Endian: 低地址存放低位 :
 (高地址)--------------------------
   a+3-----  buf[3] (0x6a) -- 高位(至關於10進制的千位)
   a+2-----  buf[2] (0x7b)
   a+1-----  buf[1] (0x8c)
   a--------  buf[0] (0x9d) -- 低位(至關於10進制的個位)
(低地址)--------------------------

 

    b) Big-Endian: 低地址存放高位:
 (高地址)--------------------------
   a+3-----  buf[3] (0x9d) -- 高位(至關於10進制的千位)
   a+2-----  buf[2] (0x8c)
   a+1-----  buf[1] (0x7b)
   a--------  buf[0] (0x6a) -- 低位(至關於10進制的個位)
(低地址)--------------------------  

     通常的處理器高地址對應的是棧底,低地址對應的是棧頂。

 

    4、高/低字節序轉換

     若是須要在不一樣的操做系統,或者是基於不一樣的語言,如c/c++和c#之間不一樣主機字節序之的網絡通訊,就須要考慮如何轉換字節序。
     若是不轉換可能遇到問題,這裏舉個C的客戶端和服務器端通訊的例子

     客戶端(低字節序的主機,如intel 奔騰系列處理器)定義:  short x=1   爲2個byte,在內存中爲[1][0](低地址在前面),發送給服務器端。這服務器端(高字節序的主機,如嵌入式的ARM處理器)接收到的爲:[1][0](低地址在前面),此時按照高字節序的"低地址存放高位"原則解析的x=256。於實際狀況不符。

     因此針對這樣的狀況,編寫跨平臺或者是跨語言的程序是須要考慮字節序的轉換。通常數據發送出去以前須要將主機字節序Little-Endian轉換爲網絡字節序Big-Endian,接收以前須要將Big-Endian轉爲Little-Endian,下面介紹小.net和C/C++常見方法。

    4.一、 .net中:
主機字節序到網絡字節序:short/int/long IPAddress.HostToNetworkOrder(short/int/long)
網絡字節序到主機字節序:short/int/long IPAddress.NetworkToHostOrder(short/int/long)

    4.2 、C/C++根據類型的不一樣有以下方法:

        ntohs =net to host short int 16位
        htons=host to net short int 16位
        ntohl =net to host long int 32位
        htonl=host to net long int 32位

       簡單的說明其中一個方法吧。

   將一個無符號短整形數從網絡字節順序轉換爲主機字節順序。
   #include <winsock.h>
   u_short PASCAL FAR ntohs( u_short netshort);
      netshort:一個以網絡字節順序表達的16位數。
   註釋:
       本函數將一個16位數由網絡字節順序轉換爲主機字節順序。
   返回值:
       ntohs()返回一個以主機字節順序表達的數。

 

   

    5、實際轉換過程須要注意點及問題集。

    5.1:C++中的unsigned char類型等同於C#的什麼類型?
    答:C#中的char是16bits的Unicode字符,而通常C++中的字符則是8位的,因此C++中的「unsigned   char」在C#中要麼轉換成char,要麼使用Byte類型來代替,前者適用於存放字符型的unsigned   char,後者適用於整數型的unsigned   char。具體的程序,具體的方法。  好比:c++中申明變量,unsigned  char  para=0x4a(表示十六進制=2的4次方 ×2的4次方,即8位。 uchar的的範圍爲0-0xff,即0-255);unsigned  char  para[4] = 0x6789abcd,表示32位。

   5.2:爲何在網絡編程中,即須要考慮字節序的問題時。對於double、float以及字符串等數據類型不須要考慮主機序列和網絡序列之間的轉換?
   答:至於float和double,與CPU無關。通常編譯器是按照IEEE標準解釋的,即把float/double看做4/8個字符的數組進行解釋。所以,只要編譯器是支持IEEE浮點標準的,就不須要考慮字節順序。

   5.3: BinaryWriter和BinaryReader
    BinaryReader和BinaryWriter使用小字節序(即低字節序)讀寫數據。
    如例子:
    var stream = new MemoryStream(new byte[] { 4, 1, 0, 0 }); //至關於申請了byte[4],而每一個byte至關於256進制
    var reader = new BinaryReader(stream);
    int i = reader.ReadInt32(); // i == 260
    //由於BinaryReader是按照低字節序讀取的,全部i=4+256×1=260;

   5.四、BitConverter和ASCIIEncoding.ASCII.GetBytes
   BitConverter主要是用於.net中byte[]和其餘類型的轉換,不涉及字符串數據類型。網絡通訊會常常用到。
   ASCIIEncoding.ASCII一般用於字符串和byte[]之間的轉換。

 

   5.x: 關於「一樣4個字節的數據,咱們能夠把它看做是1個32位整數、2個Unicode、或者字符4個ASCII字符。」引伸:
   unicode和utf-8之間最大的區別就是在存儲上。unicode是寬字符存儲(字符都是2個字節或4個字節來存儲),而utf-8是多字節存

儲,字符的個數是不肯定的(好比英文字符是1個字節表示,漢字能夠是2個到6個來表示),其字符的首字節的前幾位代表了它的字節

個數。好比某個3字節漢字的uft-8編碼(二進制)以下:  
  1110xxxx   10xxxxxx   10xxxxxx  
  首字節中1的個數爲3代表該漢字用3個字節來表示。
 
  utf-16固定使用兩個字節表示一個字符,日常所說的unicode編碼就是utf-16的。

  ascii字符在utf-8裏面仍是同樣的,一個字節表示,超出ascii字符範圍的,就用多字節表示,字節的數目由第一字節肯定,最多6

字節。以下:  
  utf-8:  
  1字節:0XXXXXXX(ascii)  
  2字節:110XXXXX   10XXXXXX  
  3字節:1110XXXX   10XXXXXX   10XXXXXX  
  4字節:11110XXX   10XXXXXX   10XXXXXX   10XXXXXX  
  5字節:。。。  
  utf-16:全部:XXXXXXXX   XXXXXXXX  
  ascii:XXXXXXXX   00000000

 

   ASCII碼是用十六進制表示,也就是說它是兩個字節byte。如:ASCII碼爲31(即0x31)對應的字符"1";ASCII碼爲32(即0x31)對

應的字符"2".java

相關文章
相關標籤/搜索