Keaper's Blog

  • 首页

  • 分类

  • 标签

  • 归档

  • 关于

如何编译"零汇编(Zero-Assembler)"的OpenJDK

发表于 2020-09-15

在使用JetBrains CLion调试OpenJDK的过程中,有时候会发现Call Stack中有一部分是汇编代码,导致无法完全探究其内部实现。本文主要针对此问题给出了如何在不引入汇编代码(零汇编,Zero-Assembler)的情况下完成OpenJDK项目的编译和调试。

阅读全文 »

Java String 面面观

发表于 2020-09-08 | 更新于 2020-09-15

本文主要介绍Java中与字符串相关的一些内容,主要包括String类的实现及其不变性、String相关类(StringBuilder、StringBuffer)的实现 以及 字符串缓存机制的用法与实现。

阅读全文 »

Java中的遍历(遍历集合或数组的几种方式)

发表于 2020-08-19 | 更新于 2020-09-15

本文主要总结了Java中遍历集合或数组的几种方式,并介绍了各种遍历方式的实现原理,以及一些最佳实践。最后介绍了Java集合类迭代器的快速失败(fail-fast)机制。

阅读全文 »

MySQL 5.7 编译调试指南(Ubuntu 16.04 + MacOS 10.15)

发表于 2020-07-30 | 更新于 2020-09-15

本文主要介绍在MacOS 10.15和Ubuntu 16.04系统下编译构建MySQL 5.7.30并使用JetBrains CLion(以下简称CLion)进行运行调试的方法。

阅读全文 »

使用CLion调试Redis(Ubuntu 16.04 + MacOS 10.15)

发表于 2020-07-05 | 更新于 2020-09-15

本文主要介绍如何编译Redis项目并在JetBrains CLion(以下简称CLion)中运行/调试。

阅读全文 »

OpenJDK 编译调试指南(Ubuntu 16.04 + MacOS 10.15)

发表于 2020-07-04 | 更新于 2020-09-15

本篇文章主要介绍在MacOS系统和Ubuntu系统上如何编译OpenJDK项目代码,并使用IDE工具JetBrains CLion(下文简称CLion)来运行/调试OpenJDK。

阅读全文 »

记一次诡异的JVM退出问题

发表于 2019-11-13 | 更新于 2020-09-15

背景

  1. 项目基于Spring Boot,会与另外一个项目通过HTTP协议进行RPC通信,所以项目中启动了Netty作为HTTP Server。
  2. 项目使用Maven构建,使用appassembler-maven-plugin插件进行打包,使用打包后的脚本启动。

问题现象

某次更改代码,本地测试通过,没有问题。
部署测试环境,发现服务刚启动就会退出,重启还是退出,但是日志中没有找到任何任何报错异常信息。

问题复现

开始在本地复现:

  1. 使用IDEA启动主类运行程序,没有问题,一切正常。
  2. 会不会是appassembler插件生成的启动脚本的问题?根据测试环境的配置打包,使用启动脚本运行程序,这次问题复现了。
  3. 在另外一台电脑上使用启动脚本运行程序,没有问题,一切正常。

问题并不能百分之百复现,与运行环境有着微妙的关系。

问题原因

我们从头来思考下这个问题,我们能看到的现象是JAVA应用退出了,也就是JVM关闭了,那么导致JVM关闭的原因有那些呢?

  1. 调用Runtim.exit方法或者Runtime.halt方法,并且 SecurityManager允许执行退出操作。
  2. 所有的非守护线程结束,线程结束原因有两种:
    • run()方法正常返回
    • 线程中抛出了未捕获的异常

JVM退出原因翻译至JDK文档,原文如下:

When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:

  • The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.
  • All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method.

项目是一个Web Server应用,代码中没有主动调用exit()方法,所以排除第一种情况,重点考虑的是第二种情况。
不管是项目中LogBack的日志还是JVM的日志,都没有发现异常信息。基本可排除发生了异常。
那就只有可能是程序真的就是没有任何”问题”,所有线程都正常运行结束。

猜想

因为项目并没有用到Spring Boot内嵌的Web容器(Netty Server是在另外一个依赖包中启动的),所以项目启动时指定了web选项为NONE。

1
2
3
public static void main(String[] args) throws InterruptedException {
new SpringApplicationBuilder(XXXApplication.class).web(WebApplicationType.NONE).build().run();
}

因为我们指定了了web选项为NONE,所以SpringBoot不会去启动Tomcat等Web容器,有没有可能是线程启动顺序问题呢,我们需要的Netty Server相关的线程还未启动,main线程以及其他非守护线程都已经结束,所以程序退出了呢?

验证猜想

验证很简单,在主线程执行完毕退出前sleep一段时间:

1
2
3
4
public static void main(String[] args) throws InterruptedException {
new SpringApplicationBuilder(XXXApplication.class).web(WebApplicationType.NONE).build().run();
Thread.sleep(100); // sleep 100ms
}

打包编译运行,发现程序正常,没有退出。

继续验证一下是不是因为所有的非守护线程都已经执行结束导致程序退出。在主线程执行完毕退出前打印出所有线程的信息:

