java java.lang.Long详解之一:toString()

文章看过后感觉受益匪浅,所以留下了以备温故:http://www.congmo.net/blog/2012/03/05/Long-toString/

陆陆续续花了近两周时间看完了Long.java,可以说收获颇丰。也花了几天时间构思应该如何去写出来,苦于一直没有好的思路,又不能在这里干耗着浪费时间。所以就准备写出来了。很随意的写,想到哪里写到哪里。准备贴很多源码,附加我个人的理解。

toString(long i, int radix)

首先让我们目睹下Long中强大的toString方法。

public static String toString(long i, int radix) {
  if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
      radix = 10;
  if (radix == 10)
      return toString(i);
  char[] buf = new char[65];
  int charPos = 64;
  boolean negative = (i < 0);
  if (!negative) {
    i = -i;
  }
  while (i <= -radix) {
    buf[charPos--] = Integer.digits[(int)(-(i % radix))];
    i = i / radix;
  }
  buf[charPos] = Integer.digits[(int)(-i)];
  if (negative) {
    buf[--charPos] = ‘-‘;
  }
  return new String(buf, charPos, (65 - charPos));
}

第二个参数radix是进制数,范围是:2-36.大家都知道1进制没什么意义,所以进制数从2开始,我一直对36存在疑问,虽然它是0-9和a-z一共 36个字符组成,所以最大进制数定义为36.但是还有大写字母啊,还有26个呢,这样就可以定义最大62进制。也许有什么渊源在这里我不知道,我在 OSChina上也发过问,但是都没有让我很满意的答案。

radix如果不在2-36范围内,则默认10进制。而如果是10进制,Long有专门将10进制的Long转化为String的toString方法,稍后再说。

非10进制的toString就在这里负责处理。

char[] buf = new char[65];

刚开始我很不理解为什么要声明长度为65的char数组呢,long64位就够了啊。仔细琢磨了一下发现,多声明一个是为负数做准备,如果是正数那么确实是只用了64位(这里不算严谨,不过先把65这个问题说清楚)。请看下面一段代码:

if (negative) {
  buf[--charPos] = ‘-‘;
 }
return new String(buf, charPos, (65 - charPos));

清楚了吧,这个buf0如果有值,那么只可能是”-“,不会是其他任何值。最后一行也很好理解,用了几位,那么就传给String构造函数几位。所以上面说的”正数只用64位”是不严谨的。

我喜欢这里negative的用法,算不上很巧妙,但是避开了正数和负数的差异。

if (!negative) {
  i = -i;
}

一开始我同样不理解为啥要把一个正数转换为负数再处理呢?看下面这个代码片段:

while (i <= -radix) {
  buf[charPos--] = Integer.digits[(int)(-(i % radix))];
  i = i / radix;
}
buf[charPos] = Integer.digits[(int)(-i)];

请看while内部与外部,设想如果i是正是负未知,那么这两处就没法统一使用-(i % radix)和-i了。所以将i提前转换为负值了。 Integer.digits是个挺巧妙的东西,可以随意应付2-36进制的转换。它是这样定义的: Liquid error: Flag value is invalid: -O ”” 比如说,radix = 2,那么i % radix只能为0或者1,对应digits0或者digits1,依此类推。

我想这个toString已经介绍的够详细了。另外,源码中将i转换为负数,那么转换为正数也肯定成立吧。于是我做了一点点改动:

final static char[] digits = {
  ‘0‘ , ‘1‘ , ‘2‘ , ‘3‘ , ‘4‘ , ‘5‘ ,
  ‘6‘ , ‘7‘ , ‘8‘ , ‘9‘ , ‘a‘ , ‘b‘ ,
  ‘c‘ , ‘d‘ , ‘e‘ , ‘f‘ , ‘g‘ , ‘h‘ ,
  ‘i‘ , ‘j‘ , ‘k‘ , ‘l‘ , ‘m‘ , ‘n‘ ,
  ‘o‘ , ‘p‘ , ‘q‘ , ‘r‘ , ‘s‘ , ‘t‘ ,
  ‘u‘ , ‘v‘ , ‘w‘ , ‘x‘ , ‘y‘ , ‘z‘
};
public static String toString(long i, int radix) {
  if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
    radix = 10;
  if (radix == 10)
    return "";//不是重点,直接跳过
  char[] buf = new char[65];
  int charPos = 64;
  boolean negative = (i < 0);
  if (negative) {
    i = -i;
  }
  while (i >= radix) {
    buf[charPos--] = digits[(int)((i % radix))];
    i = i / radix;
  }
  buf[charPos] = digits[(int)(i)];
  if (negative) {
    buf[--charPos] = ‘-‘;
  }
  return new String(buf, charPos, (65 - charPos));
}

