SegmentFault Coding最新的文章
2019-07-16T20:36:51+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
浅析 Linux 进程与线程
https://segmentfault.com/a/1190000019781604
2019-07-16T20:36:51+08:00
2019-07-16T20:36:51+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
7
<h2>简介</h2>
<p>进程与线程是所有的程序员都熟知的概念,简单来说进程是一个执行中的程序,而线程是进程中的一条执行路径。进程是操作系统中基本的抽象概念,本文介绍 Linux 中进程和线程的用法以及原理,包括创建、消亡等。</p>
<h2>进程</h2>
<h3>创建与执行</h3>
<p>Linux 中进程的创建与执行分为两个函数,分别是 <code>fork</code> 和 <code>exec</code>,如下代码所示:</p>
<pre><code class="c">int main() {
pid_t pid;
if ((pid = fork() < 0) {
printf("fork error\n");
} else if (pid == 0) {
// child
if (execle("/home/work/bin/test1", "test1", NULL) < 0) {
printf("exec error\n");
}
}
// parent
if (waitpid(pid, NULL) < 0) {
printf("wait error\n");
}
}</code></pre>
<p><code>fork</code> 从当前进程创建一个子进程,此函数返回两次,对于父进程而言,返回的是子进程的进程号,对于子进程而言返回 0。子进程是父进程的副本,拥有与父进程一样的数据空间、堆和栈的副本,并且共享代码段。</p>
<p>由于子进程通常是为了调用 <code>exec</code> 装载其它程序执行,所以 Linux 采用了<strong>写时拷贝</strong>技术,即数据段、堆和栈的副本并不会在 <code>fork</code> 之后就真的拷贝,只是将这些内存区域的访问权限变为只读,如果父子进程中有任一个要修改这些区域,才会修改对应的内存页生成新的副本,这样子是为了提高性能。</p>
<p><code>fork</code> 之后父进程先执行还是子进程先执行是不确定的,所以如果要求父子进程进行同步,往往需要使用进程间通信。<code>fork</code> 之后子进程会继承父进程的很多东西,如:</p>
<ul>
<li>打开的文件</li>
<li>实际用户 ID、组用户 ID 等</li>
<li>进程组</li>
<li>当前工作目录</li>
<li>信号屏蔽和安排</li>
<li>...</li>
</ul>
<p>父子进程的区别在于:</p>
<ul>
<li>进程 ID 不同</li>
<li>子进程不继承父进程的文件锁</li>
<li>子进程的未处理信号集为空</li>
<li>...</li>
</ul>
<p><code>fork</code> 之后,子进程可以执行不同的代码段,也可以使用 <code>exec</code> 函数执行其它的程序。</p>
<h3>进程描述符</h3>
<p>进程在运行的时候,除了加载程序,还会打开文件、占用一些资源,并且会进入睡眠等其它状态。操作系统为了支持进程的运行,必然有一个数据结构保存着这些东西。在 Linux 中,一个名为 <code>task_struct</code> 的结构保存了进程运行时的所有信息,称为进程描述符:</p>
<pre><code class="c">struct task_struct {
unsigned long state;
int prio;
pid_t pid;
...
}</code></pre>
<p>进程描述符完整描述了一个进程:打开的文件、进程的地址空间、挂起的信号以及进程的信号等。系统将所有的进程描述符放在一个双端循环列表中:</p>
<p><img src="/img/bVbu7jP?w=586&h=136" alt="queue_task_struct.png" title="queue_task_struct.png"></p>
<p>进程描述符具体存放在内存的哪里呢?在内核栈的末尾。众所周知,进程中占用的内存一部分是栈,主要用于函数调用,不过这里说的栈一般指的是用户空间的栈,其实进程还有内核栈。当进程调用系统调用的时候,进程陷入内核,此时内核代表进程执行某个操作,此时使用的是内核空间的栈。</p>
<h3>进程状态</h3>
<p>进程描述符中的 <code>state</code> 描述了进程当前的状态,有如下 5 种:</p>
<ol>
<li>TASK_RUNNING:进程是可执行的,此时进程要么是正在执行,要么是在运行队列中等待被调度</li>
<li>TASK_INTERRUPTIBLE:进程正在睡眠(阻塞),等待条件达成。如果条件达成或者收到信号,进程会被唤醒并且进入可运行状态</li>
<li>TASK_UNINTERRUPTIBLE:进程处于不可中断状态,就算信号也无法唤醒,这种状态用的比较少</li>
<li>_TASK_TRACED:进程正在被其它进程追踪,通常是为了调试</li>
<li>_TASK_STOPPED:进程停止运行,通常是接收到 SIGINT、SIGTSTP 信号的时候。</li>
</ol>
<h3>fork 与 vfork</h3>
<p>在使用了写时拷贝后,<code>fork</code> 的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。<code>fork</code> 为了创建一个进程到底做了什么呢?<code>fork</code> 其实调用了 <code>clone</code>,这是一个系统调用,通过给 <code>clone</code> 传递参数,表明父子进程需要共享的资源,<code>clone</code> 内部会调用 <code>do_fork</code>,而 <code>do_fork</code> 的主要逻辑在 <code>copy_process</code> 中,大致有以下几步:</p>
<ol>
<li>为新进程创建一个内核栈以及 task_struct,此时它们的值与父进程相同</li>
<li>将 task_struct 中某些变量,如统计信息,设置为 0</li>
<li>将子进程状态设置为 TASK_UNINTERRUPTIBLE,保证它不会被投入运行</li>
<li>分配 pid</li>
<li>根据传递给 <code>clone</code> 的参数,拷贝或者共享打开的文件、文件系统信息、信号处理函数以及进程的地址空间等。</li>
<li>返回指向子进程的指针</li>
</ol>
<p>除了 <code>fork</code> 之外,Linux 还有一个类似的函数 <code>vfork</code>。它的功能与 <code>vfork</code> 相同,子进程在父进程的地址空间运行。不过,父进程会阻塞,直到子进程退出或者执行 <code>exec</code>。需要注意的是,子进程不能向地址空间写入数据。如果子进程修改数据、进行函数调用或者没有调用 <code>exec</code> 那么会带来未知的结果。<code>vfork</code> 在 <code>fork</code> 没有写时拷贝的技术时是有着性能优势,现在已经没有太大的意义。</p>
<h3>退出</h3>
<p>进程的运行终有退出的时候,有 8 种方式使进程终止,其中 5 中为正常终止:</p>
<ol>
<li>从 main 返回</li>
<li>调用 exit</li>
<li>调用 _exit 或 _Exit</li>
<li>最后一个线程从其启动例程返回</li>
<li>从最后一个线程调用 pthread_exit</li>
</ol>
<p>异常终止方式有 3 种:</p>
<ol>
<li>调用 abort</li>
<li>接收到一个信号</li>
<li>最后一个线程对取消请求作出响应</li>
</ol>
<p><code>exit</code> 函数会执行标准 I/O 库的清理关闭操作:对所有打开的流调用 <code>fclose</code> 函数,所有缓冲中的数据会被冲洗,而 <code>_exit</code> 会直接陷入内核。看下面的代码:</p>
<pre><code class="c">#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("line 1\n");
printf("line 2"); // 没有换行符
// exit(0)
_exit(0);
}</code></pre>
<p>其中第二行输出没有 <code>\n</code>,如果末尾调用的是 <code>_exit</code>,则只会输出 <code>line 1</code>,如果替换为 <code>exit</code>,则第二行 <code>line 2</code> 也会输出。</p>
<p>进程退出最终会执行到系统的 <code>do_exit</code> 函数,主要有以下步骤:</p>
<ol>
<li>删除进程定时器</li>
<li>释放进程占用的页表</li>
<li>递减文件描述符的引用计数,如果某个引用计数为 0,则关闭文件</li>
<li>向父进程发信号,给子进程重新找养父,并且把进程状态设置为 EXIT_ZOMBIE</li>
<li>调度其它进程</li>
</ol>
<p>此时,进程的大部分资源都被释放了,并且不会进入运行状态。不过还有些资源保持着,主要是 task_struct 结构。之所以要留着是给父进程提供信息,让父进程知道子进程的一些信息,如退出码等。</p>
<p>需要注意的是,如果父进程不进行任何操作,那么这些信息会一直保留在内存中,成为<em>僵尸进程</em>,占用系统资源,如下面的代码:</p>
<pre><code class="c">int main() {
pid_t pid = fork();
if (pid == 0) {
exit(0);
} else {
sleep(10);
}
}
</code></pre>
<p>父进程 fork 出子进程后,子进程立刻退出,而父进程则进入睡眠。运行程序,观察进程状态:</p>
<p><img src="/img/bVbu7ta?w=1060&h=113" alt="zombie.png" title="zombie.png"></p>
<p>可以看到,第一行进程为父进程,状态为 <code>S</code>,表示其正在睡眠,而第二为子进程,状态为 <code>Z</code>,表示僵尸状态(<code>zombie</code>),因为此时子进程已经退出,然而 task_struct 还保存着,等待父进程来处理。</p>
<p>父进程如何处理?调用 <code>wait</code> 函数,正如本文第一段代码中所示。当父进程调用 <code>wait</code> 后,子进程的 task_struct 才被释放。</p>
<p>如果父进程先结束了呢?在父进程结束的时候,会为其子进程找新的父进程,一直往上找,最终成为 <code>init</code> 进程的子进程。<code>init</code> 子进程会负责调用 <code>wait</code> 释放子进程的遗留信息。</p>
<h2>线程</h2>
<p>上面介绍了 Linux 中的进程,那么线程又是怎么的?网上一些说法是,Linux 中并没有真正的内核线程,线程是以进程的方式实现的,只不过它们之间会共享内存。这种说法有一定道理,但并不完全准确。</p>
<p>Linux 中刚开始是不支持线程的,后来出现了线程库 LinuxThreads,不过它有很多问题,主要是与 POXIS 标准不兼容。自 Linux 2.6 以来,Linux 中使用的就是新的线程库,NPTL(Native POSIX Thread Library)。</p>
<p>NPTL 中线程的创建也是通过 <code>clone</code> 实现的,并且通过以下的参数表明了线程的特征:</p>
<pre><code>CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS |
CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM</code></pre>
<p>部分参数的含义如下:</p>
<ul>
<li>CLONE_VM:所有线程都共享同一个进程地址空间</li>
<li>CLONE_FILES:所有线程都共享进程的文件描述符列表</li>
<li>CLONE_THREAD:所有线程都共享同一个进程 ID 以及 父进程 ID</li>
</ul>
<p>NPTL 所实现的线程库是 1:1 的从用户线程映射到内核线程,并且内核为了实现 POSIX 的线程标准也做了一些改动,比如对于信号的处理等。所以说 Linux 内核完全不区分进程和线程,甚至不知道线程的存在这种说法现在是不准确的。</p>
<p>线程间共享代码段、堆以及打开的文件等,线程私有的部分有以下内容:</p>
<ul>
<li>线程 ID</li>
<li>寄存器</li>
<li>错误码(errno)</li>
<li>栈</li>
<li>信号屏蔽</li>
<li>...</li>
</ul>
<h2>总结</h2>
<p>Linux 中进程与线程的使用是程序员必备的技能,而如果能了解一些实现的原理,则可以使用的更加得心应手。本文介绍了 Linux 中进程的创建、执行以及消亡等,对于线程的实现及其与进程的关系也进行了简单的说明。进程和线程还有更多的内容可以研究,如进程调度、进程以及线程间的通信等。</p>
<p><strong>参考</strong></p>
<ul>
<li>《UNIX 环境高级编程》</li>
<li>《Linux 内核设计与实现》</li>
</ul>
Redis 的持久化与过期键
https://segmentfault.com/a/1190000017526315
2018-12-26T19:33:08+08:00
2018-12-26T19:33:08+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
12
<h2>简介</h2>
<p>Redis 是使用非常广泛的 Key-Value 内存数据库。因为数据都存放在内存中,所以存取速度非常快。不过,很多情况下我们需要将 Redis 中的数据保存到硬盘中以便做备份。Redis 提供了两种数据持久化方式,分别是 RDB 和 AOP,本文分析这两种方式的使用以及过期键对持久化的影响。</p>
<h2>RDB</h2>
<p>RDB 指的是将 Redis 数据库在某个时间点的快照保存到磁盘,所生成的 RDB 文件是一个经过压缩的二进制文件,通过这个文件可以还原出 Redis 的数据状态。</p>
<p>创建快照的方式有以下几种:</p>
<ul>
<li>客户端向 Redis 发送 <code>BGSAVE</code> 命令,Redis 会调用 fork 创建一个子进程,然后子进程负责将快照写入硬盘,而父进程继续处理命令请求。</li>
<li>客户端向 Redis 发送 <code>SAVE</code> 命令,此时 Redis 将开始创建快照,并且在完成之前不再响应其它命令。</li>
<li>用户设置 save 配置选项,比如 <code>save 60 10000</code>,那么从 Redis 最近一次创建快照算起,当 “60 秒内有 10000 次写入” 这个条件被满足时, Redis 就会自动触发 <code>BGSAVE</code> 命令。如果用户设置了多个 save 配置选项,那么当任意一个 save 配置满足时,Redis 就会触发一次 <code>BGSAVE</code> 命令。save 配置的格式如下所示:</li>
</ul>
<pre><code>save 60 10000
stop-writes-on-bgsave-error no
rdbcompression yes // 使用压缩
dbfilename dump.rdb // RBD 文件的名字
dir ./</code></pre>
<ul>
<li>当 Redis 通过 <code>SHUTDOWN</code> 命令接收到关闭服务器的请求时,或者接收到 TERM命令时,会执行一个 <code>SAVE</code> 命令,并且阻塞所有的客户端,不再执行任何请求。在 <code>SAVE</code> 命令执行结束后关闭服务器。</li>
<li>当一个 Redis 服务器连接另一个 Redis 服务器,并向对方发送 <code>SYNC</code> 命令来开始一次复制操作的时候,如果主服务器没有在执行 <code>BGSAVE</code> 操作,或者主服务器并非刚执行完 <code>BGSAVE</code>,那么主服务器会执行 <code>BGSAVE</code> 命令。</li>
</ul>
<p>RDB 的主要问题是,如果系统发生崩溃,那么最近一次执行完快照后修改的数据将被丢失。因此,RDB 适合用于即使丢失一部分数据也不会造成影响的应用程序。</p>
<h2>AOF</h2>
<p>AOF 指的是将所有执行的写命令写到 AOF 文件的末尾,以此来记录数据发生的变化。如果 Redis 想要恢复 AOF 中的数据,只要重新执行一次 AOF 文件中所包含的写命令就可以。</p>
<p>AOF 的配置如下所示:</p>
<pre><code>appendonly yes // 打开 aof
appendfsync everysec // aof 同步的频率
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100 // 文件大小增长比超过这个值开始自动重写 aof
auto-aof-rewrite-min-size 64mb // 文件大小超过这个值才可以有可能自动重写 aof</code></pre>
<p>上面的配置中,<code>appendfsync everysec</code> 设置的是同步的频率。应用程序在向硬盘写入数据的时候有 3 个步骤:</p>
<ol>
<li>调用 <code>file.write()</code> 向文件写入,此时需要写入的内容被存储到了缓冲区,并不是真正写到硬盘上了。</li>
<li>操作系统在某个时候将缓冲区的内容写入硬盘,这时数据才真正被持久化了。</li>
</ol>
<p>操作系统使用以上的文件写入方式是为了提高性能,毕竟硬盘 I/O 操作是比较耗时的。但是,这种方式的缺点在于如果机器崩溃了那么缓冲区的内容将丢失。程序可以使用 <code>file.flush()</code> 来请求操作系统尽快地将缓冲区的内容刷新到硬盘上,不过何时开始执行仍然由操作系统决定。程序也可以命令操作系统将文件同步 ( sync ) 到硬盘,同步操作会阻塞应用程序直到数据被写入硬盘。当同步操作完成后,即使系统出现故障,也不会对被同步的文件造成影响。</p>
<p>对于 Redis 来讲,可以指定 <code>appendfsync</code> 以何种方式让数据完全同步到硬盘,这个配置有 3 个选项:</p>
<ol>
<li>always: 每个 Redis 写命令都立即同步到硬盘,这是比较消耗性能的</li>
<li>everysec: 每秒执行一次同步,兼顾性能与数据安全,是比较常用的选项</li>
<li>no: 让操作系统决定何时进行同步</li>
</ol>
<p><code>always</code> 可以使得在 Redis 发生崩溃时丢失的数据最少,但是也是最消耗性能的,导致 Redis 的处理速度变慢。<code>ererysec</code> 是一种兼顾性能与数据安全的方式,在这种情况下,如果系统崩溃,用户最多会丢失一秒内的数据。<code>no</code> 选项完全将同步交给操作系统被决定,性能也不比 <code>everysec</code> 高多少,是不推荐的方式。</p>
<p>AOF 的缺点是随着 Redis 的不断运行,AOF 文件可能会非常大,甚至用完硬盘的空间。解决这个问题的办法是 AOF 重写。</p>
<h3>重写</h3>
<p>客户端可以发送 <code>BGREWRITEAOF</code> 命令让 Redis 重写 AOF 文件,Redis 会移除冗余的 AOF 命令进行重写,使得 AOF 文件的体积尽可能地小。</p>
<p>除了客户端主动发送 <code>BGREWRITEAOF</code> 命令,也可以使用配置让 Redis 在满足一定条件地情况下自动开始重写 AOF 文件。例如上一小节设置了 <code>auto-aof-rewrite-percentage 100</code> 和 <code>auto-aof-rewrite-min-size 64mb</code>。这两个配置的含义是,如果 AOF 文件大于 64MB 并且比上一次重写之后的大小增加了一倍的时候,Redis 将执行 <code>BGREWERITEAOF</code> 命令。</p>
<h2>过期键删除</h2>
<p>用户往往为 Redis 中的键设定过期时间,因此需要一定的策略来删除过期键,可以有三种策略:</p>
<ol>
<li>定时删除,即通过定时器在过期时间到达的时候删除过期的键。这种方式的优点是节省内存,不会因为大量的过期键占用内存资源,而缺点则是消耗 CPU 资源,尤其是过期键数量较多的时候,删除操作消耗太长时间,降低了 Redis 的响应时间。</li>
<li>惰性删除,即在每次获取某个键的时候判断是否过期,如果未过期,则正常返回其值,否则删除这个键,返回空。这种方式的优点是节省 CPU 资源,但是消耗了内存。尤其是过期键数量较多的时候,大量内存被无效的键占用,相当于内存泄露。</li>
<li>定期删除,即每隔一段时间周期对数据库中的键进行扫描,但是只扫描其中一部分,力求在内存和 CPU 之间达到一个平衡。</li>
</ol>
<p>从上面 3 种策略可以看出,单用第一个肯定是不行的,Redis 的响应时间至关重要。第二个则是比较好的方式,在获取键的时候判断是否过期并决定是否删除,它的缺点是很多键无法及时删除。如果一个过期键再也没有被访问,那么它将永远留在内存中,而第三种方式正好可以弥补。</p>
<p>Redis 中过期键的删除策略正是惰性删除与定期删除的结合。</p>
<h2>过期键与持久化</h2>
<p>了解了过期键的删除策略后,下面看下键的过期时间对持久化的影响。</p>
<p>在生成 RDB 文件的过程中,如果一个键已经过期,那么其不会被保存到 RDB 文件中。在载入 RDB 的时候,要分两种情况:</p>
<ol>
<li>如果 Redis 以主服务器的模式运行,那么会对 RDB 中的键进行时间检查,过期的键不会被恢复到 Redis 中。</li>
<li>如果 Redis 以从服务器的模式运行,那么 RDB 中所有的键都会被载入,忽略时间检查。在从服务器与主服务器进行数据同步的时候,从服务器的数据会先被清空,所以载入过期键不会有问题。</li>
</ol>
<p>对于 AOF 来说,如果一个键过期了,那么不会立刻对 AOF 文件造成影响。因为 Redis 使用的是<strong>惰性删除和定期删除</strong>,只有这个键被删除了,才会往 AOF 文件中追加一条 DEL 命令。在重写 AOF 的过程中,程序会检查数据库中的键,已经过期的键不会被保存到 AOF 文件中。</p>
<p>在运行过程中,对于主从复制的 Redis,主服务器和从服务器对于过期键的处理也不相同:</p>
<ol>
<li>对于主服务器,一个过期的键被删除了后,会向从服务器发送 DEL 命令,通知从服务器删除对应的键</li>
<li>从服务器接收到读取一个键的命令时,即使这个键已经过期,也不会删除,而是照常处理这个命令。</li>
<li>从服务器接收到主服务器的 DEL 命令后,才会删除对应的过期键。</li>
</ol>
<p>这么做的主要目的是保证数据一致性,所以当一个过期键存在于主服务器时,也必然存在于从服务器。</p>
<h2>总结</h2>
<p>本文对 Redis 的两种持久化方式进行了简要的梳理,分析了 Redis 删除过期键的策略以及对持久化的影响。理解了这部分内容不仅可以让我们对 Redis 的使用更加得心应手,对于学习 Redis 的其它内容如复制的过程也会很有帮助。</p>
<p><strong>参考</strong></p>
<ul>
<li>《Redis 实战》</li>
<li>《Redis 设计与实现》</li>
</ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
MyBatis 源码解析(二):SqlSession 执行流程
https://segmentfault.com/a/1190000017425870
2018-12-19T02:12:20+08:00
2018-12-19T02:12:20+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
13
<h2>简介</h2>
<p>上一篇文章(<a href="https://segmentfault.com/a/1190000017362997">MyBatis 源码解析(一):初始化和动态代理</a>)分析了 MyBatis 解析配置文件以及 Mapper 动态代理相关的源码,这一篇接着上一篇探究 SqlSession 的执行流程,另外了解一下 MyBatis 中的缓存。</p>
<h2>openSession</h2>
<p>MyBatis 在解析完配置文件后生成了一个 <code>DefaultSqlSessionFactory</code> 对象,后续执行 SQL 请求的时候都是调用其 <code>openSession</code> 方法获得 <code>SqlSessison</code>,相当于一个 SQL 会话。 <code>SqlSession</code> 提供了操作数据库的一些方法,如 <code>select</code>、<code>update</code> 等。</p>
<p>先看一下 <code>DefaultSqlSessionFactory</code> 的 <code>openSession</code> 的代码:</p>
<pre><code class="java"> public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 从 configuration 取出配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 每个 SqlSession 都有一个单独的 Executor 对象
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
// 返回 DefaultSqlSession 对象
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}</code></pre>
<p>主要代码在 <code>openSessionFromDataSource</code>,首先是从 <code>Configuration</code> 中取出相关的配置,生成 <code>Transaction</code>,接着又创建了一个 <code>Executor</code>,最后返回了 <code>DefaultSqlSession</code> 对象。</p>
<p>这里的 <code>Executor</code> 是什么呢?它其实是一个执行器,<code>SqlSession</code> 的操作会交给 <code>Executor</code> 去执行。MyBatis 的 <code>Executor</code> 常用的有以下几种:</p>
<ul>
<li>SimpleExecutor: 默认的 Executor,每个 SQL 执行时都会创建新的 Statement</li>
<li>ResuseExecutor: 相同的 SQL 会复用 Statement</li>
<li>BatchExecutor: 用于批处理的 Executor</li>
<li>CachingExecutor: 可缓存数据的 Executor,用代理模式包装了其它类型的 Executor</li>
</ul>
<p>了解了 <code>Executor</code> 的类型后,看一下 <code>configuration.newExecutor(tx, execType, autoCommit)</code> 的代码:</p>
<pre><code class="java"> public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 默认是 SimpleExecutor
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 默认启动缓存
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}</code></pre>
<p>MyBatis 默认启用一级缓存,即同一个 <code>SqlSession</code> 会共用同一个缓存,上面代码最终返回的是 <code>CachingExecutor</code>。</p>
<h2>getMapper</h2>
<p>在创建了 <code>SqlSession</code> 之后,下一步是生成 Mapper 接口的代理类,代码如下:</p>
<pre><code class="java"> public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}</code></pre>
<p>可以看出是从 <code>configuration</code> 中取得 <code>Mapper</code>,最终调用了 <code>MapperProxyFactory</code> 的 <code>newInstance</code>。<code>MapperProxyFactory</code> 在上一篇文章已经分析过,它是为了给 <code>Mapper</code> 接口生成代理类,其中关键的拦截逻辑在 <code>MapperProxy</code> 中,下面是其 <code>invoke</code> 方法:</p>
<pre><code class="java"> @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 过滤一些不需要被代理的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取 MapperMethod 然后调用
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}</code></pre>
<p><code>MapperProxy</code> 中调用了 <code>MapperMethod</code> 的 <code>execute</code>,下面是部分代码:</p>
<pre><code class="java">public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
...
}</code></pre>
<p>可以看出,最终调用了 <code>SqlSession</code> 的对应方法,也就是 <code>DefaultSqlSession</code> 中的方法。</p>
<h2>select</h2>
<p>先看一下 <code>DefaultSqlSession</code> 中 <code>select</code> 的代码:</p>
<pre><code class="java"> public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}</code></pre>
<p><code>select</code> 中调用了 <code>executor</code> 的 <code>query</code>,上面提到,默认的 <code>Executor</code> 是 <code>CachingExecutor</code>,看其中的代码:</p>
<pre><code class="java"> @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 获取缓存的key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 调用代理对象的缓存
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}</code></pre>
<p>首先检查缓存中是否有数据,如果没有再调用代理对象的 <code>query</code>,默认是 <code>SimpleExecutor</code>。<code>Executor</code> 是一个接口,下面有个实现类是 <code>BaseExecutor</code>,其中实现了其它 <code>Executor</code> 通用的一些逻辑,包括 <code>doQuery</code> 以及 <code>doUpdate</code> 等,其中封装了 JDBC 的相关操作。</p>
<h2>update</h2>
<p><code>update</code> 的执行与 <code>select</code> 类似, 都是从 <code>CachingExecutor</code> 开始,看代码:</p>
<pre><code class="java"> @Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 检查是否需要刷新缓存
flushCacheIfRequired(ms);
// 调用代理类的 update
return delegate.update(ms, parameterObject);
}</code></pre>
<p><code>update</code> 会使得缓存的失效,所以第一步是检查是否需要刷新缓存,接下来再交给代理类去执行真正的数据库更新操作。</p>
<h2>总结</h2>
<p>本文主要分析了 <code>SqlSession</code> 的执行流程,结合上一篇文章基本了解了 MyBatis 的运行原理。对于 MyBatis 的源码,还有很多地方没有深入,例如SQL 解析时参数的处理、一级缓存与二级缓存的处理逻辑等,不过在熟悉 MyBatis 的整体框架之后,这些细节可以在需要用到的时候继续学习。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
MyBatis 源码解析(一):初始化和动态代理
https://segmentfault.com/a/1190000017362997
2018-12-13T12:42:45+08:00
2018-12-13T12:42:45+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
16
<h2>简介</h2>
<p>MyBatis 是 Java 开发中非常流行的 ORM 框架,其封装了 JDBC 并且解决了 Java 对象与输入参数和结果集的映射,同时又能够让用户方便地手写 SQL 语句。MyBatis 的行为类似于以下几行代码:</p>
<pre><code class="java">Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, usr, password);
PraparedStatement st = conn.prepareStatement(sql);
st.setInt(0, 1);
st.execute();
ResultSet rs = st.getResultSet();
while (rs.next()) {
String result = rs.getString(colname);
}</code></pre>
<p>上面是 JDBC 的使用流程,MyBatis 其实就是对上面的代码进行分解包装。本文将对 MyBatis 的代码进行分析,探究其中的逻辑。</p>
<h2>基本用法</h2>
<p>首先从 MyBatis 的基本用法开始,下面是 MyBatis 官网的入门示例:</p>
<pre><code class="java">String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);</code></pre>
<p>其中 <code>mabatis-config.xml</code> 是 MyBatis 的核心配置文件,其中包括数据源、事务管理器、别名以及 SQL 对应的 Mapper 文件等,如下所示:</p>
<pre><code><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration></code></pre>
<p>有了 <code>SqlSessionFactory</code> 后就可以创建 <code>SqlSession</code> 来调用 <code>select</code> 以及 <code>update</code> 等方法请求数据了:</p>
<pre><code>try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}</code></pre>
<h2>配置文件解析</h2>
<p>我们按照上面的代码流程开始分析源码,首先是配置文件的解析:<code>SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) </code>,<code>SqlSessionFactoryBuilder</code> 显然是为了构建 <code>SqlSessionFactory</code>,而且是从配置文件的输入流构建,代码如下:</p>
<pre><code class="java">public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建 XMLConfigBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parse.parse() 进行解析
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}</code></pre>
<p>首先是创建了一个 <code>XMLConfigBuilder</code> 对象,它是用来解析 Config 文件的。<code>XMLConfigBuilder</code> 继承自 <code>BaseBuilder</code>,<code>BaseBuilder</code> 中有个 <code>Configuration</code> 类型的变量,这个类需要重点关注,Config 文件中解析出来的所有信息都保存在这个变量中。</p>
<p>创建了 <code>XMLConfigBuilder</code> 后调用了其 <code>parse</code> 方法:</p>
<pre><code class="java"> public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在这个函数中解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}</code></pre>
<p>这里主要逻辑在 <code>parseConfiguration</code> 中:</p>
<pre><code class="java">private void parseConfiguration(XNode root) {
try {
// 解析 properties
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
// 解析 type alias
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 setting
settingsElement(root.evalNode("settings"));
// 解析 environment
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}</code></pre>
<p>这里解析了 config 文件中所有的标签,包括 <code>properties</code>、<code>settings</code> 以及 <code>mappers</code> 等,下面挑几个看一下。</p>
<h3>settings</h3>
<p>settings 是对 MyBatis 的一些配置项,包括缓存的开启以及是否使用驼峰转换(mapUnderscoreToCamelCase)等,代码如下:</p>
<pre><code class="java">private void settingsElement(XNode context) throws Exception {
if (context != null) {
// 将配置项保存到 Properties 中
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
// 默认开启缓存
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
}
}</code></pre>
<p>可以看出,settings 的子节点保存在 <code>Properties</code> 中,然后校验是否有不合法的子节点,最后提取出其中的属性保存到 <code>Configuration</code> 中,上面提到这个类专门用于保存 Config 文件解析出的信息。</p>
<p>从上面也可以看到 MyBatis 的一些默认属性,例如一级缓存如果没有配置,那么默认是开启的。</p>
<h3>environments</h3>
<p>environments 包含了数据源(dataSource) 和事务管理器(transactionManager) 的配置,代码如下:</p>
<pre><code class="java">private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 解析 transactionManager
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析 dataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 设置 environment 到 configuration
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
// 通过反射实例化
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
// 通过反射实例化
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}</code></pre>
<p>其中主要是两部分,第一部分解析 <code>transactionManager</code>,第二部分解析 <code>dataSource</code>。从 <code>transactionManagerElement</code> 和 <code>dataSourceElement</code> 中可以看出通过对应 <code>Class</code> 文件的 <code>newInstance</code> 实例化出对应的工厂对象。最终解析出的 <code>transactionManager</code> 和 <code>dataSource</code> 依然是设置到 <code>Configuration</code> 中。</p>
<h3>mappers</h3>
<p>mappers 对应了具体的 SQL Mapper 文件,也是我们要分析的重点。</p>
<p>mappers 标签可以多种子标签,上面的示例中是 <code>mapper</code> 配合 <code>resource</code>:</p>
<pre><code> <mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers></code></pre>
<p>我们下面看一下此种形式在源码中的解析:</p>
<pre><code class="java">private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 这个分支解析 resource 形式的标签
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建 XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 进行解析
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}</code></pre>
<p><code>resource</code> 标签解析的对应分支是 <code>(resource != null && url == null && mapperClass == null)</code>,其中创建了一个 <code>XMLMapperBuilder</code> 对象然后调用 <code>parse</code> 方法进行解析:</p>
<pre><code class="java"> public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析 mapper 下面的标签,包括 namespace、cache、parameterMap、resultMap 等
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// namespace 对应 Mapper 对应接口的全名(包名 + 类名)
String namespace = context.getStringAttribute("namespace");
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
// 解析生成 ParameterMap
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析生成 ResultMap
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 每一个 sql 语句生成一个 MappedStatement
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
}
}</code></pre>
<p><code>configurationElement</code> 用于解析具体的子标签,如 <code>namespace</code>、<code>cache</code>、<code>parameterMap</code>、<code>resultMap</code> 以及 <code>select|insert|update|delete</code> 等。</p>
<p><code>namespace</code> 对应了 Mapper 接口类的包名 + 类名,通过 <code>namespace</code> 可以唯一定位一个 <code>Class</code> 文件,解析的 <code>namespace</code> 保存在 <code>builderAssistant</code> 中,后面会用到。</p>
<p><code>parameterMap</code> 和 <code>resultMap</code> 解析会生成 <code>ParameterMap</code> 和 <code>ResultMap</code> 对象。每个 SQL 语句解析会生成 <code>MappedStatement</code>。</p>
<p>在上面的 <code>parse</code> 方法中,解析完标签后调用了 <code>bindMapperForNamespace</code>,这个实现了加载 <code>namespace</code> 对应的 <code>Class</code>,并且为每个 <code>Class</code> 创建了代理类工厂对象(<code>MapperProxyFactory</code>)。</p>
<h4>MapperProxyFactory</h4>
<p><code>MapperProxyFactory</code> 用于为 Mapper 接口类创建代理对象,代理对象指的是<br><code>BlogMapper mapper = session.getMapper(BlogMapper.class)</code> 生成的对象。</p>
<p>下面从 <code>bindMapperForNamespace</code> 开始:</p>
<pre><code class="java">private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 加载类
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 添加 mapper 和 MapperProxyFactory
configuration.addMapper(boundType);
}
}
}
}</code></pre>
<p>其中先从 <code>builderAssistant</code> 取出 <code>namespace</code>,然后加载对应的 <code>Class</code>(<code>boundType = Resources.classForName(namespace)</code>)。最后调用 <code> configuration.addMapper(boundType)</code> 添加到 <code>configuration</code> 中。<code> configuration.addMapper(boundType)</code> 很关键,看代码:</p>
<pre><code class="java">public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 添加到 Map<Class<?>, MapperProxyFactory<?>> 中
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}</code></pre>
<p>关键的一行是 <code>knownMappers.put(type, new MapperProxyFactory<T>(type))</code>,其中 <code>knownMappers</code> 的类型是 <code>Map<Class<?>, MapperProxyFactory<?>></code>,即 key 是 <code>Class</code>,value 是 <code>MapperProxyFactory</code>。这里的 <code>MapperProxyFactory</code> 即是动态代理对象的工厂,下面是其 <code>newInstance</code> 方法的代码:</p>
<pre><code class="java"> protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}</code></pre>
<p>从中可以看出,这里用的是 Java 的动态代理,<code>Proxy.newProxyInstance</code> 方法生成指定接口的代理对象,这个方法的第三个参数是用于方法拦截的对象,这里是 <code>MapperProxy</code> 的实例。</p>
<p>由此可以知道,具体的执行 SQL 语句的操作是由这个类拦截并且执行的,看看这个类的 <code>invoke</code> 方法:</p>
<pre><code class="java"> public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}</code></pre>
<p>如果是 <code>Object</code> 类中声明的方法,则直接执行,否则调用 <code>MapperMethod</code> 的 <code>execute</code>,其中便是 JDBC 相关的逻辑了。限于篇幅,具体内容留到下一篇文章再看。</p>
<p>在分析完配置文件的解析后,再回到 <code>XMLConfigBuilder</code> 中:</p>
<pre><code class="java"> public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 构建 SqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}</code></pre>
<p>在 <code>parser.parse()</code> 执行完后,生成一个 <code>Configuration</code> 对象,最后调用 <code>build</code> 构建 <code>SqlSessionFactory</code>,代码如下:</p>
<pre><code class="java"> public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}</code></pre>
<p>可以看到,最终创建的是 <code>DefaultSqlSessionFactory</code>,这个类内部持有 <code>Configuration</code>,并且提供了多个重载的 <code>openSession</code> 方法用于创建 <code>SqlSession</code> 。</p>
<p>到这里,初始化部分就结束了。</p>
<h2>总结</h2>
<p>MyBatis 的初始化流程主要是解析配置文件,将相关信息保存在 <code>Configuration</code> 中,同时对每个 <code>namespace</code> 代表的 <code>Class</code> 生成代理对象工厂。最后,利用 <code>Configuration</code> 生成了一个 <code>DefaultSqlSessionFactory</code>,通过这个对象可以创建 <code>SqlSession</code> 执行 SQL 请求,相关内容将在下一篇(<a href="https://segmentfault.com/a/1190000017425870">MyBatis 源码解析(二):SqlSession 执行流程</a>)分析。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
函数栈的实现原理
https://segmentfault.com/a/1190000017151354
2018-11-26T22:05:24+08:00
2018-11-26T22:05:24+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
4
<h2>简介</h2>
<p>编程语言离不开函数,函数是对一段代码的封装,往往实现了某个特定的功能,在程序中可以多次调用这个函数。稍有编程经验的同学都知道,函数是由栈实现的,调用对应入栈,退出对应出栈。在写递归函数的时候,如果递归层次太深会出现栈溢出(StackOverFlow)的错误。</p>
<p>"函数栈"包含了对函数调用的基本理解,但是从细节来看,还有很多疑问,例如:</p>
<ul>
<li>函数的栈是如何开辟的?</li>
<li>如何传入参数?</li>
<li>返回值是如何得到的?</li>
</ul>
<p>本文以 C 语言为例,从内存布局、汇编代码的角度来分析函数栈的实现原理。</p>
<h2>Linux 进程内存布局</h2>
<p>当程序被执行的时候,Linux 会为其在内存中分配相应的空间以支撑程序的运行,如下图所示。</p>
<p><img src="/img/bVbj7AB?w=1294&h=570" alt="linux-memory.png" title="linux-memory.png"></p>
<p>在虚拟内存中,内存空间被分为多个区域。代码指令保存在文本段,已初始化的全局变量 <code>global</code> 保存在数据段,程序运行中动态申请的内存<code>malloc(10 * char())</code>放在堆中,而函数执行的时候则在栈中开辟空间运行。例如<code>main</code>函数便占有一个函数栈,其中的变量<code>i</code>和<code>ip</code>都保存在<code>main</code>的栈空间中。</p>
<p>函数的栈空间有个名字叫做 <code>栈帧</code>,下面就具体了解一下栈帧。</p>
<h2>栈帧</h2>
<p>下图是栈的结构。图中右侧是栈空间,其中有多个栈帧。从上往下由较早的栈帧到较新的栈帧,由于栈是从高地址往低地址生长的,所以最新的栈永远在最下面,即栈顶。</p>
<p><img src="/img/bVbj7B9?w=1550&h=792" alt="stack-frame.png" title="stack-frame.png"></p>
<p>图中有两个画出了具体结构的栈帧,分别是函数 A 和函数 B。函数 A 的栈帧最上面有一块省略号标识的区域,其中保存的是上一个栈帧的寄存器值以及函数 A 自己内部创建的局部变量。下面的参数 n 到参数 1 则是函数 A 要传给函数 B 的调用参数。那么函数 B 如何获取?答案是用寄存器。</p>
<p>CPU 计算时会把很多变量放在寄存器中,根据硬件体系的不同,寄存器数量和作用也不同。一般在 x86 32位中,寄存器 <code>%esp</code> 保存了栈指针的值,也就是栈顶,而 <code>%ebp</code> 作为当前栈帧的帧指针,也就是当前栈帧的底部,所以通过 <code>%esp</code> 和 <code>%ebp</code> 就可以知道当前栈帧的头跟尾。除了这两个寄存器,还有其它一些通用寄存器(<code>%eax</code>、<code>%edx</code>等),用于保存程序执行的临时值。</p>
<p>了解了寄存器的基本知识后,下面我们就可以知道函数 B 如何获取到函数 A 传给它的参数了。参数 1 的地址是 <code>%ebp + 8</code>,参数 2 的地址是 <code>%ebp + 12</code>,参数 n 的地址是 <code>%ebp + 4 + 4 * n</code>。相信大家已经看明白,通过帧指针往上找就可以取得这些参数,而这些参数之所以在这里当然是函数 A 预先准备好的,关于这一点下文会有例子。</p>
<p>另外在所有参数的最下面保存着 <code>返回地址</code>,这个是在函数 B 返回之后接下来要执行的指令的地址。</p>
<p>看了函数 A 之后,再看看函数 B。在函数 B 的栈帧最上面是 <code>被保存的 %ebp</code>,这个指的是函数 A 的帧指针,毕竟 <code>%ebp</code> 这个寄存器就一个,所以新的函数入栈的时候要先把老的保存起来,等函数出栈再恢复。在这个老的帧指针下面则是其它需要保存的寄存器变量以及函数 B 自己内部用到的局部变量。再往下是 <code>参数构造区域</code>,也就是函数 B 即将调用另一个函数,在这里先把参数准备好。可以看出,函数 B 与函数 A 的栈帧结构是类似的。</p>
<p>了解了栈帧的理论之后,大家可能会觉得很抽象,下面结合具体实例来看栈帧从产生到消亡的过程。</p>
<h2>函数调用实例</h2>
<p>下面图是函数 <code>caller</code> 的具体执行过程,左边是 C 代码,中间是汇编码,右边是对应的栈帧。</p>
<p><img src="/img/bVbj7LZ?w=1536&h=638" alt="caller-frame.png" title="caller-frame.png"></p>
<p>我们一行一行的来分析,看中间汇编码,上面三行绿色的:</p>
<pre><code>pushl %ebp // 保存旧的 %ebp
movl %esp, %ebp // 将 %ebp 设置为 %esp
subl $24, %esp // 将 %esp 减 24 开辟栈空间</code></pre>
<p>这三行其实是为栈帧做准备工作。第一行保存旧的 <code>%ebp</code>,此时新的栈空间还没有创建,但保存旧的 <code>%ebp</code> 的这一行空间将作为新栈帧的栈底,也就是帧指针,因此第二行将栈指针 <code>%esp</code>(永远指向栈顶)的值设置到 <code>%ebp</code> 上。 第三行将 <code>%esp</code> 下移 24 个字节,这一行其实就是为函数 <code>caller</code> 开辟栈空间了。从图中可以看出,下面的空间用于保存 <code>caller</code> 中的变量以及传给下个函数的参数。有部分空间未使用,这个是为了地址对齐,不影响我们的分析,可以忽略。</p>
<p>在开辟了栈帧之后,就开始执行 <code>caller</code> 内部的逻辑了,<code>caller</code> 首先创建了两个局部变量(<code>arg1</code>和<code>arg2</code>)。对应的汇编代码为 <code>movl $534, -4(%ebp); movl $1057, -8(%ebp)</code>,其中 <code>-4(%ebp)</code> 表示 <code>%ebp - 4</code> 的位置,也就是图中 <code>arg1</code> 所在的位置, <code>arg2</code> 的位置则是 <code>%ebp - 8</code> 的位置。这两行是把 <code>534</code> 和 <code>1057</code> 保存到传送到这两个位置上。</p>
<p>继续往下是这几行:</p>
<pre><code>leal -8(%ebp), %eax // 把 %ebp - 8 这个地址保存到 %eax
movl %eax, 4(%esp) // 把 %eax 的值保存到 %esp + 4 这个位置上
leal -4(%ebp), %eax // 把 %ebp - 4 这个地址保存到 %eax
movl %eax, ($esp) // 把 %eax 的值保存到 %esp 这个位置上</code></pre>
<p>第一行把 <code>%ebp - 8</code> 这个地址保存到 <code>%eax</code> 中,而 <code>%ebp - 8</code> 是 <code>arg2</code> 的地址,下一行把这个地址放到 <code>%esp + 4</code> 这个位置上,也就是图中 <code>&arg2</code> 的那个区域块。其实这一行是在为函数 <code>swap_add</code> 准备参数 <code>&arg2</code>,而下面两行则是准备参数 <code>&arg1</code>。</p>
<p>再下面一行是 <code>call swap_add</code>。这一行就是调用函数 <code>swap_add</code> 了,不过在这之前还需要把返回地址压到栈上,这里的返回地址是函数 <code>swap_add</code> 返回后要接着执行的代码的地址,也就是 <code>int diff = arg1 - arg2</code> 地址。</p>
<p>在调用 <code>swap_add</code> 后用到了其返回值 <code>sum</code> 继续进行计算,我们还不知道返回值是怎么拿到的。在这之前,我们先进入 <code>swap_add</code> 函数,下面是对应的代码执行图:</p>
<p><img src="/img/bVbj7TT?w=1536&h=690" alt="swap_add-frame.png" title="swap_add-frame.png"></p>
<p><code>swap_add</code> 对应的汇编代码的前三行与 <code>caller</code> 类似,同样是保存旧的帧指针,但是因为 <code>swap_add</code> 不需要保存额外的变量,只需要多用一个寄存器 <code>%ebx</code>,所以这里保存了这个寄存器的旧值,但是没有将 <code>%esp</code> 直接下移一段长度的操作。</p>
<p>接下来绿色的两行就是关键了:</p>
<pre><code>movl 8(%ebp), %edx // 从 %ebp + 8 取值保存到 %edx
movl 12(%ebp), %ecx // 从 %ebp + 12 取值保存到 %ecx</code></pre>
<p>这两行分别是从 <code>caller</code> 中保存参数 <code>&arg1</code> 和 <code>&arg2</code> 的地方取得地址值,并根据地址取得 <code>arg1</code>和<code>arg2</code> 的实际数值。</p>
<p>接下来的 4 行是交换操作,这里就不具体看每一行的逻辑了。</p>
<p>再下面一行 <code>addl %ebx, %eax</code> 是将返回值保存到寄存器 <code>%eax</code> 中,这里非常关键,<strong>函数 <code>swap_add</code> 的返回值保存在 <code>%eax</code> 中</strong>,一会儿 <code>caller</code> 就是从这个寄存器获取的。</p>
<p><code>swap_add</code> 的最后几行是出栈操作,将 <code>%ebx</code> 和 <code>%ebp</code> 分别恢复为 <code>caller</code> 中的值。最后执行 <code>ret</code> 返回到 <code>caller</code> 中。</p>
<p>下面我们继续回到 <code>caller</code> 中,刚才执行到 <code>call swap_add</code>,下面几行是执行 <code>int diff = arg1 - arg2</code>,结果保存在 <code>%edx</code> 中。</p>
<p>最后一行是计算 <code>sum * diff</code>,对应的汇编代码为 <code>imull %edx, %eax</code>。这里是把 <code>%edx</code> 和 <code>%eax</code> 的值相乘并且把结果保存到 <code>%eax</code> 中。在上面的分析中,我们知道 <code>%eax</code> 保存着 <code>swap_add</code> 的返回值,这里还是从 <code>%eax</code> 中取出返回值进行计算,并且把结果继续保存到 <code>%eax</code> 中,而这个值又是 <code>caller</code> 的返回值,这样调用 <code>caller</code> 的函数也可以从这个寄存器中获取返回值了。</p>
<p><code>caller</code> 函数的最后一行汇编代码是 <code>ret</code>,这会销毁 <code>caller</code> 的栈帧并且恢复相应寄存器的旧值。到此,<code>caller</code> 和 <code>swap_add</code> 这个函数的调用过程就全部分析完了。</p>
<h2>总结</h2>
<p>本文详细分析了函数调用过程中栈帧变化的过程,对于开头提出的几个疑问也都有了解答。函数栈的实现在常规的开发中几乎不会涉及到,但是学习其中的原理有利于更深入地理解内存以及编程语言的奥秘。</p>
<p><strong>参考</strong></p>
<ul><li>《深入理解计算机系统》</li></ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
从 AbstractQueuedSynchronizer 理解 ReentrantLock
https://segmentfault.com/a/1190000015848707
2018-08-01T17:21:28+08:00
2018-08-01T17:21:28+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
11
<h2>简介</h2>
<p>Java 并发编程离不开锁, <code>Synchronized</code> 是常用的一种实现加锁的方式,使用比较简单快捷。在 Java 中还有另一种锁,即 Lock 锁。 Lock 是一个接口,提供了超时阻塞、可响应中断以及公平非公平锁等特性,相比于 <code>Synchronized</code>,Lock 功能更强大,可以实现更灵活的加锁方式。</p>
<p>Lock 的主要实现类是 <code>ReentrantLock</code>,而 <code>ReetrantLock</code> 中具体的实现方式是利用另外一个类 <code>AbstractQueuedSynchronizer</code>,所有的操作都是委托给这个类完成。<code>AbstractQueuedSynchronizer</code> 是 Lock 锁的重要组件,本文从 <code>AbstractQueuedSynchronizer</code> 来分析 <code>ReetrantLock</code> 的实现原理。</p>
<h2>基本用法</h2>
<p>先看一下 Lock 的基本用法:</p>
<pre><code class="java">Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}</code></pre>
<p><code>lock.lock()</code> 即是加锁, <code>lock.unolck()</code> 是释放锁,为了保证所能够释放,<code>unlock()</code> 应该放到 <code>finally</code> 中。</p>
<p>下面分别从 <code>lock()</code> 和 <code>unlock()</code> 方法来分析加锁和解锁到底做了什么。</p>
<h2>lock</h2>
<p>下面是 <code>lock()</code> 的代码:</p>
<pre><code class="java"> public void lock() {
sync.lock();
}</code></pre>
<p>可以看到,只是简单调用了 <code>sync</code> 对应的 <code>lock()</code> 方法。那么这个 <code>sync</code> 是什么呢?其实这个就是 <code>AbstractQueuedSynchronizer</code> 的实现类。可以看一下 <code>ReentrantLock</code> 的构造方法:</p>
<pre><code class="java"> /**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}</code></pre>
<p><code>ReentrantLock</code> 有两个方法,主要目的是选择是<strong>公平锁</strong>还是<strong>非公平锁</strong>。公平锁指的是先来后到,先争抢锁的线程先获得锁,而非公平锁则不一定。<code>ReentrantLock</code> 默认使用的是非公平锁,也可以通过构造参数选择公平锁。选择哪个锁其实是生成了一个对象并赋值给变量 <code>sync</code>,下面是涉及到的代码:</p>
<pre><code>
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
// 非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Sync object for fair locks
* 公平锁
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}</code></pre>
<p>在 <code>ReentrantLock</code> 中有一个抽象的内部类 <code>Sync</code>,继承于 <code>AbstractQueuedSynchronizer</code> 并实现了一些方法。另有两个类 <code>FairSync</code> 和 <code>NoFairSync</code> 继承了 <code>Sync</code>,它们自然就是公平锁以及非公平锁的实现。下面分析将从公平锁出发,非公平锁与公平锁差别并不是很多。</p>
<p>公平锁 <code>FairSync</code> 加锁的代码如下:</p>
<pre><code class="java"> final void lock() {
acquire(1);
}</code></pre>
<p>只有一行,调用了 <code>acquire</code>,这是 <code>AbstractQueuedSynchronizer</code> 中的一个方法,代码如下:</p>
<pre><code class="java"> public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}</code></pre>
<p><code>acquire</code> 的实现也很短,不多在其中却包含了加锁的具体实现,关键就在内部调用的几个方法中。</p>
<p>为了理解加锁和解锁的过程,下面具体介绍一下 <code>AbstractQueuedSynchronizer</code>(以下简称 <code>AQS</code>)。</p>
<h2>AbstractQueuedSynchronizer</h2>
<p><code>AQS</code> 中使用一个同步队列来实现线程同步状态的管理,当一个线程获取锁失败的时候, <code>AQS</code>将此线程构造成一个节点(<code>Node</code>)并加入同步队列并且阻塞线程。当锁释放时,会从同步队列中将第一个节点唤醒并使其再次获取锁。</p>
<p>同步队列中的节点用来保存获取锁失败的线程的相关信息,包含如下属性:</p>
<pre><code class="java">static final class Node {
/** Marker to indicate a node is waiting in shared mode */
// 标识共享模式
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
// 标识独占模式
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled. */
// 线程取消
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking. */
// 需要唤醒后继节点
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition. */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate.
*/
static final int PROPAGATE = -3;
// 节点状态,为上面的几个状态之一
volatile int waitStatus;
// 前置节点
volatile Node prev;
// 后继节点
volatile Node next;
// 节点所表示的线程
volatile Thread thread;
Node nextWaiter;
...
}</code></pre>
<p><code>Node</code> 是 AQS 的内部类,其中包含一些属性标识一个阻塞线程的节点,包括是独占模式还是共享模式、节点的状态、前驱结点、后继结点以及节点所代表的线程。</p>
<p>同步队列是一个双向列表,在 AQS 中有这样几个属性:</p>
<pre><code class="java"> /**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
// 头结点
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
// 尾节点
private transient volatile Node tail;
/**
* The synchronization state.
*/
// 锁的状态
private volatile int state;</code></pre>
<p>其中,<code>head</code> 和 <code>tail</code> 分别指向同步队列的头结点和尾节点,<code>state</code> 标识锁当前的状态,为 0 时表示当前锁未被占用,大于 1 表示被占用,之所以是大于 1 是因为锁可以重入,每重入一次增加 1。同步队列的结构大致如下图:</p>
<p><img src="/img/bVbeE6u?w=512&h=92" alt="lock_queue" title="lock_queue"></p>
<p>了解了同步队列后,下面具体看看加锁和解锁的过程。</p>
<h3>加锁</h3>
<pre><code class="java"> final void lock() {
acquire(1);
}
public final void acquire(int arg) {
// 加锁的主要代码
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}</code></pre>
<p>主要逻辑其实就是一行代码:<code>if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</code>,<code>tryAcquire</code> 是尝试获取一下锁,为什么说是尝试呢?看代码:</p>
<pre><code class="java">protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 获取当前状态
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 成功获取到锁
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}</code></pre>
<p><code>tryAcquire</code> 可以分为三条分支:</p>
<ol>
<li>当前锁未被占用(<code>getState() == 0</code>),则判断是否有前驱结点,没有的话就用 CAS 加锁(<code>compareAndSetState(0, acquires)</code>),加锁成功则调用 <code>setExclusiveOwnerThread(current)</code> 标示一下并返回 <code>true</code>。</li>
<li>当前线程已经获取过这个锁,则此时是重入,改变 <code>state</code> 的计数即可,返回 <code>true</code> 表示加锁成功。</li>
<li>如果不是上面两种情况,那么说明锁被占用或者 CAS 没有抢过其它线程,则需要进入同步队列,返回 <code>false</code> 表示尝试加锁失败。</li>
</ol>
<p>回到 <code>if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</code> 这一行,如果 <code>tryAcquire(arg)</code> 返回 <code>false</code> 将会执行 <code>acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</code>。先看一下 <code>addWaiter(Node.EXCLUSIVE)</code> ,这个方法的代码如下所示:</p>
<pre><code class="java">private Node addWaiter(Node mode) {
// 将线程包装成 Node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 尾节点为空
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}</code></pre>
<p>其中主要逻辑是将当前线程包装为一个 <code>Node</code> 节点并加入同步队列。如果尾节点为空,则用 CAS 设置尾节点,如果入队失败则调用 <code>enq(node)</code>,这个方法内部是一个循环,利用自旋 CAS 把节点加入同步队列,具体代码就不分析了。</p>
<p>在节点加入队列之后,执行的是 <code>acquireQueued</code> 方法,代码如下:</p>
<pre><code class="java">final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 这是一个无限循环
for (;;) {
final Node p = node.predecessor();
// 如果前驱节点是 head,则尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 获取锁失败则进入判断是否要进入睡眠
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}</code></pre>
<p><code>acquireQueued</code> 实现了线程的睡眠与唤醒。在内部是一个无限循环,每次获取前驱节点,如果前驱结点是 <code>HEAD</code>,那么尝试去获取锁,获取成功则将此节点变为新的头结点并将原先的头结点出队。如果前驱节点不是头结点或者获取锁失败,那么就会进入 <code>shouldParkAfterFailedAcquire</code> 方法,判断是否进入睡眠,如果这个方法返回 <code>true</code>,则调用 <code>parkAndCheckInterrupt</code> 让线程进入睡眠状态。下面是 <code>parkAndCheckInterrupt</code> 的代码:</p>
<pre><code class="java">private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 线程在这一步进入阻塞状态
return Thread.interrupted();
}</code></pre>
<p>对于 <code>shouldParkAfterFailedAcquire</code> 来说,如果前驱节点正常,那么会返回 <code>true</code>,表示当前线程应该挂起,如果前驱结点取消了排队,那么当前线程有机会抢锁,此时返回 <code>false</code>,并继续 <code>acquireQueued</code> 中的循环。</p>
<h3>解锁</h3>
<p>相比于加锁,解锁稍微简单一点,看一下 <code>unlock</code> 的代码:</p>
<pre><code class="java">public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 尝试解锁
if (tryRelease(arg)) {
// 解锁成功
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}</code></pre>
<p>首先调用 <code>tryRelease</code> 解锁,如果解锁成功则唤醒后继结点,返回值表示是否成功释放锁。那为什么会解锁不成功,其实是因为重入,看一下 <code>tryRelease</code> 的代码:</p>
<pre><code class="java">protected final boolean tryRelease(int releases) {
// 更新 state 计数值
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 是否完全释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}</code></pre>
<p>将 <code>state</code> 减去对应的值,如果 <code>state == 0</code>,那么说明锁已经完全释放。</p>
<p>在 <code>release</code> 中,如果锁已经完全释放,那么将调用 <code>unparkSuccessor</code> 唤醒后继节点,唤醒的节点所代表的线程阻塞在 <code>parkAndCheckInterrupt</code> 中:</p>
<pre><code class="java">private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 线程在这一步进入阻塞状态
return Thread.interrupted();
}</code></pre>
<p>线程被唤醒后,将继续 <code>acquireQueued</code> 中的循环,尝试获取锁。</p>
<h2>总结</h2>
<p>本文简要分析了 Lock 锁的原理,主要是利用 <code>AbstractQueuedSynchronizer</code>这个关键的类。AQS 的核心在于使用 CAS 更新锁的状态,并利用一个同步队列将获取锁失败的线程进行排队,当前驱节点解锁后再唤醒后继节点,是一个几乎纯 Java 实现的加锁与解锁。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
ELF 文件结构及静态链接
https://segmentfault.com/a/1190000015368273
2018-06-23T21:57:20+08:00
2018-06-23T21:57:20+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
3
<h2>简介</h2>
<p>C/C++ 代码在变成可执行文件之前需要经历预处理、编译、汇编以及链接这几个步骤,最终生成的可执行文件包含了能够被系统处理的机器码。可执行文件必须按照特定的格式进行组织才能被系统加载、执行,所以可执行文件是特定于操作系统的。对于 Linux 来说是 ELF(Executable Linkable Format) 格式的文件,Windows 是 PE(Portable) 格式。对于 Java 代码,编译生成的 Class 文件也是有着特定的格式,才能被 JVM 执行。</p>
<p>一个程序一般由多个文件组成,文件之间会有变量和函数的引用,每个文件各自编译生成中间文件后必须经过链接才能生成最终的可执行文件。根据链接方式的不同可以分为静态链接和动态链接,静态链接是在链接期间重定位所有的符号引用,而动态链接则是在装载或者执行期间进行。</p>
<p>本文主要分析 Linux 下 ELF 文件的格式以及静态链接的过程。</p>
<h2>目标文件的格式</h2>
<p>源代码被编译生成的文件叫做目标,目标文件与可执行文件的格式是类似的,只是还没有经历链接,其中包含的有些地址还没有被调整。</p>
<p>目标文件中包含机器码、数据、符号表以及调试信息等,这些属性按照不同的段(Section ) 进行存储。段就是一定长度的的区域,不同的属性放在不同名字的段,具体如下所示:</p>
<p><img src="/img/bVbcCJp?w=600&h=382" alt="c_code_storage.jpg" title="c_code_storage.jpg"></p>
<p>可以看出,代码放在了名为 <code>.text</code> 的段,变量 <code>global_init_var</code> 和 <code> static_var</code> 放在了名为 <code>.data</code> 的段,变量 <code>global_uninit_var</code> 和 <code> static_var</code> 放在名为 <code>.bss</code> 的段。<code>.bss</code> 段存放的是未初始化的全局变量和局部静态变量。</p>
<p>上图的 EFL 文件除了几个段,还有文件头(File Header),其中包含了文件是否可执行、是静态链接还是动态链接以及目标硬件、操作系统等信息,还包括一个段表,段表是一个数组结构,描述了文件中各个段在文件中的偏移位置及段的属性等。用 <code>readelf -h</code> 可以读取上面代码编译后目标文件的头信息,如下图:</p>
<p><img src="/img/bVbcCMp?w=687&h=401" alt="elf_header.png" title="elf_header.png"></p>
<p>从上图可以看到,其中包含了文件的魔数(Magic) 、字长(class)、CPU 类型等信息,如果是可执行文件,还包括程序的入口地址。<code>Start of section headers</code> 的值是段表的偏移量。</p>
<p>目标文件中除了上面介绍的代码段和数据段,还有很多其它段,<code>readelf -S</code> 命令可以查看段表的信息,如下图:</p>
<p><img src="/img/bVbcCOk?w=683&h=686" alt="elf_sections.png" title="elf_sections.png"></p>
<p>可以看出,上面的目标文件总共有 12 个段,第一个为无效段,实际上是 11 个段。其中有字符串表 <code>.strtab</code>、符号表 <code>.symtab</code> 以及注释信息 <code>.comment</code> 等。还有一个段是 <code>.rela.txt</code> 段,这个是重定位表,在静态链接过程中需要用到。</p>
<h2>静态链接</h2>
<p>在了解了 ELF 文件的结构之后,接下来介绍静态链接的过程。以下面的代码为例:</p>
<pre><code class="c">/* a.c */
extern int shared;
int main()
{
int a = 100;
swap(&a, &shared);
}
/* b.c */
int shared = 1;
void swap(int *a, int *b)
{
*a ^= *b ^= *a ^= *b;
}</code></pre>
<p>在上面的代码中,b.c 定义了全局符号,分别是变量 <code>shared</code> 和函数 <code>swap</code>,<code>a.c</code> 定义了一个全局符号 <code>main</code>。在 a.c 中引用了 b.c 里面的 <code>shared</code> 和 <code>swap</code>。用 <code>gcc -c -fno-stack-protector a.c b.c </code> 编译这两个文件之后(<code>-fno-stack-protector</code> 是关闭堆栈保护功能),生成了两个目标文件 <code>a.o</code> 和 <code>b.o</code>,下一步就是要把这两个文件链接在一起,形成最终的可执行文件。</p>
<h3>空间与地址分配</h3>
<p>静态链接的第一步是把多个目标文件进行合并,一般采用相似段合并的方式。通过扫描所有的输入目标文件,并且获得它们各个段的长度、属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。多个目标文件合并后如下图所示:</p>
<p><img src="/img/bVbcD29?w=600&h=564" alt="obj_merge.jpg" title="obj_merge.jpg"></p>
<h3>符号地址的确定</h3>
<p>利用上一步收集到的数据,进行符号解析与重定位、调整代码中的地址等。利用命令 <code>ld a.o b.o -e main -o ab</code> 将 <code>a.o</code> 和 <code>b.o</code> 链接(<code>-e main</code> 是将 <code>main</code> 函数作为程序的入口),生成可执行文件 <code>ab</code>。链接前后段的地址信息如下所示:</p>
<p><img src="/img/bVbcD4n?w=749&h=949" alt="a.o_b.o_ab_address.png" title="a.o_b.o_ab_address.png"></p>
<p>上图是 <code>a.o</code>、<code>b.o</code> 以及链接后的 <code>ab</code> 的地址信息。其中 <code>Size</code> 是段的大小,<code> VMA</code> 是虚拟地址。对于 <code>a.o</code> 和 <code>b.o</code> 的 <code>.text</code> 段来说,大小分别是 <code>0000002c</code> 和 <code>0000004b</code>, 加起来正好是 <code>ab</code> 的 <code>.text</code> 段的大小 <code>00000077</code>。另外, <code>a.o</code> 和 <code>b.o</code> 的 VMA 都是 <code>00000000</code>,此时它们还没有分配地址,而在 <code>ab</code> 中,地址变为 <code>00000000004000e8</code>,这就是分配的虚拟地址,当 <code>ab</code> 被加载到内存中后, <code>.text</code> 段的起始地址便是这个。</p>
<p>段的地址被确定后,内部函数和变量的地址也就确定了,因为在每个段内,符号的表示是一个相对于段起始位置的偏移量。当段的起始位置被确定后,每个符号只要在偏移量的基础上加上这个起始位置的地址就行。但是对于引用的外部符号来说,它们的地址还不得知,需要经过符号解析和重定位的过程。</p>
<h3>符号解析与重定位</h3>
<p>在 a.c 中引用了变量 <code>shared</code> 和函数 <code>swap</code> ,单独编译 a.c 的时候并不知道 b.c 这个文件,所以在 a.o 中,用到 <code>shared</code> 的地方用 <code>0</code> 地址代替,等到链接阶段,能够确定这个变量的地址了,再把地址进行调整。</p>
<p>这里的问题是链接器如何知道哪些指令需要被调整呢?这就用到上面提到过的重定位表,命令 <code>objdump -r a.o</code> 可以查看 <code>a.o</code> 中的重定位表,如下图:</p>
<p><img src="/img/bVbcD7x?w=584&h=264" alt="relocation_table.png" title="relocation_table.png"></p>
<p>每一个需要被重定位的地方叫做一个重定位入口,可以看到,<code>a.o</code> 中需要重定位的两个符号 <code>shared</code> 和 <code>swap</code>。将重定位入口的地址进行修正,才能完成链接过程,最终生成的可执行文件便可以被系统正常运行。</p>
<h2>总结</h2>
<p>代码从文本形式到最终的可执行文件需要经历多个过程,其中链接主要做的是多个目标文件的合并以及符号的解析与重定位,最终生成特定格式的可执行文件。本文大概地介绍了 ELF 文件的结构和静态链接的主要步骤,更详细的内容可以查看相关书籍深入了解。</p>
<p><strong>参考</strong></p>
<ul>
<li>《程序员的自我修养:链接、装载与库》</li>
<li>《深入理解计算机系统》</li>
</ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
volatile 的用法及原理
https://segmentfault.com/a/1190000014912707
2018-05-17T23:49:00+08:00
2018-05-17T23:49:00+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
3
<h2>简介</h2>
<p>在 Java 并发编程中,volatile 是经常用到的一个关键字,它可以用于保证不同的线程共享一个变量时每次都能获取最新的值。volatile 具有锁的部分功能并且性能比锁更好,所以也被称为轻量级锁。下面具体分析 volatile 的用法及原理,涉及到内存模型、可见性、重排序以及伪共享等方面。</p>
<h2>内存模型</h2>
<p>在深入理解 volatile 之前,先了解一些计算机的内存模型。当 CPU 执行运算的时候,需要从内存中取数据,由于 CPU 的运算速度远远快于内存的读取速度,所以 CPU 需要等数据,这个过程就浪费了 CPU 的时间。为了提高效率, 在 CPU 和内存之间会有缓存(一般有三级缓存),缓存的读写速度高于内存,容量也会比内存小得多。当 CPU 读数据的时候会先从缓存中读,如果缓存未命中则会去内存读,并把数据放到缓存中,写数据的时候也会先写缓存,在适当的时候再将缓存中的数据刷新到内存中。</p>
<p>缓存的使用提高了 CPU 的运行效率,但是对于多核处理器会有一些问题。如果某个内存地址的数据同时被两个 CPU 缓存,其中一个 CPU 修改了这个地址的值,无论这个值是写入到了缓存中还是被刷新到了内存中,只要另一个 CPU 依然使用其缓存中的值,那还是旧值。因此对于多线程来说,需要一些手段来保证数据的一致性。</p>
<p>对于 Java 来说,程序运行在 JVM 上,JVM 提供了类似的内存抽象模型,如下图所示。</p>
<p><img src="/img/bVbaN0D?w=340&h=372" alt="JMM.png" title="JMM.png"></p>
<p>每个线程有自己的工作内存,相当于缓存,所有的线程共享主内存,相当于系统中的内存。线程之间往往会有共享变量,为了保证共享变量的可见性,需要采用 java 提供的并发技术。对于单个变量的可见性来说,volatile 是一种有效的机制。</p>
<h2>内存可见性</h2>
<p>先看下面的一段代码:</p>
<pre><code class="java"> int a = 1;
boolean flag = false;
int b = 3;
// 线程1
a = 2;
flag = true;
// 线程2
if (flag) {
b = a;
}</code></pre>
<p>上面的代码如果线程 1 执行后,线程 2 中的 flag 能立刻看到 <code>flag</code> 的新值吗?根据上面介绍的 Java 内存模型可以知道,答案是不一定。那么如何保证当线程 1 更新 <code>flag</code> 之后,线程 2 能够读取到最新的值呢?其实很简单,只需要给 <code>flag</code> 添加 <code>volatile</code> 修饰符。</p>
<p>那么 volatile 是如何做到的呢? 我们想一想,根据 Java 内存模型,要实现这种功能该怎么做?应该是两步:1. 当线程 1 写 volatile 变量的时候,将这个值从缓存刷新到主内存中 2. 当线程 2 读取 volatile 变量的时候,将本地的工作内存置为无效,从主内存读取新值。</p>
<p>其实 volatile 的实现正是以上的原理,对于一个 volatile 变量的写操作会有一行以 <code>lock</code> 作为前缀的汇编代码。这个指令在多核处理器下会引发两件事:</p>
<ol>
<li>将当前处理器缓存行的数据写回到主内存</li>
<li>这个写回内存的操作会使在其它 CPU 里缓存了该内存地址的数据无效</li>
</ol>
<p><code>lock</code> 前缀的指令会锁住系统总线或者是缓存,目的是保证在同一时间只有一个 CPU 会修改数据,使得修改具有原子性。根据 <strong>缓存一致性</strong> 协议, CPU 通过嗅探技术保证它的内部缓存、内存和其它处理器的缓存的数据的一致性。例如,一个处理器检测其它处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同的内存地址时,强制执行缓存行填充。</p>
<h2>禁止重排序</h2>
<p>volatile 除了保证内存可见性,还可以禁止重排序。在了解重排序之前,先看一段代码:</p>
<pre><code class="java">class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}</code></pre>
<p>上面的代码一看就是单例模式,并且使用了双重加锁提高效率。稍微有经验的程序员还会发现,上面的写法是不正确的,应该给 <code>instance</code> 添加 <code>volatile</code> 修饰。那么为什么需要 <code>volatile</code> 呢?</p>
<p>其实问题出在 <code>instance = new Singleton();</code> 这一行,这里是创建 <code>Singleton</code> 对象的地方,其实这里可以看成三个步骤:</p>
<ol>
<li>memory = allocate(); //1: 分配对象的内存空间</li>
<li>ctorInstance(memory); //2: 初始化对象</li>
<li>instance = memory; //3: 设置 instance 指向刚分配的内存地址</li>
</ol>
<p>上面的伪代码可能会被重排序。什么是重排序?编译器以及处理器有时候会为了执行的效率改变代码的执行顺序,这个被称为重排序。上面的三个步骤可能会被重排序为下面的步骤:</p>
<ol>
<li>memory = allocate(); //1: 分配对象的内存空间</li>
<li>instance = memory; //2: 设置 instance 指向刚分配的内存地址<br>// 注意:此时对象还没有被初始化</li>
<li>ctorInstance(memory); //3: 初始化对象</li>
</ol>
<p>在这种情况下,当一个线程执行到 <code>instance = memory;</code> 的时候,对象还没有被初始化,另一个线程也调用了 <code>getInstance</code> 方法,发现 <code>instance</code> 引用不为 <code>null</code>,就会认为这个对象已经创建好了,从而使用了未初始化的对象。</p>
<p>为什么 volatile 可以避免上面的问题?其实是因为 volatile 会禁止重排序,方法是插入了<strong>内存屏障</strong>,具体原理较复杂,这里就不深入分析了。</p>
<h2>伪共享</h2>
<p>CPU 缓存是以缓存行为单位进行存取的,一般一个缓存行是 64 字节,如果两个 volatile 变量被缓存在同一个缓存行,并且有多个 CPU 缓存了同一行数据,那么会出现 <strong>伪共享</strong> 的问题,造成性能问题。</p>
<p>例如,CPU A 以及 CPU B 都在同一个缓存行缓存了共享变量 <code>X</code> 和 <code>Y</code>,如果 CPU A 修改了 <code>X</code>,那么 CPU B 中的缓存行也就失效了,如果 CPU 只是需要读取 <code>Y</code> ,却因为 <code>X</code> 使得整个缓存行都要重新读取,这就不划算了,这叫做伪共享。</p>
<p>解决伪共享主要是让不同的 volatile 变量不要缓存到同一个缓存行,可以利用填充技术来解决,具体可以参考这篇文章:<a href="https://link.segmentfault.com/?enc=nKB%2BuDi%2B4vtXFOtC2Z4rNQ%3D%3D.e4tX4OdHwDzoZT1EF6wghy1Qe6cVXKOmXtHL7ICOIScebMPjJ%2Fw9wmj8n6tK7pA4" rel="nofollow">Java中的伪共享以及应对方案</a></p>
<h2>总结</h2>
<p>volatile 作为一个轻量级的锁可以实现内存可见性以及禁止重排序,常用于修饰标记变量以及双重加锁的场景等。需要注意的是,volatile 用于保证一个变量的可见性,但是对于 <code>i++</code> 这种复合操作是无法保证原子性的。另外,注意伪共享问题可以进一步提升性能。</p>
<p><strong>参考</strong></p>
<ul><li>《Java 并发编程的艺术》</li></ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
OkHttp 源码解析(三):连接池
https://segmentfault.com/a/1190000014044624
2018-03-28T16:32:46+08:00
2018-03-28T16:32:46+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
5
<h2>简介</h2>
<p>上一篇文章(<a href="https://segmentfault.com/a/1190000013655431">OkHttp 源码解析(二):建立连接</a>)分析了 OkHttp 建立连接的过程,主要涉及到的几个类包括 <code>StreamAllocation</code>、<code>RealConnection</code> 以及 <code>HttpCodec</code>,其中 <code>RealConnection</code> 封装了底层的 Socket。Socket 建立了 TCP 连接,这是需要消耗时间和资源的,而 OkHttp 则使用连接池来管理这里连接,进行连接的重用,提高请求的效率。OkHttp 中的连接池由 <code>ConnectionPool</code> 实现,本文主要是对这个类进行分析。</p>
<h2>get 和 put</h2>
<p>在 <code>StreamAllocation</code> 的 <code>findConnection</code> 方法中,有这样一段代码:</p>
<pre><code class="java">// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}</code></pre>
<p><code>Internal.instance.get</code> 最终是从 <code>ConnectionPool</code> 取得一个<code>RealConnection</code>, 如果有了则直接返回。下面是 <code>ConnectionPool</code> 中的代码:</p>
<pre><code class="java">@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) {
streamAllocation.acquire(connection);
return connection;
}
}
return null;
}</code></pre>
<p><code>connections</code> 是 <code>ConnectionPool</code> 中的一个队列:</p>
<pre><code class="java">private final Deque<RealConnection> connections = new ArrayDeque<>();
</code></pre>
<p>从队列中取出一个 <code>Connection</code> 之后,判断其是否能满足重用的要求:</p>
<pre><code class="java">public boolean isEligible(Address address, @Nullable Route route) {
// If this connection is not accepting new streams, we're done.
if (allocations.size() >= allocationLimit || noNewStreams) return false;
// If the non-host fields of the address don't overlap, we're done.
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// If the host exactly matches, we're done: this connection can carry the address.
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
// 省略 http2 相关代码
...
}
boolean equalsNonHost(Address that) {
return this.dns.equals(that.dns)
&& this.proxyAuthenticator.equals(that.proxyAuthenticator)
&& this.protocols.equals(that.protocols)
&& this.connectionSpecs.equals(that.connectionSpecs)
&& this.proxySelector.equals(that.proxySelector)
&& equal(this.proxy, that.proxy)
&& equal(this.sslSocketFactory, that.sslSocketFactory)
&& equal(this.hostnameVerifier, that.hostnameVerifier)
&& equal(this.certificatePinner, that.certificatePinner)
&& this.url().port() == that.url().port();
}</code></pre>
<p>如果这个 <code>Connection</code> 已经分配的数量超过了分配限制或者被标记为不能再分配,则直接返回 <code>false</code>,否则调用 <code>equalsNonHost</code>,主要是判断 <code>Address</code> 中除了 <code>host</code> 以外的变量是否相同,如果有不同的,那么这个连接也不能重用。最后就是判断 <code>host</code> 是否相同,如果相同那么对于当前的 <code>Address</code> 来说, 这个 <code>Connection</code> 便是可重用的。从上面的代码看来,<code>get</code> 逻辑还是比较简单明了的。</p>
<p>接下来看一下 <code>put</code>,在 <code>StreamAllocation</code> 的 <code>findConnection</code> 方法中,如果新创建了 <code>Connection</code>,则将其放到连接池中。</p>
<pre><code class="java">Internal.instance.put(connectionPool, result);</code></pre>
<p>最终调用的是 <code>ConnectionPool#put</code>:</p>
<pre><code class="java">void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}</code></pre>
<p>首先判断其否启动了清理线程,如果没有则将 <code>cleanupRunnable</code> 放到线程池中。最后是将 <code>RealConnection</code> 放到队列中。</p>
<h2>cleanup</h2>
<p>线程池需要对闲置的或者超时的连接进行清理,<code>CleanupRunnable</code> 就是做这件事的:</p>
<pre><code class="java">private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};</code></pre>
<p><code>run</code> 里面有个无限循环,调用 <code>cleanup</code> 之后,得到一个时间 <code>waitNano</code>,如果不为 -1 则表示线程的睡眠时间,接下来调用 <code>wait</code> 进入睡眠。如果是 -1,则表示当前没有需要清理的连接,直接返回即可。</p>
<p>清理的主要实现在 <code>cleanup</code> 方法中,下面是其代码:</p>
<pre><code class="java">long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
// 1. 判断是否是空闲连接
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
// 2. 判断是否是最长空闲时间的连接
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
// 3. 如果最长空闲的时间超过了设定的最大值,或者空闲链接数量超过了最大数量,则进行清理,否则计算下一次需要清理的等待时间
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
// 3. 关闭连接的socket
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}</code></pre>
<p>清理的逻辑大致是以下几步:</p>
<ol>
<li>遍历所有的连接,对每个连接调用 <code>pruneAndGetAllocationCount</code> 判断其是否闲置的连接。如果是正在使用中,则直接遍历一下个。</li>
<li>对于闲置的连接,判断是否是当前空闲时间最长的。</li>
<li>对于当前空闲时间最长的连接,如果其超过了设定的最长空闲时间(5分钟)或者是最大的空闲连接的数量(5个),则清理此连接。否则计算下次需要清理的时间,这样 <code>cleanupRunnable</code> 中的循环变会睡眠相应的时间,醒来后继续清理。</li>
</ol>
<p><code>pruneAndGetAllocationCount</code> 用于清理可能泄露的 <code>StreamAllocation</code> 并返回正在使用此连接的 <code>StreamAllocation</code> 的数量,代码如下:</p>
<pre><code class="java">private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List<Reference<StreamAllocation>> references = connection.allocations;
for (int i = 0; i < references.size(); ) {
Reference<StreamAllocation> reference = references.get(i);
if (reference.get() != null) {
i++;
continue;
}
// We've discovered a leaked allocation. This is an application bug.
// 如果 StreamAlloction 引用被回收,但是 connection 的引用列表中扔持有,那么可能发生了内存泄露
StreamAllocation.StreamAllocationReference streamAllocRef =
(StreamAllocation.StreamAllocationReference) reference;
String message = "A connection to " + connection.route().address().url()
+ " was leaked. Did you forget to close a response body?";
Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
references.remove(i);
connection.noNewStreams = true;
// If this was the last allocation, the connection is eligible for immediate eviction.
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
return references.size();
}</code></pre>
<p>如果 <code>StreamAllocation</code> 已经被回收,说明应用层的代码已经不需要这个连接,但是 <code>Connection</code> 仍持有 <code>StreamAllocation</code> 的引用,则表示<code>StreamAllocation</code> 中 <code>release(RealConnection connection)</code> 方法未被调用,可能是读取 <code>ResponseBody</code> 没有关闭 I/O 导致的。</p>
<h2>总结</h2>
<p>OkHttp 中的连接池主要就是保存一个正在使用的连接的队列,对于满足条件的同一个 host 的多个连接复用同一个 <code>RealConnection</code>,提高请求效率。此外,还会启动线程对闲置超时或者超出闲置数量的 <code>RealConnection</code> 进行清理。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
OkHttp 源码解析(二):建立连接
https://segmentfault.com/a/1190000013655431
2018-03-11T16:09:51+08:00
2018-03-11T16:09:51+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
5
<h2>简介</h2>
<p>上一篇文章(<a href="https://segmentfault.com/a/1190000012656606">OkHttp源码解析(一):基本流程</a>)介绍了 OkHttp 的基本流程,包括 Request 的创建、Dispatcher 对 Request 的调度以及 Interceptor 的使用。OkHttp 中默认会添加 RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor 以及 CallServerInterceptor 这几个拦截器。本文主要看一下 RetryAndFollupInterceptor 并引出建立连接相关的分析。</p>
<h2>RetryAndFollowUpInterceptor</h2>
<p>Interceptor 最主要的代码都在 <code>intercept</code> 中,下面是 <code>RetryAndFollowUpInterceptor#intercept</code> 中的部分代码:</p>
<pre><code class="java">@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace); // 1
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release(); // 2
throw new IOException("Canceled");
}
...
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null); // 3
...
}</code></pre>
<p>上面注释 1 处创建了一个 <code>StreamAllocation</code> 对象,注释 2 处 调用了其 <code>release</code> 方法,注释 3 处则把这个对象传给了下一个 Interceptor。<code>StreamAlloction</code> 这个类很重要,下面就看一下它的用途。</p>
<h2>StreamAlloction</h2>
<p><code>StreamAllocation</code> 从名字上看是<strong>流分配器</strong>,其实它是统筹管理了几样东西,注释写的非常清楚:</p>
<pre><code> /**
* This class coordinates the relationship between three entities:
*
* <ul>
* <li><strong>Connections:</strong> physical socket connections to remote servers. These are
* potentially slow to establish so it is necessary to be able to cancel a connection
* currently being connected.
* <li><strong>Streams:</strong> logical HTTP request/response pairs that are layered on
* connections. Each connection has its own allocation limit, which defines how many
* concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
* at a time, HTTP/2 typically carry multiple.
* <li><strong>Calls:</strong> a logical sequence of streams, typically an initial request and
* its follow up requests. We prefer to keep all streams of a single call on the same
* connection for better behavior and locality.
* </ul>
</code></pre>
<p>简单来说, <code>StreamAllocation</code> 协调了 3 样东西:</p>
<ul>
<li>
<code>Connections</code> : 物理的 socket 连接</li>
<li>
<code>Streams</code>:逻辑上的 HTTP request/response 对。每个 <code>Connection</code> 有个变量 <code>allocationLimit</code> ,用于定义可以承载的并发的 <code>streams</code> 的数量。HTTP/1.x 的 <code>Connection</code> 一次只能有一个 <code>stream</code>, HTTP/2 一般可以有多个。</li>
<li>
<code>Calls</code> : <code>Streams</code> 的序列。一个初始的 <code>request</code> 可能还会有后续的 <code>request</code>(如重定向)。OkHttp 倾向于让一个 <code>call</code> 所有的 <code>streams</code> 运行在同一个 <code>connection</code> 上。</li>
</ul>
<p><code>StreamAllocation</code> 提供了一些 API 来释放以上的资源对象。 在 <code>RetryAndFollowUpInterceptor</code> 中创建的 <code>StreamAllocation</code> 对象下一个用到的地方是 ConnectInterceptor,其 <code>intercept</code> 代码如下:</p>
<pre><code class="java">@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}</code></pre>
<p>在上面的代码中, <code>streamAllocation</code> 创建了 <code>httpCodec</code> 以及 <code>connection</code> 对象。 <code>httpCodec</code> 即是上面所说的 <code>Streams</code>,而 <code>connection</code> 则是上面的 <code>Connection</code>,<code>Connection</code> 是一个接口,它的唯一实现类是 <code>RealConnection</code>。</p>
<h3>newStream</h3>
<p><code>StreamAllocation</code> 中的 <code>newStream</code> 方法用于寻找新的 <code>RealConnection</code> 以及 <code>HttpCodec</code>,代码如下:</p>
<pre><code class="java">public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}</code></pre>
<p>在 <code>newStream</code> 中,通过 <code>findHealthyConnection</code> 找到可用的 <code>Connection</code> ,并用这个 <code>Connection</code> 生成一个 <code>HttpCodec</code> 对象。 <code>findHealthyConnection</code> 是找到一个健康的连接,代码如下:</p>
<pre><code class="java">private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
// successCount == 0 表示还未使用过,则可以使用
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
public boolean isHealthy(boolean doExtensiveChecks) {
if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
return false;
}
... // 省略 Http2 代码
return true;
}
</code></pre>
<p>在一个无限循环中,通过 <code>findConnection</code> 寻找一个 <code>connection</code>,并判断是否可用,首先如果没有使用过的肯定是健康的可直接返回,否则调用 <code>isHealthy</code>,主要就是判断 <code>socket</code> 是否关闭。这里的 <code>socket</code> 是在 <code>findConnection</code> 中赋值的,再看看 <code>findConnection</code> 的代码:</p>
<pre><code class="java">private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// Attempt to use an already-allocated connection.
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
// 1. 从 ConnectionPool 取得 connection
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
// Now that we have an IP address, make another attempt at getting a connection from the pool.
// 2. 有了 ip 地址后再从 connectionpool中取一次
// This could match due to connection coalescing.
Internal.instance.get(connectionPool, address, this, selectedRoute);
if (connection != null) return connection;
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
// 3. ConnectionPool 中没有,新创建一个
result = new RealConnection(connectionPool, selectedRoute);
// 3. 将 StreamAllocation 加入到 `RealConnection` 中的一个队列中
acquire(result);
}
// Do TCP + TLS handshakes. This is a blocking operation.
// 4. 建立连接,在其中创建 socket
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// Pool the connection.
// 5. 将新创建的 connection 放到 ConnectionPool 中
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}</code></pre>
<p>上面 <code>Connection</code> 的创建大体是以下几个步骤:</p>
<ol>
<li>调用 <code>Intenal.get</code> 方法从 <code>ConnectionPool</code> 中获取一个 <code>Connection</code>,主要根据 url 的 host 判断,相关代码在 <code>ConnectionPool</code> 中。</li>
<li>如果没有并且又获取了 IP 地址,则再获取一次。</li>
<li>如果 <code>ConnectionPool</code> 中没有, 则新创建一个 <code>RealConnection</code>,并调用 <code>acquire</code> 将 <code>StreamAllocation</code> 中加入 <code>RealConnection</code> 中的一个队列中。</li>
<li>调用 <code>RealConnection#connect</code> 方法建立连接,在内部会创建 <code>Socket</code>。</li>
<li>将新创建的 <code>Connection</code> 加入到 <code>ConnectionPool</code> 中。</li>
</ol>
<p>获取到了 <code>Connection</code> 之后,再创建一个 <code>HttpCodec</code> 对象。</p>
<pre><code class="java">public HttpCodec newCodec(
OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(client.readTimeoutMillis());
source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}</code></pre>
<p>根据是 Http1 还是 Http2 创建对应的 <code>HttpCodec</code>, 其中的 <code>socket</code> 是在 <code>RealConnection</code> 中的 <code>connect</code> 方法创建的。下面具体看看<code>RealConnection</code>。</p>
<h2>RealConnection</h2>
<p><code>RealConnection</code> 封装的是底层的 <code>Socket</code> 连接,内部必然有一个 <code>Socket</code> 对象,下面是 <code>RealConnection</code> 内部的变量:</p>
<pre><code class="java">public final class RealConnection extends Http2Connection.Listener implements Connection {
private static final String NPE_THROW_WITH_NULL = "throw with null exception";
private final ConnectionPool connectionPool;
private final Route route;
// The fields below are initialized by connect() and never reassigned.
/** The low-level TCP socket. */
private Socket rawSocket;
/**
* The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
* {@link #rawSocket} itself if this connection does not use SSL.
*/
private Socket socket;
private Handshake handshake;
private Protocol protocol;
private Http2Connection http2Connection;
private BufferedSource source;
private BufferedSink sink;
// The fields below track connection state and are guarded by connectionPool.
/** If true, no new streams can be created on this connection. Once true this is always true. */
public boolean noNewStreams;
public int successCount;
/**
* The maximum number of concurrent streams that can be carried by this connection. If {@code
* allocations.size() < allocationLimit} then new streams can be created on this connection.
*/
public int allocationLimit = 1;
/** Current streams carried by this connection. */
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
/** Nanotime timestamp when {@code allocations.size()} reached zero. */
public long idleAtNanos = Long.MAX_VALUE;
...
}</code></pre>
<ul>
<li>
<code>Route</code> 表示的是与服务端建立的路径,其实内部封装了 <code>Address</code>,<code>Address</code> 则是封装了请求的 URL。</li>
<li>
<code>rawSocket</code> 对象代表底层的连接,还有一个 <code>socket</code> 是用于 Https, 对于普通的 Http 请求来说,这两个对象是一样的。 <code>source</code> 和 <code>sink</code> 则是利用 Okio 封装 <code>socket</code> 得到的输入输出流。(如果想了解 Okio 的原理,可以参考我之前的文章:<a href="https://segmentfault.com/a/1190000012705584">Okio 源码解析(一):数据读取流程</a>)</li>
<li>
<code>noNewStream</code> 对象用于标识这个 <code>Connection</code> 不能再用于 Http 请求了,一旦设置为 true, 则不会再变。</li>
<li>
<code>allocationLimit</code> 指的是这个 <code>Connection</code> 最多能同时承载几个 Http 流,对于 Http/1 来说只能是一个。</li>
<li>
<code>allocations</code> 是一个 <code>List</code> 对象,里面保存着正在使用这个 <code>Connection</code> 的 <code>StreamAllocation</code> 的弱引用,当 <code>StreamAllocation</code> 调用 <code>acquire</code> 时,便会将其弱引用加入这个 <code>List</code>,调用 <code>release</code> 则是移除引用。<code>allocations</code> 为空说明此 <code>Connection</code> 为闲置, <code>ConnectionPool</code> 利用这些信息来决定是否关闭这个连接。</li>
</ul>
<h3>connect</h3>
<p><code>RealConnection</code> 用于建立连接,里面有相应的 <code>connect</code> 方法:</p>
<pre><code class="java">public void connect(
int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
...
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {
// 创建socket,建立连接
connectSocket(connectTimeout, readTimeout);
}
// 建立
establishProtocol(connectionSpecSelector);
break;
}
...
}
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
// 创建 socket
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
rawSocket.setSoTimeout(readTimeout);
try {
// 建立连接,相当于调用 socket 的 connect 方法
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
try {
// 获取输入输出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
</code></pre>
<p>如果不是 Https, 则调用 <code>connectSocket</code>,在内部创建 <code>rawSocket</code> 对象,设置超时时间。紧接着 <code>Platform.get().connectSocket</code> 根据不同的平台调用相应的 <code>connect</code> 方法,这样 <code>rawSocket</code> 就连接到服务端了。然后是用 <code>Okio</code> 封装 <code>rawSocket</code> 的输入输出流,这里的输入输出流最终是交给 <code>HttpCodec</code> 进行 Http 报文的写入都读取。通过以上步骤,就实现了 Http 请求的连接。</p>
<h2>总结</h2>
<p>本文从 <code>RetryAndFollowupIntercept</code> 中创建 <code>StreamAllocation</code> 对象,到 <code>Connection</code> 中创建 <code>RealConnection</code> 和 <code>HttpCodec</code>,分析了 OkHttp 建立连接的基本过程。可以看出, OkHttp 中的连接由 <br><code>RealConnection</code> 封装,Http 流的输入输出由 <code>HttpCodec</code> 操作,而 <code>StreamAllocation</code> 则统筹管理这些资源。在连接的寻找与创建过程,有个关键的东西是 <code>ConnectionPool</code>, 即连接池。它负责管理所有的 <code>Connection</code>,OkHttp 利用这个连接池进行 <code>Connection</code> 的重用以提高网络请求的效率。本文并没有详细分析 <code>ConnectionPool</code> ,相关内容可以参见下一篇: <a href="https://segmentfault.com/a/1190000014044624">OkHttp源码解析(三):连接池</a>。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
代理模式和装饰者模式
https://segmentfault.com/a/1190000012996742
2018-01-25T23:35:53+08:00
2018-01-25T23:35:53+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
3
<h2>简介</h2>
<p>代理模式和装饰者模式是两种常见的设计模式。代理模式是为其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰者来包裹真实的对象。</p>
<p>因为这两种模式比较相似,所以把它们放在一起做个比较与总结。</p>
<h2>代理模式</h2>
<p>代理模式包含代理对象和被代理对象,类图如下:</p>
<p><img src="/img/bVyNks?w=600&h=335" alt="代理模式UML" title="代理模式UML"></p>
<p>代理对象 <code>Proxy</code> 和被代理对象 <code>RealSubject</code> 都继承了 <code>Subject</code> 接口。客户端调用 <code>Proxy</code> 的方法,而 <code>Proxy</code> 则把具体操作委托给 <code>RealSubject</code> 执行。下面是代码实现:</p>
<pre><code class="java">interface Subject {
void doAction();
}
class RealSubject implements Subject {
@Override
public void doAction() {
System.out.println("RealSubject#doAction");
}
}
class Proxy implements Subject {
private Subject subject;
public Proxy(Subject subject) {
this.subject = subject;
}
@Override
public void doAction() {
subject.doAction();
}
}
public class Client {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
Subject proxy = new Proxy(realSubject);
proxy.doAction();
}
}
输出:RealSubject#doAction</code></pre>
<p>在 Client 中,首先创建了一个 <code>realSubject</code> 对象,然后创建一个代理对象 <code>proxy</code> 并且把 <code>realSubject</code><br>对象通过构造器传入进去。最后调用代理对象的 <code>doAction</code>,实际执行的是 <code>realSubject</code> 的对应方法。这里通过构造函数的参数将被代理对象传入到代理中,也可以通过其它方式,如提供一个 <code>setSubject</code> 方法。</p>
<p>上面的代理模式,代理对象和被代理对象需要实现相同的接口,所以如果要代理其它接口的对象需要写一个新的代理类。Java 提供了动态代理的功能,可以简化我们的代码。</p>
<h3>动态代理</h3>
<p>动态代理可以在运行期生成所需要的代理对象,看下面的代码:</p>
<pre><code class="java">class DynamicProxy implements InvocationHandler {
// 被代理对象的引用
private Object obj;
public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(obj, args);
return result;
}
}</code></pre>
<p>类 <code>DynamicProxy</code> 实现了 <code>InvocationHandler</code> 接口,这个接口中有一个 <code>invoke</code> 方法,被代理的对象的任何方法都是在 <code>invoke</code> 中调用。下面是 Client 的代码:</p>
<pre><code class="java">public class TestDelegate {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
DynamicProxy dynamicProxy = new DynamicProxy(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Subject proxy = (Subject) Proxy.newProxyInstance(loader, new Class[]{Subject.class}, dynamicProxy);
proxy.doAction();
}
}
输出:RealSubject#doAction</code></pre>
<p>首先依然是先创建一个需要被代理的对象 <code>realSubject</code>,然后把它传入到 <code>DynamicProxy</code> 的构造函数中。这个 <code>dynamicProxy</code> 还不是我们需要的代理,毕竟它没有实现 <code>Subject</code> 接口。下面通过 <code>Proxy.newProxyInstance</code> 创建了一个 <code>Subject</code> 对象,也就是最终的代理对象。</p>
<p>通过动态代理,创建一个实现了 <code>InvocationHandler</code> 接口的 <code>DynamicProxy</code> 类,通过这个类可以在运行期为各种对象创建对应的代理,比静态代理方便了很多。</p>
<h2>装饰者模式</h2>
<p>装饰者模式是为了给已有的对象增加一些逻辑,但是不改变已有对象的代码,下面是类图:</p>
<p><img src="/img/bV2G6R?w=600&h=475" alt="装饰者模式UML" title="装饰者模式UML"></p>
<p>从上图可以看出,装饰者 <code>Decorator</code>与需要被装饰的对象 <code>ContcreteComponent</code> 实现了相同的接口。具体怎么装饰则由 <code>Decorator</code> 的子类 <code>ConcreteDecorator</code> 决定。</p>
<p>Java 中使用装饰者模式的一个典型的例子是 I/O 对象的创建,比如创建一个 <code>BufferedInputStream</code> 时:</p>
<pre><code class="java">InputStream in = ...
InputStream input = new BufferedInputStream(in);</code></pre>
<p><code>BufferedInputStream</code> 继承于 <code>FilterInputStream</code>,这个 <code>FilterInputStream</code> 相当于装饰者模式中的 <code>Decorator</code>,它继承了 <code>InputStream</code> 接口。<code>BufferedInputStream</code> 则是一个具体的装饰类,其它还有 <code>DataInputStream</code> 以及 <code>ByteArrayInputStream</code> 等。而传给 <code>BufferedInputstream</code> 的对象 <code>in</code> 则是需要被装饰者。装饰者对被装饰者进行了功能的扩展,但是又不需要修改被装饰者的相应代码,符合“开闭原则”,即对于修改是封闭的,对于扩展则是开放的。</p>
<p>如果是为了给某个类提供更多的功能,继承是一种方案。但是,如果我们的功能有很多种组合,那么为每种组合编写一个继承的类可能需要创建太多的子类。而装饰者模式则可以解决这个问题,只需要为每个功能编写一个装饰类,在运行时组合不同的对象即可实现所需的功能组合。</p>
<h2>总结</h2>
<p>代理模式和装饰者模式有着很多的应用,这两者具有一定的相似性,都是通过一个新的对象封装原有的对象。二者之间的差异在于代理模式是为了实现对象的控制,可能被代理的对象难以直接获得或者是不想暴露给客户端,而装饰者模式是继承的一种替代方案,在避免创建过多子类的情况下为被装饰者提供更多的功能。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
Okio 源码解析(二):超时机制
https://segmentfault.com/a/1190000012864921
2018-01-16T19:57:25+08:00
2018-01-16T19:57:25+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
2
<h3>简介</h3>
<p>上一篇文章(<a href="https://segmentfault.com/a/1190000012705584">Okio 源码解析(一):数据读取流程</a>)分析了 Okio 数据读取的流程,从中可以看出 Okio 的便捷与高效。Okio 的另外一个优点是提供了超时机制,并且分为同步超时与异步超时。本文具体分析这两种超时的实现。</p>
<h3>同步超时</h3>
<p>回顾一下 <code>Okio.source</code> 的代码:</p>
<pre><code class="java">public static Source source(InputStream in) {
// 生成一个 Timeout 对象
return source(in, new Timeout());
}
private static Source source(final InputStream in, final Timeout timeout) {
if (in == null) throw new IllegalArgumentException("in == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
try {
// 超时检测
timeout.throwIfReached();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
} catch (AssertionError e) {
if (isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
}
}
@Override public void close() throws IOException {
in.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "source(" + in + ")";
}
};
}</code></pre>
<p>在 <code>Source</code> 的构造方法中,传入了一个 <code>Timeout</code> 对象。在下面创建的匿名的 <code>Source</code> 对象的 <code>read</code> 方法中,先调用了 <code>timeout.throwIfReached()</code>,这里显然是判断是否已经超时,代码如下:</p>
<pre><code class="java">public void throwIfReached() throws IOException {
if (Thread.interrupted()) {
throw new InterruptedIOException("thread interrupted");
}
if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
throw new InterruptedIOException("deadline reached");
}
}</code></pre>
<p>这里逻辑很简单,如果超时了则抛出异常。在 <code>TimeOut</code> 中有几个变量用于设定超时的时间:</p>
<pre><code class="java">private boolean hasDeadline;
private long deadlineNanoTime;
private long timeoutNanos;</code></pre>
<p>由于 <code>throwIfReached</code> 是在每次读取数据之前调用并且与数据读取在同一个线程,所以如果读取操作阻塞,则无法及时抛出异常。</p>
<h3>异步超时</h3>
<p>异步超时与同步超时不同,其开了新的线程用于检测是否超时,下面是 Socket 的例子。</p>
<p>Okio 可以接受一个 <code>Socket</code> 对象构建 <code>Source</code>,代码如下:</p>
<pre><code class="java">public static Source source(Socket socket) throws IOException {
if (socket == null) throw new IllegalArgumentException("socket == null");
AsyncTimeout timeout = timeout(socket);
Source source = source(socket.getInputStream(), timeout);
// 返回 timeout 封装的 source
return timeout.source(source);
}</code></pre>
<p>相比于 <code>InputStream</code>,这里的额外操作是引入了 <code>AsyncTimeout</code> 来封装 <code>socket</code>。<code>timeout</code> 方法生成一个 <code>AsyncTimeout</code> 对象,看一下代码:</p>
<pre><code class="java">private static AsyncTimeout timeout(final Socket socket) {
return new AsyncTimeout() {
@Override protected IOException newTimeoutException(@Nullable IOException cause) {
InterruptedIOException ioe = new SocketTimeoutException("timeout");
if (cause != null) {
ioe.initCause(cause);
}
return ioe;
}
// 超时后调用
@Override protected void timedOut() {
try {
socket.close();
} catch (Exception e) {
logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
} catch (AssertionError e) {
if (isAndroidGetsocknameError(e)) {
logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
} else {
throw e;
}
}
}
};
}</code></pre>
<p>上面的代码生成了一个匿名的 <code>AsyncTimeout</code>,其中有个 <code>timedout</code> 方法,这个方法是在超时的时候被调用,可以看出里面的操作主要是关闭 <code>socket</code>。有了 <code>AsyncTimeout</code> 之后,调用其 <code>source</code> 方法来封装 <code>socket</code> 的 <code>InputStream</code>。</p>
<p>下面具体看看 <code>AsyncTimeout</code> 。</p>
<h4>AsyncTimeout</h4>
<p><code>AsyncTimeout</code> 继承了 <code>Timeout</code>,提供了异步的超时机制。每一个 <code>AsyncTimeout</code> 对象包装一个 <code>source</code>,并与其它 <code>AsyncTimeout</code> 组成一个链表,根据超时时间的长短插入。<code>AsyncTimeout</code> 内部会新开一个叫做 <code>WatchDog</code> 的线程,根据超时时间依次处理 <code>AsyncTimout</code> 链表的节点。</p>
<p>下面是 <code>AsyncTimeout</code> 的一些内部变量:</p>
<pre><code class="java">// 链表头结点
static @Nullable AsyncTimeout head;
// 此节点是否在队列中
private boolean inQueue;
// 链表中下一个节点
private @Nullable AsyncTimeout next;</code></pre>
<p>其中 <code>head</code> 是链表的头结点,<code>next</code> 是下一个节点,<code>inQueue</code> 则标识此 <code>AsyncTimeout</code> 是否处于链表中。</p>
<p>在上面的 <code>Okio.source(Socket socket)</code> 中,最后返回的是 <code>timeout.source(socket)</code>,下面是其代码:</p>
<pre><code class="java">public final Source source(final Source source) {
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
boolean throwOnTimeout = false;
// enter
enter();
try {
long result = source.read(sink, byteCount);
throwOnTimeout = true;
return result;
} catch (IOException e) {
throw exit(e);
} finally {
exit(throwOnTimeout);
}
}
@Override public void close() throws IOException {
boolean throwOnTimeout = false;
try {
source.close();
throwOnTimeout = true;
} catch (IOException e) {
throw exit(e);
} finally {
exit(throwOnTimeout);
}
}
@Override public Timeout timeout() {
return AsyncTimeout.this;
}
@Override public String toString() {
return "AsyncTimeout.source(" + source + ")";
}
};
}</code></pre>
<p><code>AsyncTimtout#source</code> 依然是返回一个匿名的 <code>Source</code> 对象,只不过是将参数中真正的 <code>source</code> 包装了一下,在 <code>source.read</code> 之前添加了 <code>enter</code> 方法,在 <code>catch</code> 以及 <code>finally</code> 中添加了 <code>exit</code> 方法。<code>enter</code> 和 <code>exit</code> 是重点,其中 <code>enter</code> 中会将当前的 <code>AsyncTimeout</code> 加入链表,具体代码如下:</p>
<pre><code class="java">public final void enter() {
if (inQueue) throw new IllegalStateException("Unbalanced enter/exit");
long timeoutNanos = timeoutNanos();
boolean hasDeadline = hasDeadline();
if (timeoutNanos == 0 && !hasDeadline) {
return; // No timeout and no deadline? Don't bother with the queue.
}
inQueue = true;
scheduleTimeout(this, timeoutNanos, hasDeadline);
}
private static synchronized void scheduleTimeout(
AsyncTimeout node, long timeoutNanos, boolean hasDeadline) {
// 如果链表为空,则新建一个头结点,并且启动 Watchdog线程
if (head == null) {
head = new AsyncTimeout();
new Watchdog().start();
}
long now = System.nanoTime();
if (timeoutNanos != 0 && hasDeadline) {
node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now);
} else if (timeoutNanos != 0) {
node.timeoutAt = now + timeoutNanos;
} else if (hasDeadline) {
node.timeoutAt = node.deadlineNanoTime();
} else {
throw new AssertionError();
}
// 按时间将节点插入链表
long remainingNanos = node.remainingNanos(now);
for (AsyncTimeout prev = head; true; prev = prev.next) {
if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) {
node.next = prev.next;
prev.next = node;
if (prev == head) {
AsyncTimeout.class.notify(); // Wake up the watchdog when inserting at the front.
}
break;
}
}
}</code></pre>
<p>真正插入链表的操作在 <code>scheduleTimeout</code> 中,如果 <code>head</code> 节点还不存在则新建一个头结点,并且启动 <code>Watchdog</code> 线程。接着就是计算超时时间,然后遍历链表进行插入。如果插入在链表的最前面(<code>head</code> 节点后面的第一个节点),则主动进行唤醒 <code>Watchdog</code> 线程,从这里可以猜到 <code>Watchdog</code> 线程在等待超时的过程中是调用了 <code>AsyncTimeout.class</code> 的 <code>wait</code> 进入了休眠状态。那么就来看看 <code>WatchDog</code> 线程的实际逻辑:</p>
<pre><code class="java">private static final class Watchdog extends Thread {
Watchdog() {
super("Okio Watchdog");
setDaemon(true);
}
public void run() {
while (true) {
try {
AsyncTimeout timedOut;
synchronized (AsyncTimeout.class) {
timedOut = awaitTimeout();
// Didn't find a node to interrupt. Try again.
if (timedOut == null) continue;
// The queue is completely empty. Let this thread exit and let another watchdog thread
// get created on the next call to scheduleTimeout().
if (timedOut == head) {
head = null;
return;
}
}
// Close the timed out node.
timedOut.timedOut();
} catch (InterruptedException ignored) {
}
}
}
}</code></pre>
<p><br><code>WatchDog</code> 主要是调用 <code>awaitTimeout()</code> 获取一个已超时的 <code>timeout</code>,如果不为空并且是 <code>head</code> 节点,说明链表中已经没有其它节点,可以结束线程,否则调用 <code>timedOut.timedOut()</code>, <code>timeOut()</code> 是一个空方法,由用户实现超时后应该采取的操作。 <code>awaitTimeout</code> 是获取超时节点的方法:</p>
<pre><code class="java"> static @Nullable AsyncTimeout awaitTimeout() throws InterruptedException {
// Get the next eligible node.
AsyncTimeout node = head.next;
// 队列为空的话等待有节点进入队列或者达到超时IDLE_TIMEOUT_MILLIS的时间
if (node == null) {
long startNanos = System.nanoTime();
AsyncTimeout.class.wait(IDLE_TIMEOUT_MILLIS);
return head.next == null && (System.nanoTime() - startNanos) >= IDLE_TIMEOUT_NANOS
? head // The idle timeout elapsed.
: null; // The situation has changed.
}
// 计算等待时间
long waitNanos = node.remainingNanos(System.nanoTime());
// The head of the queue hasn't timed out yet. Await that.
if (waitNanos > 0) {
// Waiting is made complicated by the fact that we work in nanoseconds,
// but the API wants (millis, nanos) in two arguments.
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
// 调用 wait
AsyncTimeout.class.wait(waitMillis, (int) waitNanos);
return null;
}
// 第一个节点超时,移除并返回这个节点
head.next = node.next;
node.next = null;
return node;
}</code></pre>
<p>与 <code>enter</code> 相反,<code>exit</code> 则是视情况抛出异常并且移除链表中的节点,这里就不放具体代码了。</p>
<h2>总结</h2>
<p>Okio 通过 <code>Timeout</code> 以及 <code>AsyncTimeout</code> 分别提供了同步超时和异步超时功能,同步超时是在每次读取数据前判断是否超时,异步超时则是将 <code>AsyncTimeout</code> 组成有序链表,并且开启一个线程来监控,到达超时则触发相关操作。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
Okio 源码解析(一):数据读取流程
https://segmentfault.com/a/1190000012705584
2018-01-04T19:08:28+08:00
2018-01-04T19:08:28+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
4
<h3>简介</h3>
<p>Okio 是 square 开发的一个 Java I/O 库,并且也是 OkHttp 内部使用的一个组件。Okio 封装了 <code>java.io</code> 和 <code>java.nio</code>,并且有多个优点:</p>
<ul>
<li>提供超时机制</li>
<li>不需要人工区分字节流与字符流,易于使用</li>
<li>易于测试</li>
</ul>
<p>本文先介绍 Okio 的基本用法,然后分析源码中数据读取的流程。</p>
<h3>基本用法</h3>
<p>Okio 的用法很简单,下面是读取和写入的示例:</p>
<pre><code class="java">// 读取
InputStream inputStream = ...
BufferedSource bufferedSource = Okio.buffer(Okio.source(inputStream));
String line = bufferedSource.readUtf8();
// 写入
OutputStream outputStream = ...
BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));
bufferedSink.writeString("test", Charset.defaultCharset());
bufferedSink.close();</code></pre>
<p>Okio 用 <code>Okio.source</code> 封装 <code>InputStream</code>,用 <code>Okio.sink</code> 封装 <code>OutputStream</code>。然后统一交给 <code>Okio.buffer</code> 分别获得 <code>BufferedSource</code> 和 <code>BufferedSink</code>,这两个类提供了大量的读写数据的方法。<code>BufferedSource</code> 中包含的部分接口如下:</p>
<pre><code class="java">int readInt() throws IOException;
long readLong() throws IOException;
byte readByte() throws IOException;
ByteString readByteString() throws IOException;
String readUtf8() throws IOException;
String readString(Charset charset) throws IOException;</code></pre>
<p>其中既包含了读取字节流,也包含读取字符流的方法,<code>BufferedSink</code> 则提供了对应的写入数据的方法。</p>
<h3>基本框架</h3>
<p>Okio 中有4个接口,分别是 <code>Source</code>、<code>Sink</code>、 <code>BufferedSource</code> 和 <code>BufferedSink</code>。<code>Source</code> 和 <code>Sink</code> 分别用于提供字节流和接收字节流,对应于 <code>Inpustream</code> 和 <code>OutputStream</code>。<code>BufferedSource</code> 和 <code>BufferedSink</code> 则是保存了相应的缓存数据用于高效读写。这几个接口的继承关系如下:</p>
<p><img src="/img/remote/1460000012705587?w=720&h=668" alt="okio源码" title="okio源码"></p>
<p>从上图可以看出,<code>Source</code> 和 <code>Sink</code> 提供基本的 <code>read</code> 和 <code>write</code> 方法,而 <code>BufferedSource</code> 和 <code>BufferedSink</code> 则提供了更多的操作数据的方法,但这些都是接口,真正实现的类是 <code>RealBufferedSource</code> 和 <code>RealBufferedSink</code>。</p>
<p>另外还有个类是 <code>Buffer</code>, 它同时实现了 <code>BufferedSource</code> 和 <code>BufferedSink</code>,并且 <code>RealBufferedSource</code> 和 <code>RealbufferedSink</code> 都包含一个 <code>Buffer</code> 对象,真正的数据读取操作都是交给 <code>Buffer</code> 完成的。</p>
<p>由于 read 和 write 操作类似,下面以 read 的流程对代码进行分析。</p>
<h3>Okio.source</h3>
<p><code>Okio.source</code> 有几个重载的方法,用于封装输入流,最终调用的代码如下:</p>
<pre><code class="java">private static Source source(final InputStream in, final Timeout timeout) {
if (in == null) throw new IllegalArgumentException("in == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
try {
timeout.throwIfReached();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
} catch (AssertionError e) {
if (isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
}
}
@Override public void close() throws IOException {
in.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "source(" + in + ")";
}
};
}</code></pre>
<p>从上面代码可以看出,<code>Okio.source</code> 接受两个参数,一个是 <code>InputStream</code>,另一个是 <code>Timeout</code>,返回了一个匿名的 <code>Source</code> 的实现类。这里主要看一下 <code>read</code> 方法,首先是参数为空的判断,然后是从 <code>in</code> 中读取数据到类型为 <code>Buffer</code> 的 <code>sink</code> 中,这段代码中涉及到 <code>Buffer</code> 以及 <code>Segment</code>,下面先看看这两个东西。</p>
<h3>Segment</h3>
<p>在 Okio 中,每个 Segment 代表一段数据,多个 Segment 串成一个循环双向链表。下面是 Segment 的成员变量和构造方法:</p>
<pre><code class="java">final class Segment {
// segment数据的字节数
static final int SIZE = 8192;
// 共享的Segment的最低的数据大小
static final int SHARE_MINIMUM = 1024;
// 实际保存的数据
final byte[] data;
// 下一个可读的位置
int pos;
// 下一个可写的位置
int limit;
// 保存的数据是否是共享的
boolean shared;
// 保存的数据是否是独占的
boolean owner;
// 链表中下一个节点
Segment next;
// 链表中上一个节点
Segment prev;
Segment() {
this.data = new byte[SIZE];
this.owner = true;
this.shared = false;
}
Segment(Segment shareFrom) {
this(shareFrom.data, shareFrom.pos, shareFrom.limit);
shareFrom.shared = true;
}
Segment(byte[] data, int pos, int limit) {
this.data = data;
this.pos = pos;
this.limit = limit;
this.owner = false;
this.shared = true;
}
...
}</code></pre>
<p>变量的含义已经写在了注释中,可以看出 <code>Segment</code> 中的数据保存在一个字节数组中,并提供了一些变量标识读与写的位置。Segment 既然是链表中的节点,下面看一下插入与删除的方法:</p>
<pre><code class="java">// 在当前Segment后面插入一个Segment
public Segment push(Segment segment) {
segment.prev = this;
segment.next = next;
next.prev = segment;
next = segment;
return segment;
}
// 从链表中删除当前Segment,并返回其后继节点
public @Nullable Segment pop() {
Segment result = next != this ? next : null;
prev.next = next;
next.prev = prev;
next = null;
prev = null;
return result;
}</code></pre>
<p>插入与删除的代码其实就是数据结构中链表的操作。</p>
<h3>Buffer</h3>
<p>下面看看 Buffer 是如何使用 Segment 的。<code>Buffer</code> 中有两个重要变量:</p>
<pre><code class="java">@Nullable Segment head;
long size;</code></pre>
<p>一个是 <code>head</code>,表示这个 <code>Buffer</code> 保存的 <code>Segment</code> 链表的头结点。还有一个 <code>size</code>,用于记录 <code>Buffer</code> 当前的字节数。</p>
<p>在上面 <code>Okio.source</code> 中生成的匿名的 <code>Source</code> 的 <code>read</code> 方法中,要读取数据到 <code>Buffer</code> 中,首次是调用了 <code>writableSegment</code>,这个方法是获取一个可写的 <code>Segment</code>,代码如下所示:</p>
<pre><code class="java"> Segment writableSegment(int minimumCapacity) {
if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
if (head == null) {
head = SegmentPool.take(); // Acquire a first segment.
return head.next = head.prev = head;
}
Segment tail = head.prev;
if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
}
return tail;
}</code></pre>
<p>获取 <code>Segment</code> 的逻辑是先判断 <code>Buffer</code> 是否有了 <code>Segment</code> 节点,没有就先去 <code>SegmentPool</code> 中取一个,并且将首尾相连,形成循环链表。如果已经有了,找到末尾的 <code>Segment</code>,判断其剩余空间是否满足,不满足就再从 <code>SegmentPool</code> 中获取一个新的 <code>Segment</code> 添加到末尾。最后,返回末尾的 <code>Segment</code> 用于写入。</p>
<p><code>SegmentPool</code> 用于保存废弃的 <code>Segment</code>,其中有两个方法,<code>take</code> 从中获取,<code>recycle</code> 用于回收。</p>
<p>上面 <code>Okio.buffer(Okio.source(in))</code> 最终得到的是 <code>RealBufferedSource</code>,这个类中持有一个 <code>Buffer</code> 对象和一个 <code>Source</code> 对象,真正的读取操作由这两个对象合作完成。下面是 <code>readString</code> 的代码:</p>
<pre><code class="java">@Override public String readString(long byteCount, Charset charset) throws IOException {
require(byteCount);
if (charset == null) throw new IllegalArgumentException("charset == null");
return buffer.readString(byteCount, charset);
}
@Override public void require(long byteCount) throws IOException {
if (!request(byteCount)) throw new EOFException();
}
// 从source中读取数据到buffer中
@Override public boolean request(long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
while (buffer.size < byteCount) {
if (source.read(buffer, Segment.SIZE) == -1) return false;
}
return true;
}</code></pre>
<p>首先是从 <code>Source</code> 中读取数据到 <code>Buffer</code> 中,然后调用 <code>buffer.readstring</code> 方法得到最终的字符串。下面是 <code>readString</code> 的代码:</p>
<pre><code class="java">@Override public String readString(long byteCount, Charset charset) throws EOFException {
checkOffsetAndCount(size, 0, byteCount);
if (charset == null) throw new IllegalArgumentException("charset == null");
if (byteCount > Integer.MAX_VALUE) {
throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
}
if (byteCount == 0) return "";
Segment s = head;
if (s.pos + byteCount > s.limit) {
// 如果string跨多个Segment,委托给readByteArray去读
return new String(readByteArray(byteCount), charset);
}
// 将字节序列转换成String
String result = new String(s.data, s.pos, (int) byteCount, charset);
s.pos += byteCount;
size -= byteCount;
// 如果pos==limit,回收这个Segment
if (s.pos == s.limit) {
head = s.pop();
SegmentPool.recycle(s);
}
return result;
}</code></pre>
<p>在上面的代码中,便是从 <code>Buffer</code> 的 <code>Segment</code> 链表中读取数据。如果 <code>String</code> 跨多个 <code>Segment</code>,那么调用 <code>readByteArray</code> 循环读取字节序列。最终将字节序列转换为 <code>String</code> 对象。如果 <code>Segment</code> 的 <code>pos</code> 等于 <code>limit</code>,说明这个 <code>Segment</code> 的数据已经全部读取完毕,可以回收,放入 <code>SegmentPool</code>。</p>
<p>Okio 读取数据的时候统一将输入流看成是字节序列,读入 <code>Buffer</code> 后在用到的时候再转换,例如上面读取 <code>String</code> 时将字节序列进行了转换。其它还有很多类型,如下面是 <code>readInt</code> 的代码:</p>
<pre><code class="java">@Override public int readInt() {
if (size < 4) throw new IllegalStateException("size < 4: " + size);
Segment segment = head;
int pos = segment.pos;
int limit = segment.limit;
// If the int is split across multiple segments, delegate to readByte().
if (limit - pos < 4) {
return (readByte() & 0xff) << 24
| (readByte() & 0xff) << 16
| (readByte() & 0xff) << 8
| (readByte() & 0xff);
}
byte[] data = segment.data;
int i = (data[pos++] & 0xff) << 24
| (data[pos++] & 0xff) << 16
| (data[pos++] & 0xff) << 8
| (data[pos++] & 0xff);
size -= 4;
if (pos == limit) {
head = segment.pop();
SegmentPool.recycle(segment);
} else {
segment.pos = pos;
}
return i;
}</code></pre>
<p>Buffer 使用 <code>Segment</code> 链表保存数据,有个好处是在不同的 Buffer 之间移动数据只需要转移其字节序列的拥有权,如 <code>copyTo(Buffer out, long offset, long byteCount)</code> 代码所示:</p>
<pre><code class="java">public Buffer copyTo(Buffer out, long offset, long byteCount) {
if (out == null) throw new IllegalArgumentException("out == null");
checkOffsetAndCount(size, offset, byteCount);
if (byteCount == 0) return this;
out.size += byteCount;
// Skip segments that we aren't copying from.
Segment s = head;
for (; offset >= (s.limit - s.pos); s = s.next) {
offset -= (s.limit - s.pos);
}
// Copy one segment at a time.
for (; byteCount > 0; s = s.next) {
Segment copy = new Segment(s);
copy.pos += offset;
copy.limit = Math.min(copy.pos + (int) byteCount, copy.limit);
if (out.head == null) {
out.head = copy.next = copy.prev = copy;
} else {
out.head.prev.push(copy);
}
byteCount -= copy.limit - copy.pos;
offset = 0;
}
return this;
}</code></pre>
<p>其中并没有拷贝字节数据,只是链表的相关操作。</p>
<h2>总结</h2>
<p>Okio 读取数据的流程基本就如本文所分析的,写入操作与读取是类似的。Okio 通过 <code>Source</code> 与 <code>Sink</code> 标识输入流与输出流。在 <code>Buffer</code> 中使用 <code>Segment</code> 链表的方式保存字节数据,并且通过 <code>Segment</code> 拥有权的共享避免了数据的拷贝,通过 <code>SegmentPool</code> 避免了废弃数据的GC,使得 Okio 成为一个高效的 I/O 库。Okio 还有一个优点是超时机制,具体内容可进入下一篇:<a href="https://segmentfault.com/a/1190000012864921">Okio 源码解析(二):超时机制</a></p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
OkHttp 源码解析(一):基本流程
https://segmentfault.com/a/1190000012656606
2017-12-31T23:57:22+08:00
2017-12-31T23:57:22+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
8
<h2>简介</h2>
<p>OkHttp 是一款用于 Android 和 Java 的网络请求库,也是目前 Android 中最火的一个网络库。OkHttp 有很多的优点:</p>
<ul>
<li>在 HTTP/2 上允许对同一个 host 的请求共同一个 socket</li>
<li>连接池的使用减少请求延迟(如果 HTTP/2 不支持)</li>
<li>透明的 GZIP 压缩减少数据量大小</li>
<li>响应的缓存避免重复的网络请求</li>
</ul>
<p>之前写过一篇 <a href="https://segmentfault.com/a/1190000006767113">Retrofit 源码解析</a>,Retrofit 底层其实就是用的 OkHttp 去请求网络。本文分析 OKHttp 的源码,主要是针对一次网络请求的基本流程,源码基于 OKHttp-3.8.0</p>
<h2>基本用法</h2>
<p>下面是 OkHttp 的使用示例:</p>
<pre><code class="java">OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
// 同步
Response response = client.newCall(request).execute();
// 异步
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});</code></pre>
<p>首先是创建一个 <code>OkHttpClient</code> 对象,其实用过的应该知道可以用 <code>new OkHttpClient.Builder().build()</code> 的方式来配置 <code>OkHttpClient</code> 的一些参数。有了 <code>OkHttpClient</code> 之后,下面是创建一个 <code>Request</code> 对象,这个对象也是通过 Builder 模式来生成,其中可以配置一些与这条请求相关的参数,其中 url 是必不可少的。在发送请求的时候,需要生成一个 <code>Call</code> 对象,<code>Call</code> 代表了一个即将被执行的请求。如果是同步请求,调用 <code>execute</code> 方法。异步则调用 <code>enqueue</code>,并设定一个回调对象 <code>Callback</code>。</p>
<p>下面就一步步分析发送一条网络请求的基本流程。</p>
<h2>OkHttpClient、Request 及 Call 的创建</h2>
<p>OkHttpClient 的创建采用了 <strong>Builder</strong> 模式,可以配置 Interceptor、Cache 等。可以设置的参数很多,其中部分参数如下:</p>
<pre><code class="java"> final Dispatcher dispatcher; // 请求的分发器
final @Nullable Proxy proxy; // 代理
final List<Protocol> protocols; // http协议
final List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;
final CookieJar cookieJar;
final @Nullable Cache cache;</code></pre>
<p>Request 与 OkHttpClient 的创建类似,也是用了 Buidler 模式,但是其参数要少很多:</p>
<pre><code class="java">public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
...
}</code></pre>
<p>参数的含义都很明确,即使 Http 协议的url、header、method 以及 body 部分。变量 <code>tag</code> 用于标识一条 <code>Request</code>,可用于发送后取消这条请求。</p>
<p><code>client.newCall(request)</code> 生成一个 Call 对象。Call 实际上是一个接口,它封装了 Request,并且用于发起实际的网络请求。下面是 Call 的全部代码:</p>
<pre><code class="java">public interface Call extends Cloneable {
Request request();
Response execute() throws IOException;
void enqueue(Callback responseCallback);
void cancel();
boolean isExecuted();
boolean isCanceled();
Call clone();
interface Factory {
Call newCall(Request request);
}
}</code></pre>
<p>其中包含了与网络请求相关的操作,包括发起、取消等。看一下 <code>OkHttpClient</code> 是如何创建 <code>Call</code> 的:</p>
<pre><code class="java"> @Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}</code></pre>
<p>从代码可以看到,实际上是创建了一个 <code>RealCall</code> 对象,它也是 Call 的唯一一个实现类。</p>
<p>有了 <code>RealCall</code> 对象后,就可以发起网络请求了,可以是同步请求(<code>execute</code>)或者是异步请求(<code>enqueue</code>)。异步请求涉及到 <code>Dispatcher</code>,先从相对简单的同步请求开始分析。</p>
<h2>同步请求</h2>
<p>调用 <code>RealCall#execute()</code> 即是发起同步请求,代码如下:</p>
<pre><code class="java"> @Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}</code></pre>
<p>首先判断这条请求是不是已经执行过,如果是则会抛出异常(一条请求只能执行一次,重复执行可以调用 <code>Call#clone()</code>)。接着执行了 <code>client.dispatcher().executed(this)</code>,这行代码是把当前的 Call 加入到 Dispatcher 的一个队列中,这个暂时可以忽略,后面会分析 Dispatcher。</p>
<p>下面一行 <code>Response result = getResponseWithInterceptorChain()</code> 是关键,在 <code>getResponseWithInterceptorChain</code> 中真正执行了网络请求并获得 Response 并返回。(下一小节具体分析其中的逻辑)</p>
<p>最后在 finally 中调用 Dispatcher 的 <code>finished</code>,从队列中移除这条请求。</p>
<h3>拦截器 Interceptor</h3>
<p><code>getResponseWithInterceptorChain</code> 的代码如下:</p>
<pre><code class="java"> Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}</code></pre>
<p>可以看到,其中创建了一个 List 用于添加 Interceptor。首先添加的是 client 中的 interceptors,也就是在创建 <code>OkHttpClient</code> 对象时自定义的 interceptors,然后依次添加 <code>retryAndFollowUpInterceptor</code>(重试及重定向)、<code>BridgeInterceptor</code>(请求参数的添加)、<code>CacheInterceptor</code>(缓存)、<code>ConnectInterceptor</code>(开始连接)、用户自定义的 <code>networkinterceptors</code> 及 <code>CallServerInterceptor</code>(发送参数并读取响应)。从这里可以知道,OkHttp 默认添加了好几个 interceptor 用于完成不同的功能。</p>
<p>在研究各个 interceptor 之前,需要考虑一下如何让这些拦截器一个接着一个的执行?继续看上面的代码,在添加了各种 interceptors 之后,创建了一个 <code>RealInterceptorChain</code> 对象。(它的构造函数需要的参数很多,并且这些参数涉及到连接池、请求数据的发送等。由于这篇文章主要分析 OkHttp 的基本流程,所以暂时略过这部分)<code>RealInterceptorChain</code> 是接口 <code>Chain</code> 的实现类,<code>Chain</code> 是 <strong>链</strong> 的意思,其作用是把各个 Interceptor 串起来依次执行。在获得了 <code>RealInterceptorChain</code> 之后调用其 <code>proceed</code> 方法,看名字就能知道是让 <code>Request</code> 请求继续执行。</p>
<p>下面具体分析 <code>RealInterceptorChain</code>,它有如下的成员变量:</p>
<pre><code class="java"> private final List<Interceptor> interceptors; // 拦截器
private final StreamAllocation streamAllocation; // 流管理器
private final HttpCodec httpCodec; // http流,发送请求数据并读取响应数据
private final RealConnection connection; // scoket的连接
private final int index; // 当前拦截器的索引
private final Request request; // 当前的请求
private int calls; // chain 的 proceed 调用次数的记录</code></pre>
<p>其中 <code>streamAllocation</code>、<code>httpCodec</code> 和 <code>connection</code> 都与 socket 连接有关,后续文章再分析。看一下 <code>proceed</code> 方法:</p>
<pre><code class="java">public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
// 如果已经有了一个流,确保即将到来的 request 是用它
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
// 如果已经有了一个流,确保这是对 call 唯一的调用
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request); // (1)
Interceptor interceptor = interceptors.get(index); // (2)
Response response = interceptor.intercept(next); // (3)
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}</code></pre>
<p>刚开始做了一些连接方面的判断,需要关注的是标了(1)、(2)、(3)的几行,主要做了以下操作:</p>
<ol>
<li>创建新的 <code>RealInterceptorChain</code>,其中 <code>index</code> 加1用于标识当前的拦截器</li>
<li>通过 <code>index</code> 获取当前的拦截器</li>
<li>调用下一个拦截器的 <code>intercept</code> 方法,并把上面生成的新的 RealInterceptorChain 对象 <code>next</code> 传进去</li>
</ol>
<p>由之前的 <code>getResponseWithInterceptorChain</code> 方法可以知道,当前 <code>RealInterceptorChain</code> 的 interceptors 的第一个是 <code>RetryAndFollowUpInterceptor</code>,下面是其 <code>intercept</code> 的代码:</p>
<pre><code class="java"> @Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
// 调用 chain 的 proceed
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
... // 省略部分代码,主要是错误重试以及重定向
}
}</code></pre>
<p>这个 Interceptor 主要用于出错重试以及重定向的逻辑,其中省略了部分代码。在这个方法当中要关注的是再次调用了 <code>chain</code> 的 <code>proceed</code> 方法,这里的 <code>chain</code> 是之前新创建的 <code>next</code> 对象。相当于说通过调用 <code>Chain#proceed()</code> 将网络请求推向下一个拦截器(<code>proceed</code> 中会获取下一个 Interceptor 并调用其 <code>intercept</code> 方法),并且得到 response 对象,而下一个拦截器也是类似的操作。于是,多个 interceptors 就通过这种方式串起来依次执行,并且前一个 Interceptor 可以得到后一个 Interceptor 执行后的 response 从而进行处理。</p>
<p>通过不同的 Interceptor,OkHttp 实现了不同的功能。各个 Inercept 职责分明又不会互相耦合,并且可以非常方便的添加 Interceptor,这是 <strong>责任链</strong> 模式的体现,非常优雅的设计。现在可以发现 OkHttp 中的拦截器的调用过程如下图所示:</p>
<p><img src="/img/remote/1460000012656609?w=171&h=648" alt="图片描述" title="图片描述"></p>
<h2>异步请求</h2>
<p>相比于同步请求,异步请求主要是增加了 Dispatcher 的处理。Dispatcher 是请求的分发器,它有一下的成员变量:</p>
<pre><code class="java">private int maxRequests = 64; // 最大连接数
private int maxRequestsPerHost = 5; // 单个 host 最大连接数
private @Nullable Runnable idleCallback; // 空闲时的回调
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService; // 线程池
/** Ready async calls in the order they'll be run. */
// 准备执行的异步 Call 的队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
// 正在执行的的异步 Call 的队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
// 正在执行的同步 Call 的队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();</code></pre>
<p>在 Dispatcher 中,默认支持的最大并发连接数是64,每个 host 最多可以有5个并发请求。</p>
<p>下面看一下线程池 <code>executorService</code> 的创建。线程池会在两个地方创建,分别是 Dispatcher 的构造函数或者是 <code>executorService</code> 方法中(如果调用了默认的构造函数):</p>
<pre><code class="java">// 默认构造函数没有创建
public Dispatcher() {
}
// 自定义线程池
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
// 如果没有自定义线程池,则默认创建
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}</code></pre>
<p>Dispatcher 支持自定义的线程池,否则会默认创建一个。在生成 <code>OkHttpClient</code> 对象时,默认调用的是 Dispatcher 无参的构造方法。这个默认线程池通过 `new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,</p>
<pre><code> new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false))` 创建,看上去类似于一个 CachedThreadPool,没有常驻的 core 线程,空闲线程60秒后自动关闭。
</code></pre>
<h3>enqueue</h3>
<p>每个 Call 被添加到某一个队列,如果是同步请求添加到 <code>runningSyncCalls</code> 中:</p>
<pre><code class="java">synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}</code></pre>
<p>异步请求添加的逻辑如下:</p>
<pre><code class="java">synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}</code></pre>
<p>具体步骤是:</p>
<ol>
<li>判断是否超出总共的最大连接数以及单个 host 的最大连接数</li>
<li>如果没有则添加到 <code>runningAsyncCalls</code> 并且提交到线程池执行</li>
<li>否则添加到 <code>readyAsyncCalls</code> 等待后续执行</li>
</ol>
<p>需要注意的是异步请求的 Call 不是原始的 Call,而是被包装为 <code>AsyncCall</code>:</p>
<pre><code class="java">final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
...
@Override protected void execute() {
boolean signalledCallback = false;
try {
// 调用 getResponseWithInterceptorChain
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}</code></pre>
<p><code>AsyncCall</code> 继承自 <code>NamedRunnable</code>,它其实就是一个为线程设置了名字的 Runnable,在其 Run 中调用 <code>execute</code>,所以 <code>AsyncCall</code> 的主要逻辑都写在 <code>execute</code> 中。可以看到最终还是调用了 <code>getResponseWithInterceptorChain</code> 方法,所以后续执行网络请求的逻辑是一样的。在获得 response 之后,就可以调用 <code>responseCallback</code> 返回最终的信息。</p>
<h3>finished</h3>
<p>在上面的代码中,finally 里面执行了 <code>client.dispatcher().finished(this)</code>,在同步请求 <code>RealCall#execute()</code> 中也有类似的一行代码。<code>finished</code> 的作用是让 Dispatcher 从队列中移除已完成的 Call,对于异步请求还会从 <code>readyAsyncCalls</code> 中取出等待中的请求提交给线程池。下面是具体代码:</p>
<pre><code class="java"> /** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
// 异步请求会进入
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
// 找到一个等待队列中的 Call,符合连接数要求时加入 runningAsyncCalls 并提交给线程池执行。
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}</code></pre>
<p>有两个重载的 <code>finished</code> 方法均调用了另一个 pirvate 的 <code>finished</code>,区别在于这个 <code>finished</code> 的最后一个参数 <code>promoteCalls</code>。对于同步请求(参数为 <code>RealCall</code>) <code>promoteCalls</code> 为 <code>false</code>,而异步请求(参数为 <code>AsyncCall</code>) <code>promoteCalls</code> 为 <code>true</code>。 pirvate 的 <code>finished</code> 主要是从队列中移除 Call,异步请求会执行 <code>promoteCalls</code>。<code>promoteCalls</code> 里面主要是从 <code>readyAsyncCalls</code> 取出一个 Call,如果满足最大连接数的要求,则把这个 Call 加入 <code>runningAsyncCalls</code> 并提交给线程池执行。</p>
<p>通过 <code>runningAsyncCalls</code> 和 <code>readyAsyncCalls</code>,Dispatcher 实现了异步请求的调度执行。这里比较巧妙的方式是在 finally 中去执行 <code>readyAsyncCalls</code> 中的请求,避免了 wait/notity 的方式,避免了代码的复杂性。</p>
<h2>总结</h2>
<p>OkHttp 的基本执行流程如下图所示:</p>
<p><img src="/img/remote/1460000012656610?w=398&h=952" alt="图片描述" title="图片描述"></p>
<p>主要是以下步骤:</p>
<ol>
<li>
<code>OkHttpClient</code> 调用 <code>newCall</code> 创建 <code>RealCall</code> 对象,<code>Call</code> 封装了 <code>Request</code>,代表一条即将执行的请求。</li>
<li>根据同步还是异步请求分别调用 <code>RealCall</code> 的 <code>execute</code> 或 <code>enqueue</code> 方法,将<code>Call</code> 加入 <code>Dispatcher</code> 的相应队列中。最终,同步或异步请求都会调用 <code>getResponseWithInterceptorChain</code>。</li>
<li>在 <code>getResponseWithInterceptorChain</code> 中,OkHttp 添加用户自定义以及默认的 inceptors,并用一个 <code>Chain</code> 管理并依次执行每个 Interceptor。</li>
<li>每个 Interceptor 调用 <code>Chain#proceed()</code> 将请求发送给下一级的 Inceptor,并能通过这个方法获得下一级 Interceptor 的 Response。所以上图所示,Request 一级级地往下传递,而获取了网络的 Response 之后一级级地往上传递。</li>
</ol>
<p>OkHttp中一条网络请求的基本流程就是这样,下一篇文章介绍 OkHttp 如何建立连接:<a href="https://segmentfault.com/a/1190000013655431">OkHttp 源码解析(二):建立连接</a>。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
Retrofit 源码解析
https://segmentfault.com/a/1190000006767113
2016-08-30T17:19:52+08:00
2016-08-30T17:19:52+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
2
<h2>简介</h2>
<p>Retrofit 是 Square 推出的 HTTP 框架,主要用于 Android 和 Java。Retrofit 将网络请求变成方法的调用,使用起来非常简洁方便。本文先简要介绍一下 Retrofit 的用法,然后具体分析其源码执行的流程。</p>
<h2>基本用法</h2>
<p>Retrofit 把HTTP API 变成 Java 的接口。下面是 Retrofit 官网的一个例子:</p>
<pre><code>public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}</code></pre>
<p>在 <code>GithubService</code> 接口中有一个方法 <code>listRepos</code>,这个方法用了 <code>@GET</code> 的方式注解,这表明这是一个 <code>GET</code> 请求。在后面的括号中的 <code>users/{user}/repos</code> 是请求的路径,其中的 <code>{user}</code> 表示的是这一部分是动态变化的,它的值由方法的参数传递过来,而这个方法的参数<code>@Path("user") String user</code> 即是用于替换 <code>{user}</code> 。另外注意这个方法的返回值是 <code>Call<List<Repo>></code>。可以看出 Retrofit 用注解的方式来描述一个网络请求相关的参数。</p>
<p>上面才是开始,下面要发出这个网络请求:</p>
<pre><code>Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("octocat");
repos.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
}
});
</code></pre>
<p>可以看出,先是构建了一个 Retrofit 对象,其中传入了 <code>baseUrl</code> 参数,<code>baseUrl</code> 和上面的 <code>GET</code> 方法后面的路径组合起来才是一个完整的 url。除了 <code>baseUrl</code>,还有一个 <code>converterFactory</code>,它是用于把返回的 http response 转换成 Java 对象,对应方法的返回值 <code>Call<List<Repo>></code> 中的 <code>List<Repo>></code>,其中 <code>Repo</code> 是自定义的类。有了Retrofit 对象,接着调用它的 <code>create</code> 方法创建了 <code>GitHubService</code> 的实例,然后就可以调用这个实例的方法来请求网络了。调用 <code>listRepo</code> 方法得到一个 <code>Call</code>对象,然后可以使用 <code>enqueue</code> 或者 <code>execute</code> 来执行发起请求,<code>enqueue</code> 是异步执行,而 <code>execute</code> 是同步执行。</p>
<p>Retrofit 的基本用法就是这样,其它还有一些细节可以查看官网。</p>
<h2>源码分析</h2>
<p>我第一次接触 Retrofit 的时候觉得这个东西挺神奇的,用法跟一般的网络请求不一样。下面就来看看 Retrofit 的源码是怎么实现的。</p>
<h3>Retrofit 的创建</h3>
<p>从 Retrofit 的创建方法可以看出,使用的是 Builder 模式。Retrofit 中有如下的几个关键变量:</p>
<pre><code> //用于缓存解析出来的方法
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
//请求网络的OKHttp的工厂,默认是 OkHttpClient
private final okhttp3.Call.Factory callFactory;
//baseurl
private final HttpUrl baseUrl;
//请求网络得到的response的转换器的集合 默认会加入 BuiltInConverters
private final List<Converter.Factory> converterFactories;
//把Call对象转换成其它类型
private final List<CallAdapter.Factory> adapterFactories;
//用于执行回调 Android中默认是 MainThreadExecutor
private final Executor callbackExecutor;
//是否需要立即解析接口中的方法
private final boolean validateEagerly;
</code></pre>
<p>再看一下Retrofit 中的内部类 Builder 的 <code>build</code> 方法:</p>
<pre><code>public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
//默认创建一个 OkHttpClient
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
//Android 中返回的是 MainThreadExecutor
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}</code></pre>
<p>在创建 Retrofit 的时候,如果没有指定 OkHttpClient,会创建一个默认的。如果没有指定 <code>callbackExecutor</code>,会返回平台默认的,在 Android 中是 <code>MainThreadExecutor</code>,并利用这个构建一个 <code>CallAdapter</code>加入 <code>adapterFactories</code>。</p>
<h3>create 方法</h3>
<p>有了 Retrofit 对象后,便可以通过 <code>create</code> 方法创建网络请求接口类的实例,代码如下:</p>
<pre><code>public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
//提前解析方法
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.如果是Object中的方法,直接调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
//为了兼容 Java8 平台,Android 中不会执行
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//下面是重点,解析方法
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});</code></pre>
<p><code>create</code> 方法接受一个 Class 对象,也就是我们编写的接口,里面含有通过注解标识的请求网络的方法。注意 <code>return</code> 语句部分,这里调用了 <code>Proxy.newProxyInstance</code> 方法,这个很重要,因为用了<strong>动态代理模式</strong>。关于动态代理模式,可以参考这篇文章:<a href="https://link.segmentfault.com/?enc=KrV8W17sV7OHrKHvAbvLEw%3D%3D.B8hhDAqcRb%2Bq9CySdjBOd4AFEEWZPPpZ%2F8av9yCCYe0U7mlBdyCJfnp7y%2Bj8Fy%2FcT9uzHvEyUTGMTht7VqMIVw%3D%3D" rel="nofollow">http://www.codekk.com/blogs/d...</a>。简单的描述就是,<code>Proxy.newProxyInstance</code> 根据传进来的 Class 对象生成了一个实例 A,也就是代理类。每当这个代理类 A 执行某个方法时,总是会调用 <code>InvocationHandler</code>(<code>Proxy.newProxyInstance</code> 中的第三个参数) 的 <code>invoke</code> 方法,在这个方法中可以执行一些操作(这里是解析方法的注解参数等),通过这个方法真正的执行我们编写的接口中的网络请求。</p>
<h3>方法解析和类型转换</h3>
<p>下面具体看一下在 <code>invoke</code> 中解析网络请求方法的几行。首先是 <code> ServiceMethod serviceMethod = loadServiceMethod(method);</code>,其中 <code>loadServiceMethod</code> 代码如下:</p>
<pre><code>ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}</code></pre>
<p>可以看出,这里是先到缓存中找,缓存中没有再去创建。这里创建了 <code>ServiceMethod</code> 对象。<code>ServiceMethod</code> 用于把接口方法的调用转换成一个 HTTP 请求。其实,在 <code>ServiceMethod</code> 中会解析接口中方法的注解、参数等,它还有个 <code>toRequest</code> 方法,用于生成一个 <code>Request</code> 对象。这个 <code>Request</code> 对象就是 OkHttp 中的 <code>Request</code>,代表了一条网络请求(Retrofit 实际上把真正请求网络的操作交给了 OkHttp 执行)。下面是创建 <code>ServiceMethod</code> 的部分代码:</p>
<pre><code>public ServiceMethod build() {
//获取 callAdapter
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw methodError("'"
+ Utils.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
//获取 responseConverter
responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) {
//解析注解
parseMethodAnnotation(annotation);
//省略了一些代码
...
}
}
</code></pre>
<p>在得到 <code>ServiceMethod</code> 对象后,把它连同方法调用的相关参数传给了 <code>OkHttpCall</code> 对象,也就是这行代码: <code>OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);</code>。 下面介绍 <code>OkHttpCall</code>,<code>OkHttpCall</code>继承于 <code>Call</code> 接口。<code>Call</code> 是Retrofit 的基础接口,代表发送网络请求与响应调用,它包含下面几个接口方法:</p>
<ul>
<li>Response<T> execute() throws IOException; //同步执行请求</li>
<li>void enqueue(Callback<T> callback); //异步执行请求,callback 用于回调</li>
<li>boolean isExecuted(); //是否执行过</li>
<li>void cancel(); //取消请求</li>
<li>boolean isCanceled(); //是否取消了</li>
<li>Call<T> clone(); //克隆一条请求</li>
<li>Request request(); //获取原始的request</li>
</ul>
<p><code>OkHttpCall</code> 是 <code>Call</code> 的一个实现类,它里面封装了 <code>OkHttp</code> 中的原生 <code>Call</code>,在这个类里面实现了 <code>execute</code> 以及 <code>enqueue</code> 等方法,其实是调用了 OkHttp 中原生 <code>Call</code> 的对应方法。 </p>
<p>接下来把 <code>OkHttpCall</code> 传给 <code>serviceMethod.callAdapter</code> 对象,这里的callAdapter又是什么?在上面创建 <code>ServiceMethod</code> 的代码中有一行代码: <code>callAdapter = createCallAdapter()</code>,这里创建了 <code>calladapter</code>,在这个代码内部是根据方法的返回类型以及注解去寻找对应的 <code>CallAdapter</code>,去哪里寻找?去 <code>Retrofit</code> 对象的 <code>adapterFactories</code> 集合中找。当我们创建 <code>Retrofit</code> 的时候,可以调用 <code>addCallAdapter</code> 向 <code>adapterFactories</code> 中添加 <code>CallAdapter</code>。在前面的基本用法里面,我们并没有添加任何 <code>CallAdapter</code>,但 <code>adapterFactories</code> 中默认会添加一个 <code>ExecutorCallAdapterFactory</code>,调用其 <code>get</code> 方法便可获得 <code>CallAdapter</code> 对象。</p>
<p>那么 <code>CallAdapter</code> 是干嘛的呢?上面调用了<code>adapt</code> 方法,它是为了把一个 <code>Call</code> 转换成另一种类型,比如当 Retrofit 和 RxJava 结合使用的时候,接口中方法可以返回 <code>Observable<T></code>,这里相当于<strong>适配器模式</strong>。默认情况下得到的是一个 <code>Call</code> 对象,它是<code>ExecutorCallbackCall</code>,代码如下:</p>
<pre><code>public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public <R> Call<R> adapt(Call<R> call) {
return new ExecutorCallbackCall<>(callbackExecutor, call);
}
};</code></pre>
<p>}<br>这个 <code>ExecutorCallbackCall</code> 接受一个 <code>callbackExecutor</code>(Android 中默认为 <code>MainThreadExecutor</code>,把返回的数据传回主线程) 和一个 <code>call</code>,也就是 <code>OkhttpCall</code>。看下 <code>ExecutorCallbackCall</code> 部分代码:</p>
<pre><code>static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
delegate.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
}
});
}
@Override public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
callback.onFailure(ExecutorCallbackCall.this, t);
}
});
}
});
}</code></pre>
<p>在 <code>enqueue</code> 方法中,调用了 <code>OkHttpCall</code> 的 <code>enqueue</code>,所以这里相当于<strong>静态的代理模式</strong>。<code>OkHttpCall</code> 中的 <code>enqueue</code> 其实又调用了原生的 <code>OkHttp</code> 中的 <code>enqueue</code>,这里才真正发出了网络请求,部分代码如下:</p>
<pre><code>@Override public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
//真正请求网络的 call
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
//省略了部分发代码
...
call = rawCall;
//enqueue 异步执行
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
throws IOException {
Response<T> response;
try {
//解析数据 会用到 conveterFactory,把 response 转换为对应 Java 类型
response = parseResponse(rawResponse);
} catch (Throwable e) {
callFailure(e);
return;
}
callSuccess(response);
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callSuccess(Response<T> response) {
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}</code></pre>
<p>OkHttp 获取数据后,解析数据并回调 callback 响应的方法,一次网络请求便完成了。</p>
<h2>总结</h2>
<p>Retrofit 整个框架的代码不算太多,还是比较易读的。主要就是通过动态代理的方式把 Java 接口中的解析为响应的网络请求,然后交给 OkHttp 去执行。并且可以适配不同的 <code>CallAdapter</code>,可以方便地与 RxJava 结合使用。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
Java 泛型总结(三):通配符的使用
https://segmentfault.com/a/1190000005337789
2016-05-25T08:05:00+08:00
2016-05-25T08:05:00+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
48
<h2>简介</h2>
<p>前两篇文章介绍了泛型的基本用法、类型擦除以及泛型数组。在泛型的使用中,还有个重要的东西叫通配符,本文介绍通配符的使用。</p>
<p>这个系列的另外两篇文章:</p>
<ul>
<li><a href="https://segmentfault.com/a/1190000005179142">Java 泛型总结(一):基本用法与类型擦除</a></li>
<li><a href="https://segmentfault.com/a/1190000005179147">Java 泛型总结(二):泛型与数组</a></li>
</ul>
<h2>数组的协变</h2>
<p>在了解通配符之前,先来了解一下数组。Java 中的数组是<strong>协变</strong>的,什么意思?看下面的例子:</p>
<pre><code>class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
Fruit[] fruit = new Apple[10];
fruit[0] = new Apple(); // OK
fruit[1] = new Jonathan(); // OK
// Runtime type is Apple[], not Fruit[] or Orange[]:
try {
// Compiler allows you to add Fruit:
fruit[0] = new Fruit(); // ArrayStoreException
} catch(Exception e) { System.out.println(e); }
try {
// Compiler allows you to add Oranges:
fruit[0] = new Orange(); // ArrayStoreException
} catch(Exception e) { System.out.println(e); }
}
} /* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
*///:~</code></pre>
<p><code>main</code> 方法中的第一行,创建了一个 <code>Apple</code> 数组并把它赋给 <code>Fruit</code> 数组的引用。这是有意义的,<code>Apple</code> 是 <code>Fruit</code> 的子类,一个 <code>Apple</code> 对象也是一种 <code>Fruit</code> 对象,所以一个 <code>Apple</code> 数组也是一种 <code>Fruit</code> 的数组。这称作<strong>数组的协变</strong>,Java 把数组设计为协变的,对此是有争议的,有人认为这是一种缺陷。</p>
<p>尽管 <code>Apple[]</code> 可以 “向上转型” 为 <code>Fruit[]</code>,但数组元素的实际类型还是 <code>Apple</code>,我们只能向数组中放入 <code>Apple</code>或者 <code>Apple</code> 的子类。在上面的代码中,向数组中放入了 <code>Fruit</code> 对象和 <code>Orange</code> 对象。对于编译器来说,这是可以通过编译的,但是在运行时期,JVM 能够知道数组的实际类型是 <code>Apple[]</code>,所以当其它对象加入数组的时候就会抛出异常。</p>
<p>泛型设计的目的之一是要使这种运行时期的错误在编译期就能发现,看看用泛型容器类来代替数组会发生什么:</p>
<pre><code>// Compile Error: incompatible types:
ArrayList<Fruit> flist = new ArrayList<Apple>();
</code></pre>
<p>上面的代码根本就无法编译。当涉及到泛型时, 尽管 <code>Apple</code> 是 <code>Fruit</code> 的子类型,但是 <code>ArrayList<Apple></code> 不是 <code>ArrayList<Fruit></code> 的子类型,泛型不支持协变。</p>
<h2>使用通配符</h2>
<p>从上面我们知道,<code>List<Number> list = ArrayList<Integer></code> 这样的语句是无法通过编译的,尽管 <code>Integer</code> 是 <code>Number</code> 的子类型。那么如果我们确实需要建立这种 “向上转型” 的关系怎么办呢?这就需要通配符来发挥作用了。</p>
<h3>上边界限定通配符</h3>
<p>利用 <code><? extends Fruit></code> 形式的通配符,可以实现泛型的向上转型:</p>
<pre><code>public class GenericsAndCovariance {
public static void main(String[] args) {
// Wildcards allow covariance:
List<? extends Fruit> flist = new ArrayList<Apple>();
// Compile Error: can’t add any type of object:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // Legal but uninteresting
// We know that it returns at least Fruit:
Fruit f = flist.get(0);
}
}</code></pre>
<p>上面的例子中, <code>flist</code> 的类型是 <code>List<? extends Fruit></code>,我们可以把它读作:一个类型的 List, 这个类型可以是继承了 <code>Fruit</code> 的某种类型。注意,<strong>这并不是说这个 List 可以持有</strong> <code>Fruit</code> <strong>的任意类型</strong>。通配符代表了一种特定的类型,它表示 “某种特定的类型,但是 <code>flist</code> 没有指定”。这样不太好理解,具体针对这个例子解释就是,<code>flist</code> 引用可以指向某个类型的 List,只要这个类型继承自 <code>Fruit</code>,可以是 <code>Fruit</code> 或者 <code>Apple</code>,比如例子中的 <code>new ArrayList<Apple></code>,但是为了向上转型给 <code>flist</code>,<code>flist</code> 并不关心这个具体类型是什么。</p>
<p>如上所述,通配符 <code>List<? extends Fruit></code> 表示某种特定类型 ( <code>Fruit</code> 或者其子类 ) 的 List,但是并不关心这个实际的类型到底是什么,反正是 <code>Fruit</code> 的子类型,<code>Fruit</code> 是它的上边界。那么对这样的一个 List 我们能做什么呢?其实如果我们不知道这个 List 到底持有什么类型,怎么可能安全的添加一个对象呢?在上面的代码中,向 <code>flist</code> 中添加任何对象,无论是 <code>Apple</code> 还是 <code>Orange</code> 甚至是 <code>Fruit</code> 对象,编译器都不允许,唯一可以添加的是 <code>null</code>。所以如果做了泛型的向上转型 (<code>List<? extends Fruit> flist = new ArrayList<Apple>()</code>),那么我们也就失去了向这个 List 添加任何对象的能力,即使是 <code>Object</code> 也不行。</p>
<p>另一方面,如果调用某个返回 <code>Fruit</code> 的方法,这是安全的。因为我们知道,在这个 List 中,不管它实际的类型到底是什么,但肯定能转型为 <code>Fruit</code>,所以编译器允许返回 <code>Fruit</code>。</p>
<p>了解了通配符的作用和限制后,好像任何接受参数的方法我们都不能调用了。其实倒也不是,看下面的例子:</p>
<pre><code>public class CompilerIntelligence {
public static void main(String[] args) {
List<? extends Fruit> flist =
Arrays.asList(new Apple());
Apple a = (Apple)flist.get(0); // No warning
flist.contains(new Apple()); // Argument is ‘Object’
flist.indexOf(new Apple()); // Argument is ‘Object’
//flist.add(new Apple()); 无法编译
}
}</code></pre>
<p>在上面的例子中,<code>flist</code> 的类型是 <code>List<? extends Fruit></code>,泛型参数使用了受限制的通配符,所以我们失去了向其中加入任何类型对象的例子,最后一行代码无法编译。</p>
<p>但是 <code>flist</code> 却可以调用 <code>contains</code> 和 <code>indexOf</code> 方法,它们都接受了一个 <code>Apple</code> 对象做参数。如果查看 <code>ArrayList</code> 的源代码,可以发现 <code>add()</code> 接受一个泛型类型作为参数,但是 <code>contains</code> 和 <code>indexOf</code> 接受一个 <code>Object</code> 类型的参数,下面是它们的方法签名:</p>
<pre><code>public boolean add(E e)
public boolean contains(Object o)
public int indexOf(Object o)
</code></pre>
<p>所以如果我们指定泛型参数为 <code><? extends Fruit></code> 时,<code>add()</code> 方法的参数变为 <code>? extends Fruit</code>,编译器无法判断这个参数接受的到底是 <code>Fruit</code> 的哪种类型,所以它不会接受任何类型。</p>
<p>然而,<code>contains</code> 和 <code>indexOf</code> 的类型是 <code>Object</code>,并没有涉及到通配符,所以编译器允许调用这两个方法。这意味着一切取决于泛型类的编写者来决定那些调用是 “安全” 的,并且用 <code>Object</code> 作为这些安全方法的参数。如果某些方法不允许类型参数是通配符时的调用,这些方法的参数应该用类型参数,比如 <code>add(E e)</code>。</p>
<p>当我们自己编写泛型类时,上面介绍的就有用了。下面编写一个 <code>Holder</code> 类:</p>
<pre><code>public class Holder<T> {
private T value;
public Holder() {}
public Holder(T val) { value = val; }
public void set(T val) { value = val; }
public T get() { return value; }
public boolean equals(Object obj) {
return value.equals(obj);
}
public static void main(String[] args) {
Holder<Apple> Apple = new Holder<Apple>(new Apple());
Apple d = Apple.get();
Apple.set(d);
// Holder<Fruit> Fruit = Apple; // Cannot upcast
Holder<? extends Fruit> fruit = Apple; // OK
Fruit p = fruit.get();
d = (Apple)fruit.get(); // Returns ‘Object’
try {
Orange c = (Orange)fruit.get(); // No warning
} catch(Exception e) { System.out.println(e); }
// fruit.set(new Apple()); // Cannot call set()
// fruit.set(new Fruit()); // Cannot call set()
System.out.println(fruit.equals(d)); // OK
}
} /* Output: (Sample)
java.lang.ClassCastException: Apple cannot be cast to Orange
true
*///:~
</code></pre>
<p>在 <code>Holer</code> 类中,<code>set()</code> 方法接受类型参数 <code>T</code> 的对象作为参数,<code>get()</code> 返回一个 <code>T</code> 类型,而 <code>equals()</code> 接受一个 <code>Object</code> 作为参数。<code>fruit</code> 的类型是 <code>Holder<? extends Fruit></code>,所以<code>set()</code>方法不会接受任何对象的添加,但是 <code>equals()</code> 可以正常工作。</p>
<h3>下边界限定通配符</h3>
<p>通配符的另一个方向是 “超类型的通配符“: <code>? super T</code>,<code>T</code> 是类型参数的下界。使用这种形式的通配符,我们就可以 ”传递对象” 了。还是用例子解释:</p>
<pre><code>public class SuperTypeWildcards {
static void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruit()); // Error
}
}</code></pre>
<p><code>writeTo</code> 方法的参数 <code>apples</code> 的类型是 <code>List<? super Apple></code>,它表示某种类型的 List,这个类型是 <code>Apple</code> 的基类型。也就是说,我们不知道实际类型是什么,但是这个类型肯定是 <code>Apple</code> 的父类型。因此,我们可以知道向这个 List 添加一个 <code>Apple</code> 或者其子类型的对象是安全的,这些对象都可以向上转型为 <code>Apple</code>。但是我们不知道加入 <code>Fruit</code> 对象是否安全,因为那样会使得这个 List 添加跟 <code>Apple</code> 无关的类型。</p>
<p>在了解了子类型边界和超类型边界之后,我们就可以知道如何向泛型类型中 “写入” ( 传递对象给方法参数) 以及如何从泛型类型中 “读取” ( 从方法中返回对象 )。下面是一个例子:</p>
<pre><code>public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src)
{
for (int i=0; i<src.size(); i++)
dest.set(i,src.get(i));
}
}</code></pre>
<p><code>src</code> 是原始数据的 List,因为要从这里面读取数据,所以用了上边界限定通配符:<code><? extends T></code>,取出的元素转型为 <code>T</code>。<code>dest</code> 是要写入的目标 List,所以用了下边界限定通配符:<code><? super T></code>,可以写入的元素类型是 <code>T</code> 及其子类型。</p>
<h3>无边界通配符</h3>
<p>还有一种通配符是无边界通配符,它的使用形式是一个单独的问号:<code>List<?></code>,也就是没有任何限定。不做任何限制,跟不用类型参数的 <code>List</code> 有什么区别呢?</p>
<p><code>List<?> list</code> 表示 <code>list</code> 是持有某种特定类型的 List,但是不知道具体是哪种类型。那么我们可以向其中添加对象吗?当然不可以,因为并不知道实际是哪种类型,所以不能添加任何类型,这是不安全的。而单独的 <code>List list</code> ,也就是没有传入泛型参数,表示这个 list 持有的元素的类型是 <code>Object</code>,因此可以添加任何类型的对象,只不过编译器会有警告信息。</p>
<h2>总结</h2>
<p>通配符的使用可以对泛型参数做出某些限制,使代码更安全,对于上边界和下边界限定的通配符总结如下:</p>
<ul>
<li>使用 <code>List<? extends C> list</code> 这种形式,表示 list 可以引用一个 <code>ArrayList</code> ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素类型是 <code>C</code> 的子类型 ( 包含 <code>C</code> 本身)的一种。</li>
<li>使用 <code>List<? super C> list</code> 这种形式,表示 list 可以引用一个 <code>ArrayList</code> ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素就类型是 <code>C</code> 的超类型 ( 包含 <code>C</code> 本身 ) 的一种。</li>
</ul>
<p>大多数情况下泛型的使用比较简单,但是如果自己编写支持泛型的代码需要对泛型有深入的了解。这几篇文章介绍了泛型的基本用法、类型擦除、泛型数组以及通配符的使用,涵盖了最常用的要点,泛型的总结就写到这里。</p>
<p><strong>参考</strong></p>
<ul><li>Java 编程思想</li></ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
Java 泛型总结(二):泛型与数组
https://segmentfault.com/a/1190000005179147
2016-05-24T07:25:00+08:00
2016-05-24T07:25:00+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
26
<h2>简介</h2>
<p>上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面:</p>
<ol>
<li>数组创建后大小便固定,但效率更高</li>
<li>数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查</li>
<li>数组可以持有原始类型 ( int,float等 ),不过有了自动装箱,容器类看上去也能持有原始类型了</li>
</ol>
<p>那么当数组遇到泛型会怎样? 能否创建泛型数组呢?这是这篇文章的主要内容。</p>
<p>这个系列的另外两篇文章:</p>
<ul>
<li><a href="https://segmentfault.com/a/1190000005179142">Java 泛型总结(一):基本用法与类型擦除</a></li>
<li><a href="https://segmentfault.com/a/1190000005337789">Java 泛型总结(三):通配符的使用</a></li>
</ul>
<h2>泛型数组</h2>
<h3>如何创建泛型数组</h3>
<p>如果有一个类如下:</p>
<pre><code> class Generic<T> {
}</code></pre>
<p>如果要创建一个泛型数组,应该是这样: <code>Generic<Integer> ga = new Generic<Integer>[]</code>。不过行代码会报错,也就是说不能直接创建泛型数组。</p>
<p>那么如果要使用泛型数组怎么办?一种方案是使用 <code>ArrayList</code>,比如下面的例子:</p>
<pre><code>public class ListOfGenerics<T> {
private List<T> array = new ArrayList<T>();
public void add(T item) { array.add(item); }
public T get(int index) { return array.get(index); }
}
</code></pre>
<p>如何创建真正的泛型数组呢?我们不能直接创建,但可以定义泛型数组的引用。比如:</p>
<pre><code>public class ArrayOfGenericReference {
static Generic<Integer>[] gia;
}</code></pre>
<p><code>gia</code> 是一个指向泛型数组的引用,这段代码可以通过编译。但是,我们并不能创建这个确切类型的数组,也就是不能使用 <code>new Generic<Integer>[]</code>。具体参见下面的例子:</p>
<pre><code>public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
// Compiles; produces ClassCastException:
//! gia = (Generic<Integer>[])new Object[SIZE];
// Runtime type is the raw (erased) type:
gia = (Generic<Integer>[])new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic<Integer>();
//! gia[1] = new Object(); // Compile-time error
// Discovers type mismatch at compile time:
//! gia[2] = new Generic<Double>();
Generic<Integer> g = gia[0];
}
} /*输出:
Generic[]
*///:~</code></pre>
<p>数组能追踪元素的实际类型,这个类型是在数组创建的时候建立的。上面被注释掉的一行代码: <code>gia = (Generic<Integer>[])new Object[SIZE]</code>,数组在创建的时候是一个 <code>Object</code> 数组,如果转型便会报错。<strong>成功创建泛型数组的唯一方式是创建一个类型擦除的数组,然后转型</strong>,如代码: <code>gia = (Generic<Integer>[])new Generic[SIZE]</code>。<code>gia</code> 的 <code>Class</code> 对象输出的名字是 <code>Generic[]</code>。</p>
<p>我个人的理解是:由于类型擦除,所以 <code>Generic<Integer></code> 相当于初始类型 <code>Generic</code>,那么 <code>gia = (Generic<Integer>[])new Generic[SIZE]</code> 中的转型其实还是转型为 <code>Generic[]</code>,看上去像没转,但是多了编译器对参数的检查和自动转型,向数组插入 <code>new Object()</code> 和 <code>new Generic<Double>()</code> 均会报错,而 <code>gia[0]</code> 取出给 <code>Generic<Integer></code> 也不需要我们手动转型。</p>
<h3>使用 T[] array</h3>
<p>上面的例子中,元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子:</p>
<pre><code>public class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[])new Object[sz]; // 创建泛型数组
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) { return array[index]; }
// Method that exposes the underlying representation:
public T[] rep() { return array; } //返回数组 会报错
public static void main(String[] args) {
GenericArray<Integer> gai =
new GenericArray<Integer>(10);
// This causes a ClassCastException:
//! Integer[] ia = gai.rep();
// This is OK:
Object[] oa = gai.rep();
}
}</code></pre>
<p>在上面的代码中,泛型数组的创建是创建一个 <code>Object</code> 数组,然后转型为 <code>T[]</code>。但数组实际的类型还是 <code>Object[]</code>。在调用 <code>rep()</code>方法的时候,就报 <code>ClassCastException</code> 异常了,因为 <code>Object[]</code> 无法转型为 <code>Integer[]</code>。</p>
<p><strong>那创建泛型数组的代码</strong> <code>array = (T[])new Object[sz]</code> <strong>为什么不会报错呢</strong>?我的理解和前面介绍的类似,由于类型擦除,相当于转型为 <code>Object[]</code>,看上去就是没转,但是多了编译器的参数检查和自动转型。而如果把泛型参数改成 <code><T extends Integer></code>,那么因为类型是擦除到第一个边界,所以 <code>array = (T[])new Object[sz]</code> 中相当于转型为 <code>Integer[]</code>,这应该会报错。下面是实验的代码:</p>
<pre><code>public class GenericArray<T extends Integer> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[])new Object[sz]; // 创建泛型数组
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) { return array[index]; }
// Method that exposes the underlying representation:
public T[] rep() { return array; } //返回数组 会报错
public static void main(String[] args) {
GenericArray<Integer> gai =
new GenericArray<Integer>(10);
// This causes a ClassCastException:
//! Integer[] ia = gai.rep();
// This is OK:
Object[] oa = gai.rep();
}
}</code></pre>
<p>相比于原始的版本,上面的代码只修改了第一行,把 <code><T></code> 改成了 <code><T extends Integer></code>,那么不用调用 <code>rep()</code>,在创建泛型数组的时候就会报错。下面是运行结果:</p>
<pre><code>Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at GenericArray.<init>(GenericArray.java:15)
</code></pre>
<h3>使用 Object[] array</h3>
<p>由于擦除,运行期的数组类型只能是 <code>Object[]</code>,如果我们立即把它转型为 <code>T[]</code>,那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 <code>Object[]</code> 数组,在取出元素的时候再转型。看下面的例子:</p>
<pre><code>public class GenericArray2<T> {
private Object[] array;
public GenericArray2(int sz) {
array = new Object[sz];
}
public void put(int index, T item) {
array[index] = item;
}
@SuppressWarnings("unchecked")
public T get(int index) { return (T)array[index]; }
@SuppressWarnings("unchecked")
public T[] rep() {
return (T[])array; // Warning: unchecked cast
}
public static void main(String[] args) {
GenericArray2<Integer> gai =
new GenericArray2<Integer>(10);
for(int i = 0; i < 10; i ++)
gai.put(i, i);
for(int i = 0; i < 10; i ++)
System.out.print(gai.get(i) + " ");
System.out.println();
try {
Integer[] ia = gai.rep();
} catch(Exception e) { System.out.println(e); }
}
} /* Output: (Sample)
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*///:~</code></pre>
<p>现在内部数组的呈现不是 <code>T[]</code> 而是 <code>Object[]</code>,当 <code>get()</code> 被调用的时候数组的元素被转型为 <code>T</code>,这正是元素的实际类型。不过调用 <code>rep()</code> 还是会报错, 因为数组的实际类型依然是<code>Object[]</code>,终究不能转换为其它类型。使用 <code>Object[]</code> 代替 <code>T[]</code> 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。</p>
<h3>使用类型标识</h3>
<p>其实使用 <code>Class</code> 对象作为类型标识是更好的设计:</p>
<pre><code>public class GenericArrayWithTypeToken<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class<T> type, int sz) {
array = (T[])Array.newInstance(type, sz);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) { return array[index]; }
// Expose the underlying representation:
public T[] rep() { return array; }
public static void main(String[] args) {
GenericArrayWithTypeToken<Integer> gai =
new GenericArrayWithTypeToken<Integer>(
Integer.class, 10);
// This now works:
Integer[] ia = gai.rep();
}
}
</code></pre>
<p>在构造器中传入了 <code>Class<T></code> 对象,通过 <code>Array.newInstance(type, sz)</code> 创建一个数组,这个方法会用参数中的 <code>Class</code> 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 <code>Object</code>,而是 <code>T</code>。这个方法返回 <code>Object</code> 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 <code>rep()</code> 方法,因为数组的实际类型与 <code>T[]</code> 是一致的。这是比较推荐的创建泛型数组的方法。</p>
<h2>总结</h2>
<p>数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用,有兴趣的读者可进入下一篇:<a href="https://segmentfault.com/a/1190000005337789">Java 泛型总结(三):通配符的使用</a>。</p>
<p><strong>参考</strong></p>
<ul><li>Java 编程思想</li></ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
Java 泛型总结(一):基本用法与类型擦除
https://segmentfault.com/a/1190000005179142
2016-05-23T08:20:00+08:00
2016-05-23T08:20:00+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
35
<h2>简介</h2>
<p>Java 在 1.5 引入了泛型机制,泛型本质是参数化类型,也就是说变量的类型是一个参数,在使用时再指定为具体类型。泛型可以用于类、接口、方法,通过使用泛型可以使代码更简单、安全。然而 Java 中的泛型使用了类型擦除,所以只是伪泛型。这篇文章对泛型的使用以及存在的问题做个总结,主要参考自 《Java 编程思想》。</p>
<p>这个系列的另外两篇文章:</p>
<ul>
<li><a href="https://segmentfault.com/a/1190000005179147">Java 泛型总结(二):泛型与数组</a></li>
<li><a href="https://segmentfault.com/a/1190000005337789">Java 泛型总结(三):通配符的使用</a></li>
</ul>
<h2>基本用法</h2>
<h3>泛型类</h3>
<p>如果有一个类 <code>Holder</code> 用于包装一个变量,这个变量的类型可能是任意的,怎么编写 <code>Holder</code> 呢?在没有泛型之前可以这样:</p>
<pre><code>public class Holder1 {
private Object a;
public Holder1(Object a) {
this.a = a;
}
public void set(Object a) {
this.a = a;
}
public Object get(){
return a;
}
public static void main(String[] args) {
Holder1 holder1 = new Holder1("not Generic");
String s = (String) holder1.get();
holder1.set(1);
Integer x = (Integer) holder1.get();
}</code></pre>
<p>}</p>
<p>在 <code>Holder1</code> 中,有一个用 <code>Object</code> 引用的变量。因为任何类型都可以向上转型为 <code>Object</code>,所以这个 <code>Holder</code> 可以接受任何类型。在取出的时候 <code>Holder</code> 只知道它保存的是一个 <code>Object</code> 对象,所以要强制转换为对应的类型。在 <code>main</code> 方法中, <code>holder1</code> 先是保存了一个字符串,也就是 <code>String</code> 对象,接着又变为保存一个 <code>Integer</code> 对象(参数 <code>1</code> 会自动装箱)。从 <code>Holder</code> 中取出变量时强制转换已经比较麻烦,这里还要记住不同的类型,要是转错了就会出现运行时异常。</p>
<p>下面看看 <code>Holder</code> 的泛型版本:</p>
<pre><code>public class Holder2<T> {
private T a;
public Holder2(T a) {
this.a = a;
}
public T get() {
return a;
}
public void set(T a) {
this.a = a;
}
public static void main(String[] args) {
Holder2<String> holder2 = new Holder2<>("Generic");
String s = holder2.get();
holder2.set("test");
holder2.set(1);//无法编译 参数 1 不是 String 类型
}</code></pre>
<p>}</p>
<p>在 <code>Holder2</code> 中, 变量 <code>a</code> 是一个参数化类型 <code>T</code>,<code>T</code> 只是一个标识,用其它字母也是可以的。创建 <code>Holder2</code> 对象的时候,在尖括号中传入了参数 <code>T</code> 的类型,那么在这个对象中,所有出现 <code>T</code> 的地方相当于都用 <code>String</code> 替换了。现在的 <code>get</code> 的取出来的不是 <code>Object</code> ,而是 <code>String</code> 对象,因此不需要类型转换。另外,当调用 <code>set</code> 时,只能传入 <code>String</code> 类型,否则编译无法通过。这就保证了 <code>holder2</code> 中的类型安全,避免由于不小心传入错误的类型。</p>
<p>通过上面的例子可以看出泛使得代码更简便、安全。引入泛型之后,Java 库的一些类,比如常用的容器类也被改写为支持泛型,我们使用的时候都会传入参数类型,如:<code>ArrayList<Integer> list = ArrayList<>();</code>。</p>
<h3>泛型方法</h3>
<p>泛型不仅可以针对类,还可以单独使某个方法是泛型的,举个例子:</p>
<pre><code>public class GenericMethod {
public <K,V> void f(K k,V v) {
System.out.println(k.getClass().getSimpleName());
System.out.println(v.getClass().getSimpleName());
}
public static void main(String[] args) {
GenericMethod gm = new GenericMethod();
gm.f(new Integer(0),new String("generic"));
}
}
代码输出:
Integer
String
</code></pre>
<p><code>GenericMethod</code> 类本身不是泛型的,创建它的对象的时候不需要传入泛型参数,但是它的方法 <code>f</code> 是泛型方法。在返回类型之前是它的参数标识 <code><K,V></code>,注意这里有两个泛型参数,所以泛型参数可以有多个。</p>
<p>调用泛型方法时可以不显式传入泛型参数,上面的调用就没有。这是因为编译器会使用参数类型推断,根据传入的实参的类型 (这里是 <code>integer</code> 和 <code>String</code>) 推断出 <code>K</code> 和 <code>V</code> 的类型。</p>
<h2>类型擦除</h2>
<h3>什么是类型擦除</h3>
<p>Java 的泛型使用了类型擦除机制,这个引来了很大的争议,以至于 Java 的泛型功能受到限制,只能说是”伪泛型“。什么叫类型擦除呢?简单的说就是,类型参数只存在于编译期,在运行时,Java 的虚拟机 ( JVM ) 并不知道泛型的存在。先看个例子:</p>
<pre><code>public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}</code></pre>
<p>上面的代码有两个不同的 <code>ArrayList</code>:<code>ArrayList<Integer></code> 和 <code>ArrayList<String></code>。在我们看来它们的参数化类型不同,一个保存整性,一个保存字符串。但是通过比较它们的 <code>Class</code> 对象,上面的代码输出是 <code>true</code>。这说明在 JVM 看来它们是同一个类。而在 C++、C# 这些支持真泛型的语言中,它们就是不同的类。</p>
<p>泛型参数会擦除到它的第一个边界,比如说上面的 <code>Holder2</code> 类,参数类型是一个单独的 <code>T</code>,那么就擦除到 <code>Object</code>,相当于所有出现 <code>T</code> 的地方都用 <code>Object</code> 替换。所以在 JVM 看来,保存的变量 <code>a</code> 还是 <code>Object</code> 类型。之所以取出来自动就是我们传入的参数类型,这是因为编译器在编译生成的字节码文件中插入了类型转换的代码,不需要我们手动转型了。如果参数类型有边界那么就擦除到它的第一个边界,这个下一节再说。</p>
<h3>擦除带来的问题</h3>
<p>擦除会出现一些问题,下面是一个例子:</p>
<pre><code>class HasF {
public void f() {
System.out.println("HasF.f()");
}
}
public class Manipulator<T> {
private T obj;
public Manipulator(T obj) {
this.obj = obj;
}
public void manipulate() {
obj.f(); //无法编译 找不到符号 f()
}
public static void main(String[] args) {
HasF hasF = new HasF();
Manipulator<HasF> manipulator = new Manipulator<>(hasF);
manipulator.manipulate();
}</code></pre>
<p>}</p>
<p>上面的 <code>Manipulator</code> 是一个泛型类,内部用一个泛型化的变量 <code>obj</code>,在 <code>manipulate</code> 方法中,调用了 <code>obj</code> 的方法 <code>f()</code>,但是这行代码无法编译。因为类型擦除,编译器不确定 <code>obj</code> 是否有 <code>f()</code> 方法。解决这个问题的方法是给 <code>T</code> 一个边界:</p>
<pre><code>class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T x) { obj = x; }
public void manipulate() { obj.f(); }
}</code></pre>
<p>现在 <code>T</code> 的类型是 <code><T extends HasF></code>,这表示 <code>T</code> 必须是 <code>HasF</code> 或者 <code>HasF</code> 的导出类型。这样,调用 <code>f()</code> 方法才安全。<code>HasF</code> 就是 <code>T</code> 的边界,因此通过类型擦除后,所有出现 <code>T</code> 的<br>地方都用 <code>HasF</code> 替换。这样编译器就知道 <code>obj</code> 是有方法 <code>f()</code> 的。</p>
<p>但是这样就抵消了泛型带来的好处,上面的类完全可以改成这样:</p>
<pre><code>class Manipulator3 {
private HasF obj;
public Manipulator3(HasF x) { obj = x; }
public void manipulate() { obj.f(); }
}
</code></pre>
<p>所以泛型只有在比较复杂的类中才体现出作用。但是像 <code><T extends HasF></code> 这种形式的东西不是完全没有意义的。如果类中有一个返回 <code>T</code> 类型的方法,泛型就有用了,因为这样会返回准确类型。比如下面的例子:</p>
<pre><code>class ReturnGenericType<T extends HasF> {
private T obj;
public ReturnGenericType(T x) { obj = x; }
public T get() { return obj; }
}</code></pre>
<p>这里的 <code>get()</code> 方法返回的是泛型参数的准确类型,而不是 <code>HasF</code>。</p>
<h3>类型擦除的补偿</h3>
<p>类型擦除导致泛型丧失了一些功能,任何在运行期需要知道确切类型的代码都无法工作。比如下面的例子:</p>
<pre><code>
public class Erased<T> {
private final int SIZE = 100;
public static void f(Object arg) {
if(arg instanceof T) {} // Error
T var = new T(); // Error
T[] array = new T[SIZE]; // Error
T[] array = (T)new Object[SIZE]; // Unchecked warning
}
}</code></pre>
<p>通过 <code>new T()</code> 创建对象是不行的,一是由于类型擦除,二是由于编译器不知道 <code>T</code> 是否有默认的构造器。一种解决的办法是传递一个工厂对象并且通过它创建新的实例。</p>
<pre><code>interface FactoryI<T> {
T create();
}
class Foo2<T> {
private T x;
public <F extends FactoryI<T>> Foo2(F factory) {
x = factory.create();
}
// ...
}
class IntegerFactory implements FactoryI<Integer> {
public Integer create() {
return new Integer(0);
}
}
class Widget {
public static class Factory implements FactoryI<Widget> {
public Widget create() {
return new Widget();
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
}
</code></pre>
<p>另一种解决的方法是利用模板设计模式:</p>
<pre><code>abstract class GenericWithCreate<T> {
final T element;
GenericWithCreate() { element = create(); }
abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X> {
X create() { return new X(); }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
Creator c = new Creator();
c.f();
}
}
</code></pre>
<p>具体类型的创建放到了子类继承父类时,在 <code>create</code> 方法中创建实际的类型并返回。</p>
<h2>总结</h2>
<p>本文介绍了 Java 泛型的使用,以及类型擦除相关的问题。一般情况下泛型的使用比较简单,但是某些情况下,尤其是自己编写使用泛型的类或者方法时要注意类型擦除的问题。接下来会介绍数组与泛型的关系以及通配符的使用,有兴趣的读者可进入下一篇:<a href="https://segmentfault.com/a/1190000005179147">Java 泛型总结(二):泛型与数组</a>。</p>
<p><strong>参考</strong></p>
<ul><li>Java 编程思想</li></ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞支持一下(^_^)</em></strong></p>
Android Activity启动模式总结
https://segmentfault.com/a/1190000004996942
2016-04-24T16:00:44+08:00
2016-04-24T16:00:44+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
3
<h2>简介</h2>
<p>Activity 作为 Android 四大组件之一,几乎是被接触得最多的。Activity 中有个启动模式的概念,分别是 <code>standard</code>、<code>singleTop</code>、<code>singleTask</code> 以及 <code>singleinstance</code>,这篇文章总结一下这四种启动模式的特点。</p>
<h2>任务栈</h2>
<p>当我们打开一个 APP,第一个出现的 Activity 是我们指定的默认 Activity,通过这个 Activity 可以跳转到其它 Activity,按返回键可以依次返回到上一个 Activity。这是因为系统把我们打开的 Activity 放在一个任务栈中。打开第一个 Activity 时,系统会新建一个任务栈,如果继续打开新的 Activity,会创建新 Activity 的实例并且放到任务栈的栈顶,返回时将栈顶的 Activity 出栈,新的栈顶 Activity 将呈现在界面上,过程如下图。</p>
<p><img src="/img/bVu73j?w=617&h=195" alt="图片描述" title="图片描述"></p>
<p>这是默认情况下的任务的入栈出栈,如果指定了不同的启动模式将会有不同的表现。</p>
<h2>standard</h2>
<p><code>standard</code> 是标准启动模式,当我们没有指定 Activity 的启动模式时,默认就是这种模式。在 <code>standard</code> 模式下,每次启动一个 Activity 都会创建一个新的实例,它的 <code>onCreate</code>、<code>onStart</code> 以及 <code>onResume</code>均会被调用。这个新创建的 Activity将会放在启动它的 Activity 所在的任务栈的栈顶。</p>
<ul>
<li>比如 Activity A 在栈 S ,它启动了 Activity B(<code>standard</code> 模式),那么 B 将会进入 A 所在的栈 S。</li>
<li>如果在没有任务栈的情况下启动 <code>standard</code> 模式的 Activity,比如在 Service 中,此时新的 Activity 没有任务栈可入,会出现异常:<br> Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?<p>此时应该为这个 Activity 指定 <code>FLAG_ACTIVITY_NEW_TASK</code>,这样就会新建一个任务栈。</p>
</li>
</ul>
<h2>singleTop</h2>
<p><code>singleTop</code> 是栈顶复用模式。在这种模式下,如果新启动的 Activity 已经在任务栈的栈顶了,那么就不会重新创建新的实例,而是调用这个 Activity 的 <code>onPause</code>、<code>onNewIntent</code> 以及 <code>onResume</code> 方法。如果新启动的 Activity 不是位于栈顶,那么还是会重新创建。</p>
<ul>
<li>比如现在栈内情况是 ABCD 四个Activity,A 位于栈底,D 位于栈顶。如果 D 的启动模式为 <code>singleTop</code>,那么不会再次创建 D 的实例,栈内依然是 ABCD。</li>
<li>如果上面的 D 为 <code>standard</code> 启动模式,那么栈内将变为 ABCDD。</li>
</ul>
<h2>singleTask</h2>
<p><code>singleTask</code> 是栈内复用模式。这是最复杂的一种模式,因为它可能涉及到多个栈。当一个具有 <code>singleTask</code> 模式的 Activity 启动后,比如 Activity A,系统会首先寻找是否存在所需的任务栈,如果不存在,就重新创建一个任务栈,然后创建 A 的实例后把 A 放入到栈中。如果存在 A 所需要的任务栈,这时要看 A 是否在栈中有实例存在,如果有,那么系统就会把它调到栈顶并且调用它的 <code>onNewIntent</code> 方法,如果不存在,就创建 A 的实例并把 A 压入栈中。这里所说的 A 所需要的任务栈是什么意思呢?其实 Activity 是可以指定自己想要的任务栈的名字的,通过一个参数:<code>TaskAffinity</code>,默认情况下,所有的 Activity 所需要的任务栈的名字为应用的包名。</p>
<ul>
<li>如果任务栈 S1 中的情况为 ABC,这个时候 Activity D 以 <code>singleTask</code> 模式请求启动,它需要的任务栈为 S2,由于 S2 和 D 的实例均不存在,所以系统就会先创建任务栈 S2,然后在创建 D 的实例并将其入栈到 S2</li>
<li>如果上面 D 所需的任务栈为 S1,那么因为 S1 已经存在,所以系统直接创建 D 的实例并且入栈到 S1。</li>
<li>如果 D 所需的任务栈为 S1,但是 S1 中的情况为 ADBC,此时 D 不会重新创建,而是把 D 切换到栈顶并调用 <code>onNewIntent</code> 方法。那 B 和 C 怎么办? 它们会全部出栈,相当于 clearTop 效果。</li>
</ul>
<h2>singleInstance</h2>
<p><code>singleInstance</code> 是单实例模式。这种模式是 <code>singleTask</code> 的加强版,它除了具有 <code>singleTask</code> 的所有特性外,还加强了一点,那就是此种模式的 Activity 只能单独位于一个任务栈中。</p>
<ul><li>比如 Activity A 是 <code>singleInstance</code> 模式,当 A 启动后,系统会创建一个新的任务栈,然后 A 独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的 Activity,除非这个栈被销毁了。</li></ul>
<h2>总结</h2>
<p>Activity 的四种启动模式就是这样,除了 <code>singleTask</code> 稍微有点复杂,其它都很好理解。有的时候会出现多个任务栈的情况,比如现在有两个任务栈(如下图),前台的任务栈情况为 Activity1 和 Activity2, 而后台任务栈的情况为 ActivityX 和 ActivityY,假设 XY 的启动模式均为 singleTAsk。现在启动Y, 那么整个后台任务栈都被切换到前台,这时候的后退列表变为 12XY。当按返回键的时候,Activity 会依次出栈。</p>
<p><img src="/img/bVu75N?w=550&h=309" alt="图片描述" title="图片描述"></p>
<p><strong>参考</strong></p>
<ol>
<li>Android 开发艺术探索</li>
<li>
<a href="https://link.segmentfault.com/?enc=8kgSw6H9%2BDCnTPz%2Fhkx3Eg%3D%3D.1RFGRFlK966gu0%2FYV0owkwYckB1HZFrAbBS6aWUCKdei2us8fr4aAqdlwdsOj%2BgIrvncB3LAt4NcgepxT%2FtmOWOippHqAf9acbFuqlUeQNE%3D" rel="nofollow"></a><a href="https://link.segmentfault.com/?enc=gYePXFqeT%2FVzcZN5kKOpQw%3D%3D.pmTRkCBFOjQWJOfAguH9TFovOGINifKVn2mOqjbR5Jt5oOHwx1QFKbpFEw2FxAFkzw6zQhSx79XgE7nkEDxTt%2FdRCMsvZkugyV8DEg2Q7eQ%3D" rel="nofollow">http://developer.android.com/...</a>
</li>
</ol>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)</em></strong></p>
Android LruCache源码分析
https://segmentfault.com/a/1190000004993260
2016-04-23T18:08:34+08:00
2016-04-23T18:08:34+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
3
<h2>简介</h2>
<p>Android 中常常会用通过网络请求数据,为了节省流量、电量以及时间等等,一般会把得到的数据进行缓存。缓存分为内存缓存和文件缓存。Android 自带的内存缓存是 <code>LRU</code> 机制,也即是最近最少使用算法,对应的类是 <code>LruCache</code>。要说它的原理,一句话概括就是使用了 <code>LinkedHashMap</code>。本文具体分析 <code>LruCache</code> 源码的实现,其实还是比较简单的。</p>
<h2>变量及构造方法</h2>
<p><code>LruCache</code> 的内部变量及构造方法如下:</p>
<pre><code>private final LinkedHashMap<K, V> map;
private int size;
private int maxSize;
private int putCount;
private int createCount;
private int evictionCount;
private int hitCount;
private int missCount;
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}</code></pre>
<p>在 <code>LruCache</code> 中,有一个 <code>LinkedHashMap</code> 变量,它就是实际存储缓存数据的。<code>LinkedHashMap</code> 继承自 <code>HashMap</code>,但是增加了记住元素插入或者访问顺序的功能,这个是通过内部一个双向的循环链表实现的。</p>
<p><code>size</code> 和 <code>maxSize</code> 变量分别表示当前缓存数据的大小以及缓存最大可使用的大小。下面的几个以 Count 结尾的变量是记录相应操作的命中次数。</p>
<p><code>LruCache</code> 的构造方法接受一个 int 变量,用于指定缓存的最大使用量。构造方法中创建了 <code>LinkedHashMap</code>, 它有三个参数。第一个是初始容量,第二个是加载因子,这两个都是 <code>HashMap</code> 中的概念 (<a href="https://segmentfault.com/a/1190000004274074">Java HashMap源码分析</a>)。第三个参数是一个布尔值,<code>true</code> 表示 <code>LinkedListMap</code> 按照元素的最近访问排序,<code>false</code> 则表示按照元素的插入次序排序,<code>LruCache</code> 实现的是最近最少访问,所以这里指定为 <code>true</code>。</p>
<h2>put 和 get</h2>
<p><code>LruCache</code> 创建后最常用的两个操作就是 <code>put</code> 和 <code>get</code> 了。先看 <code>put</code>的代码:</p>
<pre><code>public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}</code></pre>
<p>如果 key 或者 value 为空会抛出异常,否则在同步块中进行添加操作。首先是 <code>putCount</code> 加一,然后调用 <code>safeSizeOf</code> 方法增加 <code>size</code>,接着把数据放到 <code>map</code> 中,如果这个 key 已经存放了数据,那么应该减去这条数据的大小,因为它已经被覆盖调了。同步块结束后,如果确实覆盖了数据,会调用 <code>entryRemoved</code>,这个方法默认是空,什么也没做,我们自己创建 <code>LruCache</code> 时可以选择重写。最后还需要调用 <code>trimToSize</code>,这个方法用来防止数据超出 <code>maxSize</code>。</p>
<p>上面在计算 <code>size</code> 大小时调用了 <code>safeSizeOf</code> 方法,看名字就觉得不一般,继续看它的代码:</p>
<pre><code>private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
protected int sizeOf(K key, V value) {
return 1;
}</code></pre>
<p>这个方法又调用了 <code>sizeOf</code> 返回数据的大小,如果小于 0 抛出异常,否则就返回。<code>sizeOf</code> 这个方法是我们熟悉的,一般使用 <code>LruCache</code> 都会重写这个方法返回每条数据的实际大小。为什么要重写呢? 因为这个方法默认的实现是返回 1。这样的话,<code>size</code> 相当于记录的是缓存数据的条数,而这可能并不是我们想要的。</p>
<p>下面再看看 <code>trimToSize</code> 的实现:</p>
<pre><code>public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}</code></pre>
<p>内部是一个无限循环,删除 <code>map</code> 里面最久未使用的,然后更新 <code>size</code>,如果 <code>size</code> 小于 <code>maxSize</code> 就跳出循环。</p>
<p><code>put</code>相关的代码就是这样,现在看看 <code>get</code> 的实现:</p>
<pre><code>public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
//找不到就创建一个value
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
protected V create(K key) {
return null;
}</code></pre>
<p>key 依然不能为空,然后就是从 <code>map</code> 中取数据,递增<code>hitCount</code>,最后直接返回数据。这是成功找到缓存的情况,如果找不到还会执行下面的代码。下面的逻辑是调用 <code>create</code> 创建 value。<code>create</code>需要我们自己重写,默认返回 <code>null</code>,所以默认情况下找不到缓存就返回 <code>null</code>。 如果重写了 <code>create</code> 那么接着会把新建的数据加入 <code>map</code>,并且增加 <code>size</code>,执行 <code>trimToSize</code> 等操作。</p>
<h2>其它操作</h2>
<p>除了 <code>get</code> 和 <code>put</code>,其它还有一些操作。比如 <code>evictAll</code> 用于清除所有缓存,<code>size()</code> 返回 <code>size</code> 大小,一系列的以 Count 结尾的方法用于返回 hitCount 等计数值的大小。这些代码都比较简单,没什么好说的。</p>
<h2>总结</h2>
<p><code>LruCache</code> 实现了数据的内存缓存,可以看出整体思路并不是很复杂,关键在于使用了 <code>LinkedHashMap</code>。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)</em></strong></p>
Android Volley源码解析
https://segmentfault.com/a/1190000004902204
2016-04-09T05:09:13+08:00
2016-04-09T05:09:13+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
2
<h2>简介</h2>
<p>Volley 是非常火的一个网络请求框架,一方面它是由谷歌官方在2013年I/O大会推出的,另一方面大家都说它很优秀。Volley 非常适合去进行数据量不大,但通信频繁的网络操作。Volley 可以传输 String 、Json,还可以很方便的加载图片。它的用法很简单,无非就是获取一个 <code>RequestQueue</code> ,把请求 <code>request</code> 加入其中。网上的介绍也很多,这里就不多说了。</p>
<p>那么 <code>RequestQueue</code> 是怎么工作的? 它跟 <code>request</code> 什么关系? Volley 是怎么处理<code>Cache</code> 的?这篇文章深入 Volley 的源码,了解它的工作流程。</p>
<h2>RequestQueue</h2>
<p>使用 Volley 的时候,需要先获得一个 <code>RequestQueue</code> 对象。它用于添加各种请求任务,通常是调用 <code>Volly.newRequestQueue()</code> 方法获取一个默认的 <code>RequestQueue</code>。我们就从这个方法开始,下面是它的源码:</p>
<pre><code>public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}</code></pre>
<p><code>newRequestQueue(context)</code> 调用了它的重载方法 <code>newRequestQueue(context,null)</code>。在这个方法中,先是通过 context 获得了缓存目录并且构建了 userAgent 信息。接着判断 stack 是否为空,从上面的调用可以知道,默认情况下 stack==null, 所以新建一个 stack 对象。根据系统版本不同,在版本号大于 9 时,stack 为 HurlStack,否则为 HttpClientStack。它们的区别是,HurlStack 使用 HttpUrlConnection 进行网络通信,而 HttpClientStack 使用 HttpClient。有了 stack 后,用它创建了一个 BasicNetWork 对象,可以猜到它是用来处理网络请求任务的。紧接着,新建了一个 <code>RequestQueue</code>,这也是最终返回给我们的请求队列。这个 <code>RequestQueue</code> 接受两个参数,第一个是 <code>DiskBasedCache</code> 对象,从名字就可以看出这是用于硬盘缓存的,并且缓存目录就是方法一开始取得的 cacheDir;第二个参数是刚刚创建的 network 对象。最后调用 <code>queue.start()</code> 启动请求队列。</p>
<p>在分析 start() 之前,先来了解一下 <code>RequestQueue</code> 一些关键的内部变量以及构造方法:</p>
<pre><code>//重复的请求将加入这个集合
private final Map<String, Queue<Request>> mWaitingRequests =
new HashMap<String, Queue<Request>>();
//所有正在处理的请求任务的集合
private final Set<Request> mCurrentRequests = new HashSet<Request>();
//缓存任务的队列
private final PriorityBlockingQueue<Request> mCacheQueue =
new PriorityBlockingQueue<Request>();
//网络请求队列
private final PriorityBlockingQueue<Request> mNetworkQueue =
new PriorityBlockingQueue<Request>();
//默认线程池大小
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
//用于响应数据的存储与获取
private final Cache mCache;
//用于网络请求
private final Network mNetwork;
//用于分发响应数据
private final ResponseDelivery mDelivery;
//网络请求调度
private NetworkDispatcher[] mDispatchers;
//缓存调度
private CacheDispatcher mCacheDispatcher;
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
</code></pre>
<p><code>RequestQueue</code> 有多个构造方法,最终都会调用最后一个。在这个方法中,<code>mCache</code> 和 <code>mNetWork</code> 分别设置为 newRequestQueue 中传来的 DiskBasedCache 和 BasicNetWork。<code>mDispatchers</code> 为网络请求调度器的数组,默认大小 4 (<code>DEFAULT_NETWORK_THREAD_POOL_SIZE</code>)。<code>mDelivery</code> 设置为 new ExecutorDelivery(new Handler(Looper.getMainLooper())),它用于响应数据的传递,后面会具体介绍。可以看出,其实我们可以自己定制一个 <code>RequestQueue</code> 而不一定要用默认的 newRequestQueue。</p>
<p>下面就来看看 <code>start()</code> 方法是如何启动请求队列的:</p>
<pre><code>public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}</code></pre>
<p>代码比较简单,就做了两件事。第一,创建并且启动一个 <code>CacheDispatcher</code>。第二,创建并启动四个 <code>NetworkDispatcher</code>。所谓的启动请求队列就是把任务交给缓存调度器和网络请求调度器处理。</p>
<p>这里还有个问题,请求任务是怎么加入请求队列的?其实就是调用了 <code>add()</code> 方法。现在看看它内部怎么处理的:</p>
<pre><code>public Request add(Request request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}</code></pre>
<p>这个方法的代码稍微有点长,但逻辑并不复杂。首先把这个任务加入 <code>mCurrentRequests</code>,然后判断是否需要缓存,不需要的话就直接加入网络请求任务队列 <code>mNetworkQueue</code> 然后返回。默认所有任务都需要缓存,可以调用 <code>setShouldCache(boolean shouldCache)</code> 来更改设置。所有需要缓存的都会加入缓存任务队列 <code>mCacheQueue</code>。不过先要判断 <code>mWaitingRequests</code> 是不是已经有了,避免重复的请求。</p>
<h2>Dispatcher</h2>
<p><code>RequestQueue</code> 调用 start() 之后,请求任务就被交给 <code>CacheDispatcher</code> 和 <code>NetworkDispatcher</code> 处理了。它们都继承自 Thread,其实就是后台工作线程,分别负责从缓存和网络获取数据。</p>
<h3>CacheDispatcher</h3>
<p><code>CacheDispatcher</code> 不断从 <code>mCacheQueue</code> 取出任务处理,下面是它的 <code>run()</code> 方法:</p>
<pre><code>public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available. 取出缓存队列的任务
final Request request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}</code></pre>
<p>首先是调用 <code>mCache.initialize()</code> 初始化缓存,然后是一个 while(true) 死循环。在循环中,取出缓存队列的任务。先判断任务是否取消,如果是就执行 <code>request.finish("cache-discard-canceled")</code> 然后跳过下面的代码重新开始循环,否则从缓存中找这个任务是否有缓存数据。如果缓存数据不存在,把任务加入网络请求队列,并且跳过下面的代码重新开始循环。如果找到了缓存,就判断是否过期,过期的还是要加入网络请求队列,否则调用 <code>request</code> 的<code>parseNetworkResponse</code> 解析响应数据。最后一步是判断缓存数据的新鲜度,不需要刷新新鲜度的直接调用 <code>mDelivery.postResponse(request, response)</code> 传递响应数据,否则依然要加入 <code>mNetworkQueue</code> 进行新鲜度验证。</p>
<p>上面的代码逻辑其实不是很复杂,但描述起来比较绕,下面这张图可以帮助理解:</p>
<p><img src="/img/bVuI9o" alt="CacheDispatcher-run-flow-chart.png" title="CacheDispatcher-run-flow-chart.png"></p>
<h3>NetworkDispatcher</h3>
<p><code>CacheDispatcher</code> 从缓存中寻找任务的响应数据,如果任务没有缓存或者缓存失效就要交给 <code>NetworkDispatcher</code> 处理了。它不断从网络请求任务队列中取出任务执行。下面是它的 <code>run()</code> 方法:</p>
<pre><code>public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request request;
while (true) {
try {
// Take a request from the queue.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
// Tag the request (if API >= 14)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
}
// Perform the network request. 发送网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
}
}</code></pre>
<p>可以看出,<code>run()</code> 方法里面依然是个无限循环。从队列中取出一个任务,然后判断任务是否取消。如果没有取消就调用 <code>mNetwork.performRequest(request)</code> 获取响应数据。如果数据是 304 响应并且已经有这个任务的数据传递,说明这是 <code>CacheDispatcher</code> 中验证新鲜度的请求并且不需要刷新新鲜度,所以跳过下面的代码重新开始循环。否则继续下一步,解析响应数据,看看数据是不是要缓存。最后调用 <code>mDelivery.postResponse(request, response)</code> 传递响应数据。下面这张图展示了这个方法的流程:</p>
<p><img src="/img/bVuJbz" alt="NetworkDispatcher-run-flow-chart.png" title="NetworkDispatcher-run-flow-chart.png"></p>
<h3>Delivery</h3>
<p>在 <code>CacheDispatcher</code> 和 <code>NetworkDispatcher</code> 中,获得任务的数据之后都是通过 <code>mDelivery.postResponse(request, response)</code> 传递数据。我们知道 Dispatcher 是另开的线程,所以必须把它们获取的数据通过某种方法传递到主线程,来看看 Deliver 是怎么做的。<br><code>mDelivery</code> 的类型为 <code>ExecutorDelivery</code>,下面是它的 <code>postResponse</code> 方法源码:</p>
<pre><code>public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}</code></pre>
<p>从上面的代码可以看出,最终是通过调用 <code>mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable))</code> 进行数据传递。这里的 <code>mResponsePoster</code> 是一个 <code>Executor</code> 对象。</p>
<pre><code>private final Executor mResponsePoster;
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};</code></pre>
<p><code>Executor</code> 是线程池框架接口,里面只有一个 <code>execute()</code> 方法,<code>mResponsePoster</code> 的这个方法实现为用 <code>handler</code> 传递 Runnable 对象。而在 <code>postResponse</code> 方法中,<code>request</code> 和 <code>response</code> 被封装为 <code>ResponseDeliveryRunnable</code>, 它正是一个 Runnable 对象。所以响应数据就是通过 <code>handler</code> 传递的,那么这个 <code>handler</code> 是哪里来的?其实在介绍 <code>RequestQueue</code> 的时候已经提到了:<code>mDelivery</code> 设置为 new ExecutorDelivery(new Handler(Looper.getMainLooper())),这个 <code>handler</code> 便是 <code>new Handler(Looper.getMainLooper())</code>,是与主线程的消息循环连接在一起的,这样数据便成功传递到主线程了。</p>
<h2>总结</h2>
<p>Volley 的基本工作原理就是这样,用一张图总结一下它的运行流程:</p>
<p><img src="/img/bVuJgn" alt="Volley-run-flow-chart.png" title="Volley-run-flow-chart.png"></p>
<p><strong>参考</strong></p>
<ul><li><a href="https://link.segmentfault.com/?enc=yNpPXHmJpExrgmgNjGJBZA%3D%3D.szWSyAmFLFdpE10ShGq20po8%2F6ARTEjAG3P89tf07y8OqqiFt1g%2F%2B%2BPbfcX5fJnq92Gb4Pbv%2FyQF7bS8Uyb8RnNdYlqHHWXYLysyhnT8w8O1HrRXY9TG7Tp6yQ%2BWTQGo" rel="nofollow">Volley 源码解析</a></li></ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)</em></strong></p>
Android IntentService源码分析
https://segmentfault.com/a/1190000004875002
2016-04-06T01:20:59+08:00
2016-04-06T01:20:59+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
1
<h2>简介</h2>
<p>Service 是 Android 四大组件之一,用于后台运行,但由于 Service 依然运行在主线程,所以是不能直接进行耗时操作的。如果有耗时操作,还是需要放到子线程中,可以手动开启线程,也可以使用 Android 提供的一个非常简便的类 <code>IntentService</code>。这个类的源码还是很简单的,本文分析一下它的实现。</p>
<h2>HandlerThread</h2>
<p>在分析 <code>IntentService</code> 之前,先要了解 <code>HandlerThread</code>。看名字就知道这个类是与 <code>Handler</code> 有关的线程类,API 是这么描述的:</p>
<blockquote>Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.</blockquote>
<p>它是为创建附带 <code>Looper</code> 对象的线程的帮助类。<code>Looper</code> 对象可用于创建 <code>Handler</code> 类。 当然仍然要调用 start() 来开启线程。</p>
<p>在上篇文章 <a href="https://segmentfault.com/a/1190000004866028">Android Handler的原理</a> 中,我们知道一个线程创建 <code>Handler</code> 的时候,必须要有 <code>Looper</code> 对象,利用 <code>Looper</code> 开启消息循环。在主线程中,系统已经帮我们做了这些工作。那么如果在其它子线程,我们该怎么创建 <code>Looper</code> 呢? 看看 <code>HandlerThread</code> 的 run() 方法:</p>
<pre><code>public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}</code></pre>
<p>在这个方法中调用了 <code>Looper.prepare()</code> 为线程获取 <code>Looper</code> 对象,并且保存这个对象。接着又调用 <code>Looper.loop()</code> 开启消息循环,这个方法里面有个无限循环不断从消息队列中取出消息,于是这个线程的消息系统便建立起来了。这些在上一篇文章中已经分析过。</p>
<h2>IntentService</h2>
<p>搞清楚 <code>HandlerThread</code>,<code>IntentService</code> 就很简单了。<code>IntentService</code> 内部使用了 <code>HandlerThread</code>。<code>IntentService</code> 继承了 <code>Service</code> 并且是 一个抽象类。下面是它的 <code>onCreate()</code> 方法:</p>
<pre><code> public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
</code></pre>
<p>这个方法里面创建了一个 <code>HandlerThread</code> 对象,并且获取它的 <code>Looper</code>,紧接着用这个 <code>Looper</code> 创建了 <code>Handler</code>。<code>IntentService</code> 的 <code>handleMessage</code>方法把接收的消息交给 <code>onHandleIntent</code> 处理,这个方法是一个抽象方法,也是我们使用 <code>IntentService</code> 时需要重写的方法。<code>onHandleIntent</code> 处理完成后 <code>IntentService</code>会调用 <code>stopSelf()</code> 自动停止。<code>handleMessage</code> 将在 <code>Looper.loop()</code> 方法中被调用,运行在 <code>HandlerThread</code> 中,所以可以安全地处理耗时操作。</p>
<p>消息又是怎么传过来的呢?看一下 <code>onStartCommand()</code> 的源码:</p>
<pre><code>public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}</code></pre>
<p>很简单,把 <code>intent</code> 参数包装到 message 的 obj 中,然后发送消息。这里的 <code>Intent</code> 就是启动服务时 <code>startService(Intent)</code> 里的 <code>Intent</code>。</p>
<p>对于 <code>Service</code> 而言,多次调用 <code>startService(Intent)</code> 时,<code>onCreate()</code> 方法只会调用一次,所以在这里面做一些初始化工作,而 <code>onStartCommand</code> 则相应地会调用多次。因此,只要 <code>Intent</code> 的参数不同,便可以完成不同的任务。</p>
<h2>总结</h2>
<p>从上面的分析可以看出,只要明白 <code>Handler</code> 的原理,<code>IntentService</code> 还是比较好理解的。使用 <code>IntentService</code> 需要注意几点:</p>
<ul>
<li>不可以直接和UI做交互。为了把它执行的结果体现在UI上,需要把结果返回给Activity。</li>
<li>工作任务队列是顺序执行的,如果一个任务正在IntentService中执行,此时你再发送一个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕才开始执行。</li>
<li>正在执行的任务无法打断。</li>
</ul>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)</em></strong></p>
Android Handler的原理
https://segmentfault.com/a/1190000004866028
2016-04-05T08:30:00+08:00
2016-04-05T08:30:00+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
6
<h2>简介</h2>
<p>在 Android 中,只有主线程才能操作 UI,但是主线程不能进行耗时操作,否则会阻塞线程,产生 ANR 异常,所以常常把耗时操作放到其它子线程进行。如果在子线程中需要更新 UI,一般是通过 <code>Handler</code> 发送消息,主线程接受消息并且进行相应的逻辑处理。除了直接使用 <code>Handler</code>,还可以通过 View 的 <code>post</code> 方法以及 Activity 的 <code>runOnUiThread</code> 方法来更新 UI,它们内部也是利用了 <code>Handler</code> 。在上一篇文章 <a href="https://segmentfault.com/a/1190000004699080">Android AsyncTask源码分析</a> 中也讲到,其内部使用了 <code>Handler</code> 把任务的处理结果传回 UI 线程。</p>
<p>本文深入分析 Android 的消息处理机制,了解 <code>Handler</code> 的工作原理。</p>
<h2>Handler</h2>
<p>先通过一个例子看一下 <code>Handler</code> 的用法。</p>
<pre><code>public class MainActivity extends AppCompatActivity {
private static final int MESSAGE_TEXT_VIEW = 0;
private TextView mTextView;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_TEXT_VIEW:
mTextView.setText("UI成功更新");
default:
super.handleMessage(msg);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mTextView = (TextView) findViewById(R.id.text_view);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.obtainMessage(MESSAGE_TEXT_VIEW).sendToTarget();
}
}).start();
}
}</code></pre>
<p>上面的代码先是新建了一个 <code>Handler</code>的实例,并且重写了 <code>handleMessage</code> 方法,在这个方法里,便是根据接受到的消息的类型进行相应的 UI 更新。那么看一下 <code>Handler</code>的构造方法的源码:</p>
<pre><code>public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
</code></pre>
<p>在构造方法中,通过调用 <code>Looper.myLooper()</code> 获得了 <code>Looper</code> 对象。如果 <code>mLooper</code> 为空,那么会抛出异常:"Can't create handler inside thread that has not called Looper.prepare()",意思是:不能在未调用 <code>Looper.prepare()</code> 的线程创建 <code>handler</code>。上面的例子并没有调用这个方法,但是却没有抛出异常。其实是因为主线程在启动的时候已经帮我们调用过了,所以可以直接创建 <code>Handler</code> 。如果是在其它子线程,直接创建 <code>Handler</code> 是会导致应用崩溃的。</p>
<p>在得到 <code>Handler</code> 之后,又获取了它的内部变量 <code>mQueue</code>, 这是 <code>MessageQueue</code> 对象,也就是消息队列,用于保存 <code>Handler</code> 发送的消息。</p>
<p>到此,Android 消息机制的三个重要角色全部出现了,分别是 <code>Handler</code> 、<code>Looper</code> 以及 <code>MessageQueue</code>。 一般在代码我们接触比较多的是 <code>Handler</code> ,但 <code>Looper</code> 与 <code>MessageQueue</code> 却是 <code>Handler</code> 运行时不可或缺的。</p>
<h2>Looper</h2>
<p>上一节分析了 <code>Handler</code> 的构造,其中调用了 <code>Looper.myLooper()</code> 方法,下面是它的源码:</p>
<pre><code>static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
</code></pre>
<p>这个方法的代码很简单,就是从 <code>sThreadLocal</code> 中获取 <code>Looper</code> 对象。<code>sThreadLocal</code> 是 <code>ThreadLocal</code> 对象,这说明 <code>Looper</code> 是线程独立的。</p>
<p>在 <code>Handler</code> 的构造中,从抛出的异常可知,每个线程想要获得 <code>Looper</code> 需要调用 <code>prepare()</code> 方法,继续看它的代码:</p>
<pre><code>private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}</code></pre>
<p>同样很简单,就是给 <code>sThreadLocal</code> 设置一个 <code>Looper</code>。不过需要注意的是如果 <code>sThreadLocal</code> 已经设置过了,那么会抛出异常,也就是说一个线程只会有一个 <code>Looper</code>。创建 <code>Looper</code> 的时候,内部会创建一个消息队列:</p>
<pre><code>private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
</code></pre>
<p>现在的问题是, <code>Looper</code>看上去很重要的样子,它到底是干嘛的?<br>回答: <code>Looper</code> 开启消息循环系统,不断从消息队列 <code>MessageQueue</code> 取出消息交由 <code>Handler</code> 处理。</p>
<p>为什么这样说呢,看一下 <code>Looper</code> 的 <code>loop</code>方法:</p>
<pre><code>public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//无限循环
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}</code></pre>
<p>这个方法的代码有点长,不去追究细节,只看整体逻辑。可以看出,在这个方法内部有个死循环,里面通过 <code>MessageQueue</code> 的 <code>next()</code> 方法获取下一条消息,没有获取到会阻塞。如果成功获取新消息,便调用 <code>msg.target.dispatchMessage(msg)</code>,<code>msg.target</code>是 <code>Handler</code> 对象(下一节会看到),<code>dispatchMessage</code> 则是分发消息(此时已经运行在 UI 线程),下面分析消息的发送及处理流程。</p>
<h2>消息发送与处理</h2>
<p>在子线程发送消息时,是调用一系列的 <code>sendMessage</code>、<code>sendMessageDelayed</code> 以及 <code>sendMessageAtTime</code> 等方法,最终会辗转调用 <code>sendMessageAtTime(Message msg, long uptimeMillis)</code>,代码如下:</p>
<pre><code>public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
</code></pre>
<p>这个方法就是调用 <code>enqueueMessage</code> 在消息队列中插入一条消息,在 <code>enqueueMessage</code>总中,会把 <code>msg.target</code> 设置为当前的 <code>Handler</code> 对象。</p>
<p>消息插入消息队列后, <code>Looper</code> 负责从队列中取出,然后调用 <code>Handler</code> 的 <code>dispatchMessage</code> 方法。接下来看看这个方法是怎么处理消息的:</p>
<pre><code>public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}</code></pre>
<p>首先,如果消息的 <code>callback</code> 不是空,便调用 <code>handleCallback</code> 处理。否则判断 <code>Handler</code> 的 <code>mCallback</code> 是否为空,不为空则调用它的 <code>handleMessage</code>方法。如果仍然为空,才调用 <code>Handler</code> 自身的 <code>handleMessage</code>,也就是我们创建 <code>Handler</code> 时重写的方法。</p>
<p>如果发送消息时调用 <code>Handler</code> 的 <code>post(Runnable r)</code>方法,会把 <code>Runnable</code>封装到消息对象的 <code>callback</code>,然后调用 <code>sendMessageDelayed</code>,相关代码如下:</p>
<pre><code>public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
</code></pre>
<p>此时在 <code>dispatchMessage</code>中便会调用 <code>handleCallback</code>进行处理:</p>
<pre><code> private static void handleCallback(Message message) {
message.callback.run();
}</code></pre>
<p>可以看到是直接调用了 <code>run</code> 方法处理消息。</p>
<p>如果在创建 <code>Handler</code>时,直接提供一个 <code>Callback</code> 对象,消息就交给这个对象的 <code>handleMessage</code> 方法处理。<code>Callback</code> 是 <code>Handler</code> 内部的一个接口:</p>
<pre><code>public interface Callback {
public boolean handleMessage(Message msg);
}
</code></pre>
<p>以上便是消息发送与处理的流程,发送时是在子线程,但处理时 <code>dispatchMessage</code> 方法运行在主线程。</p>
<h2>总结</h2>
<p>至此,Android消息处理机制的原理就分析结束了。现在可以知道,消息处理是通过 <code>Handler</code> 、<code>Looper</code> 以及 <code>MessageQueue</code>共同完成。 <code>Handler</code> 负责发送以及处理消息,<code>Looper</code> 创建消息队列并不断从队列中取出消息交给 <code>Handler</code>, <code>MessageQueue</code> 则用于保存消息。</p>
<p><strong><em>如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)</em></strong></p>
Android ViewStub的使用
https://segmentfault.com/a/1190000004707516
2016-03-28T23:09:19+08:00
2016-03-28T23:09:19+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
1
<h2>简介</h2>
<p>在Android开发中,布局的加载速度会影响APP的性能。如果布局实现的不好,会导致程序非常占内存并且UI运行缓慢。优化布局可以从三个方面着手:</p>
<ol>
<li>使用<include>标签重用layouts</li>
<li>使用<merge>标签避免冗余的布局嵌套</li>
<li>使用<code>ViewStub</code>实现按需加载</li>
</ol>
<p>本文讲解一下<code>ViewStub</code>的使用。<code>ViewStub</code>相当于延迟加载,有的时候在布局中有一些不怎么重用的视图,可以只在需要的时候再加载,提高UI的渲染速度。</p>
<h2>定义ViewStub</h2>
<p><code>ViewStub</code>是轻量级视图,不需要大小信息,不会在布局中绘制任何东西,每个 ViewStub 只需要设置<code>android:layout</code>属性来指定需要被 inflate 的 Layout 类型。</p>
<p>比如下面的布局文件:</p>
<pre><code><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="加载ViewStub" />
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/extra" />
</LinearLayout>
</code></pre>
<p>布局中定义了一个<code>ViewStub</code>,<code>layout</code>属性引用了另一个布局<code>extra.xml</code>,这个布局就是被延迟加载的布局,而<code>ViewStub</code>本身不会显示任何内容。上面的<code>Button</code>是为了在代码中实现延迟加载。</p>
<p><code>extra.xml</code>定义了一个<code>TextView</code>和一个<code>progressBar</code>,代码如下:</p>
<pre><code><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#36e7ea"
android:gravity="center"
android:textSize="18sp" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
</code></pre>
<h2>加载ViewStub布局</h2>
<p>要加载<code>ViewStub</code>引用的布局只需要调用<code>inlfate()</code>方法,在我们的这个例子中设置为点击<code>Button</code>,代码比较简单:</p>
<pre><code>Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
View view = ((ViewStub)findViewById(R.id.view_stub)).inflate();
TextView tv = (TextView) view.findViewById(R.id.text_view);
tv.setText("ViewStub的使用");
}
});
</code></pre>
<p>获取<code>ViewStub</code>之后直接调用<code>inflate()</code>即可把<code>extra.xml</code>解析为<code>View</code>,通过它可以得到<code>extra.xml</code>内部的控件,这样便实现了按需加载。</p>
<p>下面两幅图分别是点击<code>Button</code>前后的界面:</p>
<p><img src="/img/bVtUOS?w=346&h=619" alt="图片描述" title="图片描述"><img src="/img/bVtUOZ?w=352&h=619" alt="图片描述" title="图片描述"></p>
<p>注意:<code>ViewStub</code>不支持使用<merge>标签的布局。</p>
Android AsyncTask源码分析
https://segmentfault.com/a/1190000004699080
2016-03-27T20:51:17+08:00
2016-03-27T20:51:17+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
3
<h2>简介</h2>
<p>Android中只能在主线程中进行UI操作,如果是其它子线程,需要借助异步消息处理机制Handler。除此之外,还有个非常方便的<code>AsyncTask</code>类,这个类内部封装了<code>Handler</code>和线程池。本文先简要介绍<code>AsyncTask</code>的用法,然后分析具体实现。</p>
<h2>基本用法</h2>
<p><code>AsyncTask</code>是一个抽象类,我们需要创建子类去继承它,并且重写一些方法。<code>AsyncTask</code>接受三个泛型参数:</p>
<ul>
<li>
<code>Params</code>: 指定传给任务执行时的参数的类型</li>
<li>
<code>Progress</code>: 指定后台任务执行时将任务进度返回给UI线程的参数类型</li>
<li>
<code>Result</code>: 指定任务完成后返回的结果的类型</li>
</ul>
<p>除了指定泛型参数,还需要根据需要重写一些方法,常用的如下:</p>
<ul>
<li>
<code>onPreExecute()</code>: 这个方法在UI线程调用,用于在任务执行前做一些初始化操作,如在界面上显示加载进度控件</li>
<li>
<code>doInBackground</code>: 在<code>onPreExecute()</code>结束之后立刻在后台线程调用,用于耗时操作。在这个方法中可调用<code>publishProgress</code>方法返回任务的执行进度</li>
<li>
<code>onProgressUpdate</code>: 在<code>doInBackground</code>调用<code>publishProgress</code>后被调用,工作在UI线程</li>
<li>
<code>onPostExecute</code>: 后台任务结束后被调用,工作在UI线程</li>
</ul>
<h2>源码分析</h2>
<p>下面分析这个类的实现,主要有线程池以及<code>Handler</code>两部分。</p>
<h3>线程池</h3>
<p>当执行一个<code>AsyncTask</code>的时候调用的是<code>execute()</code>方法,就从这个开始看:</p>
<pre><code>public final AsyncTask<Params, Progress, Result> execute(Params... params){
return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:" + " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
//先执行 onPreExecute
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
</code></pre>
<p><code>execute</code>方法会调用<code>executeOnExecutor</code>。在这个方法中先检查任务是否已经执行或者执行结束,然后把任务标记为<code>running</code>。最开始执行的是<code>onPreExecute</code>,接着把参数赋值给<code>mWorker</code>对象。这个<code>mWorker</code>是一个<code>Callable</code>对象,最终被包装为<code>FutureTask</code>,代码如下:</p>
<pre><code>private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
</code></pre>
<p>从上面的代码可以看出,在<code>mWorker</code>对象中的<code>call()</code>方法会调用<code>doInbackground</code>,返回值交给<code>postResult</code>方法,这个方法通过<code>Handler</code>发送消息,这一点稍后再详细分析。</p>
<p>在<code>mWorker</code>对象被封装成<code>FutureTask</code>之后交由线程池执行,从<code>execute</code>方法可以看出,使用的是<code>sDefaultExecutor</code>,它的值默认为<code>SERIAL_EXECUTOR</code>,也就是串行执行器,实现如下:</p>
<pre><code> private static class SerialExecutor implements Executor {
//线性双向队列,用来存储所有的AsyncTask任务
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
//当前正在执行的AsyncTask任务
Runnable mActive;
public synchronized void execute(final Runnable r) {
//将新的AsyncTask任务加入到双向队列中
mTasks.offer(new Runnable() {
public void run() {
try {
//执行AsyncTask任务
r.run();
} finally {
//当前任务执行结束后执行下一个任务
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
//从任务队列中取出队列头部的任务,如果有就交给并发线程池去执行
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
</code></pre>
<p>在上面的代码中,如果有任务执行,那么<code>SerialExecutor</code>的<code>execute</code>方法会被调用,它的逻辑是把<code>Runnable</code>对象加入<code>ArrayDeque</code>队列中,然后判断<code>mActivie</code>是否为空。第一次执行时<code>mActive</code>当然为空,所以执行<code>scheduleNext</code>,其实就是取出任务队列中的第一个任务交给线程池(<code>THREAD_POOL_EXECUTOR</code>)执行。加入<code>mTask</code>队列的<code>Runnable</code>对象的<code>run</code>方法里最终一定会调用<code>scheduleNext</code>,那么又会从任务队列中取出队头任务执行。这样便实现了单线程顺序执行任务,所以在<code>AsyncTask</code>中<strong>默认启用的是单线程执行,只有上一个任务执行后才会执行下一个任务</strong>。如果想要启用多线程执行任务,可以直接调用 <code>executeOnExecutor(Executor exec, Params... params)</code>,这里的<code>Executor</code>参数可以使用<code>AsyncTask</code>自带的<code>THREAD_POOL_EXECUTOR</code>,也可以自己定义。</p>
<h3>Handler</h3>
<p><code>AsyncTask</code>内部用<code>Handler</code>传递消息,它的实现如下:</p>
<pre><code>private static class InternalHandler extends Handler {
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
</code></pre>
<p>如果消息类型是任务执行后的返回值(<code>MESSAGE_POST_RESULT</code>)将调用<code>finish()</code>方法:</p>
<pre><code>private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
} </code></pre>
<p>从上面可以知道,如果任务取消了,将调用<code>onCancelled</code>,否则调用<code>onPostExecute</code>,所以一个<code>AsyncTask</code>任务如果取消了,那么<code>onPostExecute</code>将不会得到执行。</p>
<p>如果消息类型是执行进度(<code>MESSAGE_POST_PROGRESS</code>)将调用<code>onProgressUpdate</code>,这个方法默认是空方法,我们可以根据自己的需要重写。</p>
<h2>总结</h2>
<p><code>AsyncTask</code>的主要逻辑就如上面所分析的,总结几个需要注意的地方:</p>
<ul>
<li>
<code>AsyncTask</code>的类必须在UI线程加载(从4.1开始系统会帮我们自动完成)</li>
<li>
<code>AsyncTask</code>对象必须在UI线程创建</li>
<li>
<code>execute</code>方法必须在UI线程调用</li>
<li>不要手动调用<code>onPreExecute()</code>、<code>doInBackground</code>、<code>onProgressUpdate</code>方法</li>
<li>一个任务只能被调用一次(第二次调用会抛出异常)</li>
<li>多个 <code>AsyncTask</code> 默认是串行执行,可以改为并发执行,但要注意资源同步的问题。</li>
<li>大量 <code>AsyncTask</code> 任务填满线程池的队列会抛出异常。</li>
</ul>
<p>其它还有一些细节可以自行研究源码,另外推荐几篇不错的文章:</p>
<ol>
<li><a href="https://link.segmentfault.com/?enc=OeB%2FQDIZYXpn%2BfWS4MfhGA%3D%3D.k2QWDEV2UxuWpVrNCBZcb7sBihceruIAeQVeRzd2gaqwl65fBMUIgLPWU3vNcdOiPCAAKG7TlPqGvg90BsbaKQ%3D%3D" rel="nofollow">Android源码分析—带你认识不一样的AsyncTask</a></li>
<li><a href="https://link.segmentfault.com/?enc=Fk1CCl7q3Jq%2BLlkZakyLyg%3D%3D.5NdkUaGxiPK8N4BnklMaMNlWKdq2Gr5fLEQmkvlUycYavA86ipyTzsqiZSXOhAidZkF7TGdRV2P46FAYfQJMdg%3D%3D" rel="nofollow">Android AsyncTask完全解析,带你从源码的角度彻底理解</a></li>
</ol>
Android 自定义View---进度条
https://segmentfault.com/a/1190000004624339
2016-03-17T19:30:00+08:00
2016-03-17T19:30:00+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
1
<h2>简介</h2>
<p>Android系统内置的控件有时候无法满足我们的效果,这时候可以自定义View。自定义View可以通过几种方式实现:</p>
<ol>
<li>继承View重写OnDraw方法</li>
<li>继承ViewGroup实现特殊的Layout</li>
<li>继承特定的View(比如TextView)</li>
<li>继承特定的ViewGroup(比如LinearLayout)</li>
</ol>
<p>本文实现一个通过继承View实现自定义的加载进度条,效果如下图所示:</p>
<p><img src="/img/bVty84?w=479&h=798" alt="图片描述" title="图片描述"><br>(gif录的有点卡,真机上不会)</p>
<h2>自定义属性</h2>
<p>首先新建一个名为<code>ColorProgressBar</code>的class继承View。为了提供一些可定制属性,在values目录下新建一个attrs.xml文件,内容如下:</p>
<pre><code><?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ColorProgressBar">
<attr name="firstColor" format="color" />
<attr name="secondColor" format="color" />
<attr name="circleWidth" format="dimension" />
</declare-styleable>
</resources></code></pre>
<p>其中提供了进度条的两种颜色以及圆的宽度几种属性。然后在布局文件中,使用这些属性。</p>
<pre><code><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.administrator.test.ColorPregressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="100dp" />
<com.example.administrator.test.ColorPregressBar
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:layout_marginTop="80dp"
app:circleWidth="16dp"
app:firstColor="#f58a47"
app:secondColor="#5be9d8"
/>
</LinearLayout>
</code></pre>
<p>上面在布局文件中使用了两个自定义的控件,第二个定制了颜色、宽度。</p>
<h2>获取属性</h2>
<p>在<code>ColorProgressBar</code>的构造方法中获取布局文件中的属性,如下所示:</p>
<pre><code>public class ColorPregressBar extends View {
private Paint mPaint;
private RectF mRectF;
//进度
private int mProgress;
//颜色以及宽度
private int mFirstColor;
private int mSecondColor;
private int mCircleWidth;
//是否到下一圈
private boolean mChanged;
//空间的宽度 以及 高度(两个值设为一样)
private int mWidth;
public ColorPregressBar(Context context) {
this(context, null);
}
public ColorPregressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ColorPregressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColorProgressBar);
mFirstColor = ta.getColor(R.styleable.ColorProgressBar_firstColor, Color.RED);
mSecondColor = ta.getColor(R.styleable.ColorProgressBar_secondColor, Color.BLUE);
mCircleWidth = ta.getDimensionPixelSize(R.styleable.ColorProgressBar_circleWidth, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, getResources().getDisplayMetrics()));
ta.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mCircleWidth);
mChanged = false;
}
...//省略了其他方法
}
</code></pre>
<h2>onMeasure</h2>
<p>如果布局文件中控件的宽度设为<code>wrap_content</code>,必须要进行特殊处理,代码如下:</p>
<pre><code>@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST) {
mWidth = Math.min(widthSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 160, getResources().getDisplayMetrics()));
setMeasuredDimension(mWidth, mWidth);
}
}
</code></pre>
<p>在<code>onMeasure</code>中,如果<code>MeasurecSpec</code>的模式是<code>AT_MOST</code>则把宽高设置为160像素。</p>
<h2>onDraw</h2>
<p>最关键的代码在于<code>onDraw</code>方法,这里实现了圆的绘制以及更新:</p>
<pre><code>@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int center = getWidth() / 2;
int radius = center - mCircleWidth / 2;
mRectF = new RectF(center - radius, center - radius, center + radius, center + radius);
if (!mChanged) {
mPaint.setColor(mSecondColor);
canvas.drawCircle(center, center, radius, mPaint);
mPaint.setColor(mFirstColor);
canvas.drawArc(mRectF, -90, mProgress, false, mPaint);
} else {
mPaint.setColor(mFirstColor);
canvas.drawCircle(center, center, radius, mPaint);
mPaint.setColor(mSecondColor);
canvas.drawArc(mRectF, -90, mProgress, false, mPaint);
}
startProgress();
}
</code></pre>
<p>上面的实现方法是先判断当前颜色,画一个圆形,然后改成另一种颜色绘制一段圆弧,代表进度。进度的更新是调用<code>startProgress</code>方法,代码如下:</p>
<pre><code>private void startProgress() {
if (isShown()) {
postDelayed(new Runnable() {
@Override
public void run() {
Log.d("ly", "调用post");
mProgress += 10;
if (mProgress >= 360) {
mProgress = 0;
mChanged = !mChanged;
}
invalidate();
}
}, 10);
}
}
</code></pre>
<p>其实就是在控件显示的情况下增加<code>mProgress</code>的值,如果进度到头了则重新变为<code>0</code>,开始下一圈,然后调用<code>invalidate</code>更新视图。每<code>10</code>毫秒更新一次,不断循环。这个也可以用线程实现,不过要在<code>onDetachedFromWindow</code>方法中关闭线程,以免内存泄漏。</p>
Android View绘制流程总结
https://segmentfault.com/a/1190000004622988
2016-03-17T09:33:22+08:00
2016-03-17T09:33:22+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
3
<h2>简介</h2>
<p>在Android开发中,<code>View</code>扮演了很重要的角色。在官方的API中,对View的描述是这样的:</p>
<blockquote><ol><li>View occupies a rectangular area on the screen and is responsible for drawing and event handling</li></ol></blockquote>
<p>意思是<code>View</code>在屏幕上占用一块矩形区域,并且负责绘制和事件处理。<code>View</code>的绘制和事件处理是两个重要的主题。本文简单分析一下<code>View</code>的绘制流程。</p>
<p><code>View</code>的绘制流程是从<code>ViewRootImpl</code>的<code>performTraversals</code>方法开始,它经过<code>measure</code>、<code>layout</code>和<code>draw</code>三个过程才能最终将一个<code>View</code>绘制出来。</p>
<h2>理解MeasureSpec</h2>
<p>为了明白<code>View</code>的绘制原理,首先要知道<code>MeasureSpec</code>的概念。<code>MeasureSpec</code>相当于是<code>View</code>的测量说明,其中封装了测量模式(SpecMode)和测量规格(SpecSize)。看一下<code>MeasureSpec</code>的代码:</p>
<pre><code> private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
</code></pre>
<p><code>MeasureSpec</code>将SpecMode和SpecSize封装成一个<code>int</code>值,通过<code>getMode</code>方法可以提取出SpecMode,<code>getSize</code>方法可以提取出SpecSize。</p>
<p>测量模式(SpecMode)有三种类型:</p>
<ul><li>EXACTLY</li></ul>
<p>父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。</p>
<ul><li>AT_MOST</li></ul>
<p>父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。</p>
<ul><li>UNSPECIFIED<br>父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态。</li></ul>
<p>一个<code>View</code>的<code>MeasureSpec</code>由父布局<code>MeasureSpec</code>和自身的<code>LayoutParams</code>共同产生。父布局的<code>MeasureSpec</code>从何而来?从父布局的父布局而来。最顶层的布局是<code>DecorView</code>,常用的<code>setContent(view)</code>便是设置<code>DecorView</code>。<code>DecorView</code>的<code>MeasureSpec</code>是通过<code>ViewRootImpl</code>中的<code>getRootMeasureSpec</code>方法得到的。</p>
<p>下面主要分析普通<code>View</code>的<code>MeasureSpec</code>的产生,看一下<code>ViewGroup</code>的<code>measureChild</code>方法:</p>
<pre><code> protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
</code></pre>
<p>从上面的代码可以看出调用<code>getChildMeasureSpec</code>方法得到子<code>View</code>的<code>MeasureSpec</code>,传进去的参数是父容器的<code>MeasureSpec</code>和子<code>View</code>的<code>LayoutParams</code>,由此可见<code>MeasurecSpec</code>是由父容器和<code>View</code>本身共同决定的。<code>getChildMeasureSpec</code>方法获取子<code>View</code>的<code>MeasureSpec</code>的逻辑如下图所示:</p>
<p><img src="/img/bVtyII" alt="SouthEast" title="SouthEast"><br>其中<code>UNSPECIFID</code>不需要考虑。</p>
<p>注意当子<code>View</code>的<code>LayoutParams</code>为<code>wrap_content</code>时,最终的SpecMode都是<code>AT_MOST</code>,SpecSize为父容器剩余空间大小。</p>
<p>得到子<code>View</code>的<code>MeasureSpec</code>后,调用子<code>View</code>的<code>measure</code>方法,传入相应的参数,开始下一层的<code>measure</code>过程。</p>
<h2>Measure</h2>
<p><code>View</code>的<code>measure</code>的过程由其<code>measure</code>方法来完成,这是一个<code>final</code>类型的方法,这意味着子类不能重写此方法,在<code>View</code>的方法中去调用<code>View</code>的<code>onMeasure</code>方法,它的实现如下:</p>
<pre><code>protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
</code></pre>
<p>可以看出,最终是调用<code>getDefaultSize</code>方法得到实际的测量值。上一节提到,当子<code>View</code>的<code>LayoutParams</code>为<code>wrap_content</code>时,最终的SpecMode都是<code>AT_MOST</code>,SpecSize为父容器剩余空间大小。在<code>getDefaultSize</code>方法中,对于<code>AT_MOST</code>和<code>EXACTLY</code>均是直接使用父容器传进来的值,这可能不是我们想要的值,所以自定义<code>View</code>时要重写<code>onMeasure</code>方法处理<code>AT_MOST</code>,否则使用<code>wrap_content</code>相当于使用<code>match_parent</code>。</p>
<p>对于<code>ViewGroup</code>,测量完自己还要调用子<code>View</code>的<code>measure</code>方法,各个子元素再递归去执行这个过程。</p>
<h2>Layout</h2>
<p>Layout的作用是<code>ViewGroup</code>用来确定子元素的位置,当<code>ViewGroup</code>的位置被确定以后,它在<code>onLayout</code>中会遍历所有的子元素并调用其<code>layout</code>方法,在<code>layout</code>方法中<code>onLayout</code>方法又会被调用。<code>onlayout</code>方法是抽象方法,所以自定义<code>ViewGroup</code>时需要实现这个方法确定子元素的布局。常用的<code>LinearLayout</code>以及<code>RelativeLayout</code>方法均重写了这个方法。</p>
<h2>Draw</h2>
<p>Draw过程就是将<code>View</code>绘制到屏幕上,有如下几步:</p>
<ol>
<li>绘制背景</li>
<li>绘制自己</li>
<li>绘制children</li>
<li>绘制装饰</li>
</ol>
<p><code>View</code>中有一个特殊的方法<code>setWillNotDraw</code>,源码如下:</p>
<pre><code>public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
</code></pre>
<p>这个方法的意义是,如果一个<code>View</code>不需要绘制任何内容,设置这个标记位为<code>true</code>后,系统会进行相应的优化。默认情况下,<code>View</code>没有启用这个标记位,但是<code>ViewGroup</code>默认启用。</p>
<h2>总结</h2>
<p><code>View</code>的绘制要经过<code>measure</code>、<code>layout</code>以及<code>draw</code>三个步骤,总体来说还是比较复杂的,本文只是简要概括,更详细的分析见文末参考。</p>
<p><strong>参考</strong></p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=LwITzfQeWnMj7Cxmob5OzQ%3D%3D.WLDAhRhtaUZigTRki7RxR6h9pad%2F5A5EXufpo%2FmWt1CaJB07%2BuFoUnG9mtjGMpNBdBvq77jrQKS74jxtgiUL3w%3D%3D" rel="nofollow">Android View系统解析(下)</a></li>
<li><a href="https://link.segmentfault.com/?enc=ttQQJr39lHTKd2OXPiBBcA%3D%3D.2TLRJd0EGV8kAVTx4SCxEqtyxSj2%2BuKcZsV3wMKR7tTTHRp%2B0K8ZNOGNnaegBj1C" rel="nofollow">Android开发艺术探索</a></li>
</ul>
Java LinkedList源码分析
https://segmentfault.com/a/1190000004362493
2016-01-25T17:05:30+08:00
2016-01-25T17:05:30+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
2
<h2>简介</h2>
<p><code>LinkedList</code>是一个常用的集合类,用于顺序存储元素。<code>LinkedList</code>经常和<code>ArrayList</code>一起被提及。大部分人应该都知道<code>ArrayList</code>内部采用数组保存元素,适合用于随机访问比较多的场景,而随机插入、删除等操作因为要移动元素而比较慢。<code>LinkedList</code>内部采用链表的形式存储元素,随机访问比较慢,但是插入、删除元素比较快,一般认为时间复杂都是<code>O(1)</code>(需要查找元素时就不是了,下面会说明)。本文分析<code>LinkedList</code>的具体实现。</p>
<h2>继承关系</h2>
<pre><code>public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable</code></pre>
<p><code>LinkedList</code>继承了一个抽象类<code>AbstractSequentialList</code>,这个类就是用调用<code>ListIterator</code>实现了元素的增删查改,比如<code>add</code>方法:</p>
<pre><code>public void add(int index, E element) {
try {
listIterator(index).add(element);
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}</code></pre>
<p>不过这些方法在<code>LinkedList</code>中被复写了。</p>
<p><code>LinkedList</code>实现了<code>List</code>、<code>Deque</code>、<code>Cloneable</code>以及<code>Serializable</code>接口。其中<code>Deque</code>是双端队列接口,所以<code>LinkedList</code>可以当作是栈、队列或者双端队队列。</p>
<h2>内部变量</h2>
<pre><code>transient int size = 0;
transient Node<E> first;
transient Node<E> last;</code></pre>
<p>总共就三个内部变量,<code>size</code>是元素个数,<code>first</code>是指向第一个元素的指针,<code>last</code>则指向最后一个。元素在内部被封装成<code>Node</code>对象,这是一个内部类,看一下它的代码:</p>
<pre><code>private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}</code></pre>
<p>可以看到这是一个双向链表的结构,每个节点保存它的前驱节点和后继节点。</p>
<h2>私有方法</h2>
<p><code>LinkedList</code>内部有几个关键的私有方法,它们实现了链表的插入、删除等操作。比如在表头插入:</p>
<pre><code>private void linkFirst(E e) {
final Node<E> f = first; //先保存当前头节点
//创建一个新节点,节点值为e,前驱节点为空,后继节点为当前头节点
final Node<E> newNode = new Node<>(null, e, f);
first = newNode; //让first指向新节点
if (f == null) //如果链表原来为空,把last指向这个唯一的节点
last = newNode;
else · //否则原来的头节点的前驱指向新的头节点
f.prev = newNode;
size++;
modCount++;
}</code></pre>
<p>其实就是双向链表的插入操作,调整指针的指向,时间复杂度为<code>O(1)</code>,学过数据结构的应该很容易看懂。其它还有几个类似的方法:</p>
<pre><code>//尾部插入
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) //如果链表原来为空,让first指向这个唯一的节点
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//中间插入
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
//删除头节点
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next; //先保存下一个节点
f.item = null;
f.next = null; // help GC
first = next; //让first指向下一个节点
if (next == null) //如果下一个节点为空,说明链表原来只有一个节点,现在成空链表了,要把last指向null
last = null;
else //否则下一个节点的前驱节点要置为null
next.prev = null;
size--;
modCount++;
return element;
}
//删除尾节点
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev; //保存前一个节点
l.item = null;
l.prev = null; // help GC
last = prev; //last指向前一个节点
if (prev == null) //与头节点删除一样,判断是否为空
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//从链表中间删除节点
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next; //保存前驱节点
final Node<E> prev = x.prev; //保存后继节点
if (prev == null) { //前驱为空,说明删除的是头节点,first要指向下一个节点
first = next;
} else { //否则前驱节点的后继节点变为当前删除节点的下一个节点
prev.next = next;
x.prev = null;
}
if (next == null) { //判断后继是否为空,与前驱节点是否为空的逻辑类似
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
</code></pre>
<h2>公开方法</h2>
<p>公开的方法几乎都是调用上面几个方法实现的,例如<code>add</code>方法:</p>
<pre><code>public boolean add(E e) {
linkLast(e);
return true;
}
public boolean add(E e) {
linkLast(e);
return true;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}</code></pre>
<p>这些方法的实现都很简单。注意最后一个方法<code>add(int index, E element)</code>,这个方法是在指定的位置插入元素。首先判断位置是否越界,然后判断是不是最后一个位置。如果是就直接插入链表末尾,否则调用<code>linkBefore(element, node(index)</code>方法。这里在传参数的时候又调用了<code>node(index)</code>,这个方法的目的是找到这个位置的节点对象,代码如下:</p>
<pre><code>Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}</code></pre>
<p>这里有个小技巧是先判断位置是在链表的前半段还是后半段,然后决定从链表的头还是尾去寻找节点。要注意的是<strong>遍历链表寻找节点的时间复杂度是</strong><code>O(n)</code>,即使做了位置的判断,最坏情况下也要遍历链表中一半的元素。所以此时插入操作的时间复杂度就不是<code>O(1)</code>,而是<code>O(n/2)+O(1)</code>。用于查找指定位置元素的<code>get(int index)</code>方法便是调用<code>node</code>实现的:</p>
<pre><code>public E get(int index) {
checkElementIndex(index);
return node(index).item;
}</code></pre>
<p>再看一下<code>remove</code>方法:</p>
<pre><code>public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}</code></pre>
<p>第一个<code>remove(int index)</code>方法同样要调用<code>node(index)</code>寻找节点。而第二个方法<code>remove(Object o)</code>是删除指定元素,这个方法要依次遍历节点进行元素的比较,最坏情况下要比较到最后一个元素,比调用<code>node</code>方法更慢,时间复杂度为<code>O(n)</code>。另外从这个方法可以看出<code>LinkedList</code>的元素可以是<code>null</code>。</p>
<h2>总结</h2>
<ul>
<li>
<code>LinkedList</code>基于双向链表实现,元素可以为<code>null</code>。</li>
<li>
<code>LinkedList</code>插入、删除元素比较快,如果只要调整指针的指向那么时间复杂度是<code>O(1)</code>,但是如果针对特定位置需要遍历时,时间复杂度是<code>O(n)</code>。</li>
</ul>
Java HashMap源码分析
https://segmentfault.com/a/1190000004274074
2016-01-08T17:37:30+08:00
2016-01-08T17:37:30+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
5
<h2>简介</h2>
<p><code>HashMap</code>是<code>Map</code>接口下比较常用的一个类,我们都知道它存储的是键值对(<code>key-value</code>),可以高效地插入和删除。这篇文章分析一下它内部的实现,由于源码比较长,只看一些重要的。</p>
<h2>存储结构</h2>
<p>首先,<code>HashMap</code>是基于哈希表存储的。它内部有一个数组,当元素要存储的时候,先计算其<code>key</code>的哈希值,根据哈希值找到元素在数组中对应的下标。如果这个位置没有元素,就直接把当前元素放进去,如果有元素了(这里记为<code>A</code>),就把当前元素链接到元素<code>A</code>的前面,然后把当前元素放入数组中。所以在<code>Hashmap</code>中,数组其实保存的是链表的首节点。下面是百度百科的一张图:<br><img src="/img/bVr5F8" alt="91ef76c6a7efce1b028711e2ad51f3deb48f656e.jpg" title="91ef76c6a7efce1b028711e2ad51f3deb48f656e.jpg"></p>
<p>如上图,每个元素是一个<code>Entry</code>对象,在其中保存了元素的<code>key</code>和<code>value</code>,还有一个指针可用于指向下一个对象。所有哈希值相同的<code>key</code>(也就是冲突了)用链表把它们串起来,这是拉链法。</p>
<h2>内部变量</h2>
<pre><code>//默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认装载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//哈希表
transient Entry<K,V>[] table;
//键值对的数量
transient int size;
//扩容的阈值
int threshold;
//哈希数组的装载因子
final float loadFactor;</code></pre>
<p>在上面的变量中,<code>capacity</code>是指哈希表的长度,也就是<code>table</code>的大小,<strong>默认为16</strong>。装载因子<code>loadFactor</code>是哈希表的“装满程度”,JDK的文档是这样说的:</p>
<blockquote><p>The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased. When the number of entries in the hash table exceeds the product of the load factor and the current capacity, the hash table is rehashed (that is, internal data structures are rebuilt) so that the hash table has approximately twice the number of buckets.</p></blockquote>
<p>大体意思是:装载因子是哈希表在扩容之前能装多满的度量值。当哈希表中“键值对”的数量超过当前容量(<code>capacity</code>)和装载因子的乘积后,哈希表重新散列(也就是内部的数据结构重建了),并且哈希表的容量大约变为原来的两倍。</p>
<p>从上面的变量定义可以看出,默认的装载因子<code>DEFAULT_LOAD_FACTOR </code>是<code>0.75</code>。这个值越大,空间利用率越高,但查询速度(包括<code>get</code>和<code>put</code>)操作会变慢。明白了装载因子之后,<code>threshold</code>也就能理解了,它其实等于<code>容量*装载因子</code>。</p>
<h2>构造器</h2>
<pre><code>public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity) //计算出大于指定容量的最小的2的幂
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity]; //给哈希表分配空间
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}</code></pre>
<p>构造器有好几个,最终都会调用上面的这个。它接受两个参数,一个是初始容量,还有一个是装载因子。刚开始先判断值合不合法,有问题的话会抛出异常。重要的是下面的<code>capacity</code>的计算,它的逻辑是计算出大于<code>initialCapacity</code>的最小的2的幂。其实目的就是要让容量能大于等于指定的初始容量,但这个值还得是2的指数倍,这是一个关键的问题。这么做的原因主要是为了哈希值的映射。先来看一下<code>HashMap</code>中有关哈希的方法:</p>
<pre><code>final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}</code></pre>
<p><code>hash()</code>方法重新计算了<code>key</code>的哈希值,用了比较复杂的位运算,具体逻辑我也不清楚,反正肯定是比较好的方法,能减少冲突什么的。</p>
<p>下面的<code>indexFor()</code>是根据哈希值得到元素在哈希表中的下标。一般在哈希表中是用哈希值对表长取模得到。当<code>length</code>(也就是<code>capacity</code>)为2的幂时,<code>h & (length-1)</code>是同样的效果。并且,2的幂一定是偶数,那么减1之后就是奇数,二进制的最后一位一定是<code>1</code>。那么<code>h & (length-1)</code>的最后一位可能是<code>1</code>,也可能是<code>0</code>,可以均匀地散列。如果<code>length</code>是奇数,那么<code>length-1</code>就是偶数,最后一位是<code>0</code>。此时<code>h & (length-1)</code>的最后一位只可能是<code>0</code>,所有得到的下标都是偶数,那么哈希表就浪费了一半的空间。所以<code>HashMap</code>中的容量(<code>capacity</code>)一定要是<strong>2的幂</strong>。可以看到默认的<code>DEFAULT_INITIAL_CAPACITY=16</code>和<code>MAXIMUM_CAPACITY=1<<30</code>都是这样的。</p>
<h2>Entry对象</h2>
<p><code>HashMap</code>中的键值对被封装成<code>Entry</code>对象,这是<code>HashMap</code>中的一个内部类,看一下它的实现:</p>
<pre><code>static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
void recordAccess(HashMap<K,V> m) {
}
void recordRemoval(HashMap<K,V> m) {
}
}</code></pre>
<p>这个类的实现还是简洁易懂的。提供了<code>getKey()</code>、<code>getValue()</code>等方法供调用,判断相等是要求<code>key</code>和<code>value</code>均相等。</p>
<h2>put操作</h2>
<p>先<code>put</code>了才能<code>get</code>,所以先看一下<code>put()</code>方法:</p>
<pre><code>public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}</code></pre>
<p>在这个方法中,先判断<code>key</code>是否为<code>null</code>,是的话调用<code>putForNullKey()</code>方法,这说明<code>HashMap</code>允许<code>key</code>为<code>null</code>(其实<code>value</code>可以)。如果不是<code>null</code>,计算哈希值并且得到在表中的下标。然后到对应的链表中查询是否已经存在相同的<code>key</code>,如果已经存在就直接更新值(<code>value</code>)。否则,调用<code>addEntry()</code>方法进行插入。</p>
<p>看一下<code>putForNullKey()</code>方法:</p>
<pre><code>private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}</code></pre>
<p>可以看到,<code>key</code>为<code>null</code>时直接在下标<code>0</code>处插入,同样是存在就更新值,否则调用<code>addEntry()</code>插入。</p>
<p>下面是<code>addEntry()</code>方法的实现:</p>
<pre><code>void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}</code></pre>
<p>首先判断是否要扩容(<strong>扩容会重新计算下标值,并且复制元素</strong>),然后计算数组下标,最后在<code>createEntry()</code>中使用头插法插入元素。</p>
<h2>get操作</h2>
<pre><code>public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}</code></pre>
<p>这个比<code>put()</code>简单一些,同样要判断<code>key</code>是不是<code>null</code>,然后就是链表的遍历查询。</p>
<h2>总结</h2>
<p><code>HashMap</code>的基本实现就如上面所分析的,最后做下总结:</p>
<ul>
<li>
<code>HashMap</code>内部用<code>Entry</code>对象保存键值对,基于哈希表存储,用拉链法解决冲突。</li>
<li>
<code>HashMap</code>的默认容量大小为<code>16</code>,默认装载因子为<code>0.75</code>。可以指定容量大小,容量最终一定会被设置为2的幂,这是为了均匀地散列。</li>
<li>
<code>HashMap</code>的<code>key</code>和<code>value</code>都可以是<code>null</code>,当然只能有一个<code>key</code>是<code>null</code>,<code>value</code>可以有多个。</li>
<li>
<code>HashMap</code>的键值对数量超过<code>容量*装载因子</code>时会扩容,扩容后的容量大约是原来的两倍。扩容会重新散列,所以元素的位置可能发生会变化,而且这是一个耗时操作。</li>
<li>
<code>HashMap</code>是线程不安全的。</li>
</ul>
Java StringBuilder和StringBuffer源码分析
https://segmentfault.com/a/1190000004261063
2016-01-06T19:50:23+08:00
2016-01-06T19:50:23+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
5
<h2>简介</h2>
<p><code>StringBuilder</code>与<code>StringBuffer</code>是两个常用的操作字符串的类。大家都知道,<code>StringBuilder</code>是线程不安全的,而<code>StringBuffer</code>是线程安全的。前者是JDK1.5加入的,后者在JDK1.0就有了。下面分析一下它们的内部实现。</p>
<h2>继承关系</h2>
<pre><code>public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence</code></pre>
<p>可以看到,两个类的继承关系是一模一样的。<code>Serializable</code>是可以序列化的标志。<code>CharSequence</code>接口包含了<code>charAt()</code>、<code>length()</code> 、<code>subSequence()</code>、<code>toString()</code>这几个方法,<code>String</code>类也实现了这个接口。这里的重点是抽象类<code>AbstractStringBuilder</code>,这个类封装了<code>StringBuilder</code>和<code>StringBuffer</code>大部分操作的实现。</p>
<h2>AbstractStringBuilder</h2>
<h3>变量及构造方法</h3>
<pre><code>char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
</code></pre>
<p><code>AbstractStringBuilder</code>内部用一个<code>char[]</code>数组保存字符串,可以在构造的时候指定初始容量方法。</p>
<h3>扩容</h3>
<pre><code>public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
</code></pre>
<p>扩容的方法最终是由<code>expandCapacity()</code>实现的,在这个方法中首先把容量扩大为<strong>原来的容量加2</strong>,如果此时仍小于指定的容量,那么就把新的容量设为<code>minimumCapacity</code>。然后判断是否溢出,如果溢出了,把容量设为<code>Integer.MAX_VALUE</code>。最后把<code>value</code>值进行拷贝,<strong>这显然是耗时操作</strong>。</p>
<h3>append()方法</h3>
<pre><code>public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}</code></pre>
<p><code>append()</code>是最常用的方法,它有很多形式的重载。上面是其中一种,用于追加字符串。如果<code>str</code>是<code>null</code>,则会调用<code>appendNull()</code>方法。这个方法其实是追加了<code>'n'</code>、<code>'u'</code>、<code>'l'</code>、<code>'l'</code>这几个字符。如果不是<code>null</code>,则首先扩容,然后调用<code>String</code>的<code>getChars()</code>方法将<code>str</code>追加到<code>value</code>末尾。最后返回对象本身,所以<code>append()</code>可以连续调用。</p>
<h2>StringBuilder</h2>
<p><code>AbstractStringBuilder</code>已经实现了大部分需要的方法,<code>StringBuilder</code>和<code>StringBuffer</code>只需要调用即可。下面来看看<code>StringBuilder</code>的实现。</p>
<h3>构造器</h3>
<pre><code>public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}</code></pre>
<p>可以看出,<code>StringBuilder</code><strong>默认的容量大小为16</strong>。当然也可以指定初始容量,或者以一个已有的字符序列给<code>StringBuilder</code>对象赋初始值。</p>
<h3>append()方法</h3>
<pre><code>public StringBuilder append(String str) {
super.append(str);
return this;
}
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}</code></pre>
<p><code>append()</code>的重载方法很多,这里随便列举了两个。显然,这里是直接调用的父类<code>AbstractStringBuilder</code>中的方法。</p>
<h3>toString()</h3>
<pre><code> public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}</code></pre>
<p><code>toString()</code>方法返回了一个新的<code>String</code>对象,与原来的对象不共享内存。其实<code>AbstractStringBuilder</code>中的<code>subString()</code>方法也是如此。</p>
<h2>SringBuffer</h2>
<p><code>StiringBuffer</code>跟<code>StringBuilder</code>类似,只不过为了实现同步,很多方法使用l<code>Synchronized</code>修饰,如下面的方法:</p>
<pre><code>public synchronized int length() {
return count;
}
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}</code></pre>
<p>可以看到,方法前面确实加了<code>Synchronized</code>。<br>另外,在上面的<code>append()</code>以及<code>setLength()</code>方法里面还有个变量<code>toStringCache</code>。这个变量是用于最近一次<code>toString()</code>方法的缓存,任何时候只要<code>StringBuffer</code>被修改了这个变量会被赋值为<code>null</code>。<code>StringBuffer</code>的<code>toString</code>如下:</p>
<pre><code>public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}</code></pre>
<p>在这个方法中,如果<code>toStringCache</code>为<code>null</code>则先缓存。最终返回的<code>String</code>对象有点不同,这个构造方法还有个参数<code>true</code>。找到<code>String</code>的源码看一下:</p>
<pre><code> String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}</code></pre>
<p>原来这个构造方法构造出来的<code>String</code>对象并没有实际复制字符串,只是把<code>value</code>指向了构造参数,这是为了节省复制元素的时间。不过这个构造器是具有包访问权限,一般情况下是不能调用的。</p>
<h2>总结</h2>
<ul>
<li>
<code>StringBuilder</code>和<code>StringBuffer</code>都是可变字符串,前者线程不安全,后者线程安全。</li>
<li>
<code>StringBuilder</code>和<code>StringBuffer</code>的大部分方法均调用父类<code>AbstractStringBuilder</code>的实现。其扩容机制首先是把容量变为原来容量的2倍加2。最大容量是<code>Integer.MAX_VALUE</code>,也就是<code>0x7fffffff</code>。</li>
<li>
<code>StringBuilder</code>和<code>StringBuffer</code>的默认容量都是16,最好预先估计好字符串的大小避免扩容带来的时间消耗。</li>
</ul>
归并排序及其优化
https://segmentfault.com/a/1190000004232716
2015-12-31T18:30:12+08:00
2015-12-31T18:30:12+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
4
<h2>简单实现</h2>
<p>如果有两个数组已经有序,那么可以把这两个数组归并为更大的一个有序数组。归并排序便是建立在这一基础上。要将一个数组排序,可以将它划分为两个子数组分别排序,然后将结果归并,使得整体有序。子数组的排序同样采用这样的方法排序,这个过程是递归的。</p>
<p>下面举例说明,假如要对数组<code>a={2,1,3,5,2,3}</code>进行排序,那么把数组划分为<code>{2,1,3}</code>和<code>{5,2,3}</code>两个子数组,这两个子数组排序后变为<code>{1,2,3}</code>和<code>{2,3,5}</code>,然后对这两个数组进行归并操作便得到最终的有序数组。代码实现如下:</p>
<pre><code>void sort(int[] a) {
int[] aux = new int[a.length]; //辅助数组
mergeSort(a, 0, a.length - 1, aux);
}
void mergeSort(int[] a, int lo, int hi, int[] aux) {
if (hi <= lo)
return;
int mid = lo + (hi - lo) / 2;
mergeSort(a, lo, mid, aux);
mergeSort(a, mid + 1, hi, aux);
merge(a, lo, mid, hi, aux);
}
void merge(int[] a, int lo, int mid, int hi, int[] aux) {
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
aux[k] = a[k];
}
for (int k = lo; k <= hi; k++) {
if (i > mid)
a[k] = aux[j++];
else if (j > hi)
a[k] = aux[i++];
else if (aux[i] <= aux[j])
a[k] = aux[i++];
else
a[k] = aux[j++];
}
}
</code></pre>
<p>对于归并排序有几点说明:</p>
<ol>
<li>归并排序的时间复杂度是<code>O(NLogN)</code>,空间复杂度是<code>O(N)</code>。</li>
<li>辅助数组是一个共用的数组。如果在每个归并的过程中都申请一个临时数组会造成比较大的时间开销。</li>
<li>归并的过程需要将元素复制到辅助数组,再从辅助数组排序复制回原数组,会拖慢排序速度。</li>
</ol>
<h2>优化</h2>
<p>归并排序有以下几点优化方法:</p>
<ol>
<li>和快速排序一样,对于小数组可以使用插入排序或者选择排序,避免递归调用。</li>
<li>在<code>merge()</code>调用之前,可以判断一下<code>a[mid]</code>是否小于等于<code>a[mid+1]</code>。如果是的话那么就不用归并了,数组已经是有序的。原因很简单,既然两个子数组已经有序了,那么<code>a[mid]</code>是第一个子数组的最大值,<code>a[mid+1]</code>是第二个子数组的最小值。当<code>a[mid]<=a[mid+1]</code>时,数组整体有序。</li>
<li>为了节省将元素复制到辅助数组作用的时间,可以在递归调用的每个层次交换原始数组与辅助数组的角色。</li>
<li>
<p>在<code>merge()</code>方法中的归并过程需要判断<code>i</code>和<code>j</code>是否已经越界,即某半边已经用尽。可以用另一种方式,去掉检测是否某半边已经用尽的代码。具体步骤是将数组<code>a[]</code>的后半部分以降序的方式复制到<code>aux[]</code>,然后从两端归并。对于数组<code>{1,2,3}</code>和<code>{2,3,5}</code>,第一个子数组照常复制,第二个则从后往前复制,最终<code>aux[]</code>中的元素为<code>{1,2,3,5,3,2}</code>。这种方法的缺点是使得归并排序变为<strong>不稳定</strong>排序。代码实现如下:</p>
<pre><code>void merge(int[] a, int lo, int mid, int hi, int[] aux) {
for (int k = lo; k <= mid; k++) {
aux[k] = a[k];
}
for (int k = mid + 1;k <= hi; k++) {
aux[k] = a[hi - k + mid + 1];
}
int i = lo, j = hi; //从两端往中间
for (int k = lo; k <= hi; k++)
if (aux[i] <= aux[j]) a[k] = aux[i++];
else a[k] = aux[j--];
}</code></pre>
</li>
</ol>
<h2>另一种实现:自底向上的归并排序</h2>
<p>在上面的实现中,相当于将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。将一个大的数组的排序划分为小数组的排序是自顶向下的排序。还有一种实现是自底向上的排序,即先两两归并,然后四四归并......代码实现如下:</p>
<pre><code>void sort(int[] a) {
int N = a.length;
int[] aux = new int[N];
for (int sz = 1; sz < N; sz += sz) {
for (int lo = 0; lo < N - sz; lo += sz + sz) {
//在每轮归并中,最后一次归并的第二个子数组可能比第一个子数组要小
merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1), aux);
}
}
}
</code></pre>
快速排序及其优化
https://segmentfault.com/a/1190000004227954
2015-12-30T20:25:24+08:00
2015-12-30T20:25:24+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
3
<h2>简单实现</h2>
<p>快速排序是比较经典、常用的算法,下面简要介绍其思路。对于一个数组,选取某个元素作为切分元素(比如第一个元素),然后把比这个元素小的都放到它前面,比这个元素大的都放到它后面,这样切分元素的最终位置就确定了,并且数组被划分为两个子数组。然后再用同样的方法分别对子数组进行排序,最终整个数组将变成有序的。<br>这里的关键是实现数组划分的方法,这里举例说明。假如要对数组<code>a={5,3,6,5,8,4,7}</code>排序,选取<code>a[0]</code> 作为切分元素,排序过程如下:</p>
<pre><code>5,3,6,5,8,4,7
i j
5,3,6,5,8,4,7
i j
5,3,4,5,8,6,7
ij
</code></pre>
<p>如上所示,刚开始<code>i</code>指向数组第一个元素,而<code>j</code>指向数组末尾元素后一个位置,<code>i</code>从前往后扫描,直到找到一个大于等于切分元素时停下(这里是<code>6</code>),<code>j</code>从后往前扫描,直到找到一个小于等于切分元素的时候停下(这里是<code>4</code>),然后交换这两个元素。然后继续寻找直到两个指针相遇,则本轮扫描结束。此时<code>j<=i</code>,<code>j</code>右边的元素均大于切分元素,交换<code>a[j]</code>与切分元素即可。接着对两个子数组<code>{5,3,4}</code>以及<code>{8,6,7}</code>继续用刚才的方法排序,最终数组将全部有序。代码实现如下:</p>
<pre><code>void sort(int[] a, int lo, int hi) {
if (hi <= lo)
return;
int j = partition(a, lo, hi);
sort(a, lo, j - 1);
sort(a, j + 1, hi);
}
int partition(int[] a, int lo, int hi) {
int i = lo, j = hi + 1;
int v = a[lo];
while (true) {
while (a[++i] < v)
if (i == hi)
break;
while (a[--j] > v)
if (j == lo)
break;
if (i >= j)
break;
swap(a, i, j); //交换元素
}
swap(a, lo, j);
return j;
}</code></pre>
<p>对于快速排序有几点说明:</p>
<ul>
<li>快速排序是原地排序(只需要一个辅助栈)</li>
<li>一般情况下快速排序的时间复杂度是<code>O(NlgN)</code>,因为平均而言划分的子数组是对半分的。但如果切分不均匀,那么时间可能会变成平方级别。例如对于一个已经有序的数组,快速排序的性能反而很差。</li>
<li>在每次扫描时,左侧扫描遇到大于等于切分元素时停下,右侧扫描则是遇到小于等于切分元素时停下。这样会将一些等值元素交换,但可以避免在数组中含有大量重复值的时候运行时间变成平方级别。</li>
</ul>
<h2>优化</h2>
<p>对于快速排序有3点优化思路:</p>
<ol>
<li>对于小数组,可以采用插入排序,避免递归调用。例如,当<code>if(hi <= lo + M)</code>时,就可以转到插入排序。</li>
<li>采用子数组的一部分元素的中位数来切分数组。这样做得到的切分更好,但代价是需要计算中位数。</li>
<li>
<p>如果数组中含有大量的重复元素,可以采用三向切分。将数组切分为三部分,分别对应于小于、等于和大于切分元素的数组元素。代码实现如下:</p>
<pre><code>private static void sort1(int[] a, int lo, int hi) {
if (hi <= lo)
return;
int lt = lo, i = lo + 1, gt = hi;
int v = a[lo];
while (i <= gt) {
if (a[i] < v) {
swap(a, lt++, i++);
} else if (a[i] > v) {
swap(a, i, gt--);
} else {
i++;
}
sort(a, lo, lt - 1);
sort(a, gt + 1, hi);
}</code></pre>
<p>}</p>
</li>
</ol>
<h2>另一种实现:单向扫描</h2>
<p>快速排序的数组切分还有另一种单向扫描的版本,具体步骤是选择数组中最后一个元素作为切分元素,同样设置两个指针,指针<code>i</code>指向数组中第一个元素前面一个位置,<code>j</code>则指向数组中第一个元素。<code>j</code>从前左右往右扫描,遇到小于等于切分元素时就把<code>i</code>加一,然后交换<code>i</code>和<code>j</code>指向的元素。最后把<code>i+1</code>位置的元素和切分元素交换即可完成一次数组划分。代码实现如下:</p>
<pre><code>int partition(int[] a, int lo, int hi) {
int i = lo - 1, j = lo;
int v = a[hi];
while (j < hi) {
if (a[j] <= v) {
swap(a, ++i, j);
}
j++;
}
swap(a, i + 1, hi);
return i + 1;
}
</code></pre>
冒泡排序及其优化
https://segmentfault.com/a/1190000004226234
2015-12-30T15:56:19+08:00
2015-12-30T15:56:19+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
4
<h2>简单实现</h2>
<p>冒泡排序是比较简单的,其排序步骤就是比较相邻元素并将较大的往后移。每扫描一轮,将确定一个元素的位置。代码实现如下:</p>
<pre><code>void sort(int[] a) {
int tmp = 0;
int n = a.length;
for (int i = 0; i < n; ++i) {
for (int j = 1; j < n - i; j++) {
if (a[j] < a[j - 1]) {
tmp = a[j];
a[j] = a[j - 1];
a[j - 1] = tmp;
}
}
}
}
</code></pre>
<h2>优化</h2>
<p>对冒泡排序的优化主要是减少交换次数。如果一次扫描中元素没有发生交换,那么排序就可以结束了。为此可设置一标志量<code>flag</code>,默认为<code>false</code>,如果扫描中发生交换了则把<code>flag</code>置为<code>true</code>,下轮扫描前先检查这个变量,如果<code>flag=false</code>则排序结束。<br>更进一步,可以记录每次扫描中最后一次交换的位置,下次扫描的时候只要扫描到上次的最后交换位置就行了,因为后面的都是已经排好序的,无需再比较。代码实现如下:</p>
<pre><code>void sort(int[] a) {
int tmp = 0;
int k = a.length;
int flag = k; //flag用于记录每轮扫描发生交换的最后位置
while (flag > 0) {
k = flag;
flag = 0;
for (int j = 1; j < k; ++j) {
if (a[j] < a[j - 1]) {
tmp = a[j];
a[j] = a[j - 1];
a[j - 1] = tmp;
flag = j;
}
}
}
}
</code></pre>
Java ArrayList源码分析
https://segmentfault.com/a/1190000003743218
2015-09-14T08:34:26+08:00
2015-09-14T08:34:26+08:00
然则
https://segmentfault.com/u/ming_55e57cb682df4
2
<h2>ArrayList介绍</h2>
<p><code>List</code> 接口的一个实现类,内部是用一个数组存储元素值,相当于一个可变大小的数组。</p>
<h2>签名</h2>
<pre><code>public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
</code></pre>
<p>可以看到<code>ArrayList</code>继承了<code>AbstractList</code>抽象类,它实现了<code>List</code>接口的大多数方法。如果要实现一个不可变的<code>List</code>,只要继承这个类并且实现<code>get(int)</code>和<code>size</code>方法。如果要实现可变的<code>List</code>,需要覆盖<code>set(int, E)</code>。另外,如果<code>List</code>的大小是可变的,还要覆盖<code>add(int, E)</code>和<code>remove()</code>方法。</p>
<h2>构造器</h2>
<p><code>ArrayList</code>提供了三个构造器:</p>
<pre><code>ArrayList()
ArrayList(Collection<? extends E> c)
ArrayList(int initialCapacity)</code></pre>
<p><code>Collection</code>接口约定,每个集合类应该提供两个”标准”构造器,一个是无参数的构造器(上面第一个),另外一个是拥有单个参数(类型为<code>Collettion</code>)的构造器(上面第二个)。ArrayList还提供了第三个构造器,其接受一个int值,用于设置ArrayLi的初始大小(默认大小为10)。</p>
<h2>相关方法</h2>
<h3><code>trimToSize</code></h3>
<pre><code>public void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}</code></pre>
<p>用于把<code>ArrayList</code>的容量缩减到当前实际大小,减少存储容量。其中的变量<code>modCount</code>由<code>AbstracList</code>继承而来,记录List发生结构化修改(structurally modified)的次数。<code>elementData</code>中实际存储了<code>ArrayList</code>的元素,在<code>ArrayList</code>中声明为:<code>private transient Object[] elementData;</code>变量<code>size</code>是<code>ArrayList</code>的元素数量,当<code>size < oldCapacity</code>时,调用<code>Arrays.copyOf</code>方法实现缩减。</p>
<h3>
<code>indexOf</code> 和 <code>lasIndexOf</code>
</h3>
<pre><code>public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}</code></pre>
<p>这两个方法返回指定元素的下标,要区分参数是否为<code>null</code>。<code>lastIndexOf</code>和<code>indexOf</code>类似,只不过是从后往前搜索。</p>
<h3><code>ensureCapacity</code></h3>
<pre><code>public void ensureCapacity(int minCapacity) {
if (minCapacity > 0)
ensureCapacityInternal(minCapacity);
}
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}</code></pre>
<p>这个方法可以确保<code>ArrayList</code>的大小</p>
<h3>
<code>add</code> 和 <code>addAll</code>
</h3>
<pre><code>public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}</code></pre>
<p><code>add(int index, E element)</code>向指定位置添加元素,首先调用<code>rangeCheckForAdd</code>检查<code>index</code>是否有效,如果<code>index > size || index < 0</code>将抛出异常。然后确保容量加1,调用<code>System.arraycopy</code>把从<code>index</code>开始的元素往后移动一个位置。最后把<code>index</code>处的值设置为添加的元素。还有一个重载的<code>add(E e)</code>方法是直接把元素添加到末尾。<br><code>addAll(Collection<? extends E> c)</code>和<code>addAll(int index, Collection<? extends E> c)</code>则分别是向末尾和指定位置添加<code>Collection</code>中的所有元素。</p>
<h3>
<code>remove</code> 和 <code>removeAll</code>
</h3>
<pre><code>public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}</code></pre>
<p><code>remove(Object o)</code>方法删除指定的元素。首先是查找元素位置,然后调用<code>fastRemove(index)</code>删除,其代码如下:</p>
<pre><code>private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
//把index+1往后的元素都往前移动一个位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}</code></pre>
<p>重载的<code>remove(int index)</code>方法用于删除指定位置的元素。<code>removeRange(int fromIndex, int toIndex)</code>用于删除指定位置之间的所有元素。<br><code>removeAll(Collection<?> c)</code>和<code>retainAll(Collection<?> c)</code>代码如下:</p>
<pre><code>public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}</code></pre>
<p>它们都是通过调用<code>batchRemove</code>方法实现的,其代码如下:</p>
<pre><code>private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}</code></pre>
<p>这个方法有两个参数,第一个是操作的<code>Collection</code>,第二个是一个布尔值,通过设置为<code>true</code>或<code>false</code>来选择是<code>removeAll</code>还是<code>retainAll</code>。<code>try</code>里面的语句是把留下来的放在0到w之间,然后在<code>finally</code>中第二个<code>if</code>处理w之后的空间,第一个是在<code>c.contains()</code>抛出异常时执行。</p>