1
2
3
4
public static void main(String[] args) throws InterruptedException {
new SpringApplicationBuilder(XXXApplication.class).web(WebApplicationType.NONE).build().run();
Thread.getAllStackTraces().keySet().forEach(t -> System.out.println(t.getName() + "," + t.isDaemon()));
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
xxl-job, executor JobLogFileCleanThread,true
Reference Handler,true
ZkClient-EventThread-13-tjwqstaging.zk.hadoop.srv:2181,true
xxl-job, executor TriggerCallbackThread,true
main,false
Finalizer,true
Thread-6,true
Signal Dispatcher,true
main-EventThread,true
Timer-0,true
Thread-7,true
main-SendThread(tj-hadoop-staging-zk05.kscn:2181),true

可以看到除了main线程之外的其他线程都是daemon线程,所以在main线程执行完后程序会退出。到这里说明我们起初的猜想是没有问题的。

溯源

那么我们想要的提供HTTP服务的相关线程为什么没有启动呢?我们Sleep 100ms之后再打印一下所有线程信息:

1
2
3
4
5
public static void main(String[] args) throws InterruptedException {
new SpringApplicationBuilder(XXXApplication.class).web(WebApplicationType.NONE).build().run();
Thread.sleep(100);
Thread.getAllStackTraces().keySet().forEach(t -> System.out.println(t.getName() + "," + t.isDaemon()));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xxl-job, executor JobLogFileCleanThread,true
main,false
Signal Dispatcher,true
main-EventThread,true
Timer-0,true
Thread-7,true
nioEventLoopGroup-2-1,false
nioEventLoopGroup-4-1,false
Reference Handler,true
ZkClient-EventThread-13-tjwqstaging.zk.hadoop.srv:2181,true
xxl-job, executor ExecutorRegistryThread,true
xxl-job, executor TriggerCallbackThread,true
Finalizer,true
Thread-6,true
main-SendThread(tj-hadoop-staging-zk03.kscn:2181),true

可以看到多出来的两个非守护线程是nioEventLoopGroup-x-x,这组线程正是Netty中用来处理HTTP请求的工作线程。为什么这组线程没有来得及创建呢?
我们找到创建它的地方,查看他启动Web Server的过程,看到这样一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
public void start(final XxlRpcProviderFactory xxlRpcProviderFactory) throws Exception {
thread = new Thread(new Runnable() {
@Override
public void run() {
// param
final ThreadPoolExecutor serverHandlerPool = ThreadPoolUtil.makeServerThreadPool(NettyHttpServer.class.getSimpleName());
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// start server
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
/*ch.pipeline().addLast(new HttpResponseEncoder());
ch.pipeline().addLast(new HttpRequestDecoder());*/
/*ch.pipeline().addLast(new ChunkedWriteHandler());*/
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new HttpObjectAggregator(5*1024*1024)); // merge request & reponse to FULL
ch.pipeline().addLast(new NettyHttpServerHandler(xxlRpcProviderFactory, serverHandlerPool));
}
})
.childOption(ChannelOption.SO_KEEPALIVE, true);
// ...
} catch (InterruptedException e) {
// ...
} finally {
// ...
}
}
});
thread.setDaemon(true); // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
thread.start();
}

可以看到在这个方法中启动了一个线程来创建Web Server,但是这个线程是daemon的,所以如果NioEventLoopGroup线程还未启动,其他非守护线程(包括main线程)已经执行完的话,那么程序就会退出。
而在我们上面sleep 100ms 的这段时间,给了这组线程启动的机会,所以程序能够继续运行。

至于有时能复现有时复现不了的原因猜测,应该是机器环境或者是JAVA启动参数不同导致线程启动先后顺序有微妙的差异。

总结

  1. JVM退出的原因:
    • 调用Runtim.exit方法或者Runtime.halt方法,并且 SecurityManager允许执行退出操作。
    • 所有的非守护线程结束,线程结束原因有两种:要么是run()方法正常返回,要么是线程中抛出了未捕获的异常

参考文档

  1. https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html

Java 如何处理未捕获异常

发表于 2019-09-06 | 更新于 2020-09-15

问题

如果代码中发生了异常,但我们没有用try/catch捕获,JVM会如何处理?
这是一段肯定会发生异常的代码,但我们没有处理异常,运行这个代码会发生什么?

1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
System.out.println(1 / 0);
System.out.println("test");
}
}

程序结束,并且控制台输出下面的内容:

1
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.xxx.xxx.web.Main.main(Main.java:6)

这是如何背后的逻辑是什么,为什么会输出这些内容呢?

JAVA异常体系

首先来复习一下JAVA的异常体系。

异常分类

异常继承结构大致如下:

JAVA中所有的异常都是继承自Throwable类。可以分为以下两类:

  1. 非检查异常(unchecked exceptions),包括以下两种:
    • 错误(Error),包括Error类及其子类。这种异常是在正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如JVM自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如OutOfMemoryError类。
    • 运行时异常(RuntimeException),包括RuntimeException类及其子类。这种异常通常是可以通过编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
  2. 受检查异常(checked exception),除了上面两种(Error类及其子类,RuntimeException类及其子类),其他异常都属于受检查异常。这种异常通常是外部错误,不是代码逻辑的错误,编译器强制要求对这种异常进行处理,比如网络连接错误会抛出IOException,我们应该提前预料这种情况并对其进行处理(比如重试)。

异常处理

JAVA中处理异常的方式有两种:

  1. 使用try/catch捕获异常并进行处理。
  2. 使用throws关键字,在方法上声明可能会抛出的异常,由外层调用者去处理这个异常。