依旧奏效哦!

System.out.println(Long.toString(-8L, 2));
System.out.println(toString(-8L,2));
输出结果:
-1000
-1000

toString(long i)

这个toString方法用于将参数i转化为十进制形式的字符串,toString(long i)本身是很简单的,核心是getChars(long i, int index, char buf)。那么就一起目睹下它们都是如何实现的。

public static String toString(long i) {
  if (i == Long.MIN_VALUE)
    eturn "-9223372036854775808";
  int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
  char[] buf = new char[size];
  getChars(i, size, buf);
  return new String(0, size, buf);
}
static void getChars(long i, int index, char[] buf) {
  long q;
  int r;
  int charPos = index;
  char sign = 0;
  if (i < 0) {
    sign = ‘-‘;
    i = -i;
  }
  // Get 2 digits/iteration using longs until quotient fits into an int
  //8-4字节
  while (i > Integer.MAX_VALUE) {
    q = i / 100;
    // really: r = i - (q * 100);
    r = (int)(i - ((q << 6) + (q << 5) + (q << 2)));
    i = q;
    buf[--charPos] = Integer.DigitOnes[r];
    buf[--charPos] = Integer.DigitTens[r];
  }
  // Get 2 digits/iteration using ints
  //4-2字节
  int q2;
  int i2 = (int)i;
  while (i2 >= 65536) {
    q2 = i2 / 100;
    // really: r = i2 - (q * 100);
    r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2));
    i2 = q2;
    buf[--charPos] = Integer.DigitOnes[r];
    buf[--charPos] = Integer.DigitTens[r];
  }
  // Fall thru to fast mode for smaller numbers
  // assert(i2 <= 65536, i2);
  //2-0字节
  for (;;) {
    q2 = (i2 * 52429) >>> (16+3);
    r = i2 - ((q2 << 3) + (q2 << 1));  // r = i2-(q2*10) ...
    buf[--charPos] = Integer.digits[r];
    i2 = q2;
    if (i2 == 0) break;
  }
  if (sign != 0) {
    buf[--charPos] = sign;
  }
}

这个getChars可是各种巧妙,首先来看看下面这张图:

从图中可以看出,一个long类型的数字被分成了3段:8-4字节,4-2字节,2-0字节。三段分别处理。下面就一段一段剖析其中的巧妙之处。

8-4字节处理

这段的主要目的是经过它的处理之后,能将一个long类型的数字转换成int来处理。这段while中几乎每行都是经典,首先这行:r = (int)(i - ((q << 6) + (q << 5) + (q << 2)));很巧妙的使用高效的位移运算完成了r = i - (q * 100)。其实这样做事为了得到i的最后两位,比如2147483649这样一个long,经过这步之后r = 49.

随后更巧妙的一件事就又发生了。那就是Integer中的DigitOnes和DigitTens巧妙的设计。那就看看这两个数组是如何巧妙的。

