diff --git a/.travis.yml b/.travis.yml index ff3d354..07f0b20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,10 @@ sudo: required language: java jdk: - - oraclejdk8 +# - oraclejdk8 - openjdk8 + +ignore: + - *.md + - .gitignore + - LICENSE diff --git a/KeyWords/src/main/java/transientkey/RunTest.java b/KeyWords/src/main/java/transientkey/RunTest.java deleted file mode 100644 index daa8e1e..0000000 --- a/KeyWords/src/main/java/transientkey/RunTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package transientkey; - -import java.io.*; - -/** - * Created by TommyYang on 2018年3月19日. - */ -public class RunTest { - - public static void main(String[] args) throws IOException, ClassNotFoundException { - //write Serializable object to file - TransientTest test = new TransientTest(); - FileOutputStream fileOutputStream = new FileOutputStream("KeyWords/res/transienttest.txt"); - ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); - objectOutputStream.writeObject(test); - objectOutputStream.flush(); - objectOutputStream.close(); - - //get Serializable object from file - ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("KeyWords/res/transienttest.txt")); - TransientTest transientTest = (TransientTest) objectInputStream.readObject(); - System.out.println("a=" + transientTest.getA() + "\t" +"b=" + transientTest.getB()); - } - -} diff --git a/KeyWords/src/main/java/volatilekey/ThreadCommunicate.java b/KeyWords/src/main/java/volatilekey/ThreadCommunicate.java deleted file mode 100644 index 10fcf36..0000000 --- a/KeyWords/src/main/java/volatilekey/ThreadCommunicate.java +++ /dev/null @@ -1,59 +0,0 @@ -package volatilekey; - -import java.util.Random; - -/** - * Created by TommyYang on 2018年3月19日. - */ -public class ThreadCommunicate { - - static class Counter{ - - private volatile int value; - - private volatile int value1; - - public void increment(){ - value = value + 1; - value1 = value; - } - - public void decrement(){ - value = value - 1; - value1 = value; - } - - public int getValue1(){ - return value1; - } - - public int getValue() { - return this.value; - } - } - - public static Counter counter = new Counter(); - - class IncrementTask implements Runnable{ - - public void run() { - for (int i=0; i < 100000; i++){ - counter.increment(); - //System.out.println("increment: "+counter.getValue()); - } - - } - } - - class DecrementTask implements Runnable{ - - public void run() { - for (int i=0; i < 100000; i++){ - counter.decrement(); - //System.out.println("decrement: "+counter.getValue()); - } - - } - } - -} diff --git a/KeyWords/volatile.md b/KeyWords/volatile.md deleted file mode 100644 index 9033f3a..0000000 --- a/KeyWords/volatile.md +++ /dev/null @@ -1,22 +0,0 @@ -# 关键字-volatile - -## 介绍 -词义:易变的 - -volatile也是变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时, -都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。 -这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 -在此解释一下Java的内存机制: -Java使用一个主内存来保存变量当前值,而每个线程则有其独立的工作内存。 -线程访问变量的时候会将变量的值拷贝到自己的工作内存中,这样, -当线程对自己工作内存中的变量进行操作之后,就造成了工作内存中的变量拷贝的值与主内存中的变量值不同。 -Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝, -而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。 -这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。 -而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。 - - - -对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取, -而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化, -系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..88936e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [TommyYang] [TommyYang] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index bb35d21..cdb2a9b 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,57 @@ -# Java面试总结 - +# Java 知识总结 [![Build Status](https://travis-ci.org/joyang1/JavaInterview.svg?branch=master)](https://travis-ci.org/joyang1/JavaInterview) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) + +**该项目主要分享一些个人经验,以及一些个人项目中遇到的问题**;还有就是一些读书笔记。 + +如果大家觉得该项目还不错,可以帮忙 `star` 或者 fork 下,你的 `star` 就是我的动力,谢谢! + +**为开源贡献自己的一份力量。** + +**欢迎关注个人公众号,每天更新原创技术好文**: +![Tommy 学习录](./img/tommy学习录.png) + +## :blue_book: [JVM 篇](/jvm) + +## :coffee: [Java 基础知识篇](/javabase) + +## :scroll: [代码篇](/codeinterview) +主要介绍 LeetCode 上面的算法题目,以及面试过程中遇到的实际编码问题总结。 + +### [排序相关](/sortpro) +- [冒泡排序](/sortpro/1.bubbleSort.md) +- [选择排序](/sortpro/2.selectionSort.md) +- [插入排序](/sortpro/3.insertionSort.md) +- [希尔排序](/sortpro/4.shellSort.md) +- [归并排序](/sortpro/5.mergeSort.md) +- [快速排序](/sortpro/6.quickSort.md) +- [堆排序](/sortpro/7.heapSort.md) +- [计数排序](/sortpro/8.countingSort.md) +- [桶排序](/sortpro/9.bucketSort.md) +- [基数排序](/sortpro/10.radixSort.md) + +## :lock: [Lock 篇](/lock) + +## :file_folder: [IO 篇](/io) + +## :computer: [大数据篇](/bigdata) + +## :earth_asia: [机器学习](/machinelearning) + +## :floppy_disk: [架构篇](/architecture) -有任何问题,或者好的建议,好的面试相关的题目都可以在这里面进行提交!
-联系我: tingzai.yang@gmail.com
-有什么问题也可以在这里进行[讨论](https://github.com/joyang1/JavaInterview/issues/1) +## :earth_asia: [web 篇](/web) -同时也欢迎大家fork或star +## :books: [书单篇](/books) +各种编程类书籍整理。大家可以直接下载阅读,增长自己编程技术。 + + +# Author +联系我:tingzai.yang@gmail.com -1.面试之-[排序算法总结](SortPro) +[个人博客](https://blog.tommyyang.cn) -2.面试之-[Servlet和JSP]() +有任何问题,或者好的建议,好的面试相关的题目都可以在这里面进行提交! -3.面试之-[Cookie和Session详解](Web) +有什么问题也可以在这里进行[讨论](https://github.com/joyang1/JavaInterview/issues/1)。 -4.面试之-[关键字总结](KeyWords) diff --git a/architecture/2PC-3PC.md b/architecture/2PC-3PC.md new file mode 100644 index 0000000..b1671e9 --- /dev/null +++ b/architecture/2PC-3PC.md @@ -0,0 +1,226 @@ +# 分布式一致性协议 +**2PC** 和 **3PC** 协议。 + +## 2PC - 二阶段提交 +2PC,Two-Phase Commit 的缩写,即二阶段提交,是计算机网络尤其是在数据库领域内,为了使分布式下的所有结点在进行事务处理过程中能够保持原子性和一致性的一种算法。通常,二阶段提交协议也被认为是一种一致性协议,用来保证分布式系统的一致性。目前,绝大多数关系型数据库都是采用二阶段提交协议来完成分布式事务处理的,利用该协议能够非常方便地完成所有分布式事务参与者的协调,统一决定事务的提交和回滚,从而能够有效地保证分布式系统的一致性,因此,二阶段提交协议被广泛地应用到许多分布式系统中。 + +### 协议说明 +顾名思义,二阶段提交协议是将事务的提交过程分成了两个阶段来进行处理,其执行流程如下: + +#### 阶段一:提交事务请求 + 1. 事务询问 + + 协调者向所有参与者发送事务内容,询问是否可以执行事务的提交操作,然后等待所有参与者的响应。 + + 2. 执行事务 + + 各参与者结点执行事务操作,并将 Undo 和 Redo 信息记入事务日志中。 + + 3. 各参与者向协调者反馈事务询问的响应。 + + 如果参与者成功执行了事务操作,那么就反馈给协调者 YES 响应,表示事务可以执行; + 如果参与者没有成功执行事务,那么就反馈给协调者 NO 响应,表示事务不可以执行。 + +上述阶段类似于一个 "投票阶段",即各参与者投票表明是否要继续执行接下来的事务提交操作。 + +#### 阶段二:执行事务提交 +该阶段根据阶段一中各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下,有如下两种可能。 + +**执行事务提交** + + 假如协调者从各参与者那得到的反馈是 YES 响应,那么就会执行事务提交操作。 + + 1. 发送提交请求 + + 协调者向所有参与者节点发出 Commit 请求。 + + 2. 事务提交 + + 参与者接收到 Commit 请求后,会正式执行事务提交操作,并在完成提交之后释放在整个 + 事务执行期间占用的事务资源。 + + 3. 反馈事务提交结果 + + 参与者在完成事务 Commit 之后,向协调者发送 Ack 消息。 + + 4. 完成事务 + + 协调者在接收到所有参与者节点反馈的 Ack 消息后,完成事务。 + +**中断事务** + + 假如协调者从任意一个参与者那得到了 NO 响应,或者在等待超时之后,协调者尚无法接收到所有 + 参与者的反馈响应,那么就中断请求。 + + 1. 发送回滚请求 + + 协调者向所有参与者节点发出 Rollback 请求。 + + 2. 事务回滚 + + 参与者接收到 Rollback 请求后,会利用其在阶段一中记录的 Undo 信息来执行事务回滚操作, + 并在完成回滚之后释放在整个事务执行期间占用的资源。 + + 3. 反馈事务回滚结果 + + 参与者在完成事务 Rollback 之后,向协调者发送 Ack 消息。 + + 4. 中断事务 + + 协调者在接收到所有参与者节点反馈的 Ack 消息后,完成事务中断。 + +总结来说,二阶段提交将一个事务的处理过程分为了投票和执行两个阶段,其核心是对每个事务都采用了先尝试后提交的处理方式,因此也可以将二阶段提交看作是一个强一致性算法,下图 1 和图 2 分别展示了二阶段提交过程中"事务提交"和"事务中断"两种场景下的交互流程。 + +**事务提交场景:** + + +**事务中断场景:** + + + +### 优缺点 +二阶段提交协议: +- 优点:原理简单,实现方便。 +- 缺点:同步阻塞、单点问题、脑裂、太过保守。 + +`同步阻塞` + + 二阶段提交协议存在的最明显也是最大的一个问题就是同步阻塞,这会极大的限制分布式系统的性能。在二阶段提交 + 的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,也就是说,各个参与者在等待其它参与者响应的过程中, + 也无法进行其它任何操作。 + +`单点问题` + + 协调者在整个二阶段提交协议中起到了非常重要的作用。一旦协调者出现问题,那么整个二阶段提交流程将无法运转, + 更为严重的是,如果协调者在第二阶段出现问题,那么其它参与者将会一直处于锁定事务资源的状态中,而无法继续 + 完成事务操作。 + +`数据不一致` + + 在二阶段协议的阶段二,即执行事务提交的时候,当协调者向所有参与者发送 Commit 请求后,发生了局部网络 + 异常或协调者在尚未发布完 Commit 请求前自身崩溃了,导致最终只有部分参与者接收到了 Commit 请求。于是, + 这部分接收到了 Commit 请求的参与者就会执行事务的提交,没有接收到的就无法进行事务的提交,而是处于等待 + 过程中,于是整个分布式系统便出现了数据不一致的现象。 + +`太过保守` + + 如果协调者在获取参与者事务是否可以提交的询问过程中,参与者出现故障而导致协调者一直无法获取到所有参与者的 + 响应,这时,协调者只能根据自身的的超时机制来判断是否中断事务,这样的策略显得比较保守。换句话说,二阶段提 + 交协议没有设计较为完善的容错机制,任意一个节点的失败都会导致整个事务的失败。 + + +## 3PC - 三阶段提交 +### 协议说明 + +3PC,是 Three-Phase Commit 的缩写,即三阶段提交,是 2PC 的改进版,其将二阶段提交协议的"提交事务请求" +过程一分为二,形成了有 CanCommit、 PreCommit 和 do Commit 三个阶段组成的事务处理协议,其协议设计如 +下图: + + + +#### 阶段一: CanCommit + +1. 事务询问 + + 协调者向所有参与者发送一个包含事务的 canCommit 请求,询问是否可以执行事务提交操作,并开始等待各参与者 + 的响应。 + +2. 各参与者向协调者反馈事务询问的响应 + + 各参与者在接收到协调者的 canCommit 请求后,正常情况下,如果其自身认为可以顺利执行事务,那么反馈 YES + 响应,并进入预备状态;否则返回 NO 响应。 + + +#### 阶段二: PreCommit +协调者会根据各参与者的响应来决定是否是否可以进行事务的 preCommit 操作,正常情况下,包含两种结果。 + +`执行事务提交` + + 所有参与者返回 YES 请求。 + + 1. 发送预提交请求 + + 协调者向所有参与者节点发出 preCommit 请求,并进入 Prepared 阶段。 + + 2. 事务预提交 + + 参与者接收到 preCommit 请求后,会执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中。 + + 3. 各参与者向协调者反馈事务执行的响应 + + 如果参与者成功地执行了事务操作,那么就会反馈给协调者 Ack 响应,同时等待最终的指令: + 提交(`commit`) 或中止(`abort`)。 + +`中断事务` + + 有一个以上的参与者返回 NO 请求,或者在等待超时之后,协调者尚无法接收到所有参与者的响应,那么就会 + 中断事务。 + + 1. 发送中断请求 + + 协调者向所有参与者发送 abort 请求。 + + 2. 中断事务 + + 无论是收到来自协调者的 abort 请求,或者是在等待协调者请求过程中出现超时,参与者都会中断事务。 + + +#### 阶段三: DoCommit +该阶段进行真正的事务提交,会存在以下两种可能的情况。 + +`执行提交` + + 1. 发送提交请求。 + + 进入这一阶段,假设协调者处于正常工作状态,并且它接收到了所有参与者的 Ack 响应,那么它将从"预提交"状态转换到"提交"状态, + 并向所有参与者发送 doCommit 请求。 + + 2. 事务提交。 + + 参与这接收到 doCommit 请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。 + + 3. 反馈事务提交结果。 + + 参与者在完成事务提交之后,向协调者发送 Ack 消息。 + + 4. 完成事务。 + + 协调者在接收到所有参与者反馈的 Ack 消息后,完成事务。 + +`中断事务` + 进入这一阶段,假设协调者处于正常工作状态,并且有任意一个参与者向协调者反馈了 NO 响应,或者在等待超时之后,协调者尚无法 + 接收到所有参与者的反馈响应,那么中断事务。 + + 1. 发送中断请求。 + + 协调者向所有参与者发送 abort 请求。 + + 2. 事务回滚。 + + 参与者接收到 abort 请求后,会利用其在阶段二中记录的 Undo 信息来执行事务回滚操作,并在完成回滚之后释放在整个事务 + 执行期间占用的资源。 + + 3. 反馈事务回滚结果。 + + 参与者在完成事务回滚之后,向协调者发送 Ack 消息。 + + 4. 中断事务。 + + 协调者在接收到所有参与者反馈的 Ack 消息后,中断事务。 + + + 需要注意的是,一旦进入阶段三,可能会存在以下两种故障。 + - 协调者出现问题。 + - 协调者和参与者之间的网络出现故障。 + + 无论出现那种情况,最终都会导致参与者无法及时接收到来自协调者的 doCommit 请求或是 abort 请求,针对这种情况,参与者都会在等待超时之后,继续执行事务提交。 + + + ### 优缺点 + 三阶段提交协议: + - 优点:相较于二阶段提交协议,三阶段提交协议最大的优点就是降低了参与者的阻塞范围,并且能够在出现单点故障后,继续达成数据 + 一致。 + + - 缺点:三阶段提交协议在去除阻塞的同时也引入了新的问题,那就是参与者在接收到 preCommit 消息后, 如果网络出现分区 + (网络分区指由于网络设备的failure,造成网络分裂为多个独立的组),此时协调者所在的节点和参与者无法进行正常 + 的网络通信,在这种情况下,该参与者依然后进行事务的提交,这必然会出现数据的不一致。 \ No newline at end of file diff --git a/architecture/README.md b/architecture/README.md new file mode 100644 index 0000000..0133378 --- /dev/null +++ b/architecture/README.md @@ -0,0 +1,39 @@ +# 架构篇 + +## UML图 +分享项目设计中需要的类图、对象图、时序图等。
+[UML图总结](uml.md) + +## 设计模式 +分享实际项目中的设计模式经验。
+[设计模式](design_patterns.md) + +## 分布式 + +### 分布式一致性问题 +1. 分布式一致性理论 + - [2PC-3PC](2PC-3PC.md) + - Paxos 理论 + - CAP 理论 + +2. Raft协议(etcd) +4. zookeeper在分布式中的应用 +5. 金融方面: 任务补偿,最终一致性 + +## 微服务 + +## 大型网站架构 + +## 流行数据库架构 +- [MySQL](mysql.md) +- [Redis](redis.md) +- [ElasticSearch](elasticSearch.md) +- MongoDB + +## 大数据 +- Hadoop +- Hive +- Spark + +## 推荐系统 +- [推荐系统](recommend_system.md) diff --git a/architecture/design_patterns.md b/architecture/design_patterns.md new file mode 100644 index 0000000..6a37917 --- /dev/null +++ b/architecture/design_patterns.md @@ -0,0 +1,177 @@ +# 设计模式 +该章节主要是在读《Head First 设计模式》的总结。 + +## 策略模式 +案例:鸭子应用(模拟鸭子游戏)。 +游戏中需要设计各种鸭子,有戏水的、呱呱叫的。用学习过的OO技术,张三(鸭子游戏"首席架构师")首先会设计一个鸭子超类,并让各种鸭子继承该超类。 +![OO类图1](https://cdn.jsdelivr.net/gh/filess/img10@main/2021/04/06/1617640082714-8a14a0d7-73d1-471a-893f-a94fe3a5966f.jpg) + +类图中,超类Duck实现了鸭子呱呱叫(quack)和游泳(swim),由于鸭子外观都不同,所以display()方法是抽象的,由子类去实现。 +具体代码在该项目的位置:JavaInterview/architecture/src/main/java/cn.tommyyang.designpatterns.duckgame.Duck。 + +以上完成第一版鸭子应用。 + +由于鸭子游戏鸭子种类一直就那么几种,导致游戏用户增长上遇到了较大的瓶颈,所以急需创新。通过大家的头脑风暴,首先我们得让鸭子飞起来。这时,负责鸭子应用的开发人员张三接到了这个需求,认为只需在Duck超类上加上fly方法即可。设计如下: +![OO类图2](https://cdn.jsdelivr.net/gh/filess/img16@main/2021/04/06/1617640156038-1a4f4a43-5071-4ca9-a1a9-055d154629cd.jpg) + +改完后,完成了游戏的第二次发版。改动点: +- 加入鸭子会飞的逻辑。 +- 有鸭子的叫声不是呱呱叫,而是吱吱叫。 + +发布完成后,在公司内部开始进行试运行。试运行期间,同事们说,为什么橡皮鸭也会飞啊。张三收到了反馈后,他发现出问题了,不能直接在超类进行fly()方法的实现,或者在橡皮鸭内部覆盖fly方法,然后什么都不做。 +如果后面有几万种鸭子,比如加入了诱饵鸭(是木头鸭),不会飞也不会叫,那怎么办,也去覆盖quack、fly方法?那这样继承这些方法有什么意义呢? +看到这里,不知道大家怎么想? + +首先总结下利用继承来提供Duck的行为会导致哪些缺点: +- 代码在多个子类中重复。 +- 运行时的行为不容易改变。 +- 不能让鸭子跳舞。 +- 很难知道所有鸭子的全部行为。 +- 鸭子不能同时又飞又叫。 +- 改变会牵一发动全身,造成其他鸭子不想要的改变。 + +通过在游戏的第二版发布过程中,张三认识到继承无法解决在当下游戏版本经常更新的情况。张三知道鸭子的规则会经常改变,每当游戏中新增新的鸭子时,就需要检查是否需要覆盖fly()/quack()......等方法,张三作为"首席架构师",显然觉得这样的架构太low了,无法容忍。 + +故张三认为需要定义一些更清晰的接口,让"某些"(非全部)鸭子类型可飞/可叫......以后还可能扩展更多方法。 +新的架构图如下: +![接口类图1](https://cdn.jsdelivr.net/gh/filess/img6@main/2021/04/06/1617640189105-a5fc6182-7ad7-41d1-be45-d88e3e642903.jpg) + +以上是张三使用接口实现的,但是张三对着类图思考了一会,发现虽然Flyable和Quackable可以解决"一小部分"问题(不会出现会飞的橡皮鸭),但是却造成了大量重复代码(大量的重复飞,重复嘎嘎叫),但代码却无法复用,甚至,在会飞的鸭子中,飞行的动作还可能各种变化...... + +作为"首席架构师",张三还是觉得否定了上述方案,深思许久后,总结道: +- 继承无法解决问题。 +- Flyable、Quackable接口不错,但是需要让会飞的鸭子才继承Flybale。 +- Java接口不具有实现代码(java8 default接口可以,但是不鼓励使用),所以继承接口无法达到代码的复用。 + +这时,张三突然想到从《Head First 设计模式》中看到的第一个设计模式中提到的三个原则: +- 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。 +- 针对接口编程,而不是针对实现编程。 +- 多用组合,少用继承。 + +通过上述三个原则,张三知道,Duck类中fly()和quack()会随着鸭子的不同而改变。那么需要把这两个行为从Duck类中取出来,建立一组新类来代表每个行为。及游戏中需要**鸭子类**和`鸭子行为类`。`鸭子行为类`专门用于提供某行为接口的实现,那么鸭子类就不需要了解行为的实现细节了。这就是`针对接口编程,而不是针对实现编程`。 + +了解到这些设计原则后,张三开始重新设计类图。 +![策略模式类图](https://cdn.jsdelivr.net/gh/filess/img16@main/2021/04/06/1617640242705-5b21c883-4753-46b4-8aab-885fe5be2894.jpg) + +通过以上设计,关键在于,鸭子将飞行和呱呱叫的动作"委托"(delegate)别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。 +这样的好处,就是上述的三个原则,并且在运行时,可以随意修改其呱呱叫和飞行的行为,通过setFlyBehavior和setQuackBehavior来实现。 + +MallardDuck 的具体实现如下: + +``` java + +/** + * 野鸭类 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:00 + * @Software: IntelliJ IDEA + * @File : MallardDuck.java + */ +public class MallardDuck extends Duck { + + public MallardDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 具体实现鸭子外观 + */ + public void display() { + System.out.println("绿头"); + } +} + +``` + +在构造野鸭类的时候,完成 FlyBehavior 和 QuackBehavior 的定义。 + +最后,张三"首席架构师",通过`策略模式(Strategy Pattern)`实现了鸭子游戏。 + +`策略模式`定义了算法簇,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 + +恭喜你,完成了第一个设计模式`策略模式`的学习,后续希望该模式在你的编程生涯中可以持续被使用。 + +## 观察者模式 +张三由于通过使用`策略模式`完成鸭子游戏应用的重构后,在鸭子游戏的维护和更新上只需要投入少量的时间。 +于是张三接到了下一个需求,*互联网气象观测站*的架构工作。 + +*互联网气象观测站*的三个组成部分: +- 气象站(获得实际气象数据的物理装置) +- WeatherData 对象(追踪来自气象站的数据,并更新布告板) +- 布告板(显示当前天气状况)给用户看 + +张三总结出系统用例如下: + +张三认为,如果团队接受了这个项目,那工作就是建立一个应应用,利用 WeatherData 对象取得数据(由公司兄弟团队去和气象站对接),并更新三个布告板:当前状况、气象统计、天气预报。 + +张三团队开始了工作,第二天由公司兄弟团队提供的获取相关气象数据的 WeatherData 类,类图如下: + +下面看一下实现的反面案例:针对具体实现编程。 +具体代码在该项目的位置:[JavaInterview](https://github.com/joyang1/JavaInterview); +JavaInterview/architecture/src/main/java/cn.tommyyang.designpatterns.observable.WeatherData。 + +```java + +public class WeatherData { + /** + * 温度 + */ + private float temperature; + + /** + * 湿度 + */ + private float humidity; + + /** + * 气压 + */ + private float pressure; + + public float getTemperature() { + return this.temperature; + } + + public float getHumidity() { + return this.humidity; + } + + public float getPressure() { + return pressure; + } + + /** + * 一旦气象测量更新,此方法会被调用 + */ + public void measurementsChanged() { + float temp = getTemperature(); + float humidity = getHumidity(); + float pressure = getPressure(); + + // 布告板数据更新 + // 针对具体的实现编程,导致后续增删布告板时必须修改程序 + CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); + currentConditionsDisplay.update(temp, humidity, pressure); + } +} + +``` +回故下上一篇讲解的一些设计原则: +- 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。 +- 针对接口编程,而不是针对实现编程。 +- 多用组合,少用继承。 + +不难发现上面的代码非常*垃圾*。 + +下面由首席架构师张三带大家使用观察者模式来实现该工程。 + +`观察者模式`:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。 +张三画了一张简易的观察者模式图如下: + +## 装饰模式 + +## 工厂模式 +### 简单工厂模式 + +### 工厂方法模式 \ No newline at end of file diff --git a/architecture/elasticSearch.md b/architecture/elasticSearch.md new file mode 100644 index 0000000..86a4bf4 --- /dev/null +++ b/architecture/elasticSearch.md @@ -0,0 +1,131 @@ +# ElasticSearch + +ElasticSearch 技术相关介绍。 + +`目录` +[too many dynamic script rejected](#Too Many Dynamic Script Rejected) + +## Too Many Dynamic Script Rejected + +最近工程中会使用到ElasticSearch(以下统称ES),就是将一些统计结果(点击量:click_count,曝光量:impr_count,点击曝光比:ctr=click_count/impr_count)写入到ES,会用到ES的dynamic script去实时修改ctr。然后就遇到了too many dynamic script rejected的问题。 + +### 问题解决过程 +#### 获取EsClient的源码 + +``` java +public static synchronized TransportClient getInstance() throws UnknownHostException { + if(transportClient == null){ + String clusterName = DataSourceUtils.getProperteValue("es.cluster", "es"); + String host = DataSourceUtils.getProperteValue("es.host","localhost"); + Integer port = Integer.parseInt(DataSourceUtils.getProperteValue("es.port","9300")); + Settings settings = Settings.builder() + .put("cluster.name", clusterName) + .put("client.transport.sniff", true) + .build(); + transportClient = new PreBuiltTransportClient(settings).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); + } + return transportClient; + } +``` + +#### 贴上有问题的源码(dynamic script update ctr) + +``` java +TransportClient esClient = EsClient.getInstance(); +UpdateRequest updateRequest = new UpdateRequest("notes2", "note", "2"); +String script = String.format("ctx._source.click_count=%d;ctx._source.impr_count=%d;ctx._source.ctr=(double)ctx._source.click_count/ctx._source.impr_count", 15, 120); +updateRequest.script(new Script(script)); +updateRequest.retryOnConflict(3); +esClient.update(updateRequest).get(); +``` + +用以上code去update ctr, 然后发现log中出现了too many dynamic script rejected,导致ctr更新失败。 + +#### 寻找原因 + +在ES官网上查找Script相关的文档说明,在Script Parameters栏目发现以下参数 + +`lang` + +Specifies the language the script is written in. Defaults to painless. +指定编写脚本的语言,默认为painless。 + +`source, id` + +Specifies the source of the script. An inline script is specified source. A stored script is specified id and is retrieved from the cluster state. +指定脚本的来源。inline类型的脚本是指定source。stored类型的脚本是指定source的id,通过id从ES集群上检索对应的source。 + +`params` + +Specifies any named parameters that are passed into the script as variables. +指定作为变量传递到脚本的任何参数。 + +然后在这个下面的一段话,很重要,就是解决我们问题的关键信息 + +The first time Elasticsearch sees a new script, it compiles it and stores the compiled version in a cache. Compilation can be a heavy process. + +If you need to pass variables into the script, you should pass them in as named params instead of hard-coding values into the script itself. For example, if you want to be able to multiply a field value by different multipliers, don’t hard-code the multiplier into the script: +> "source": "doc['my_field'] * 2" + +Instead, pass it in as a named parameter: + +> "source": "doc['my_field'] * multiplier", + "params": { + "multiplier": 2 + } + +通过以上code我们可以得知 对于dynamic script我们应该使用params参数来传递参数,而不是把参数拼接在script中,这样就不需要ES就会在第一次的时候把script编译后保存在缓存中,而不是每次都会去编译。 + +`方法一` 使用inline type script + +``` java +TransportClient esClient = EsClient.getInstance(); +UpdateRequest updateRequest = new UpdateRequest("notes2", "note", "2"); +Map params = new HashMap() { + { + put("click_count", 120); + put("impr_count", 5); + } +}; + +String code = "ctx._source.click_count=params.click_count;ctx._source.impr_count=params.impr_count;ctx._source.ctr=(double)ctx._source.click_count/ctx._source.impr_count*100"; +Script script = new Script(ScriptType.INLINE, "painless", code, params); +updateRequest.script(script); +esClient.update(updateRequest).get(); +``` + +`方法二` 使用stored type script + +``` java +TransportClient esClient = EsClient.getInstance(); +UpdateRequest updateRequest = new UpdateRequest("notes2", "note", "2"); +Map params = new HashMap() { + { + put("click_count", 120); + put("impr_count", 5); + } +}; + +//String code = "ctx._source.read_time=params.read_time;ctx._source.read_num=params.read_num;ctx._source.avg_read_time=(double)ctx._source.read_time/ctx._source.read_num"; +Script script = new Script(ScriptType.STORED, "painless", "ctr_calc", params); +updateRequest.script(script); +esClient.update(updateRequest).get(); +``` + +使用方法二stored script的时候 需要在kibana里面将script存储到ES集群里面 + +`方法如下` + +``` java +POST _scripts/ctr_calc +{ + "script": { + "lang": "painless", + "source": "ctx._source.click_count=params.click_count;ctx._source.impr_count=params.impr_count;ctx._source.ctr=(double)ctx._source.click_count/ctx._source.impr_count*100" + } +} +``` + +#### 结果 + +通过以上两种方法,都可以解决too many dynamic script rejected的问题。 diff --git a/architecture/img/aggregation.png b/architecture/img/aggregation.png new file mode 100644 index 0000000..3ab02c7 Binary files /dev/null and b/architecture/img/aggregation.png differ diff --git a/architecture/img/association.png b/architecture/img/association.png new file mode 100644 index 0000000..96068ad Binary files /dev/null and b/architecture/img/association.png differ diff --git a/architecture/img/combine.png b/architecture/img/combine.png new file mode 100644 index 0000000..b5f64f4 Binary files /dev/null and b/architecture/img/combine.png differ diff --git a/architecture/img/depend.png b/architecture/img/depend.png new file mode 100644 index 0000000..457ad2a Binary files /dev/null and b/architecture/img/depend.png differ diff --git a/architecture/img/extends.png b/architecture/img/extends.png new file mode 100644 index 0000000..e0fb4c1 Binary files /dev/null and b/architecture/img/extends.png differ diff --git a/architecture/img/implement.png b/architecture/img/implement.png new file mode 100644 index 0000000..9057c45 Binary files /dev/null and b/architecture/img/implement.png differ diff --git a/architecture/img/uml-class-animal.jpg b/architecture/img/uml-class-animal.jpg new file mode 100644 index 0000000..fd38c89 Binary files /dev/null and b/architecture/img/uml-class-animal.jpg differ diff --git a/architecture/img/uml-object.jpg b/architecture/img/uml-object.jpg new file mode 100644 index 0000000..3d165eb Binary files /dev/null and b/architecture/img/uml-object.jpg differ diff --git a/architecture/mysql.md b/architecture/mysql.md new file mode 100644 index 0000000..12b7375 --- /dev/null +++ b/architecture/mysql.md @@ -0,0 +1,705 @@ +# MySQL +MySQL 是我们日常开发中用到的最多的关系型数据库,该篇总结 MySQL 的常用知识点。 + +>**目录** +- [查询优化](#查询优化) + + - [优化之 EXPLAIN](#优化之EXPLAIN) + - [优化访问](#优化数据访问) + - [重构查询方式](#重构查询方式) + +- [MySQL事物隔离级别与锁总结](#MySQL事物隔离级别与锁总结) + +- [主从复制](#主从复制) + +- [读写分离](#读写分离) + +## 查询优化 +### 优化之EXPLAIN +使用 EXPLAIN 可以帮助分析自己写的 SQL 语句,看看我们是否用到了索引。 + +#### 按以下两个 SQL 新建两张表 +```sql + +CREATE TABLE `demo` ( + `ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo name', + `author` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo author', + PRIMARY KEY (`ID`), + KEY `IX_name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE `demo_details` ( + `ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `demoId` int(11) unsigned NOT NULL COMMENT 'demo id', + `url` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo author', + PRIMARY KEY (`ID`), + KEY `FIX_demoId_ID` (`demoId`), + CONSTRAINT `demo_details_ibfk_1` FOREIGN KEY (`demoId`) REFERENCES `demo` (`ID`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +``` + +#### 具体用法 + +``` +mysql> EXPLAIN SELECT * FROM demo WHERE ID = 1\G + +执行结果: + +id: 1 +select_type: SIMPLE +table: demo +partitions: NULL +type: const +possible_keys: PRIMARY +key: PRIMARY +key_len: 4 +ref: const +rows: 1 +filtered: 100.00 +Extra: NULL +1 row in set, 1 warning (0.00 sec) + +``` + +以上执行结果各行表示的含义: +- id: SELECT 查询的标识符,每个 SELECT 都会自动分配一个唯一的标识符 +- select_type: SELECT 查询的类型 +- table: 查询的是哪个表 +- partitions: 匹配的分区 +- type: join 类型 +- possible_keys: 此次查询中可能选用的索引 +- key: 此次查询中确切使用到的索引 +- ref: 哪个字段或常数与 key 一起被使用 +- rows: 显示此查询一共扫描了多少行,这个是一个估计值 +- filtered: 表示此查询条件所过滤的数据的百分比 +- extra: 额外的信息 + +#### select_type +`select_type` 表示了查询的类型, 它的常用取值有: +- SIMPLE: 表示此查询不包含 UNION 查询或子查询 +- PRIMARY: 表示此查询是最外层的查询 +- UNION: 表示此查询是 UNION 的第二或随后的查询 +- DEPENDENT UNION: UNION 中的第二个或后面的查询语句, 取决于外面的查询 +- UNION RESULT: UNION 的结果 +- SUBQUERY: 子查询中的第一个 SELECT +- DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果 + +最常见的应该是 SIMPLE,当我们的查询 SQL 里面没有 UNION 查询或者子查询的时候,那么通常就是 SIMPLE 类型。 + +#### type +`type` 字段比较重要,它提供了判断查询是否高效的重要依据。通过 type 字段,我们可以判断此次查询是**全表扫描**,还是**索引扫描**等。 + +`type` 常用取值有: +- system + + 表中只有一条数据,这个类型是特殊的 const 类型。 + +- const + + 针对主键或唯一索引的等值查询扫描,最多只返回一行数据,const 查询速度非常快,因为它仅仅读取一次即可。 + +- eq_ref + + 此类型通常出现在多表的 join 查询,表示对于前表的每一个结果,都只能匹配到后表的一行结果,并且查询的比较操作通常是 =,查询效率较高。 + demo 如下: + ``` + + mysql>EXPLAIN select * from demo,demo_details where demo.ID = demo_details.demoId\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo_details + partitions: NULL + type: ALL + possible_keys: FIX_demoId_ID + key: NULL + key_len: NULL + ref: NULL + rows: 1 + filtered: 100.00 + Extra: NULL + *************************** 2. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: eq_ref + possible_keys: PRIMARY + key: PRIMARY + key_len: 4 + ref: springdemo.demo_details.demoId + rows: 1 + filtered: 100.00 + Extra: NULL + 2 rows in set, 1 warning (0.00 sec) + + ``` + +- ref + + 此类型通常出现在多表的 join 查询,针对于非唯一或非主键索引,或者是使用了**最左前缀**规则索引的查询。 + + ``` + + mysql> EXPLAIN select * from demo where name='tommy'\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: ref + possible_keys: IX_name + key: IX_name + key_len: 258 + ref: const + rows: 1 + filtered: 100.00 + Extra: NULL + 1 row in set, 1 warning (0.00 sec) + + ``` + +- range + + 表示使用索引范围查询,通过索引字段范围获取表中部分数据记录,这个类型通常出现在 =、 、>、>=、 <、 <=、 IS NULL、 <=>、 BETWEEN、 IN 操作中。 + 当 type 是 range 时,那么 EXPLAIN 输出的 ref 字段为 NULL, 并且 key_len 字段是此次查询中使用到的索引的最长的那个。 + demo 如下: + ``` + + mysql> EXPLAIN select * from demo where id between 1 and 2\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: range + possible_keys: PRIMARY + key: PRIMARY + key_len: 4 + ref: NULL + rows: 2 + filtered: 100.00 + Extra: Using where + 1 row in set, 1 warning (0.00 sec) + + ``` + +- index + + 表示全索引扫描(full index scan),和 ALL 类型类似,只不过 ALL 类型是全表扫描,而 index 类型则仅仅扫描所有的索引,而不扫描数据。 + index 类型通常出现在:所要查询的数据直接在索引树中就可以获取到,而不需要扫描数据。当是这种情况时,Extra 字段 会显示 Using index。 + demo 如下: + ``` + + mysql> EXPLAIN select name from demo\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: index + possible_keys: NULL + key: IX_name + key_len: 258 + ref: NULL + rows: 2 + filtered: 100.00 + Extra: Using index + 1 row in set, 1 warning (0.00 sec) + + ``` + +- ALL + + 表示全表扫描,这个类型的查询是性能最差的查询之一。通常来说,我们的查询不应该出现 ALL 类型的查询,因为这样的查询在数据量大的情况下,对数据库的性能是巨大的灾难。 + 如一个查询是 ALL 类型查询,那么一般来说可以对相应的字段添加索引来避免。 + demo 如下: + ``` + + mysql> EXPLAIN select * from demo\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: ALL + possible_keys: NULL + key: NULL + key_len: NULL + ref: NULL + rows: 2 + filtered: 100.00 + Extra: NULL + 1 row in set, 1 warning (0.00 sec) + + ``` + +#### type 类型的性能比较 +通常来说, 不同的 type 类型的性能关系如下: +`ALL < index < range < index_merge < ref < eq_ref < const < system` + +ALL 类型因为是全表扫描,因此在相同的查询条件下,它是速度最慢的。 +而 index 类型的查询虽然不是全表扫描,但是它扫描了所有的索引,因此比 ALL 类型的稍快。 +后面的几种类型都是利用了索引来查询数据,因此可以过滤部分或大部分数据,因此查询效率就比较高了。 + +#### possible_keys +`possible_key` 表示 MySQL 在查询时,可能使用到的索引。即使有些索引出现在 possible_key 中,但是并不表示此索引一定会被 MySQL 使用到。MySQL 在查询时具体使用到那些索引,与 key 和你写的 SQL 有关。 + +#### key +此字段表示 MySQL 在当前查询时所真正会使用到的索引。 + +#### key_len +表示查询优化器使用了索引的字节数。这个字段可以评估组合索引是否完全被使用,或只有最左部分字段被使用到。 +key_len 的计算规则如下: + +- 字符串 + - char(n):n 字节长度 + - varchar(n):如果是 utf8 编码,则是 3n + 2字节;如果是 utf8mb4 编码,则是 4n + 2 字节。 + +- 数值类型 + - TINYINT: 1字节 + - SMALLINT: 2字节 + - MEDIUMINT: 3字节 + - INT: 4字节 + - BIGINT: 8字节 + +- 时间类型 + - DATE:3字节 + - TIMESTAMP:4字节 + - DATETIME:8字节 + +- 字段属性 + NULL 属性 占用一个字节。如果一个字段是 NOT NULL 的,则没有此属性。 + +#### rows +rows 也是一个重要的字段。MySQL 查询优化器根据统计信息,估算 SQL 要查找到结果集需要扫描读取的数据行数。这个值非常直观显示 SQL 的效率好坏,原则上 rows 越少越好。 + +#### Extra +EXPLAIN 中的很多额外的信息会在 Extra 字段显示,常见的有以下几种内容: +- Using filesort + + 当 Extra 中有 Using filesort 时,表示 MySQL 需额外的排序操作,不能通过索引顺序达到排序效果。一般有 Using filesort,都建议优化去掉,因为这样的查询 CPU 资源消耗大。 + demo 如下: + ``` + + mysql> EXPLAIN select * from demo order by name\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: ALL + possible_keys: NULL + key: NULL + key_len: NULL + ref: NULL + rows: 2 + filtered: 100.00 + Extra: Using filesort + 1 row in set, 1 warning (0.00 sec) + + ``` + +- Using index + + **覆盖索引扫描**,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,往往说明性能不错。 + demo 如下: + ``` + + mysql> EXPLAIN select * from demo order by id desc\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: index + possible_keys: NULL + key: PRIMARY + key_len: 4 + ref: NULL + rows: 2 + filtered: 100.00 + Extra: Backward index scan + 1 row in set, 1 warning (0.00 sec) + + ``` + +- Using temporary + + 查询有使用临时表,一般出现于排序,分组和多表 join 的情况,查询效率不高,建议优化。 + +- Using where + + 列数据是从仅仅使用了索引中的信息而没有读取实际行的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示 MySQL 服务器将在存储引擎检索行后再进行过滤。 + +### 优化数据访问 +#### 减少返回数据的数量 +- 只返回必要的列:最好不要使用 SELECT * 语句。 +- 只返回必要的行:使用 LIMIT 语句来限制返回的数据。 + +#### 减少服务器端扫描的行数 +- 最有效的方式是使用索引来覆盖查询。 +- 缓存重复查询的数据:使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。 + +### 重构查询方式 +#### 切分大查询 +一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。 + +```SQL + +DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 12 MONTH); +rows_affected = 0 +do { + rows_affected = do_query( + "DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 12 MONTH) LIMIT 5000") +} while rows_affected> 0 + +``` + + +#### 分解大连接查询 +将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有: +- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。 +- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。 +- 减少锁竞争。(select *** for update) +- 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。 + +## MySQL事物隔离级别与锁总结 +锁是计算机协调多个进程或线程并发访问某一资源的机制。锁保证数据并发访问的一致性、有效性;锁冲突也是影响数据库并发访问性能的一个重要因素。锁是 MySQL 在服务器层和存储引擎层的并发控制。 + +### MySQL 事物隔离级别 +说到 MySQL 的锁,先来了解一下 MySQL 的事物隔离级别。 + +#### 事务的四个重要特性 --- ACID 特性 +- 原子性(Atomicity) + + 事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。 + +- 一致性(Consistency) + + 指事务将数据库从一种状态转变为另一种一致的的状态。事务开始前和结束后,数据库的完整性约束没有被破坏。 + +- 隔离性(Isolation) + + 要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务不可见。也可以理解为多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行结果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自完整的数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。 **Tips:MySQL 通过锁机制来保证事务的隔离性**。 + +- 持久性(Durability) + 事务一旦提交,则其结果就是永久性的。即使发生宕机的故障,数据库也能将数据恢复,也就是说事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。这只是从事务本身的角度来保证,排除 RDBMS(关系型数据库管理系统,例如 Oracle、MySQL 等)本身发生的故障。**Tips:MySQL 使用 redo log 来保证事务的持久性**。 + +#### 事务的四种隔离级别 +在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。我们的数据库锁,也是为了构建这些隔离级别存在的。 + +| 隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read)| +| :----: | :----: | :----: | :----: | +| 未提交读(Read Uncommitted) | 可能 | 可能 | 可能 | +| 已提交读(Read Committed) | 不可能 | 可能 | 可能 | +| 可重复读(Repeated Read) | 不可能 | 不可能 | 可能 | +| 可串行化(Serializable) | 不可能 | 不可能 | 不可能 | + +- 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。 +- 已提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。 +- 可重复读(Repeated Read):在同一个事务内的查询都是事务开始时刻一致的,InnoDB 默认级别。在 SQL 标准中,该隔离级别消除了不可重复读,但是还存在幻读。 +- 可串行化(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。 + +通过上述描述可以看出,Read Uncommitted 这种级别,数据库一般都不会用,而且任何操作都不会加锁。 + +#### MySQL 事务级别详解 +以下信息都是针对 MySQL 8.0 版本进行测试。 + +新建一张测试表 demo,SQL 如下: + +```sql + +CREATE TABLE `demo` ( + `ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo name', + `author` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo author', + PRIMARY KEY (`ID`), + KEY `IX_name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +``` + +`查看 MySQL 当前事务级别`:select @@session.transaction_isolation; + +**未提交读(Read Uncommitted)** + +该隔离级别的事务会读到其它未提交事务的数据,此现象也称之为**脏读**。 + +准备数据: + +```sql + +SET @@session.transaction_isolation = 'READ-UNCOMMITTED'; + +insert into demo(name, author) values('name1', 'tommy'); + +``` + +- 准备两个终端,MySQL 终端 1 和 MySQL 终端 2,再准备一张测试表 demo,写入一条测试数据并调整隔离级别为 Read Uncommitted,任意一个终端执行即可。 + +- 登录 MySQL 终端 1,开启一个事务,将 ID 为 1 对应的 name1 的记录更新为 name2。 + +> begin;
+ update demo set name = 'name2' where id = 1;
+ select * from demo; -- 此时看到一条 name 为 name2 的记录 + +- 登录 MySQL 终端 2,开启一个事务后查看表中的数据。 + +>use demo;
+ begin;
+ select * from demo; -- 此时看到一条 name 为 name2 的记录 + + +最后一步读取到了 MySQL 终端 1 中未提交的事务(没有 commit 提交动作),即产生了**脏读**,大部分业务场景都不允许**脏读**出现,但是此隔离级别下数据库的并发是最好的。由于会出现**脏读**,所以这种隔离级别一般数据库都不会使用。 + + +**已提交读(Read Committed)** + +一个事务可以读取另一个已提交的事务,多次读取会造成不一样的结果,此现象称为不可重复读问题,Oracle 和 SQL Server 的默认隔离级别,但是不是 MySQL 的默认隔离级别。 + +准备数据: + +```sql + +SET @@session.transaction_isolation = 'READ-COMMITTED'; + +insert into demo(name, author) values('name1', 'tommy'); + +``` + +- 准备两个终端,MySQL 终端 1 和 MySQL 终端 2,再准备一张测试表 demo,写入一条测试数据并调整隔离级别为 Read Committed,任意一个终端执行即可。 + +- 登录 MySQL 终端 1,开启一个事务,将 ID 为 1 对应的 name1 的记录更新为 name2。 + +> begin;
+ update demo set name = 'name2' where id = 1;
+ select * from demo; -- 此时看到一条 name 为 name2 的记录 + +- 登录 MySQL 终端 2,开启一个事务后查看表中的数据。 + +> use demo;
+ begin;
+ select * from demo; -- 此时看到一条 name 为 name1 的记录 + +- 切换 MySQL 终端 1,提交事务。 + +> commit; + +- 切换 MySQL 终端 2。 + +> select * from test; -- 此时看到一条 name 为 name2 的记录 + +MySQL 终端 2 在开启了一个事务之后,在第一次读取 demo 表(此时 MySQL 终端 1 的事务还未提交)时 name 的值为 'name1',在第二次读取 demo 表(此时 MySQL 终端 1 的事务已经提交)时 name 列的值 'name1' 已经变为 'name2',说明在此隔离级别下只能读取到已提交的事务。 + + +**可重复读(Repeated Read)** + +该隔离级别是 MySQL 默认的隔离级别,在同一个事务里,select 的结果是事务开始时时间点的状态,因此,同样的 select 操作读到的结果会是一致的,但是,会有**幻读**现象。MySQL 的 InnoDB 引擎可以通过 next-key locks(行锁) 机制来避免**幻读**。使用行锁来避免**幻读**会在后续锁的介绍中进行解释。 + +准备数据: + +```sql + +SET @@session.transaction_isolation = 'REPEATABLE-READ'; + +``` + +- 准备两个终端,MySQL 终端 1 和 MySQL 终端 2,再准备一张测试表 demo,写入一条测试数据并调整隔离级别为 Repeated Read,任意一个终端执行即可。 + +- 登录 MySQL 终端 1,开启一个事务。 + +> begin;
+ select * from demo; -- 无记录 + +- 登录 MySQL 终端 2,开启一个事务后查看表中的数据。 + +> begin;
+ select * from demo; -- 无记录 + +- 切换 MySQL 终端 1,提交事务。 + +> insert into demo(id, name, author) values(1, 'name1', 'tommy');
+ commit; + +- 切换 MySQL 终端 2。 + +> select * from demo; --此时查询还是无记录 + + 以上可以证明,在该隔离级别下已经读取不到别的已提交的事务,如果想看到 MySQL 终端 1 提交的事务,在 MySQL 终端 2 将当前事务提交后再次查询就可以读取到 MySQL 终端 1 提交的事务。我们接着实验,看看在该隔离级别下是否会存在别的问题。 + +- 此时接着在 MySQL 终端 2 插入一条数据。 + +> insert into demo(id, name, author) values(1, 'name1', 'tommy'); Duplicate entry '1' for key 'PRIMARY',主键冲突。 + + 这时你肯定会有疑问,明明在上一步没有数据,为什么在这里会报错呢?其实这就是该隔离级别下可能产生的问题,MySQL 称之为幻读。注意我在这里强调的是 MySQL 数据库,Oracle 数据库对于幻读的定义可能有所不同。 + + +**可串行化(Serializable)** + +在该隔离级别下事务都是串行顺序执行的,MySQL 数据库的 InnoDB 引擎会给读操作隐式加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。 + +准备数据: + +```sql + +SET @@session.transaction_isolation = 'SERIALIZABLE'; + +``` + +- 准备两个终端,MySQL 终端 1 和 MySQL 终端 2,再准备一张测试表 demo,写入一条测试数据并调整隔离级别为 Serializable,任意一个终端执行即可。 + +- 登录 MySQL 终端 1,开启一个事务,并写入一条数据。 + +> begin;
+ insert into demo(id, name, author) values(1, 'name1', 'tommy'); + +- 登录 MySQL 终端 2,开启一个事务。 + +> begin;
+ select * from test; -- 此时会一直卡住 + +- 立马切换到 MySQL 终端 1,提交事务。 + +> commit; + +一旦事务提交,MySQL 终端 2 会立马返回 ID 为 1 的记录,否则会一直卡住,直到超时,其中超时参数是由 innodb_lock_wait_timeout 控制。该隔离级别的数据库并发能力最弱,因为每条 select 语句都会加锁。 + +### 锁机制 +InnoDB 实现了两种类型的行级锁: +**共享锁(也称为 S 锁)**:允许事务读取一行数据。 +可以使用 SQL 语句 select * from tableName where ... lock in share mode; 手动加 S 锁。 + +**独占锁(也称为 X 锁)**:允许事务删除或更新一行数据。 +可以使用 SQL 语句 select * from tableName where ... for update; 手动加 X 锁。 + +S 锁和 S 锁是兼容的,X 锁和其它锁都不兼容,举个例子,事务 T1 获取了一个行 r1 的 S 锁,另外事务 T2 可以立即获得行 r1 的 S 锁,此时 T1 和 T2 共同获得行 r1 的 S 锁,此种情况称为锁兼容,但是另外一个事务 T2 此时如果想获得行 r1 的 X 锁,则必须等待 T1 对行 r 锁的释放,此种情况也成为锁冲突。 + +为了实现多粒度的锁机制,InnoDB 还有两种内部使用的意向锁,由 InnoDB 自动添加,且都是表级别的锁。 + +**意向共享锁(IS)**:事务即将给表中的各个行设置共享锁,事务给数据行加 S 锁前必须获得该表的 IS 锁。 +**意向排他锁(IX)**:事务即将给表中的各个行设置排他锁,事务给数据行加 X 锁前必须获得该表 IX 锁。 + +**意向锁解决的问题**:如果下一个事务试图在该表级别上应用共享和排它锁,则会受到由第一个任务控制的表级别意向锁的阻塞。下一个事务在其锁定该表前不必检查各个页或行锁,而只需检查该表上的意向锁。 + +#### 表级意向锁和行级锁的兼容互斥性 +| 锁类型 | X | IX | S | IS | +| :----: | :----: | :----: | :----: | :----: | +| X | 互斥 | 互斥 | 互斥 | 互斥 | +| IX | 互斥 | 兼容 | 互斥 | 兼容 | +| S | 互斥 | 互斥 | 兼容 | 兼容 | +| IS | 互斥 | 兼容 | 兼容 | 兼容 | + + +#### 锁粒度 + +#### 不同粒度锁的比较 + +### MylSAM 表锁 +#### MylSAM 表级锁模式 + +#### MylSAM 加表锁方法 + +### 死锁案例分析 +- show variables like 'innodb_deadlock_detect'; +- show status like 'table_locks%'; +- show status like 'innodb_row_lock%'; +- show engine innodb status; + +## 主从复制 +- 数据分布 +- 负载平衡(Load Balancing) +- 备份 +- 高可用性(High Availability)和容错 + +### 相关命令 +- show status 查看整个 mysql 状态,这个命令也可以看到 Seconds_Behind_Master。 +- show master status 查看主库信息 +- show slave status 查看从库信息 + + 查看参数 Seconds_Behind_Master 来看主从是否延迟。 + - 0:该值为零,是我们极为渴望看到的情况,表示主从复制良好,可以认为lag不存在。 + - 正值:表示主从已经出现延时,数字越大表示从库落后主库越多。 + - 负值:几乎很少见,我只是听一些资深的DBA说见过,其实,这是一个BUG值,该参数是不支持负值的,也就是不应该出现。 + +- show processlist 查看数据库线程列表信息 + +### 原理 + +#### 基于语句的复制 +在 MySQL5.0 及之前的版本只支持基于语句的复制。基于语句复制的模式下,主库会记录那些造成数据更改的事件,当备库读取并重放这些事件时,备库只是把主库上执行过的 SQL 再执行一遍。 + +**优点** +- 实现简单。 +- 二进制日志里的事件更加的紧凑。(全部是需要执行的 SQL 语句) + +**缺点** +- 执行语句的时间不同。(机器的 CPU 和内存可能很不一样) +- 还有一些动态数据,比如 `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`,还有一些使用函数的语句,比如包含 `CURRENT_USER` 的语句。 +- 更新是串行的。(需要考虑锁带来的性能消耗) + +#### 基于行的复制 +在 MySQL5.1 开始支持基于行的复制,这种方式会将实际的数据记录到二进制日志中。 + +**优点** +- 可以正确的复制每一行。(不存在基于语句的复制出现的那种问题) + +- 可以更高效地复制数据。(备库不用重放 MySQL 的事件,这个也是针对具体的 SQL,有的 SQL 可以提高效率,有的确会降低效率。) + ```sql + insert into tab1 (col1, col2, sum_col3) select col1, col2, sum(col1, col2) from tab2 group by col1, col2; # 基于行的复制只需要把插入结果记录下来 + + update tab3 set col1 = 0; // 基于行的复制就要在二进制中记录全表的数据 + ``` +- 有利于数据的恢复 + +**缺点** +- 无法判断数据库做了什么,因为不知道执行的 SQL。 +- 针对上述全表数据更新的时候,效率会很低。 + +#### 复制文件 +- mysql-bin.index + + 该文件是 MySQL 用来识别具体的二进制 binlog 文件;该文件记录磁盘上 binlog 文件。 + +- mysql-relay-bin.index + + 中继日志的索引文件。跟 mysql-bin.index 作用类似。 + +- master.info + + 保存备库连接到主库所需要的信息,格式为纯文本。这个文件以文本的方式记录了复制用户的密码。故要注意该文件的权限。 + +- relay-log.info + + 包含当前备库复制的二进制日志和中继日志的坐标(及备库复制到主库的具体位置)。 + + +#### 发送复制事件到其它的备库 +当设置 log_slave_updates 时,你可以让 slave 扮演其它 slave 的 master。此时,slave 把 SQL 线程执行的事件写进行自己的二进制日志(binary log),然后,它的 slave 可以获取这些事件并执行它。 + + + +#### 复制过滤器 +复制过滤可以让你只复制服务器中的一部分数据,有两种复制过滤:在 master 上过滤二进制日志中的事件;在 slave 上过滤中继日志中的事件。 + + + +## 读写分离 +基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。 +读写分离可以提高系统的效率,特别是对于写少读多的系统,使用读写分离可以大大提高系统的效率。这也是从库会有多个的原因,读的时候可以做负载均衡(可以通过主健或者用户 id 等 hash 的方式,也可以使用 Round Robin 轮询算法;负载均衡算法有很多种,这里就不一一列举),让读请求分布到不同的从库上,提高读请求的效率。 + +## 持久化数据分析 + +数据InnoDB到磁盘需要经过 + +- InnoDB buffer pool, Redo log buffer。这个是InnoDB应用系统本身的缓冲。 +- page cache /Buffer cache(可通过o_direct绕过)。这个是vfs层的缓冲。 +- Inode cache/directory buffer。这个也是vfs层的缓冲。需要通过O_SYNC或者fsync()来刷新。 +- Write-Back buffer。(可设置存储控制器参数绕过) +- Disk on-broad buffer。(可通过设置磁盘控制器参数绕过) + + + + diff --git a/architecture/pom.xml b/architecture/pom.xml new file mode 100644 index 0000000..4b413ab --- /dev/null +++ b/architecture/pom.xml @@ -0,0 +1,15 @@ + + + + javainterview + cn.tommyyang + 1.0-SNAPSHOT + + 4.0.0 + + architecture + + + \ No newline at end of file diff --git a/architecture/redis.md b/architecture/redis.md new file mode 100644 index 0000000..bf13fd0 --- /dev/null +++ b/architecture/redis.md @@ -0,0 +1,32 @@ +# Redis +Redis 作为一种 KV 缓存服务器,有着极高的性能,相对于 Memcache,Redis 支持更多种数据类型,因此在业界应用广泛。 + +## 性能高的原因 +- 纯内存操作 +- 单线程 +- 高效的数据结构 +- 合理的数据编码 +- 其他方面的优化 + +## 适用场景 +在 Redis 中,常用的 5 种数据结构和应用场景如下: + +- String:缓存、计数器、分布式锁等。 +- List:链表、队列、微博关注人时间轴列表等。 +- Hash:用户信息、Hash 表等。 +- Set:去重、赞、踩、共同好友等。 +- ZSet:访问量排行榜、点击量排行榜等。 + +## 数据结构分析 + +### SDS + +### 字典 + +### 跳跃表 + +### List + +### Set + +### ZSet diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/Duck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/Duck.java new file mode 100644 index 0000000..dc55e99 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/Duck.java @@ -0,0 +1,70 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * 鸭子超类(抽象类) + * + * @Author : TommyYang + * @Time : 2021年04月05日 15:57 + * @Software: IntelliJ IDEA + * @File : Duck.java + */ +public abstract class Duck { + + public Duck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + this.flyBehavior = flyBehavior; + this.quackBehavior = quackBehavior; + } + + /** + * 名称 + */ + private String name; + + /** + * 飞行行为 + */ + private FlyBehavior flyBehavior; + + /** + * 呱呱叫行为 + */ + private QuackBehavior quackBehavior; + + /** + * 鸭子的叫声 + */ + public void performQuack() { + this.quackBehavior.quack(); + } + + /** + * 鸭子游泳 + */ + public void swim() { + System.out.println("游泳"); + } + + + /** + * 执行飞行行为 + */ + public void performFly() { + this.flyBehavior.fly(); + } + + /** + * 鸭子外观都不相同,所以设计为抽象方法 + */ + public abstract void display(); + + public void setFlyBehavior(FlyBehavior flyBehavior) { + this.flyBehavior = flyBehavior; + } + + public void setQuackBehavior(QuackBehavior quackBehavior) { + this.quackBehavior = quackBehavior; + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MainExecutor.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MainExecutor.java new file mode 100644 index 0000000..ac5502c --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MainExecutor.java @@ -0,0 +1,43 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.Duck; +import cn.tommyyang.designpatterns.duckgame.RubberDuck; +import cn.tommyyang.designpatterns.duckgame.WoodDuck; +import cn.tommyyang.designpatterns.duckgame.behavior.impl.FlyWithNoWay; +import cn.tommyyang.designpatterns.duckgame.behavior.impl.MuteQuack; +import cn.tommyyang.designpatterns.duckgame.behavior.impl.Squeak; + +/** + * 鸭子游戏执行器 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:00 + * @Software: IntelliJ IDEA + * @File : MainExecutor.java + */ +public class MainExecutor { + + public static void main(String[] args) { + + // 实现不能飞行的橡皮鸭 + RubberDuck rubberDuck = new RubberDuck(new FlyWithNoWay(), new Squeak()); + execute(rubberDuck); + + // 实现不能飞也不能叫的橡皮鸭 + WoodDuck woodDuck = new WoodDuck(new FlyWithNoWay(), new MuteQuack()); + execute(woodDuck); + + // 后续各种鸭子实现,根据业务方的需求,设置其行为即可,行为可扩展,可复用 + // 这就是"策略模式",各种策略实现好,组合使用即可 + } + + + /** + * 鸭子游戏开始运行 + */ + private static void execute(Duck duck) { + duck.display(); + duck.performQuack(); + duck.performFly(); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MallardDuck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MallardDuck.java new file mode 100644 index 0000000..0bc89f2 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MallardDuck.java @@ -0,0 +1,29 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * 野鸭类 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:00 + * @Software: IntelliJ IDEA + * @File : MallardDuck.java + */ +public class MallardDuck extends Duck { + + /** + * 构造器 + */ + public MallardDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 具体实现鸭子外观 + */ + public void display() { + System.out.println("绿头"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RedheadDuck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RedheadDuck.java new file mode 100644 index 0000000..999f6b2 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RedheadDuck.java @@ -0,0 +1,28 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * 红头鸭 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:02 + * @Software: IntelliJ IDEA + * @File : RedheadDuck.java + */ +public class RedheadDuck extends Duck { + /** + * 构造器 + */ + public RedheadDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 具体实现鸭子外观 + */ + public void display() { + System.out.println("红头"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RubberDuck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RubberDuck.java new file mode 100644 index 0000000..08b9ed1 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RubberDuck.java @@ -0,0 +1,28 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * 橡皮鸭 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:17 + * @Software: IntelliJ IDEA + * @File : RubberDuck.java + */ +public class RubberDuck extends Duck { + /** + * 构造器 + */ + public RubberDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 具体实现鸭子外观 + */ + public void display() { + System.out.println("橡皮鸭"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/WoodDuck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/WoodDuck.java new file mode 100644 index 0000000..e620fef --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/WoodDuck.java @@ -0,0 +1,26 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * @Author : TommyYang + * @Time : 2021年04月10日 22:23 + * @Software: IntelliJ IDEA + * @File : WoodDuck.java + */ +public class WoodDuck extends Duck { + /** + * 构造器 + */ + public WoodDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 木头鸭外观 + */ + public void display() { + System.out.println("木头鸭"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/FlyBehavior.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/FlyBehavior.java new file mode 100644 index 0000000..28f878d --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/FlyBehavior.java @@ -0,0 +1,18 @@ +package cn.tommyyang.designpatterns.duckgame.behavior; + +/** + * 鸭子飞行行为接口 + * + * @Author : TommyYang + * @Time : 2021年04月06日 00:10 + * @Software: IntelliJ IDEA + * @File : FlyBehavior.java + */ +public interface FlyBehavior { + + /** + * 定义飞行方法 + */ + void fly(); + +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/QuackBehavior.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/QuackBehavior.java new file mode 100644 index 0000000..c1880c7 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/QuackBehavior.java @@ -0,0 +1,18 @@ +package cn.tommyyang.designpatterns.duckgame.behavior; + +/** + * 鸭子呱呱叫行为接口 + * + * @Author : TommyYang + * @Time : 2021年04月06日 00:10 + * @Software: IntelliJ IDEA + * @File : QuackBehavior.java + */ +public interface QuackBehavior { + + /** + * 定义呱呱叫方法 + */ + void quack(); + +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithNoWay.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithNoWay.java new file mode 100644 index 0000000..35280ca --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithNoWay.java @@ -0,0 +1,21 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; + +/** + * 不能飞行行为定义 + * + * @Author : TommyYang + * @Time : 2021年04月10日 22:16 + * @Software: IntelliJ IDEA + * @File : FlyWithNoWay.java + */ +public class FlyWithNoWay implements FlyBehavior { + /** + * 不能飞行行为实现 + */ + @Override + public void fly() { + System.out.println("no way to fly"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWay.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWay.java new file mode 100644 index 0000000..fcf0bd8 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWay.java @@ -0,0 +1,21 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; + +/** + * 实现飞行方法 + * + * @Author : TommyYang + * @Time : 2021年04月07日 23:23 + * @Software: IntelliJ IDEA + * @File : FlyWithWay.java + */ +public class FlyWithWay implements FlyBehavior { + /** + * 实现飞行行为 + */ + @Override + public void fly() { + System.out.println("fly with way"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWings.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWings.java new file mode 100644 index 0000000..2918389 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWings.java @@ -0,0 +1,21 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; + +/** + * 通过翅膀飞行实现类 + * + * @Author : TommyYang + * @Time : 2021年04月07日 23:21 + * @Software: IntelliJ IDEA + * @File : FlyWithWings.java + */ +public class FlyWithWings implements FlyBehavior { + /** + * 实现飞行行为 + */ + @Override + public void fly() { + System.out.println("fly with wings"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/MuteQuack.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/MuteQuack.java new file mode 100644 index 0000000..0e6d65c --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/MuteQuack.java @@ -0,0 +1,21 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * @Author : TommyYang + * @Time : 2021年04月09日 22:31 + * @Software: IntelliJ IDEA + * @File : MuteQuack.java + */ +public class MuteQuack implements QuackBehavior { + /** + * 实现 quack 方式 + * 不能叫 + */ + @Override + public void quack() { + // nothing + System.out.println("no quack"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Quack.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Quack.java new file mode 100644 index 0000000..1ce977d --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Quack.java @@ -0,0 +1,19 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * @Author : TommyYang + * @Time : 2021年04月09日 22:29 + * @Software: IntelliJ IDEA + * @File : Quack.java + */ +public class Quack implements QuackBehavior { + /** + * 实现 quack 方式 + */ + @Override + public void quack() { + System.out.println("呱呱叫"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Squeak.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Squeak.java new file mode 100644 index 0000000..010ef06 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Squeak.java @@ -0,0 +1,19 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * @Author : TommyYang + * @Time : 2021年04月09日 22:30 + * @Software: IntelliJ IDEA + * @File : Squeak.java + */ +public class Squeak implements QuackBehavior { + /** + * 实现 quack 方式 + */ + @Override + public void quack() { + System.out.println("吱吱叫"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/observable/WeatherData.java b/architecture/src/main/java/cn/tommyyang/designpatterns/observable/WeatherData.java new file mode 100644 index 0000000..35ae682 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/observable/WeatherData.java @@ -0,0 +1,56 @@ +package cn.tommyyang.designpatterns.observable; + +import cn.tommyyang.designpatterns.observable.display.CurrentConditionsDisplay; + +/** + * 观察者模式 + * WeatherData 类 + * + * @Author : TommyYang + * @Time : 2021年04月18日 15:51 + * @Software: IntelliJ IDEA + * @File : WeatherData.java + */ +public class WeatherData { + /** + * 温度 + */ + private float temperature; + + /** + * 湿度 + */ + private float humidity; + + /** + * 气压 + */ + private float pressure; + + public float getTemperature() { + return this.temperature; + } + + public float getHumidity() { + return this.humidity; + } + + public float getPressure() { + return pressure; + } + + /** + * 一旦气象测量更新,此方法会被调用 + */ + public void measurementsChanged() { + float temp = getTemperature(); + float humidity = getHumidity(); + float pressure = getPressure(); + + // 布告板数据更新 + // 针对具体的实现编程,导致后续增删布告板时必须修改程序 + CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); + currentConditionsDisplay.update(temp, humidity, pressure); + } + +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/observable/display/CurrentConditionsDisplay.java b/architecture/src/main/java/cn/tommyyang/designpatterns/observable/display/CurrentConditionsDisplay.java new file mode 100644 index 0000000..f10a615 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/observable/display/CurrentConditionsDisplay.java @@ -0,0 +1,15 @@ +package cn.tommyyang.designpatterns.observable.display; + +/** + * @Author : TommyYang + * @Time : 2021年04月18日 16:00 + * @Software: IntelliJ IDEA + * @File : CurrentConditionsDisplay.java + */ +public class CurrentConditionsDisplay { + + public void update(float temp, float humidity, float pressure) { + + } + +} diff --git a/architecture/uml.md b/architecture/uml.md new file mode 100644 index 0000000..60fc393 --- /dev/null +++ b/architecture/uml.md @@ -0,0 +1,57 @@ +# UML 图总结 +## UML 类图 +> 描述类、接口、协作及他们之间的关系的图。显示系统中类的静态结构。 + +### 有什么作用? +描述软件系统的静态结构 +- 对系统的词汇建模 +- 对简单协作建模 +- 对逻辑数据库模式建模 + +### 类图说明 +> 类名为斜体则为抽象类
类方法为斜体则为抽象方法

第一行:类名称
第二行:类属性
第三行:类方法

类/属性/方法说明:
'-' 表示私有(private)
'#'表示保护(protected)
'+'表示公开(public) + + + +### 依赖关系 +> `虚线箭头`表示。 + + + + +### 继承关系 +> `实线空心三角形箭头`表示。 + + + +### 实现关系 +> `虚线空心三角形箭头`表示。 + + + +### 关联关系 +> `实线`表示。 + + + +### 组合关系 +> `实线黑色菱形箭头`表示。 + + + +### 聚合关系 +> `实线空心菱形箭头`表示。 + + + +## UML 对象图 +> 显示了某一时刻的一组对象及它们之间的关系。对象图可被看作是类图的实例,用来表达各个对象在某一时刻的状态。 + + + +### 对象图说明 +> stu 实例名称,Student 所属类。
第一行:对象名称
第二行:实例属性具体值 + +- stu:Student 标准表示法 +- :Student 匿名表示法 +- stu 省略类名表示法 \ No newline at end of file diff --git a/bigdata/README.md b/bigdata/README.md new file mode 100644 index 0000000..08b18fe --- /dev/null +++ b/bigdata/README.md @@ -0,0 +1,12 @@ +# 大数据篇 + +## 集合 +[海量数据去重](maxlist_set.md) + +## Hadoop 平台 +[hadoop 结构](hadoop.md) +[hive 结构](hive.md) +[hive-udf 讲解](hive_udf.md) + +## 消息队列 +[Kafka 内部结构详解](kafka_1.md) \ No newline at end of file diff --git a/bigdata/hadoop.md b/bigdata/hadoop.md new file mode 100644 index 0000000..607ec19 --- /dev/null +++ b/bigdata/hadoop.md @@ -0,0 +1,37 @@ +# Hadoop 架构 + +## Hadoop 组成 +- HDFS +- MapReduce +- Yarn (2.0 版本引入) + +## HDFS + +## MapReduce +MapReduce是一种用于大规模数据处理的编程模型和计算框架。它被广泛应用于分布式系统中,旨在提供高效、可扩展和可靠的数据处理解决方案。 + +MapReduce的核心思想是将大规模数据集分成小的数据块,然后并行处理这些数据块。这个过程包括两个主要阶段:Map阶段和Reduce阶段。 + +在Map阶段,数据集被划分成多个小的输入块,每个输入块由一个Map任务处理。Map任务将输入块转换成键值对的集合。键值对的生成是通过应用用户自定义的Map函数来实现的。这个函数将输入数据转换成一个或多个键值对,其中键表示数据的特定属性,值则包含与该键相关联的信息。 + +在Reduce阶段,Map任务生成的键值对集合被分组和排序,然后传递给Reduce任务进行处理。Reduce任务的数量通常与计算集群中的处理节点数量相匹配。Reduce任务将相同键的键值对进行合并、计算和聚合操作,生成最终的输出结果。Reduce函数也是用户自定义的,根据需要执行各种操作,例如求和、计数、平均值等。 + +MapReduce的优点在于它的并行性和可伸缩性。通过将数据划分成小块并将其分发给多个处理节点进行并行处理,MapReduce可以有效地处理大规模数据集,提高处理速度和性能。此外,它提供了自动处理故障和容错的机制,保证了计算的可靠性。 + +MapReduce在分布式系统中得到了广泛应用。它是Apache Hadoop框架的核心组件之一,被用于处理大数据和进行复杂的数据分析任务。MapReduce还可以在其他领域中发挥作用,如搜索引擎、机器学习和图形处理等。 + +总而言之,MapReduce是一种强大的编程模型和计算框架,通过将大规模数据集划分成小块并在分布式环境中并行处理,提供了高效、可扩展和可靠的数据处理解决方案。它在大数据领域的应用前景广阔,并在多个领域中发挥着重要的作用。 + +## Yarn + + +## 例子 +我们在 Hive 执行一个 select 操作需要经历的步骤。 +1. 通过 HiveServer2 得到 hdfs 文件目录; +2. 向 yarn 集群提交我们的 Map Reduce 程序; +3. yarn 集群中的 Resourcemanger 和 nodemanager 进行通信,根据集群情况,分配 Container 给集群的某个 Nodemanager,Nodemanager 启动 Container; +4. Resource Manager 将 MapReduce Applicationmaster 分发到刚才 Nodemanager 启动的 container,然后自身在 container 中启动。 +5. MapRedeuce ApplicationMaster 启动之后,立马向 Resource manager 注册,并为 MapReduce 申请资源。 +6. MapReduce ApplicationMaster 申请到容器之后立马和 NodeManager 通信,将用户的 MapReduce 程序分发到对应的 Container 中运行,运行的是 Map 进行和 Reduce 进程。 +7. Map 和 Reduce 进程执行期间与 MapReduce ApplicationMaster 进行通信,汇报自身的运行状态,如果运行结束,MapReduce ApplicationMaster ApplicationMaster 会向 ResourceManager 注销并释放所有的容器资源。 +8. 最后返回 Reduce 的所有结果。 diff --git a/bigdata/hive.md b/bigdata/hive.md new file mode 100644 index 0000000..c4458fa --- /dev/null +++ b/bigdata/hive.md @@ -0,0 +1,11 @@ +# Hive + +## 问题 +1. Hive 数据倾斜的原因。 +- key 分布不均匀。 +- 业务数据本身的特性。 +- 建表考虑不周全。 +- 某些 HQL 语句本身就存在数据倾斜。 + + + \ No newline at end of file diff --git a/bigdata/hive_udf.md b/bigdata/hive_udf.md new file mode 100644 index 0000000..9315c4a --- /dev/null +++ b/bigdata/hive_udf.md @@ -0,0 +1,87 @@ +# Hive 函数 +相信大家对 Hive 都不陌生,那么大家肯定用过 Hive 里面各种各样的函数。可能大家都会使用这些函数,但是没有自己动手去写过 Hive 里面的函数。下面主要来介绍一下 Hive 里面的各种函数。 + + +## 依赖 +开发 Hive UDF 之前,我们需要引入一个 jar,这个 jar 就是 hive-exec,里面定义了各种我们自定义的 UDF 函数的类型:UDF、GenericUDF、GenericUDTF 等。 + +```xml + + + org.apache.hive + hive-exec + 2.1.1 + + +``` + + +## UDF(User-Defined-Function) +UDF:用户自定义函数,也是实现起来最简单的一种函数。支持的就是**一进一出**类型的函数。 + +如何实现? + +- 继承 UDF 类。 + +- 重写 evaluate 方法。 + +- 将该 java 文件打包成 jar。 + +- 在 beeline(hive 的一种终端)中输入如下命令: + + - 0: jdbc:hive2://localhost:10000> add jar /data/tommyyang/HiveUDF.jar; + + - 0: jdbc:hive2://localhost:10000> create temporary function ip2loc as 'cn.tommyyang.IPToLocation'; + + - 0: jdbc:hive2://localhost:10000> select ip2loc("118.28.1.1"); + + - 0: jdbc:hive2://localhost:10000> drop temporary function ip2loc; + +具体代码实现: + +```java + +@Description( + name = "ip2loc", + value = "_FUNC_(str) - covert ip to location", + extended = "Example:\n" + + "> SELECT ip2loc(ip) FROM ips;\n" + + " [中国,上海]" +) +public class IPToLocation extends UDF { + + private static final InputStream stream = IPToLocation.class.getClassLoader().getResourceAsStream("ipipfree.ipdb"); + private static City db = null; + + static { + try { + db = new City(stream); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 具体实现逻辑 + public ArrayList evaluate(Text s) throws IOException, IPFormatException { + ArrayList allTexts = new ArrayList(); + if (s != null) { + CityInfo info = db.findInfo(s.toString(), "CN"); + allTexts.add(info.getCountryName()); + allTexts.add(info.getRegionName()); + allTexts.add(info.getCityName()); + } + return allTexts; + } + +} + +``` + +@Description 为该 UDF 的描述,你可以通过命令 **desc function ip2loc**查看具体的描述;如下图: + + +## GenericUDF +相对 UDF,GenericUDF 就复杂一些,下面我们通过创建一个 containsString 方法的例子, + + +## GenericUDTF diff --git a/bigdata/kafka_1.md b/bigdata/kafka_1.md new file mode 100644 index 0000000..f67511b --- /dev/null +++ b/bigdata/kafka_1.md @@ -0,0 +1,103 @@ +## kafka的几个重要概念 + +- `Broker`:消息中间件处理节点,一个 Kafka 节点就是一个 broker,多个 broker 可以组成一个 Kafka 集群; +- `Topic`:一类消息,例如 note impression 日志、 click 日志等都可以以 topic 的形式存在,Kafka 集群能够同时负责多个 topic 的分发; +- `Partition`:topic 物理上的分组,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队; +- `segment`:每个 partition 又由多个segment file组成; +- `offset`:每个 partition 都由一系列有序的、不可变的消息组成,这些消息被连续的追加到 partition 中。partition 中的每个消息都有一个连续的序列号叫做 offset,用于 partition 唯一标识一条消息; +- `message`:这个算是 kafka 文件中最小的存储单位,即是 one commit log。 + +kafka 的 message 是以 topic 为基本单位,不同 topic 之间是相互独立的。 +每个 topic 又可分为几个不同的 partition,每个 partition 存储一部分 message。 +同一个 topic 的不同 partition 可以分布在不同的 broker 上,这正是分布式的设计。 +partition 是以文件夹的形式存储在 broker 机器上。 +topic 与 partition 的关系如下: + + +## Partition中的文件 +### segment file +对于一个 partition,里面又有很多大小相等的 segment 数据文件(这个文件的具体大小可以在config/server.properties中进行设置),这种特性可以方便 old segment file的快速删除。 + +下面介绍一下 partition 中的 segment file的组成: +1. segment file 组成:由2部分组成,分别为 index file和 data file,这两个文件是一一对应的,后缀".index"和".log"分别表示索引文件和数据文件;数值最大为64位 long 大小,19位数字字符长度,没有数字用0填充; +2. 自0.10.0.1开始的kafka segment file组成:多了一部分,还有 .timeindex 索引文件,基于时间的索引文件;目前支持的时间戳类型有两种: CreateTime 和 LogAppendTime 前者表示 producer 创建这条消息的时间;后者表示 broker 接收到这条消息的时间(严格来说,是 leader broker 将这条消息写入到 log 的时间) +3. segment file 命名规则:partition 的第一个 segment 从0开始,后续每个 segment 文件名为上一个 segment 文件最后一条消息的 offset, ofsset 的数值最大为64位(long 类型),20位数字字符长度,没有数字用0填充。如下图所示: +老版本 segment file 结构: + +新版本 segment file 结构: + + +4. 关于 segment file 中 index 索引文件与 data 文件对应关系图,这里我们借用网上的一个图片,如下所示: + + +5. segment的索引文件中存储着大量的元数据,数据文件中存储着大量消息,索引文件中的元数据指向对应数据文件中的 message 的物理偏移地址。以索引文件中的6,1407为例,在数据文件中表示第6个 message(在全局 partition 表示第368775个 message),以及该消息的物理偏移地址为1407。 + +6. Kafka message 结构如下图: + + +`注`:.index文件的第一个数是 message 相对 offset,后面的数字是该消息的物理偏移地址。 + + +### 为什么分成多个segment file +1. 问题: 如果 topic 的 partition 中只有一个文件会怎么样? +- 新数据加在文件的末尾(调用内部方法),不论文件多大,该操作的时间复杂度都是O(1);这个没问题。 +- 查找某个 offset 的时候,是顺序查找。想想,如果文件很大的话,查找的效率就会很低;这个是要解决的。 + +2. Kakfa 是如何解决上述问题的呢 +通过上述分析发现,如果 Kafka 只有一个文件的话,插入新数据的效率是没问题的;只是在查找的时候,效率很低。 + +`解决办法` + +`方法一` 数据文件分段 + +将大文件分成多个小的文件,便于维护。 由 offset 的起始位置来命名,则通过二分查找可以确定一个待查找的 offset +属于在那个小文件里面。 + +`方法二` 为数据文件建立索引文件 + +即为上图中可以看到的 .index 文件, 数据分段虽然解决了一个大文件的问题,但是找到具体的小文件后,还是要通过 +顺序扫描的方式才能找到对应 offset 的 message。 为了提高顺序扫描的效率,kafka 为每个分段后的文件建立了一个 +与数据文件名一样的,扩展名为 .index 的文件。 + +**索引文件的内部结构** + +索引文件包含两个部分(均为4个字节),分别为 offset 和 position。 + +- 相对 offset: 由于数据文件分段以后,除了第一个数据以外,每个数据文件的起始 offset 不为0,相对 offset 表示 +该 message 相对于其所属数据文件中最小的 offset 的大小,即在每个文件中都是从1开始。 然后查找具体 offset 的时候, +只需要通过二分查找找到具体的在哪个文件中,然后再用 offset - 文件名对应的数值, 即可确定在文件中的相对 offset。 +存储相对 offset 的好处是*可以减小索引文件占用的空间*。 + +- position: 表示该条 message 在数据文件中的绝对位置, 只要打开数据文件并移动文件指针到这个 position 就可以 +读取到对应的 message 数据了。 + +3. 通过 offset 查找 message +分为两个步骤: + +- 通过`二分查找`文件列表,快速定位到具体的.index和.log文件; 即找到小于或等于给定 offset 的最大的 offset 文件。 + +- 通过 segment file 查找具体的 message,找到具体的文件后,先定位到 .index 文件中的 元数据物理位置 position, +即 .log 文件中的物理偏移地址。 然后在通过顺序查找到具体的 offset。 + +上述最后还需要顺序查找具体的 offset, 而不是直接通过 position 定位到具体的 message,是因为 segment index file +没有为数据文件中的每条 message 建立索引,而是采用`稀疏索引`的存储方式,每隔一定字节的数据建立一条索引,这样可以减小 +索引文件,通过 map 可以直接进行内存操作, 稀疏索引为数据文件的每个对应 message 设置一个元数据指针, 这样比稠密索引 +节省了存储空间,但是查找时需要消耗更多的时间,其实最后通过 .index 文件中 position 无法直接定位到 没有建立索引 +的 message, 而是需要通过顺序查找,再继续在 .log 文件中继续往下查找。 + +### 总结 + +Kafka 高效文件存储设计特点: + +- 将 topic 中一个 partition 大文件分割成多个小文件段,这样更容易定期清除和删除已经消费完的文件,减少磁盘占用。 +- 通过索引可以快速定位到 message 和 确定 response 的最大大小。 +- 通过 index 元数据全部映射到 memory,可以避免 segment file的 IO 磁盘操作。 +- 通过索引文件稀疏存储,可以大幅降低 index 文件元数据占用空间大小。 + + +### 参考 + +- [kafka-fs-design-theory](https://tech.meituan.com/2015/01/13/kafka-fs-design-theory.html) +- [Kafka 存储](http://matt33.com/2016/03/08/kafka-store/) + + diff --git a/bigdata/maxlist_set.md b/bigdata/maxlist_set.md new file mode 100644 index 0000000..f0952a5 --- /dev/null +++ b/bigdata/maxlist_set.md @@ -0,0 +1,207 @@ +## MaxList以及Set模块 + +> MaxList模块主要是对Java集合大数据去重的相关介绍。 + +背景: 最近在项目中遇到了List集合中的数据要去重,大概一个2500万的数据,开始存储在List中,需要跟一个2万的List去去重。 + +### 直接两个List去重 + +说到去重,稍微多讲一点啊,去重的时候有点小伙伴可能直接对2500万List foreach循环后直接删除,其实这种是错误的(java.util.ConcurrentModificationException),大家可以自己去试一下;(注: for循环遍历删除不报错,但是效率低,不推荐使用)首先你需要去看下foreach和迭代器的实现。foreach的实现就是用到了迭代器,所以你在foreach的时候对list进行删除操作,迭代器Iterator无法感知到list删除了,所以会报错。直接贴代码解释下。 + +ArrayList中Iterator的实现: +``` +private class Itr implements Iterator { + int cursor; // index of next element to return + int lastRet = -1; // index of last element returned; -1 if no such + int expectedModCount = modCount; + + public boolean hasNext() { + return cursor != size; + } + + @SuppressWarnings("unchecked") + public E next() { + checkForComodification(); + int i = cursor; + if (i>= size) + throw new NoSuchElementException(); + Object[] elementData = ArrayList.this.elementData; + if (i>= elementData.length) + throw new ConcurrentModificationException(); + cursor = i + 1; + return (E) elementData[lastRet = i]; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + ArrayList.this.remove(lastRet); + cursor = lastRet; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + @Override + @SuppressWarnings("unchecked") + public void forEachRemaining(Consumer consumer) { + Objects.requireNonNull(consumer); + final int size = ArrayList.this.size; + int i = cursor; + if (i>= size) { + return; + } + final Object[] elementData = ArrayList.this.elementData; + if (i>= elementData.length) { + throw new ConcurrentModificationException(); + } + while (i != size && modCount == expectedModCount) { + consumer.accept((E) elementData[i++]); + } + // update once at end of iteration to reduce heap write traffic + cursor = i; + lastRet = i - 1; + checkForComodification(); + } + + final void checkForComodification() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + } +} + +``` +通过上述的ArrayList里面的Iterator迭代器的实现我们可以看到:基本上ArrayList采用size属性来维护自已的状态,而Iterator采用cursor来来维护自已的状态。当你直接在foreach里面对list进行删除操作,size出现变化时,cursor并不一定能够得到同步,除非这种变化是Iterator主动导致的。(调用list.iterator()方法的原因) + +从上面的代码可以看到当Iterator.remove方法导致ArrayList列表发生变化时,他会更新cursor来同步这一变化。但其他方式导致的ArrayList变化,Iterator是无法感知的。ArrayList自然也不会主动通知Iterator们,那将是一个繁重的工作。Iterator到底还是做了努力:为了防止状态不一致可能引发的无法设想的后果,Iterator会经常做checkForComodification检查,以防有变。如果有变,则以异常抛出,所以就出现了上面的异常。如果对正在被迭代的集合进行结构上的改变(即对该集合使用add、remove或clear方法),那么迭代器就不再合法(并且在其后使用该迭代器将会有ConcurrentModificationException异常被抛出)。 + +如果使用迭代器自己的remove方法,那么这个迭代器就仍然是合法的。 + + +``` +public static void deWeightList(List des, List sourse){ + if(sourse == null || sourse.size() <= 0){ + return; + }l + Iterator listStr = sourse.iterator(); + while (listStr.hasNext()){ + String item = listStr.next(); + for (String ditem: des) { + if(item.equals(ditem)){ + listStr.remove(); + break; + } + } + + } + logger.info("after deWight list size: " + sourse.size()); +} +``` + + +### List结合Set去重 +``` +public static void deWeightList(Set des, List sourse) { + if (sourse == null || sourse.size() <= 0) { + return; + } + Iterator listStr = sourse.iterator(); + while (listStr.hasNext()) { + String item = listStr.next(); + if (des.contains(item)) { + listStr.remove(); + } + } + logger.info("after deWight list size: " + sourse.size()); +} +``` +### List结合Set去重(不是直接对list进行删除,而是组装新list,考虑到list删除效率低) +``` +public static void deWeightListByNewList(Set des, List sourse) { + if (sourse == null || sourse.size() <= 0) { + return; + } + Iterator listStr = sourse.iterator(); + List existList = new ArrayList(); + while (listStr.hasNext()) { + String item = listStr.next(); + if(!des.contains(item)){ + //TODO 对去重后的数据进行逻辑操作,不一定要删除,可以换个思路(是否可以直接逻辑操作,不一定非要再把数据写进集合后,然后遍历集合在进行逻辑操作) + existList.add(item); //改成添加进新的list,考虑到list的删除效率慢(非要得到删除后的集合的情况下,否则走else) + } +// if (des.contains(item)) { +// //listStr.remove(); //考虑到list的删除效率慢,此种方法对于大数据集合来说不合适 +// } + } + sourse.clear(); + sourse = existList; + logger.info("after deWight list size: " + sourse.size()); +} + +``` + +### 遍历过程中去重 + +个人最为推荐的一种,因为效率最高,也能达到功能的需要。 + +``` +for (String item: maxArrayList) { + if(testSet.contains(item)){ + //TODO + } +} + +``` + + +### 测试结果如下 +``` +下面是1000万的list和20000的list去重两种方式所花的时间,可以看出使用set去重的效率要高很多。 +1.list结合list去重时间: +14:52:02,408 INFO [RunTest:37] start test list:17-11-07 14:52:02 +14:59:49,828 INFO [ListUtils:66] after deWight list size: 9980000 +14:59:49,829 INFO [RunTest:39] end test list:17-11-07 14:59:49 + +2.list结合set去重时间: +14:59:53,226 INFO [RunTest:44] start test set:17-11-07 14:59:53 +15:01:30,079 INFO [ListUtils:80] after deWight list size: 9980000 +15:01:30,079 INFO [RunTest:46] end test set:17-11-07 15:01:30 + +下面是2500万的list和20000的list去重两种方式所花的时间,可以看出使用set去重的效率要更加的高,(数据量越大越明显)。 +个人对set的大小为1500万也进行了测试,方案3,4的效率也是非常的高。 + +1.list结合list去重时间: +15:17:47,114 INFO [RunTest:35] start test list, start time: 17-11-07 15:17:47 +15:49:04,876 INFO [ListUtils:57] after deWight list size: 24980000 +15:49:04,877 INFO [RunTest:39] end test list, end time: 17-11-07 15:49:04 + +2.list结合set去重时间: +15:49:17,842 INFO [RunTest:44] start test set, start time: 17-11-07 15:49:17 +15:53:22,716 INFO [ListUtils:71] after deWight list size: 24980000 +15:53:22,718 INFO [RunTest:48] end test set, end time: 17-11-07 15:53:22 + +3. List结合Set去重(不是直接对list进行删除,而是组装新list,考虑到list删除效率低) +17:18:44,583 INFO [RunTest:57] start test set, start time: 17-11-22 17:18:44 +17:18:54,628 INFO [ListUtils:92] after deWight list size: 23500000 +17:18:54,628 INFO [RunTest:61] end test set, end time: 17-11-22 17:18:49 + +4.遍历过程中结合set去重:(个人最为推荐的原因之一,效率高到令人爽到不行) +15:17:45,762 INFO [RunTest:24] start test foreach list directly, start time: 17-11-07 15:17:45 +15:17:47,114 INFO [RunTest:32] end test foreach list directly, end time: 17-11-07 15:17:47 + +``` + +### 总结 +通过上述测试我们可以看出,有时候我们排重的时候,不一定要拍完重再对排重后的数据进行遍历,可以在遍历的过程中进行排重,注意用来排重的那个集合放到Set中,可以是HashSet,或者其他Set(推荐使用HashSet),因为Set的contains效率更高,比list高很多。 + +然后考虑到如果非要拿到去重后的list,考虑使用方案3《List结合Set去重(不是直接对list进行删除,而是组装新list,考虑到list删除效率低)》,通过测试,这种方法效率也是非常的高。与方案4相比,稍微慢一点点。 + +对于上述方案1,测试也使用过组装新list的方式,而不是list.remove。但是效率还是比较慢。 + +这是实际工作中总结出来的经验。希望对大家有帮助! +欢迎大家来交流! diff --git a/bigdata/pom.xml b/bigdata/pom.xml new file mode 100644 index 0000000..862fc11 --- /dev/null +++ b/bigdata/pom.xml @@ -0,0 +1,72 @@ + + + + javainterview + cn.tommyyang + 1.0-SNAPSHOT + + 4.0.0 + + bigdata + + + + + org.apache.hive + hive-exec + 3.1.1 + + + org.apache.zookeeper + zookeeper + + + org.apache.hadoop + hadoop-hdfs + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient + + + org.apache.logging.log4j + log4j-1.2-api + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.slf4j + slf4j-api + + + + + + net.ipip + ipdb + 1.1.2 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/bigdata/src/main/java/cn/tommyyang/bigfile/BigFile.java b/bigdata/src/main/java/cn/tommyyang/bigfile/BigFile.java new file mode 100644 index 0000000..abc8c96 --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/bigfile/BigFile.java @@ -0,0 +1,23 @@ +package cn.tommyyang.bigfile; + +import cn.tommyyang.bigfile.fileservice.FileService; + +import java.io.IOException; + +/** + * @author TommyYang on 2019年04月09日 + * + * 使用说明 + * run cmd: java -cp bigdata-1.0-SNAPSHOT.jar cn.tommyyang.bigfile.BigFile + */ +public class BigFile { + + public static void main(String[] args) throws IOException { + //BigFileTool.readContent(args[0], args[1]); + FileService fileService = new FileService("./smallfiles/sink-0.txt"); + fileService.countWords(); + System.out.println(fileService.getTreeMap()); + System.out.println(fileService.getTreeMap().firstEntry().getKey() + ":" + fileService.getTreeMap().firstEntry().getValue()); + } + +} diff --git a/bigdata/src/main/java/cn/tommyyang/bigfile/fileservice/FileService.java b/bigdata/src/main/java/cn/tommyyang/bigfile/fileservice/FileService.java new file mode 100644 index 0000000..c8bba94 --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/bigfile/fileservice/FileService.java @@ -0,0 +1,51 @@ +package cn.tommyyang.bigfile.fileservice; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.TreeMap; + +/** + * @author TommyYang on 2019年04月12日 + */ +public class FileService { + + private String fileName; + + private TreeMap treeMap; + + public FileService(String fileName) { + this.fileName = fileName; + this.treeMap = new TreeMap(); + } + + public TreeMap getTreeMap() { + return treeMap; + } + + public void countWords() throws IOException { + File file = new File(this.fileName); + FileInputStream inputStream = new FileInputStream(file); + FileChannel fc = inputStream.getChannel(); + + ByteBuffer bf = ByteBuffer.allocate(10 * 1024 * 1024); + + int len = -1; + while ((len = fc.read(bf)) != -1){ + bf.clear(); + byte[] bytes = bf.array(); + String values = new String(bytes, 0 , len); + String[] valArr = values.split(System.lineSeparator()); + for (String val : valArr){ + Long cnt = this.treeMap.get(val); + if (cnt == null){ + this.treeMap.put(val, 0l); + }else { + this.treeMap.put(val, ++cnt); + } + } + } + } +} diff --git a/bigdata/src/main/java/cn/tommyyang/bigfile/utils/BigFileTool.java b/bigdata/src/main/java/cn/tommyyang/bigfile/utils/BigFileTool.java new file mode 100644 index 0000000..96e614d --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/bigfile/utils/BigFileTool.java @@ -0,0 +1,44 @@ +package cn.tommyyang.bigfile.utils; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; + +/** + * @author TommyYang on 2019年04月09日 + */ +public class BigFileTool { + + public static void readContent(String filePath, String sinkDir) throws IOException { + File file = new File(filePath); + FileInputStream inputStream = new FileInputStream(file); + FileChannel channel = inputStream.getChannel(); + + ByteBuffer buffer = ByteBuffer.allocate(8092); + int length = -1; + while ((length = channel.read(buffer)) != -1){ + buffer.clear(); + + byte[] bytes = buffer.array(); + String values = new String(bytes, 0 , length); + + String[] valArr = values.split(" "); + for (String val : valArr){ + int code = Math.abs(val.hashCode()) & 15; + String txtName = "sink-" + code + ".txt"; + String sinkPath = sinkDir + txtName; + writeContent(sinkPath, Charset.forName("utf-8").encode(val + System.lineSeparator())); + } + } + } + + public static void writeContent(String sinkPath, ByteBuffer bf) throws IOException { + FileOutputStream outputStream = new FileOutputStream(new File(sinkPath), true); + FileChannel channel = outputStream.getChannel(); + channel.write(bf); + channel.close(); + outputStream.close(); + } + +} diff --git a/bigdata/src/main/java/cn/tommyyang/bigfile/utils/IHandle.java b/bigdata/src/main/java/cn/tommyyang/bigfile/utils/IHandle.java new file mode 100644 index 0000000..9b6e9ea --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/bigfile/utils/IHandle.java @@ -0,0 +1,10 @@ +package cn.tommyyang.bigfile.utils; + +/** + * @author TommyYang on 2019年04月12日 + */ +public interface IHandle { + + void handle(String line); + +} diff --git a/bigdata/src/main/java/cn/tommyyang/bigfile/utils/MultiThreadReader.java b/bigdata/src/main/java/cn/tommyyang/bigfile/utils/MultiThreadReader.java new file mode 100644 index 0000000..2ed070a --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/bigfile/utils/MultiThreadReader.java @@ -0,0 +1,241 @@ +package cn.tommyyang.bigfile.utils; +/** + * @author TommyYang on 2019年04月12日 + */ +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel.MapMode; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; + +public class MultiThreadReader { + private int threadSize; + private String charset; + private int bufferSize; + private IHandle handle; + private ExecutorService executorService; + private long fileLength; + private RandomAccessFile rAccessFile; + private Set startEndPairs; + private CyclicBarrier cyclicBarrier; + private AtomicLong counter = new AtomicLong(0); + + private MultiThreadReader(File file,IHandle handle,String charset,int bufferSize,int threadSize){ + this.fileLength = file.length(); + this.handle = handle; + this.charset = charset; + this.bufferSize = bufferSize; + this.threadSize = threadSize; + try { + this.rAccessFile = new RandomAccessFile(file,"r"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + this.executorService = Executors.newFixedThreadPool(threadSize); + startEndPairs = new HashSet(); + } + + public void start(){ + long everySize = this.fileLength/this.threadSize; + try { + calculateStartEnd(0, everySize); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + final long startTime = System.currentTimeMillis(); + cyclicBarrier = new CyclicBarrier(startEndPairs.size(),new Runnable() { + + @Override + public void run() { + System.out.println("use time: "+(System.currentTimeMillis()-startTime)); + System.out.println("all line: "+counter.get()); + } + }); + for(StartEndPair pair:startEndPairs){ + System.out.println("分配分片:"+pair); + this.executorService.execute(new SliceReaderTask(pair)); + } + } + + private void calculateStartEnd(long start,long size) throws IOException{ + if(start>fileLength-1){ + return; + } + StartEndPair pair = new StartEndPair(); + pair.start=start; + long endPosition = start+size-1; + if(endPosition>=fileLength-1){ + pair.end=fileLength-1; + startEndPairs.add(pair); + return; + } + + rAccessFile.seek(endPosition); + byte tmp =(byte) rAccessFile.read(); + while(tmp!='\n' && tmp!='\r'){ + endPosition++; + if(endPosition>=fileLength-1){ + endPosition=fileLength-1; + break; + } + rAccessFile.seek(endPosition); + tmp =(byte) rAccessFile.read(); + } + pair.end=endPosition; + startEndPairs.add(pair); + + calculateStartEnd(endPosition+1, size); + + } + + + + public void shutdown(){ + try { + this.rAccessFile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + this.executorService.shutdown(); + } + private void handle(byte[] bytes) throws UnsupportedEncodingException{ + String line = null; + if(this.charset==null){ + line = new String(bytes); + }else{ + line = new String(bytes,charset); + } + if(line!=null && !"".equals(line)){ + this.handle.handle(line); + counter.incrementAndGet(); + } + } + private static class StartEndPair{ + public long start; + public long end; + + @Override + public String toString() { + return "star="+start+";end="+end; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (end ^ (end>>> 32)); + result = prime * result + (int) (start ^ (start>>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + StartEndPair other = (StartEndPair) obj; + if (end != other.end) + return false; + if (start != other.start) + return false; + return true; + } + + } + private class SliceReaderTask implements Runnable{ + private long start; + private long sliceSize; + private byte[] readBuff; + /** + * @param pair StartEndPair + */ + public SliceReaderTask(StartEndPair pair) { + this.start = pair.start; + this.sliceSize = pair.end-pair.start+1; + this.readBuff = new byte[bufferSize]; + } + + @Override + public void run() { + try { + MappedByteBuffer mapBuffer = rAccessFile.getChannel().map(MapMode.READ_ONLY,start, this.sliceSize); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + for(int offset=0;offset0){ + handle(bos.toByteArray()); + } + cyclicBarrier.await();//测试性能用 + }catch (Exception e) { + e.printStackTrace(); + } + } + + } + + public static class Builder{ + private int threadSize=1; + private String charset=null; + private int bufferSize=1024*1024; + private IHandle handle; + private File file; + public Builder(String file,IHandle handle){ + this.file = new File(file); + if(!this.file.exists()) + throw new IllegalArgumentException("文件不存在!"); + this.handle = handle; + } + + public Builder withTreahdSize(int size){ + this.threadSize = size; + return this; + } + + public Builder withCharset(String charset){ + this.charset= charset; + return this; + } + + public Builder withBufferSize(int bufferSize){ + this.bufferSize = bufferSize; + return this; + } + + public MultiThreadReader build(){ + return new MultiThreadReader(this.file,this.handle,this.charset,this.bufferSize,this.threadSize); + } + } + + +} + diff --git a/bigdata/src/main/java/cn/tommyyang/collections/ListUtils.java b/bigdata/src/main/java/cn/tommyyang/collections/ListUtils.java new file mode 100644 index 0000000..ae2c8e0 --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/collections/ListUtils.java @@ -0,0 +1,108 @@ +package cn.tommyyang.collections; + +import java.util.*; + +/** + * @Author : TommyYang + * @Time : 2019年05月31日 16:07 + * @Software: IntelliJ IDEA + * @File : ListUtils.java + */ +public class ListUtils { + + public static List maxArrayList(int size) { + List strList = new ArrayList(size); + for (int i = 0; i < size; i++) { + strList.add(i + "test"); + } + return strList; + } + + public static List maxList(int size) { + List strList = new LinkedList(); + for (int i = 0; i < size; i++) { + strList.add(i + "test"); + } + return strList; + } + + public static List testList(int size) { + List testList = new ArrayList(); + for (int i = 0; i < size; i++) { + testList.add(i + "test"); + } + return testList; + } + + public static Set testSet(int size) { + Set testSet = new HashSet(); + for (int i = 0; i < size; i++) { + testSet.add(i + "test"); + } + return testSet; + } + + public static void deWeightList(List des, List sourse) { + if (sourse == null || sourse.size() <= 0) { + return; + } + Iterator listStr = sourse.iterator(); + while (listStr.hasNext()) { + String item = listStr.next(); + if (des.contains(item)) { + listStr.remove(); //考虑到list的删除效率慢,此种方法对于大数据集合来说不合适 + } + } + System.out.println("after deWight list size: " + sourse.size()); + } + + public static void deWeightList(Set des, List sourse) { + if (sourse == null || sourse.size() <= 0) { + return; + } + Iterator listStr = sourse.iterator(); + while (listStr.hasNext()) { + String item = listStr.next(); + if (des.contains(item)) { + listStr.remove(); //考虑到list的删除效率慢,此种方法对于大数据集合来说不合适 + } + } + System.out.println("after deWight list size: " + sourse.size()); + } + + public static void deWeightListByNewList(Set des, List sourse) { + if (sourse == null || sourse.size() <= 0) { + return; + } + Iterator listStr = sourse.iterator(); + List existList = new ArrayList(); + while (listStr.hasNext()) { + String item = listStr.next(); + if(!des.contains(item)){ + //TODO 对去重后的数据进行逻辑操作,不一定要删除,可以换个思路(是否可以直接逻辑操作,不一定非要再把数据写进集合后,然后遍历集合在进行逻辑操作) + existList.add(item); //改成添加进新的list,考虑到list的删除效率慢(非要得到删除后的集合的情况下,否则走else) + } + } + sourse.clear(); + sourse = existList; + System.out.println("after deWight list size: " + sourse.size()); + } + + public static void deWeightListOther(List des, List sourse) { + if (sourse == null || sourse.size() <= 0) { + return; + } + for (int i = sourse.size() - 1; i>= 0; i--) { + String str = sourse.get(i); + for (String item : des) { + if (str.equals(item)) { + sourse.remove(i); + break; + } + } + } + System.out.println("after other deWight list size: " + sourse.size()); + } + + +} diff --git a/bigdata/src/main/java/cn/tommyyang/collections/RunTest.java b/bigdata/src/main/java/cn/tommyyang/collections/RunTest.java new file mode 100644 index 0000000..a640605 --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/collections/RunTest.java @@ -0,0 +1,69 @@ +package cn.tommyyang.collections; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * @Author : TommyYang + * @Time : 2019年05月31日 16:09 + * @Software: IntelliJ IDEA + * @File : RunTest.java + */ +public class RunTest { + + public static void main(String[] args) { + SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); + + List maxArrayList = ListUtils.maxArrayList(25000000); +// Set maxSet = new HashSet(maxArrayList); +// maxSet.addAll(maxArrayList); + + List testList = ListUtils.testList(20000); + Set testSet = ListUtils.testSet(1500000); + + //遍历过程中去重 + System.out.println("start test foreach list directly, start time: " + sdf.format(new Date())); + + for (String item: maxArrayList) { + if(!testSet.contains(item)){ + //TODO + } + } + + System.out.println("end test foreach list directly, end time: " + sdf.format(new Date()) + "\n"); + + + //List结合List去重 + System.out.println("start test list, start time: " + sdf.format(new Date())); + + ListUtils.deWeightList(testList, maxArrayList); + + System.out.println("end test list, end time: " + sdf.format(new Date()) + "\n"); + + maxArrayList.clear(); + maxArrayList = ListUtils.maxArrayList(25000000); + + //List结合Set去重 + System.out.println("start test set, start time: " + sdf.format(new Date())); + + ListUtils.deWeightList(testSet, maxArrayList); + + System.out.println("end test set, end time: " + sdf.format(new Date())); + + maxArrayList.clear(); + maxArrayList = ListUtils.maxArrayList(25000000); + //List结合Set去重(不是直接对list进行删除,而是组装新list,考虑到list删除效率低) + System.out.println("start test set, start time: " + sdf.format(new Date())); + + ListUtils.deWeightListByNewList(testSet, maxArrayList); + + System.out.println("end test set, end time: " + sdf.format(new Date())); + + + } + + + +} diff --git a/bigdata/src/main/java/cn/tommyyang/hive/udf/ContainsString.java b/bigdata/src/main/java/cn/tommyyang/hive/udf/ContainsString.java new file mode 100644 index 0000000..4e6ea8a --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/hive/udf/ContainsString.java @@ -0,0 +1,31 @@ +package cn.tommyyang.hive.udf; + +import org.apache.hadoop.hive.ql.exec.UDFArgumentException; +import org.apache.hadoop.hive.ql.metadata.HiveException; +import org.apache.hadoop.hive.ql.udf.generic.GenericUDF; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; + +/** + * @Author : TommyYang + * @Time : 2019年09月19日 15:22 + * @Software: IntelliJ IDEA + * @File : ContainsString.java + */ +public class ContainsString extends GenericUDF { + + @Override + public ObjectInspector initialize(ObjectInspector[] objectInspectors) throws UDFArgumentException { + return null; + } + + @Override + public Object evaluate(DeferredObject[] deferredObjects) throws HiveException { + return null; + } + + @Override + public String getDisplayString(String[] strings) { + return null; + } + +} diff --git a/bigdata/src/main/java/cn/tommyyang/hive/udf/IPToLocation.java b/bigdata/src/main/java/cn/tommyyang/hive/udf/IPToLocation.java new file mode 100644 index 0000000..b1aab2b --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/hive/udf/IPToLocation.java @@ -0,0 +1,52 @@ +package cn.tommyyang.hive.udf; + +import net.ipip.ipdb.City; +import net.ipip.ipdb.CityInfo; +import net.ipip.ipdb.IPFormatException; +import org.apache.hadoop.hive.ql.exec.Description; +import org.apache.hadoop.hive.ql.exec.UDF; +import org.apache.hadoop.io.Text; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +/** + * @Author : TommyYang + * @Time : 2019年08月30日 16:53 + * @Software: IntelliJ IDEA + * @File : IPToLocation.java + */ +@Description( + name = "ip2loc", + value = "_FUNC_(str) - covert ip to location", + extended = "Example:\n" + + "> SELECT ip2loc(ip) FROM ips;\n" + + " [中国,上海]" +) +public class IPToLocation extends UDF { + + private static final InputStream stream = IPToLocation.class.getClassLoader().getResourceAsStream("ipipfree.ipdb"); + private static City db = null; + + static { + try { + db = new City(stream); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 具体实现逻辑 + public ArrayList evaluate(Text s) throws IOException, IPFormatException { + ArrayList allTexts = new ArrayList(); + if (s != null) { + CityInfo info = db.findInfo(s.toString(), "CN"); + allTexts.add(info.getCountryName()); + allTexts.add(info.getRegionName()); + allTexts.add(info.getCityName()); + } + return allTexts; + } + +} \ No newline at end of file diff --git a/books/README.md b/books/README.md new file mode 100644 index 0000000..3c2a045 --- /dev/null +++ b/books/README.md @@ -0,0 +1,35 @@ +# 书籍总结 +本文主要分享个人总结的电子书,如有侵权,请通知删除,谢谢! + +## 大数据 +| 书名 | 链接 | 提取码 | +| :----: | :----: | :----: | +| Kafka技术内幕.pdf | [下载](https://pan.baidu.com/s/1ue-o_Q4mnVK8aXYc044Vyw) | 4osu | +| Kafka权威指南.pdf | [下载](https://pan.baidu.com/s/1gdLZ28_BfH-cg8HFRupbmA) | d05a | +| Redis 设计与实现.pdf | [下载](https://pan.baidu.com/s/1ySc4g8CCBcm8ec6EqLnaEQ) | osbz | +| Redis 深度历险:核心原理和应用实践.pdf | [下载](https://pan.baidu.com/s/1hC_JUeBhCXFjYPNZ8b3xuw) | qdcg | +| Redis 开发与运维.pdf | [下载](https://pan.baidu.com/s/1u1GwsqPerdf7N9y0j3yKOg) | uwy9 | + +## Java +| 书名 | 链接 | 提取码 | +| :----: | :----: | :----: | +| Effective Java 中文版(第 2 版).pdf | [下载](https://pan.baidu.com/s/150ztSubX3-NhgMHMPiRFYQ) | mmdw | +| 实战 Java 高并发程序设计.pdf | [下载](https://pan.baidu.com/s/1QUse0rmMBIvxFgeiJZ7TxQ) | l9xt | +| Java8 实战.pdf | [下载](https://pan.baidu.com/s/1y5m1hgn9cJT7pyE5qI9UuQ) | og1w | +| 深入理解 Java 虚拟机第二版.pdf | [下载](https://pan.baidu.com/s/1mFE-B03b5Dwuz3_CJgHA7g) | c1j1 | + +## 算法 +| 书名 | 链接 | 提取码 | +| :----: | :----: | :----: | +| 编程珠玑(第 2 版).pdf | [下载](https://pan.baidu.com/s/174v9WNIHBFpAxmy9uCYX-g) | 5698 | + +## 架构 +| 书名 | 链接 | 提取码 | +| :----: | :----: | :----: | +| 微服务设计.pdf | [下载](https://pan.baidu.com/s/1uaCQhagPU1ElrC2zuBePdA) | bfir | +| 大型网站技术架构.pdf | [下载](https://pan.baidu.com/s/1F_CpOz-0sspDjGktG1z3Yw) | wf1l | + +## 人工智能 +### 读书笔记 +#### A/B测试 +![A/B测试](https://github.com/joyang1/JavaInterview/blob/master/img/AB%E6%B5%8B%E8%AF%95.png) \ No newline at end of file diff --git a/codeinterview/README.md b/codeinterview/README.md new file mode 100644 index 0000000..ec8289b --- /dev/null +++ b/codeinterview/README.md @@ -0,0 +1,10 @@ +# 代码篇 +## LeetCode + + +## 面试中遇到的具体编码问题 +- [多线程(使用线程池)按顺序打印 0-100;(至少3个线程交替按顺序打印)](order-print-num.md) +- [爬楼梯问题](dynamic-programming.md#L11) + +## 算法相关 +- [动态规划](dynamic-programming.md) \ No newline at end of file diff --git a/codeinterview/dynamic-programming.md b/codeinterview/dynamic-programming.md new file mode 100644 index 0000000..aa3d083 --- /dev/null +++ b/codeinterview/dynamic-programming.md @@ -0,0 +1,81 @@ +# 动态规划(Dynamic Programming) +动态规划其实和分治策略是类似的,也是将一个原问题分解为若干个规模较小的子问题,递归的求解这些子问题,然后合并子问题的解得到原问题的解。 +区别在于这些子问题会有重叠,一个子问题在求解后,可能会再次求解,于是我们想到将这些子问题的**解存储起来**,当下次再次求解这个子问题时,直接拿过来就是。 +其实就是说,动态规划所解决的问题是分治策略所解决问题的一个子集,只是这个子集更适合用动态规划来解决从而得到更小的运行时间。 +**即用动态规划能解决的问题分治策略肯定能解决,只是运行时间长了**。因此,分治策略一般用来解决子问题相互对立的问题,称为标准分治,而动态规划用来解决子问题重叠的问题。 + +将 **动态规划** 的概念关键点抽离出来描述就是这样的: +- 动态规划法试图只解决每个子问题一次。 +- 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 + +## 爬楼梯问题 +下面通过爬楼梯问题来分析使用动态规划的解决问题(该问题也是在现场面试中遇到的)。 + +**问题描述**:爬楼梯,我们每次可以走 1 级阶梯或者 2 级阶梯,那我们走 50(n) 级阶梯总共有多少种走法。 +在现场思考过程中,如果没有使用动态规划的话,那就要用递归的概念去做。 + +### 递归方式 +我们在高中的数学课里面应该学习过规律推理,那我们可以很容易的去推理出来公式: +1 级台阶: 1 种走法(f(1)) +2 级台阶: 2 种走法(f(2)) +3 级台阶: 3 种走法(f(3) = f(2) + f(1)) +4 级台阶: 5 种走法(f(4) = f(3) + f(2)) +5 级台阶: 8 种走法(f(5) = f(4) + f(3)) +... +n 级台阶: f(n) = f(n-1) + f(n-2) **(n> 2)** + +通过以上公式,很容易推导出一下递归代码: + +``` java + +int f(int n) { + if (n == 1) return 1; + if (n == 2) return 2; + return f(n-1) + f(n-2); +} + +``` + +code: + +``` java + +int f(int n) { + if (n == 1) return 1; + if (n == 2) return 2; + // a 保存倒数第二个子状态数据,b 保存倒数第一个子状态数据, temp 保存当前状态的数据 + int a = 1, b = 2; + int temp = a + b; + for (int i = 3; i <= n; i++) { + temp = a + b; + a = b; + b = temp; + } + return temp; +} + +``` + +### 从递归到动态规划 +还是以**爬台阶**为例,如果以递归的方式解决的话,那么这种方法的时间复杂度为O(2^n) + +### 详解动态规划 + +**动态规划**中包含三个重要的概念: +- 最优子结构 +- 边界 +- 状态转移公式 + +在**爬台阶问题**中: + +f(10) = f(9) + f(8) 是最优子结构 +f(1) 与 f(2) 是边界 +f(n) = f(n-1) + f(n-2) 状态转移公式 + +**爬台阶问题**只是动态规划中相对简单的问题,因为它只有一个变化维度,如果涉及多个维度的话,那么问题就变得复杂多了。 + +难点就在于找出**动态规划**中的这三个概念。感觉跟我们高中学习的推理证明题有点像,通过现在的条件推断出结果。 + +## 国王和金矿问题 +### 问题描述 +有一个国家发现了 5 座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是 10 人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿? \ No newline at end of file diff --git a/codeinterview/order-print-num.md b/codeinterview/order-print-num.md new file mode 100644 index 0000000..c1ef09b --- /dev/null +++ b/codeinterview/order-print-num.md @@ -0,0 +1,210 @@ +问题分析:首先我们需要看清楚题目,多线程、顺序打印、然后就是几个线程交替顺序打印; + +首先看下输出结果: + +``` + +thread-0:0 +thread-1:1 +thread-2:2 +thread-0:3 +thread-1:4 +thread-2:5 +thread-0:6 +thread-1:7 +thread-2:8 +thread-0:9 +thread-1:10 +thread-2:11 +thread-0:12 +thread-1:13 +thread-2:14 +thread-0:15 +thread-1:16 +thread-2:17 +thread-0:18 +thread-1:19 +thread-2:20 +thread-0:21 +thread-1:22 +thread-2:23 +thread-0:24 +thread-1:25 +thread-2:26 +thread-0:27 +thread-1:28 +thread-2:29 +thread-0:30 +thread-1:31 +thread-2:32 +thread-0:33 +thread-1:34 +thread-2:35 +thread-0:36 +thread-1:37 +thread-2:38 +thread-0:39 +thread-1:40 +thread-2:41 +thread-0:42 +thread-1:43 +thread-2:44 +thread-0:45 +thread-1:46 +thread-2:47 +thread-0:48 +thread-1:49 +thread-2:50 +thread-0:51 +thread-1:52 +thread-2:53 +thread-0:54 +thread-1:55 +thread-2:56 +thread-0:57 +thread-1:58 +thread-2:59 +thread-0:60 +thread-1:61 +thread-2:62 +thread-0:63 +thread-1:64 +thread-2:65 +thread-0:66 +thread-1:67 +thread-2:68 +thread-0:69 +thread-1:70 +thread-2:71 +thread-0:72 +thread-1:73 +thread-2:74 +thread-0:75 +thread-1:76 +thread-2:77 +thread-0:78 +thread-1:79 +thread-2:80 +thread-0:81 +thread-1:82 +thread-2:83 +thread-0:84 +thread-1:85 +thread-2:86 +thread-0:87 +thread-1:88 +thread-2:89 +thread-0:90 +thread-1:91 +thread-2:92 +thread-0:93 +thread-1:94 +thread-2:95 +thread-0:96 +thread-1:97 +thread-2:98 +thread-0:99 +thread-1:100 + +``` + +其实这个题目的重点就是怎么实现多个线程交替顺序打印,想一想,这里我们需要用到的就是线程间的通信,然后怎么通信呢?我们可以通过 synchronized + wait + notifyAll,或者 ReentrantLock + Condition + await + signalAll; + +知道了线程间怎么通信,然后就是在每个线程内部怎么实现了,是不是多个线程共用一把锁,然后自然数0-100是公共资源,让3个线程去消费; + +我们可以每个线程加上一个标号:0,1,2,来表示具体是哪个线程;通过一个计数器对3进行求余,余数和具体的线程标号去比较,只有当余数和线程标号相等的时候才进行打印(不等加入等待队列),打印完计数器进行自增;然后唤醒等待队列里面线程去获取锁,获取锁成功则继续执行以上步骤,否则加入锁的同步队列中,等待上一个线程唤醒; + +`用 synchronized 锁实现如下:` + +```java +class PrintThread1 implements Runnable { + private static final Object LOCK = new Object(); + + private static int count = 0; // 计数,同时确定线程是否要加入等待队列,还是可以直接去资源队列里面去获取数据进行打印 + private LinkedList queue; + private Integer threadNo; + + public PrintThread1(LinkedList queue, Integer threadNo) { + this.queue = queue; + this.threadNo = threadNo; + } + + @Override + public void run() { + while (true) { + synchronized (LOCK) { + while (count % 3 != this.threadNo) { + if (count>= 101) { + break; + } + try { + LOCK.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (count>= 101) { + break; + } + + Integer val = this.queue.poll(); + System.out.println("thread-" + this.threadNo + ":" + val); + count++; + + LOCK.notifyAll(); + } + } + } +} +``` + +`用 ReentrantLock 锁实现如下:` + +```java +class PrintThread2 implements Runnable { + private static final ReentrantLock lock = new ReentrantLock(); + private static final Condition c = lock.newCondition(); + + private static int count = 0; //作为计数,同时也作为资源;因为这道题目是自然数作为资源,所以正好可以公用; + private Integer threadNo; + + public PrintThread2(Integer threadNo) { + this.threadNo = threadNo; + } + + @Override + public void run() { + while (true) { + try { + lock.lock(); + while (count % 3 != this.threadNo) { + if (count>= 101) { + break; + } + try { + c.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (count>= 101) { + break; + } + System.out.println("thread-" + this.threadNo + ":" + count); + count++; + + c.signalAll(); + + } finally { + lock.unlock(); + } + } + } +} +``` + +`具体代码实现:` + +参见:[code](https://github.com/joyang1/JavaInterview/blob/master/codeinterview/src/main/java/cn/tommyyang/multithread/OrderPrintNum.java) \ No newline at end of file diff --git a/codeinterview/pom.xml b/codeinterview/pom.xml new file mode 100644 index 0000000..a24355a --- /dev/null +++ b/codeinterview/pom.xml @@ -0,0 +1,27 @@ + + + + javainterview + cn.tommyyang + 1.0-SNAPSHOT + + 4.0.0 + + codeinterview + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/codeinterview/src/main/java/cn/tommyyang/array/FindPosition.java b/codeinterview/src/main/java/cn/tommyyang/array/FindPosition.java new file mode 100644 index 0000000..439aa03 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/array/FindPosition.java @@ -0,0 +1,22 @@ +package cn.tommyyang.array; + +/** + * @author TommyYang on 2019年03月27日 + */ +public class FindPosition { + + public int searchInsert(int[] nums, int target) { + if (nums == null || nums.length == 0 || nums[0]>= target){ + return 0; + } + for (int i = 0; i < nums.length; i++){ + if (nums[i]>= target){ + return i; + } + } + + return nums.length; + + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/array/InterSection.java b/codeinterview/src/main/java/cn/tommyyang/array/InterSection.java new file mode 100644 index 0000000..f7b80df --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/array/InterSection.java @@ -0,0 +1,42 @@ +package cn.tommyyang.array; + + +import java.util.HashSet; +import java.util.Set; + +/** + * @author TommyYang on 2019年05月19日 + */ +//求两个数组的交集(https://leetcode-cn.com/problems/intersection-of-two-arrays/) +public class InterSection { + + //暂时没想到更好的解法,感觉解法效率有待提高。 + public int[] intersection(int[] nums1, int[] nums2) { + + if(nums1 == null || nums2 == null){ + return new int[]{}; + } + + Set set = new HashSet(nums1.length); + for (int i : nums1){ + set.add(i); + } + + Set res = new HashSet(nums2.length); + for (int i : nums2){ + if (set.contains(i)){ + res.add(i); + } + } + + int[] resArr = new int[res.size()]; + + int index = 0; + for(int i : res){ + resArr[index++] = i; + } + + return resArr; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/array/TwoSortArrMerge.java b/codeinterview/src/main/java/cn/tommyyang/array/TwoSortArrMerge.java new file mode 100644 index 0000000..b633fbb --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/array/TwoSortArrMerge.java @@ -0,0 +1,39 @@ +package cn.tommyyang.array; + +/** + * @author TommyYang on 2019年03月21日 + */ +public class TwoSortArrMerge { + + public static void main(String[] args) { + int[] nums1 = new int[]{2,5,6, 0 , 0, 0}; + int[] nums2 = new int[]{1,2,3}; + + merge(nums1, 3, nums2, 3); + + for(int i : nums1){ + System.out.println(i); + } + } + + public static void merge(int[] nums1, int m, int[] nums2, int n) { + int i = m - 1, j = n - 1, index = m + n - 1; + while (i>= 0 || j>= 0){ + //i < 0; 说明 nums1已经填充完了,只剩下nums2,所以只需要把nums2填充到nums1对应的index上就行 + if(i < 0){ + nums1[index--] = nums2[j--]; + continue; + }else if (j < 0){ + // j < 0; 说明nums2已经全部填充到了nums1,直接跳出循环就行了; + break; + } + //将数据填充到具体的index + if(nums1[i]>= nums2[j]){ + nums1[index--] = nums1[i--]; + } else { + nums1[index--] = nums2[j--]; + } + } + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/CrossLinkedList.java b/codeinterview/src/main/java/cn/tommyyang/listnode/CrossLinkedList.java new file mode 100644 index 0000000..ad670cd --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/CrossLinkedList.java @@ -0,0 +1,27 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019年03月25日 + */ + +//相交链表题目(https://leetcode-cn.com/problems/intersection-of-two-linked-lists/) +public class CrossLinkedList { + + //找到两个单链表相交的起始节点,返回相交的链表 + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + + ListNode first = headA; + ListNode second = headB; + + //当第一个链表运行完后,赋值给第二个链表的头节点; + //第二个链表运行完后,赋值给第一个链表的头结点; + //运行到相交的时候(相交),或者都运行到null的时候(不相交),都是走了同样的距离;可以画图结合理解更简单。 + while (first != second){ + first = (first == null ? headB : first.next); + second = (second == null ? headA : second.next); + } + + return first; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/CycleNode2.java b/codeinterview/src/main/java/cn/tommyyang/listnode/CycleNode2.java new file mode 100644 index 0000000..7a8aedb --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/CycleNode2.java @@ -0,0 +1,38 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019年05月06日 + */ +//环形链表2(https://leetcode-cn.com/problems/linked-list-cycle-ii/) +public class CycleNode2 { + + public ListNode detectCycle(ListNode head) { + ListNode fast = head; + ListNode slow = head; + boolean meet = false; + + while (fast != null && fast.next != null){ + fast = meet ? fast.next : fast.next.next; + slow = slow.next; + + if (fast == slow){ + if (!meet){ + //说明环相交的地方在头结点,则直接相交的时候,快的结点走了两圈,慢的结点走了一圈 + if (fast == head){ + return fast; + } + // 如果相交的地方不在头节点,通过a+b+b+c=2(a+b); + // 这题一定要利用fast指针的速度是slow指针的2倍,得到fast指针重设为head指针 + // 运行后第二次相遇的地方一定是相交的地方 + fast = head; + meet = true; + } else { + return fast; + } + } + } + + return null; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/DeleteNodeInLinkedList.java b/codeinterview/src/main/java/cn/tommyyang/listnode/DeleteNodeInLinkedList.java new file mode 100644 index 0000000..9dd0347 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/DeleteNodeInLinkedList.java @@ -0,0 +1,16 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019年05月07日 + */ +//删除某个链表中给定的(非末尾)节点 +public class DeleteNodeInLinkedList { + + //其实这个题目很简单 删除某个结点 其实你可以想成是删除某个结点的后面一个结点 + //同时把后面一个结点的数据填充到当前结点 从而做到删除当前结点 + public void deleteNode(ListNode node) { + node.val = node.next.val; + node.next = node.next.next; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/DetectCycle.java b/codeinterview/src/main/java/cn/tommyyang/listnode/DetectCycle.java new file mode 100644 index 0000000..e8a02af --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/DetectCycle.java @@ -0,0 +1,12 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019年04月08日 + */ +public class DetectCycle { + + public ListNode detectCycle(ListNode head){ + return null; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/HasCycle.java b/codeinterview/src/main/java/cn/tommyyang/listnode/HasCycle.java new file mode 100644 index 0000000..d314e24 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/HasCycle.java @@ -0,0 +1,26 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019年04月08日 + */ +public class HasCycle { + + public boolean hasCycle(ListNode head){ + ListNode dummy = new ListNode(0); + + ListNode first = dummy, second = dummy; + + while (second != null && second.next != null){ + first = first.next; + second = second.next.next; + + if (first == second){ + return true; + } + } + + return false; + + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/ListNode.java b/codeinterview/src/main/java/cn/tommyyang/listnode/ListNode.java new file mode 100644 index 0000000..55c7de4 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/ListNode.java @@ -0,0 +1,28 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019年03月20日 + */ +public class ListNode { + + int val; + + ListNode next; + + public ListNode(int val) { + this.val = val; + } + + public ListNode(int val, ListNode next) { + this.val = val; + this.next = next; + } + + public ListNode getNext() { + return next; + } + + public void setNext(ListNode next) { + this.next = next; + } +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/MergeTwoSortLinkedList.java b/codeinterview/src/main/java/cn/tommyyang/listnode/MergeTwoSortLinkedList.java new file mode 100644 index 0000000..6345531 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/MergeTwoSortLinkedList.java @@ -0,0 +1,83 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019年03月20日 + */ +public class MergeTwoSortLinkedList { + + public static void main(String[] args) throws InterruptedException { + ListNode node1 = new ListNode(1); + ListNode node2 = new ListNode(3); + ListNode node3 = new ListNode(4); + + ListNode node4 = new ListNode(1); + ListNode node5 = new ListNode(2); + ListNode node6 = new ListNode(4); + + node1.setNext(node2); + node2.setNext(node3); + + node4.setNext(node5); + node5.setNext(node6); + + ListNode head = mergeTwoLists(node1, node4); + while (head != null){ + System.out.println(head.val); + head = head.next; + } + } + + public static ListNode mergeTwoLists(ListNode l1, ListNode l2) { +// if(l1 == null){ +// return l2; +// } +// +// if(l2 == null){ +// return l1; +// } +// +// ListNode tmp; +// if(l1.val> l2.val){ +// tmp = l1; +// l2 = tmp; +// l1 = l2; +// } +// ListNode head = l1; +// ListNode tmp2; +// while(l1.next != null && l2 != null){ +// if(l1.val <= l2.val && l1.next.val <= l2.val){ +// l1 = l1.next; +// }else if (l1.val <= l2.val && l1.next.val> l2.val){ +// tmp = l1.next; +// l1.next = l2; +// tmp2 = l2.next; +// l2.next = tmp; +// l2 = tmp2; +// l1 = l1.next; +// } +// } +// if(l1 != null){ +// l1.next = l2; +// } +// +// return head; + + if(l1 == null){ + return l2; + } + + if(l2 == null){ + return l1; + } + + if(l1.val <= l2.val){ + l1.next = mergeTwoLists(l1.next, l2); + return l1; + } else{ + l2.next = mergeTwoLists(l1, l2.next); + return l2; + } + + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/RemoveElementsInLinkedList.java b/codeinterview/src/main/java/cn/tommyyang/listnode/RemoveElementsInLinkedList.java new file mode 100644 index 0000000..bd1da68 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/RemoveElementsInLinkedList.java @@ -0,0 +1,27 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019-05-07 + */ +//删除链表中等于给定值 val 的所有节点。 +public class RemoveElementsInLinkedList { + + public ListNode removeElements(ListNode head, int val) { + ListNode dummy = new ListNode(-1); + dummy.next = head; + + ListNode current = dummy; + while (current.next != null){ + if (current.next.val == val) { + // 删除链表后面一个结点后 即相当于链表后移了一个位置 + // 需要继续判断current.next.val 需不需要移除 + current.next = current.next.next; + }else { + current = current.next; + } + + } + return dummy.next; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/RemoveNthFromEnd.java b/codeinterview/src/main/java/cn/tommyyang/listnode/RemoveNthFromEnd.java new file mode 100644 index 0000000..8a3f754 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/RemoveNthFromEnd.java @@ -0,0 +1,28 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019-04-08 + */ +public class RemoveNthFromEnd { + + public ListNode removeNthFromEnd(ListNode head, int n){ + ListNode dummy = new ListNode(0); + dummy.next = head; + + ListNode first = dummy, second = dummy; + + for(int i = 0; i <= n; i++){ + first = first.next; + } + + while (first != null){ + first = first.next; + second = second.next; + } + + second.next = second.next.next; + + return dummy.next; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList.java b/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList.java new file mode 100644 index 0000000..745cd4d --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList.java @@ -0,0 +1,85 @@ +package cn.tommyyang.listnode; + +/** + * 链表反转 + * 1、迭代算法 + * 2、递归算法 + * + * @Author : TommyYang + * @Time : 2019-06-19 18:29 + * @Software: IntelliJ IDEA + * @File : ReverseLinkedList.java + */ +public class ReverseLinkedList { + + /** + * 迭代算法反转 + * + * @param head + * @return + */ + public ListNode reverseWithIterate(ListNode head) { + ListNode cur = head; + ListNode next; + ListNode pre = null; + + while (cur != null) { + next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + return pre; + } + + /** + * 递归算法反转 + * + * @param head + * @return + */ + public ListNode reverseWithRecursion(ListNode head) { + if (head == null || head.next == null) { + return head; + } + + ListNode newNode = reverseWithRecursion(head.next); + + head.next.next = head; + head.next = null; + + return newNode; + } + + public static void main(String[] args) { + ListNode node5 = new ListNode(5, null); + ListNode node4 = new ListNode(4, node5); + ListNode node3 = new ListNode(3, node4); + ListNode node2 = new ListNode(2, node3); + ListNode node1 = new ListNode(1, node2); + + print(node1); + + ListNode newNode = new ReverseLinkedList().reverseWithRecursion(node1); + + print(newNode); + } + + /** + * 打印链表 + * + * @param head 链表头 + */ + private static void print(ListNode head) { + while (head != null) { + System.out.print(head.val); + if (head.next != null) { + System.out.print("->"); + } + head = head.next; + } + System.out.println(); + } + + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList2.java b/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList2.java new file mode 100644 index 0000000..6176320 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList2.java @@ -0,0 +1,44 @@ +package cn.tommyyang.listnode; + +/** + * @author TommyYang on 2019年05月04日 + */ +//反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。 +public class ReverseLinkedList2 { + + public ListNode reverseBetween(ListNode head, int m, int n) { + + ListNode dummy = new ListNode(0); + dummy.next = head; + + ListNode index = dummy; + ListNode left, right, tmp; + + //count 为倒转链表的长度 + int count = n - m + 1; + //找到倒转链表左结点的前面一个结点 + while (m> 1){ + m--; + index = index.next; + } + left = index; + index = index.next; + right = index; + left.next = null; + + while (count> 0){ + count--; + tmp = index; + index = index.next; + tmp.next = left.next; + left.next = tmp; + } + + //连接右半部分 + right.next = index; + + return left == dummy ? left.next : head; + } + +} + diff --git a/codeinterview/src/main/java/cn/tommyyang/multithread/OrderPrintNum.java b/codeinterview/src/main/java/cn/tommyyang/multithread/OrderPrintNum.java new file mode 100644 index 0000000..eb15e9b --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/multithread/OrderPrintNum.java @@ -0,0 +1,145 @@ +package cn.tommyyang.multithread; + +import java.util.LinkedList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @Author : TommyYang + * @Time : 2019年06月25日 23:20 + * @Software: IntelliJ IDEA + * @File : OrderPrintNum.java + */ +public class OrderPrintNum { + + private static final LinkedList QUEUE = new LinkedList(); + private static final ExecutorService POOL_SERVICE = Executors.newFixedThreadPool(3); + + public static void main(String[] args) throws InterruptedException { + +// runPrint2(); + + initData(); + + POOL_SERVICE.execute(new PrintThread1(QUEUE, 2)); + POOL_SERVICE.execute(new PrintThread1(QUEUE, 1)); + POOL_SERVICE.execute(new PrintThread1(QUEUE, 0)); + + POOL_SERVICE.shutdown(); + + while (true) { + if (POOL_SERVICE.isTerminated()) { + System.out.println("finished!!"); + break; + } + } + } + + private static void initData() { + for (int i = 0; i <= 100; i++) { + QUEUE.add(i); + } + } + + + // 方案二 + private static void runPrint2() { + POOL_SERVICE.execute(new PrintThread2(0)); + POOL_SERVICE.execute(new PrintThread2(1)); + POOL_SERVICE.execute(new PrintThread2(2)); + + POOL_SERVICE.shutdown(); + + while (true) { + if (POOL_SERVICE.isTerminated()) { + System.out.println("finished!!"); + break; + } + } + } +} + +class PrintThread1 implements Runnable { + private static final Object LOCK = new Object(); + + private static int count = 0; // 计数,同时确定线程是否要加入等待队列,还是可以直接去资源队列里面去获取数据进行打印 + private LinkedList queue; + private Integer threadNo; + + public PrintThread1(LinkedList queue, Integer threadNo) { + this.queue = queue; + this.threadNo = threadNo; + } + + @Override + public void run() { + while (true) { + synchronized (LOCK) { + while (count % 3 != this.threadNo) { + if (count>= 101) { + break; + } + try { + LOCK.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (count>= 101) { + break; + } + + Integer val = this.queue.poll(); + System.out.println("thread-" + this.threadNo + ":" + val); + count++; + + LOCK.notifyAll(); + } + } + } +} + +class PrintThread2 implements Runnable { + private static final ReentrantLock lock = new ReentrantLock(); + private static final Condition c = lock.newCondition(); + + private static int count = 0; //作为计数,同时也作为资源;因为这道题目是自然数作为资源,所以正好可以公用; + private Integer threadNo; + + public PrintThread2(Integer threadNo) { + this.threadNo = threadNo; + } + + @Override + public void run() { + while (true) { + try { + lock.lock(); + while (count % 3 != this.threadNo) { + if (count>= 101) { + break; + } + try { + c.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (count>= 101) { + break; + } + System.out.println("thread-" + this.threadNo + ":" + count); + count++; + + c.signalAll(); + + } finally { + lock.unlock(); + } + } + } +} \ No newline at end of file diff --git a/codeinterview/src/main/java/cn/tommyyang/str/LongestSubstring.java b/codeinterview/src/main/java/cn/tommyyang/str/LongestSubstring.java new file mode 100644 index 0000000..b699c4d --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/str/LongestSubstring.java @@ -0,0 +1,26 @@ +package cn.tommyyang.str; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author TommyYang on 2019年04月08日 + */ +public class LongestSubstring { + + public int lengthofLongestSubstring(String s){ + int i = 0, j = 0, maxLen = 0; + Set set = new HashSet(); + int n = s.length(); + while (i < n && j < n){ + if (!set.contains(s.charAt(j))){ + set.add(s.charAt(j++)); + maxLen = Math.max(maxLen, j - i); + } else { + set.remove(s.charAt(i++)); + } + } + return maxLen; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/str/ReverseInt.java b/codeinterview/src/main/java/cn/tommyyang/str/ReverseInt.java new file mode 100644 index 0000000..9c9094f --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/str/ReverseInt.java @@ -0,0 +1,22 @@ +package cn.tommyyang.str; + +/** + * @author TommyYang on 2019-04-08 + */ +public class ReverseInt { + + public int reverse(int x) { + long res = 0; + while (x != 0){ + res = res * 10 + x % 10; + res /= 10; + } + + if (res> Integer.MAX_VALUE || res < Integer.MIN_VALUE){ + return 0; + } + + return (int)res; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/tree/InvertTree.java b/codeinterview/src/main/java/cn/tommyyang/tree/InvertTree.java new file mode 100644 index 0000000..9014bde --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/tree/InvertTree.java @@ -0,0 +1,24 @@ +package cn.tommyyang.tree; + +/** + * @author TommyYang on 2019-05-08 + */ +//翻转一棵二叉树。 +public class InvertTree { + + public TreeNode invertTree(TreeNode root) { + if (root == null){ + return root; + } + + TreeNode tmp = root.left; + root.left = root.right; + root.right = tmp; + + invertTree(root.left); + invertTree(root.right); + + return root; + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/tree/MergeTwoBTree.java b/codeinterview/src/main/java/cn/tommyyang/tree/MergeTwoBTree.java new file mode 100644 index 0000000..b8c6bd1 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/tree/MergeTwoBTree.java @@ -0,0 +1,66 @@ +package cn.tommyyang.tree; + +/** + * @author TommyYang on 2019-03-22 + */ +public class MergeTwoBTree { + + public static void main(String[] args) { + TreeNode root1 = new TreeNode(1); + TreeNode t1 = new TreeNode(2); + TreeNode t2 = new TreeNode(3); + TreeNode t3 = new TreeNode(4); + + TreeNode root2 = new TreeNode(5); + TreeNode t4 = new TreeNode(4); + TreeNode t5 = new TreeNode(3); + TreeNode t6 = new TreeNode(2); + + root1.left = t1; + t1.left = t2; + t1.right = t3; + + root2.right = t4; + t4.left = t5; + t5.right = t6; + + TreeNode resNode = mergeTrees2(t1, t4); + displayTree(resNode); + } + + //以t1为存储结果的TreeNode + public static TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if(t1 == null){ + return t2; + } + if (t2 == null){ + return t1; + } + t1.val += t2.val; + t1.left = mergeTrees(t1.left, t2.left); + t1.right = mergeTrees(t1.right, t2.right); + return t1; + } + + //新建一个TreeNode来存储结果 + public static TreeNode mergeTrees2(TreeNode t1, TreeNode t2){ + if(t1 == null && t2 == null){ + return null; + } + + TreeNode root = new TreeNode((t1 == null ? 0 : t1.val) + (t2 == null ? 0 : t2.val)); + root.left = mergeTrees2((t1 == null ? null : t1.left), (t2 == null ? null : t2.left)); + root.right = mergeTrees2((t1 == null ? null : t1.right), (t2 == null ? null : t2.right)); + return root; + } + + public static void displayTree(TreeNode node){ + if (node == null){ + return; + } + System.out.println(node.val); + displayTree(node.left); + displayTree(node.right); + } + +} diff --git a/codeinterview/src/main/java/cn/tommyyang/tree/TreeNode.java b/codeinterview/src/main/java/cn/tommyyang/tree/TreeNode.java new file mode 100644 index 0000000..ac36115 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/tree/TreeNode.java @@ -0,0 +1,17 @@ +package cn.tommyyang.tree; + +/** + * @author TommyYang on 2019-03-22 + */ +public class TreeNode { + + public int val; + + public TreeNode left; + + public TreeNode right; + + public TreeNode(int val) { + this.val = val; + } +} diff --git a/codeinterview/src/main/java/cn/tommyyang/tree/ValidBST.java b/codeinterview/src/main/java/cn/tommyyang/tree/ValidBST.java new file mode 100644 index 0000000..cebef03 --- /dev/null +++ b/codeinterview/src/main/java/cn/tommyyang/tree/ValidBST.java @@ -0,0 +1,40 @@ +package cn.tommyyang.tree; + +/** + * @author TommyYang on 2019-03-28 + */ +public class ValidBST { + + + public static void main(String[] args) { + TreeNode t1 = new TreeNode(10); + TreeNode t2 = new TreeNode(5); + + TreeNode t3 = new TreeNode(15); + TreeNode t4 = new TreeNode(6); + TreeNode t5 = new TreeNode(20); + + t1.left = t2; + t1.right = t3; + t3.left = t4; + t3.right = t5; + + new ValidBST().isValidBST(t1); + } + + + double last = -Double.MAX_VALUE; + public boolean isValidBST(TreeNode root) { + if (root == null) { + return true; + } + if (isValidBST(root.left)) { + if (last < root.val) { + last = root.val; + return isValidBST(root.right); + } + } + return false; + } + +} diff --git "a/img/AB346円265円213円350円257円225円.png" "b/img/AB346円265円213円350円257円225円.png" new file mode 100644 index 0000000..5071b60 Binary files /dev/null and "b/img/AB346円265円213円350円257円225円.png" differ diff --git "a/img/tommy345円255円246円344円271円240円345円275円225円.png" "b/img/tommy345円255円246円344円271円240円345円275円225円.png" new file mode 100644 index 0000000..133cc3d Binary files /dev/null and "b/img/tommy345円255円246円344円271円240円345円275円225円.png" differ diff --git a/io/Multi-Thread-Write-File.md b/io/Multi-Thread-Write-File.md new file mode 100644 index 0000000..07bf223 --- /dev/null +++ b/io/Multi-Thread-Write-File.md @@ -0,0 +1,242 @@ +# 多线程 N 次写文件 +记一次项目中多线程 N 次写文件的愚蠢做法,通过这次愚蠢的编码,从中体会到的就是我们需要对 JDK 里面源码尽量熟悉,多看源码,这样才能提高我们编码的性能。 + +愚蠢代码如下: + +```java + +public class FileUtils { + + public static synchronized void writeFile(String filePath, String content) { + FileOutputStream outStream = null; + BufferedWriter bfWriter = null; + try { + outStream = new FileOutputStream(filePath, true); + bfWriter = new BufferedWriter(new OutputStreamWriter(outStream, "UTF-8")); + bfWriter.write(content); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (bfWriter != null) { + bfWriter.close(); + } + if (outStream != null) { + outStream.close(); + } + } catch (Exception e) { + + } + } + } + +} + +``` + +## 问题描述 +上述代码封装了一个通用的多线程 N 次追加写文件的类,上述代码虽然可以保证完成任务,但是代码是非常低效。我发现这段代码的问题,也是因为,一开始我在项目里面只需要使用这段代码写一个文件,写入的次数是大概是在 1500 次左右;而后续由于功能需求增加,我需要使用这段代码写多个文件,每个文件写入次数都是 1500 次左右,从而项目运行的效率变得异常缓慢,如龟速运行,让我无法接受。 + +## 改进版本一 +于是我开始改进,首先,我觉得每次写入的时候都去 new BufferedWriter 实例,这个是不可取的;浪费了 new 对象的时间,其实我们写文件,BufferedWriter 对象 new 一次就可以了。改进代码如下: + +```java + +public class BigFileWriter1 { + + private FileOutputStream outStream = null; + private BufferedWriter bfWriter = null; + + private String filePath; + + public BigFileWriter(String filePath) { + this.filePath = filePath; + open(); // new writer 对象的时候,open 需要使用的对象一次,节约每次写入的时候都去 new 对象的时间。 + } + + private void open() { + try { + outStream = new FileOutputStream(this.filePath, true); + bfWriter = new BufferedWriter(new OutputStreamWriter(outStream, "UTF-8")); + } catch (Exception e) { + System.out.println("get big file writer error"); + e.printStackTrace(); + } + } + + // synchronized 同步方法块, 非常抢眼且多余的 synchronized + public synchronized void writeFile(String content) { + try { + bfWriter.write(content); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * + * 该方法没有同步,原因是该方法应该是 main thread 去进行 close 的。 + * 因为我们需要让所有线程都执行完了,最后才会 close writer 对象 + * + */ + public void close() { + try { + if (bfWriter != null) { + bfWriter.close(); + } + if (outStream != null) { + outStream.close(); + } + } catch (Exception e) { + + } + } + +} + +``` + +经过上述代码改进后,代码效率基本可以了,但是由于对源码不熟悉,如果专业的人看到这段代码,会觉得我们比较业余,因为那个 synchronized 是抢眼且多余的,在专业的人看来会非常的不舒服的。由于 IO 操作是我们日常编程中使用到最多的 API,但是我们却对源码是那么的不熟悉。 + +## JDK 中 Writer 源码分析 +我们先来看看 Writer 的构造方法: + +``` java + +/** + * The object used to synchronize operations on this stream. For + * efficiency, a character-stream object may use an object other than + * itself to protect critical sections. A subclass should therefore use + * the object in this field rather than this or a synchronized + * method. + */ +protected Object lock; + +/** + * Creates a new character-stream writer whose critical sections will + * synchronize on the writer itself. + */ +protected Writer() { + this.lock = this; +} + +/** + * Creates a new character-stream writer whose critical sections will + * synchronize on the given object. + * + * @param lock + * Object to synchronize on + */ +protected Writer(Object lock) { + if (lock == null) { + throw new NullPointerException(); + } + this.lock = lock; +} + +``` + +从以上源码可以看出,Writer 里面关键的流部分,都会有 lock 锁进行同步;所以,对于同一个 writer instance 是线程安全的;所以我们写同一个文件的时候使用同一个 writer instance 是线程安全的。也就是说我们使用的 Writer、FileWriter、BufferedWriter 是线程安全的。 + +具体的 write 方法源码分析如下: + +``` java + +public void write(String str, int off, int len) throws IOException { + synchronized (lock) { + char cbuf[]; + if (len <= WRITE_BUFFER_SIZE) { + if (writeBuffer == null) { + writeBuffer = new char[WRITE_BUFFER_SIZE]; + } + cbuf = writeBuffer; + } else { // Don't permanently allocate very large buffers. + cbuf = new char[len]; + } + str.getChars(off, (off + len), cbuf, 0); + write(cbuf, 0, len); + } +} + +``` + +在初始化 Writer Instance 的时候,我们会确定一个同步锁对象,所以只要我们使用的是一个 Writer 对象,则可以保证线程安全。 + +## 改进版本二 +通过以上源码分析,我们可以很清楚地改进最优的代码如下: + +```java + +public class BigFileWriter { + + private FileOutputStream outStream = null; + private BufferedWriter bfWriter = null; + + private String filePath; + + public BigFileWriter(String filePath) { + this.filePath = filePath; + open(); // new writer 对象的时候,open 需要使用的对象一次,节约每次写入的时候都去 new 对象的时间。 + } + + private void open() { + try { + outStream = new FileOutputStream(this.filePath, true); + bfWriter = new BufferedWriter(new OutputStreamWriter(outStream, "UTF-8")); + } catch (Exception e) { + System.out.println("get big file writer error"); + e.printStackTrace(); + } + } + + // 由于 bufferedWriter 对象是线程安全的,所以不需要 synchronized 关键字。 + public void writeFile(String content) { + try { + bfWriter.write(content); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * + * 该方法没有同步,原因是该方法应该是 main thread 去进行 close 的。 + * 因为我们需要让所有线程都执行完了,最后才会 close writer 对象 + * + */ + public void close() { + try { + if (bfWriter != null) { + bfWriter.close(); + } + if (outStream != null) { + outStream.close(); + } + } catch (Exception e) { + + } + } + +} + +``` + +## 测试 +通过以上三种方法进行测试,使用时间如下: + +``` properties + +big file writer const:114 ms +big file writer 1 const:124 ms +file utils const:23236 ms + +``` + +大家可以通过如下测试源码自行测试: + +[测试源码](https://github.com/joyang1/JavaInterview/blob/master/io/src/main/java/cn/tommyyang/file/FileTest.java) + +通过测试我们可以发现,确实是改进版本二的代码效率最高。 + +## 总结 +希望大家多看源码,了解 JDK 源码,学习优秀的编码方式。 \ No newline at end of file diff --git a/io/README.md b/io/README.md new file mode 100644 index 0000000..eabf08d --- /dev/null +++ b/io/README.md @@ -0,0 +1,24 @@ +# IO 篇 + +该模块主要是介绍 `IO` 方面的知识; +- `NIO`, `AIO`; +- 网络编程相关; +- 多线程、多核 CPU、进程之间的关系(因为这些跟 IO 都有关系,所有放在该篇来介绍)。 + +## NIO 和 AIO +主要会用 `JDK` 原始 `NIO`、 `AIO` 相关 `API` 去实现 `Socket` 网路编程小实例; +以及使用 `Netty` 实现网络编程的小实例;然后说明一些使用 `Netty` 的好处。 + + +### `Linux` 系统 `IO` 模型介绍 +//TODO + +### `IO` 相关 `API` 介绍 +//TODO + +### `NIO` 相关 `API` 介绍 +//TODO + +### `AIO` 相关 `API` 介绍 +//TODO + diff --git a/io/Thread-CPU-Process.md b/io/Thread-CPU-Process.md new file mode 100644 index 0000000..d5ffa9d --- /dev/null +++ b/io/Thread-CPU-Process.md @@ -0,0 +1,2 @@ +# 多线程、进程、多核 CPU 详细介绍 +进程和线程的区别是什么?相信大家可能会经常会被问到这个问题;虽然看起来是一个简单的问题,但是都有可能是模棱两可的回答。下面我们从内核的角度来看看进程是什么? diff --git a/io/pom.xml b/io/pom.xml new file mode 100644 index 0000000..438b01a --- /dev/null +++ b/io/pom.xml @@ -0,0 +1,36 @@ + + + + javainterview + cn.tommyyang + 1.0-SNAPSHOT + + 4.0.0 + + io + + + + io.netty + netty-all + 5.0.0.Alpha2 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + \ No newline at end of file diff --git a/io/src/main/java/cn/tommyyang/aio/AcceptCompletionHandler.java b/io/src/main/java/cn/tommyyang/aio/AcceptCompletionHandler.java new file mode 100644 index 0000000..6b235ee --- /dev/null +++ b/io/src/main/java/cn/tommyyang/aio/AcceptCompletionHandler.java @@ -0,0 +1,24 @@ +package cn.tommyyang.aio; + +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; + +/** + * @author TommyYang on 2019年05月20日 + */ +public class AcceptCompletionHandler implements CompletionHandler { + + @Override + public void completed(AsynchronousSocketChannel result, AsyncTimeServerHandler attachment) { + attachment.asyncServerSocketChannel.accept(attachment, this); + ByteBuffer buffer = ByteBuffer.allocate(1024); + result.read(buffer, buffer, new ReadCompletionHandler(result)); + } + + @Override + public void failed(Throwable exc, AsyncTimeServerHandler attachment) { + exc.printStackTrace(); + attachment.latch.countDown(); + } +} diff --git a/io/src/main/java/cn/tommyyang/aio/AsyncTimeClientHandler.java b/io/src/main/java/cn/tommyyang/aio/AsyncTimeClientHandler.java new file mode 100644 index 0000000..cfe591a --- /dev/null +++ b/io/src/main/java/cn/tommyyang/aio/AsyncTimeClientHandler.java @@ -0,0 +1,110 @@ +package cn.tommyyang.aio; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; +import java.util.concurrent.CountDownLatch; + +/** + * @author TommyYang on 2019年05月21日 + */ +public class AsyncTimeClientHandler implements CompletionHandler, Runnable { + + private String host; + private int port; + private AsynchronousSocketChannel timeClient; + private CountDownLatch latch; + + public AsyncTimeClientHandler(String host, int port) { + this.host = host; + this.port = port; + try { + this.timeClient = AsynchronousSocketChannel.open(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void completed(Void result, AsyncTimeClientHandler attachment) { + byte[] req = "QUERY TIME ORDER".getBytes(); + ByteBuffer reqBuffer = ByteBuffer.allocate(req.length); + reqBuffer.put(req); + reqBuffer.flip(); + + this.timeClient.write(reqBuffer, reqBuffer, new CompletionHandler() { + @Override + public void completed(Integer result, ByteBuffer attachment) { + if (attachment.hasRemaining()){ + timeClient.write(attachment, attachment, this); + } else { + ByteBuffer respBuffer = ByteBuffer.allocate(1024); + timeClient.read(respBuffer, respBuffer, new CompletionHandler() { + @Override + public void completed(Integer result, ByteBuffer attachment) { + attachment.flip(); + byte[] respBytes = new byte[attachment.remaining()]; + attachment.get(respBytes); + try { + String respBody = new String(respBytes, "UTF-8"); + System.out.println("Now is:" + respBody); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + exc.printStackTrace(); + close(); + } + }); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + exc.printStackTrace(); + close(); + } + }); + } + + @Override + public void failed(Throwable exc, AsyncTimeClientHandler attachment) { + exc.printStackTrace(); + this.close(); + } + + @Override + public void run() { + this.latch = new CountDownLatch(1); + this.timeClient.connect(new InetSocketAddress(this.host, this.port), this, this); + + try { + this.latch.await();//让当前client执行线程一直处于挂起状态,直到当前线程执行完毕,latch 释放。 + } catch (InterruptedException e) { + e.printStackTrace(); + } + + try { + this.timeClient.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private void close(){ + try { + timeClient.close(); + } catch (IOException e) { + e.printStackTrace(); + } + latch.countDown(); + } + +} diff --git a/io/src/main/java/cn/tommyyang/aio/AsyncTimeServerHandler.java b/io/src/main/java/cn/tommyyang/aio/AsyncTimeServerHandler.java new file mode 100644 index 0000000..a5d97ff --- /dev/null +++ b/io/src/main/java/cn/tommyyang/aio/AsyncTimeServerHandler.java @@ -0,0 +1,42 @@ +package cn.tommyyang.aio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.AsynchronousServerSocketChannel; +import java.util.concurrent.CountDownLatch; + +/** + * @author TommyYang on 2019年05月20日 + */ +public class AsyncTimeServerHandler implements Runnable { + + private int port; + + CountDownLatch latch; + AsynchronousServerSocketChannel asyncServerSocketChannel; + + public AsyncTimeServerHandler(int port) { + this.port = port; + try { + this.asyncServerSocketChannel = AsynchronousServerSocketChannel.open(); + this.asyncServerSocketChannel.bind(new InetSocketAddress(this.port)); + System.out.println("The time server is start in port:" + port); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void run() { + this.latch = new CountDownLatch(1); + doAccept(); + try { + this.latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private void doAccept(){ + this.asyncServerSocketChannel.accept(this, new AcceptCompletionHandler()); + } +} diff --git a/io/src/main/java/cn/tommyyang/aio/ReadCompletionHandler.java b/io/src/main/java/cn/tommyyang/aio/ReadCompletionHandler.java new file mode 100644 index 0000000..a48ad07 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/aio/ReadCompletionHandler.java @@ -0,0 +1,75 @@ +package cn.tommyyang.aio; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; +import java.util.Date; + +/** + * @author TommyYang on 2019年05月20日 + */ +public class ReadCompletionHandler implements CompletionHandler { + + private AsynchronousSocketChannel channel; + + public ReadCompletionHandler(AsynchronousSocketChannel channel) { + if (this.channel == null){ + this.channel = channel; + } + } + + @Override + public void completed(Integer result, ByteBuffer attachment) { + attachment.flip(); + byte[] body = new byte[attachment.remaining()]; + attachment.get(body); + + try { + String req = new String(body, "utf-8"); + System.out.println("The Time Server receive order :" + req); + String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new Date().toString() + : "BAD ORDER"; + doWrite(currentTime); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + try { + this.channel.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void doWrite(String resp){ + if (resp != null && resp.trim().length()> 0){ + byte[] respBytes = resp.getBytes(); + ByteBuffer writeBuffer = ByteBuffer.allocate(respBytes.length); + writeBuffer.put(respBytes); + writeBuffer.flip(); + this.channel.write(writeBuffer, writeBuffer, new CompletionHandler() { + @Override + public void completed(Integer result, ByteBuffer attachment) { + if (attachment.hasRemaining()){ + channel.write(writeBuffer, writeBuffer, this); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + try { + channel.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + } + +} diff --git a/io/src/main/java/cn/tommyyang/aio/TimeClient.java b/io/src/main/java/cn/tommyyang/aio/TimeClient.java new file mode 100644 index 0000000..fea59cf --- /dev/null +++ b/io/src/main/java/cn/tommyyang/aio/TimeClient.java @@ -0,0 +1,17 @@ +package cn.tommyyang.aio; + +/** + * @author TommyYang on 2019年05月21日 + */ +public class TimeClient { + + public static void main(String[] args) { + int port = 8000; + if (args != null && args.length> 1) { + port = Integer.valueOf(args[0]); + } + for (int i = 0; i < 10; i++) + new Thread(new AsyncTimeClientHandler("127.0.0.1", port), "AIO-AsyncTimeClient-001").start(); + } + +} diff --git a/io/src/main/java/cn/tommyyang/aio/TimeServer.java b/io/src/main/java/cn/tommyyang/aio/TimeServer.java new file mode 100644 index 0000000..84f6899 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/aio/TimeServer.java @@ -0,0 +1,23 @@ +package cn.tommyyang.aio; + +/** + * @author TommyYang on 2019-05-21 + */ +public class TimeServer { + + public static void main(String[] args) { + int port = 8000; + if (args != null && args.length == 1){ + try { + port = Integer.valueOf(args[0]); + } catch (NumberFormatException e){ + e.printStackTrace(); + System.out.println("param is error, please set port of time server!!!"); + } + } + AsyncTimeServerHandler timeServerHandler = new AsyncTimeServerHandler(port); + + new Thread(timeServerHandler, "AIO-AsyncTimeServer-001").start(); + } + +} diff --git a/io/src/main/java/cn/tommyyang/file/BigFileWriter.java b/io/src/main/java/cn/tommyyang/file/BigFileWriter.java new file mode 100644 index 0000000..aaa1871 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/file/BigFileWriter.java @@ -0,0 +1,56 @@ +package cn.tommyyang.file; + +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; + +/** + * @Author : TommyYang + * @Time : 2019-09-09 16:36 + * @Software: IntelliJ IDEA + * @File : BigFileWriter.java + */ +public class BigFileWriter { + + private FileOutputStream outStream = null; + private BufferedWriter bfWriter = null; + + private String filePath; + + public BigFileWriter(String filePath) { + this.filePath = filePath; + open(); + } + + private void open() { + try { + outStream = new FileOutputStream(this.filePath, true); + bfWriter = new BufferedWriter(new OutputStreamWriter(outStream, "UTF-8")); + } catch (Exception e) { + System.out.println("get big file writer error"); + e.printStackTrace(); + } + } + + public void writeFile(String content) { + try { + bfWriter.write(content); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void close() { + try { + if (bfWriter != null) { + bfWriter.close(); + } + if (outStream != null) { + outStream.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/io/src/main/java/cn/tommyyang/file/BigFileWriter1.java b/io/src/main/java/cn/tommyyang/file/BigFileWriter1.java new file mode 100644 index 0000000..df268e0 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/file/BigFileWriter1.java @@ -0,0 +1,56 @@ +package cn.tommyyang.file; + +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; + +/** + * @Author : TommyYang + * @Time : 2019-09-09 18:15 + * @Software: IntelliJ IDEA + * @File : BigFileWriter1.java + */ +public class BigFileWriter1 { + + private FileOutputStream outStream = null; + private BufferedWriter bfWriter = null; + + private String filePath; + + public BigFileWriter1(String filePath) { + this.filePath = filePath; + open(); + } + + private void open() { + try { + outStream = new FileOutputStream(this.filePath, true); + bfWriter = new BufferedWriter(new OutputStreamWriter(outStream, "UTF-8")); + } catch (Exception e) { + System.out.println("get big file writer error"); + e.printStackTrace(); + } + } + + public synchronized void writeFile(String content) { + try { + bfWriter.write(content); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void close() { + try { + if (bfWriter != null) { + bfWriter.close(); + } + if (outStream != null) { + outStream.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/io/src/main/java/cn/tommyyang/file/FileTest.java b/io/src/main/java/cn/tommyyang/file/FileTest.java new file mode 100644 index 0000000..f637279 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/file/FileTest.java @@ -0,0 +1,41 @@ +package cn.tommyyang.file; + +import java.util.stream.IntStream; + +/** + * @Author : TommyYang + * @Time : 2019-09-09 23:30 + * @Software: IntelliJ IDEA + * @File : FileTest.java + */ +public class FileTest { + + public static void main(String[] args) { + new Thread(()-> { + long startTime = System.currentTimeMillis(); + BigFileWriter fileWriter = new BigFileWriter("/Users/tommy/Documents/aa.txt"); + IntStream.range(0, 500000).parallel().forEach(index -> fileWriter.writeFile("user:" + index + "\n")); + fileWriter.close(); + long endTime = System.currentTimeMillis(); + System.out.println("big file writer const:" + (endTime - startTime) + " ms"); + }).start(); + + new Thread(()-> { + long startTime = System.currentTimeMillis(); + BigFileWriter1 fileWriter = new BigFileWriter1("/Users/tommy/Documents/bb.txt"); + IntStream.range(0, 500000).parallel().forEach(index -> fileWriter.writeFile("user:" + index + "\n")); + fileWriter.close(); + long endTime = System.currentTimeMillis(); + System.out.println("big file writer 1 const:" + (endTime - startTime) + " ms"); + }).start(); + + + new Thread(()-> { + long startTime = System.currentTimeMillis(); + IntStream.range(0, 500000).parallel().forEach(index -> FileUtils.writeFile("/Users/tommy/Documents/cc.txt", "user:" + index + "\n")); + long endTime = System.currentTimeMillis(); + System.out.println("file utils const:" + (endTime - startTime) + " ms"); + }).start(); + } + +} diff --git a/io/src/main/java/cn/tommyyang/file/FileUtils.java b/io/src/main/java/cn/tommyyang/file/FileUtils.java new file mode 100644 index 0000000..45997fa --- /dev/null +++ b/io/src/main/java/cn/tommyyang/file/FileUtils.java @@ -0,0 +1,38 @@ +package cn.tommyyang.file; + +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; + +/** + * @Author : TommyYang + * @Time : 2019年09月09日 16:36 + * @Software: IntelliJ IDEA + * @File : FileUtils.java + */ +public class FileUtils { + + public static synchronized void writeFile(String filePath, String content) { + FileOutputStream outStream = null; + BufferedWriter bfWriter = null; + try { + outStream = new FileOutputStream(filePath, true); + bfWriter = new BufferedWriter(new OutputStreamWriter(outStream, "UTF-8")); + bfWriter.write(content); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (bfWriter != null) { + bfWriter.close(); + } + if (outStream != null) { + outStream.close(); + } + } catch (Exception e) { + + } + } + } + +} diff --git a/io/src/main/java/cn/tommyyang/nettynio/TimeClient.java b/io/src/main/java/cn/tommyyang/nettynio/TimeClient.java new file mode 100644 index 0000000..86ba305 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/nettynio/TimeClient.java @@ -0,0 +1,45 @@ +package cn.tommyyang.nettynio; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +/** + * @author TommyYang on 2019年05月21日 + */ +public class TimeClient { + + private void connect(String host, int port) throws Exception{ + //配置客户端NIO线程组 + EventLoopGroup group = new NioEventLoopGroup(); + + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(group) + .channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline().addLast(new TimeClientHandler()); + } + }); + + //发起异步连接操作 + ChannelFuture cf = bootstrap.connect(host, port).sync(); + + //等待 客户端链路关闭 + cf.channel().closeFuture().sync(); + } + + public static void main(String[] args) throws Exception { + int port = 8080; + + new TimeClient().connect("127.0.0.1", port); + } + +} diff --git a/io/src/main/java/cn/tommyyang/nettynio/TimeClientHandler.java b/io/src/main/java/cn/tommyyang/nettynio/TimeClientHandler.java new file mode 100644 index 0000000..d9c5084 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/nettynio/TimeClientHandler.java @@ -0,0 +1,44 @@ +package cn.tommyyang.nettynio; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; + +/** + * @author TommyYang on 2019年05月22日 + */ +public class TimeClientHandler extends ChannelHandlerAdapter { + + private final ByteBuf msg; + + public TimeClientHandler() { + byte[] req = "QUERY TIME ORDER".getBytes(); + this.msg = Unpooled.buffer(req.length); + this.msg.writeBytes(req); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ctx.writeAndFlush(this.msg); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ByteBuf resp = (ByteBuf)msg; + byte[] respBytes = new byte[resp.readableBytes()]; + resp.readBytes(respBytes); + + System.out.println("Now is:" + new String(respBytes, "UTF-8")); + + //查询完毕,释放资源,结束程序 + ctx.close(); + + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + //释放资源 + ctx.close(); + } +} diff --git a/io/src/main/java/cn/tommyyang/nettynio/TimeServer.java b/io/src/main/java/cn/tommyyang/nettynio/TimeServer.java new file mode 100644 index 0000000..2ba3615 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/nettynio/TimeServer.java @@ -0,0 +1,69 @@ +package cn.tommyyang.nettynio; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; + +/** + * @author TommyYang on 2019年05月21日 + */ +public class TimeServer { + + private void bind(int port) throws Exception{ + + EventLoopGroup bossGroup = new NioEventLoopGroup(); + EventLoopGroup workGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 1024) + .childHandler(new ChildChannelHandler()); + + //绑定端口,同步等待成功 + ChannelFuture cf = bootstrap.bind(port).sync(); + + System.out.println("Netty Nio server start-001"); + + //等待服务端监听端口关闭 + cf.channel().closeFuture().sync(); + } finally { + //优雅的退出 + bossGroup.shutdownGracefully(); + workGroup.shutdownGracefully(); + } + + } + + private class ChildChannelHandler extends ChannelInitializer{ + + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline().addLast(new TimeServerHandler()); + } + + } + + public static void main(String[] args) throws Exception { + int port = 8080; + + if (args != null && args.length> 0){ + try { + port = Integer.valueOf(args[0]); + } catch (NumberFormatException e){ + e.printStackTrace(); + } + + } + + new TimeServer().bind(port); + + } + +} diff --git a/io/src/main/java/cn/tommyyang/nettynio/TimeServerHandler.java b/io/src/main/java/cn/tommyyang/nettynio/TimeServerHandler.java new file mode 100644 index 0000000..c98d12f --- /dev/null +++ b/io/src/main/java/cn/tommyyang/nettynio/TimeServerHandler.java @@ -0,0 +1,38 @@ +package cn.tommyyang.nettynio; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; + +import java.util.Date; + +/** + * @author TommyYang on 2019年05月21日 + */ +public class TimeServerHandler extends ChannelHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ByteBuf buf = (ByteBuf)msg; + byte[] req = new byte[buf.readableBytes()]; + buf.readBytes(req);//读取buf中的数据到byte[] + + String body = new String(req, "UTF-8"); + String currentTime = body.equalsIgnoreCase("QUERY TIME ORDER") ? new Date().toString() : "BAD ORDER"; + + ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); + ctx.write(resp); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/io/src/main/java/cn/tommyyang/nio/MultiplexerTimeServer.java b/io/src/main/java/cn/tommyyang/nio/MultiplexerTimeServer.java new file mode 100644 index 0000000..b9b2c9b --- /dev/null +++ b/io/src/main/java/cn/tommyyang/nio/MultiplexerTimeServer.java @@ -0,0 +1,131 @@ +package cn.tommyyang.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.Date; +import java.util.Iterator; +import java.util.Set; + +/** + * @author TommyYang on 2019年05月09日 + */ +public class MultiplexerTimeServer implements Runnable{ + + private Selector selector; + + private ServerSocketChannel serverSocketChannel; + + private volatile boolean stop; + + /** + * 初始化多路复用器 + * + * @param port + * + * */ + public MultiplexerTimeServer(int port) { + try { + this.selector = Selector.open(); + this.serverSocketChannel = ServerSocketChannel.open(); + this.serverSocketChannel.configureBlocking(false); + this.serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024); + this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT); + System.out.println("The Multiplexer Time Server is start on port:" + port); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + } + + public void run() { + while (!stop){ + try { + this.selector.select(1000); + Set keys = this.selector.selectedKeys(); + Iterator itkeys = keys.iterator(); + while (itkeys.hasNext()){ + SelectionKey key = itkeys.next(); + itkeys.remove(); + try{ + handleDataFromClient(key); + }catch (IOException e){ + if (key != null){ + key.cancel(); + if (key.channel() != null){ + key.channel().close(); + } + } + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (this.selector != null){ + try { + this.selector.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void stop(){ + this.stop = true; + } + + private void handleDataFromClient(SelectionKey key) throws IOException { + if(!key.isValid()){ + return; + } + //处理新接入的消息 + if (key.isAcceptable()){ + ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); + SocketChannel sc = ssc.accept(); + sc.configureBlocking(false); + //添加新的connection到selector + sc.register(this.selector, SelectionKey.OP_READ); + } + + //read the data from client + if (key.isReadable()){ + SocketChannel sc = (SocketChannel) key.channel(); + ByteBuffer buf = ByteBuffer.allocate(1024); + int size = sc.read(buf); + if (size> 0){ + buf.flip(); + byte[] bytes = new byte[buf.remaining()]; + buf.get(bytes); + String body = new String(bytes, "UTF-8"); + String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) + ? new Date().toString() + : "ERROR ORDER"; + doWrite(sc, currentTime); + } else if (size < 0){ + //客户端链路关闭 + key.cancel(); + sc.close(); + } else { + //没有数据 不处理 + } + } + } + + private void doWrite(SocketChannel sc, String respone) throws IOException { + if (respone != null && respone.trim().length()> 0){ + byte[] respArr = respone.getBytes(); + ByteBuffer buf = ByteBuffer.allocate(respArr.length); + buf.put(respArr); + buf.flip(); + sc.write(buf); + } + } + +} diff --git a/io/src/main/java/cn/tommyyang/nio/TimeClient.java b/io/src/main/java/cn/tommyyang/nio/TimeClient.java new file mode 100644 index 0000000..32f6ac4 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/nio/TimeClient.java @@ -0,0 +1,20 @@ +package cn.tommyyang.nio; + +/** + * @author TommyYang on 2019年05月09日 + */ +public class TimeClient { + public static void main(String[] args) { + int port = 8000; + if (args != null && args.length == 1){ + try { + port = Integer.parseInt(args[0]); + } catch (Exception e){ + e.printStackTrace(); + System.out.println("param is error, please set port of time client!!!"); + } + } + for(int i = 0; i < 10; i++) + new Thread(new TimeClientHandler("127.0.0.1", port), "NIO-TimeClient-001").start(); + } +} diff --git a/io/src/main/java/cn/tommyyang/nio/TimeClientHandler.java b/io/src/main/java/cn/tommyyang/nio/TimeClientHandler.java new file mode 100644 index 0000000..ce608d7 --- /dev/null +++ b/io/src/main/java/cn/tommyyang/nio/TimeClientHandler.java @@ -0,0 +1,132 @@ +package cn.tommyyang.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; + +/** + * @author TommyYang on 2019-05-12 + */ +public class TimeClientHandler implements Runnable { + + private String host; + private int port; + private Selector selector; + private SocketChannel socketChannel; + private volatile boolean stop; + + public TimeClientHandler(String host, int port) { + this.host = host == null ? "127.0.0.1" : host; + this.port = port; + try { + this.selector = Selector.open(); + this.socketChannel = SocketChannel.open(); + this.socketChannel.configureBlocking(false); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + } + + public void run() { + try { + doConnect(); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + while (!stop) { + try { + this.selector.select(1000); + Set keys = this.selector.selectedKeys(); + Iterator itKeys = keys.iterator(); + while (itKeys.hasNext()) { + SelectionKey key = itKeys.next(); + itKeys.remove(); + try { + handleDataFromServer(key); + } catch (IOException e) { + if (key != null) { + key.cancel(); + if (key.channel() != null) { + key.channel().close(); + } + } + } + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + } + + if (this.selector != null) { + try { + this.selector.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void handleDataFromServer(SelectionKey key) throws IOException { + if (key.isValid()) { + SocketChannel sc = (SocketChannel) key.channel(); + //判断先前是否连接成功,如果构造器里面的socketChannel已经连接成功了, + // 则此处不会有connectable的操作select到 + if (key.isConnectable()) { + if (sc.finishConnect()) { + sc.register(this.selector, SelectionKey.OP_READ); + doWrite(sc); + } else { + //连接失败,程序退出 + System.out.println("connect failed!!!"); + System.exit(-1); + } + } + + // read the data from server + if (key.isReadable()) { + ByteBuffer readBuf = ByteBuffer.allocate(1024); + int readByteLen = sc.read(readBuf); + if (readByteLen> 0) { + readBuf.flip(); + byte[] readBytes = new byte[readBuf.remaining()]; + readBuf.get(readBytes); + String body = new String(readBytes, "UTF-8"); + System.out.println("Now is:" + body); + this.stop = true; + } + } + } + } + + private void doConnect() throws IOException { + //如果连接成功,则注册到多路复用器上,发送请求消息,读应答 + if (this.socketChannel.connect(new InetSocketAddress(this.host, this.port))) { + this.socketChannel.register(this.selector, SelectionKey.OP_READ); + doWrite(this.socketChannel); + } else { + //连接失败,则重新尝试connect, 在handleInput方法里面可以看到 + this.socketChannel.register(this.selector, SelectionKey.OP_CONNECT); + } + } + + private void doWrite(SocketChannel sc) throws IOException { + byte[] req = "QUERY TIME ORDER".getBytes(); + ByteBuffer writeBuffer = ByteBuffer.allocate(req.length); + writeBuffer.put(req); + writeBuffer.flip(); + sc.write(writeBuffer); + System.out.println("Send order 2 server succeed!!!"); +// while (!writeBuffer.hasRemaining()) { +// System.out.println("Send order 2 server succeed!!!"); +// } + } + +} diff --git a/io/src/main/java/cn/tommyyang/nio/TimeServer.java b/io/src/main/java/cn/tommyyang/nio/TimeServer.java new file mode 100644 index 0000000..078c8ac --- /dev/null +++ b/io/src/main/java/cn/tommyyang/nio/TimeServer.java @@ -0,0 +1,23 @@ +package cn.tommyyang.nio; + +/** + * @author TommyYang on 2019年05月09日 + */ +public class TimeServer { + + public static void main(String[] args) { + int port = 8000; + if (args != null && args.length == 1){ + try { + port = Integer.parseInt(args[0]); + } catch (Exception e){ + e.printStackTrace(); + System.out.println("param is error, please set port of time server!!!"); + } + } + + MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port); + new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start(); + } + +} diff --git a/javabase/README.md b/javabase/README.md new file mode 100644 index 0000000..16994e2 --- /dev/null +++ b/javabase/README.md @@ -0,0 +1,103 @@ +# Java 基础知识 + +## 基础 +### Integer类 +```java + +public class Test { + public static void main(String[] args){ + Integer a = new Integer(129); + Integer b = new Integer(129); + + // 1 + System.out.println(a == b); + + + Integer c = new Integer(127); + Integer d = new Integer(127); + // 2 + System.out.println(c == d); + } +} + +``` + +上述代码可以看出1处 输出 false; 2 处输出 true。 +从Integer类源码中,有一个内部类,代码如下: +```java + +private static class IntegerCache { + static final int low = -128; + static final int high; + static final Integer cache[]; + + static { + // high value may be configured by property + int h = 127; + String integerCacheHighPropValue = + sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); + if (integerCacheHighPropValue != null) { + try { + int i = parseInt(integerCacheHighPropValue); + i = Math.max(i, 127); + // Maximum array size is Integer.MAX_VALUE + h = Math.min(i, Integer.MAX_VALUE - (-low) -1); + } catch( NumberFormatException nfe) { + // If the property cannot be parsed into an int, ignore it. + } + } + high = h; + + cache = new Integer[(high - low) + 1]; + int j = low; + for(int k = 0; k < cache.length; k++) + cache[k] = new Integer(j++); + + // range [-128, 127] must be interned (JLS7 5.1.7) + assert IntegerCache.high>= 127; + } + + private IntegerCache() {} + } + +``` +从上述源码可以找出出现上述1处、2处结果的原因。 + + +### Calendar类 +> 添加天数:DAY_OF_MONTH、DAY_OF_YEAR、DAY_OF_WEEK、DAY_OF_WEEK_IN_MONTH的区别 + +就单纯的add操作结果都一样,因为都是将日期+1, 不管是月的日期中加1还是年的日期中加1。
+强行解释区别如下:
+- `DAY_OF_MONTH` 的主要作用是get(DAY_OF_MONTH),用来获得这一天在是这个月的第多少天。 +- `DAY_OF_YEAR` 的主要作用get(DAY_OF_YEAR),用来获得这一天在是这个年的第多少天。 +- 同样,`DAY_OF_WEEK`,用来获得当前日期是一周的第几天;`DAY_OF_WEEK_IN_MONTH`,用来获取 day 所在的周是这个月的第几周 + +### [String、StringBuilder和StringBuffer](stringbuilderandstringbuffer.md) +| 类名| 描述| 是否可| 线程安全性| +|:---- |:---- |:---- |:---- | +|String |字符串常量 | 不可变类 | 线程安全 | +|StringBuilder |字符串变量 | 可变类 | 线程不安全 | +|StringBuffer |字符串变量 | 可变类 | 线程安全 | + +### [关键字篇](keywords) +- [transient](keywords/transient.md) +- [volatile](keywords/volatile.md) + + +## [异常篇](exception.md) + +## [Java8 篇](java8) + +## :house_with_garden: [数据结构篇](datastructure) +- [BlockingQueue和BlockingDeque](datastructure/blockingqueueanddeque.md) + * Queue + * Deque + * LinkedList + * ArrayBlockingQueue + * LinkedBlockingQueue + * LinkedBlockingDeque + +## 多线程相关 +- [线程池](threadpool.md) + diff --git a/javabase/datastructure/README.md b/javabase/datastructure/README.md new file mode 100644 index 0000000..41f3057 --- /dev/null +++ b/javabase/datastructure/README.md @@ -0,0 +1,5 @@ +# 数据结构篇 + +## [BlockingQueue](blockingqueueanddeque.md) + +## [线程池相关](../threadpool.md) \ No newline at end of file diff --git a/javabase/datastructure/blockingqueueanddeque.md b/javabase/datastructure/blockingqueueanddeque.md new file mode 100644 index 0000000..fe77c4d --- /dev/null +++ b/javabase/datastructure/blockingqueueanddeque.md @@ -0,0 +1,849 @@ +# BlockingQueue 和 BlockingDeque 内部实现分析 + +## BlockingQueue 介绍 +`BlockingQueue` 继承自 `Queue` 接口,下面看看阻塞队列提供的接口; +```java +public interface BlockingQueue extends Queue { + /** + * 插入数据到队列尾部(如果立即可行且不会超过该队列的容量) + * 在成功时返回 true,如果此队列已满,则抛IllegalStateException。(与offer方法的区别) + */ + boolean add(E e); + + /** + * 插入数据到队列尾部,如果没有空间,直接返回false; + * 有空间直接插入,返回true。 + */ + boolean offer(E e); + + /** + * 插入数据到队列尾部,如果队列没有空间,一直阻塞; + * 有空间直接插入。 + */ + void put(E e) throws InterruptedException; + + /** + * 插入数据到队列尾部,如果没有额外的空间,等待一定的时间,有空间即插入,返回true, + * 到时间了,还是没有额外空间,返回false。 + */ + boolean offer(E e, long timeout, TimeUnit unit) + throws InterruptedException; + + /** + * 取出和删除队列中的头元素,如果没有数据,会一直阻塞到有数据 + */ + E take() throws InterruptedException; + + /** + * 取出和删除队列中的头元素,如果没有数据,需要会阻塞一定的时间,过期了还没有数据,返回null + */ + E poll(long timeout, TimeUnit unit) + throws InterruptedException; + + //除了上述方法还有继承自Queue接口的方法 + /** + * 取出和删除队列头元素,如果是空队列直接返回null。 + */ + E poll(); + + /** + * 取出但不删除头元素,该方法与peek方法的区别是当队列为空时会抛出NoSuchElementException异常 + */ + E element(); + + /** + * 取出但不删除头元素,空队列直接返回null + */ + E peek(); + + /** + * 返回队列总额外的空间 + */ + int remainingCapacity(); + + /** + * 删除队列中存在的元素 + */ + boolean remove(Object o); + + /** + * 判断队列中是否存在当前元素 + */ + boolean contains(Object o); + +} +``` + +- 插入方法 + +`add(E e)`: 添加成功返回true,失败抛IllegalStateException异常 + +`offer(E e)`: 成功返回 true,如果此队列已满,则返回 false。 + +`put(E e)`: 将元素插入此队列的尾部,如果该队列已满,则一直阻塞 + +- 删除方法 + +`remove(Object o)`: 移除指定元素,成功返回true,失败返回false + +`poll()`: 获取并移除此队列的头元素,若队列为空,则返回 null + +`take()`: 获取并移除此队列的头元素,若队列为空,则一直阻塞 + +- 检查方法 + +`peek()`: 获取但不移除此队列的头元素,没有元素则抛NoSuchElementException异常 + +`element()`: 获取但不移除此队列的头;若队列为空,则返回 null。 + +## ArrayBlockingQueue +ArrayBlockingQueue() 是一个用数组实现的有界阻塞队列,内部按先进先出的原则对元素进行排序; +其中 `put` 方法和 `take` 方法为添加和删除元素的阻塞方法。 + +ArrayBlockingQueue 实现的生产者消费者的 Demo,代码只是一个简单的 ArrayBlockingQueue 的 +使用,Consumer 消费者和 Producer 生产者通过 ArrayBlockingQueue 来获取(take)和添加(put) +数据。具体代码请访问:[ABQ demo](https://github.com/joyang1/JavaInterview/blob/master/datastructure/src/main/java/cn/tommyyang/queue/ArrayBlockingQueueDemo.java)。 + +ArrayBlockingQueue 内部的阻塞队列是通过 ReentrantLock 和 Condition 条件队列实现的, +所以 ArrayBlockingQueue 中的元素存在公平和非公平访问的区别,这是因为 ReentrantLock 里面存在公平锁和非公平锁的原因, +ReentrantLock 的具体分析会在 Lock 章节进行具体分析的; 对于 Lock 是公平锁的时候, +则被阻塞的队列可以按照阻塞的先后顺序访问队列,Lock 是非公平锁的时候, +阻塞的线程将进入争夺锁资源的过程中,谁先抢到锁就可以先执行,没有固定的先后顺序。 + +下面对 ArrayBlockingQueue 构造方法进行分析: +``` java +/** + * 创建一个具体容量的队列,默认是非公平队列 + */ +public ArrayBlockingQueue(int capacity) { + this(capacity, false); +} + +/** + * 创建一个具体容量、是否公平的队列 + */ +public ArrayBlockingQueue(int capacity, boolean fair) { + if (capacity <= 0) + throw new IllegalArgumentException(); + this.items = new Object[capacity]; + lock = new ReentrantLock(fair); + notEmpty = lock.newCondition(); + notFull = lock.newCondition(); +} + +``` + +ArrayBlockingQueue 除了实现上述 BlockingQueue 接口的方法,其他方法介绍如下: +``` java +//返回队列剩余容量 +public int remainingCapacity() + +// 判断队列中是否存在当前元素o +public boolean contains(Object o) + +// 返回一个按正确顺序,包含队列中所有元素的数组 +public Object[] toArray() + +// 返回一个按正确顺序,包含队列中所有元素的数组;数组的运行时类型是指定数组的运行时类型 +@SuppressWarnings("unchecked") +public T[] toArray(T[] a) + + +// 自动清空队列中的所有元素 +public void clear() + +// 移除队列中所有可用元素,并将他们加入到给定的 Collection 中 +public int drainTo(Collection c) + +// 从队列中最多移除指定数量的可用元素,并将他们加入到给定的 Collection 中 +public int drainTo(Collection c, int maxElements) + +// 返回此队列中按正确顺序进行迭代的,包含所有元素的迭代器 +public Iterator iterator() + +``` + +### ArrayBlockingQueue 源码和实现原理分析 +#### 内部成员变量分析 + +``` java +public class ArrayBlockingQueue extends AbstractQueue + implements BlockingQueue, java.io.Serializable { + + /** 存储数据的数组 */ + final Object[] items; + + /** 获取数据的索引,用于下次 take, poll, peek or remove 等方法 */ + int takeIndex; + + /** 添加元素的索引, 用于下次 put, offer, or add 方法 */ + int putIndex; + + /** 队列元素的个数 */ + int count; + + /* + * 并发控制使用任何教科书中的经典双条件算法 + */ + + /** 控制并发访问的锁 */ + final ReentrantLock lock; + + /** 非空条件对象,用于通知 take 方法中在等待获取数据的线程,队列中已有数据,可以执行获取操作 */ + private final Condition notEmpty; + + /** 未满条件对象,用于通知 put 方法中在等待添加数据的线程,队列未满,可以执行添加操作 */ + private final Condition notFull; + + /** 迭代器 */ + transient Itrs itrs = null; +} + +``` + +从上面成员变量中可以看出,内部使用数组对象 items 来存储所有的数据;通过同一个 ReentrantLock 来同时控制添加数据线程和移除数据线程的并发访问,这个与 LinkedBlockingQueue 有很大区别(下面会进行分析)。 + +对于 `notEmpty` 条件对象是用于存放等待调用(此时队列中没有数据) take 方法的线程,这些线程会加入到 `notEmpty` 条件对象的等待队列(单链表)中,同时当队列中有数据后会通过 `notEmpty` 条件对象唤醒等待队列(链表)中等待的线程(链表中第一个***non-null 且 status 为 Condition*** 的线程)去 take 数据。 + +对于 `notFull` 条件对象是用于存放等待调用(此时队列容量已满) put 方法的线程,这些线程会加入到 `notFull` 条件对象的等待队列(单链表)中,同时当队列中数据被消费后会通过 `notFull` 条件对象唤醒等待队列(链表)中等待的线程去 put 数据。takeIndex 表示的是下一个(take、poll、peek、remove)方法被调用时获取数组元素的索引,putIndex 表示的是下一个(put、offer、add)被调用时添加元素的索引。 + +数据出队、入队操作如下: + + +#### 添加(阻塞添加)的实现分析 +``` java +/** + * 在当前 put 位置插入数据,put 位置前进一位, + * 同时唤醒 notEmpty 条件对象等待队列(链表)中第一个可用线程去 take 数据。 + * 当然这一系列动作只有该线程获取锁的时候才能进行,即只有获取锁的线程 + * 才能执行 enqueue 操作。 + */ +// 元素统一入队操作 +private void enqueue(E x) { + // assert lock.getHoldCount() == 1; + // assert items[putIndex] == null; + final Object[] items = this.items; + items[putIndex] = x; // putIndex 位置添加数据 + //putIndex 进行自增,当达到数组长度的时候,putIndex 重头再来,即设置为0 + //为什么呢?下面会具体介绍 + if (++putIndex == items.length) + putIndex = 0; + count++; //元素个数自增 + notEmpty.signal(); //添加完数据后,说明数组中有数据了,所以可以唤醒 notEmpty 条件对象等待队列(链表)中第一个可用线程去 take 数据 +} + +// 添加数据,数组中元素已满时,直接返回 false。 +public boolean offer(E e) { + checkNotNull(e); + final ReentrantLock lock = this.lock; + // 获取锁,保证线程安全 + lock.lock(); + try { + // 当数组元素个数已满时,直接返回false + if (count == items.length) + return false; + else { + // 执行入队操作,enqueue 方法在上面分析了 + enqueue(e); + return true; + } + } finally { + // 释放锁,保证其他等待锁的线程可以获取到锁 + // 为什么放到 finally (避免死锁) + lock.unlock(); + } +} + +// add 方法其实就是调用了 offer 方法来实现, +// 与 offer 方法的区别就是 offer 方法数组满,抛出 IllegalStateException 异常。 +public boolean add(E e) { + if (offer(e)) + return true; + else + throw new IllegalStateException("Queue full"); +} + +``` + +offer 方法和 add 方法实现很简单,大家只需要知道其区别就好了;这里着重讲一下 enqueue 方法里面留下的疑问,为什么当 putIndex 到了数组最后一个元素之后,是重头再来,设置为0;首先,你要想到 ArrayBlockingQueue 整个入队和出队操作都是线程安全的,而且 ArrayBlockingQueue 也是先进先出的队列;所以想一想,是不是数据入队后,从第一个数组位置上开始添加数据,依次往后入队;数据出队也是从数组第一个位置出队,出队后该位置数据为空,依次出队,然后这些位置数据都为空;所以只要 count 的个数没有达到数组长度时,虽然 putIndex 达到了数组长度,说明数组前面的位置上已经有数据出队了,所以添加元素,是不是就从头开始就行了(想明白了其实就很简单了,哈哈)。因为我们有一个 count 成员变量来记录元素的个数,当队列已满时,put 操作是会阻塞,add 操作会抛出异常,offer 操作会直接返回false;因此我们也不用担心数据会覆盖。这个 putIndex 和 takeIndex 达到数据长度后都会重新设置为0,重头开始再获取数据,整个过程就是一个无限循环的过程。 通过分析,我们发现有添加操作是不是有两种场景,一个是直接往后添加,一个是达到数据长度后,需要重头再来, + +具体操作如下图: + + +下面看看阻塞添加方法(put) + +``` java +/** + * 插入数据到队列尾部,如果队列已满,阻塞等待空间 + */ +public void put(E e) throws InterruptedException { + checkNotNull(e); + final ReentrantLock lock = this.lock; + // 获取锁,期间线程可以打断,打断则不会添加 + lock.lockInterruptibly(); + try { + // 通过上述分析,我们通过 count 来判断数组中元素个数 + while (count == items.length) + notFull.await(); // 元素已满,线程挂起,线程加入 notFull 条件对象等待队列(链表)中,等待被唤醒 + enqueue(e); // 队列未满,直接执行入队操作 + } finally { + lock.unlock(); + } +} +``` + +通过源码分析,发现 offer, add 都是无阻塞添加方法,两者的具体区别在上面分析过了;而 put 方法确实是一个阻塞方法,当队列已满的时候,线程会挂起,然后将该线程加入到 notFull 条件对象的等待队列(链表)中;notFull 条件对象有两种情况,第一种是当队列已满,新来的 put 数据的线程会加入到其等待队列(链表)中,第二种情况是,当队列有空间时,会移除队列中的线程,移除成功同时唤醒 put 线程,加入到获取 lock 的等待队列(双链表)的尾部。 + +具体操作,如下图: + + +通过以上分析,ArrayBlockingQueue 的 offer、 add、 put 方法已经都详细分析完毕,希望大家可以对其有深入的了解。 + +#### 提取(阻塞提取)的实现分析 + +提取即移除数组中的元素,下面我们具体来分析 ArrayBlockingQueue 的提取数组中元素的操作。 + +同上分析,我们首先从 dequeue 方法分析开始。 + +``` java +/** + * 提取 takeIndex 位置上的元素, 然后 takeIndex 前进一位, + * 同时唤醒 notFull 等待队列(链表)中的第一个可用线程去 put 数据。 + * 这些操作都是在当前线程获取到锁的前提下进行的, + * 同时也说明了 dequeue 方法线程安全的。 + */ +private E dequeue() { + // assert lock.getHoldCount() == 1; + // assert items[takeIndex] != null; + final Object[] items = this.items; + @SuppressWarnings("unchecked") + E x = (E) items[takeIndex]; // 提取 takeIndex位置上的数据 + items[takeIndex] = null; // 同时清空数组在 takeIndex 位置上的数据 + // takeIndex 向前前进一位,如果前进后位置超过了数组的长度,则将其设置为0; + // 为什么设置为0,理由在 putIndex 设置为0的时候介绍过了,原因是一样的。 + if (++takeIndex == items.length) + takeIndex = 0; + count--; // 同时数组的元素个数进行减1 + if (itrs != null) + itrs.elementDequeued(); // 同时更新迭代器中的元素,迭代器的具体分析会在下面单独整理 + notFull.signal(); // 提取完数据后,说明数组中有空位,所以可以唤醒 notFull 条件对象的等待队列(链表)中的第一个可用线程去 put 数据 + return x; +} + +// 提取数据,数组中数据为空时,直接返回 null +public E poll() { + final ReentrantLock lock = this.lock; + lock.lock(); // 加锁,前面也分析过,要执行 dequeue操作时,当前线程必须获取锁,保证线程安全 + try { + return (count == 0) ? null : dequeue(); // 元素个数为0时,直接返回 null,不为0时,元素出队 + } finally { + // 释放锁,在 finally 中释放可以避免死锁 + lock.unlock(); + } +} + +``` + +上面 poll() 方法分析得很清晰了,内部通过 dequeue 删除队列头元素。下面分析下 peek 方法,与 poll 有较大的区别。 + +``` java + +// 返回数组上第 i 个元素 +final E itemAt(int i) { + return (E) items[i]; +} + +/** + * 通过代码可以看到,peek 是获取元素,而不是提取, 不会删除 takeIndex 位置上的数据。 + * 内部通过 itemAt 方法实现,而不是 dequeue 方法。 + */ +public E peek() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return itemAt(takeIndex); //当队列为空时,返回 null + } finally { + lock.unlock(); + } +} + +``` + +通过上述代码,可以看出 peek 和 poll 的区别,peek 是获取元素,不会删除 takeIndex 位置原有的数据,takeIndex 也不会向前前进一位。 + +下面来分析下阻塞提取 take 方法: + +``` java +// 从队列头部提取数据,队列中没有元素则阻塞,阻塞期间线程可中断 +public E take() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); //获取锁,期间线程可以打断,打断则不会提取 + try { + // 元素为0时,当有线程提取元素,则将该线程加入到 notEmpty 条件对象的等待队列中, + // 直到当队列中有数据之后,会唤醒该线程去提取数据。 + while (count == 0) + notEmpty.await(); + return dequeue(); // 若有数据,直接调用 dequeue 提取数据 + } finally { + lock.unlock(); + } +} +``` + +其实分析完阻塞添加 put 方法后,再来看 take 方法,发现也是非常简单的,队列中有元素,直接提取,没有元素则线程阻塞(可中断的阻塞),将该线程加入到 notEmpty 条件对象的等待队列中;等有新的 put 线程添加了数据,分析发现,会在 put 操作中唤醒 notEmpty 条件对象的等待队列中的 take 线程,去执行 take 操作。 + +具体操作如下图: + + +通过以上分析,我们把 poll、take 提取元素的方法分析了,也把 peek 获取元素的方法分析了,我们使用的时候,根据具体的场景使用具体的方法。 + +分析完提取方法后,我们来分析一下 ArrayBlockingQueue 中的删除元素的 remove 方法。 + +``` java +void removeAt(final int removeIndex) { + // assert lock.getHoldCount() == 1; + // assert items[removeIndex] != null; + // assert removeIndex>= 0 && removeIndex < items.length; + final Object[] items = this.items; + if (removeIndex == takeIndex) { + // removing front item; just advance + items[takeIndex] = null; + if (++takeIndex == items.length) + takeIndex = 0; + count--; + if (itrs != null) + itrs.elementDequeued(); + } else { + // an "interior" remove + + // slide over all others up through putIndex. + final int putIndex = this.putIndex; + for (int i = removeIndex;;) { + int next = i + 1; + if (next == items.length) + next = 0; + if (next != putIndex) { + items[i] = items[next]; + i = next; + } else { + items[i] = null; + this.putIndex = i; + break; + } + } + count--; + if (itrs != null) + itrs.removedAt(removeIndex); + } + notFull.signal(); +} + +public boolean remove(Object o) { + if (o == null) return false; + final Object[] items = this.items; + final ReentrantLock lock = this.lock; + lock.lock(); + try { + if (count> 0) { + final int putIndex = this.putIndex; + int i = takeIndex; + do { + if (o.equals(items[i])) { + removeAt(i); + return true; + } + if (++i == items.length) + i = 0; + } while (i != putIndex); + } + return false; + } finally { + lock.unlock(); + } +} +``` + +#### ArrayBlockingQueue 的迭代器分析 + +```java + +private class Itr implements Iterator { + /** Index to look for new nextItem; NONE at end */ + private int cursor; + + /** Element to be returned by next call to next(); null if none */ + private E nextItem; + + /** Index of nextItem; NONE if none, REMOVED if removed elsewhere */ + private int nextIndex; + + /** Last element returned; null if none or not detached. */ + private E lastItem; + + /** Index of lastItem, NONE if none, REMOVED if removed elsewhere */ + private int lastRet; + + /** Previous value of takeIndex, or DETACHED when detached */ + private int prevTakeIndex; + + /** Previous value of iters.cycles */ + private int prevCycles; + + /** Special index value indicating "not available" or "undefined" */ + private static final int NONE = -1; + + /** + * Special index value indicating "removed elsewhere", that is, + * removed by some operation other than a call to this.remove(). + */ + private static final int REMOVED = -2; + + /** Special value for prevTakeIndex indicating "detached mode" */ + private static final int DETACHED = -3; + + Itr() { + // assert lock.getHoldCount() == 0; + lastRet = NONE; + final ReentrantLock lock = ArrayBlockingQueue.this.lock; + lock.lock(); + try { + if (count == 0) { + // assert itrs == null; + cursor = NONE; + nextIndex = NONE; + prevTakeIndex = DETACHED; + } else { + final int takeIndex = ArrayBlockingQueue.this.takeIndex; + prevTakeIndex = takeIndex; + nextItem = itemAt(nextIndex = takeIndex); + cursor = incCursor(takeIndex); + if (itrs == null) { + itrs = new Itrs(this); + } else { + itrs.register(this); // in this order + itrs.doSomeSweeping(false); + } + prevCycles = itrs.cycles; + // assert takeIndex>= 0; + // assert prevTakeIndex == takeIndex; + // assert nextIndex>= 0; + // assert nextItem != null; + } + } finally { + lock.unlock(); + } + } + + boolean isDetached() { + // assert lock.getHoldCount() == 1; + return prevTakeIndex < 0; + } + + private int incCursor(int index) { + // assert lock.getHoldCount() == 1; + if (++index == items.length) + index = 0; + if (index == putIndex) + index = NONE; + return index; + } + + /** + * Returns true if index is invalidated by the given number of + * dequeues, starting from prevTakeIndex. + */ + private boolean invalidated(int index, int prevTakeIndex, + long dequeues, int length) { + if (index < 0) + return false; + int distance = index - prevTakeIndex; + if (distance < 0) + distance += length; + return dequeues> distance; + } + + /** + * Adjusts indices to incorporate all dequeues since the last + * operation on this iterator. Call only from iterating thread. + */ + private void incorporateDequeues() { + // assert lock.getHoldCount() == 1; + // assert itrs != null; + // assert !isDetached(); + // assert count> 0; + + final int cycles = itrs.cycles; + final int takeIndex = ArrayBlockingQueue.this.takeIndex; + final int prevCycles = this.prevCycles; + final int prevTakeIndex = this.prevTakeIndex; + + if (cycles != prevCycles || takeIndex != prevTakeIndex) { + final int len = items.length; + // how far takeIndex has advanced since the previous + // operation of this iterator + long dequeues = (cycles - prevCycles) * len + + (takeIndex - prevTakeIndex); + + // Check indices for invalidation + if (invalidated(lastRet, prevTakeIndex, dequeues, len)) + lastRet = REMOVED; + if (invalidated(nextIndex, prevTakeIndex, dequeues, len)) + nextIndex = REMOVED; + if (invalidated(cursor, prevTakeIndex, dequeues, len)) + cursor = takeIndex; + + if (cursor < 0 && nextIndex < 0 && lastRet < 0) + detach(); + else { + this.prevCycles = cycles; + this.prevTakeIndex = takeIndex; + } + } + } + + /** + * Called when itrs should stop tracking this iterator, either + * because there are no more indices to update (cursor < 0 && + * nextIndex < 0 && lastRet < 0) or as a special exception, when + * lastRet>= 0, because hasNext() is about to return false for the + * first time. Call only from iterating thread. + */ + private void detach() { + // Switch to detached mode + // assert lock.getHoldCount() == 1; + // assert cursor == NONE; + // assert nextIndex < 0; + // assert lastRet < 0 || nextItem == null; + // assert lastRet < 0 ^ lastItem != null; + if (prevTakeIndex>= 0) { + // assert itrs != null; + prevTakeIndex = DETACHED; + // try to unlink from itrs (but not too hard) + itrs.doSomeSweeping(true); + } + } + + /** + * For performance reasons, we would like not to acquire a lock in + * hasNext in the common case. To allow for this, we only access + * fields (i.e. nextItem) that are not modified by update operations + * triggered by queue modifications. + */ + public boolean hasNext() { + // assert lock.getHoldCount() == 0; + if (nextItem != null) + return true; + noNext(); + return false; + } + + private void noNext() { + final ReentrantLock lock = ArrayBlockingQueue.this.lock; + lock.lock(); + try { + // assert cursor == NONE; + // assert nextIndex == NONE; + if (!isDetached()) { + // assert lastRet>= 0; + incorporateDequeues(); // might update lastRet + if (lastRet>= 0) { + lastItem = itemAt(lastRet); + // assert lastItem != null; + detach(); + } + } + // assert isDetached(); + // assert lastRet < 0 ^ lastItem != null; + } finally { + lock.unlock(); + } + } + + public E next() { + // assert lock.getHoldCount() == 0; + final E x = nextItem; + if (x == null) + throw new NoSuchElementException(); + final ReentrantLock lock = ArrayBlockingQueue.this.lock; + lock.lock(); + try { + if (!isDetached()) + incorporateDequeues(); + // assert nextIndex != NONE; + // assert lastItem == null; + lastRet = nextIndex; + final int cursor = this.cursor; + if (cursor>= 0) { + nextItem = itemAt(nextIndex = cursor); + // assert nextItem != null; + this.cursor = incCursor(cursor); + } else { + nextIndex = NONE; + nextItem = null; + } + } finally { + lock.unlock(); + } + return x; + } + + public void remove() { + // assert lock.getHoldCount() == 0; + final ReentrantLock lock = ArrayBlockingQueue.this.lock; + lock.lock(); + try { + if (!isDetached()) + incorporateDequeues(); // might update lastRet or detach + final int lastRet = this.lastRet; + this.lastRet = NONE; + if (lastRet>= 0) { + if (!isDetached()) + removeAt(lastRet); + else { + final E lastItem = this.lastItem; + // assert lastItem != null; + this.lastItem = null; + if (itemAt(lastRet) == lastItem) + removeAt(lastRet); + } + } else if (lastRet == NONE) + throw new IllegalStateException(); + // else lastRet == REMOVED and the last returned element was + // previously asynchronously removed via an operation other + // than this.remove(), so nothing to do. + + if (cursor < 0 && nextIndex < 0) + detach(); + } finally { + lock.unlock(); + // assert lastRet == NONE; + // assert lastItem == null; + } + } + + /** + * Called to notify the iterator that the queue is empty, or that it + * has fallen hopelessly behind, so that it should abandon any + * further iteration, except possibly to return one more element + * from next(), as promised by returning true from hasNext(). + */ + void shutdown() { + // assert lock.getHoldCount() == 1; + cursor = NONE; + if (nextIndex>= 0) + nextIndex = REMOVED; + if (lastRet>= 0) { + lastRet = REMOVED; + lastItem = null; + } + prevTakeIndex = DETACHED; + // Don't set nextItem to null because we must continue to be + // able to return it on next(). + // + // Caller will unlink from itrs when convenient. + } + + private int distance(int index, int prevTakeIndex, int length) { + int distance = index - prevTakeIndex; + if (distance < 0) + distance += length; + return distance; + } + + /** + * Called whenever an interior remove (not at takeIndex) occurred. + * + * @return true if this iterator should be unlinked from itrs + */ + boolean removedAt(int removedIndex) { + // assert lock.getHoldCount() == 1; + if (isDetached()) + return true; + + final int cycles = itrs.cycles; + final int takeIndex = ArrayBlockingQueue.this.takeIndex; + final int prevCycles = this.prevCycles; + final int prevTakeIndex = this.prevTakeIndex; + final int len = items.length; + int cycleDiff = cycles - prevCycles; + if (removedIndex < takeIndex) + cycleDiff++; + final int removedDistance = + (cycleDiff * len) + (removedIndex - prevTakeIndex); + // assert removedDistance>= 0; + int cursor = this.cursor; + if (cursor>= 0) { + int x = distance(cursor, prevTakeIndex, len); + if (x == removedDistance) { + if (cursor == putIndex) + this.cursor = cursor = NONE; + } + else if (x> removedDistance) { + // assert cursor != prevTakeIndex; + this.cursor = cursor = dec(cursor); + } + } + int lastRet = this.lastRet; + if (lastRet>= 0) { + int x = distance(lastRet, prevTakeIndex, len); + if (x == removedDistance) + this.lastRet = lastRet = REMOVED; + else if (x> removedDistance) + this.lastRet = lastRet = dec(lastRet); + } + int nextIndex = this.nextIndex; + if (nextIndex>= 0) { + int x = distance(nextIndex, prevTakeIndex, len); + if (x == removedDistance) + this.nextIndex = nextIndex = REMOVED; + else if (x> removedDistance) + this.nextIndex = nextIndex = dec(nextIndex); + } + else if (cursor < 0 && nextIndex < 0 && lastRet < 0) { + this.prevTakeIndex = DETACHED; + return true; + } + return false; + } + + /** + * Called whenever takeIndex wraps around to zero. + * + * @return true if this iterator should be unlinked from itrs + */ + boolean takeIndexWrapped() { + // assert lock.getHoldCount() == 1; + if (isDetached()) + return true; + if (itrs.cycles - prevCycles> 1) { + // All the elements that existed at the time of the last + // operation are gone, so abandon further iteration. + shutdown(); + return true; + } + return false; + } + +// /** Uncomment for debugging. */ +// public String toString() { +// return ("cursor=" + cursor + " " + +// "nextIndex=" + nextIndex + " " + +// "lastRet=" + lastRet + " " + +// "nextItem=" + nextItem + " " + +// "lastItem=" + lastItem + " " + +// "prevCycles=" + prevCycles + " " + +// "prevTakeIndex=" + prevTakeIndex + " " + +// "size()=" + size() + " " + +// "remainingCapacity()=" + remainingCapacity()); +// } +} + +``` + +## LinkedBlockingQueue + + +## LinkedBlockingDeque + + +## ConcurrentLinkedQueue \ No newline at end of file diff --git a/javabase/datastructure/pom.xml b/javabase/datastructure/pom.xml new file mode 100644 index 0000000..09511d5 --- /dev/null +++ b/javabase/datastructure/pom.xml @@ -0,0 +1,27 @@ + + + + javainterview + cn.tommyyang + 1.0-SNAPSHOT + + 4.0.0 + + datastructure + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.17 + 1.17 + + + + + + + \ No newline at end of file diff --git a/javabase/datastructure/src/main/java/cn/tommyyang/RunTest.java b/javabase/datastructure/src/main/java/cn/tommyyang/RunTest.java new file mode 100644 index 0000000..553b8b2 --- /dev/null +++ b/javabase/datastructure/src/main/java/cn/tommyyang/RunTest.java @@ -0,0 +1,45 @@ +package cn.tommyyang; + +import cn.tommyyang.forkjoinpool.CountTask; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author TommyYang on 2019年04月12日 + */ +public class RunTest { + + public static void main(String[] args) throws ExecutionException, InterruptedException { +// runForkJoinPool(); + + Thread t = new Thread(()-> + System.out.println("aa") + ); + + t.start(); + t.start(); + } + + private static void runForkJoinPool() throws ExecutionException, InterruptedException { + ForkJoinPool forkJoinPool = new ForkJoinPool(5); + List arrs = new ArrayList(100000); + for (int i = 1; i <= 100000; i++) { + arrs.add(i); + } + + CountTask countTask = new CountTask(arrs, 0, arrs.size() - 1); + + Future r1 = forkJoinPool.submit(countTask); + System.out.println(r1.get()); + ExecutorService service = Executors.newWorkStealingPool(1); + service.execute(() -> System.out.println("aa")); +// ThreadPoolExecutor +// Math +// LinkedBlockingQueue +// HashMap + + } +} diff --git a/javabase/datastructure/src/main/java/cn/tommyyang/forkjoinpool/CountTask.java b/javabase/datastructure/src/main/java/cn/tommyyang/forkjoinpool/CountTask.java new file mode 100644 index 0000000..fb0d230 --- /dev/null +++ b/javabase/datastructure/src/main/java/cn/tommyyang/forkjoinpool/CountTask.java @@ -0,0 +1,54 @@ +package cn.tommyyang.forkjoinpool; + +import java.util.List; +import java.util.concurrent.RecursiveTask; + +/** + * @author TommyYang on 2019年04月23日 + */ +public class CountTask extends RecursiveTask { + + private final static int THRESHOLD = 10000; + + private List list; + private int start; + private int end; + + public CountTask(List list, int start, int end) { + this.list = list; + this.start = start; + this.end = end; + } + + @Override + protected Integer compute() { + int sum = 0; + + boolean canCompute = (this.end - this.start) <= THRESHOLD; + if (canCompute) { + System.out.println(this.start + "--" + this.end); + for (int i = this.start; i <= this.end; i++){ + sum += this.list.get(i); + } + System.out.println(sum); + } else { + int mid = (this.start + this.end) / 2; + CountTask leftTask = new CountTask(this.list, this.start, mid); + CountTask rightTask = new CountTask(this.list, mid + 1, this.end); + + leftTask.fork(); + rightTask.fork(); + + int leftResult = leftTask.join(); + int rightResult = rightTask.join(); + + sum = leftResult + rightResult; + } + + return sum; + } + + + + +} diff --git a/javabase/datastructure/src/main/java/cn/tommyyang/queue/ArrayBlockingQueueDemo.java b/javabase/datastructure/src/main/java/cn/tommyyang/queue/ArrayBlockingQueueDemo.java new file mode 100644 index 0000000..8d358f3 --- /dev/null +++ b/javabase/datastructure/src/main/java/cn/tommyyang/queue/ArrayBlockingQueueDemo.java @@ -0,0 +1,88 @@ +package cn.tommyyang.queue; + +/** + * @Author : TommyYang + * @Time : 2019-06-13 18:23 + * @Software: IntelliJ IDEA + * @File : ArrayBlockingQueueDemo.java + */ + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * 使用 ArrayBlockingQueue 实现的生产者消费者简单模型 + */ +public class ArrayBlockingQueueDemo { + + private final static ExecutorService THREAD_POOL = Executors.newFixedThreadPool(4); + private final static ArrayBlockingQueue QUEUE = new ArrayBlockingQueue(1); + + public static void main(String[] args) { + THREAD_POOL.execute(new Producer(QUEUE)); + THREAD_POOL.execute(new Consumer(QUEUE)); + THREAD_POOL.execute(new Producer(QUEUE)); + THREAD_POOL.execute(new Consumer(QUEUE)); + THREAD_POOL.shutdown(); + } +} + +class Data { + +} + +class Producer implements Runnable { + + private final ArrayBlockingQueue mAbq; + + public Producer(ArrayBlockingQueue mAbq) { + this.mAbq = mAbq; + } + + @Override + public void run() { + for (int i = 0; i < 10; i++) { + produce(); + } + + } + + private void produce() { + try { + Data data = new Data(); + this.mAbq.put(data); + System.out.println("生产了数据@" + data); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} + +class Consumer implements Runnable { + + private final ArrayBlockingQueue mAbq; + + public Consumer(ArrayBlockingQueue mAbq) { + this.mAbq = mAbq; + } + + @Override + public void run() { + for (int i = 0; i < 10; i++) { + consumer(); + } + } + + private void consumer() { + try { + Data data = this.mAbq.take(); + System.out.println("消费数据-" + data); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} + + diff --git a/javabase/datastructure/src/main/java/cn/tommyyang/string/StringTest.java b/javabase/datastructure/src/main/java/cn/tommyyang/string/StringTest.java new file mode 100644 index 0000000..86c1bc0 --- /dev/null +++ b/javabase/datastructure/src/main/java/cn/tommyyang/string/StringTest.java @@ -0,0 +1,26 @@ +package cn.tommyyang.string; + +/** + * @Author : TommyYang + * @Time : 2019-06-04 11:12 + * @Software: IntelliJ IDEA + * @File : StringTest.java + */ +public class StringTest { + + public static void main(String[] args) { + String a = "fsff"; + a.replace('s', '_'); + + String b = new String("fsff"); + b.replace('s', '_'); + + StringBuilder sb = new StringBuilder("fsff"); + sb.replace(1, 2, "_").toString().replace('_', 's'); + System.out.println(a); + System.out.println(b); + System.out.println(sb.toString()); + //通过以上例子发现 String 是不可变类, StringBuilder 是可变类 + } + +} diff --git a/javabase/exception.md b/javabase/exception.md new file mode 100644 index 0000000..47cbae6 --- /dev/null +++ b/javabase/exception.md @@ -0,0 +1,157 @@ +# 异常介绍 +异常就是有异于常态,和正常情况不一样,有错误出现。在java中,阻止当前方法或作用域的情况,称之为异常。 + +## 异常分类 +![异常分类图](https://cdn.jsdelivr.net/gh/filess/img6@main/2021/03/27/1616860311636-a2466f2e-6bb2-437e-bd71-b2124af3fd89.png) + +## Exception具体实现 +在系统开发中,平时经常需要使用的两种异常,一种是需要检查(checked)的,一种是不需要检查(unchecked)的。 +那为什么需要两种异常呢? +- 用来区分告警的优先级。系统异常优先级高,因为说明系统服务、代码存在问题。 + - 业务异常,是已知的,因为其他客观因素导致的,比如用户输入的身份证格式有问题、用户购买商品时金额不足等。 + - 系统异常,是未知的,不知道啥时候会发生,如果发生了说明系统本身或者系统上下游存在问题,需要立马告警出来,让相关开发者感知到;以便发现问题和后续优化问题。比如:系统上下游服务抖动、请求超时、请求参数存在问题等。 +- 使代码更清洁,该处理(checked)的异常内部处理掉,无法处理(unchecked)的异常告警出来。 + +往往对于开发者来说,比较难区分,何为系统系统,何为业务异常。其中系统异常是unchecked的,业务异常是checked。 + +### RuntimeException +`RuntimeException`是在Java虚拟机的正常操作期间可以抛出的那些异常的超类,是Exception的子类,是Exception中unchecked子类的超类。 +比如系统上下游抖动、请求超时等,是允许在系统运行期间抛出的,所以该类异常应该继承自`RuntimeException`;且无需检查(unchecked)。 +所以系统异常应该继承自`RuntimeException`。 + +开发时具体实现: + +```java + +/** + * @Author : TommyYang + * @Time : 2021-03-27 12:40 + * @Software: IntelliJ IDEA + * @File : SystemException.java + */ +public class SystemException extends RuntimeException { + + private String code; + + public SystemException(String code) { + this.code = code; + } + + public SystemException(String message, String code) { + super(message); + this.code = code; + } + + @Override + public String toString() { + return "SystemException{" + + "code='" + code + '\'' + + '}'; + } + +} + +``` + +### Exception +异常类和任何不是RuntimeException的子类的子类都是检查异常。 检查的异常需要在方法或构造函数的throws子句中声明,如果它们可以通过执行方法或构造函数抛出,并在方法或构造函数边界之外传播。 +比如用户输入的身份证格式有问题、用户购买商品时金额不足等,这些是在开发系统的时候,就会已经的会出现这样的问题,这类异常是应该内部处理(checked)掉,而不应该告警出来。 +所以业务异常应该继承自`Exception`,且需要检查(checked)。 + +开发时具体实现: + +```java + +/** + * @Author : TommyYang + * @Time : 2021-03-27 12:40 + * @Software: IntelliJ IDEA + * @File : BusinessException.java + */ +public class BusinessException extends Exception { + + private String code; + + public BusinessException(String code) { + this.code = code; + } + + public BusinessException(String message, String code) { + super(message); + this.code = code; + } + + @Override + public String toString() { + return "BusinessException{" + + "code='" + code + '\'' + + '}'; + } +} + +``` + +### 测试 + +```java + +/** + * @Author : TommyYang + * @Time : 2021-03-27 13:19 + * @Software: IntelliJ IDEA + * @File : ExceptionTest.java + */ +public class ExceptionTest { + + /** + * 测试checked异常 + */ + @Test + public void testException() { + try { + throwsBusinessException(); + } catch (BusinessException e) { + System.out.println("这是一个业务异常,内部处理掉" + e.toString()); + } + } + + /** + * 测试unchecked异常 + */ + @Test + public void testRuntimeException() { + throwsSystemException(); + } + + /** + * 抛出业务异常(checked) + * 所以需要throw出去,让外部调用方感知到,这是一个checked的异常,是已经问题 + */ + private void throwsBusinessException() throws BusinessException { + throw new BusinessException("403"); + } + + /** + * 抛出系统异常(unchecked) + */ + private void throwsSystemException() { + throw new SystemException("400"); + } + +} + +``` + +## Error介绍 +`Error`表示严重的问题,合理的应用程序不应该试图捕获。 大多数这样的错误是异常情况。 ThreadDeath错误虽然是"正常"的条件,但也是Error一个子类,因为大多数应用程序不应该试图抓住它。 + +`Error`是由虚拟机生成并抛出,大多数错误与代码开发者所执行的操作无关。 +常见的Error,比如Java虚拟机运行错误(VirtualMachineError);当JVM执行操作所需的内存资源不够时,将出现OutOfMemoryError;当这些异常发生时,JVM一般会选择线程终止。 +还有部分Error是发生在虚拟机试图执行应用时,比如类定义错误(NoClassDefFoundError)、链接错误(LinkageError);这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。 +对于合理设计的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。 +在Java中,错误通常是使用Error的子类描述。 + + + + + diff --git a/javabase/img/exception_category.png b/javabase/img/exception_category.png new file mode 100644 index 0000000..e04bca1 Binary files /dev/null and b/javabase/img/exception_category.png differ diff --git a/javabase/java8/README.md b/javabase/java8/README.md new file mode 100644 index 0000000..da8a91d --- /dev/null +++ b/javabase/java8/README.md @@ -0,0 +1,96 @@ +# Java8 +分享 Java8 的新特性。 + +## 行为参数化(函数式接口) + +1. 筛选某种颜色的苹果 +2. 筛选重量大于某个值的苹果 + +**Java8 之前的代码** + +```java + +public class Apple { + + public static List filterApplesByColor(List inventory, String color) { + List result = new ArrayList(); + for (Apple apple: inventory){ + if ( apple.getColor().equals(color) ) { + result.add(apple); + } + } + return result; + } + + public static List filterApplesByWeight(List inventory, int weight) { + List result = new ArrayList(); + for (Apple apple: inventory){ + if ( apple.getWeight()> weight ){ + result.add(apple); + } + } + return result; + } +} + +``` + +**Java8 的代码简化** + +```java + +@FunctionalInterface +public interface ApplePredicate{ + boolean test (Apple apple); +} + +public class ProgressDemo { + + public static List processApple(List appleList, ApplePredicate p) { + List res = new ArrayList(); + for (Apple apple : appleList) { + if (p.test(apple)) { + res.add(apple); + } + } + return res; + } + +} + + +``` + +## Stream API + +1. stream() + +2. parallelStream() + + 非线程安全的; + 默认线程池的数量就是处理器的数量,特殊场景下可以使用系统属性: + -Djava.util.concurrent.ForkJoinPool.common.parallelism={N} 调整 + +## Lambada 表达式(链式编程) + +`基本语法` + +1. (parameters) -> expression +2. (parameters) -> { statements; } + +## 默认方法 +实现有 default 方法的接口, 可以重写 default 方法,不重写则默认使用 interface 中的默认方法。 +接口有默认方法之后,个人觉得接口和抽象类之间的区别更小了。 + +```java + +public interface DefaultInterface { + + void test(); + + default String getName() { + return "default"; + } +} + +``` \ No newline at end of file diff --git a/javabase/java8/pom.xml b/javabase/java8/pom.xml new file mode 100644 index 0000000..b0be4ac --- /dev/null +++ b/javabase/java8/pom.xml @@ -0,0 +1,28 @@ + + + + javainterview + cn.tommyyang + 1.0-SNAPSHOT + + 4.0.0 + + javabase + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + \ No newline at end of file diff --git a/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ApplePredicate.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ApplePredicate.java new file mode 100644 index 0000000..b4dfd0c --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ApplePredicate.java @@ -0,0 +1,14 @@ +package cn.tommyyang.functioninterface; + +import cn.tommyyang.model.Apple; + +/** + * @Author : TommyYang + * @Time : 2019年08月08日 14:44 + * @Software: IntelliJ IDEA + * @File : ApplePredicate.java + */ + +public interface ApplePredicate { + boolean test (Apple apple); +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/functioninterface/DefaultInterface.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/DefaultInterface.java new file mode 100644 index 0000000..2ef2e4e --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/DefaultInterface.java @@ -0,0 +1,16 @@ +package cn.tommyyang.functioninterface; + +/** + * @Author : TommyYang + * @Time : 2019年08月08日 16:16 + * @Software: IntelliJ IDEA + * @File : DefaultInterface.java + */ +public interface DefaultInterface { + + void test(); + + default String getName() { + return "default"; + } +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/functioninterface/Demo.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/Demo.java new file mode 100644 index 0000000..74bc9b3 --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/Demo.java @@ -0,0 +1,19 @@ +package cn.tommyyang.functioninterface; + +/** + * @Author : TommyYang + * @Time : 2019年08月08日 16:17 + * @Software: IntelliJ IDEA + * @File : Demo.java + */ +public class Demo implements DefaultInterface { + @Override + public void test() { + System.out.println("test"); + } + +// @Override +// public String getName() { +// return "demo"; +// } +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/functioninterface/Predicate.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/Predicate.java new file mode 100644 index 0000000..4146d68 --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/Predicate.java @@ -0,0 +1,27 @@ +package cn.tommyyang.functioninterface; + +/** + * @Author : TommyYang + * @Time : 2019年08月08日 15:05 + * @Software: IntelliJ IDEA + * @File : Predicate.java + */ + +@FunctionalInterface +public interface Predicate { + + boolean test(T t); + + default Predicate or(Predicate other) { + return t -> test(t) || other.test(t); + } + + default Predicate and(Predicate other) { + return t -> test(t) && other.test(t); + } + + default Predicate negate() { + return t -> !test(t); + } + +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java new file mode 100644 index 0000000..81a340d --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java @@ -0,0 +1,123 @@ +package cn.tommyyang.functioninterface; + +import cn.tommyyang.model.Apple; +import cn.tommyyang.model.Fruit; +import cn.tommyyang.model.Pear; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * @Author : TommyYang + * @Time : 2019年08月08日 14:43 + * @Software: IntelliJ IDEA + * @File : ProgressDemo.java + */ +public class ProgressDemo { + + public static List processApple(List appleList, ApplePredicate p) { + List res = new ArrayList(); + for (Apple apple : appleList) { + if (p.test(apple)) { + res.add(apple); + } + } + return res; + } + + private static final List apples; + private static final List pears; + + static { + apples = Arrays.asList(new Apple("red", 150), + new Apple("green", 250), new Apple("red", 350), + new Apple("red", 300)); + pears = Arrays.asList(new Pear("red", 150), + new Pear("green", 250), new Pear("red", 350), + new Pear("red", 300)); + } + + public static void main(String[] args) { + IntStream.range(0, 10).forEach(System.out::println); +// Collection + +// test1(); + +// test2(); + +// test3(); + +// test3_(); + +// test4(); + +// test5(); + + } + + // lambada + public static void test1() { + + List appleList1 = processApple(apples, new ApplePredicate() { + @Override + public boolean test(Apple apple) { + return apple.getWeight()>= 300 && apple.getColor().equals("red"); + } + }); + + ApplePredicate predicate = (Apple apple) -> apple.getWeight()>= 300 && apple.getColor().equals("red"); + List appleList2 = processApple(apples, predicate); +// List appleList2 = processApple(apples, apple -> apple.getWeight()>= 300 && apple.getColor().equals("red")); + appleList2.stream().forEach(System.out::println); + } + + // function interface + public static void test2() { + + Predicate predicate1 = (Apple apple) -> apple.getWeight()>= 300 && apple.getColor().equals("red"); + Predicate predicate2 = (Pear pear) -> pear.getWeight()>= 300 && pear.getColor().equals("red"); + + new Fruit().process(apples, predicate1).stream().forEach(System.out::println); + new Fruit().process(pears, predicate2).stream().forEach(System.out::println); + } + + // default method + public static void test3_() { + System.out.println(new Demo().getName()); + } + + // default method + public static void test3() { + Predicate predicate1 = (Apple apple) -> apple.getWeight()>= 300 || apple.getColor().equals("red"); + Predicate predicate2 = (Pear pear) -> pear.getWeight()>= 300 || pear.getColor().equals("red"); + + Predicate predicate3 = predicate1.negate(); + Predicate predicate4 = predicate2.negate(); + + new Fruit().process(apples, predicate3).stream().forEach(System.out::println); + new Fruit().process(pears, predicate4).stream().forEach(System.out::println); + + } + + // stream api + public static void test4() { + apples.stream().filter(apple -> apple.getWeight()>= 300 && apple.getColor().equals("red")). + forEach(System.out::println); + + java.util.function.Predicate predicate1 = (Apple apple) -> apple.getWeight()>= 300 || apple.getColor().equals("red"); + + apples.stream().filter(predicate1).forEach(System.out::println); + apples.stream().filter(predicate1.negate()).forEach(System.out::println); + + } + + // group by + public static void test5() { + Map maps = apples.stream(). + collect(Collectors.groupingBy(Apple::getColor, Collectors.counting())); + + maps.entrySet().stream().forEach(System.out::println); + } + +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/model/Apple.java b/javabase/java8/src/main/java/cn/tommyyang/model/Apple.java new file mode 100644 index 0000000..c00b885 --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/model/Apple.java @@ -0,0 +1,34 @@ +package cn.tommyyang.model; + +/** + * @Author : TommyYang + * @Time : 2019年08月08日 14:44 + * @Software: IntelliJ IDEA + * @File : Apple.java + */ +public class Apple { + + private String color; + private int weight; + + public Apple(String color, int weight) { + this.color = color; + this.weight = weight; + } + + public String getColor() { + return color; + } + + public int getWeight() { + return weight; + } + + @Override + public String toString() { + return "Apple{" + + "color='" + color + '\'' + + ", weight=" + weight + + '}'; + } +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/model/Fruit.java b/javabase/java8/src/main/java/cn/tommyyang/model/Fruit.java new file mode 100644 index 0000000..44106c4 --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/model/Fruit.java @@ -0,0 +1,27 @@ +package cn.tommyyang.model; + +import cn.tommyyang.functioninterface.Predicate; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author : TommyYang + * @Time : 2019年08月08日 15:32 + * @Software: IntelliJ IDEA + * @File : Fruit.java + */ + +public class Fruit { + + public List process(List ts, Predicate p) { + List res = new ArrayList(); + for (T t : ts) { + if (p.test(t)) { + res.add(t); + } + } + return res; + } + +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/model/Pear.java b/javabase/java8/src/main/java/cn/tommyyang/model/Pear.java new file mode 100644 index 0000000..66d1961 --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/model/Pear.java @@ -0,0 +1,34 @@ +package cn.tommyyang.model; + +/** + * @Author : TommyYang + * @Time : 2019年08月08日 15:28 + * @Software: IntelliJ IDEA + * @File : Pear.java + */ +public class Pear { + + private String color; + private int weight; + + public Pear(String color, int weight) { + this.color = color; + this.weight = weight; + } + + public String getColor() { + return color; + } + + public int getWeight() { + return weight; + } + + @Override + public String toString() { + return "Pear{" + + "color='" + color + '\'' + + ", weight=" + weight + + '}'; + } +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/streamapi/CountService.java b/javabase/java8/src/main/java/cn/tommyyang/streamapi/CountService.java new file mode 100644 index 0000000..cae69f1 --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/streamapi/CountService.java @@ -0,0 +1,32 @@ +package cn.tommyyang.streamapi; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * @Author : TommyYang + * @Time : 2019年08月06日 16:58 + * @Software: IntelliJ IDEA + * @File : CountService.java + */ +public class CountService { + + private long a; + private AtomicLong atomicLong = new AtomicLong(0); + + public void count(int b) { + this.a = a + b; + } + + public void atomicCount(int b) { + this.atomicLong.addAndGet(b); + } + + public long getA() { + return a; + } + + public long getCount() { + return this.atomicLong.get(); + } + +} diff --git a/javabase/java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java b/javabase/java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java new file mode 100644 index 0000000..5a1e8fd --- /dev/null +++ b/javabase/java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java @@ -0,0 +1,127 @@ +package cn.tommyyang.streamapi; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +/** + * @Author : TommyYang + * @Time : 2019年08月06日 16:56 + * @Software: IntelliJ IDEA + * @File : StreamAPI.java + */ +public class StreamAPI { + + private static List addItems; + private static Stream stream; + private static Stream parallelStream; + + static { + addItems = new ArrayList(10); + for (int i = 1; i <= 10; i++) { + addItems.add(i); + } + stream = addItems.stream(); + parallelStream = addItems.parallelStream(); + } + + public static void main(String[] args) { +// testDisplay(); +// testAdd(); + streamDisplay(); + } + + public static void testAdd() { + long t1 = System.currentTimeMillis(); + streamAdd(); + long t2 = System.currentTimeMillis(); + parallelStreamAdd(); + long t3 = System.currentTimeMillis(); + defaultAdd(); + long t4 = System.currentTimeMillis(); + + System.out.println("streamAdd time used:" + (t2 - t1)); + System.out.println("parallelStreamAdd time used:" + (t3 - t2)); + System.out.println("defaultAdd time used:" + (t4 - t3)); + } + + public static void testDisplay() { + long t1 = System.currentTimeMillis(); + streamDisplay(); + long t2 = System.currentTimeMillis(); + parallelStreamDisplay(); + long t3 = System.currentTimeMillis(); + defaultDisplay(); + long t4 = System.currentTimeMillis(); + + System.out.println("stream time used:" + (t2 - t1)); + System.out.println("parallelStream time used:" + (t3 - t2)); + System.out.println("default time used:" + (t4 - t3)); + } + + public static void streamAdd() { + CountService countService = new CountService(); + stream.forEach(countService::atomicCount); + + System.out.println(countService.getCount()); + } + + public static void parallelStreamAdd() { + CountService countService = new CountService(); + parallelStream.forEach(countService::atomicCount); + + System.out.println(countService.getCount()); + } + + public static void defaultAdd() { + CountService countService = new CountService(); + for (int i : addItems) { + countService.atomicCount(i); + } + + System.out.println(countService.getCount()); + } + + public static void streamDisplay() { +// stream.forEach(System.out::println); +// stream.forEach(item -> { +// try { +// Thread.sleep(500); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// System.out.println(item); +// }); + + addItems.forEach(System.out::println); + + System.out.println("stream display end ------"); + } + + public static void parallelStreamDisplay() { + parallelStream.forEach(item -> { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(item); + }); + + System.out.println("parallel stream display end ------"); + } + + public static void defaultDisplay() { + for (int i : addItems) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(i); + } + + System.out.println("end--------"); + } + +} diff --git a/KeyWords/README.md b/javabase/keywords/README.md similarity index 79% rename from KeyWords/README.md rename to javabase/keywords/README.md index c74f0f9..78052f2 100644 --- a/KeyWords/README.md +++ b/javabase/keywords/README.md @@ -1,4 +1,4 @@ -# 列表 +# 关键字篇 1. [transient](transient.md) 2. [volatile](volatile.md) diff --git a/KeyWords/pom.xml b/javabase/keywords/pom.xml similarity index 100% rename from KeyWords/pom.xml rename to javabase/keywords/pom.xml diff --git a/KeyWords/res/transient.png b/javabase/keywords/res/transient.png similarity index 100% rename from KeyWords/res/transient.png rename to javabase/keywords/res/transient.png diff --git a/KeyWords/res/transienttest.txt b/javabase/keywords/res/transienttest.txt similarity index 100% rename from KeyWords/res/transienttest.txt rename to javabase/keywords/res/transienttest.txt diff --git a/KeyWords/src/main/java/transientkey/TransientTest.java b/javabase/keywords/src/main/java/transientkey/TransientTest.java similarity index 100% rename from KeyWords/src/main/java/transientkey/TransientTest.java rename to javabase/keywords/src/main/java/transientkey/TransientTest.java diff --git a/javabase/keywords/src/main/java/volatilekey/ordering/AThread.java b/javabase/keywords/src/main/java/volatilekey/ordering/AThread.java new file mode 100644 index 0000000..4e8bf97 --- /dev/null +++ b/javabase/keywords/src/main/java/volatilekey/ordering/AThread.java @@ -0,0 +1,16 @@ +package volatilekey.ordering; + +/** + * @author TommyYang on 2019年1月30日 + */ +public class AThread implements Runnable { + + public void run() { +// Model.b = 4; +// Model.c = 5; +// Model.d = 6; + + Model.a = 11; + } + +} diff --git a/javabase/keywords/src/main/java/volatilekey/ordering/BThread.java b/javabase/keywords/src/main/java/volatilekey/ordering/BThread.java new file mode 100644 index 0000000..f2800d6 --- /dev/null +++ b/javabase/keywords/src/main/java/volatilekey/ordering/BThread.java @@ -0,0 +1,19 @@ +package volatilekey.ordering; + +/** + * @author TommyYang on 2019年1月30日 + */ +public class BThread implements Runnable { + + public void run() { + + System.out.println(Model.a); +// System.out.println(Model.b); +// System.out.println(Model.c); +// System.out.println(Model.d); + + + + } + +} diff --git a/javabase/keywords/src/main/java/volatilekey/ordering/Model.java b/javabase/keywords/src/main/java/volatilekey/ordering/Model.java new file mode 100644 index 0000000..3299d02 --- /dev/null +++ b/javabase/keywords/src/main/java/volatilekey/ordering/Model.java @@ -0,0 +1,13 @@ +package volatilekey.ordering; + +/** + * @author TommyYang on 2019年1月30日 + */ +public class Model { + + public volatile static int a; + public static int b = 1; + public static int c = 2; + public static int d = 3; + +} diff --git a/javabase/keywords/src/main/java/volatilekey/ordering/RunTest.java b/javabase/keywords/src/main/java/volatilekey/ordering/RunTest.java new file mode 100644 index 0000000..7bfb73d --- /dev/null +++ b/javabase/keywords/src/main/java/volatilekey/ordering/RunTest.java @@ -0,0 +1,27 @@ +package volatilekey.ordering; + +/** + * @author TommyYang on 2019年1月30日 + */ +public class RunTest { + + public static void main(String[] args) throws InterruptedException { + + for (int i = 0; i < 10000; i++){ + Model.a = 10; + Thread at = new Thread(new AThread()); + Thread bt = new Thread(new BThread()); + + + at.start(); + bt.start(); + + bt.join(); + at.join(); + } + + + + } + +} diff --git a/javabase/keywords/src/main/java/volatilekey/unsafe/Counter.java b/javabase/keywords/src/main/java/volatilekey/unsafe/Counter.java new file mode 100644 index 0000000..2b7093a --- /dev/null +++ b/javabase/keywords/src/main/java/volatilekey/unsafe/Counter.java @@ -0,0 +1,56 @@ +package volatilekey.unsafe; + +import sun.misc.Unsafe; + +/** + * @author TommyYang on 2019/1/28 + */ +public class Counter{ + + private final Long serialVersionUID = 0L; + + private static final Unsafe unsafe = OwnUnSafe.getUnSafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (Counter.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile int value; + + public Counter(int value) { + this.value = value; + } + + public final void increment(){ + for(;;){ + int current = getValue(); + int next = current + 1; + if(compareAndSet(current, next)){ + return; + } + } + } + + public final void decrement(){ + for(;;){ + int current = getValue(); + int next = current - 1; + if(compareAndSet(current, next)){ + return; + } + } + } + + public final int getValue() { + return this.value; + } + + public final boolean compareAndSet(int expect, int update) { + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); + } + +} diff --git a/javabase/keywords/src/main/java/volatilekey/unsafe/OwnUnSafe.java b/javabase/keywords/src/main/java/volatilekey/unsafe/OwnUnSafe.java new file mode 100644 index 0000000..7574656 --- /dev/null +++ b/javabase/keywords/src/main/java/volatilekey/unsafe/OwnUnSafe.java @@ -0,0 +1,33 @@ +package volatilekey.unsafe; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +/** + * @author TommyYang on 2019/1/29 + */ +public class OwnUnSafe { + + public static Unsafe getUnSafe() { + try { + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe"); + boolean orignialAccessible = theUnsafeField.isAccessible(); + theUnsafeField.setAccessible(true); + Object unsafeInstance = theUnsafeField.get(null); //unsafeInstance就是Unsafe的实例 + theUnsafeField.setAccessible(orignialAccessible); + return (Unsafe)unsafeInstance; + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + return null; + } + + +} diff --git a/KeyWords/src/main/java/volatilekey/Runtest.java b/javabase/keywords/src/main/java/volatilekey/unsafe/Runtest.java similarity index 50% rename from KeyWords/src/main/java/volatilekey/Runtest.java rename to javabase/keywords/src/main/java/volatilekey/unsafe/Runtest.java index 8b68a40..9d17655 100644 --- a/KeyWords/src/main/java/volatilekey/Runtest.java +++ b/javabase/keywords/src/main/java/volatilekey/unsafe/Runtest.java @@ -1,4 +1,4 @@ -package volatilekey; +package volatilekey.unsafe; /** * Created by TommyYang on 2018/3/19. @@ -6,14 +6,17 @@ public class Runtest { public static void main(String[] args) throws InterruptedException { - Thread incrementTask = new Thread( new ThreadCommunicate().new IncrementTask()); - Thread decrementTask = new Thread(new ThreadCommunicate().new DecrementTask()); + ThreadCommunicate th = new ThreadCommunicate(); + + Thread incrementTask = new Thread(th.new IncrementTask()); + Thread decrementTask = new Thread(th.new DecrementTask()); incrementTask.start(); decrementTask.start(); incrementTask.join(); //让主线程等待子线程结束之后才能继续运行。 decrementTask.join(); - System.out.println(ThreadCommunicate.counter.getValue()); - System.out.println(ThreadCommunicate.counter.getValue1()); + + + System.out.println(th.getCounter().getValue()); } } diff --git a/javabase/keywords/src/main/java/volatilekey/unsafe/ThreadCommunicate.java b/javabase/keywords/src/main/java/volatilekey/unsafe/ThreadCommunicate.java new file mode 100644 index 0000000..a831565 --- /dev/null +++ b/javabase/keywords/src/main/java/volatilekey/unsafe/ThreadCommunicate.java @@ -0,0 +1,34 @@ +package volatilekey.unsafe; + +/** + * Created by TommyYang on 2018/3/19. + */ +public class ThreadCommunicate { + + + Counter counter = new Counter(0); + + public Counter getCounter() { + return counter; + } + + class IncrementTask implements Runnable { + + public void run() { + for(int i = 0; i < 100000; i++){ + counter.increment(); + } + + } + } + + class DecrementTask implements Runnable { + + public void run() { + for(int i = 0; i < 100000; i++){ + counter.decrement(); + } + } + } + +} diff --git a/javabase/keywords/src/main/java/volatilekey/visibility/RunTest.java b/javabase/keywords/src/main/java/volatilekey/visibility/RunTest.java new file mode 100644 index 0000000..6f7dc74 --- /dev/null +++ b/javabase/keywords/src/main/java/volatilekey/visibility/RunTest.java @@ -0,0 +1,35 @@ +package volatilekey.visibility; + +/** + * @author TommyYang on 2019/1/29 + */ +public class RunTest extends Thread { + + //volatile保证线程的可见性 + //private volatile boolean isRunning = true; + private boolean isRunning = true; + + public boolean isRunning() { + return isRunning; + } + + public void setRunning(boolean running) { + isRunning = running; + } + + public void run(){ + + while (isRunning()){ + } + System.out.println("not running"); + } + + public static void main(String[] args) throws InterruptedException { + RunTest voatileTest = new RunTest(); + voatileTest.start(); + + Thread.sleep(1000); + + voatileTest.setRunning(false); + } +} diff --git a/KeyWords/transient.md b/javabase/keywords/transient.md similarity index 88% rename from KeyWords/transient.md rename to javabase/keywords/transient.md index a941133..f34a03f 100644 --- a/KeyWords/transient.md +++ b/javabase/keywords/transient.md @@ -3,9 +3,7 @@ ## 介绍 词义:短暂的 -首先说说"序列化",把一个对象的表示转化为字节流的过程称为串行化(也称为序列化,serialization), -从字节流中把对象重建出来称为反串行化(也称为为反序列化,deserialization)。 -transient 为不应被串行化的数据提供了一个语言级的标记数据方法。 +首先说说"序列化",把一个对象的表示转化为字节流的过程称为串行化(也称为序列化,serialization),从字节流中把对象重建出来称为反串行化(也称为为反序列化,deserialization)。transient 为不应被串行化的数据提供了一个语言级的标记数据方法。 ## 代码测试 ``` diff --git a/javabase/keywords/volatile.md b/javabase/keywords/volatile.md new file mode 100644 index 0000000..73aef35 --- /dev/null +++ b/javabase/keywords/volatile.md @@ -0,0 +1,14 @@ +# 关键字-volatile + +## 介绍 +词义:易变的 + +volatile也是变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 + +在此解释一下Java的内存机制: + +Java使用一个主内存来保存变量当前值,而每个线程则有其独立的工作内存。线程访问变量的时候会将变量的值拷贝到自己的工作内存中,这样,当线程对自己工作内存中的变量进行操作之后,就造成了工作内存中的变量拷贝的值与主内存中的变量值不同。Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。 + +而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。 + +对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。 \ No newline at end of file diff --git a/javabase/stringbuilderandstringbuffer.md b/javabase/stringbuilderandstringbuffer.md new file mode 100644 index 0000000..ce416ae --- /dev/null +++ b/javabase/stringbuilderandstringbuffer.md @@ -0,0 +1,134 @@ +# StringBuffer 和 StringBuilder + +## 介绍 +大多数情况下, StringBuffer 的速度要比 String 快; StringBuilder 要比StringBuffer快;StringBuffer 和 StringBuilder 都是 AbstractStringBuilder 的子类,区别在于StringBuffer 的方法大部分都有 synchronized 修饰。 + +## 源码解析 + +### AbstractStringBuilder + +#### 变量及构造方法 + +``` java + +/** + * 用来存储字符的数组 + * The value is used for character storage. + */ +char[] value; + +/** + * 字符个数 + * The count is the number of characters used. + */ +int count; + +/** + * This no-arg constructor is necessary for serialization of subclasses. + */ +AbstractStringBuilder() { +} + +/** + * 在构造方法中指定字符数组的长度 + * Creates an AbstractStringBuilder of the specified capacity. + */ +AbstractStringBuilder(int capacity) { + value = new char[capacity]; +} + +``` + + +#### 扩容 + +``` java +public void ensureCapacity(int minimumCapacity) { + if (minimumCapacity> 0) + ensureCapacityInternal(minimumCapacity); +} + +private void ensureCapacityInternal(int minimumCapacity) { + // overflow-conscious code + if (minimumCapacity - value.length> 0) { + value = Arrays.copyOf(value, + newCapacity(minimumCapacity)); + } +} + +private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + +private int newCapacity(int minCapacity) { + // overflow-conscious code + int newCapacity = (value.length << 1) + 2; + if (newCapacity - minCapacity < 0) { + newCapacity = minCapacity; + } + return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) + ? hugeCapacity(minCapacity) + : newCapacity; +} + +private int hugeCapacity(int minCapacity) { + if (Integer.MAX_VALUE - minCapacity < 0) { // overflow + throw new OutOfMemoryError(); + } + return (minCapacity> MAX_ARRAY_SIZE) + ? minCapacity : MAX_ARRAY_SIZE; +} +``` + +扩容的方法最终由 `newCapacity()` 实现的,首先将容量左移一位(即扩大2倍)同时加2,如果此时任小于指定的容量,那么就将容量设置为 `minimumCapacity` 。然后判断是否溢出,通过 `hugeCapacity` 实现,如果溢出了(长度大于 `Integer.MAX_VALUE` ),则抛错( `OutOfMemoryError` );否则根据 `minCapacity` 和 `Integer.MAX_VALUE - 8` 的大小比较确定数组容量为 `max(minCapacity, Integer.MAX_VALUE - 8)`。最后将 `value` 值进行拷贝,这一步显然是最耗时的操作。 + +#### append() 方法 + +``` java +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; +} +``` +`append()` 是最常用的方法,它有很多形式的重载。上面是最常用的一种,用于追加字符串。如果 `str` 是 `null` ,则直接调用 +`appendNull()` 方法。这个方法就是直接追加 `'n'` 、 `'u'` 、`'l'`、`'l'` 这几个字符,方法如下: + +``` java + +private AbstractStringBuilder appendNull() { + int c = count; + ensureCapacityInternal(c + 4); + final char[] value = this.value; + value[c++] = 'n'; + value[c++] = 'u'; + value[c++] = 'l'; + value[c++] = 'l'; + count = c; + return this; +} + +``` + +如果不是` null `,则首先需要判断数组容量是否足够,不够则需要扩容(扩容则是调用上述分析的扩容方法); +然后调用 `String` 的 `getChars()` 方法将 `str` 追加到 `value` 末尾; +最后返回对象本身,所以 `append()` 可以连续调用(就是一种类似于链式编程)。 + + +### 思考 +- 为什么每次扩容是扩容为原来的两倍? +个人觉得是为了避免经常扩容带来的成本消耗。 +- 为什么会加2呢? +个人也没想出什么好的解释,觉得可能是因为 `Java` 开发者认为我们在 `append` 数据的时候,中间经常会加一个分隔符,恰好这个分隔符在 `Java` 中正好占用两个字节。也不知道分析的对不对,有其他意见的大佬们可以在 [issue](https://github.com/joyang1/JavaInterview/issues/2) +中进行讨论。 + + +### StringBuilder 与 StringBuffer 方法对比 + +通过查看源码分析发现两者都继承至 `AbstractStringBuilder` 。 而 `StringBuffer` 之所以是线程安全的,是因为重写 `AbstractStringBuilder` 的方法的时候在前面加上了 `synchronzied` 修饰这些方法;而 `StringBuilder` 重写的时候只是直接调用父类的方法,没有做其他的操作。 + +其实通过阅读源码发现 `StringBuilder` 和 `StringBuffer` 之间的关系,类似于 `HashMap` 和 `HashTable` 之间的关系。 + + diff --git a/javabase/threadpool.md b/javabase/threadpool.md new file mode 100644 index 0000000..d2f8b77 --- /dev/null +++ b/javabase/threadpool.md @@ -0,0 +1,199 @@ +# 线程池 +线程池,从字面意义上来讲,是指管理一组同构工作线程的资源池。线程是与工作队列(Work Queue)密切相关的,其中在工作队列中保存了所有等待执行的任务。工作者线程(Work Thread)的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。线程池简化了线程的管理工作,为用户开发多线程应用提供了更加方便的 API。 + +**好处** +- 线程池可以重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。 +- 当请求到达时,工作线程通常已经存在,因此不会由于等待线程创建线程而延迟任务的执行,从而提高响应性。 +- 通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保证忙碌状态,同时还可以防止多线程相互竞争资源而使应用程序耗尽内存或失败。 + +## Executor 框架 +采用生产者-消费者模式,提交任务的操作相当于生产者(生产待完成的工作单元),执行任务的线程相当于消费者(执行完这些工作单元)。 + +```java +public interface Executor { + + /** + * + * @param command the runnable task + * @throws RejectedExecutionException if this task cannot be + * accepted for execution + * @throws NullPointerException if command is null + */ + void execute(Runnable command); +} +``` + +虽然 Executor 是个很简单的接口,但它却为灵活且强大的异步执行框架提供了基础,该框架能支持多种不同类型的任务执行策略。它提供了一种标准的方法将任务的提交过程与执行过程解藕开来,并用 Runnable 来表示任务。Executor 的实现还提供了对生命周期的支持,以及统计信息收集、应用程序管理机制和性能监视等机制。 + +## ThreadPoolExecutor +线程池的具体实现类。以下的线程池都是由 ThreadPoolExecutor 来具体实现的。 + +## 分类 +- newSingleThreadPool +- newFixedThreadPool +- newCachedThreadPool +- newScheduledThreadPool +- newWorkStealingPool + +## 具体分析 +### newSingleThreadPool +Executors.newSingleThreadPool() 是一个单线程的 Executor,它创建单个线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadPool 能确保依照任务在队列中的顺序来串行执行(比如 FIFO、 LIFO、优先级)。 + +### newFixedThreadPool +Executors.newFixedThreadPool() 创建一个固定长度的线程池,每当提交一个任务的时就创建一个线程,直到达到线程池的最大数量,这时,线程池的规模将不在变化(如果某个线程由于发生了未预期的 Exception 而结束,那么线程池会补充一个新的线程)。 + +### newCachedThreadPool +Executors.newCachedThreadPool() 创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。 + +### newScheduledThreadPool +Executors.newScheduledThreadPool() 创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于 Timer。 + +### newWorkStealingPool +Executors.newWorkStealingPool() 创建了一个工作窃取池,具体地由 ForkJoinPool 构成;具体就是先把大任务 fork 成小任务,然后再把小任务的结果 join 起来,最后得到一个具体的结果。 + +### ThreadPoolExecutor 源码分析 + +#### 构造方法 + +```java + +public class ThreadPoolExecutor extends AbstractExecutorService { + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + Executors.defaultThreadFactory(), defaultHandler); + } + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + threadFactory, defaultHandler); + } + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + RejectedExecutionHandler handler) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + Executors.defaultThreadFactory(), handler); + } + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory, + RejectedExecutionHandler handler) { + if (corePoolSize < 0 || + maximumPoolSize <= 0 || + maximumPoolSize < corePoolSize || + keepAliveTime < 0) + throw new IllegalArgumentException(); + if (workQueue == null || threadFactory == null || handler == null) + throw new NullPointerException(); + this.acc = System.getSecurityManager() == null ? + null : + AccessController.getContext(); + this.corePoolSize = corePoolSize; + this.maximumPoolSize = maximumPoolSize; + this.workQueue = workQueue; + this.keepAliveTime = unit.toNanos(keepAliveTime); + this.threadFactory = threadFactory; + this.handler = handler; + } + +} + + +``` + +#### 7 个参数 +- corePoolSize + + 核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存 keepAliveTime 限制。除非将 allowCoreThreadTimeOut 设置为 true。 + +- maximumPoolSize + + 线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的 LinkedBlockingDeque 时,这个值无效。 + +- keepAliveTime + + 非核心线程的闲置超时时间,超过这个时间就会被回收。 + +- unit + + 指定 keepAliveTime 的单位,如 TimeUnit.SECONDS。当将 allowCoreThreadTimeOut 设置为 true 时对 corePoolSize 生效。 + +- workQueue + + 线程池中的任务队列。常用的有三种队列,`SynchronousQueue`,`LinkedBlockingDeque`,`ArrayBlockingQueue`。 + +- threadFactory + + 线程工厂,提供创建新线程的功能。ThreadFactory 是一个接口,只有一个方法,如下: + + ```java + + public interface ThreadFactory { + Thread newThread(Runnable r); + } + + ``` + + 通过线程工厂可以对线程的一些属性进行定制。 + + 默认工厂源码: + + ```java + + static class DefaultThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + DefaultThreadFactory() { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : + Thread.currentThread().getThreadGroup(); + namePrefix = "pool-" + + poolNumber.getAndIncrement() + + "-thread-"; + } + + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, + namePrefix + threadNumber.getAndIncrement(), + 0); + if (t.isDaemon()) + t.setDaemon(false); + if (t.getPriority() != Thread.NORM_PRIORITY) + t.setPriority(Thread.NORM_PRIORITY); + return t; + } + } + + ``` + +- handler + + `RejectedExecutionHandler` 也是一个接口,同时也只有一个方法,如下: + ```java + + public interface RejectedExecutionHandler { + void rejectedExecution(Runnable var1, ThreadPoolExecutor var2); + } + + ``` + 当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用 RejectedExecutionHandler 的 rejectedExecution 方法。 \ No newline at end of file diff --git a/jvm/README.md b/jvm/README.md new file mode 100644 index 0000000..00b7071 --- /dev/null +++ b/jvm/README.md @@ -0,0 +1,82 @@ +# 深入浅出 JVM +该篇主要是**深入理解 Java 虚拟机-第二版**的读书笔记总结。 + +>**目录** + +- [运行时数据区域](#运行时数据区域) +- [垃圾回收算法](#垃圾回收算法) +- [垃圾收集器](#垃圾收集器) + +## 运行时数据区域 +- 线程私有区域 + - 程序计数器 + - Java 虚拟机栈 + - 本地方法栈 +- 线程共享区域 + - Java 堆 + - 方法区 + - 运行时常量池(属于方法区的一部分) + +--- + +### 线程私有区域 +#### 程序计数器 +程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计算器来完成。 + +由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。故为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,所以这块内存区域是"线程私有"的区域。 + +#### Java 虚拟机栈 +Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 + +#### 本地方法栈 +本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机栈执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 + +--- + +### 线程共享区域 +#### Java 堆 +对于大多数应用来说,Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被**所有线程共享**的一块内存区域,在虚拟机启动是创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java 虚拟机规范中的描述是:所有对象的实例以及数组都要在堆上分配,但是随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有对象都分配在堆上也渐渐变得没那么"绝对"了。 + +#### 方法区(非堆) +方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把 Java 方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 **Non Heap**(非堆),目的应该是与 Java 堆区分开来。 + +方法区也被开发者成为"永久代"(Permanent Generation)。 + +#### 运行时常量池(属于方法区的一部分) +Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。 + + +--- + +## 垃圾回收算法 +### 标记-清除(Mark-Sweep)算法 +如同名字一样,算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,它的标记过程其实在前一节讲述对象标记判定时已经介绍过了。之所以说它是最基础的算法,是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到的。 + +它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 + + +### 复制算法 +为了解决效率问题,复制(Copying)算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样就是每次只对其中一块内存进行回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。 + +这种算法的代价是将内存缩小为了原来的一半,代价很大。这种算法也在特殊场景中会有很大用处,比如回收新生代的时候,IBM 公司的专门研究表明,新生代的对象 98% 是"朝生夕灭"的,所以不需要按照 1:1 的比例来划分内存区域,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor 空间。当回收时,将 Eden 空间和 Survivor 空间中还存活着的对象一次性地复制到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才使用过的 Survivor 空间。这里肯定有一个具体空间分配比例,HotSpot 虚拟机默认 Eden:Survivor 为 8:1,也就是每次新生代中可用内存为整个新生代的 90%(80%+10%),只有 10% 的内存会被"浪费"。当然,98% 的对象可回收只是一般场景下的数据,JVM 没有办法保证每次回收都只有不多于 10% 的对象存活,当 Survivor 空间不够用时,需要依赖其它内存(这里指老年代)进行分配担保(Handle Promotion)。 + +内存的分配担保就好比我们现在使用支付宝里面的花呗,如果我们信誉很好,在 98% 的情况下都能按时偿还,于是支付宝会默认我们会在下一月也能按时按量的偿还我们的预支,只需要有一个担保人能保证如果我下次不能还款时,可以帮助你还钱,那支付宝就认为我们预支花呗是没有风险的。内存的分配担保也一样,如果另外一块 Survivor 空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。具体怎么分配担保会在后续分析。 + +### 标记-整理算法 +复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保,以应对使用的内存中所有对象都 100% 存活的极端情况,所以在老年代一般不能直接选用复制算法。 + +根据老年代存活时间较长的特点,有人提出了另一种"标记-整理"(Mark-Compact)的算法,标记过程仍然与"标记-清除"算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,然后直接清理掉边界以外的内存。 + +### 分代收集算法 +这种算法没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的算法。 + +- 新生代:复制算法 + + 因为在新生代中,每次垃圾回收时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需付出少量存活对象的复制成本就可以完成收集。 + +- 老年代:标记-清理/标记-整理 + + 因为老年代对象存活率高、没有额外空间对它进行分配担保。 + +## 垃圾收集器 +该部分单独总结,参见[garbage_collectors](garbage_collectors.md)。 \ No newline at end of file diff --git a/jvm/garbage_collectors.md b/jvm/garbage_collectors.md new file mode 100644 index 0000000..313552a --- /dev/null +++ b/jvm/garbage_collectors.md @@ -0,0 +1,126 @@ +# 垃圾回收器 +垃圾回收算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。 + +## 分类 +以下分类是针对 HotSpot 虚拟机的垃圾回收器。 + +- Serial 收集器 +- ParNew 收集器 +- Parallel Scavenge 收集器 +- Serial Old 收集器 +- Parallel Old 收集器 +- CMS 收集器 +- G1 收集器 + +按收集器的回收对象以及收集器之间的联系,可以参照下图一: + + +通过上图可以发现,Serial、ParNew、 Parallel Scavenge 是用来收集新生代的收集器,CMS、 Serial Old、Parallel Old 是用来收集老年代的收集器;然后 CMS 是不能和 Parallel Scavenge 结合起来用的,CMS 是可以和 Serial Old 联合起来收集老年代的。 + + +## Serial 收集器 +Serial 收集器是最基本、发展历史最悠久的收集器,(在JDK 1.3.1 之前)是虚拟机新生代收集的唯一选择。通过名字,大家也可以发现该收集器是一个单线程的收集器,但它的"单线程"的意义并不仅仅说明它只会使用一个 CPU 或 一条收集线程去完成垃圾收集工作,更重要的是在它进行收集时,必须暂停其它所有的工作线程,直到它收集结束。"Stop The World"就是该收集器的一个特点,这项工作是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉,这对很多应用来说都是难以接受的。 + +Serial 收集器的工作过程如下图二: + + +对于 "Stop The World" 带给用户的不良体验,虚拟机的设计者们表示完全理解,但是表示非常委屈:"你妈妈在给你打扫房间的时候,肯定也会让你处在一个不会干扰她打扫的状态,比如坐在椅子上不动,或者到房间外面去,不然她一边打扫,你一边乱扔垃圾,这房间还能打扫完?"这确实是一个合情合理的矛盾,虽然垃圾收集这项工作听起来和打扫房间属于一个性质的,但实际上肯定还要比打扫房间复杂很多很多的。 + +垃圾收集器也是处在一个不断发展的过程中,由 Serial(串行)收集器到 Parallel(并行)收集器,再到 Concurrent Mark Sweep(CMS)乃至 Garbage First(G1)收集器,我们看到一个个越来越优秀(也越来越复杂)的收集器出现,用户线程的停顿时间在不断缩短。 + +Serial 收集器是虚拟机运行在 Client 模式下的默认新生代收集器。原因是由于在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十甚至一两百兆的新生代(仅仅是新生代的内存,桌面程序大多数情况下不会再大了),停顿时间完全可以控制在几十毫秒最多一百毫秒之内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial 收集器对于运行在 Client 模式下的虚拟机来说是一个很好的选择。 + +## ParNew 收集器 +ParNew 收集器其实就是 Serial 收集器的多线程版本。其它的与 Serial 收集器基本相同,也是 Stop The World,在实现上,这两种收集器也共用了相当多的代码。 + +ParNew 收集器的工作过程如下图三: + + +ParNew 收集器是许多运行在 Server 模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因,除了 Serial 收集器外,目前只有它能与 CMS 收集器配合工作。 CMS 收集器是在 JDK 1.5时期推出的一款真正意义上的并发(Concurrent)收集器,它第一次实现了让垃圾收集器与用户线程(基本上)同时工作,用前面的例子来说就是做到了在你妈妈打扫房间的同时你还能一边往地上扔垃圾。 + +但是,我们在图一中也可以看到, CMS 作为老年代的收集器,却无法与 JDK 1.4 时期推出的新生代收集器 Parallel Scavenge 配合工作,所以在 JDK 1.5 中使用 CMS 收集老年代的时候,新生代只能选择 ParNew 或者 Serial 收集器中的一个。 + +ParNew 在单 CPU 的环境中绝对不会比 Serial 收集器有更好的效果,甚至由于存在线程交互的开销(由用户态切到内核态之间的切换),该收集器在通过超线程技术实现的两个 CPU 的环境中都不能 100% 地保证可以超越 Serial 收集器。当然,随着 CPU 数量的增加,它对于 GC 是系统资源的有效利用还是很多效果的。它默认开启的线程数与 CPU 数量相同。 + +## Parallel Scavenge 收集器 +Parallel Scavenge 也是并行收集器,与 ParNew 类似。那它的特点是什么? + +Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同,CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标是达到一个可控的吞吐量(Throughput)。所谓吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值,即吞吐量 = 运行用户代码的时间 / (用户运行代码的时间 + 垃圾收集时间),比如:虚拟机总共运行了 100 分钟,其中垃圾收集花掉了 1 分钟,那吞吐量就是 99%。 + +停顿时间越短越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高效率则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。 + +## Serial Old 收集器 +Serial Old 是 Serial 收集器的老年代版本,它同样是一个单线程收集器,使用"标记-整理"算法。该收集器的主要意义也是在于给 Client 模式下的虚拟机使用。在 Server 模式下,它还有两大用途:一种用途是在 JDK 1.5 以及之前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途就是作为 CMS 收集器的后备方案,在并发收集发生 Concurrent Mode Failure 时使用。 + +## Parallel Old 收集器 +Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和"标记-整理"算法。 + +## CMS 收集器 +CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java 应用集中在互联网站或者 B/S 系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,给用户带来较好的体验(CMS 非常符合这类需求)。 + +CMS 的**收集过程**分为如下 4 个步骤: +- 初始标记(CMS initial mark) +- 并发标记(CMS concurrent mark) +- 重新标记(CMS remark) +- 并发清除(CMS concurrent sweep) + +其中,初始标记和重新标记这两个步骤仍需"Stop The World"。初始标记仅仅只是标记一下 GCRoot 能直接关联到的对象,速度很快,并发标记阶段就是进行 GCRoot Tracing 的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,该阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。 + +由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。 + +`优点:` +- 并发收集; +- 低停顿。 + +`缺点:` +- 对 CPU 资源非常敏感; +- 无法处理浮动垃圾(Floating Garbage),可能出现"Concurrent Mode Failure" 失败而导致另一次 Full GC 的产生; +- 因为是基于"标记-清除"算法实现的收集器,所以在回收结束时会有大量空间碎片产生。 + +## G1 收集器 +G1 是一款面向服务端应用的垃圾回收器。HotSpot 开发团队赋予它的使命是在未来(时间可能比较长)可以替换掉 CMS 收集器。 + +与其它收集器相比,G1 具备如下**特点**: + +- 并行与并发 + + G1 能充分利用多 CPU、多核环境下的优势,使用多个 CPU 来缩短 Stop-The-World 停顿的时间,部分其它收集器原本需要停顿 + Java 线程执行 GC 动作,G1 收集器仍然可以通过并发的方式让 Java 进程继续执行。 + +- 分代收集 + + G1 收集器不需要配合其它收集器就可以进行分代回收,可以独立地管理整个 GC 堆,能够采用不同的方式去处理新创建的对象和已经 + 存活了一段时间、熬过多次 GC 的旧对象以获得更好的收集效果。 + +- 空间整合 + + 与 CMS 的"标记-清理"算法不同,G1 从整体来看是基于"标记-整理"算法实现的回收器,从局部(两个 Region 之间)上来看是 + 基于"复制"算法实现的,但无论如何,这两种算法都意味着 G1 运作期间不会产生空间碎片,收集后能提供规整的可用内存。这种特 + 性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。 + +- 可预测的停顿 + + 这是 G1 相对于 CMS 的另一大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但是 G1 除了追求低停顿外,还能建立可预测 + 的停顿时间模型,能让使用者明确指定一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒,这几乎已经是 + 实时 Java(RTSJ)的垃圾回收器的特征。 + +G1 具体收集器的过程: + +- 初始标记(Initial Marking) +- 并发标记(Concurrent Marking) +- 最终标记(Final Marking) +- 筛选回收(Live Data Counting and Evacuation) + + +# 收集器中使用的参数列表 + +| 参数 | 参数说明 | +| :----: | :----: | +| -XX:+UseConcMarkSweepGC | 使用 CMS 收集器,ParNew 是使用该选项后的默认新生代收集器 | +| -XX:+UseParNewGC | 使用 ParNew 收集器 | +| -XX:+ParallelGCThreads | 设置垃圾回收线程数 | +| -XX:MaxGCPauseMillis | Parallel Scavenge 收集器设置最大垃圾收集的停顿时间 | +| -XX:GCTimeRatio | Parallel Scavenge 收集器设置最大垃圾收集的停顿时间 | + + + diff --git a/jvm/jvm_params.md b/jvm/jvm_params.md new file mode 100644 index 0000000..97989f5 --- /dev/null +++ b/jvm/jvm_params.md @@ -0,0 +1,11 @@ +# JVM 参数 +- 查看 JVM 使用什么垃圾回收器:java -XX:+PrintCommandLineFlags -version + +``` properties + +-XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=32210157568 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC +openjdk version "1.8.0_222" +OpenJDK Runtime Environment (build 1.8.0_222-8u222-b10-1ubuntu1~16.04.1-b10) +OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode) + +``` diff --git a/lock/README.md b/lock/README.md new file mode 100644 index 0000000..1a32b14 --- /dev/null +++ b/lock/README.md @@ -0,0 +1,278 @@ +# Lock 篇 +其实本来 Lock 应该放到 data-structure 模块的,但是为了体现其重要性,单独用一个篇章来整理。 + +# 锁的分类介绍 + +## 乐观锁与悲观锁 +锁的一种宏观分类是**乐观锁**与**悲观锁**。乐观锁与悲观锁并不是特定的指哪个锁(Java 中也没有那个具体锁的实现名就叫乐观锁或悲观锁),而是在并发情况下两种不同的策略。 + +乐观锁(Optimistic Lock)就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁。但是如果想要更新数据,则会在**更新之前检查在读取至更新这段时间别人有没有修改过这个数据**。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃更新操作)。 + +悲观锁(Pessimistic Lock)就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次都在拿数据的时候上锁。这样别人拿数据的时候就会被挡住,直到悲观锁释放,想获取数据的线程再去获取锁,然后再获取数据。 + +**悲观锁阻塞事务,乐观锁回滚重试**,它们各有优缺点,没有好坏之分,只有适应场景的不同区别。比如:乐观锁适合用于写比较少的情况下,即冲突真的很少发生的场景,这样可以省去锁的开销,加大了系统的整个吞吐量。但是如果经常产生冲突,上层应用会不断的进行重试,这样反而降低了性能,所以这种场景悲观锁比较合适。 + +总结:**乐观锁适合写比较少,冲突很少发生的场景;而写多,冲突多的场景适合使用悲观锁**。 + +## 乐观锁的基础 --- CAS +在乐观锁的实现中,我们必须要了解的一个概念:CAS。 + +什么是 CAS 呢? Compare-and-Swap,即**比较并替换**,或者**比较并设置**。 + +- 比较:读取到一个值 A,在将其更新为 B 之前,检查原值是否为 A(未被其它线程修改过,**这里忽略 ABA 问题**)。 + +- 替换:如果是,更新 A 为 B,结束。如果不是,则不会更新。 + +上面两个步骤都是原子操作,可以理解为瞬间完成,在 CPU 看来就是一步操作。 + +有了 CAS,就可以实现一个乐观锁: + +```java + +public class OptimisticLockSample{ + + public void test(){ + int data = 123; // 共享数据 + + // 更新数据的线程会进行如下操作 + for (;;) { + int oldData = data; + int newData = doSomething(oldData); + + // 下面是模拟 CAS 更新操作,尝试更新 data 的值 + if (data == oldData) { // compare + data = newData; // swap + break; // finish + } else { + // 什么都不干,循环重试 + } + } + } + + /** + * + * 很明显,test() 里面的代码根本不是原子性的,只是展示了下 CAS 的流程。 + * 因为真正的 CAS 利用了 CPU 指令。 + * + * */ + + +} + +``` + +在 Java 中也是通过 native 方法实现的 CAS。 + +```java + +public final class Unsafe { + + ... + + public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); + + public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); + + public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6); + + ... +} + + +``` + +上面写了一个简单直观的乐观锁(确切的来说应该是乐观锁流程)的实现,它允许多个线程同时读取(因为根本没有加锁操作),如果更新数据的话,有且仅有一个线程可以成功更新数据,并导致其它线程需要回滚重试。CAS 利用 CPU 指令,从硬件层面保证了原子性,以达到类似于锁的效果。 + +从乐观锁的整个流程中可以看出,并没有**加锁**和**解锁**的操作,因此乐观锁策略也被称作为**无锁编程**。换句话说,乐观锁其实不是"锁",它仅仅是一个循环重试的 CAS 算法而已。 + + +## 自旋锁 + +## synchronized 与 Lock interface +Java 中两种实现加锁的方式:一种是使用 synchronized 关键字,另一种是使用 Lock 接口的实现类。 + +在一篇文章中看到一个好的对比,非常形象,synchronized 关键字就像是**自动挡**,可以满足一切的驾驶需求。但是如果你想要做更高级的操作,比如玩漂移或者各种高级的骚操作,那么就需要**手动挡**,也就是 Lock 接口的实现类。 + +而 synchronized 在经过 Java 每个版本的各种优化后,效率也变得很高了。只是使用起来没有 Lock 接口的实现类那么方便。 + +### synchronized 锁升级过程就是其优化的核心:**偏向锁** -> **轻量级锁** -> **重量级锁** + +```java + +class Test{ + private static final Object object = new Object(); + + public void test(){ + synchronized(object) { + // do something + } + } + +} + +``` + +使用 synchronized 关键字锁住某个代码块的时候,一开始锁对象(就是上述代码中的 object)并不是**重量级锁**,而是偏向锁。偏向锁的字面意思就是"偏向于第一个获取它的线程"的锁。线程执行完同步代码块之后,并**不会主动释放偏向锁**。当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程 ID 在对象头里存储),如果是则正常往下执行。**由于之前没有释放,这里就不需要重新加锁**,如果从头到尾都是一个线程在使用锁,很明显偏向锁几乎没有额外开销,性能极高。 + +一旦有第二个线程加入**锁竞争**,偏向锁转换为**轻量级锁**(**自旋锁**)。锁竞争:如果多个线程轮流获取一个锁,但是每次获取的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程获取锁的时候,发现锁已经被占用,需要等待其释放,则说明发生了锁竞争。 + +在轻量级锁状态上继续锁竞争,没有抢到锁的线程进行**自旋**操作,即在一个循环中不停判断是否可以获取锁。获取锁的操作,就是通过 CAS 操作修改对象头里的锁标志位。先**比较**当前锁标志位是否为**释放**状态,如果是,将其设置为**锁定**状态,比较并设置是原子性操作,这个是 JVM 层面保证的。当前线程就算持有了锁,然后线程将当前锁的持有者信息改为自己。 + +假如我们获取到锁的线程操作时间很长,比如会进行复杂的计算,数据量很大的网络传输等;那么其它等待锁的线程就会进入长时间的自旋操作,这个过程是非常耗资源的。其实这时候相当于只有一个线程在有效地工作,其它的线程什么都干不了,在白白地消耗 CPU,这种现象叫做**忙等(busy-waiting)**。所以如果多个线程使用**独占锁**,但是没有发生锁竞争,或者发生了很轻微的锁竞争,那么 synchronized 就是轻量级锁,允许短时间的忙等现象。这是一种择中的想法,**短时间的忙等,换取线程在用户态和内核态之间切换的开销**。 + +显然,忙等是有限度的(JVM 有一个计数器记录自旋次数,默认允许循环 10 次,可以通过[虚拟机参数更改](#参数介绍))。如果锁竞争情况严重,达到某个最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是通过 CAS 修改锁标志位,但不修改持有锁的线程 ID)。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是上面说的忙等,即不会自旋),等待释放锁的线程去唤醒。在 JDK1.6 之前, synchronized 直接加重量级锁,很明显现在通过一系列的优化过后,性能明显得到了提升。 + +JVM 中,synchronized 锁只能按照偏向锁、轻量级锁、重量级锁的顺序逐渐升级(也有把这个称为**锁膨胀**的过程),不允许降级。 + +## 可重入锁(递归锁) +可重入锁的字面意思是"可以重新进入的锁",即**允许同一个线程多次获取同一把锁**。比如一个递归函数里有加锁操作,递归函数里这个锁会阻塞自己么?如果不会,那么这个锁就叫可重入锁(因为这个原因可重入锁也叫做**递归锁**)。 + +Java 中以 Reentrant 开头命名的锁都是可重入锁,而且 **JDK 提供的所有现成 Lock 的实现类,包括 synchronized 关键字锁都是可重入的**。如果真的需要不可重入锁,那么就需要自己去实现了,获取去网上搜索一下,有很多,自己实现起来也很简单。 + +如果不是可重入锁,在递归函数中就会造成死锁,所以 Java 中的锁基本都是可重入锁,不可重入锁的意义不是很大,我暂时没有想到什么场景下会用到;**注意:有想到需要不可重入锁场景的小伙伴们可以留言一起探讨**。 + +下图展示一下 Lock 的相关实现类: + + + +## 公平锁和非公平锁 +如果多个线程申请一把**公平锁**,那么获得锁的线程释放锁的时候,先申请的先得到,很公平。如果是**非公平锁**,后申请的线程可能先获得锁,是随机获取还是其它方式,都是根据实现算法而定的。 + +对 ReentrantLock 类来说,通过构造函数可以**指定该锁是否是公平锁,默认是非公平锁**。因为在大多数情况下,非公平锁的吞吐量比公平锁的大,如果没有特殊要求,优先考虑使用非公平锁。 + +而对于 synchronized 锁而言,它只能是一种非公平锁,没有任何方式使其变成公平锁。这也是 ReentrantLock 相对于 synchronized 锁的一个优点,更加的灵活。 + +以下是 ReentrantLock 构造器代码: + +``` java + +/** + * 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(); +} + +``` + +ReentrantLock 内部实现了 FairSync 和 NonfairSync 两个内部类来实现公平锁和非公平锁。具体源码分析会在接下来的章节给出,敬请关注该项目,欢迎 fork 和 star。 + + +## 可中断锁 +字面意思是"可以**响应中断**的锁"。 + +首先,我们需要理解的是什么是**中断**。 Java 中并没有提供任何可以直接中断线程的方法,只提供了**中断机制**。那么何为**中断机制**呢?线程 A 向线程 B 发出"请你停止运行"的请求,就是调用 Thread.interrupt() 的方法(当然线程 B 本身也可以给自己发送中断请求,即 Thread.currentThread().interrupt()),但线程 B 并不会立即停止运行,而是自行选择在合适的时间点以自己的方式响应中断,也可以直接忽略此中断。也就是说,Java 的**中断不能直接终止线程**,只是设置了状态为响应中断的状态,需要被中断的线程自己决定怎么处理。这就像在读书的时候,老师在晚自习时叫学生自己复习功课,但学生是否复习功课,怎么复习功课则完全取决于学生自己。 + +回到锁的分析上来,如果线程 A 持有锁,线程 B 等待持获取该锁。由于线程 A 持有锁的时间过长,线程 B 不想继续等了,我们可以让线程 B 中断自己或者在别的线程里面中断 B,这种就是 **可中断锁**。 + +在 Java 中, synchronized 锁是**不可中断锁**,而 Lock 的实现类都是 **可中断锁**。从而可以看出 JDK 自己实现的 Lock 锁更加的灵活,这也就是有了 synchronized 锁后,为什么还要实现那么些 Lock 的实现类。 + +Lock 接口的相关定义: + +```java + +public interface Lock { + + void lock(); + + void lockInterruptibly() throws InterruptedException; + + boolean tryLock(); + + boolean tryLock(long time, TimeUnit unit) throws InterruptedException; + + + void unlock(); + + Condition newCondition(); +} + +``` + +其中 lockInterruptibly 就是获取可中断锁。 + +## 共享锁 +字面意思是多个线程可以共享一个锁。一般用共享锁都是在读数据的时候,比如我们可以允许 10 个线程同时读取一份共享数据,这时候我们可以设置一个有 10 个凭证的共享锁。 + +在 Java 中,也有具体的共享锁实现类,比如 Semaphore。 该类的源码分析会在后续章节进行分析,敬请关注该项目,欢迎 fork 和 star。 + +## 互斥锁 +字面意思是线程之间互相排斥的锁,也就是表明锁只能被一个线程拥有。 + +在 Java 中, ReentrantLock、synchronized 锁都是互斥锁。 + +## 读写锁 +读写锁其实是一对锁,一个读锁(共享锁)和一个写锁(互斥锁、排他锁)。 + +在 Java 中, ReadWriteLock 接口只规定了两个方法,一个返回读锁,一个返回写锁。 + +```java + +public interface ReadWriteLock { + /** + * Returns the lock used for reading. + * + * @return the lock used for reading + */ + Lock readLock(); + + /** + * Returns the lock used for writing. + * + * @return the lock used for writing + */ + Lock writeLock(); +} + +``` + +文章前面讲过[乐观锁策略](#乐观锁的基础 --- CAS),所有线程可以随时读,仅在写之前判断值有没有被更改。 + +读写锁其实做的事情是一样的,但是策略稍有不同。很多情况下,线程知道自己读取数据后,是否是为了更改它。那么为何不在加锁的时候直接明确这一点呢?如果我读取值是为了更新它(SQL 的 for update 就是这个意思),那么加锁的时候直接加**写锁**,我持有写锁的时候,别的线程无论是读还是写都需要等待;如果读取数据仅仅是为了前端展示,那么加锁时就明确加一个**读锁**,其它线程如果也要加读锁,不需要等待,可以直接获取(读锁计数器加 1)。 + +虽然读写锁感觉与乐观锁有点像,但是**读写锁是悲观锁策略**。因为读写锁并没有在**更新前**判断值有没有被修改过,而是在**加锁前**决定应该用读锁还是写锁。乐观锁特指无锁编程。 + +JDK 内部提供了一个唯一一个 ReadWriteLock 接口实现类是 ReentrantReadWriteLock。通过名字可以看到该锁提供了读写锁,并且也是可重入锁。 + +## 总结 +Java 中使用的各种锁基本都是**悲观锁**,那么 Java 中有乐观锁么?结果是肯定的,那就是 java.util.concurrent.atomic 下面的原子类都是通过乐观锁实现的。如下: + +``` java + +public final int getAndAddInt(Object var1, long var2, int var4) { + int var5; + do { + var5 = this.getIntVolatile(var1, var2); + } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); + + return var5; +} + +``` + +通过上述源码可以发现,在一个循环里面不断 CAS,直到成功为止。 + +## 参数介绍 +``` +-XX:-UseBiasedLocking=false 关闭偏向锁 + + +JDK1.6 + +-XX:+UseSpinning 开启自旋锁 + +-XX:PreBlockSpin=10 设置自旋次数 + +JDK1.7 之后 去掉此参数,由 JVM 控制 + + +``` + + + + + + \ No newline at end of file diff --git a/lock/pom.xml b/lock/pom.xml new file mode 100644 index 0000000..8a56184 --- /dev/null +++ b/lock/pom.xml @@ -0,0 +1,35 @@ + + + + javainterview + cn.tommyyang + 1.0-SNAPSHOT + + 4.0.0 + + lock + + + + cn.tommyyang + keywords + 1.0-SNAPSHOT + + + + org.redisson + redisson + 3.10.7 + + + + + org.apache.zookeeper + zookeeper + 3.4.14 + + + + \ No newline at end of file diff --git a/lock/src/main/java/cn/tommyyang/demo/RunTest.java b/lock/src/main/java/cn/tommyyang/demo/RunTest.java new file mode 100644 index 0000000..d0196b5 --- /dev/null +++ b/lock/src/main/java/cn/tommyyang/demo/RunTest.java @@ -0,0 +1,129 @@ +package cn.tommyyang.demo; + +import cn.tommyyang.demo.lock.Consumer; +import cn.tommyyang.demo.lock.Producer; +import cn.tommyyang.demo.lock.ReentrantLockDemo; +import cn.tommyyang.demo.lock.SynchronizedDemo; +import volatilekey.unsafe.Counter; + +import java.util.concurrent.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author TommyYang on 2019年1月28日 + */ +public class RunTest { + + public static void main(String[] args) { + + //runSynchronizedDemo(); + + //runReenTrantLockDemo(); + + //runCASDemo(); + + runSynchronizedP_C_Demo(); + + //runReentrantLockP_C_Demo(); + } + + + + public static void runSynchronizedP_C_Demo(){ + Producer pro = new Producer(); + Consumer con = new Consumer(); + Thread t1 = new Thread(pro); + Thread t2 = new Thread(pro); + + Thread c1 = new Thread(con); + Thread c2 = new Thread(con); + + t1.start(); + t2.start(); + + c1.start(); + c2.start(); + + + } + + public static void runReentrantLockP_C_Demo(){ + ReentrantLock lock = new ReentrantLock(); + Condition cCondition = lock.newCondition(); + Condition pCondition = lock.newCondition(); + Producer pro = new Producer(lock, cCondition, pCondition); + Thread t1 = new Thread(pro); + Thread t2 = new Thread(pro); + + Consumer con = new Consumer(lock, cCondition, pCondition); + Thread c1 = new Thread(con); + Thread c2 = new Thread(con); + + t1.start(); + t2.start(); + + c1.start(); + c2.start(); + + + + } + + public static void runSynchronizedDemo(){ + final ExecutorService pool = Executors.newFixedThreadPool(10); + for(int i = 0; i < 2; i++){ + pool.execute(new SynchronizedDemo()); + } + pool.shutdown(); + + while (true){ + if(pool.isTerminated()){ + System.out.println(SynchronizedDemo.common); + break; + } + } + } + + public static void runReenTrantLockDemo(){ + ReentrantLock lock = new ReentrantLock(true); + ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo(); + + reentrantLockDemo.newThread(lock, "tttt1"); + reentrantLockDemo.newThread(lock, "tttt2"); + + reentrantLockDemo.newThread(lock, "tttt3"); + reentrantLockDemo.newThread(lock, "tttt4"); + + } + + public static void runCASDemo(){ + final Counter counter = new Counter(0); + + new Thread(new Runnable() { + public void run() { + counter.compareAndSet(0, 1); + + System.out.println("sleep 1s"); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("重置value为0"); + counter.compareAndSet(1, 0); + } + }).start(); + + new Thread(new Runnable() { + public void run() { + System.out.println("等待中....."); + counter.compareAndSet(0, 2); + System.out.println(counter.getValue()); + } + }).start(); + + } +} diff --git a/lock/src/main/java/cn/tommyyang/demo/lock/Consumer.java b/lock/src/main/java/cn/tommyyang/demo/lock/Consumer.java new file mode 100644 index 0000000..575ea43 --- /dev/null +++ b/lock/src/main/java/cn/tommyyang/demo/lock/Consumer.java @@ -0,0 +1,76 @@ +package cn.tommyyang.demo.lock; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author TommyYang on 2019-04-06 + */ +public class Consumer implements Runnable { + + private ReentrantLock lock; + private Condition cCondition; + private Condition pCondition; + + public Consumer() { + } + + public Consumer(ReentrantLock lock, Condition cCondition, Condition pCondition) { + this.lock = lock; + this.cCondition = cCondition; + this.pCondition = pCondition; + } + + public void consume1() throws InterruptedException { + synchronized (Producer.data) { + while (!Producer.flag){ + System.out.println("进入Consumer wait"); + Producer.data.wait(); + } + this.consume(); + Producer.flag = false; + Producer.data.notifyAll(); + } + + } + + public void consume2() throws InterruptedException { + try { + this.lock.lock(); + while (!Producer.flag){ + System.out.println("进入Consumer await"); + this.cCondition.await(); + } + this.consume(); + Producer.flag = false; + this.pCondition.signalAll(); + } finally { + this.lock.unlock(); + } + + + + } + + public void consume(){ + System.out.println(String.format("thread id:%d, name:%s start to consume data", + Thread.currentThread().getId(), Thread.currentThread().getName())); + if (Producer.data.size()> 0) { + System.out.println(String.format("consume value: %d", Producer.data.remove())); + } else { + System.out.println("no data"); + } + } + + public void run() { + + for (; ; ) { + try { + this.consume1(); + //this.consume2(); + }catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/lock/src/main/java/cn/tommyyang/demo/lock/Producer.java b/lock/src/main/java/cn/tommyyang/demo/lock/Producer.java new file mode 100644 index 0000000..0af2c71 --- /dev/null +++ b/lock/src/main/java/cn/tommyyang/demo/lock/Producer.java @@ -0,0 +1,74 @@ +package cn.tommyyang.demo.lock; + +import java.util.*; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author TommyYang on 2019年04月06日 + */ +public class Producer implements Runnable { + + public static LinkedList data = new LinkedList(); + public static boolean flag = false; + + private ReentrantLock lock; + private Condition cCondition; + private Condition pCondition; + + public Producer() { + } + + public Producer(ReentrantLock lock, Condition cCondition, Condition pCondition) { + this.lock = lock; + this.cCondition = cCondition; + this.pCondition = pCondition; + } + + public void produce() { + System.out.println(String.format("thread id:%d, name:%s start to produce data", + Thread.currentThread().getId(), Thread.currentThread().getName())); + data.add(1); + } + + public void produce1() throws InterruptedException { + synchronized (data) { + while (flag){ + System.out.println("进入Producer wait"); + data.wait(); + } + this.produce(); + flag = true; + data.notifyAll(); + } + + } + + public void produce2() throws InterruptedException { + try { + this.lock.lock(); + while (flag){ + System.out.println("进入Producer await"); + this.pCondition.await(); + } + this.produce(); + flag = true; + this.cCondition.signalAll(); + } finally { + this.lock.unlock(); + } + + } + + public void run() { + for (; ; ) { + try { + this.produce1(); + //this.produce2(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + } +} diff --git a/lock/src/main/java/cn/tommyyang/demo/lock/ReentrantLockDemo.java b/lock/src/main/java/cn/tommyyang/demo/lock/ReentrantLockDemo.java new file mode 100644 index 0000000..9968c37 --- /dev/null +++ b/lock/src/main/java/cn/tommyyang/demo/lock/ReentrantLockDemo.java @@ -0,0 +1,31 @@ +package cn.tommyyang.demo.lock; + +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author TommyYang on 2019年03月21日 + */ +public class ReentrantLockDemo { + + public void newThread(final ReentrantLock lock, final String name){ + new Thread(new Runnable() { + public void run() { + lock.lock(); + System.out.println(name + ":第一次进入lock"); + try { + Thread.sleep(1000); + System.out.println(name + ":再次进入lock"); + lock.lock(); + } catch (InterruptedException e) { + e.printStackTrace(); + }finally { + lock.unlock(); + System.out.println(name + ":第一次unlock"); + lock.unlock(); + System.out.println(name + ":第二次unlock"); + } + } + }, name).start(); + } + +} diff --git a/lock/src/main/java/cn/tommyyang/demo/lock/SynchronizedDemo.java b/lock/src/main/java/cn/tommyyang/demo/lock/SynchronizedDemo.java new file mode 100644 index 0000000..9cc47cc --- /dev/null +++ b/lock/src/main/java/cn/tommyyang/demo/lock/SynchronizedDemo.java @@ -0,0 +1,22 @@ +package cn.tommyyang.demo.lock; + +/** + * @author TommyYang on 2019年1月28日 + */ +public class SynchronizedDemo implements Runnable { + + public static int common = 0; + + public void run() { + for(int j=0;j<1000000;j++){ + this.add(); + } + } + + public synchronized void add(){ + synchronized (this){ + common++; + } + } + +} diff --git a/lock/src/main/java/cn/tommyyang/distributedlock/RedisDistributedLock.java b/lock/src/main/java/cn/tommyyang/distributedlock/RedisDistributedLock.java new file mode 100644 index 0000000..c5c4fd1 --- /dev/null +++ b/lock/src/main/java/cn/tommyyang/distributedlock/RedisDistributedLock.java @@ -0,0 +1,10 @@ +package cn.tommyyang.distributedlock; + +/** + * @author TommyYang on 2019-05-12 + */ +public class RedisDistributedLock { + + + +} diff --git a/machinelearning/model.md b/machinelearning/model.md new file mode 100644 index 0000000..6ed8ec6 --- /dev/null +++ b/machinelearning/model.md @@ -0,0 +1,391 @@ +# 相关模型介绍 +# 分类模型 + +## 决策树 +决策树(Decision Tree)算法是一种基本的分类与回归方法,是最经常使用的数据挖掘算法之一。我们这章节只讨论用于分类的决策树。 + +决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是 if-then 规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。 + +决策树学习通常包括 3 个步骤: 特征选择、决策树的生成和决策树的修剪。 + +### 决策树场景 +一个叫做 "二十个问题" 的游戏,游戏的规则很简单: 参与游戏的一方在脑海中想某个事物,其他参与者向他提问,只允许提 20 个问题,问题的答案也只能用对或错回答。问问题的人通过推断分解,逐步缩小待猜测事物的范围,最后得到游戏的答案。 + +**一个邮件分类系统,大致工作流程如下**: +![邮件分类](https://blog.tommyyang.cn/img/ml/model/dt_email.png) + +- 首先检测发送邮件域名地址。如果地址为 myEmployer.com, 则将其放在分类 "无聊时需要阅读的邮件"中。 +- 如果邮件不是来自这个域名,则检测邮件内容里是否包含单词 "曲棍球" , 如果包含则将邮件归类到 "需要及时处理的朋友邮件"。 +- 如果不包含则将邮件归类到 "无需阅读的垃圾邮件" 。 + +**假如你了解足球,让你预测世界杯 32 个球队哪支球队是冠军**: +![预测夺冠](https://blog.tommyyang.cn/img/ml/model/dt_32_champion.png) + +- 在 1-16 么?在 +- 在 9-16 么?在 +- 在 13-16 么? 在 +- 在 15 -16 么? 在 +- 是 15 么? 不是 + +答案:16 号球队夺冠。 + +`tips` +最多通过 5 次机会就猜测出哪只球队是冠军球队。这个 5 代表的是 5 bit(比特),相当于是 2^5=32,通过 5 bit 可以描述出这个信息量。 +如果是 64 支球队,那么需要 6 bit 来描述这个信息量。 + +### 信息熵(香农熵) +香农通过世界杯预测冠军的问题提出了信息熵,故信息熵也叫香农熵,信息熵用来度量信息量;信息量的度量等于不确定性的多少。 +通过上述问题描述,相信读者已经了解了信息量的比特数和所有可能情况的对数函数 log 有关。(log32 = 5, log64 = 6)。 + +当然,如果是你对足球比较了解,通过历届球队的实力比较了解,以及世界杯参赛球队球星也比较了解。你可能会对一些球队能力的强弱进行分组, +因为你了解的信息量会多一些,所以猜测的不确定性就会小一些,从而你可能不需要 5 次就可以猜中。故香农提出了如下公式来计算信息熵: +H(x) = -(P1 * logP1 + P2 * logP2 + P3 * logP3 + ... + P32 * logP32), +其中P1、P2、P3...P32 分别代表 32 支球队夺冠的概率。 + +### 信息增益 +信息增益 = 信息熵 - 条件熵。 + +条件熵:在你得知一个信息(条件)后,得出结论还需要多少信息量。 + +信息增益越大,说明你得到的这个信息就越重要。 + +### 决策树定义 +分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成。结点有两种类型: 内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。 + +用决策树对需要测试的实例进行分类: 从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分配到叶结点的类中。 + +### 决策树工作原理 +如何构造一个决策树? + +使用 create_branch() 方法,伪代码如下所示: +``` + +def createBranch(): +''' +此处运用了迭代的思想。 感兴趣可以搜索 迭代 recursion, 甚至是 dynamic programing。 +''' + 检测数据集中的所有数据的分类标签是否相同: + If so return 类标签 + Else: + 寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征) + 划分数据集 + 创建分支节点 + for 每个划分的子集 + 调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中 + return 分支节点 + +``` + +### 决策树开发流程 +- 收集数据: 可以使用任何方法。 +- 准备数据: 树构造算法 (这里使用的是ID3算法,只适用于标称型数据,这就是为什么数值型数据必须离散化。 还有其他的树构造算法,比如CART)。 +- 分析数据: 可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。 +- 训练算法: 构造树的数据结构。 +- 测试算法: 使用训练好的树计算错误率。 +- 使用算法: 此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。 + +### 决策树算法特点 +- 优点: 计算复杂度不高,输出结果易于理解,数据有缺失也能跑,可以处理不相关特征。 +- 缺点: 容易过拟合。 +- 适用数据类型: 数值型和标称型。 + +### 决策树案例分析 +#### 实例一 鱼类非鱼类问题 +决策树如下: +![预测夺冠](https://blog.tommyyang.cn/img/ml/model/dt_fish.png) + +##### 项目概述 +根据以下 2 个特征,将动物分成两类: 鱼类和非鱼类。 + +特征: +- 不浮出水面是否可以生存。 +- 是否有脚蹼(du 第三声)。 + +##### 构造数据集 +``` python +# 构造数据集 +def create_data_set(): + """ + :return: data_set 数据集, labels 标签数组 + """ + data_set = [ + [1, 1, 'yes'], + [1, 1, 'yes'], + [1, 0, 'no'], + [0, 1, 'no'], + [0, 1, 'no'], + [0, 0, 'no'] + ] + + labels = ['no surfacing', 'flippers'] + + return data_set, labels +``` + +##### 计算香农熵(经验熵)shannonEnt +```python + +# 计算香农熵(经验熵)shannonEnt +def calc_shannon_ent(data_set): + """ + + :param data_set: 数据集 + :return: shannon_ent 香农熵 + + """ + + # 计算 list 的长度,表示计算参与训练的数据量 + num_entries = len(data_set) + # 计算分类标签 label 出现的次数 + label_counts = {} + + for feat_vec in data_set: + # 存储当前实例的标签存储,即每一行数据的最后一个数据代表的是标签 + current_label = feat_vec[-1] + # 为所有可能的分类创建字典,如果当前值不存在,则扩展字典并将当前值加入字典。每个键值都记录了当前类别出现的次数。 + if current_label not in label_counts.keys(): + label_counts[current_label] = 0 + label_counts[current_label] += 1 + + # 对于 label 标签的占比,求出 label 标签的香农熵 + shannon_ent = 0.0 + for key in label_counts: + # 使用所有类标签的发生频率计算类别出现的概率 + prob = float(label_counts[key]) / num_entries + # 计算香农熵 + shannon_ent -= prob * log(prob, 2) + + return shannon_ent + +``` + +##### 将指定特征值等于 value 的行剩下列作为子数据集 +``` python +# 将指定特征值等于 value 的行剩下列作为子数据集 +def split_data_set(data_set, index, value): + """ + (通过遍历 data_set 数据集,求出 index 对应的 column 列的值为 value 的行) + 就是依据 index 列进行分类,如果index列的数据等于 value 的时候,就要将 index 划分到我们创建的新的数据集中 + + :param data_set: 数据集 待划分的数据集 + :param index: 每一行的 index 列 划分数据集的特征 + :param value: index 列对应的 value 值 需要返回的特征的值 + :return: + ret_data_set 指定特征值等于 value 的行剩下列(不包含值等于该 value 的列)作为子数据集 + """ + ret_data_set = [] + for feat_vec in data_set: + # index 列为 value 的数据集[该数据集需要排除 index 列] + # 判断 index 列的值是否为 value + if feat_vec[index] == value: + # chop out index used for splitting + # [:index]表示前 index 行,即若 index 为 2,就是取 feat_vec 的前 2 行 + reduced_feat_vec = feat_vec[:index] + ''' + 请百度查询一下: extend和append的区别 + music_media.append(object) 向列表中添加一个对象object + music_media.extend(sequence) 把一个序列seq的内容添加到列表中 (跟 += 在list运用类似, music_media += sequence) + 1、使用append的时候,是将object看作一个对象,整体打包添加到music_media对象中。 + 2、使用extend的时候,是将sequence看作一个序列,将这个序列和music_media序列合并,并放在其后面。 + music_media = [] + music_media.extend([1,2,3]) + print music_media + #结果: + #[1, 2, 3] + + music_media.append([4,5,6]) + print music_media + #结果: + #[1, 2, 3, [4, 5, 6]] + + music_media.extend([7,8,9]) + print music_media + #结果: + #[1, 2, 3, [4, 5, 6], 7, 8, 9] + ''' + # 取 index+1 行开始,后面的所有行数据 + reduced_feat_vec.extend(feat_vec[index + 1:]) + + # 收集结果值 index 列为 value 的行[该行需要排除 index 列] + ret_data_set.append(reduced_feat_vec) + return ret_data_set +``` + +##### 选择信息增益最大的特征列 +```python + +def choose_best_feature_to_split(data_set): + """ + :param data_set: 数据集 + :return: + best_feature 最优的特征列 + """ + # 求第一行有多少列的 Feature, 最后一列是 label 列 + num_features = len(data_set[0]) - 1 + # 数据集的原始信息熵 + base_entropy = calc_shannon_ent(data_set) + # 最优的信息增益值, 和最优的 feature 编号 + best_info_gain, best_feature = 0.0, -1 + + # iterate over all the features + for i in range(num_features): + # create a list of all the examples of this feature + # 获取对应的 feature 下的所有数据 + feat_list = [example[i] for example in data_set] + # get a set of unique values + # 获取剔重后的集合,使用set对list数据进行去重 + unique_vals = set(feat_list) + print("unique_vals:", unique_vals) + # 创建一个临时的信息熵 + new_entropy = 0.0 + # 遍历某一列的 value 集合,计算该列的信息熵 + # 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集,计算数据集的新熵值,并对所有唯一特征值得到的熵求和。 + for val in unique_vals: + sub_data_set = split_data_set(data_set, i, val) + # 计算概率 + prob = len(sub_data_set) / float(len(data_set)) + print("prob:", prob) + # 计算信息熵 + new_entropy += prob * calc_shannon_ent(sub_data_set) + print("new_entropy:", new_entropy) + # gain[信息增益]: 划分数据集前后的信息变化, 获取信息熵最大的值 + # 信息增益是熵的减少或者是数据无序度的减少。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。 + info_gain: float = base_entropy - new_entropy + print('info_gain=', info_gain, 'best_feature=', i, base_entropy, new_entropy) + if info_gain> best_info_gain: + best_info_gain = info_gain + best_feature = i + return best_feature + +``` + +##### 构建决策树 +``` python + +def create_tree(data_set, labels): + """ + :param data_set: 数据集 + :param labels: label 标签 + :return: 返回决策树 + """ + class_list = [example[-1] for example in data_set] + # 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行 + # 第一个停止条件: 所有的类标签完全相同,则直接返回该类标签。 + # count() 函数是统计括号中的值在list中出现的次数 + if class_list.count(class_list[0]) == len(class_list): + return class_list[0] + # 如果数据集只有 1 列,那么最初出现 label 次数最多的一类,作为结果 + # 第二个停止条件: 使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。 + if len(data_set[0]) == 1: + return majority_cnt(class_list) + # 选择最优的列,得到最优列对应的label含义 + best_feat = choose_best_feature_to_split(data_set) + # 获取label的名称 + best_feat_label = labels[best_feat] + # 初始化myTree + my_tree = {best_feat_label: {}} + # 注: labels列表是可变对象,在PYTHON函数中作为参数时传址引用,能够被全局修改 + # 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list + del (labels[best_feat]) + # 取出最优列,然后它的branch做分类 + feat_vals = [example[best_feat] for example in data_set] + unique_vals = set(feat_vals) + for val in unique_vals: + # 求出剩余的标签label + sub_labels = labels[:] + # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数create_tree() + my_tree[best_feat_label][val] = create_tree(split_data_set(data_set, best_feat, val), sub_labels) + # print('myTree', val, my_tree) + return my_tree + +``` + +##### 通过输入的特征,预测分类 +``` python + +def classify(input_tree, feat_labels, test_vec): + """ + 给输入的节点,进行分类 + :param input_tree: 决策树模型 + :param feat_labels: feature标签对应的名称 + :param test_vec: 测试输入的数据 + :return: + class_label 分类的结果值,需要映射 label 才能知道名称 + """ + # 获取 tree 的根节点对于的 key 值 + first_str = list(input_tree.keys())[0] + # 通过key得到根节点对应的value + second_dict = input_tree[first_str] + # 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类 + feat_index = feat_labels.index(first_str) + # 测试数据,找到根节点对应的label位置,也就知道从输入的数据的第几位来开始分类 + key = test_vec[feat_index] + feat_val = second_dict[key] + print('+++', first_str, 'xxx', second_dict, '---', key, '>>>', feat_val) + # 判断分枝是否结束: 判断 feat_val 是否是 dict 类型 + if isinstance(feat_val, dict): + class_label = classify(feat_val, feat_labels, test_vec) + else: + class_label = feat_val + return class_label + +``` + +##### 选择出现次数最多的一个结果 +``` python + +def majority_cnt(class_list): + """ + 选择出现次数最多的一个结果 + :param class_list: label 列的集合 + :return: best_feature 最优的特征列 + """ + # -----------majorityCnt的第一种方式 start------------------------------------ + class_count = {} + for vote in class_list: + if vote not in class_count.keys(): + class_count[vote] = 0 + class_count[vote] += 1 + # 倒叙排列classCount得到一个字典集合,然后取出第一个就是结果(yes/no),即出现次数最多的结果 + sorted_class_count = sorted(class_count.iteritems(), key=operator.itemgetter(1), reverse=True) + print('sortedClassCount:', sorted_class_count) + return sorted_class_count[0][0] + # -----------majorityCnt的第一种方式 end------------------------------------ + + # # -----------majorityCnt的第二种方式 start------------------------------------ + # major_label = Counter(classList).most_common(1)[0] + # return major_label + # # -----------majorityCnt的第二种方式 end------------------------------------ + +``` + + +## GBDT +GBDT: Gradient Boost Decision Tree。DT-Decision Tree决策树,GB是Gradient Boosting,是一种学习策略,GBDT的含义就是用Gradient Boosting的策略训练出来的DT模型。可以处理二分类问题。 + + +## LightGBM + + +## XGBoost +### 分类树与回归树 +XGBoost是以CART回归树作为基本分类器。 +分类树:分类树的样本输出都是以类别的形式,比如说判断用户会不会购买华为Mate40,判断西瓜是甜还是不甜。 + +回归树:回归树的样本输出是数值的形式,比如给某人发放房屋贷款的数额,给某人发放的红包金额。 + +### Boost介绍 +Boost可以用于回归和分类问题,它每一步会产生一个弱分类器 (如决策树),然后通过加权累加起来变成一个强分类器。比如每一步都会产生一个f(x),F(x)=sum(fi(x)),其实就是一堆分类器通过加权合并成一个强分类器。 + +### 提升树 +首先要明确一点,xgboost 是基于提升树的。 + +什么是提升树,简单说,就是一个模型表现不好,我继续按照原来模型表现不好的那部分训练第二个模型,依次类推。 + +来几个形象的比喻就是: + +做题的时候,第一个人做一遍得到一个分数,第二个人去做第一个人做错的题目,第三个人去做第二个人做错的题目,以此类推,不停的去拟合从而可以使整张试卷分数可以得到100分(极端情况)。 + +把这个比喻替换到模型来说,就是真实值为100,第一个模型预测为90,差10分,第二个模型以10为目标值去训练并预测,预测值为7,差三分,第三个模型以3为目标值去训练并预测,以此类推。 + +## Random Forests diff --git a/machinelearning/recommend_system.md b/machinelearning/recommend_system.md new file mode 100644 index 0000000..d0ebaba --- /dev/null +++ b/machinelearning/recommend_system.md @@ -0,0 +1,53 @@ +# 推荐系统整理 +该篇主要对推荐系统进行一些整理。 + +## 推荐系统涉及到的一些模块 +核心模块有用户、Item(包括文章、视频、商品、音乐、电影等)、用户实时画像、Item 特征等。 +核心服务有召回服务、推荐服务、排序服务等。 + +如下图: +![相关模块](https://blog.tommyyang.cn/img/architecture/recommend-module.png) + +比较重要就是要去了解 NLP(自然语言处理),基于 NLP 的特征分析。基于模型的排序服务,比如决策树、FM 模型、FFM 模型、双线性 FFM 模型、DNN、Wide&Deep 等。排序模型个人觉得讲的比较好的一篇文章,[推荐排序模型](https://www.infoq.cn/article/vKoKh_ZDXcWRh8fLSsRp)。 + +## 推荐模型时序图 +![时序图](https://blog.tommyyang.cn/img/architecture/recommend-time.png) + +- 1.2.3 和 1.2.4 应该是在一步同时异步执行的 +- 1.3 获取文章title、content、发布时间、作者等相关信息 +- 1.2.3 里面包括 label 画像召回、LDA(Topic) 画像召回、用户与作者亲密度召回(我理解为基于社交关系的召回,这一类文章加上推荐理由效果会更好) + +tips: 为什么 Doc2vec 画像单独弄成一个微服务,因为 vector 是一个 300 维的 float 类型的数组,计算量特别大;所以做成 T+1 模式,提前计算好,然后索引到文件中。(使用的是开源的 Annoy 进行文件索引)。 + +## 实时画像时序图 +![实时画像时序图](https://blog.tommyyang.cn/img/architecture/recommend-profile-time.png) + +- 流处理框架使用当下比较流行的 Flink。 +- Flink 支持批量和流处理两种模式。 +- 批量计算时只是对接数据源不一样,数据仓库大多数为 Hive。 + +## 数据分析 +通过之前做过的一些视频、文章推荐举例。 + +- 多策略召回。 +- Rank 模型训练特征由离线转为实时。 +- 特征精细化,比如由文章总体 CTR 变为每个策略下的 CTR;不同分类下文章的时效性不一样,所以 CTR 计算周期不一样,比如有的文章在一开始 CTR 特别高,但随着时间变化,慢慢降低,但是相比其它的一些文章,还是很高,这时候,就需要调整 CTR 计算周期。 +- 分析重要的特征加入到训练模型,比如对文章来说:CTR、CDR;统计用户在每个分类下的 CTR、CDR,可以更加具体的描述出用户对不同分类的喜欢程度。 + +## 名词介绍 +- CTR:Click-Through-Rate,点击通过率。 +- CDR:Completion-reaDing-Rate,阅读完成率。 + +## 文章推荐 +- [从 FFM 到 DeepFFM,推荐排序模型到底哪家强?](https://www.infoq.cn/article/vKoKh_ZDXcWRh8fLSsRp) +- [NLP](https://easyai.tech/ai-definition/nlp/) +- [LDA](https://www.hankcs.com/nlp/lda-java-introduction-and-implementation.html) + + + + + + + + + diff --git a/machinelearning/tensorflow.md b/machinelearning/tensorflow.md new file mode 100644 index 0000000..4b0e209 --- /dev/null +++ b/machinelearning/tensorflow.md @@ -0,0 +1,2 @@ +# tensorflow 入门 +## 环境搭建 \ No newline at end of file diff --git a/mymp.jpeg b/mymp.jpeg new file mode 100644 index 0000000..317c843 Binary files /dev/null and b/mymp.jpeg differ diff --git a/pom.xml b/pom.xml index 53b27a3..0742932 100644 --- a/pom.xml +++ b/pom.xml @@ -9,9 +9,17 @@ pom 1.0-SNAPSHOT - SortPro - KeyWords - Web + sortpro + web + lock + bigdata + codeinterview + io + test + javabase/datastructure + architecture + javabase/java8 + javabase/keywords diff --git a/SortPro/1.bubbleSort.md b/sortpro/1.bubbleSort.md similarity index 100% rename from SortPro/1.bubbleSort.md rename to sortpro/1.bubbleSort.md diff --git a/SortPro/10.radixSort.md b/sortpro/10.radixSort.md similarity index 100% rename from SortPro/10.radixSort.md rename to sortpro/10.radixSort.md diff --git a/SortPro/2.selectionSort.md b/sortpro/2.selectionSort.md similarity index 100% rename from SortPro/2.selectionSort.md rename to sortpro/2.selectionSort.md diff --git a/SortPro/3.insertionSort.md b/sortpro/3.insertionSort.md similarity index 100% rename from SortPro/3.insertionSort.md rename to sortpro/3.insertionSort.md diff --git a/SortPro/4.shellSort.md b/sortpro/4.shellSort.md similarity index 100% rename from SortPro/4.shellSort.md rename to sortpro/4.shellSort.md diff --git a/SortPro/5.mergeSort.md b/sortpro/5.mergeSort.md similarity index 100% rename from SortPro/5.mergeSort.md rename to sortpro/5.mergeSort.md diff --git a/SortPro/6.quickSort.md b/sortpro/6.quickSort.md similarity index 100% rename from SortPro/6.quickSort.md rename to sortpro/6.quickSort.md diff --git a/SortPro/7.heapSort.md b/sortpro/7.heapSort.md similarity index 100% rename from SortPro/7.heapSort.md rename to sortpro/7.heapSort.md diff --git a/SortPro/8.countingSort.md b/sortpro/8.countingSort.md similarity index 100% rename from SortPro/8.countingSort.md rename to sortpro/8.countingSort.md diff --git a/SortPro/9.bucketSort.md b/sortpro/9.bucketSort.md similarity index 100% rename from SortPro/9.bucketSort.md rename to sortpro/9.bucketSort.md diff --git a/SortPro/README.md b/sortpro/README.md similarity index 88% rename from SortPro/README.md rename to sortpro/README.md index ab1250e..37bb282 100644 --- a/SortPro/README.md +++ b/sortpro/README.md @@ -1,5 +1,7 @@ -# Java面试--10大经典排序 +# 排序篇 + 排序算法是《数据结构与算法》中最基本的算法之一。 +因为排序在日常编程中是出现的最频繁的一种算法,所以单独用一个篇章来整理。 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:**插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序**等。用一张图概括: @@ -55,4 +57,4 @@ ---- -本项目内容几乎完全来源于网络,动图演示感谢hustcc,来自于hustcc的项目。 \ No newline at end of file +本篇内容几乎完全来源于网络,动图演示感谢hustcc,来自于hustcc的项目。 \ No newline at end of file diff --git a/SortPro/pom.xml b/sortpro/pom.xml similarity index 100% rename from SortPro/pom.xml rename to sortpro/pom.xml diff --git a/SortPro/res/bubbleSort.gif b/sortpro/res/bubbleSort.gif similarity index 100% rename from SortPro/res/bubbleSort.gif rename to sortpro/res/bubbleSort.gif diff --git a/SortPro/res/countingSort.gif b/sortpro/res/countingSort.gif similarity index 100% rename from SortPro/res/countingSort.gif rename to sortpro/res/countingSort.gif diff --git a/SortPro/res/heapSort.gif b/sortpro/res/heapSort.gif similarity index 100% rename from SortPro/res/heapSort.gif rename to sortpro/res/heapSort.gif diff --git a/SortPro/res/insertionSort.gif b/sortpro/res/insertionSort.gif similarity index 100% rename from SortPro/res/insertionSort.gif rename to sortpro/res/insertionSort.gif diff --git a/SortPro/res/mergeSort.gif b/sortpro/res/mergeSort.gif similarity index 100% rename from SortPro/res/mergeSort.gif rename to sortpro/res/mergeSort.gif diff --git a/SortPro/res/quickSort.gif b/sortpro/res/quickSort.gif similarity index 100% rename from SortPro/res/quickSort.gif rename to sortpro/res/quickSort.gif diff --git a/SortPro/res/radixSort.gif b/sortpro/res/radixSort.gif similarity index 100% rename from SortPro/res/radixSort.gif rename to sortpro/res/radixSort.gif diff --git a/SortPro/res/selectionSort.gif b/sortpro/res/selectionSort.gif similarity index 100% rename from SortPro/res/selectionSort.gif rename to sortpro/res/selectionSort.gif diff --git a/SortPro/res/sort.png b/sortpro/res/sort.png similarity index 100% rename from SortPro/res/sort.png rename to sortpro/res/sort.png diff --git a/SortPro/src/main/java/Run.java b/sortpro/src/main/java/Run.java similarity index 93% rename from SortPro/src/main/java/Run.java rename to sortpro/src/main/java/Run.java index 713b133..bad6e82 100644 --- a/SortPro/src/main/java/Run.java +++ b/sortpro/src/main/java/Run.java @@ -14,6 +14,7 @@ public static void main(String[] args) { IArraySort shellSort = new ShellSort(); IArraySort mergeSort = new MergeSort(); IArraySort quickSort = new QuickSort(); + IArraySort quickSort1 = new QuickSort1(); IArraySort heapSort = new HeapSort(); IArraySort bucketSort = new BucketSort(); IArraySort radixSort = new RadixSort(); @@ -27,7 +28,7 @@ public static void main(String[] args) { //int[] desArr = quickSort.sort(arr); //int[] desArr = heapSort.sort(arr); //int[] desArr = bucketSort.sort(arr); - int[] desArr = radixSort.sort(arr); + int[] desArr = quickSort1.sort(arr); System.out.println("排序结果:"); for (Integer i : desArr) { System.out.print(i); diff --git a/sortpro/src/main/java/search/BinarySearch.java b/sortpro/src/main/java/search/BinarySearch.java new file mode 100644 index 0000000..a94c405 --- /dev/null +++ b/sortpro/src/main/java/search/BinarySearch.java @@ -0,0 +1,40 @@ +package search; + +/** + * @author TommyYang on 2019年04月12日 + */ +public class BinarySearch { + + //循环实现 + public static int binarySearch(int[] arr, int target){ + int low = 0, high = arr.length - 1; + + while (low <= high){ + int mid = low + (high - low) / 2; + if (arr[mid] < target) low = mid + 1; + else if (arr[mid]> target) high = mid - 1; + else return mid; + } + + return -1; + } + + //递归实现 + public static int binarySearchRecur(int[] arr, int target, int low, int high){ + + if (low> high){ + return -1; + } + + int mid = low + (high - low) / 2; + + if(arr[mid] < target){ + return binarySearchRecur(arr, target, mid + 1, high); + } else if(arr[mid]> target){ + return binarySearchRecur(arr, target, low, mid - 1); + } else { + return mid; + } + } + +} diff --git a/SortPro/src/main/java/sort/BubbleSort.java b/sortpro/src/main/java/sort/BubbleSort.java similarity index 100% rename from SortPro/src/main/java/sort/BubbleSort.java rename to sortpro/src/main/java/sort/BubbleSort.java diff --git a/SortPro/src/main/java/sort/BucketSort.java b/sortpro/src/main/java/sort/BucketSort.java similarity index 100% rename from SortPro/src/main/java/sort/BucketSort.java rename to sortpro/src/main/java/sort/BucketSort.java diff --git a/SortPro/src/main/java/sort/CountingSort.java b/sortpro/src/main/java/sort/CountingSort.java similarity index 100% rename from SortPro/src/main/java/sort/CountingSort.java rename to sortpro/src/main/java/sort/CountingSort.java diff --git a/SortPro/src/main/java/sort/HeapSort.java b/sortpro/src/main/java/sort/HeapSort.java similarity index 100% rename from SortPro/src/main/java/sort/HeapSort.java rename to sortpro/src/main/java/sort/HeapSort.java diff --git a/SortPro/src/main/java/sort/IArraySort.java b/sortpro/src/main/java/sort/IArraySort.java similarity index 100% rename from SortPro/src/main/java/sort/IArraySort.java rename to sortpro/src/main/java/sort/IArraySort.java diff --git a/SortPro/src/main/java/sort/InsertSort.java b/sortpro/src/main/java/sort/InsertSort.java similarity index 100% rename from SortPro/src/main/java/sort/InsertSort.java rename to sortpro/src/main/java/sort/InsertSort.java diff --git a/SortPro/src/main/java/sort/MergeSort.java b/sortpro/src/main/java/sort/MergeSort.java similarity index 100% rename from SortPro/src/main/java/sort/MergeSort.java rename to sortpro/src/main/java/sort/MergeSort.java diff --git a/SortPro/src/main/java/sort/QuickSort.java b/sortpro/src/main/java/sort/QuickSort.java similarity index 100% rename from SortPro/src/main/java/sort/QuickSort.java rename to sortpro/src/main/java/sort/QuickSort.java diff --git a/sortpro/src/main/java/sort/QuickSort1.java b/sortpro/src/main/java/sort/QuickSort1.java new file mode 100644 index 0000000..a6170a0 --- /dev/null +++ b/sortpro/src/main/java/sort/QuickSort1.java @@ -0,0 +1,57 @@ +package sort; + +import java.util.Arrays; + +/** + * @author TommyYang on 2019年04月12日 + */ +public class QuickSort1 implements IArraySort { + + public void quickSort(int[] arr, int left, int right) { + + if(left>= right){ + return; + } + + int partition = partition(arr, left, right); + + quickSort(arr, left, partition - 1); + quickSort(arr, partition + 1, right); + + } + + + public int partition(int[] arr, int i, int j) { + int pivot = arr[i]; + while (i < j) { + while (i < j && arr[j]> pivot) + j--; + if (i < j) { + arr[i] = arr[j]; + i++; + } + + while (i < j && arr[i] < pivot) + i++; + + if (i < j) { + arr[j] = arr[i]; + j--; + } + } + + arr[i] = pivot; + return i; + + } + + @Override + public int[] sort(int[] sourArr) throws Exception { + // 对 arr 进行拷贝,不改变参数内容 + int[] arr = Arrays.copyOf(sourArr, sourArr.length); + + quickSort(arr, 0, arr.length - 1); + + return arr; + } +} diff --git a/SortPro/src/main/java/sort/RadixSort.java b/sortpro/src/main/java/sort/RadixSort.java similarity index 100% rename from SortPro/src/main/java/sort/RadixSort.java rename to sortpro/src/main/java/sort/RadixSort.java diff --git a/SortPro/src/main/java/sort/SelectionSort.java b/sortpro/src/main/java/sort/SelectionSort.java similarity index 100% rename from SortPro/src/main/java/sort/SelectionSort.java rename to sortpro/src/main/java/sort/SelectionSort.java diff --git a/SortPro/src/main/java/sort/ShellSort.java b/sortpro/src/main/java/sort/ShellSort.java similarity index 100% rename from SortPro/src/main/java/sort/ShellSort.java rename to sortpro/src/main/java/sort/ShellSort.java diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 0000000..0805452 --- /dev/null +++ b/test/pom.xml @@ -0,0 +1,89 @@ + + + + javainterview + cn.tommyyang + 1.0-SNAPSHOT + + 4.0.0 + + test + + + + io.grpc + grpc-netty-shaded + 1.20.0 + + + io.grpc + grpc-protobuf + 1.20.0 + + + io.grpc + grpc-stub + 1.20.0 + + + com.alibaba + fastjson + 1.2.60 + + + commons-beanutils + commons-beanutils + 1.8.0 + + + org.apache.commons + commons-collections4 + 4.1 + + + + + + + kr.motd.maven + os-maven-plugin + 1.5.0.Final + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.5.1 + + ${project.basedir}/src/main/protos + com.google.protobuf:protoc:3.6.1:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:1.19.0:exe:${os.detected.classifier} + ${project.basedir}/src/main/java/ + false + + + + + compile + compile-custom + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/test/src/main/java/HiveTableChange.java b/test/src/main/java/HiveTableChange.java new file mode 100644 index 0000000..92d1115 --- /dev/null +++ b/test/src/main/java/HiveTableChange.java @@ -0,0 +1,77 @@ +import java.util.stream.IntStream; + +/** + * @Author : TommyYang + * @Time : 2019年08月22日 11:29 + * @Software: IntelliJ IDEA + * @File : HiveTableChange.java + */ +public class HiveTableChange { + + // CASCADE; add or replace column with CASCADE will update table and partition metadata; + + public static void main(String[] args) { + StringBuilder sBuilder = new StringBuilder("ALTER TABLE hdfs.rank_user_features ADD COLUMNS ("); +// StringBuilder sBuilder = new StringBuilder("CREATE EXTERNAL TABLE `hdfs.rank_user_features`("); + builder(sBuilder, "add"); + + System.out.println(sBuilder.toString()); + } + + private static void builder(StringBuilder sBuilder, String mode) { + if (!mode.equals("add")) { + sBuilder.append("`batch_id` string, ").append(" `gender` int, ") + .append("`entity_type` string, ").append("`entity_id` string, "); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`click_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`impr_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`read_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + } + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`latest_click_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`latest_impr_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`latest_read_label%d` int", index); + if (index < 85) { + sBuilder.append(latesClickStr).append(","); + } else { + sBuilder.append(latesClickStr); + } + }); + + if (mode.equals("add")){ + sBuilder.append(") CASCADE;"); + } else if (mode.equals("replace")) { + sBuilder.append(",dt string) CASCADE;"); + } else if (mode.equals("create")) { + sBuilder.append(")\n"); + sBuilder.append("PARTITIONED BY (`dt` string)\n") + .append("ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'\n") + .append("WITH SERDEPROPERTIES ('field.delim'='\\t', 'serialization.format'='\\t')\n") + .append("STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'\n") + .append("OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'\n") + .append("LOCATION 'hdfs://ns1/data/frontpagenote_recommend_records/user_features';"); + } + + } + +} diff --git a/test/src/main/java/StringUitls.java b/test/src/main/java/StringUitls.java new file mode 100644 index 0000000..f4807b6 --- /dev/null +++ b/test/src/main/java/StringUitls.java @@ -0,0 +1,27 @@ +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Stream; + +/** + * @Author : TommyYang + * @Time : 2019-09-17 16:23 + * @Software: IntelliJ IDEA + * @File : StringUitls.java + */ +public class StringUitls { + + public static void main(String[] args) throws IOException { + Stream lines = Files.lines(Paths.get("/Users/tommy/Documents/aa.txt"), Charset.defaultCharset()); + lines.forEach(line -> { +// System.out.println(line.trim()); + int start = line.trim().indexOf(">"); + int end = line.trim().lastIndexOf("<"); + String value = line.trim().substring(start + 1, end); + line = line.trim().replace("