上面两种异常中,受检查异常必须被捕获,否则会编译失败,而非检查异常在编译期不强制要求被捕获。

未捕获异常

如果一个非检查异常没有被捕获处理,那这就是未捕获异常。因为受检查异常都必须在代码中捕获进行处理,所以未捕获异常实际上都是在说非检查异常。

在探究如何处理未捕获异常之前先来看一个接口,这个接口是处理未捕获异常的关键接口:

Thread.UncaughtExceptionHandler接口

1
2
3
4
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}

这个接口很简单,只有一个方法,用来处理未捕获的异常,参数是线程信息,以及异常信息,

下面从源码层面来看看如何处理未捕获异常。

未捕获异常处理流程

Thread类中有一个dispatchUncaughtException方法,这个方法的作用是分发异常信息到正确的UncaughtExceptionHandler。当线程运行中出现了未捕获的异常,JVM会调用线程的这个方法,来寻找一个UncaughtExceptionHandler处理异常。

1
2
3
4
5
6
7
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}

getUncaughtExceptionHandler的获取逻辑是,如果此线程的uncaughtExceptionHandler属性不为null,则分发异常到线程自己的uncaughtExceptionHandler,否则将异常分发给此线程所在的线程组。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Returns the handler invoked when this thread abruptly terminates
* due to an uncaught exception. If this thread has not had an
* uncaught exception handler explicitly set then this thread's
* <tt>ThreadGroup</tt> object is returned, unless this thread
* has terminated, in which case <tt>null</tt> is returned.
* @since 1.5
* @return the uncaught exception handler for this thread
*/
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}

分别来看下两种方式:

线程自己处理

Thread类有一个uncaughtExceptionHandler属性,表示这个线程当这个线程发生未捕获异常时的处理器,可以通过Thread.setUncaughtExceptionHandler方法来设置这个属性。如果没有显式调用此方法设置,那么uncaughtExceptionHandler属性默认为null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

/**
* Set the handler invoked when this thread abruptly terminates
* due to an uncaught exception.
* <p>A thread can take full control of how it responds to uncaught
* exceptions by having its uncaught exception handler explicitly set.
* If no such handler is set then the thread's <tt>ThreadGroup</tt>
* object acts as its handler.
* @param eh the object to use as this thread's uncaught exception
* handler. If <tt>null</tt> then this thread has no explicit handler.
* @throws SecurityException if the current thread is not allowed to
* modify this thread.
* @see #setDefaultUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}

交给线程组处理

如果没有设置线程的uncaughtExceptionHandler属性或者为null,则会将异常信息分发给线程所在的线程组。上面代码可以将group作为结果返回是因为所有线程组的父类ThreadGroup类实现了Thread.UncaughtExceptionHandler接口。
如果当前线程的线程组重写了uncaughtException方法,会调用重写的uncaughtException方法,否则调用ThreadGroup类的uncaughtException方法。
下面是这个ThreadGroup类的uncaughtException方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Called by the Java Virtual Machine when a thread in this
* thread group stops because of an uncaught exception, and the thread
* does not have a specific {@link Thread.UncaughtExceptionHandler}
* installed.
* <p>
* The <code>uncaughtException</code> method of
* <code>ThreadGroup</code> does the following:
* <ul>
* <li>If this thread group has a parent thread group, the
* <code>uncaughtException</code> method of that parent is called
* with the same two arguments.
* <li>Otherwise, this method checks to see if there is a
* {@linkplain Thread#getDefaultUncaughtExceptionHandler default
* uncaught exception handler} installed, and if so, its
* <code>uncaughtException</code> method is called with the same
* two arguments.
* <li>Otherwise, this method determines if the <code>Throwable</code>
* argument is an instance of {@link ThreadDeath}. If so, nothing
* special is done. Otherwise, a message containing the
* thread's name, as returned from the thread's {@link
* Thread#getName getName} method, and a stack backtrace,
* using the <code>Throwable</code>'s {@link
* Throwable#printStackTrace printStackTrace} method, is
* printed to the {@linkplain System#err standard error stream}.
* </ul>
* <p>
* Applications can override this method in subclasses of
* <code>ThreadGroup</code> to provide alternative handling of
* uncaught exceptions.
*
* @param t the thread that is about to exit.
* @param e the uncaught exception.
* @since JDK1.0
*/
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}

处理流程如下:

  1. 首先,如果这个线程组有父线程组(parent属性),将会调用父线程组的uncaughtException方法处理。
  2. 否则,先调用Thread.getDefaultUncaughtExceptionHandler()检查是否有一个默认的UncaughtExceptionHandler,如果有,交给这个默认的UncaughtExceptionHandler来处理。
  3. 否则,如果该异常是ThreadDeath的实例,那么直接退出,如果不是,会将线程名字以及异常栈打印至标准错误输出流(控制台)。这是我们没有设置任何处理器时的默认逻辑,开头那段代码就是这种情况,没有设置任何处理器,所以只是在控制台输出了线程名称和异常信息。

默认处理器