final static char [] DigitTens = {
  ‘0‘, ‘0‘, ‘0‘, ‘0‘, ‘0‘, ‘0‘, ‘0‘, ‘0‘, ‘0‘, ‘0‘,
  ‘1‘, ‘1‘, ‘1‘, ‘1‘, ‘1‘, ‘1‘, ‘1‘, ‘1‘, ‘1‘, ‘1‘,
  ‘2‘, ‘2‘, ‘2‘, ‘2‘, ‘2‘, ‘2‘, ‘2‘, ‘2‘, ‘2‘, ‘2‘,
  ‘3‘, ‘3‘, ‘3‘, ‘3‘, ‘3‘, ‘3‘, ‘3‘, ‘3‘, ‘3‘, ‘3‘,
  ‘4‘, ‘4‘, ‘4‘, ‘4‘, ‘4‘, ‘4‘, ‘4‘, ‘4‘, ‘4‘, ‘4‘,
  ‘5‘, ‘5‘, ‘5‘, ‘5‘, ‘5‘, ‘5‘, ‘5‘, ‘5‘, ‘5‘, ‘5‘,
  ‘6‘, ‘6‘, ‘6‘, ‘6‘, ‘6‘, ‘6‘, ‘6‘, ‘6‘, ‘6‘, ‘6‘,
  ‘7‘, ‘7‘, ‘7‘, ‘7‘, ‘7‘, ‘7‘, ‘7‘, ‘7‘, ‘7‘, ‘7‘,
  ‘8‘, ‘8‘, ‘8‘, ‘8‘, ‘8‘, ‘8‘, ‘8‘, ‘8‘, ‘8‘, ‘8‘,
  ‘9‘, ‘9‘, ‘9‘, ‘9‘, ‘9‘, ‘9‘, ‘9‘, ‘9‘, ‘9‘, ‘9‘,
  } ;
final static char [] DigitOnes = {
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
  } ;

通过这两个精巧的数组,巧妙的将两位数字转化为十位和个位,比如49用这两个数组表示就是DigitTens49=4和DigitOnes49=9。我真心喜欢这个设计。这样也就非常简单的就将一个两位数一位一位放入char数组中了。 再次强调q * 100的实现方式,100 = 64 + 32 + 4,通过位移q再相加,完美实现q * 100。 直到这个long可以被int表示时,转入下一段。

4-2字节处理

这段代码的处理方式几乎同上一段是一模一样的,区别在于这里处理的是int而不是long。

2-0字节处理

我一直都特别奇怪为什么要在2个字节这个点分割呢!我们稍后在小结里面描述,除了这点,还有一处亮点:q2 = (i2 * 52429) >>> (16+3); 这个可是让我困惑了好久好久的。后来无意中在javaeye上搜到一篇帖子上揭露了这个美丽的亮点,52429/524288 = 0.10000038146972656, 524288 = 1 << 19,换句话说q2 = (i2 * 52429) >>> (16+3);就是q2 = i2/10为了避免效率低下的除法,换用了这种方式实现除法,真是绝啊!

小结

总结一下getChars这个绝妙的方法,之所以分成3段,是因为JVM的实现中,int的效率最高,long的效率很低,所以第一步就将long转换成 int,再进行处理。然后呢,为了避免除法,而且乘以52429之后可以被int表示,不会溢出,所以就出现了2字节这个分割点。总之 呢,toString(long i)方法绝对是个绝妙的方法啊。里面有许多值得借鉴的地方。

toUnsignedString(long i, int shift)

接下来让我们认识下toUnsignedString(long i, int shift),这个方法同样巧妙,一个方法就把long转二进制,八进制,十六进制全部搞定。仅仅通过shift一个参数,同样是通过位移来实现的。比如 八进制,那么shift就是3,然后通过1 >> 3实现。唯一一个限制就是只能表示进制数是2的n次幂。

private static String toUnsignedString(long i, int shift) {
  char[] buf = new char[64];
  int charPos = 64;
  int radix = 1 << shift;
  long mask = radix - 1;
  do {
    buf[--charPos] = Integer.digits[(int)(i & mask)];
    i >>>= shift;
  } while (i != 0);
  return new String(buf, charPos, (64 - charPos));
}

首先通过int radix = 1 << shift;实现进制数的转换,

随后就是一个精心的设计,long mask = radix -1; 为什么要有这样一个值呢?其实是这样的,radix是2的n次幂,减1之后就是全1了,比如8-1的二进制就是111,其他同理。然后i & mask就取到进制数对应二进制的位数。比如十六进制的mask = 15,对应的二进制为1111,i & mask就是取i对应二进制的后四位。再从Integer.digits中取得对应进制数的值。

最后,再通过i >>>= shift; 将已经取得的位数移除掉,直至i=0为止。

总结

这样Long中的toString方法簇就解析完毕。总之一句话,Long就像一座宝库,每走一步都是金子。期待下一桶金子吧。

