未验证 提交 b539ce06 编写于 作者: J jiahaochang 提交者: GitHub

Merge pull request #4 from CyC2018/master

更新
...@@ -42,6 +42,10 @@ ...@@ -42,6 +42,10 @@
整理自《图解 HTTP》 整理自《图解 HTTP》
> [Socket](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Socket.md)
整理自《Unix 网络编程》
## 面向对象 :couple: ## 面向对象 :couple:
...@@ -63,6 +67,10 @@ ...@@ -63,6 +67,10 @@
整理自《SQL 必知必会》 整理自《SQL 必知必会》
> [Leetcode-Database 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode-Database%20题解.md)
Leetcode 上数据库题目的解题记录。
> [MySQL](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/MySQL.md) > [MySQL](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/MySQL.md)
整理自《高性能 MySQL》 整理自《高性能 MySQL》
...@@ -73,13 +81,17 @@ ...@@ -73,13 +81,17 @@
## Java :coffee: ## Java :coffee:
> [Java 基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20基础.md)
整理了一些常见考点。
> [Java 虚拟机](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20虚拟机.md) > [Java 虚拟机](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20虚拟机.md)
整理自《深入理解 Java 虚拟机》 整理自《深入理解 Java 虚拟机》
> [Java 并发](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20并发.md) > [Java 并发](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20并发.md)
只整理了一些比较基础的概念,之后会继续添加更多内容 整理了一些并发的基本概念
> [Java 容器](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20容器.md) > [Java 容器](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20容器.md)
...@@ -89,10 +101,6 @@ ...@@ -89,10 +101,6 @@
File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
> [Java 基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20基础.md)
整理了一些常见考点。
> [JDK 中的设计模式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/JDK%20中的设计模式.md) > [JDK 中的设计模式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/JDK%20中的设计模式.md)
对每种设计模式做了一个总结,并给出在 JDK 中的使用实例。 对每种设计模式做了一个总结,并给出在 JDK 中的使用实例。
...@@ -103,10 +111,6 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO ...@@ -103,10 +111,6 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
整理自《大规模分布式存储系统》 整理自《大规模分布式存储系统》
> [一致性协议](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/一致性协议.md)
两阶段提交、Paxos、Raft、拜占庭将军问题。
> [分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md) > [分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md)
分布式事务、负载均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。 分布式事务、负载均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。
......
...@@ -129,7 +129,7 @@ HEAD is now at 049d078 added the index file (To restore them type "git stash app ...@@ -129,7 +129,7 @@ HEAD is now at 049d078 added the index file (To restore them type "git stash app
# SSH 传输设置 # SSH 传输设置
Git 仓库和 Github 中心仓库之间是通过 SSH 加密。 Git 仓库和 Github 中心仓库之间的传输是通过 SSH 加密。
如果工作区下没有 .ssh 目录,或者该目录下没有 id_rsa 和 id_rsa.pub 这两个文件,可以通过以下命令来创建 SSH Key: 如果工作区下没有 .ssh 目录,或者该目录下没有 id_rsa 和 id_rsa.pub 这两个文件,可以通过以下命令来创建 SSH Key:
...@@ -143,9 +143,9 @@ $ ssh-keygen -t rsa -C "youremail@example.com" ...@@ -143,9 +143,9 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
忽略以下文件: 忽略以下文件:
1. 操作系统自动生成的文件,比如缩略图; - 操作系统自动生成的文件,比如缩略图;
2. 编译生成的中间文件,比如 Java 编译产生的 .class 文件; - 编译生成的中间文件,比如 Java 编译产生的 .class 文件;
3. 自己的敏感信息,比如存放口令的配置文件。 - 自己的敏感信息,比如存放口令的配置文件。
不需要全部自己编写,可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。 不需要全部自己编写,可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。
......
此差异已折叠。
...@@ -138,7 +138,7 @@ java.util.Enumeration ...@@ -138,7 +138,7 @@ java.util.Enumeration
## 5. 中间人模式 ## 5. 中间人模式
使用中间人对象来封装对象之间的交互。中间人模式可以降低交互对象之间的耦合程度。 使用中间人对象来封装对象之间的交互。中间人模式可以降低交互对象之间的耦合程度。
```java ```java
java.util.Timer java.util.Timer
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
* [通道与缓冲区](#通道与缓冲区) * [通道与缓冲区](#通道与缓冲区)
* [缓冲区状态变量](#缓冲区状态变量) * [缓冲区状态变量](#缓冲区状态变量)
* [文件 NIO 实例](#文件-nio-实例) * [文件 NIO 实例](#文件-nio-实例)
* [选择器](#选择器)
* [套接字 NIO 实例](#套接字-nio-实例) * [套接字 NIO 实例](#套接字-nio-实例)
* [内存映射文件](#内存映射文件) * [内存映射文件](#内存映射文件)
* [对比](#对比) * [对比](#对比)
...@@ -74,10 +75,16 @@ byte[] bytes = str.getBytes(encoding); // 编码 ...@@ -74,10 +75,16 @@ byte[] bytes = str.getBytes(encoding); // 编码
String str = new String(bytes, encoding) // 解码 String str = new String(bytes, encoding) // 解码
``` ```
GBK 编码中,中文占 2 个字节,英文占 1 个字节;UTF-8 编码中,中文占 3 个字节,英文占 1 个字节;Java 使用双字节编码 UTF-16be,中文和英文都占 2 个字节。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。 如果编码和解码过程使用不同的编码方式那么就出现了乱码。
- GBK 编码中,中文占 2 个字节,英文占 1 个字节;
- UTF-8 编码中,中文占 3 个字节,英文占 1 个字节;
- UTF-16be 编码中,中文和英文都占 2 个字节。
UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码正是为了让一个中文或者一个英文都能使用一个 char 来存储。
# 五、对象操作 # 五、对象操作
序列化就是将一个对象转换成字节序列,方便存储和传输。 序列化就是将一个对象转换成字节序列,方便存储和传输。
...@@ -148,15 +155,19 @@ is.close(); ...@@ -148,15 +155,19 @@ is.close();
# 七、NIO # 七、NIO
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。 - [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
- [Java NIO 浅析](https://tech.meituan.com/nio.html)
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
## 流与块 ## 流与块
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
面向流的 I/O 一次处理一个字节数据,一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。 面向流的 I/O 一次处理一个字节数据,一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。
一个面向块的 I/O 系统以块的形式处理数据,一次处理一个数据块。按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。 面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。 I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
...@@ -177,7 +188,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重 ...@@ -177,7 +188,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
### 2. 缓冲区 ### 2. 缓冲区
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。 发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。 缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
...@@ -203,11 +214,11 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重 ...@@ -203,11 +214,11 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
<div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br> <div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br>
② 从输入通道中读取 3 个字节数据写入缓冲区中,此时 position 移动设为 3,limit 保持不变。 ② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 移动设置为 5,limit 保持不变。
<div align="center"> <img src="../pics//4628274c-25b6-4053-97cf-d1239b44c43d.png"/> </div><br> <div align="center"> <img src="../pics//80804f52-8815-4096-b506-48eef3eed5c6.png"/> </div><br>
以下图例为已经从输入通道读取了 5 个字节数据写入缓冲区中。在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。 ③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
<div align="center"> <img src="../pics//952e06bd-5a65-4cab-82e4-dd1536462f38.png"/> </div><br> <div align="center"> <img src="../pics//952e06bd-5a65-4cab-82e4-dd1536462f38.png"/> </div><br>
...@@ -221,153 +232,206 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重 ...@@ -221,153 +232,206 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
## 文件 NIO 实例 ## 文件 NIO 实例
① 为要读取的文件创建 FileInputStream,之后通过 FileInputStream 获取输入 FileChannel; 以下展示了使用 NIO 快速复制文件的实例:
```java ```java
FileInputStream fin = new FileInputStream("readandshow.txt"); public class FastCopyFile {
FileChannel fic = fin.getChannel(); public static void main(String args[]) throws Exception {
```
String inFile = "data/abc.txt";
② 创建一个容量为 1024 的 Buffer; String outFile = "data/abc-copy.txt";
```java // 获得源文件的输入字节流
ByteBuffer buffer = ByteBuffer.allocate(1024); FileInputStream fin = new FileInputStream(inFile);
``` // 获取输入字节流的文件通道
FileChannel fcin = fin.getChannel();
③ 将数据从输入 FileChannel 写入到 Buffer 中,如果没有数据的话,read() 方法会返回 -1;
// 获取目标文件的输出字节流
```java FileOutputStream fout = new FileOutputStream(outFile);
int r = fcin.read(buffer); // 获取输出字节流的通道
if (r == -1) { FileChannel fcout = fout.getChannel();
break;
// 为缓冲区分配 1024 个字节
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
// 从输入通道中读取数据到缓冲区中
int r = fcin.read(buffer);
// read() 返回 -1 表示 EOF
if (r == -1)
break;
// 切换读写
buffer.flip();
// 把缓冲区的内容写入输出文件中
fcout.write(buffer);
// 清空缓冲区
buffer.clear();
}
}
} }
``` ```
④ 为要写入的文件创建 FileOutputStream,之后通过 FileOutputStream 获取输出 FileChannel ## 选择器
```java 一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去检查多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。
FileOutputStream fout = new FileOutputStream("writesomebytes.txt");
FileChannel foc = fout.getChannel();
```
⑤ 调用 flip() 切换读写 因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件具有更好的性能。
```java <div align="center"> <img src="../pics//4d930e22-f493-49ae-8dff-ea21cd6895dc.png"/> </div><br>
buffer.flip();
```
⑥ 把 Buffer 中的数据读取到输出 FileChannel 中 ### 1. 创建选择器
```java ```java
foc.write(buffer); Selector selector = Selector.open();
``` ```
⑦ 最后调用 clear() 重置缓冲区 ### 2. 将通道注册到选择器上
```java ```java
buffer.clear(); ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
``` ```
## 套接字 NIO 实例 通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它时间,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。
### 1. ServerSocketChannel 在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类:
每一个监听端口都需要有一个 ServerSocketChannel 用来监听连接。 - SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
```java 它们在 SelectionKey 的定义如下:
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // 设置为非阻塞
ServerSocket ss = ssc.socket(); ```java
InetSocketAddress address = new InetSocketAddress(ports[i]); public static final int OP_READ = 1 << 0;
ss.bind(address); // 绑定端口号 public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
``` ```
### 2. Selectors 可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如:
异步 I/O 通过 Selector 注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接等等,在发生这样的事件时,系统将会发送通知。
创建 Selectors 之后,就可以对不同的通道对象调用 register() 方法。register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 ACCEPT 事件,也就是在新的连接建立时所发生的事件。
SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。
```java ```java
Selector selector = Selector.open(); int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
``` ```
### 3. 主循环 ### 3. 监听事件
首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时,select() 方法将返回所发生的事件的数量。
接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个集合。
我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。
```java ```java
int num = selector.select(); int num = selector.select();
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
// ... deal with I/O event ...
}
``` ```
### 4. 监听新连接 使用 select() 来监听事件到达,它会一直阻塞直到有至少一个事件到达。
程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件: ### 4. 获取到达的事件
```java ```java
if ((key.readyOps() & SelectionKey.OP_ACCEPT) Set<SelectionKey> keys = selector.selectedKeys();
== SelectionKey.OP_ACCEPT) { Iterator<SelectionKey> keyIterator = keys.iterator();
// Accept the new connection while (keyIterator.hasNext()) {
// ... SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
} }
``` ```
可以肯定地说,readOps() 方法告诉我们该事件是新的连接。 ### 5. 事件循环
### 5. 接受新的连接
因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞:
```java 因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
```
下一步是将新连接的 SocketChannel 配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector 上,如下所示:
```java ```java
sc.configureBlocking(false); while (true) {
SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ); int num = selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}
}
``` ```
注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于读取而不是接受新连接。 ## 套接字 NIO 实例
### 6. 删除处理过的 SelectionKey
在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey:
```java ```java
it.remove(); public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
ServerSocket serverSocket = ssChannel.socket();
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
serverSocket.bind(address);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();
// 服务器会为每个新连接创建一个 SocketChannel
SocketChannel sChannel = ssChannel1.accept();
sChannel.configureBlocking(false);
// 这个新连接主要用于从客户端读取数据
sChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sChannel = (SocketChannel) key.channel();
System.out.println(readDataFromSocketChannel(sChannel));
sChannel.close();
}
keyIterator.remove();
}
}
}
private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuffer data = new StringBuffer();
while (true) {
buffer.clear();
int n = sChannel.read(buffer);
if (n == -1)
break;
buffer.flip();
int limit = buffer.limit();
char[] dst = new char[limit];
for (int i = 0; i < limit; i++)
dst[i] = (char) buffer.get(i);
data.append(dst);
buffer.clear();
}
return data.toString();
}
}
``` ```
现在我们可以返回主循环并接受从一个套接字中传入的数据 (或者一个传入的 I/O 事件) 了。
### 7. 传入的 I/O
当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey 将被标记为 OP_READ 事件,如下所示:
```java ```java
} else if ((key.readyOps() & SelectionKey.OP_READ) public class NIOClient {
== SelectionKey.OP_READ) {
// Read the data public static void main(String[] args) throws IOException {
SocketChannel sc = (SocketChannel)key.channel(); Socket socket = new Socket("127.0.0.1", 8888);
// ... OutputStream out = socket.getOutputStream();
String s = "hello world";
out.write(s.getBytes());
out.close();
}
} }
``` ```
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
* [十一、特性](#十一特性) * [十一、特性](#十一特性)
* [面向对象三大特性](#面向对象三大特性) * [面向对象三大特性](#面向对象三大特性)
* [Java 各版本的新特性](#java-各版本的新特性) * [Java 各版本的新特性](#java-各版本的新特性)
* [Java 与 C++ 的区别](#java-与-c++-的区别) * [Java 与 C++ 的区别](#java-与-c-的区别)
* [JRE or JDK](#jre-or-jdk) * [JRE or JDK](#jre-or-jdk)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
...@@ -51,7 +51,7 @@ final A y = new A(); ...@@ -51,7 +51,7 @@ final A y = new A();
y.a = 1; y.a = 1;
``` ```
**2. 方法** </font> </br> **2. 方法**
声明方法不能被子类覆盖。 声明方法不能被子类覆盖。
...@@ -79,7 +79,7 @@ public class A { ...@@ -79,7 +79,7 @@ public class A {
**2. 静态方法** **2. 静态方法**
静态方法在类加载的时候就存在了,它不依赖于任何实例,所以 static 方法必须实现,也就是说它不能是抽象方法(abstract)。 静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。
**3. 静态语句块** **3. 静态语句块**
...@@ -87,11 +87,11 @@ public class A { ...@@ -87,11 +87,11 @@ public class A {
**4. 静态内部类** **4. 静态内部类**
内部类的一种,静态内部类不依赖外部类,且不能访问外部类的非 static 变量和方法。 内部类的一种,静态内部类不依赖外部类,且不能访问外部类的非静态的变量和方法。
**5. 静态导包** **5. 静态导包**
```source-java ```java
import static com.xxx.ClassName.* import static com.xxx.ClassName.*
``` ```
...@@ -131,12 +131,12 @@ public InitialOrderTest() { ...@@ -131,12 +131,12 @@ public InitialOrderTest() {
存在继承的情况下,初始化顺序为: 存在继承的情况下,初始化顺序为:
1. 父类(静态变量、静态语句块) - 父类(静态变量、静态语句块)
2. 子类(静态变量、静态语句块) - 子类(静态变量、静态语句块)
3. 父类(实例变量、普通语句块) - 父类(实例变量、普通语句块)
4. 父类(构造函数) - 父类(构造函数)
5. 子类(实例变量、普通语句块) - 子类(实例变量、普通语句块)
6. 子类(构造函数) - 子类(构造函数)
# 二、Object 通用方法 # 二、Object 通用方法
...@@ -191,15 +191,14 @@ x.equals(x); // true ...@@ -191,15 +191,14 @@ x.equals(x); // true
(二)对称性 (二)对称性
```java ```java
x.equals(y) == y.equals(x) // true x.equals(y) == y.equals(x); // true
``` ```
(三)传递性 (三)传递性
```java ```java
if(x.equals(y) && y.equals(z)) { if (x.equals(y) && y.equals(z))
x.equals(z); // true; x.equals(z); // true;
}
``` ```
(四)一致性 (四)一致性
...@@ -255,7 +254,7 @@ public class EqualExample { ...@@ -255,7 +254,7 @@ public class EqualExample {
hasCode() 返回散列值,而 equals() 是用来判断两个实例是否等价。等价的两个实例散列值一定要相同,但是散列值相同的两个实例不一定等价。 hasCode() 返回散列值,而 equals() 是用来判断两个实例是否等价。等价的两个实例散列值一定要相同,但是散列值相同的两个实例不一定等价。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证相等的两个实例散列值也等价 在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个实例散列值也相等
下面的代码中,新建了两个等价的实例,并将它们添加到 HashSet 中。我们希望将这两个实例当成一样的,只在集合中添加一个实例,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个实例的散列值是不同的,最终导致集合添加了两个等价的实例。 下面的代码中,新建了两个等价的实例,并将它们添加到 HashSet 中。我们希望将这两个实例当成一样的,只在集合中添加一个实例,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个实例的散列值是不同的,最终导致集合添加了两个等价的实例。
...@@ -271,7 +270,7 @@ System.out.println(set.size()); // 2 ...@@ -271,7 +270,7 @@ System.out.println(set.size()); // 2
理想的散列函数应当具有均匀性,即不相等的实例应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。 理想的散列函数应当具有均匀性,即不相等的实例应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
一个数与 31 相乘可以转换成移位和减法:31\*x == (x<<5)-x 一个数与 31 相乘可以转换成移位和减法:`31\*x == (x<<5)-x`,编译器会自动进行这个优化
```java ```java
@Override @Override
...@@ -593,9 +592,9 @@ ac2.func1(); ...@@ -593,9 +592,9 @@ ac2.func1();
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。 从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
接口也可以包含字段,并且这些字段隐式都是 static 和 final 的 接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected
接口中的方法默认都是 public 的,并且不允许定义为 private 或者 protected 接口的字段默认都是 static 和 final 的
```java ```java
public interface InterfaceExample { public interface InterfaceExample {
...@@ -606,7 +605,7 @@ public interface InterfaceExample { ...@@ -606,7 +605,7 @@ public interface InterfaceExample {
} }
int x = 123; int x = 123;
//int y; // Variable 'y' might not have been initialized // int y; // Variable 'y' might not have been initialized
public int z = 0; // Modifier 'public' is redundant for interface fields public int z = 0; // Modifier 'public' is redundant for interface fields
// private int k = 0; // Modifier 'private' not allowed here // private int k = 0; // Modifier 'private' not allowed here
// protected int l = 0; // Modifier 'protected' not allowed here // protected int l = 0; // Modifier 'protected' not allowed here
...@@ -892,15 +891,15 @@ switch (s) { ...@@ -892,15 +891,15 @@ switch (s) {
switch 不支持 long,是因为 swicth 的设计初衷是为那些只需要对少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。 switch 不支持 long,是因为 swicth 的设计初衷是为那些只需要对少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
```java ```java
// long x = 111; // long x = 111;
// switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum' // switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'
// case 111: // case 111:
// System.out.println(111); // System.out.println(111);
// break; // break;
// case 222: // case 222:
// System.out.println(222); // System.out.println(222);
// break; // break;
// } // }
``` ```
> [Why can't your switch statement data type be long, Java?](https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java) > [Why can't your switch statement data type be long, Java?](https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java)
...@@ -959,7 +958,7 @@ public class Box<T> { ...@@ -959,7 +958,7 @@ public class Box<T> {
} }
``` ```
> [Java 泛型详解](https://www.ziwenxie.site/2017/03/01/java-generic/)</br>[10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693) > [Java 泛型详解](http://www.importnew.com/24029.html)</br>[10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693)
# 十、注解 # 十、注解
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
...@@ -2208,9 +2208,8 @@ Output : [0, 2] ...@@ -2208,9 +2208,8 @@ Output : [0, 2]
```java ```java
public List<Integer> diffWaysToCompute(String input) { public List<Integer> diffWaysToCompute(String input) {
int n = input.length();
List<Integer> ways = new ArrayList<>(); List<Integer> ways = new ArrayList<>();
for (int i = 0; i < n; i++) { for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i); char c = input.charAt(i);
if (c == '+' || c == '-' || c == '*') { if (c == '+' || c == '-' || c == '*') {
List<Integer> left = diffWaysToCompute(input.substring(0, i)); List<Integer> left = diffWaysToCompute(input.substring(0, i));
...@@ -2232,9 +2231,10 @@ public List<Integer> diffWaysToCompute(String input) { ...@@ -2232,9 +2231,10 @@ public List<Integer> diffWaysToCompute(String input) {
} }
} }
} }
if (ways.size() == 0) {
if (ways.size() == 0)
ways.add(Integer.valueOf(input)); ways.add(Integer.valueOf(input));
}
return ways; return ways;
} }
``` ```
...@@ -2365,7 +2365,7 @@ private int rob(int[] nums, int first, int last) { ...@@ -2365,7 +2365,7 @@ private int rob(int[] nums, int first, int last) {
定义一个数组 dp 存储错误方式数量,dp[i] 表示 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况: 定义一个数组 dp 存储错误方式数量,dp[i] 表示 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况:
- i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)\*dp[i-2] 种错误装信方式。 - i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)\*dp[i-2] 种错误装信方式。
- i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (n-1)\*dp[i-1] 种错误装信方式。 - i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (i-1)\*dp[i-1] 种错误装信方式。
综上所述,错误装信数量方式数量为: 综上所述,错误装信数量方式数量为:
...@@ -6197,20 +6197,19 @@ We cannot find a way to divide the set of nodes into two independent subsets. ...@@ -6197,20 +6197,19 @@ We cannot find a way to divide the set of nodes into two independent subsets.
public boolean isBipartite(int[][] graph) { public boolean isBipartite(int[][] graph) {
int[] colors = new int[graph.length]; int[] colors = new int[graph.length];
Arrays.fill(colors, -1); Arrays.fill(colors, -1);
for (int i = 0; i < graph.length; i++) {
for (int i = 0; i < graph.length; i++) if (colors[i] == -1 && !isBipartite(graph, i, 0, colors))
if (colors[i] != -1 && !isBipartite(graph, i, 0, colors))
return false; return false;
}
return true; return true;
} }
private boolean isBipartite(int[][] graph, int cur, int color, int[] colors) { private boolean isBipartite(int[][] graph, int node, int color, int[] colors) {
if (colors[cur] != -1) if (colors[node] != -1)
return colors[cur] == color; return colors[node] == color;
colors[cur] = color; colors[node] = color;
for (int next : graph[cur]) for (int next : graph[node])
if (!isBipartite(graph, next, 1 - color, colors)) if (!isBipartite(graph, next, 1 - color, colors))
return false; return false;
......
<!-- GFM-TOC -->
* [175. Combine Two Tables](#175-combine-two-tables)
* [181. Employees Earning More Than Their Managers](#181-employees-earning-more-than-their-managers)
* [183. Customers Who Never Order](#183-customers-who-never-order)
* [184. Department Highest Salary](#184-department-highest-salary)
* [未完待续...](#未完待续)
<!-- GFM-TOC -->
# 175. Combine Two Tables
https://leetcode.com/problems/combine-two-tables/description/
## Description
Person 表:
```html
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| PersonId | int |
| FirstName | varchar |
| LastName | varchar |
+-------------+---------+
PersonId is the primary key column for this table.
```
Address 表:
```html
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| AddressId | int |
| PersonId | int |
| City | varchar |
| State | varchar |
+-------------+---------+
AddressId is the primary key column for this table.
```
查找 FirstName, LastName, City, State 数据,而不管一个用户有没有填地址信息。
## SQL Schema
```sql
DROP TABLE Person;
CREATE TABLE Person ( PersonId INT, FirstName VARCHAR ( 255 ), LastName VARCHAR ( 255 ) );
DROP TABLE Address;
CREATE TABLE Address ( AddressId INT, PersonId INT, City VARCHAR ( 255 ), State VARCHAR ( 255 ) );
INSERT INTO Person ( PersonId, LastName, FirstName )
VALUES
( 1, 'Wang', 'Allen' );
INSERT INTO Address ( AddressId, PersonId, City, State )
VALUES
( 1, 2, 'New York City', 'New York' );
```
## Solution
使用左外连接。
```sql
SELECT FirstName, LastName, City, State
FROM Person AS P LEFT JOIN Address AS A
ON P.PersonId = A.PersonId;
```
# 181. Employees Earning More Than Their Managers
https://leetcode.com/problems/employees-earning-more-than-their-managers/description/
## Description
Employee 表:
```html
+----+-------+--------+-----------+
| Id | Name | Salary | ManagerId |
+----+-------+--------+-----------+
| 1 | Joe | 70000 | 3 |
| 2 | Henry | 80000 | 4 |
| 3 | Sam | 60000 | NULL |
| 4 | Max | 90000 | NULL |
+----+-------+--------+-----------+
```
查找所有员工,它们的薪资大于其经理薪资。
## SQL Schema
```sql
DROP TABLE Employee;
CREATE TABLE Employee ( Id INT, NAME VARCHAR ( 255 ), Salary INT, ManagerId INT );
INSERT INTO Employee ( Id, NAME, Salary, ManagerId )
VALUES
( '1', 'Joe', '70000', '3' ),
( '2', 'Henry', '80000', '4' ),
( '3', 'Sam', '60000', NULL ),
( '4', 'Max', '90000', NULL );
```
## Solution
```sql
SELECT E1.Name AS Employee
FROM Employee AS E1, Employee AS E2
WHERE E1.ManagerId = E2.Id AND E1.Salary > E2.Salary;
```
# 183. Customers Who Never Order
https://leetcode.com/problems/customers-who-never-order/description/
## Description
Curstomers 表:
```html
+----+-------+
| Id | Name |
+----+-------+
| 1 | Joe |
| 2 | Henry |
| 3 | Sam |
| 4 | Max |
+----+-------+
```
Orders 表:
```html
+----+------------+
| Id | CustomerId |
+----+------------+
| 1 | 3 |
| 2 | 1 |
+----+------------+
```
查找没有订单的顾客信息:
```html
+-----------+
| Customers |
+-----------+
| Henry |
| Max |
+-----------+
```
## SQL Schema
```sql
DROP TABLE Customers;
CREATE TABLE Customers ( Id INT, NAME VARCHAR ( 255 ) );
DROP TABLE Orders;
CREATE TABLE Orders ( Id INT, CustomerId INT );
INSERT INTO Customers ( Id, NAME )
VALUES
( '1', 'Joe' ),
( '2', 'Henry' ),
( '3', 'Sam' ),
( '4', 'Max' );
INSERT INTO Orders ( Id, CustomerId )
VALUES
( '1', '3' ),
( '2', '1' );
```
## Solution
左外链接
```sql
SELECT C.Name AS Customers
FROM Customers AS C LEFT JOIN Orders AS O
ON C.Id = O.CustomerId
WHERE O.CustomerId IS NULL;
```
子查询
```sql
SELECT C.Name AS Customers
FROM Customers AS C
WHERE C.Id NOT IN (
SELECT CustomerId
FROM Orders
);
```
# 184. Department Highest Salary
https://leetcode.com/problems/department-highest-salary/description/
## Description
Employee 表:
```html
+----+-------+--------+--------------+
| Id | Name | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1 | Joe | 70000 | 1 |
| 2 | Henry | 80000 | 2 |
| 3 | Sam | 60000 | 2 |
| 4 | Max | 90000 | 1 |
+----+-------+--------+--------------+
```
Department 表:
```html
+----+----------+
| Id | Name |
+----+----------+
| 1 | IT |
| 2 | Sales |
+----+----------+
```
查找一个 Department 中收入最高者的信息:
```html
+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT | Max | 90000 |
| Sales | Henry | 80000 |
+------------+----------+--------+
```
## SQL Schema
```sql
DROP TABLE Employee;
CREATE TABLE Employee ( Id INT, NAME VARCHAR ( 255 ), Salary INT, DepartmentId INT );
DROP TABLE Department;
CREATE TABLE Department ( Id INT, NAME VARCHAR ( 255 ) );
INSERT INTO Employee ( Id, NAME, Salary, DepartmentId )
VALUES
( 1, 'Joe', 70000, 1 ),
( 2, 'Henry', 80000, 2 ),
( 3, 'Sam', 60000, 2 ),
( 4, 'Max', 90000, 1 );
INSERT INTO Department ( Id, NAME )
VALUES
( 1, 'IT' ),
( 2, 'Sales' );
```
## Solution
```sql
SELECT D.Name AS Department, E.Name AS Employee, E.Salary
FROM Employee AS E, Department AS D,
(SELECT DepartmentId, MAX(Salary) AS Salary
FROM Employee
GROUP BY DepartmentId) AS M
WHERE E.DepartmentId = D.Id
AND E.DepartmentId = M.DepartmentId
AND E.Salary = M.Salary;
```
# 未完待续...
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
<!-- GFM-TOC -->
* [一、两阶段提交协议](#一两阶段提交协议)
* [二、Paxos 协议](#二paxos-协议)
* [三、Raft 协议](#三raft-协议)
* [四、拜占庭将军问题](#四拜占庭将军问题)
* [五、参考资料](#五参考资料)
<!-- GFM-TOC -->
# 一、两阶段提交协议
Two-phase Commit(2PC)。
可以保证一个事务跨越多个节点时保持 ACID 特性。
两类节点:协调者(Coordinator)和参与者(Participants),协调者只有一个,参与者可以有多个。
## 运行过程
1. 准备阶段:协调者询问参与者事务是否执行成功;
2. 提交阶段:如果事务在每个参与者上都执行成功,协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
<div align="center"> <img src="../pics//07717718-1230-4347-aa18-2041c315e670.jpg"/> </div><br>
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
## 存在的问题
- 参与者发生故障。解决方案:可以给事务设置一个超时时间,如果某个参与者一直不响应,那么认为事务执行失败。
- 协调者发生故障。解决方案:将操作日志同步到备用协调者,让备用协调者接替后续工作。
# 二、Paxos 协议
用于达成共识性问题,即对多个节点产生的值,该算法能保证只选出唯一一个值。
主要有三类节点:
1. 提议者(Proposer):提议一个值;
2. 接受者(Acceptor):对每个提议进行投票;
3. 告知者(Learner):被告知投票的结果,不参与投票过程。
<div align="center"> <img src="../pics//0aaf4630-d2a2-4783-b3f7-a2b6a7dfc01b.jpg"/> </div><br>
## 执行过程
规定一个提议包含两个字段:[n, v],其中 n 为序号(具有唯一性),v 为提议值。
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向所有 Acceptor 发送提议请求。
<div align="center"> <img src="../pics//2bf2fd8f-5ade-48ba-a2b3-74195ac77c4b.png" width="500"/> </div><br>
当 Acceptor 接收到一个提议请求,包含的提议为 [n1, v1],并且之前还未接收过提议请求,那么发送一个提议响应,设置当前接收到的提议为 [n1, v1],并且保证以后不会再接受序号小于 n1 的提议。
如下图,Acceptor X 在收到 [n=2, v=8] 的提议请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的提议响应,并且设置当前接收到的提议为 [n=2, v=8],并且保证以后不会再接受序号小于 2 的提议。其它的 Acceptor 类似。
<div align="center"> <img src="../pics//3f5bba4b-7813-4aea-b578-970c7e3f6bf3.jpg"/> </div><br>
如果 Acceptor 接收到一个提议请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 > n2,那么就丢弃该提议请求;否则,发送提议响应,该提议响应包含之前已经接收过的提议 [n1, v1],设置当前接收到的提议为 [n2, v2],并且保证以后不会再接受序号小于 n2 的提议。
如下图,Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的提议请求,由于之前已经接收过 [n=4, v=5] 的提议,并且 n > 2,因此就抛弃该提议请求;Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的提议请求,因为之前接收到的提议为 [n=2, v=8],并且 2 <= 4,因此就发送 [n=2, v=8] 的提议响应,设置当前接收到的提议为 [n=4, v=5],并且保证以后不会再接受序号小于 4 的提议。Acceptor Y 类似。
<div align="center"> <img src="../pics//9b829410-86c4-40aa-ba8d-9e8e26c0eeb8.jpg" width="600"/> </div><br>
当一个 Proposer 接收到超过一半 Acceptor 的提议响应时,就可以发送接受请求。
Proposer A 接收到两个提议响应之后,就发送 [n=2, v=8] 接受请求。该接受请求会被所有 Acceptor 丢弃,因为此时所有 Acceptor 都保证不接受序号小于 4 的提议。
Proposer B 过后也收到了两个提议响应,因此也开始发送接受请求。需要注意的是,接受请求的 v 需要取它收到的最大 v 值,也就是 8。因此它发送 [n=4, v=8] 的接受请求。
<div align="center"> <img src="../pics//2c4556e4-0751-4377-ab08-e7b89d697ca7.png" width="400"/> </div><br>
Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺的最小序号,那么就发送通知给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
<div align="center"> <img src="../pics//8adb2591-d3f1-4632-84cb-823fb9c5eb09.jpg"/> </div><br>
## 约束条件
### 1. 正确性
指只有一个提议值会生效。
因为 Paxos 协议要求每个生效的提议被多数 Acceptor 接收,并且 Acceptor 不会接受两个不同的提议,因此可以保证正确性。
### 2. 可终止性
指最后总会有一个提议生效。
Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。
# 三、Raft 协议
Raft 和 Paxos 类似,但是更容易理解,也更容易实现。
Raft 主要是用来竞选主节点。
## 单个 Candidate 的竞选
有三种节点:Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。
① 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
<div align="center"> <img src="../pics//111521118015898.gif"/> </div><br>
② 此时 A 发送投票请求给其它所有节点。
<div align="center"> <img src="../pics//111521118445538.gif"/> </div><br>
③ 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
<div align="center"> <img src="../pics//111521118483039.gif"/> </div><br>
④ 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。
<div align="center"> <img src="../pics//111521118640738.gif"/> </div><br>
## 多个 Candidate 竞选
① 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。
<div align="center"> <img src="../pics//111521119203347.gif"/> </div><br>
② 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。
<div align="center"> <img src="../pics//111521119368714.gif"/> </div><br>
## 日志复制
① 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
<div align="center"> <img src="../pics//7.gif"/> </div><br>
② Leader 会把修改复制到所有 Follower。
<div align="center"> <img src="../pics//9.gif"/> </div><br>
③ Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
<div align="center"> <img src="../pics//10.gif"/> </div><br>
④ 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
<div align="center"> <img src="../pics//11.gif"/> </div><br>
# 四、拜占庭将军问题
> [拜占庭将军问题深入探讨](http://www.8btc.com/baizhantingjiangjun)
# 五、参考资料
- 杨传辉. 大规模分布式存储系统: 原理解析与架构实战[M]. 机械工业出版社, 2013.
- [区块链技术指南](https://www.gitbook.com/book/yeasy/blockchain_guide/details)
- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft)
- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
...@@ -47,7 +47,7 @@ My **name** is Zheng. ...@@ -47,7 +47,7 @@ My **name** is Zheng.
**-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符; **-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符;
**^** 是取非操作,必须在 [ ] 字符集合中使用; **^** 在 [ ] 字符集合中是取非操作。
**应用** **应用**
...@@ -131,7 +131,7 @@ abc[^0-9] ...@@ -131,7 +131,7 @@ abc[^0-9]
``` ```
[\w.]+@\w+\.\w+ [\w.]+@\w+\.\w+
[\w.]+@[\w]+\.[\w]+ [\w.]+@[\w]+[\.][\w]+
``` ```
**{n}** 匹配 n 个字符,**{m, n}** 匹配 m\~n 个字符,**{m,}** 至少匹配 m 个字符; **{n}** 匹配 n 个字符,**{m, n}** 匹配 m\~n 个字符,**{m,}** 至少匹配 m 个字符;
...@@ -222,11 +222,11 @@ a.+c ...@@ -222,11 +222,11 @@ a.+c
匹配 IP 地址。IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下情况是合法的: 匹配 IP 地址。IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下情况是合法的:
1. 一位数字 - 一位数字
2. 不以 0 开头的两位数字 - 不以 0 开头的两位数字
3. 1 开头的三位数 - 1 开头的三位数
4. 2 开头,第 2 位是 0-4 的三位数 - 2 开头,第 2 位是 0-4 的三位数
5. 25 开头,第 3 位是 0-5 的三位数 - 25 开头,第 3 位是 0-5 的三位数
**正则表达式** **正则表达式**
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
以下实现中,私有静态变量 uniqueInstance 被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。 以下实现中,私有静态变量 uniqueInstance 被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if(uniqueInstance == null) ,那么就会多次实例化 uniqueInstance。 这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (uniqueInstance == null) ,并且此时 uniqueInstance 为 null,那么多个线程会执行 uniqueInstance = new Singleton(); 语句,这将导致实例化多次 uniqueInstance。
```java ```java
public class Singleton { public class Singleton {
...@@ -350,8 +350,8 @@ public class Client { ...@@ -350,8 +350,8 @@ public class Client {
public static void main(String[] args) { public static void main(String[] args) {
AbstractFactory abstractFactory = new ConcreteFactory1(); AbstractFactory abstractFactory = new ConcreteFactory1();
AbstractProductA productA = abstractFactory.createProductA(); AbstractProductA productA = abstractFactory.createProductA();
abstractFactory = new ConcreteFactory2(); AbstractProductB productB = abstractFactory.createProductB();
productA = abstractFactory.createProductA(); // do something with productA and productB
} }
} }
``` ```
......
此差异已折叠。
...@@ -136,7 +136,7 @@ public class Person { ...@@ -136,7 +136,7 @@ public class Person {
} }
public void work() { public void work() {
if(18 <= age && age <= 50) { if (18 <= age && age <= 50) {
System.out.println(name + " is working very hard!"); System.out.println(name + " is working very hard!");
} else { } else {
System.out.println(name + " can't work any more!"); System.out.println(name + " can't work any more!");
...@@ -163,9 +163,9 @@ Animal animal = new Cat(); ...@@ -163,9 +163,9 @@ Animal animal = new Cat();
运行时多态有三个条件: 运行时多态有三个条件:
1. 继承 - 继承
2. 覆盖 - 覆盖(重写)
3. 向上转型 - 向上转型
下面的代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了父类的 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。 下面的代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了父类的 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。
...@@ -236,9 +236,9 @@ public class Music { ...@@ -236,9 +236,9 @@ public class Music {
和关联关系不同的是,依赖关系是在运行过程中起作用的。A 类和 B 类是依赖关系主要有三种形式: 和关联关系不同的是,依赖关系是在运行过程中起作用的。A 类和 B 类是依赖关系主要有三种形式:
1. A 类是 B 类中的(某中方法的)局部变量; - A 类是 B 类中的(某中方法的)局部变量;
2. A 类是 B 类方法当中的一个参数; - A 类是 B 类方法当中的一个参数;
3. A 类向 B 类发送消息,从而影响 B 类发生变化; - A 类向 B 类发送消息,从而影响 B 类发生变化;
<div align="center"> <img src="../pics//c7d4956c-9988-4a10-a704-28fdae7f3d28.png"/> </div><br> <div align="center"> <img src="../pics//c7d4956c-9988-4a10-a704-28fdae7f3d28.png"/> </div><br>
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册