上面代码中第二个分支中Thread.getDefaultUncaughtExceptionHandler()是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
/**
* Set the default handler invoked when a thread abruptly terminates
* due to an uncaught exception, and no other handler has been defined
* for that thread.
*
* <p>Uncaught exception handling is controlled first by the thread, then
* by the thread's {@link ThreadGroup} object and finally by the default
* uncaught exception handler. If the thread does not have an explicit
* uncaught exception handler set, and the thread's thread group
* (including parent thread groups) does not specialize its
* <tt>uncaughtException</tt> method, then the default handler's
* <tt>uncaughtException</tt> method will be invoked.
* <p>By setting the default uncaught exception handler, an application
* can change the way in which uncaught exceptions are handled (such as
* logging to a specific device, or file) for those threads that would
* already accept whatever &quot;default&quot; behavior the system
* provided.
*
* <p>Note that the default uncaught exception handler should not usually
* defer to the thread's <tt>ThreadGroup</tt> object, as that could cause
* infinite recursion.
*
* @param eh the object to use as the default uncaught exception handler.
* If <tt>null</tt> then there is no default handler.
*
* @throws SecurityException if a security manager is present and it
* denies <tt>{@link RuntimePermission}
* (&quot;setDefaultUncaughtExceptionHandler&quot;)</tt>
*
* @see #setUncaughtExceptionHandler
* @see #getUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
defaultUncaughtExceptionHandler = eh;
}
/**
* Returns the default handler invoked when a thread abruptly terminates
* due to an uncaught exception. If the returned value is <tt>null</tt>,
* there is no default.
* @since 1.5
* @see #setDefaultUncaughtExceptionHandler
* @return the default uncaught exception handler for all threads
*/
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}

可以看到这个方法是一个Thread类的静态方法,defaultUncaughtExceptionHandler也是Thread类的静态属性,表示可以供所有线程使用的默认的UncaughtExceptionHandler。可以分别通过setter方法和getter方法设置和获取。

处理流程总览

Uncaught Exception Process

注:上图中Handler指的是UncaughtExceptionHandler

扩展知识

ThreadGroup - 线程组

  • 线程组是一组线程的集合
  • 线程组中也可以包含其他线程组
  • 线程组的组织是一个树结构,除了初始线程组之外每个线程组都有父线程组
  • 线程组实现了Thread.UncaughtExceptionHandler接口
  • 初始线程组是system线程组,由系统创建,这个线程组没有父线程组,通过下面这个构造方法创建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * Creates an empty Thread group that is not in any Thread group.
    * This method is used to create the system Thread group.
    */
    private ThreadGroup() { // called from C code
    this.name = "system";
    this.maxPriority = Thread.MAX_PRIORITY;
    this.parent = null;
    }

ThreadDeath

我们还忽略了一个小细节,就是在ThreadGroup类的默认处理逻辑中,如果异常是ThreadDeath的实例,是不会进行处理的。
ThreadDeath的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* An instance of {@code ThreadDeath} is thrown in the victim thread
* when the (deprecated) {@link Thread#stop()} method is invoked.
*
* <p>An application should catch instances of this class only if it
* must clean up after being terminated asynchronously. If
* {@code ThreadDeath} is caught by a method, it is important that it
* be rethrown so that the thread actually dies.
*
* <p>The {@linkplain ThreadGroup#uncaughtException top-level error
* handler} does not print out a message if {@code ThreadDeath} is
* never caught.
*
* <p>The class {@code ThreadDeath} is specifically a subclass of
* {@code Error} rather than {@code Exception}, even though it is a
* "normal occurrence", because many applications catch all
* occurrences of {@code Exception} and then discard the exception.
*
* @since JDK1.0
*/
public class ThreadDeath extends Error {
private static final long serialVersionUID = -4417128565033088268L;
}

当Thread.stop()方法被调用时,会抛出一个ThreadDeath类的实例。
应用程序只有必须在异步终止后进行清理时才应该捕获该类的实例。如果 ThreadDeath 被一个方法捕获,那么将它重新抛出非常重要,因为这样才能让该线程真正终止。
这就是为什么ThreadGroup的uncaughtException没有捕获ThreadDeath异常。

参考链接

  1. Lesson: Exceptions (The Java™ Tutorials > Essential Classes)
  2. JVM 如何处理未捕获异常
  3. How uncaught exceptions are handled
  4. Thread (Java Platform SE 8 )

JDK源码阅读-Integer

发表于 2019-06-15 | 更新于 2020-09-15

主要属性

  1. 最基本的是表示包装的基本类型的值:
    1
    private final int value;
  2. 与类型相关的常量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // int能表示的最小值
    public static final int MIN_VALUE = 0x80000000;
    // int能表示的最大值
    public static final int MAX_VALUE = 0x7fffffff;
    // 代表基本类型int的Class类示例
    public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

    //补码形式的比特数
    public static final int SIZE = 32;
    //补码形式的字节数
    public static final int BYTES = SIZE / Byte.SIZE;
  3. 用来辅助而定义的常量

digits常量中存的是所有可以用来表示数字的字符,最大包含36进制。

1
2
3
4
5
6
7
8
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'
};

DigitTens和DigitOnes分别用来获取一个两位数的十位字符表示和个位字符表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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',
} ;

上面几个数组主要是用来在转化数字为字符串时通过查表快速获得对应的位。

sizeTable主要用来快速判断一个数的位数。

1
2
3
4
5
6
7
8
9
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };

// Requires positive x
static int stringSize(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}

IntegerCache类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

Integer内部有一个static内部类,使用数组缓存了一部分值对应的的包装对象,这部分值的默认范围是[-128,127],不过缓存大小可以通过-XX:AutoBoxCacheMax=<size>参数来控制,该参数可以设置初始化缓存初始化时high的值,但是low是不变的,固定为-128。
缓存中的值在public static Integer valueOf(int i)方法中使用。