时间: 2024-11-05 00:42:06

java java.lang.Long详解之一:toString()的相关文章

Java虚拟机工作原理详解

原文地址:http://blog.csdn.net/bingduanlbd/article/details/8363734 一.类加载器 首先来看一下java程序的执行过程. 从这个框图很容易大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘当中.然后你在命令行中输入 [java] view plaincopy javac YourClassName.java 此时,你的java代码就被编译成字节码(.class).如果你是在Eclipse IDE或者其他开发工具中,你保存代码

Java Service Wrapper配置详解

1 #encoding=UTF-8 2 # Configuration files must begin with a line specifying the encoding 3 # of the the file. 4 5 #******************************************************************** 6 # Wrapper License Properties (Ignored by Community Edition) 7 #*

java classLoader体系结构使用详解

原创整理不易,转载请注明出处:java classLoader体系结构使用详解 代码下载地址:http://www.zuidaima.com/share/1774052029516800.htm jvm classLoader architecture: Bootstrap ClassLoader/启动类加载器 主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作. Extension ClassLoader/扩展类加载器  主要负

夯实JAVA基本之一 —— 泛型详解(1)

前言:无论何时,相信自己. 相关文章: 1.<夯实JAVA基本之一 -- 泛型详解(1)>2.<夯实JAVA基本之一 -- 泛型详解(2)> 一.引入 1.泛型是什么 首先告诉大家ArrayList就是泛型.那ArrayList能完成哪些想不到的功能呢?先看看下面这段代码: ArrayList<String> strList = new ArrayList<String>(); ArrayList<Integer> intList = new A

Java中的HashTable详解

Hashtables提供了一个很有用的方法可以使应用程序的性能达到最佳. Hashtables(哈 希表)在计算机领域中已不 是一个新概念了.它们是用来加快计算机的处理速度的,用当今的标准来处理,速度非常慢,而它们可以让你在查询许多数据条目时,很快地找到一个特殊的条目. 尽管现代的机器速度已快了几千倍,但是为了得到应用程序的最佳性能,hashtables仍然是个很有用的方法. 设想一下,你有一个 包含约一千条记录的数据文件??比如一个小企业的客户记录还有一个程序,它把记录读到内存中进行处理.每个

Java 8 Stream API详解

Java 8 Stream API详解 一.Stream API介绍 Java 8引入了全新的Stream API,此Stream与Java I/O包里的InputStream和OutputStream是完全不同的概念,它不同于StAX对XML解析的Stream,也不同于Amazon Kinesis对大数据实时处理的Stream.Stream API更像具有Iterable的集合类,但行为和集合类又有所不同,它是对集合对象功能的增强,专注于对集合对象进行各种非常便捷.高效的聚合操作或大批量数据操

Java axis2.jar包详解及缺少jar包错误分析

Java  axis2.jar包详解及缺少jar包错误分析 一.最小开发jar集 axis2 开发最小jar包集: activation-1.1.jar axiom-api-1.2.13.jar axiom-dom-1.2.13.jar axiom-impl-1.2.13.jar axis2-adb-1.6.2.jar axis2-adb-codegen-1.6.2.jar axis2-codegen-1.6.2.jar axis2-java2wsdl-1.6.2.jar axis2-kerne

好程序员Java教程Java动态代理机制详解

好程序员Java教程Java动态代理机制详解:在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的.首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的: InvocationHandler: 1InvocationHandler is the interface implemented by the invocation handle

Java的位运算符详解实例

Java的位运算符详解实例——与(&).非(~).或(|).异或(^).右移(>>).左移(<<).无符号右移(>>>) 位运算符主要针对二进制, 它包括了:“与”.“非”.“或”.“异或”."右移"."左移"."无符号右移". 从表面上看似乎有点像逻辑运算符, 但逻辑运算符是针对两个关系运算符来进行逻辑运算, 而位运算符主要针对两个二进制数的位进行逻辑运算. 下面详细介绍每个位运算符. 1.与运

java中static作用详解

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念. 被static修饰的成员变量和成员方法独立于该类的任何对象.也就是说,它不依赖类特定的实例,被类的所有实例共享. 只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们.因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象. 用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类