构造方法

1
2
3
4
5
6
7
public Integer(int value) {
this.value = value;
}
// 调用parse方法(后面会讲)将字符串按照十进制解析得到int值
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}

int->Integer 方法

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

首先从判断是否在初始化好的IntegerCache的缓存范围内,如果在直接返回缓存中的值,否则new一个返回。

String->int 方法

以下这些方法主要用来将String类型解析为int基本类型:

1
2
3
4
5
6
7
8
9
//解析指定进制的字符串转化为int基本类型
public static int parseInt(String s, int radix);
//上面方法的重载方法,radix = 10
public static int parseInt(String s);

//解析指定进制的无符号字符串转化为int基本类型,即不处理负号
public static int parseUnsignedInt(String s, int radix);
//上面方法的重载方法,radix = 10
public static int parseUnsignedInt(String s);

parseInt方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public static int parseInt(String s, int radix) throws NumberFormatException{
if (s == null) {
throw new NumberFormatException("null");
}
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX");
}
int result = 0;
boolean negative = false;
int i = 0, len = s.length();
int limit = -Integer.MAX_VALUE;
int multmin;
int digit;
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
throw NumberFormatException.forInputString(s);

if (len == 1) // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
// 相乘之前判断防止溢出
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
// 相减之前判断防止溢出
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;
}

主要逻辑:

  1. 判断字符串不为空,并且传进来进制参数在2和36之间。
  2. 处理首位符号+或者-。
  3. 接下来就是将字符串转化为数字的逻辑。就是我们熟悉的每一位权重乘以权值(也就是进制数)依次相加,如:127转换成十进制 1*10*10+2*10+7*1=127。
    • 但是这里的处理方式不同之处在于统一按照负数来计算而不是按照正数来计算,这是为了在计算得到结果后可以直接将符号位加在前面得到结果。而如果采用正数来计算,正数的范围是无法表示Integer.MIN_VALUE的相反数的,这样省去了单独处理Integer.MIN_VALUE的逻辑,非常妙。
    • 循环中的两次判断都是为了防止溢出,在相乘或者相减之前提前判断以防止相乘之后溢出导致判断大小出错。

parseUnsignedInt方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static int parseUnsignedInt(String s, int radix)
throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
int len = s.length();
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar == '-') {
throw new
NumberFormatException(String.format("Illegal leading minus sign " + "on unsigned string %s.", s));
} else {
if (len <= 5 || // Integer.MAX_VALUE in Character.MAX_RADIX is 6 digits
(radix == 10 && len <= 9) ) { // Integer.MAX_VALUE in base 10 is 10 digits
return parseInt(s, radix);
} else {
long ell = Long.parseLong(s, radix);
if ((ell & 0xffff_ffff_0000_0000L) == 0) {
return (int) ell;
} else {
throw new
NumberFormatException(String.format("String value %s exceeds " + "range of unsigned int.", s));
}
}
}
} else {
throw NumberFormatException.forInputString(s);
}
}

这个方法是将字符串按照无符号数来处理

  1. 如果字符串前有-,抛出异常。
  2. 对于确定在不会超过int最大范围内的数(根据位数判断),调用Integer.parseInt方法处理。
  3. 有可能超过int范围的,调用Long.parseLong方法解析为long基本类型,如果解析后的值不超过32位,强制转换为int返回,如果超出了32位,抛出异常。

    Long.parseLong方法的逻辑与Integer.parseInt方法逻辑一致。

String->Integer 方法

以下这几个方法主要用来将String类型转化为Integer包装类型。

1
2
3
4
5
6
public static Integer valueOf(String s, int radix) throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}

这两个方法首先将String类型解析为int基本类型,然后调用valueOf(int i)返回其包装类型。

int->String 方法

toString方法

以下这几个方法主要用来将int基本类型转化为String类型。

1
2
3
4
// 处理任意进制(2-36),遇到十进制会调用下面一个方法
public static String toString(int i, int radix);
// 只处理十进制,针对十进制进行了优化
public static String toString(int i);

toString(int i, int radix)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static String toString(int i, int radix) {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;
/* Use the faster version */
if (radix == 10) {
return toString(i);
}
char buf[] = new char[33];
boolean negative = (i < 0);
int charPos = 32;
if (!negative) {
i = -i;
}
while (i <= -radix) {
buf[charPos--] = digits[-(i % radix)];
i = i / radix;
}
buf[charPos] = digits[-i];

if (negative) {
buf[--charPos] = '-';
}
return new String(buf, charPos, (33 - charPos));
}
  1. 判断进制,不符合条件按照十进制处理,处理十进制调用toString(int i)方法,速度更快。
  2. 接下来的过程我们也很熟悉。初始化一个char数组,然后就是依次取余,得到其对应的char字符,倒序填充数组,在第一位处理符号位。
    • 这里同样是统一转化为负数处理来避免Integer.MIN_MAX转化为正数时溢出
    • 用提前初始化好的数组digits快速获查表得对应的字符

toString(int i)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static String toString(int i) {
// 提前排除 Integer.MIN_VALUE
if (i == Integer.MIN_VALUE)
return "-2147483648";
// 计算所需位数
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// Generate two digits per iteration
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];
}

// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16+3);
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
if (sign != 0) {
buf [--charPos] = sign;
}
}

这个方法和上一个方法思想是一样的,但是用到几个技巧:

  1. int的高两个字节和低两个自己分开处理,低两个字节一次处理两位,高两个字节一次处理一位。
  2. 取余数时没有用取模运算,而是先做除法,再做乘法。
    • 一次处理两位时,先除以100,再乘以100,用原数相减得到余数。乘以100用位运算(q * 100 = ((q << 6) + (q << 5) + (q << 2)))代替。
    • 一次处理一位时,先除以10,再乘以10,用原数相减得到余数。除以10用乘法和位运算(i / 10 = (i * 52429) >>> (16+3))代替,乘以10用位运算(q * 10 = ((q << 3) + (q << 1)))代替。

以上位运算替换均是为了加快计算速度。

  1. q * 100 = ((q << 6) + (q << 5) + (q << 2))
  2. q * 10 = ((q << 3) + (q << 1))
  3. i / 10 = (i * 52429) >>> (16+3)

分别解释一下这三个等式为什么成立:

  1. 100 = 2^6 + 2^5 + 2^2 -> q * 100 = q * (2^6) + q * (2^5) + q * (2^2) -> (q << 6) + (q << 5) + (q << 2)
  2. 10 = 2^3 + 2^1 -> q * 10 = q * (2^3) + q * (2^1) -> (q << 3) + (q << 1)
  3. (i * 52429) >>> (16+3) -> (i * 52429) / (2^19) -> (i * 52429) / (2^19) -> (i * 52429) / 524288 , 这个结果在不考虑余数的情况下等于i / 10

解释完正确性之后,来思考几个问题:

  1. 为什么要用这么难理解的位运算的来代替乘除呢?显然是为了运算效率,几种运算的效率差别分别为:位运算 > 乘法 > 除法。所以用处理高位时用位运算代替乘法,处理低位时用位运算和乘法代替除法,用位运算代替乘法。
  2. 为什么要将高位和低位分开运算呢?答案也是计算速度上的考量,计算高两个字节时可以一次计算两位加快效率,而计算低两个字节时可以通过用乘法和移位代替除法(上面第三个等式)来加快计算效率。

toUnsignedString方法

以下两个方法将将参数中的i作为无符号数转换为String。例如:
Integer.toString(Integer.MIN_VALUE) -> -2147483648
Integer.toUnsignedString(Integer.MIN_VALUE) -> 2147483648

1
2
3
4
5
6
7
8
9
10
11
public static String toUnsignedString(int i, int radix) {
// 这个地方其实等于 Long.toString(toUnsignedLong(i), radix);
// 因为 Long.toUnsignedString(long i, int radix) 在 参数 i >= 0 时调用的就是 Long.toString(long i, int radix)
return Long.toUnsignedString(toUnsignedLong(i), radix);
}
public static String toUnsignedString(int i) {
return Long.toString(toUnsignedLong(i));
}
public static long toUnsignedLong(int x) {
return ((long) x) & 0xffffffffL;
}

这两个方法首先都将i当成无符号数然后转换为long型,也就是保留低32位,高32位填0(转换后的long型值一定为正数)。然后调用Long.toString方法或者Long.toUnsignedString方法得到结果。

特殊进制的格式化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static String toOctalString(int i) {
return toUnsignedString0(i, 3);
}
public static String toHexString(int i) {
return toUnsignedString0(i, 4);
}
public static String toBinaryString(int i) {
return toUnsignedString0(i, 1);
}
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];

formatUnsignedInt(val, shift, buf, 0, chars);

// Use special constructor which takes over "buf".
return new String(buf, true);
}
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
buf[offset + --charPos] = Integer.digits[val & mask];
val >>>= shift;
} while (val != 0 && charPos > 0);

return charPos;
}

这几个方法分别是二进制,八进制,十六进制的格式化方法。之所以这这几个进制有单独的方法,是因为他们都是2的幂次,所以可以直接处理其二进制位。例如八进制就是一次处理三位,十六进制就是一次处理四位。

注意这里都是按照无符号数来处理的。

toUnsignedString0()方法就是通用的处理方法。

  1. 首先计算最后的值的长度,这里并没有考虑符号的问题,统一是按照无符号数来处理的。((mag + (shift - 1)) / shift)相当于(mag / shift)向上取整。numberOfLeadingZeros计算的是前导0的个数,后面会说。
  2. formatUnsignedInt这个方法比较好理解,就是一次处理shift位,方法是与(1 << shift - 1)相与,然后右移,处理下一个shift位。

有关位运算的一些方法

代码中的一些位运算方法注释中的 HD指的是《Hacker’s Delight》一书,其中有解释了大量位运算相关的技巧,中文译本《算法心得:高效算法的奥秘》

highestOneBit | lowestOneBit

1
2
3
4
5
6
7
8
9
10
11
12
13
public static int highestOneBit(int i) {
// HD, Figure 3-1
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return i - (i >>> 1);
}
public static int lowestOneBit(int i) {
// HD, Section 2-1
return i & -i;
}

这两个方法返回都是2的幂(即二进制表示中至多只有一位是1),两个方法分别表示参数i的二进制表示中只保留最高位1和只保留最低位1后的值。
例如:14的二进制表示为1110(省略高位0),highestOneBit(14) = 8,即1000,lowestOneBit(i) = 2,即0010。
如果将参数i看成二进制的话,就是一个返回最高位1的权值,一个返回最低位1的权值。将14表示为2的幂的和:14 = 8 + 4 + 2, highestOneBit可以得到第一个数,lowestOneBit可以得到最后一个数。
但是注意两点:

  1. 负数的时候,highestOneBit始终返回-2147483648,因为最高位始终为符号位1。
  2. 参数为0时,两个方法返回都是0。

这两个方法有什么实际用途呢?

  1. highestOneBit得到的值等于将参数x下调为小于等于x且与之最接近的2的幂

我们来看一下具体是怎么实现的。
highestOneBit的思想是将最高位的1一直向右传播,直到其右方全是1,然后将其右边的0全部‘减掉’。示例:

1
2
3
4
5
6
7
i		01000000 00000110 11100011 11011100
i |= (i >> 1) 01100000 00000111 11110011 11111110
i |= (i >> 2) 01111000 00000111 11111111 11111111
i |= (i >> 4) 01111111 10000111 11111111 11111111
i |= (i >> 8) 01111111 11111111 11111111 11111111
i |= (i >> 16) 01111111 11111111 11111111 11111111
i - (i >>> 1) 00100000 00000000 00000000 00000000

lowestOneBit思想是来自于相反数的补码表示,一个数的相反数的补码表示为原数的补码表示按位取反,再加1,加1之后,最右边的就会进位一直到第一个0也就是原数第一个1的位置。最后相与,因为前面的位都是相反的,所以都为0,而最低位1之后都相等。看个例子就明白了。

1
2
3
132416			00000000 00000010 00000101 01000000
-132416 11111111 11111101 11111010 11000000
132416 & -132416 00000000 00000000 00000000 01000000

numberOfLeadingZeros | numberOfLeadingZeros(前导0和后导0的个数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) { n += 16; i <<= 16; }
if (i >>> 24 == 0) { n += 8; i <<= 8; }
if (i >>> 28 == 0) { n += 4; i <<= 4; }
if (i >>> 30 == 0) { n += 2; i <<= 2; }
n -= i >>> 31;
return n;
}

public static int numberOfTrailingZeros(int i) {
// HD, Figure 5-14
int y;
if (i == 0) return 32;
int n = 31;
y = i <<16; if (y != 0) { n = n -16; i = y; }
y = i << 8; if (y != 0) { n = n - 8; i = y; }
y = i << 4; if (y != 0) { n = n - 4; i = y; }
y = i << 2; if (y != 0) { n = n - 2; i = y; }
return n - ((i << 1) >>> 31);
}

这两个方法是用来计算前导0的个数和后导0的个数。具体实现方式使用了二分的思想。
numberOfLeadingZeros方法中,首先判断前16位(i >>> 16)是不是全为0,如果不是则判断前8位,如果是则计数增加16并且左移16位之后再判断前8位(这个时候实际判断的是原数的17-24位),然后依次判断前4位,前2位,前1位。
numberOfTrailingZeros方法中,不同的是计数初始值为31,首先判断后16位(i << 16)是不是等于0,如果是则判断最后24位,如果不是则计数减16并且左移16位后判断最后24位(这个时候判断的实际是原数的最后8位),依次判断后28,30,31位。

我们来看下其中的细节:
numberOfLeadingZeros为什么最后一步不太一样呢,其实原本的样子应该是这样的:

1
2
3
4
5
......
int n = 0;
......
if (i >>> 31 == 0) { n += 1; i <<= 1; }
return n;

可以将最后一个分支转化为n = n + 1 - (i >>> 31),如果将n初始化为1,加法也可以省去。为了计算效率,真是操碎了心啊。
numberOfTrailingZeros最后一步也不太一样,其实原本的样子是这样的:

1
2
3
......
y = i << 1; if(y != 0) { n = n - 1;}
return n;

用i >>> 31代替了判断是否为0和加1操作。

bitCount(计算1的位数)

1
2
3
4
5
6
7
8
9
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}

bitCount方法用来判断二进制表示中1的个数。
先来说一下其原理,基本的思想是分治法,首先将相邻的两位相加结果存放到一个宽度为2的位段,此时这个两位值表示的就是原先两位中1的个数;然后将相邻两个位段相加结存到一个宽度为4的位段中,此时这个四位值表示是原先四位中1的个数;依次处理8位,16位,最后得到的结果就是原数中1的个数。
可以用下面这张图来解释:
图片来自HD一书

再来说下代码,首先,代码原型是这样:

1
2
3
4
5
6
x = (x & 0x55555555) + ((x >>>  1) & 0x55555555);
x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
x = (x & 0x0F0F0F0F) + ((x >>> 4) & 0x0F0F0F0F);
x = (x & 0x00FF00FF) + ((x >>> 8) & 0x00FF00FF);
x = (x & 0x0000FFFF) + ((x >>> 16) & 0x0000FFFF);
return x;

这个代码是如何变成上面的那个四不像代码的呢?其实进行了一些优化:

  1. 第一步: 这一步有点看起来像黑魔法,将加法操作变为了减法操作。
    我们先只看两位操作,原操作相当于x & 0x01 + ((x >>> 1) & 0x01),而这个黑魔法相当于x - (x >>> 1 & 0x01)。
    如何证明这两个操作是等同的呢?
    假设两个位从左到右分别为b0,b1,也就是原先的值x等于b0 * 2^1 + b1 * 2^0 = 2b0 + b1,而我们最后想要的值其实是b0 + b1,b0 + b1 = (2b0 + b1) - b0,就等于x - (x >>> 1 & 0x01)。32位一起处理就是上面的结果i - ((i >>> 1) & 0x55555555)
  2. 第二步:原样不变,没有优化。
  3. 第三步:因为求和操作的结果最多为8,不会超过四位,所以可以先相加后位与
  4. 第四第五步:相加结果不会向相邻位段进位,所以可以先相加后位与,但是因为最多只有32个1占6位,所以这两步可以的消位操作可以放到最后一起做。

rotateLeft | rotateRight (循环左移和循环右移)

1
2
3
4
5
6
7
public static int rotateLeft(int i, int distance) {
return (i << distance) | (i >>> -distance);
}

public static int rotateRight(int i, int distance) {
return (i >>> distance) | (i << -distance);
}

这两个方法返回一个int值循环左移和循环右移后的值。distance参数中除了最低的五位外都会被忽略(相当于与0x3f相与),可以认为范围始终在0-32之间,这与java语言规范中<<,>>,>>>指令的语义规范一致。

If the promoted type of the left-hand operand is int, then only the five lowest-order
bits of the right-hand operand are used as the shift distance. It is as if the right-hand
operand were subjected to a bitwise logical AND operator & with the
mask value 0x1f (0b11111). The shift distance actually used is therefore always in
the range 0 to 31, inclusive.

代码很好理解,左移就是左移d位后的值与右移32-d后的值相与,就相当于将左移溢出的部分补全到右边的位置;右移同理。那么代码中的(i >>> -distance)和(i << -distance)是怎么回事呢?其实-distance & 0x1f就等于32 - distance。所以这里可以直接用-distance来做运算。

reverse(比特翻转)

1
2
3
4
5
6
7
8
9
public static int reverse(int i) {
// HD, Figure 7-1
i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
i = (i << 24) | ((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) | (i >>> 24);
return i;
}

reverse方法将int型值的二进制补码表示翻转顺序。思想也比较简单,还是分治思想,首先将以1bit为单位翻转顺序然后以2bit为单位翻转顺序,依次进行。
为什么最后一句不太一样,因为到了按照8bit操作时,直接按照byte来操作了。

reverseBytes(字节翻转)

1
2
3
4
5
6
public static int reverseBytes(int i) {
return ((i >>> 24)) |
((i >> 8) & 0xFF00) |
((i << 8) & 0xFF0000) |
((i << 24));
}

将int型值的二进制补码表示以byte为单位翻转。

signum (计算符号位)

1
2
3
4
public static int signum(int i) {
// HD, Section 2-7
return (i >> 31) | (-i >>> 31);
}

signum方法用来计算一个int型值的符号,正数返回1,负数返回-1,0返回0。
分别考虑|两边的式子
(i >> 31)在i>0时返回0,i<0时返回-1,i=0时返回0
(-i >>> 31)在i>0是返回1,i<0时返回0,i=0时返回0
可见,两个式子分别能满足正数和负数的返回值要求,将两者相与便都可以可以覆盖正数和负数。

无符号除法

1
2
3
4
5
6
7
8
9
10
11
12
13
public static int divideUnsigned(int dividend, int divisor) {
// In lieu of tricky code, for now just use long arithmetic.
return (int)(toUnsignedLong(dividend) / toUnsignedLong(divisor));
}

public static int remainderUnsigned(int dividend, int divisor) {
// In lieu of tricky code, for now just use long arithmetic.
return (int)(toUnsignedLong(dividend) % toUnsignedLong(divisor));
}

public static long toUnsignedLong(int x) {
return ((long) x) & 0xffffffffL;
}

divideUnsigned 和 remainderUnsigned分别是求两个int数作为无符号数相除得到的商和余。原理是将两个数转换为无符号long型(高32位填0)相除和求余。

获取指定的int型系统变量

TODO…

JDK源码阅读-包装类

发表于 2019-06-15 | 更新于 2020-09-15

概览

JAVA中的基本类型有八种,分别是:byte,short,int,long,float,double,char,boolean。
在JDK中都有对应的包装类,均在java.lang包中,分别是:Byte,Short,Integer,Long,Float,Double,Boolean,Character。

以下源码基于JDK1.8。

这几个类型的继承关系如图:
https://blog-picture.nos-eastchina1.126.net/picture0011.jpg

Number

Byte,Short,Integer,Long,Float,Double这几个表示数字的类均继承自Number类。
Number类是一个 abstract类,Numbr类有以下方法:

1
2
3
4
5
6
7
8
9
10
public abstract int intValue();
public abstract long longValue();
public abstract float floatValue();
public abstract double doubleValue();
public byte byteValue() {
return (byte)intValue();
}
public short shortValue() {
return (short)intValue();
}

这几个方法分别对应了每种类型向其他类型转换的方法。byteValue,shortValue这两个方法是直接由intvalue()返回值强制转换过来的。

系列文章:
JDK源码阅读-Integer

12…7<i class="fa fa-angle-right" aria-label="下一页"></i>
Keaper

Keaper

62 日志
12 标签
GitHub
© 2020 Keaper
由 Hexo 强力驱动
|
主题 – NexT.Mist