diff --git a/.gitignore b/.gitignore index 61f36d59..c91a56b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ +.DS_Store *.txt -.idea/ diff --git a/BOOKLIST.md b/BOOKLIST.md new file mode 100644 index 00000000..e88ba706 --- /dev/null +++ b/BOOKLIST.md @@ -0,0 +1,82 @@ +# 数据结构与算法 + +- [算法](https://book.douban.com/subject/19952400/) +- [数据结构与算法分析](https://book.douban.com/subject/3351237/) +- [编程珠玑](https://book.douban.com/subject/3227098/) +- [剑指 Offer](https://book.douban.com/subject/25910559/) + +# 操作系统 + +- [现代操作系统](https://book.douban.com/subject/3852290/) +- [深入理解计算机系统](https://book.douban.com/subject/26912767/) +- [鸟哥的 Linux 私房菜](https://book.douban.com/subject/4889838/) +- [Unix 环境高级编程](https://book.douban.com/subject/25900403/) +- [Unix/Linux 编程实践教程](https://book.douban.com/subject/1219329/) +- [Operating Systems: Three Easy Pieces (OSTEP)](http://pages.cs.wisc.edu/~remzi/OSTEP/) + +# 计算机网络 + +- [计算机网络](https://book.douban.com/subject/2970300/) +- [计算机网络 自顶向下方法](https://book.douban.com/subject/1391207/) +- [图解 HTTP](https://book.douban.com/subject/25863515/) +- [TCP/IP 详解 卷 1:协议](https://book.douban.com/subject/1088054/) +- [UNIX 网络编程](https://book.douban.com/subject/1500149/) +- [Linux 多线程服务端编程](https://book.douban.com/subject/20471211/) + +# 面向对象 + +- [Head First 设计模式](https://book.douban.com/subject/2243615/) +- [设计模式 可复用面向对象软件的基础](https://book.douban.com/subject/1052241/) +- [敏捷软件开发 原则、模式与实践](https://book.douban.com/subject/1140457/) + +# 数据库 + +- [数据库系统概念](https://book.douban.com/subject/10548379/) +- [MySQL 必知必会](https://book.douban.com/subject/3354490/) +- [高性能 MySQL](https://book.douban.com/subject/23008813/) +- [Redis 设计与实现](https://book.douban.com/subject/25900156/) +- [Redis 实战](https://book.douban.com/subject/26612779/) + +# Java + +- [Java 编程思想](https://book.douban.com/subject/2130190/) +- [Effective java 中文版](https://book.douban.com/subject/3360807/) +- [深入理解 Java 虚拟机](https://book.douban.com/subject/24722612/) +- [Java 并发编程实战](https://book.douban.com/subject/10484692/) +- [精通 Spring 4.x](https://book.douban.com/subject/26952826/) +- [Spring 揭秘](https://book.douban.com/subject/3897837/) + +# C++ + +- [C++ Primer](https://book.douban.com/subject/25708312/) +- [Effective C++](https://book.douban.com/subject/1842426/) +- [C++ 编程规范](https://book.douban.com/subject/1480481/) +- [STL 源码剖析](https://book.douban.com/subject/1110934/) +- [深度探索 C++ 对象模型](https://book.douban.com/subject/1091086/) + +# 系统设计 + +- [大规模分布式存储系统](https://book.douban.com/subject/25723658/) +- [从 Paxos 到 Zookeeper](https://book.douban.com/subject/26292004/) +- [大型网站系统与 Java 中间件开发实践](https://book.douban.com/subject/25867042/) +- [淘宝技术这十年](https://book.douban.com/subject/24335672/) +- [深入理解 Nginx](https://book.douban.com/subject/22793675/) + +# 开发工具 + +- [Pro Git](https://git-scm.com/book/zh/v2) +- [正则表达式必知必会](https://book.douban.com/subject/2269648/) + +# 编码实践 + +- [重构](https://book.douban.com/subject/4262627/) +- [代码大全](https://book.douban.com/subject/1477390/) +- [人月神话](https://book.douban.com/subject/1102259/) +- [程序员的职业素养](https://book.douban.com/subject/11614538/) +- [编写可读代码的艺术](https://book.douban.com/subject/10797189/) + +# 其它 + +- [JavaScript 语言精粹](https://book.douban.com/subject/3590768/) +- [利用 Python 进行数据分析](https://book.douban.com/subject/25779298/) +- [概率论与数理统计](https://book.douban.com/subject/2201479/) diff --git a/README.md b/README.md index 722c681e..258fa75d 100644 --- a/README.md +++ b/README.md @@ -1,179 +1,241 @@ - | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:| -| 算法[:pencil2:](#算法-pencil2) | 操作系统[:computer:](#操作系统-computer)|网络[:cloud:](#网络-cloud) | 面向对象[:couple:](#面向对象-couple) |数据库[:floppy_disk:](#数据库-floppy_disk)| Java [:coffee:](#java-coffee)| 分布式[:sweat_drops:](#分布式-sweat_drops)| 工具[:hammer:](#工具-hammer)| 编码实践[:speak_no_evil:](#编码实践-speak_no_evil)| 后记[:memo:](#后记-memo) | +| 算法[:pencil2:](#pencil2-算法) | 操作系统[:computer:](#computer-操作系统)|网络[:cloud:](#cloud-网络) | 面向对象[:couple:](#couple-面向对象) |数据库[:floppy_disk:](#floppy_disk-数据库)| Java [:coffee:](#coffee-java)| 系统设计[:bulb:](#bulb-系统设计)| 工具[:hammer:](#hammer-工具)| 编码实践[:speak_no_evil:](#speak_no_evil-编码实践)| 后记[:memo:](#memo-后记) | -本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.) -
+
-## 算法 :pencil2: +
+ +
+ +
+### :pencil2: 算法 -> [剑指 Offer 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/剑指%20offer%20题解.md) +- [剑指 Offer 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/剑指%20offer%20题解.md) -《剑指 Offer 第二版》的最优解,在牛客网在线编程中出现的题目都已 AC。 + 目录根据原书第二版进行编排,代码和原书有所不同,尽量比原书更简洁。 -> [Leetcode 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode%20题解.md) +- [Leetcode 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode%20题解.md) -对题目做了一个分类,并对每种题型的解题思路做了总结。 + 对题目做了一个大致分类,并对每种题型的解题思路做了总结。 + + - [算法](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/算法.md) -> [算法](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/算法.md) + 排序、并查集、栈和队列、红黑树、散列表。 -整理自《算法 第四版》 +### :computer: 操作系统 -## 操作系统 :computer: +- [计算机操作系统](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机操作系统.md) -> [计算机操作系统](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机操作系统.md) + 进程管理、内存管理、设备管理、链接。 -整理自《现代操作系统》和《计算机操作系统》 +- [Linux](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Linux.md) -> [Linux](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Linux.md) + 基本实现原理以及基本操作。 -整理自《鸟哥的 Linux 私房菜》 +### :cloud: 网络 +- [计算机网络](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机网络.md) -## 网络 :cloud: + 物理层、链路层、网络层、运输层、应用层。 -> [计算机网络](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机网络.md) +- [HTTP](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/HTTP.md) -整理自《计算机网络 第七版》,重点内容会在标题后面加 \*。 + 方法、状态码、Cookie、缓存、连接管理、HTTPs、HTTP 2.0。 -> [HTTP](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/HTTP.md) +- [Socket](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Socket.md) -整理自《图解 HTTP》 + I/O 模型、I/O 多路复用。 -> [Socket](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Socket.md) +### :couple: 面向对象 -整理自《Unix 网络编程》 +- [设计模式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/设计模式.md) + 实现了 Gof 的 23 种设计模式。 -## 面向对象 :couple: +- [面向对象思想](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/面向对象思想.md) -> [设计模式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/设计模式.md) + 三大原则(继承、封装、多态)、类图、设计原则。 -整理自《Head First 设计模式》 +### :floppy_disk: 数据库 -> [面向对象思想](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/面向对象思想.md) +- [数据库系统原理](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/数据库系统原理.md) -一些面向对象思想和设计原则。 + 事务、锁、隔离级别、MVCC、间隙锁、范式。 -## 数据库 :floppy_disk: +- [SQL](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/SQL.md) -> [数据库系统原理](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/数据库系统原理.md) + SQL 基本语法。 -整理自《数据库系统概论 第四版》 +- [Leetcode-Database 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode-Database%20题解.md) -> [SQL](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/SQL.md) + Leetcode 上数据库题目的解题记录。 -整理自《SQL 必知必会》 +- [MySQL](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/MySQL.md) -> [Leetcode-Database 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode-Database%20题解.md) + 存储引擎、索引、查询优化、切分、复制。 -Leetcode 上数据库题目的解题记录。 +- [Redis](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Redis.md) -> [MySQL](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/MySQL.md) + 五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。 -整理自《高性能 MySQL》 +### :coffee: Java -> [Redis](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Redis.md) +- [Java 基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20基础.md) -整理自《Redis 设计与实现》和《Redis 实战》 + 不会涉及很多基本语法介绍,主要是一些实现原理以及关键特性。 -## 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) + 源码分析:ArrayList、Vector、CopyOnWriteArrayList、LinkedList、HashMap、ConcurrentHashMap、LinkedHashMap、WeekHashMap。 -整理了一些常见考点。 +- [Java 并发](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20并发.md) -> [Java 虚拟机](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20虚拟机.md) + 线程使用方式、两种互斥同步方法、线程协作、JUC、线程安全、内存模型、锁优化。 -整理自《深入理解 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 I/O](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20IO.md) -> [Java 容器](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20容器.md) + NIO 的原理以及实例。 -容器的一些总结,包含容器源码的分析。 +### :bulb: 系统设计 -> [Java I/O](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20IO.md) +- [系统设计基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/系统设计基础.md) -File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO + 性能、伸缩性、扩展性、可用性、安全性 -> [JDK 中的设计模式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/JDK%20中的设计模式.md) +- [分布式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式.md) -对每种设计模式做了一个总结,并给出在 JDK 中的使用实例。 + 分布式锁、分布式事务、CAP、BASE、Paxos、Raft -## 分布式 :sweat_drops: +- [集群](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/集群.md) -> [分布式基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式基础.md) + 负载均衡、Session 管理 -整理自《大规模分布式存储系统》 +- [攻击技术](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/攻击技术.md) -> [分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md) + XSS、CSRF、SQL 注入、DDoS -分布式事务、负载均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。 +- [缓存](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/缓存.md) + 缓存特征、缓存位置、缓存问题、数据分布、一致性哈希、LRU、CDN -## 工具 :hammer: +- [消息队列](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/消息队列.md) -> [Git](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Git.md) + 消息处理模型、使用场景、可靠性 -整理一些 Git 的使用和概念。 +### :hammer: 工具 -> [正则表达式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/正则表达式.md) +- [Git](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Git.md) -整理自《正则表达式必知必会》 + 一些 Git 的使用和概念。 -## 编码实践 :speak_no_evil: +- [Docker](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Docker.md) -> [重构](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/重构.md) + Docker 基本原理。 -重构是对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 +- [正则表达式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/正则表达式.md) -> [代码可读性](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/代码可读性.md) + 正则表达式基本语法。 -编程有很大一部分时间是在阅读代码,可读性良好的代码能够大大提高编程效率。 +- [构建工具](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/构建工具.md) -> [代码风格规范](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/代码风格规范.md) + 构建工具的基本概念、主流构建工具介绍。 -Google 开源项目的代码风格规范。 +### :speak_no_evil: 编码实践 -## 后记 :memo: +- [重构](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/重构.md) -**关于仓库** + 参考 重构 改善既有代码的设计。 -本仓库是笔者在准备 2018 年春招实习过程中的学习总结,内容以计算机书籍的学习笔记为主,在整理重点知识的同时会尽量保证知识的系统性。 +- [代码可读性](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/代码可读性.md) -**关于贡献** + 参考 编写可读代码的艺术。 -因为大部分内容是笔者一个字一个字打上去的,所以难免会有一些笔误。如果发现,可以直接在相应的文档上编辑修改。 +- [代码风格规范](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/代码风格规范.md) -笔者能力有限,很多内容还不够完善。如果您希望和笔者一起完善这个仓库,可以发表一个 Issue,表明您想要添加的内容,笔者会及时查看。 + Google 开源项目的代码风格规范。 -因为不打算将这个仓库做成一个大而全的面试宝典,只希望添加一些比较通用的基础知识,或者是与 Java 和分布式相关的内容,但是不添加 Java Web 相关的内容。 +### :memo: 后记 -您也可以在 Issues 中发表关于改进本仓库的建议。 +#### About -**关于上传** +本仓库主要是根据计算机经典书籍以及官方技术文档进行总结的学习笔记,希望对大家有所帮助。 + +学习笔记不是从网上到处拼凑而来,除了少部分引用书上和技术文档的原文,其余都是笔者的原创。在您引用本仓库内容或者对内容进行修改演绎时,请遵循文末的开源协议,谢谢。 + +#### BookList + +本仓库参考的书目:[BOOKLIST](https://github.com/CyC2018/Interview-Notebook/blob/master/BOOKLIST.md)。 + +#### How To Contribute + +笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接对相应文档进行编辑修改。 + +如果想要提交一个仓库现在还没有的全新内容,可以先将相应的文档放到 other 目录下。 + +欢迎在 Issue 中提交对本仓库的改进建议~ + +#### Typesetting + +笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。 + +笔记不使用 `![]()` 这种方式来引用图片,而是用 `` 标签。一方面是为了能够控制图片以合适的大小显示,另一方面是因为 GFM 不支持 `
![]()
` 让图片居中显示,只能使用 `
` 达到居中的效果。 + +笔者将自己实现的文档排版功能提取出来,放在 Github Page 中,无需下载安装即可免费使用:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。 + +#### Uploading 笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上,实现了一整套自动化上传方案,包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。 -进行 Markdown 文档转换是因为 Github 使用的 GFM 不支持 MathJax 公式和 TOC 标记,所以需要替换 MathJax 公式为 CodeCogs 的云服务和重新生成 TOC 目录。这里提供了笔者实现的 GFM 文档转换工具的下载:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。 +进行 Markdown 文档转换是因为 Github 使用的 GFM 不支持 MathJax 公式和 TOC 标记,所以需要替换 MathJax 公式为 CodeCogs 的云服务和重新生成 TOC 目录。 -**关于排版** +笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github,或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。 -笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。这里提供了笔者实现的中英混排文档在线排版工具:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting),目前实现了加空格的功能,之后打算实现对英文专有名词提示首字母大写的功能。 +#### Logo -不使用 `![]()` 这种方式来引用图片是为了能够控制图片以合适的大小显示。而且 GFM 不支持 `
![]()
` 让图片居中显示,只能使用 `
` ,所以只能使用 img 标签来引用图片。 +Power by [logomakr](https://logomakr.com/). -**关于转载** +#### Statement -本仓库内容使用到的资料都会在最后面的参考资料中给出引用链接,希望您在使用本仓库的内容时也能给出相应的引用链接。 +本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.) -**鸣谢** +#### Acknowledgements -[TeeKee](https://github.com/linw7) +感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与笔者联系。 + +​ + + +​ + + +​ + + +​ + + +​ + + +​ + + +​ + + +​ + + +#### License + +在对本作品进行演绎时,请署名并以相同方式共享。 + +知识共享许可协议 diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 00000000..a864c37f --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,49 @@ +This file used to generate gitbook catalogue. + +# Summary + +* 算法 + * [剑指 Offer 题解](/notes/剑指 offer 题解.md) + * [Leetcode 题解](/notes/Leetcode 题解.md) + * [算法](/notes/算法.md) +* 操作系统 + * [计算机操作系统](/notes/计算机操作系统.md) + * [Linux](/notes/Linux.md) +* 网络 + * [计算机网络](/notes/计算机网络.md) + * [HTTP](/notes/HTTP.md) + * [Socket](/notes/Socket.md) +* 面向对象 + * [设计模式](/notes/设计模式.md) + * [面向对象思想](/notes/面向对象思想.md) +* 数据库 + * [数据库系统原理](/notes/数据库系统原理.md) + * [SQL](/notes/SQL.md) + * [Leetcode-Database 题解](/notes/Leetcode-Database 题解.md) + * [MySQL](/notes/MySQL.md) + * [Redis](/notes/Redis.md) +* Java + * [Java 基础](/notes/Java 基础.md) + * [Java 虚拟机](/notes/Java 虚拟机.md) + * [Java 并发](/notes/Java 并发.md) + * [Java 容器](/notes/Java 容器.md) + * [Java I/O](/notes/Java IO.md) +* 系统设计 + * [系统设计基础](/notes/系统设计基础.md) + * [分布式](/notes/分布式.md) + * [集群](/notes/集群.md) + * [攻击技术](/notes/攻击技术.md) + * [缓存](/notes/缓存.md) + * [消息队列](/notes/消息队列.md) +* 工具 + * [Git](/notes/Git.md) + * [Docker](/notes/Docker.md) + * [正则表达式](/notes/正则表达式.md) + * [构建工具](/notes/构建工具.md) +* 编码实践 + * [重构](/notes/重构.md) + * [代码可读性](/notes/代码可读性.md) + * [代码风格规范](/notes/代码风格规范.md) +* 参考书目 + * [BOOKLIST](/BOOKLIST.md) + diff --git a/notes/2016 校招真题题解.md b/notes/2016 校招真题题解.md deleted file mode 100644 index da6709ab..00000000 --- a/notes/2016 校招真题题解.md +++ /dev/null @@ -1,742 +0,0 @@ - -* [前言](#前言) -* [1. 小米-小米Git](#1-小米-小米git) -* [2. 小米-懂二进制](#2-小米-懂二进制) -* [3. 小米-中国牛市](#3-小米-中国牛市) -* [4. 微软-LUCKY STRING](#4-微软-lucky-string) -* [5. 微软-Numeric Keypad](#5-微软-numeric-keypad) -* [6. 微软-Spring Outing](#6-微软-spring-outing) -* [7. 微软-S-expression](#7-微软-s-expression) -* [8. 华为-最高分是多少](#8-华为-最高分是多少) -* [9. 华为-简单错误记录](#9-华为-简单错误记录) -* [10. 华为-扑克牌大小](#10-华为-扑克牌大小) -* [11. 去哪儿-二分查找](#11-去哪儿-二分查找) -* [12. 去哪儿-首个重复字符](#12-去哪儿-首个重复字符) -* [13. 去哪儿-寻找Coder](#13-去哪儿-寻找coder) -* [14. 美团-最大差值](#14-美团-最大差值) -* [15. 美团-棋子翻转](#15-美团-棋子翻转) -* [16. 美团-拜访](#16-美团-拜访) -* [17. 美团-直方图内最大矩形](#17-美团-直方图内最大矩形) -* [18. 美团-字符串计数](#18-美团-字符串计数) -* [19. 美团-平均年龄](#19-美团-平均年龄) -* [20. 百度-罪犯转移](#20-百度-罪犯转移) -* [22. 百度-裁减网格纸](#22-百度-裁减网格纸) -* [23. 百度-钓鱼比赛](#23-百度-钓鱼比赛) -* [24. 百度-蘑菇阵](#24-百度-蘑菇阵) - - - -# 前言 - -省略的代码: - -```java -import java.util.*; -``` - -```java -public class Solution { -} -``` - -```java -public class Main { - public static void main(String[] args) { - Scanner in = new Scanner(System.in); - while (in.hasNext()) { - } - } -} -``` - -# 1. 小米-小米Git - -- 重建多叉树 -- 使用 LCA - -```java -private class TreeNode { - int id; - List childs = new ArrayList<>(); - - TreeNode(int id) { - this.id = id; - } -} - -public int getSplitNode(String[] matrix, int indexA, int indexB) { - int n = matrix.length; - boolean[][] linked = new boolean[n][n]; // 重建邻接矩阵 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - linked[i][j] = matrix[i].charAt(j) == '1'; - } - } - TreeNode tree = constructTree(linked, 0); - TreeNode ancestor = LCA(tree, new TreeNode(indexA), new TreeNode(indexB)); - return ancestor.id; -} - -private TreeNode constructTree(boolean[][] linked, int root) { - TreeNode tree = new TreeNode(root); - for (int i = 0; i < linked[root].length; i++) { - if (linked[root][i]) { - linked[i][root] = false; // 因为题目给的邻接矩阵是双向的,在这里需要把它转为单向的 - tree.childs.add(constructTree(links, i)); - } - } - return tree; -} - -private TreeNode LCA(TreeNode root, TreeNode p, TreeNode q) { - if (root == null || root.id == p.id || root.id == q.id) return root; - TreeNode ancestor = null; - int cnt = 0; - for (int i = 0; i < root.childs.size(); i++) { - TreeNode tmp = LCA(root.childs.get(i), p, q); - if (tmp != null) { - ancestor = tmp; - cnt++; - } - } - return cnt == 2 ? root : ancestor; -} -``` - -# 2. 小米-懂二进制 - -对两个数进行异或,结果的二进制表示为 1 的那一位就是两个数不同的位。 - -```java -public int countBitDiff(int m, int n) { - return Integer.bitCount(m ^ n); -} -``` - -# 3. 小米-中国牛市 - -背包问题,可以设一个大小为 2 的背包。 - -状态转移方程如下: - -```html -dp[i, j] = max(dp[i, j-1], prices[j] - prices[jj] + dp[i-1, jj]) { jj in range of [0, j-1] } = max(dp[i, j-1], prices[j] + max(dp[i-1, jj] - prices[jj])) -``` - -```java -public int calculateMax(int[] prices) { - int n = prices.length; - int[][] dp = new int[3][n]; - for (int i = 1; i <= 2; i++) { - int localMax = dp[i - 1][0] - prices[0]; - for (int j = 1; j < n; j++) { - dp[i][j] = Math.max(dp[i][j - 1], prices[j] + localMax); - localMax = Math.max(localMax, dp[i - 1][j] - prices[j]); - } - } - return dp[2][n - 1]; -} -``` - -# 4. 微软-LUCKY STRING - -- 斐波那契数列可以预计算; -- 从头到尾遍历字符串的过程,每一轮循环都使用一个 Set 来保存从 i 到 j 出现的字符,并且 Set 保证了字符都不同,因此 Set 的大小就是不同字符的个数。 - -```java -Set fibSet = new HashSet<>(Arrays.asList(1, 2, 3, 5, 8, 13, 21, 34, 55, 89)); -Scanner in = new Scanner(System.in); -String str = in.nextLine(); -int n = str.length(); -Set ret = new HashSet<>(); -for (int i = 0; i < n; i++) { - Set set = new HashSet<>(); - for (int j = i; j < n; j++) { - set.add(str.charAt(j)); - int cnt = set.size(); - if (fibSet.contains(cnt)) { - ret.add(str.substring(i, j + 1)); - } - } -} -String[] arr = ret.toArray(new String[ret.size()]); -Arrays.sort(arr); -for (String s : arr) { - System.out.println(s); -} -``` - -# 5. 微软-Numeric Keypad - -```java -private static int[][] canReach = { - {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0 - {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 1 - {1, 0, 1, 1, 0, 1, 1, 0, 1, 1}, // 2 - {0, 0, 0, 1, 0, 0, 1, 0, 0, 1}, // 3 - {1, 0, 0, 0, 1, 1, 1, 1, 1, 1}, // 4 - {1, 0, 0, 0, 0, 1, 1, 0, 1, 1}, // 5 - {0, 0, 0, 0, 0, 0, 1, 0, 0, 1}, // 6 - {1, 0, 0, 0, 0, 0, 0, 1, 1, 1}, // 7 - {1, 0, 0, 0, 0, 0, 0, 0, 1, 1}, // 8 - {0, 0, 0, 0, 0, 0, 0, 0, 0, 1} // 9 -}; - -private static boolean isLegal(char[] chars, int idx) { - if (idx >= chars.length || idx < 0) return true; - int cur = chars[idx] - '0'; - int next = chars[idx + 1] - '0'; - return canReach[cur][next] == 1; -} - -public static void main(String[] args) { - Scanner in = new Scanner(System.in); - int T = Integer.valueOf(in.nextLine()); - for (int i = 0; i < T; i++) { - String line = in.nextLine(); - char[] chars = line.toCharArray(); - for (int j = 0; j < chars.length - 1; j++) { - while (!isLegal(chars, j)) { - if (--chars[j + 1] < '0') { - chars[j--]--; - } - for (int k = j + 2; k < chars.length; k++) { - chars[k] = '9'; - } - } - } - System.out.println(new String(chars)); - } -} -``` - -# 6. 微软-Spring Outing - -下面以 N = 3,K = 4 来进行讨论。 - -初始时,令第 0 个地方成为待定地点,也就是呆在家里。 - -从第 4 个地点开始投票,每个人只需要比较第 4 个地方和第 0 个地方的优先级,里,如果超过半数的人选择了第 4 个地方,那么更新第 4 个地方成为待定地点。 - -从后往前不断重复以上步骤,不断更新待定地点,直到所有地方都已经投票。 - -上面的讨论中,先令第 0 个地点成为待定地点,是因为这样的话第 4 个地点就只需要和这个地点进行比较,而不用考虑其它情况。如果最开始先令第 1 个地点成为待定地点,那么在对第 2 个地点进行投票时,每个人不仅要考虑第 2 个地点与第 1 个地点的优先级,也要考虑与其后投票地点的优先级。 - -```java -int N = in.nextInt(); -int K = in.nextInt(); -int[][] votes = new int[N][K + 1]; -for (int i = 0; i < N; i++) { - for (int j = 0; j < K + 1; j++) { - int place = in.nextInt(); - votes[i][place] = j; - } -} -int ret = 0; -for (int place = K; place > 0; place--) { - int cnt = 0; - for (int i = 0; i < N; i++) { - if (votes[i][place] < votes[i][ret]) { - cnt++; - } - } - if (cnt > N / 2) { - ret = place; - } -} -System.out.println(ret == 0 ? "otaku" : ret); -``` - -# 7. 微软-S-expression - -# 8. 华为-最高分是多少 - -```java -int N = in.nextInt(); -int M = in.nextInt(); -int[] scores = new int[N]; -for (int i = 0; i < N; i++) { - scores[i] = in.nextInt(); -} -for (int i = 0; i < M; i++) { - String str = in.next(); - if (str.equals("U")) { - int id = in.nextInt() - 1; - int newScore = in.nextInt(); - scores[id] = newScore; - } else { - int idBegin = in.nextInt() - 1; - int idEnd = in.nextInt() - 1; - int ret = 0; - if (idBegin > idEnd) { - int t = idBegin; - idBegin = idEnd; - idEnd = t; - } - for (int j = idBegin; j <= idEnd; j++) { - ret = Math.max(ret, scores[j]); - } - System.out.println(ret); - } -} -``` - -# 9. 华为-简单错误记录 - -```java -HashMap map = new LinkedHashMap<>(); -while (in.hasNextLine()) { - String s = in.nextLine(); - String key = s.substring(s.lastIndexOf('\\') + 1); - map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1); -} -List> list = new LinkedList<>(map.entrySet()); -Collections.sort(list, (o1, o2) -> o2.getValue() - o1.getValue()); -for (int i = 0; i < 8 && i < list.size(); i++) { - String[] token = list.get(i).getKey().split(" "); - String filename = token[0]; - String line = token[1]; - if (filename.length() > 16) filename = filename.substring(filename.length() - 16); - System.out.println(filename + " " + line + " " + list.get(i).getValue()); -} -``` - -# 10. 华为-扑克牌大小 - -```java -public class Main { - - private Map map = new HashMap<>(); - - public Main() { - map.put("3", 0); - map.put("4", 1); - map.put("5", 2); - map.put("6", 3); - map.put("7", 4); - map.put("8", 5); - map.put("9", 6); - map.put("10", 7); - map.put("J", 8); - map.put("Q", 9); - map.put("K", 10); - map.put("A", 11); - map.put("2", 12); - map.put("joker", 13); - map.put("JOKER ", 14); - } - - private String play(String s1, String s2) { - String[] token1 = s1.split(" "); - String[] token2 = s2.split(" "); - CardType type1 = computeCardType(token1); - CardType type2 = computeCardType(token2); - if (type1 == CardType.DoubleJoker) return s1; - if (type2 == CardType.DoubleJoker) return s2; - if (type1 == CardType.Bomb && type2 != CardType.Bomb) return s1; - if (type2 == CardType.Bomb && type1 != CardType.Bomb) return s2; - if (type1 != type2 || token1.length != token2.length) return "ERROR"; - for (int i = 0; i < token1.length; i++) { - int val1 = map.get(token1[i]); - int val2 = map.get(token2[i]); - if (val1 != val2) return val1 > val2 ? s1 : s2; - } - return "ERROR"; - } - - private CardType computeCardType(String[] token) { - boolean hasjoker = false, hasJOKER = false; - for (int i = 0; i < token.length; i++) { - if (token[i].equals("joker")) hasjoker = true; - else if (token[i].equals("JOKER")) hasJOKER = true; - } - if (hasjoker && hasJOKER) return CardType.DoubleJoker; - int maxContinueLen = 1; - int curContinueLen = 1; - String curValue = token[0]; - for (int i = 1; i < token.length; i++) { - if (token[i].equals(curValue)) curContinueLen++; - else { - curContinueLen = 1; - curValue = token[i]; - } - maxContinueLen = Math.max(maxContinueLen, curContinueLen); - } - if (maxContinueLen == 4) return CardType.Bomb; - if (maxContinueLen == 3) return CardType.Triple; - if (maxContinueLen == 2) return CardType.Double; - boolean isStraight = true; - for (int i = 1; i < token.length; i++) { - if (map.get(token[i]) - map.get(token[i - 1]) != 1) { - isStraight = false; - break; - } - } - if (isStraight && token.length == 5) return CardType.Straight; - return CardType.Sigal; - } - - private enum CardType { - DoubleJoker, Bomb, Sigal, Double, Triple, Straight; - } - - public static void main(String[] args) { - Main main = new Main(); - Scanner in = new Scanner(System.in); - while (in.hasNextLine()) { - String s = in.nextLine(); - String[] token = s.split("-"); - System.out.println(main.play(token[0], token[1])); - } - } -} -``` - -# 11. 去哪儿-二分查找 - -对于有重复元素的有序数组,二分查找需要注意以下要点: - -- if (val <= A[m]) h = m; -- 因为 h 的赋值为 m 而不是 m - 1,因此 while 循环的条件也就为 l < h。(如果是 m - 1 循环条件为 l <= h) - -```java -public int getPos(int[] A, int n, int val) { - int l = 0, h = n - 1; - while (l < h) { - int m = l + (h - l) / 2; - if (val <= A[m]) h = m; - else l = m + 1; - } - return A[h] == val ? h : -1; -} -``` - -# 12. 去哪儿-首个重复字符 - -```java -public char findFirstRepeat(String A, int n) { - boolean[] hasAppear = new boolean[256]; - for (int i = 0; i < n; i++) { - char c = A.charAt(i); - if(hasAppear[c]) return c; - hasAppear[c] = true; - } - return ' '; -} -``` - -# 13. 去哪儿-寻找Coder - -```java -public String[] findCoder(String[] A, int n) { - List> list = new ArrayList<>(); - for (String s : A) { - int cnt = 0; - String t = s.toLowerCase(); - int idx = -1; - while (true) { - idx = t.indexOf("coder", idx + 1); - if (idx == -1) break; - cnt++; - } - if (cnt != 0) { - list.add(new Pair<>(s, cnt)); - } - } - Collections.sort(list, (o1, o2) -> (o2.getValue() - o1.getValue())); - String[] ret = new String[list.size()]; - for (int i = 0; i < list.size(); i++) { - ret[i] = list.get(i).getKey(); - } - return ret; -} - -// 牛客网无法导入 javafx.util.Pair,这里就自己实现一下 Pair 类 -private class Pair { - T t; - K k; - - Pair(T t, K k) { - this.t = t; - this.k = k; - } - - T getKey() { - return t; - } - - K getValue() { - return k; - } -} -``` - -# 14. 美团-最大差值 - -贪心策略。 - -```java -public int getDis(int[] A, int n) { - int max = 0; - int soFarMin = A[0]; - for (int i = 1; i < n; i++) { - if(soFarMin > A[i]) soFarMin = A[i]; - else max = Math.max(max, A[i]- soFarMin); - } - return max; -} -``` - -# 15. 美团-棋子翻转 - -```java -public int[][] flipChess(int[][] A, int[][] f) { - int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; - for (int[] ff : f) { - for (int[] dd : direction) { - int r = ff[0] + dd[0] - 1, c = ff[1] + dd[1] - 1; - if(r < 0 || r > 3 || c < 0 || c > 3) continue; - A[r][c] ^= 1; - } - } - return A; -} -``` - -# 16. 美团-拜访 - -```java -private Set paths; -private List curPath; - -public int countPath(int[][] map, int n, int m) { - paths = new HashSet<>(); - curPath = new ArrayList<>(); - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - if (map[i][j] == 1) { - map[i][j] = -1; - int[][] leftRightDirection = {{1, 0}, {-1, 0}}; - int[][] topDownDirection = {{0, 1}, {0, -1}}; - for (int[] lr : leftRightDirection) { - for (int[] td : topDownDirection) { - int[][] directions = {lr, td}; - backtracking(map, n, m, i, j, directions); - } - } - return paths.size(); - } - } - } - return 0; -} - -private void backtracking(int[][] map, int n, int m, int r, int c, int[][] directions) { - if (map[r][c] == 2) { - String path = ""; - for (int num : curPath) { - path += num; - } - paths.add(path); - return; - } - for (int i = 0; i < directions.length; i++) { - int nextR = r + directions[i][0]; - int nextC = c + directions[i][1]; - if (nextR < 0 || nextR >= n || nextC < 0 || nextC >= m || map[nextR][nextC] == -1) continue; - map[nextR][nextC] = map[nextR][nextC] == 2 ? 2 : -1; - curPath.add(nextR); - curPath.add(nextC); - backtracking(map, n, m, nextR, nextC, directions); - curPath.remove(curPath.size() - 1); - curPath.remove(curPath.size() - 1); - map[nextR][nextC] = map[nextR][nextC] == 2 ? 2 : 0; - } -} -``` - -# 17. 美团-直方图内最大矩形 - -```java -public int countArea(int[] A, int n) { - int max = 0; - for (int i = 0; i < n; i++) { - int min = A[i]; - for (int j = i; j < n; j++) { - min = Math.min(min, A[j]); - max = Math.max(max, min * (j - i + 1)); - } - } - return max; -} -``` - -# 18. 美团-字符串计数 - -字符串都是小写字符,可以把字符串当成是 26 进制。但是字典序的比较和普通的整数比较不同,是从左往右进行比较,例如 "ac" 和 "abc",字典序的比较结果为 "ac" > "abc",如果按照整数方法比较,因为 "abc" 是三位数,显然更大。 - -由于两个字符串的长度可能不想等,在 s1 空白部分和 s2 对应部分进行比较时,应该把 s1 的空白部分看成是 'a' 字符进行填充的。 - -还有一点要注意的是,s1 到 s2 长度为 leni 的字符串个数只比较前面 i 个字符。例如 'aaa' 和 'bbb' ,长度为 2 的个数为 'aa' 到 'bb' 的字符串个数,不需要考虑后面部分的字符。 - -在统计个数时,从 len1 开始一直遍历到最大合法长度,每次循环都统计长度为 i 的子字符串个数。 - -```java -String s1 = in.next(); -String s2 = in.next(); -int len1 = in.nextInt(); -int len2 = in.nextInt(); -int len = Math.min(s2.length(), len2); -int[] subtractArr = new int[len]; -for (int i = 0; i < len; i++) { - char c1 = i < s1.length() ? s1.charAt(i) : 'a'; - char c2 = s2.charAt(i); - subtractArr[i] = c2 - c1; -} -int ret = 0; -for (int i = len1; i <= len; i++) { - for (int j = 0; j < i; j++) { - ret += subtractArr[j] * Math.pow(26, i - j - 1); - } -} -System.out.println(ret - 1); -``` - -# 19. 美团-平均年龄 - -```java -int W = in.nextInt(); -double Y = in.nextDouble(); -double x = in.nextDouble(); -int N = in.nextInt(); -while (N-- > 0) { - Y++; // 老员工每年年龄都要加 1 - Y += (21 - Y) * x; -} -System.out.println((int) Math.ceil(Y)); -``` - -# 20. 百度-罪犯转移 - -部分和问题,将每次求的部分和缓存起来。 - -```java -int n = in.nextInt(); -int t = in.nextInt(); -int c = in.nextInt(); -int[] values = new int[n]; -for (int i = 0; i < n; i++) { - values[i] = in.nextInt(); -} -int cnt = 0; -int totalValue = 0; -for (int s = 0, e = c - 1; e < n; s++, e++) { - if (s == 0) { - for (int j = 0; j < c; j++) totalValue += values[j]; - } else { - totalValue = totalValue - values[s - 1] + values[e]; - } - if (totalValue <= t) cnt++; -} -System.out.println(cnt); -``` - -# 22. 百度-裁减网格纸 - -```java -int n = in.nextInt(); -int minX, minY, maxX, maxY; -minX = minY = Integer.MAX_VALUE; -maxX = maxY = Integer.MIN_VALUE; -for (int i = 0; i < n; i++) { - int x = in.nextInt(); - int y = in.nextInt(); - minX = Math.min(minX, x); - minY = Math.min(minY, y); - maxX = Math.max(maxX, x); - maxY = Math.max(maxY, y); -} -System.out.println((int) Math.pow(Math.max(maxX - minX, maxY - minY), 2)); -``` - -# 23. 百度-钓鱼比赛 - -P ( 至少钓一条鱼 ) = 1 - P ( 一条也钓不到 ) - -坑:读取概率矩阵的时候,需要一行一行进行读取,而不能直接用 in.nextDouble()。 - -```java -public static void main(String[] args) { - Scanner in = new Scanner(System.in); - while (in.hasNext()) { - int n = in.nextInt(); - int m = in.nextInt(); - int x = in.nextInt(); - int y = in.nextInt(); - int t = in.nextInt(); - in.nextLine(); // 坑 - double pcc = 0.0; - double sum = 0.0; - for (int i = 1; i <= n; i++) { - String[] token = in.nextLine().split(" "); // 坑 - for (int j = 1; j <= m; j++) { - double p = Double.parseDouble(token[j - 1]); - // double p = in.nextDouble(); - sum += p; - if (i == x && j == y) { - pcc = p; - } - } - } - double pss = sum / (n * m); - pcc = computePOfIRT(pcc, t); - pss = computePOfIRT(pss, t); - System.out.println(pcc > pss ? "cc" : pss > pcc ? "ss" : "equal"); - System.out.printf("%.2f\n", Math.max(pcc, pss)); - } -} - -// compute probability of independent repeated trials -private static double computePOfIRT(double p, int t) { - return 1 - Math.pow((1 - p), t); -} -``` - -# 24. 百度-蘑菇阵 - -这题用回溯会超时,需要用 DP。 - -dp[i][j] 表示到达 (i,j) 位置不会触碰蘑菇的概率。对于 N\*M 矩阵,如果 i == N || j == M,那么 (i,j) 只能有一个移动方向;其它情况下能有两个移动方向。 - -考虑以下矩阵,其中第 3 行和第 3 列只能往一个方向移动,而其它位置可以有两个方向移动。 - - -```java -int N = in.nextInt(); -int M = in.nextInt(); -int K = in.nextInt(); -boolean[][] mushroom = new boolean[N][M]; -while (K-- > 0) { - int x = in.nextInt(); - int y = in.nextInt(); - mushroom[x - 1][y - 1] = true; -} -double[][] dp = new double[N][M]; -dp[0][0] = 1; -for (int i = 0; i < N; i++) { - for (int j = 0; j < M; j++) { - if (mushroom[i][j]) dp[i][j] = 0; - else { - double cur = dp[i][j]; - if (i == N - 1 && j == M - 1) break; - if (i == N - 1) dp[i][j + 1] += cur; - else if (j == M - 1) dp[i + 1][j] += cur; - else { - dp[i][j + 1] += cur / 2; - dp[i + 1][j] += cur / 2; - } - } - } -} -System.out.printf("%.2f\n", dp[N - 1][M - 1]); -``` diff --git a/notes/CyC 学习交流群 问题汇总.md b/notes/CyC 学习交流群 问题汇总.md new file mode 100644 index 00000000..806d48d0 --- /dev/null +++ b/notes/CyC 学习交流群 问题汇总.md @@ -0,0 +1,16 @@ + +* [0. 进程内存空间中,堆和栈的区别](#0-进程内存空间中,堆和栈的区别) + + + +# 0. 进程内存空间中,堆和栈的区别 + +> C++ + +堆:动态、malloc()、new、链式分配、向上生长;栈:函数调用、编译器分配回收、向下生长。 + +https://www.cnblogs.com/sunziying/p/6510030.html + +By @CyC + +--- diff --git a/notes/Docker.md b/notes/Docker.md new file mode 100644 index 00000000..1b61a7fe --- /dev/null +++ b/notes/Docker.md @@ -0,0 +1,91 @@ + +* [一、解决的问题](#一解决的问题) +* [二、与虚拟机的比较](#二与虚拟机的比较) +* [三、优势](#三优势) +* [四、使用场景](#四使用场景) +* [五、镜像与容器](#五镜像与容器) +* [参考资料](#参考资料) + + + +

+ +# 一、解决的问题 + +由于不同的机器有不同的操作系统,以及不同的库和组件,在将一个应用部署到多台机器上需要进行大量的环境配置操作。 + +Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其他机器中。 + +# 二、与虚拟机的比较 + +虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。 + +

+ +

+ +## 启动速度 + +启动虚拟机需要启动虚拟机的操作系统,再启动应用,这个过程非常慢; + +而启动 Docker 相当于启动宿主操作系统上的一个进程。 + +## 占用资源 + +虚拟机是一个完整的操作系统,需要占用大量的磁盘、内存和 CPU,一台机器只能开启几十个的虚拟机。 + +而 Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。 + +# 三、优势 + +除了启动速度快以及占用资源少之外,Docker 具有以下优势: + +## 更容易迁移 + +提供一致性的运行环境,可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。 + +## 更容易维护 + +使用分层技术和镜像,使得应用可以更容易复用重复部分。复用程度越高,维护工作也越容易。 + +## 更容易扩展 + +可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。 + +# 四、使用场景 + +## 持续集成 + +持续集成指的是频繁地将代码集成到主干上,这样能够更快地发现错误。 + +Docker 具有轻量级以及隔离性的特点,在将代码集成到一个 Docker 中不会对其它 Docker 产生影响。 + +## 提供可伸缩的云服务 + +根据应用的负载情况,可以很容易地增加或者减少 Docker。 + +## 搭建微服务架构 + +Docker 轻量级的特点使得它很适合用于部署、维护、组合微服务。 + +# 五、镜像与容器 + +镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。 + +镜像包含着容器运行时所需要的代码以及其它组件,它是一种分层结构,每一层都是只读的(read-only layers)。构建镜像时,会一层一层构建,前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。 + +构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。 + +

+ +# 参考资料 + +- [DOCKER 101: INTRODUCTION TO DOCKER WEBINAR RECAP](https://blog.docker.com/2017/08/docker-101-introduction-docker-webinar-recap/) +- [Docker 入门教程](http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html) +- [Docker container vs Virtual machine](http://www.bogotobogo.com/DevOps/Docker/Docker_Container_vs_Virtual_Machine.php) +- [How to Create Docker Container using Dockerfile](https://linoxide.com/linux-how-to/dockerfile-create-docker-container/) +- [理解 Docker(2):Docker 镜像](http://www.cnblogs.com/sammyliu/p/5877964.html) +- [为什么要使用 Docker?](https://yeasy.gitbooks.io/docker_practice/introduction/why.html) +- [What is Docker](https://www.docker.com/what-docker) +- [持续集成是什么?](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html) + diff --git a/notes/Git.md b/notes/Git.md index 4bb0a543..71ff502c 100644 --- a/notes/Git.md +++ b/notes/Git.md @@ -1,8 +1,7 @@ -* [学习资料](#学习资料) * [集中式与分布式](#集中式与分布式) -* [Git 的中心服务器](#git-的中心服务器) -* [Git 工作流](#git-工作流) +* [中心服务器](#中心服务器) +* [工作流](#工作流) * [分支实现](#分支实现) * [冲突](#冲突) * [Fast forward](#fast-forward) @@ -11,16 +10,10 @@ * [SSH 传输设置](#ssh-传输设置) * [.gitignore 文件](#gitignore-文件) * [Git 命令一览](#git-命令一览) +* [参考资料](#参考资料) -# 学习资料 - -- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html) -- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html) -- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000) -- [Learn Git Branching](https://learngitbranching.js.org/) - # 集中式与分布式 Git 属于分布式版本控制系统,而 SVN 属于集中式。 @@ -33,11 +26,13 @@ Git 属于分布式版本控制系统,而 SVN 属于集中式。 分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。 -# Git 的中心服务器 +# 中心服务器 -Git 的中心服务器用来交换每个用户的修改。没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。Github 就是一种 Git 中心服务器。 +中心服务器用来交换每个用户的修改,没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。 -# Git 工作流 +Github 就是一个中心服务器。 + +# 工作流

@@ -54,14 +49,14 @@ Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master

-可以跳过暂存区域直接从分支中取出修改或者直接提交修改到分支中 +可以跳过暂存区域直接从分支中取出修改,或者直接提交修改到分支中。 - git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交 - git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作 # 分支实现 -Git 把每次提交都连成一条时间线。分支使用指针来实现,例如 master 分支指针指向时间线的最后一个节点,也就是最后一次提交。HEAD 指针指向的是当前分支。 +使用指针将每个提交连接成一条时间线,HEAD 指针指向当前分支指针。

@@ -69,7 +64,7 @@ Git 把每次提交都连成一条时间线。分支使用指针来实现,例

-每次提交只会让当前分支向前移动,而其它分支不会移动。 +每次提交只会让当前分支指针向前移动,而其它分支指针不会移动。

@@ -155,4 +150,9 @@ $ ssh-keygen -t rsa -C "youremail@example.com" 比较详细的地址:http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf +# 参考资料 +- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html) +- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html) +- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000) +- [Learn Git Branching](https://learngitbranching.js.org/) diff --git a/notes/HTTP.md b/notes/HTTP.md index 11045573..f9abd238 100644 --- a/notes/HTTP.md +++ b/notes/HTTP.md @@ -1,6 +1,5 @@ * [一 、基础概念](#一-基础概念) - * [Web 基础](#web-基础) * [URL](#url) * [请求和响应报文](#请求和响应报文) * [二、HTTP 方法](#二http-方法) @@ -25,9 +24,9 @@ * [响应首部字段](#响应首部字段) * [实体首部字段](#实体首部字段) * [五、具体应用](#五具体应用) + * [连接管理](#连接管理) * [Cookie](#cookie) * [缓存](#缓存) - * [连接管理](#连接管理) * [内容协商](#内容协商) * [内容编码](#内容编码) * [范围请求](#范围请求) @@ -40,47 +39,34 @@ * [认证](#认证) * [完整性保护](#完整性保护) * [HTTPs 的缺点](#https-的缺点) - * [配置 HTTPs](#配置-https) -* [七、Web 攻击技术](#七web-攻击技术) - * [跨站脚本攻击](#跨站脚本攻击) - * [跨站请求伪造](#跨站请求伪造) - * [SQL 注入攻击](#sql-注入攻击) - * [拒绝服务攻击](#拒绝服务攻击) -* [八、GET 和 POST 的区别](#八get-和-post-的区别) +* [七、HTTP/2.0](#七http20) + * [HTTP/1.x 缺陷](#http1x-缺陷) + * [二进制分帧层](#二进制分帧层) + * [服务端推送](#服务端推送) + * [首部压缩](#首部压缩) +* [八、HTTP/1.1 新特性](#八http11-新特性) +* [九、GET 和 POST 比较](#九get-和-post-比较) * [作用](#作用) * [参数](#参数) * [安全](#安全) * [幂等性](#幂等性) * [可缓存](#可缓存) * [XMLHttpRequest](#xmlhttprequest) -* [九、HTTP/1.0 与 HTTP/1.1 的区别](#九http10-与-http11-的区别) -* [十、HTTP/2.0](#十http20) - * [HTTP/1.x 缺陷](#http1x-缺陷) - * [二进制分帧层](#二进制分帧层) - * [服务端推送](#服务端推送) - * [首部压缩](#首部压缩) * [参考资料](#参考资料) # 一 、基础概念 -## Web 基础 - -- WWW(World Wide Web)的三种技术:HTML、HTTP、URL -- HTML(HyperText Markup Language,超文本标记语言) -- HTTP(HyperText Transfer Protocol,超文本传输协议) -- RFC(Request for Comments,征求修正意见书),互联网的设计文档。 - ## URL -- URI(Uniform Resource Indentifier,统一资源标识符) -- URL(Uniform Resource Locator,统一资源定位符) -- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4。 - URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。 -

+- URI(Uniform Resource Identifier,统一资源标识符) +- URL(Uniform Resource Locator,统一资源定位符) +- URN(Uniform Resource Name,统一资源名称) + +

## 请求和响应报文 @@ -116,7 +102,7 @@ URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基 POST 主要用来传输数据,而 GET 主要用来获取资源。 -更多 POST 与 GET 的比较请见第八章。 +更多 POST 与 GET 的比较请见第九章。 ## PUT @@ -169,9 +155,9 @@ DELETE /file.html HTTP/1.1 ## CONNECT -> 要求用隧道协议连接代理 +> 要求在与代理服务器通信时建立隧道 -要求在与代理服务器通信时建立隧道,使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 +使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 ```html CONNECT www.example.com:443 HTTP/1.1 @@ -187,8 +173,7 @@ CONNECT www.example.com:443 HTTP/1.1 发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。 -通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。 - +通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。 # 三、HTTP 状态码 服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。 @@ -211,7 +196,7 @@ CONNECT www.example.com:443 HTTP/1.1 - **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 -- **206 Partial Content** :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。 +- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。 ## 3XX 重定向 @@ -233,7 +218,7 @@ CONNECT www.example.com:443 HTTP/1.1 - **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 -- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。 +- **403 Forbidden** :请求被拒绝。 - **404 Not Found** @@ -241,7 +226,7 @@ CONNECT www.example.com:443 HTTP/1.1 - **500 Internal Server Error** :服务器正在执行请求时发生错误。 -- **503 Service Unavilable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 +- **503 Service Unavailable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 # 四、HTTP 首部 @@ -318,11 +303,32 @@ CONNECT www.example.com:443 HTTP/1.1 # 五、具体应用 +## 连接管理 + +

+ +### 1. 短连接与长连接 + +当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。 + +长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。 + +- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`; +- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`。 + +### 2. 流水线 + +默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。 + +流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。 + ## Cookie HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 -Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。它用于告知服务端两个请求是否来自同一浏览器,并保持用户的登录状态。 +Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 + +Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。 ### 1. 用途 @@ -330,8 +336,6 @@ Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据 - 个性化设置(如用户自定义设置、主题等) - 浏览器行为跟踪(如跟踪分析用户行为等) -Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB。 - ### 2. 创建过程 服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。 @@ -345,7 +349,7 @@ Set-Cookie: tasty_cookie=strawberry [page content] ``` -客户端之后对同一个服务器发送请求时,会从浏览器中读出 Cookie 信息通过 Cookie 请求首部字段发送给服务器。 +客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。 ```html GET /sample_page.html HTTP/1.1 @@ -356,33 +360,13 @@ Cookie: yummy_cookie=choco; tasty_cookie=strawberry ### 3. 分类 - 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。 -- 持久性 Cookie:指定一个特定的过期时间(Expires)或有效期(Max-Age)之后就成为了持久性的 Cookie。 +- 持久性 Cookie:指定一个特定的过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。 ```html Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; ``` -### 4. JavaScript 获取 Cookie - -通过 `Document.cookie` 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。 - -```html -document.cookie = "yummy_cookie=choco"; -document.cookie = "tasty_cookie=strawberry"; -console.log(document.cookie); -``` - -### 5. Secure 和 HttpOnly - -标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。 - -标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。因为跨域脚本 (XSS) 攻击常常使用 JavaScript 的 `Document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。 - -```html -Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly -``` - -### 6. 作用域 +### 4. 作用域 Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。 @@ -392,29 +376,50 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径 - /docs/Web/ - /docs/Web/HTTP -### 7. Session +### 5. JavaScript + +通过 `Document.cookie` 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。 + +```html +document.cookie = "yummy_cookie=choco"; +document.cookie = "tasty_cookie=strawberry"; +console.log(document.cookie); +``` + +### 6. HttpOnly + +标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `Document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。 + +```html +Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly +``` + +### 7. Secure + +标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。 + +### 8. Session 除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 -Session 可以存储在服务器上的文件、数据库或者内存中,现在最常见的是将 Session 存储在内存型数据库中,比如 Redis。 +Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 -使用 Session 维护用户登录的过程如下: +使用 Session 维护用户登录状态的过程如下: - 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; -- 服务器验证该用户名和密码; -- 如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 ID 称为 Session ID; +- 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID; - 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中; -- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之后的业务操作。 +- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。 应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。 -### 8. 浏览器禁用 Cookie +### 9. 浏览器禁用 Cookie 此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。 -### 9. Cookie 与 Session 选择 +### 10. Cookie 与 Session 选择 -- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存取任何类型的数据,因此在考虑数据复杂性时 首选 Session; +- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存取任何类型的数据,因此在考虑数据复杂性时首选 Session; - Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; - 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 @@ -423,7 +428,7 @@ Session 可以存储在服务器上的文件、数据库或者内存中,现在 ### 1. 优点 - 缓解服务器压力; -- 减低客户端获取资源的延迟(缓存资源比服务器上的资源离客户端更近)。 +- 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存在地理位置上也有可能比源服务器来得近,例如浏览器缓存。 ### 2. 实现方法 @@ -434,7 +439,7 @@ Session 可以存储在服务器上的文件、数据库或者内存中,现在 HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。 -**(一)禁止进行缓存** +**3.1 禁止进行缓存** no-store 指令规定不能对请求或响应的任何一部分进行缓存。 @@ -442,7 +447,7 @@ no-store 指令规定不能对请求或响应的任何一部分进行缓存。 Cache-Control: no-store ``` -**(二)强制确认缓存** +**3.2 强制确认缓存** no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效才将能使用该缓存对客户端的请求进行响应。 @@ -450,7 +455,7 @@ no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源 Cache-Control: no-cache ``` -**(三)私有缓存和公共缓存** +**3.3 私有缓存和公共缓存** private 指令规定了将资源作为私有缓存,只能被单独用户所使用,一般存储在用户浏览器中。 @@ -464,7 +469,7 @@ public 指令规定了将资源作为公共缓存,可以被多个用户所使 Cache-Control: public ``` -**(四)缓存过期机制** +**3.4 缓存过期机制** max-age 指令出现在请求报文中,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。 @@ -474,15 +479,18 @@ max-age 指令出现在响应报文中,表示缓存资源在缓存服务器中 Cache-Control: max-age=31536000 ``` -Expires 字段也可以用于告知缓存服务器该资源什么时候会过期。在 HTTP/1.1 中,会优先处理 Cache-Control : max-age 指令;而在 HTTP/1.0 中,Cache-Control : max-age 指令会被忽略掉。 +Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。 ```html Expires: Wed, 04 Jul 2012 08:26:05 GMT ``` +- 在 HTTP/1.1 中,会优先处理 max-age 指令; +- 在 HTTP/1.0 中,max-age 指令会被忽略掉。 + ### 4. 缓存验证 -需要先了解 ETag 首部字段的含义,它是资源的唯一表示。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一表示。 +需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。 ```html ETag: "82e22293907ce725faf67773957acd12" @@ -494,7 +502,7 @@ ETag: "82e22293907ce725faf67773957acd12" If-None-Match: "82e22293907ce725faf67773957acd12" ``` -Last-Modified 首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 Not Modified 响应, +Last-Modified 首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 Not Modified 响应。 ```html Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT @@ -504,39 +512,23 @@ Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT ``` -## 连接管理 - -

- -### 1. 短连接与长连接 - -当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源,如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。 - -HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 Connection : close;而在 HTTP/1.1 之前默认是短连接的,如果需要长连接,则使用 Connection : Keep-Alive。 - -### 2. 流水线 - -默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到应答过后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。 - -流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。 - ## 内容协商 通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。 ### 1. 类型 -**(一)服务端驱动型内容协商** +**1.1 服务端驱动型** 客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Languag,服务器根据这些字段返回特定的资源。 它存在以下问题: - 服务器很难知道客户端浏览器的全部信息; -- 客户端提供的信息相当冗长(HTTP/2 协议的首部压缩机制缓解了这个问题),并且存在隐私风险(HTTP 指纹识别技术)。 +- 客户端提供的信息相当冗长(HTTP/2 协议的首部压缩机制缓解了这个问题),并且存在隐私风险(HTTP 指纹识别技术); - 给定的资源需要返回不同的展现形式,共享缓存的效率会降低,而服务器端的实现会越来越复杂。 -**(二)代理驱动型协商** +**1.2 代理驱动型** 服务器返回 300 Multiple Choices 或者 406 Not Acceptable,客户端从中选出最合适的那个资源。 @@ -552,9 +544,11 @@ Vary: Accept-Language ## 内容编码 -内容编码将实体主体进行压缩,从而减少传输的数据量。常用的内容编码有:gzip、compress、deflate、identity。 +内容编码将实体主体进行压缩,从而减少传输的数据量。 -浏览器发送 Accept-Encoding 首部,其中包含有它所支持的压缩算法,以及各自的优先级,服务器则从中选择一种,使用该算法对响应的消息主体进行压缩,并且发送 Content-Encoding 首部来告知浏览器它选择了哪一种算法。由于该内容协商过程是基于编码类型来选择资源的展现形式的,在响应中,Vary 首部中至少要包含 Content-Encoding,这样的话,缓存服务器就可以对资源的不同展现形式进行缓存。 +常用的内容编码有:gzip、compress、deflate、identity。 + +浏览器发送 Accept-Encoding 首部,其中包含有它所支持的压缩算法,以及各自的优先级。服务器则从中选择一种,使用该算法对响应的消息主体进行压缩,并且发送 Content-Encoding 首部来告知浏览器它选择了哪一种算法。由于该内容协商过程是基于编码类型来选择资源的展现形式的,在响应的 Vary 首部至少要包含 Content-Encoding。 ## 范围请求 @@ -632,13 +626,18 @@ HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名, 使用代理的主要目的是: - 缓存 +- 负载均衡 - 网络访问控制 - 访问日志记录 -代理服务器分为正向代理和反向代理两种,用户察觉得到正向代理的存在,而反向代理一般位于内部网络中,用户察觉不到。 +代理服务器分为正向代理和反向代理两种: + +- 用户察觉得到正向代理的存在。

+- 而反向代理一般位于内部网络中,用户察觉不到。 +

### 2. 网关 @@ -647,7 +646,7 @@ HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名, ### 3. 隧道 -使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。 +使用 SSL 等加密手段,在客户端和服务器之间建立一条安全的通信线路。 # 六、HTTPs @@ -657,7 +656,7 @@ HTTP 有以下安全性问题: - 不验证通信方的身份,通信方的身份有可能遭遇伪装; - 无法证明报文的完整性,报文有可能遭篡改。 -HTTPs 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信。也就是说 HTTPs 使用了隧道进行通信。 +HTTPs 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信,也就是说 HTTPs 使用了隧道进行通信。 通过使用 SSL,HTTPs 具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)。 @@ -667,25 +666,29 @@ HTTPs 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer) ### 1. 对称密钥加密 -对称密钥加密(Symmetric-Key Encryption),加密的加密和解密使用同一密钥。 +对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。 - 优点:运算速度快; -- 缺点:密钥容易被获取。 +- 缺点:无法安全地将密钥传输给通信方。

-### 2. 公开密钥加密 +### 2.非对称密钥加密 -公开密钥加密(Public-Key Encryption),也称为非对称密钥加密,使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。 +非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。 -- 优点:更为安全; -- 缺点:运算速度慢; +公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。 + +非对称密钥除了用来加密,还可以用来进行签名。因为私有密钥无法被其他人获取,因此通信发送方使用其私有密钥进行签名,通信接收方使用发送方的公开密钥对签名进行解密,就能判断这个签名是否正确。 + +- 优点:可以更安全地将公开密钥传输给通信发送方; +- 缺点:运算速度慢。

### 3. HTTPs 采用的加密方式 -HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称密钥来保证安全性,之后使用对称密钥加密进行通信来保证效率。(下图中的 Session Key 就是对称密钥) +HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。(下图中的 Session Key 就是对称密钥)

@@ -699,237 +702,81 @@ HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称 进行 HTTPs 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。 +通信开始时,客户端需要使用服务器的公开密钥将自己的私有密钥传输给服务器,之后再进行对称密钥加密。 +

## 完整性保护 SSL 提供报文摘要功能来进行完整性保护。 -HTTP 也提供了 MD5 报文摘要功能,但是却不是安全的。例如报文内容被篡改之后,同时重新计算 MD5 的值,通信接收方是无法意识到发生篡改。 +HTTP 也提供了 MD5 报文摘要功能,但不是安全的。例如报文内容被篡改之后,同时重新计算 MD5 的值,通信接收方是无法意识到发生了篡改。 HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认证这两个操作。试想一下,加密之后的报文,遭到篡改之后,也很难重新计算报文摘要,因为无法轻易获取明文。 ## HTTPs 的缺点 - 因为需要进行加密解密等过程,因此速度会更慢; -- 需要支付证书授权的高费用。 +- 需要支付证书授权的高额费用。 +# 七、HTTP/2.0 -## 配置 HTTPs +## HTTP/1.x 缺陷 -[Nginx 配置 HTTPS 服务器](https://aotu.io/notes/2016/08/16/nginx-https/index.html) +HTTP/1.x 实现简单是以牺牲性能为代价的: -# 七、Web 攻击技术 +- 客户端需要使用多个连接才能实现并发和缩短延迟; +- 不会压缩请求和响应首部,从而导致不必要的网络流量; +- 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。 -## 跨站脚本攻击 +## 二进制分帧层 -### 1. 概念 +HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。 -跨站脚本攻击(Cross-Site Scripting, XSS),可以将代码注入到用户浏览的网页上,这种代码包括 HTML 和 JavaScript。 +

-例如有一个论坛网站,攻击者可以在上面发布以下内容: +在通信过程中,只会有一个 TCP 连接存在,它承载了任意数量的双向数据流(Stream)。 -```html - -``` +- 一个数据流(Stream)都有一个唯一标识符和可选的优先级信息,用于承载双向信息。 +- 消息(Message)是与逻辑请求或响应对应的完整的一系列帧。 +- 帧(Frame)是最小的通信单位,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。 -之后该内容可能会被渲染成以下形式: +

-```html -

-``` +## 服务端推送 -另一个用户浏览了含有这个内容的页面将会跳转到 domain.com 并携带了当前作用域的 Cookie。如果这个论坛网站通过 Cookie 管理用户登录状态,那么攻击者就可以通过这个 Cookie 登录被攻击者的账号了。 +HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。 -### 2. 危害 +

-- 窃取用户的 Cookie 值 -- 伪造虚假的输入表单骗取个人信息 -- 显示伪造的文章或者图片 +## 首部压缩 -### 3. 防范手段 +HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。 -**(一)设置 Cookie 为 HttpOnly** +HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表,从而避免了重复传输。 -设置了 HttpOnly 的 Cookie 可以防止 JavaScript 脚本调用,在一定程度上可以防止 XSS 窃取用户的 Cookie 信息。 +不仅如此,HTTP/2.0 也使用 Huffman 编码对首部字段进行压缩。 -**(二)过滤特殊字符** +

-许多语言都提供了对 HTML 的过滤: +# 八、HTTP/1.1 新特性 -- PHP 的 htmlentities() 或是 htmlspecialchars()。 -- Python 的 cgi.escape()。 -- Java 的 xssprotect (Open Source Library)。 -- Node.js 的 node-validator。 +详细内容请见上文 -例如 htmlspecialchars() 可以将 `<` 转义为 `<`,将 `>` 转义为 `>`,从而避免 HTML 和 Jascript 代码的运行。 +- 默认是长连接 -**(三)富文本编辑器的处理** +- 支持管线化处理 -富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。 +- 支持同时打开多个 TCP 连接 -富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,可以定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。 +- 支持虚拟主机 -以下例子中,form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。 +- 新增状态码 100 -[XSS 过滤在线测试](http://jsxss.com/zh/try.html) +- 支持分块传输编码 -```html -

XSS Demo

+- 新增缓存处理指令 max-age -

-Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist. -

- -
- - -
- -
hello
- -

- http -

- -

Features:

-
    -
  • Specifies HTML tags and their attributes allowed with whitelist
  • -
  • Handle any tags or attributes using custom function
  • -
- - -``` - -```html -

XSS Demo

- -

-Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist. -

- -<form> - <input type="text" name="q" value="test"> - <button id="submit">Submit</button> -</form> - -
hello
- -

- http -

- -

Features:

-
    -
  • Specifies HTML tags and their attributes allowed with whitelist
  • -
  • Handle any tags or attributes using custom function
  • -
- -<script type="text/javascript"> -alert(/xss/); -</script> -``` - -## 跨站请求伪造 - -### 1. 概念 - -跨站请求伪造(Cross-site request forgery,CSRF),是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了 Web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。 - -XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户浏览器的信任。 - -假如一家银行用以执行转账操作的 URL 地址如下: - -``` -http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName。 -``` - -那么,一个恶意攻击者可以在另一个网站上放置如下代码: - -``` -。 -``` - -如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。 - -这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。 - -透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。 - -### 2. 防范手段 - -**(一)检查 Referer 字段** - -HTTP 头中有一个 Referer 字段,这个字段用于标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下。 - -这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 HTTP 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。 - -**(二)添加校验 Token** - -由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 Cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再执行 CSRF 攻击。这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 Token 的值为空或者错误,拒绝这个可疑请求。 - -也可以要求用户输入验证码来进行校验。 - -## SQL 注入攻击 - -### 1. 概念 - -服务器上的数据库运行非法的 SQL 语句,主要通过拼接来完成。 - -### 2. 攻击原理 - -例如一个网站登录验证的 SQL 查询代码为: - -```sql -strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');" -``` - -如果填入以下内容: - -```sql -userName = "1' OR '1'='1"; -passWord = "1' OR '1'='1"; -``` - -那么 SQL 查询字符串为: - -```sql -strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');" -``` - -此时无需验证通过就能执行以下查询: - -```sql -strSQL = "SELECT * FROM users;" -``` - -### 3. 防范手段 - -**(一)使用参数化查询** - -以下以 Java 中的 PreparedStatement 为例,它是预先编译的 SQL 语句,可以传入适当参数并且多次执行。由于没有拼接的过程,因此可以防止 SQL 注入的发生。 - -```java -PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE userid=? AND password=?"); -stmt.setString(1, userid); -stmt.setString(2, password); -ResultSet rs = stmt.executeQuery(); -``` - -**(二)单引号转换** - -将传入的参数中的单引号转换为连续两个单引号,PHP 中的 Magic quote 可以完成这个功能。 - -## 拒绝服务攻击 - -拒绝服务攻击(denial-of-service attack,DoS),亦称洪水攻击,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。 - -分布式拒绝服务攻击(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) - -# 八、GET 和 POST 的区别 +# 九、GET 和 POST 比较 ## 作用 @@ -937,7 +784,9 @@ GET 用于获取资源,而 POST 用于传输实体主体。 ## 参数 -GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。 +GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。 + +因为 URL 只支持 ASCII 码,因此 GET 的参数中如果存在中文等字符就需要先进行编码。例如 `中文` 会转换为 `%E4%B8%AD%E6%96%87`,而空格会转换为 `%20`。POST 参考支持标准字符集。 ``` GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1 @@ -949,10 +798,6 @@ Host: w3schools.com name1=value1&name2=value2 ``` -不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。 - -因为 URL 只支持 ASCII 码,因此 GET 的参数中如果存在中文等字符就需要先进行编码,例如`中文`会转换为`%E4%B8%AD%E6%96%87`,而空格会转换为`%20`。POST 支持标准字符集。 - ## 安全 安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。 @@ -965,9 +810,13 @@ GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实 ## 幂等性 -幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。所有的安全方法也都是幂等的。 +幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。 -GET /pageX HTTP/1.1 是幂等的。连续调用多次,客户端接收到的结果都是一样的: +所有的安全方法也都是幂等的。 + +在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。 + +GET /pageX HTTP/1.1 是幂等的,连续调用多次,客户端接收到的结果都是一样的: ``` GET /pageX HTTP/1.1 @@ -976,10 +825,10 @@ 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 -> Adds a 1nd row POST /add_row HTTP/1.1 -> Adds a 2nd row POST /add_row HTTP/1.1 -> Adds a 3rd row ``` @@ -1006,48 +855,10 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404 > XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过 URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。 -在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。而 GET 方法 Header 和 Data 会一起发送。 +- 在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。 +- 而 GET 方法 Header 和 Data 会一起发送。 -# 九、HTTP/1.0 与 HTTP/1.1 的区别 -- HTTP/1.1 默认是持久连接 -- HTTP/1.1 支持管线化处理 -- HTTP/1.1 支持虚拟主机 -- HTTP/1.1 新增状态码 100 -- HTTP/1.1 支持分块传输编码 -- HTTP/1.1 新增缓存处理指令 max-age - -# 十、HTTP/2.0 - -## HTTP/1.x 缺陷 - - HTTP/1.x 实现简单是以牺牲应用性能为代价的: - -- 客户端需要使用多个连接才能实现并发和缩短延迟; -- 不会压缩请求和响应首部,从而导致不必要的网络流量; -- 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。 - -## 二进制分帧层 - -HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。 - -

- -在通信过程中,只会有一个 TCP 连接存在,它承载了任意数量的双向数据流(Stream)。一个数据流都有一个唯一标识符和可选的优先级信息,用于承载双向信息。消息(Message)是与逻辑请求或响应消息对应的完整的一系列帧。帧(Fram)是最小的通信单位,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。 - -

- -## 服务端推送 - -HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。 - -

- -## 首部压缩 - -HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表,从而避免了重复传输。不仅如此,HTTP/2.0 也使用 Huffman 编码对首部字段进行压缩。 - -

# 参考资料 @@ -1055,6 +866,7 @@ HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。HTTP/2.0 - [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) - [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn) - [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php) +- [Difference between file URI and URL in java](http://java2db.com/java-io/how-to-get-and-the-difference-between-file-uri-and-url-in-java) - [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement) - [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html) - [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/) @@ -1071,10 +883,6 @@ HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。HTTP/2.0 - [COOKIE 和 SESSION 有什么区别](https://www.zhihu.com/question/19786827) - [Cookie/Session 的机制与安全](https://harttle.land/2015/08/10/cookie-session.html) - [HTTPS 证书原理](https://shijianan.com/2017/06/11/https/) -- [维基百科:跨站脚本](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC) -- [维基百科:SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A) -- [维基百科:跨站点请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0) -- [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A) - [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 (XHR) Uses Multiple Packets for HTTP POST?](https://blog.josephscott.org/2009/08/27/xmlhttprequest-xhr-uses-multiple-packets-for-http-post/) diff --git a/notes/JDK 中的设计模式.md b/notes/JDK 中的设计模式.md deleted file mode 100644 index 0d858e4b..00000000 --- a/notes/JDK 中的设计模式.md +++ /dev/null @@ -1,283 +0,0 @@ - -* [一、创建型](#一创建型) - * [1. 单例模式](#1-单例模式) - * [2. 简单工厂模式](#2-简单工厂模式) - * [3. 工厂方法模式](#3-工厂方法模式) - * [4. 抽象工厂](#4-抽象工厂) - * [5. 生成器模式](#5-生成器模式) - * [6. 原型模式](#6-原型模式) -* [二、行为型](#二行为型) - * [1. 责任链](#1-责任链) - * [2. 命令模式](#2-命令模式) - * [3. 解释器模式](#3-解释器模式) - * [4. 迭代器](#4-迭代器) - * [5. 中间人模式](#5-中间人模式) - * [6. 备忘录模式](#6-备忘录模式) - * [7. 观察者模式](#7-观察者模式) - * [8. 策略模式](#8-策略模式) - * [9. 模板方法](#9-模板方法) - * [10. 访问者模式](#10-访问者模式) - * [11. 空对象模式](#11-空对象模式) -* [三、结构型](#三结构型) - * [1. 适配器](#1-适配器) - * [2. 桥接模式](#2-桥接模式) - * [3. 组合模式](#3-组合模式) - * [4. 装饰者模式](#4-装饰者模式) - * [5. 蝇量模式](#5-蝇量模式) - * [6. 动态代理](#6-动态代理) -* [参考资料](#参考资料) - - - -# 一、创建型 - -## 1. 单例模式 - -确保只实例化一个对象,并提供一个对象的全局访问点。 - -```java -java.lang.Runtime#getRuntime() -java.awt.Toolkit#getDefaultToolkit() -java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment() -java.awt.Desktop#getDesktop() -``` - -## 2. 简单工厂模式 - -在不对用户暴露对象内部逻辑的前提下创建对象。 - -## 3. 工厂方法模式 - -定义创建对象的接口,但是让子类来决定应该使用哪个类来创建。 - -```java -java.lang.Proxy#newProxyInstance() -java.lang.Object#toString() -java.lang.Class#newInstance() -java.lang.reflect.Array#newInstance() -java.lang.reflect.Constructor#newInstance() -java.lang.Boolean#valueOf(String) -java.lang.Class#forName() -``` - -## 4. 抽象工厂 - -提供一个创建相关对象家族的接口,而没有明确指明它们的类。 - -```java -java.util.Calendar#getInstance() -java.util.Arrays#asList() -java.util.ResourceBundle#getBundle() -java.sql.DriverManager#getConnection() -java.sql.Connection#createStatement() -java.sql.Statement#executeQuery() -java.text.NumberFormat#getInstance() -javax.xml.transform.TransformerFactory#newInstance() -``` - -## 5. 生成器模式 - -定义一个新的类来构造另一个类的实例,以创建一个复杂的对象。 - -它可以封装一个对象的构造过程,并允许按步骤构造。 - -```java -java.lang.StringBuilder#append() -java.lang.StringBuffer#append() -java.sql.PreparedStatement -javax.swing.GroupLayout.Group#addComponent() -``` - -## 6. 原型模式 - -使用原型实例指定要创建对象的类型;通过复制这个原型来创建新对象。 - -```java -java.lang.Object#clone() -java.lang.Cloneable -``` - -# 二、行为型 - -## 1. 责任链 - -避免将请求的发送者附加到其接收者,从而使其它对象也可以处理请求;将请求以对象的方式发送到链上直到请求被处理完毕。 - -```java -java.util.logging.Logger#log() -javax.servlet.Filter#doFilter() -``` - -## 2. 命令模式 - -将命令封装进对象中;允许使用命令对象对客户对象进行参数化;允许将命令对象存放到队列中。 - -```java -java.lang.Runnable -javax.swing.Action -``` - -## 3. 解释器模式 - -为语言创建解释器,通常由语言的语法和语法分析来定义。 - -```java -java.util.Pattern -java.text.Normalizer -java.text.Format -``` - -## 4. 迭代器 - -提供一种一致的访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。 - -```java -java.util.Iterator -java.util.Enumeration -``` - -## 5. 中间人模式 - -使用中间人对象来封装对象之间的交互。中间人模式可以降低交互对象之间的耦合程度。 - -```java -java.util.Timer -java.util.concurrent.Executor#execute() -java.util.concurrent.ExecutorService#submit() -java.lang.reflect.Method#invoke() -``` - -## 6. 备忘录模式 - -在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。 - -```java -java.util.Date -java.io.Serializable -``` - -## 7. 观察者模式 - -定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。 - -```java -java.util.EventListener -javax.servlet.http.HttpSessionBindingListener -javax.servlet.http.HttpSessionAttributeListener -javax.faces.event.PhaseListener -``` - -## 8. 策略模式 - -定义一系列算法,封装每个算法,并使它们可以互换。策略可以让算法独立于使用它的客户端。 - -```java -java.util.Comparator#compare() -javax.servlet.http.HttpServlet -javax.servlet.Filter#doFilter() -``` - -## 9. 模板方法 - -定义算法框架,并将一些步骤的实现延迟到子类。通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。 - -```java -java.util.Collections#sort() -java.io.InputStream#skip() -java.io.InputStream#read() -java.util.AbstractList#indexOf() -``` - -## 10. 访问者模式 - -提供便捷的维护方式来操作一组对象。它使你在不改变操作对象的前提下,可以修改或扩展对象的行为。 - -例如集合,它可以包含不同类型的元素,访问者模式允许在不知道具体元素类型的前提下对集合元素进行一些操作。 - -```java -javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor -javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor -``` - -## 11. 空对象模式 - -使用什么都不做的空对象来替代 NULL。 - -# 三、结构型 - -## 1. 适配器 - -把一个类接口转换成另一个用户需要的接口。 - -```java -java.util.Arrays#asList() -javax.swing.JTable(TableModel) -java.io.InputStreamReader(InputStream) -java.io.OutputStreamWriter(OutputStream) -javax.xml.bind.annotation.adapters.XmlAdapter#marshal() -javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal() -``` - -## 2. 桥接模式 - -将抽象与实现分离开来,使它们可以独立变化。 - -```java -AWT (It provides an abstraction layer which maps onto the native OS the windowing support.) -JDBC -``` - -## 3. 组合模式 - -将对象组合成树形结构来表示整体-部分层次关系,允许用户以相同的方式处理单独对象和组合对象。 - -```java -javax.swing.JComponent#add(Component) -java.awt.Container#add(Component) -java.util.Map#putAll(Map) -java.util.List#addAll(Collection) -java.util.Set#addAll(Collection) -``` - -## 4. 装饰者模式 - -为对象动态添加功能。 - -```java -java.io.BufferedInputStream(InputStream) -java.io.DataInputStream(InputStream) -java.io.BufferedOutputStream(OutputStream) -java.util.zip.ZipOutputStream(OutputStream) -java.util.Collections#checked[List|Map|Set|SortedSet|SortedMap]() -``` - -## 5. 蝇量模式 - -利用共享的方式来支持大量的对象,这些对象一部分内部状态是相同的,而另一份状态可以变化。 - -Java 利用缓存来加速大量小对象的访问时间。 - -```java -java.lang.Integer#valueOf(int) -java.lang.Boolean#valueOf(boolean) -java.lang.Byte#valueOf(byte) -java.lang.Character#valueOf(char) -``` - -## 6. 动态代理 - -提供一个占位符来控制对象的访问。 - -代理可以是一些轻量级的对象,它控制着对重量级对象的访问,只有在真正实例化这些重量级对象时才会去实例化它。 - -```java -java.lang.reflect.Proxy -RMI -``` - -# 参考资料 - -- [The breakdown of design patterns in JDK](http://www.programering.com/a/MTNxAzMwATY.html) -- [Design Patterns](http://www.oodesign.com/) - - diff --git a/notes/Java IO.md b/notes/Java IO.md index 885ecbe1..e137fb7b 100644 --- a/notes/Java IO.md +++ b/notes/Java IO.md @@ -2,8 +2,17 @@ * [一、概览](#一概览) * [二、磁盘操作](#二磁盘操作) * [三、字节操作](#三字节操作) + * [实现文件复制](#实现文件复制) + * [装饰者模式](#装饰者模式) * [四、字符操作](#四字符操作) + * [编码与解码](#编码与解码) + * [String 的编码方式](#string-的编码方式) + * [Reader 与 Writer](#reader-与-writer) + * [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容) * [五、对象操作](#五对象操作) + * [序列化](#序列化) + * [Serializable](#serializable) + * [transient](#transient) * [六、网络操作](#六网络操作) * [InetAddress](#inetaddress) * [URL](#url) @@ -26,78 +35,190 @@ Java 的 I/O 大概可以分成以下几类: -1. 磁盘操作:File -2. 字节操作:InputStream 和 OutputStream -3. 字符操作:Reader 和 Writer -4. 对象操作:Serializable -5. 网络操作:Socket -6. 新的输入/输出:NIO +- 磁盘操作:File +- 字节操作:InputStream 和 OutputStream +- 字符操作:Reader 和 Writer +- 对象操作:Serializable +- 网络操作:Socket +- 新的输入/输出:NIO # 二、磁盘操作 -File 类可以用于表示文件和目录,但是它只用于表示文件的信息,而不表示文件的内容。 +File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。 + +递归地列出一个目录下所有文件: + +```java +public static void listAllFiles(File dir) { + if (dir == null || !dir.exists()) { + return; + } + if (dir.isFile()) { + System.out.println(dir.getName()); + return; + } + for (File file : dir.listFiles()) { + listAllFiles(file); + } +} +``` # 三、字节操作 -

+## 实现文件复制 -Java I/O 使用了装饰者模式来实现。以 InputStream 为例,InputStream 是抽象组件,FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作。FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。 +```java +public static void copyFile(String src, String dist) throws IOException { + FileInputStream in = new FileInputStream(src); + FileOutputStream out = new FileOutputStream(dist); + + byte[] buffer = new byte[20 * 1024]; + int cnt; + + // read() 最多读取 buffer.length 个字节 + // 返回的是实际读取的个数 + // 返回 -1 的时候表示读到 eof,即文件尾 + while ((cnt = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, cnt); + } + + in.close(); + out.close(); +} +``` + +## 装饰者模式 + +Java I/O 使用了装饰者模式来实现。以 InputStream 为例, + +- InputStream 是抽象组件; +- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作; +- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。 + +

实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。 ```java -BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); +FileInputStream fileInputStream = new FileInputStream(filePath); +BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); ``` DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。 -批量读入文件内容到字节数组: - -```java -byte[] buf = new byte[20*1024]; -int bytes = 0; -// 最多读取 buf.length 个字节,返回的是实际读取的个数,返回 -1 的时候表示读到 eof,即文件尾 -while((bytes = in.read(buf, 0 , buf.length)) != -1) { - // ... -} -``` - # 四、字符操作 -不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。 - -InputStreamReader 实现从文本文件的字节流解码成字符流;OutputStreamWriter 实现字符流编码成为文本文件的字节流。它们继承自 Reader 和 Writer。 +## 编码与解码 编码就是把字符转换为字节,而解码是把字节重新组合成字符。 -```java -byte[] bytes = str.getBytes(encoding); // 编码 -String str = new String(bytes, encoding); // 解码 -``` - 如果编码和解码过程使用不同的编码方式那么就出现了乱码。 -- GBK 编码中,中文占 2 个字节,英文占 1 个字节; -- UTF-8 编码中,中文占 3 个字节,英文占 1 个字节; -- 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 来存储。 +Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。 + +## String 的编码方式 + +String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。 + +```java +String str1 = "中文"; +byte[] bytes = str1.getBytes("UTF-8"); +String str2 = new String(bytes, "UTF-8"); +System.out.println(str2); +``` + +在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。 + +```java +byte[] bytes = str1.getBytes(); +``` + +## Reader 与 Writer + +不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。 + +- InputStreamReader 实现从字节流解码成字符流; +- OutputStreamWriter 实现字符流编码成为字节流。 + +## 实现逐行输出文本文件的内容 + +```java +public static void readFileContent(String filePath) throws IOException { + + FileReader fileReader = new FileReader(filePath); + BufferedReader bufferedReader = new BufferedReader(fileReader); + + String line; + while ((line = bufferedReader.readLine()) != null) { + System.out.println(line); + } + + // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象 + // 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法 + // 因此只要一个 close() 调用即可 + bufferedReader.close(); +} +``` # 五、对象操作 +## 序列化 + 序列化就是将一个对象转换成字节序列,方便存储和传输。 -序列化:ObjectOutputStream.writeObject() +- 序列化:ObjectOutputStream.writeObject() +- 反序列化:ObjectInputStream.readObject() -反序列化:ObjectInputStream.readObject() +不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。 -序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现。 +## Serializable + +序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。 + +```java +public static void main(String[] args) throws IOException, ClassNotFoundException { + + A a1 = new A(123, "abc"); + String objectFile = "file/a1"; + + ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile)); + objectOutputStream.writeObject(a1); + objectOutputStream.close(); + + ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile)); + A a2 = (A) objectInputStream.readObject(); + objectInputStream.close(); + System.out.println(a2); +} + +private static class A implements Serializable { + + private int x; + private String y; + + A(int x, String y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return "x = " + x + " " + "y = " + y; + } +} +``` + +## transient transient 关键字可以使一些属性不会被序列化。 -**ArrayList 序列化和反序列化的实现** :ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 +ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 ```java private transient Object[] elementData; @@ -107,37 +228,45 @@ private transient Object[] elementData; Java 中的网络支持: -1. InetAddress:用于表示网络上的硬件资源,即 IP 地址; -2. URL:统一资源定位符,通过 URL 可以直接读取或者写入网络上的数据; -3. Sockets:使用 TCP 协议实现网络通信; -4. Datagram:使用 UDP 协议实现网络通信。 +- InetAddress:用于表示网络上的硬件资源,即 IP 地址; +- URL:统一资源定位符; +- Sockets:使用 TCP 协议实现网络通信; +- Datagram:使用 UDP 协议实现网络通信。 ## InetAddress -没有公有构造函数,只能通过静态方法来创建实例。 +没有公有的构造函数,只能通过静态方法来创建实例。 ```java InetAddress.getByName(String host); -InetAddress.getByAddress(byte[] addr); +InetAddress.getByAddress(byte[] address); ``` ## URL -可以直接从 URL 中读取字节流数据 +可以直接从 URL 中读取字节流数据。 ```java -URL url = new URL("http://www.baidu.com"); -InputStream is = url.openStream(); // 字节流 -InputStreamReader isr = new InputStreamReader(is, "utf-8"); // 字符流 -BufferedReader br = new BufferedReader(isr); -String line = br.readLine(); -while (line != null) { - System.out.println(line); - line = br.readLine(); +public static void main(String[] args) throws IOException { + + URL url = new URL("http://www.baidu.com"); + + /* 字节流 */ + InputStream is = url.openStream(); + + /* 字符流 */ + InputStreamReader isr = new InputStreamReader(is, "utf-8"); + + /* 提供缓存功能 */ + BufferedReader br = new BufferedReader(isr); + + String line; + while ((line = br.readLine()) != null) { + System.out.println(line); + } + + br.close(); } -br.close(); -isr.close(); -is.close(); ``` ## Sockets @@ -150,22 +279,18 @@ is.close(); ## Datagram -- DatagramPacket:数据包类 - DatagramSocket:通信类 +- DatagramPacket:数据包类 # 七、NIO -- [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。 +新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。 ## 流与块 I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 -面向流的 I/O 一次处理一个字节数据,一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。 +面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。 面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。 @@ -177,7 +302,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重 通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。 -通道与流的不同之处在于,流只能在一个方向上移动,(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。 +通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。 通道包括以下类型: @@ -214,7 +339,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重

-② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 移动设置为 5,limit 保持不变。 +② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。

@@ -235,47 +360,56 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重 以下展示了使用 NIO 快速复制文件的实例: ```java -public class FastCopyFile { - public static void main(String args[]) throws Exception { +public static void fastCopy(String src, String dist) throws IOException { - String inFile = "data/abc.txt"; - String outFile = "data/abc-copy.txt"; + /* 获得源文件的输入字节流 */ + FileInputStream fin = new FileInputStream(src); - // 获得源文件的输入字节流 - FileInputStream fin = new FileInputStream(inFile); - // 获取输入字节流的文件通道 - FileChannel fcin = fin.getChannel(); + /* 获取输入字节流的文件通道 */ + FileChannel fcin = fin.getChannel(); - // 获取目标文件的输出字节流 - FileOutputStream fout = new FileOutputStream(outFile); - // 获取输出字节流的通道 - FileChannel fcout = fout.getChannel(); + /* 获取目标文件的输出字节流 */ + FileOutputStream fout = new FileOutputStream(dist); - // 为缓冲区分配 1024 个字节 - ByteBuffer buffer = ByteBuffer.allocateDirect(1024); + /* 获取输出字节流的文件通道 */ + FileChannel fcout = fout.getChannel(); - while (true) { - // 从输入通道中读取数据到缓冲区中 - int r = fcin.read(buffer); - // read() 返回 -1 表示 EOF - if (r == -1) - break; - // 切换读写 - buffer.flip(); - // 把缓冲区的内容写入输出文件中 - fcout.write(buffer); - // 清空缓冲区 - buffer.clear(); + /* 为缓冲区分配 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(); } } ``` ## 选择器 -一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去检查多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。 +NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。 -因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件具有更好的性能。 +NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。 + +通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。 + +因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。 + +应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。

@@ -293,7 +427,7 @@ ssChannel.configureBlocking(false); ssChannel.register(selector, SelectionKey.OP_ACCEPT); ``` -通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它时间,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。 +通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。 在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类: @@ -323,7 +457,7 @@ int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; int num = selector.select(); ``` -使用 select() 来监听事件到达,它会一直阻塞直到有至少一个事件到达。 +使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。 ### 4. 获取到达的事件 @@ -368,6 +502,7 @@ while (true) { public class NIOServer { public static void main(String[] args) throws IOException { + Selector selector = Selector.open(); ServerSocketChannel ssChannel = ServerSocketChannel.open(); @@ -379,41 +514,56 @@ public class NIOServer { serverSocket.bind(address); while (true) { + selector.select(); Set keys = selector.selectedKeys(); Iterator 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(); + StringBuilder data = new StringBuilder(); + while (true) { + buffer.clear(); int n = sChannel.read(buffer); - if (n == -1) + if (n == -1) { break; + } buffer.flip(); int limit = buffer.limit(); char[] dst = new char[limit]; - for (int i = 0; i < limit; i++) + for (int i = 0; i < limit; i++) { dst[i] = (char) buffer.get(i); + } data.append(dst); buffer.clear(); } @@ -439,13 +589,9 @@ public class NIOClient { 内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。 -只有文件中实际读取或者写入的部分才会映射到内存中。 +向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。 -现代操作系统一般会根据需要将文件的部分映射为内存的部分,从而实现文件系统。Java 内存映射机制只不过是在底层操作系统中可以采用这种机制时,提供了对该机制的访问。 - -向内存映射文件写入可能是危险的,仅只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。 - -下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,您可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。 +下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。 ```java MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024); @@ -455,14 +601,18 @@ MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024); NIO 与普通 I/O 的区别主要有以下两点: -- NIO 是非阻塞的。应当注意,FileChannel 不能切换到非阻塞模式,套接字 Channel 可以。 +- NIO 是非阻塞的; - NIO 面向块,I/O 面向流。 # 八、参考资料 - Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002. - [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) -- [深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) +- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html) +- [Java NIO 浅析](https://tech.meituan.com/nio.html) +- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) +- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.htm) +- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html) - [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499) - [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document) - [Socket Multicast](http://labojava.blogspot.com/2012/12/socket-multicast.html) diff --git a/notes/Java 基础.md b/notes/Java 基础.md index 6424600a..a2cf6532 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -1,31 +1,37 @@ -* [一、关键字](#一关键字) - * [final](#final) - * [static](#static) -* [二、Object 通用方法](#二object-通用方法) +* [一、数据类型](#一数据类型) + * [包装类型](#包装类型) + * [缓存池](#缓存池) +* [二、String](#二string) + * [概览](#概览) + * [不可变的好处](#不可变的好处) + * [String, StringBuffer and StringBuilder](#string,-stringbuffer-and-stringbuilder) + * [String Pool](#string-pool) + * [new String("abc")](#new-string"abc") +* [三、运算](#三运算) + * [参数传递](#参数传递) + * [float 与 double](#float-与-double) + * [隐式类型转换](#隐式类型转换) + * [switch](#switch) +* [四、继承](#四继承) + * [访问权限](#访问权限) + * [抽象类与接口](#抽象类与接口) + * [super](#super) + * [重写与重载](#重写与重载) +* [五、Object 通用方法](#五object-通用方法) * [概览](#概览) * [equals()](#equals) * [hashCode()](#hashcode) * [toString()](#tostring) * [clone()](#clone) -* [四、继承](#四继承) - * [访问权限](#访问权限) - * [抽象类与接口](#抽象类与接口) - * [super](#super) - * [覆盖与重载](#覆盖与重载) -* [五、String](#五string) - * [String, StringBuffer and StringBuilder](#string,-stringbuffer-and-stringbuilder) - * [String 不可变的原因](#string-不可变的原因) - * [String.intern()](#stringintern) -* [六、基本类型与运算](#六基本类型与运算) - * [包装类型](#包装类型) - * [switch](#switch) +* [六、关键字](#六关键字) + * [final](#final) + * [static](#static) * [七、反射](#七反射) * [八、异常](#八异常) * [九、泛型](#九泛型) * [十、注解](#十注解) * [十一、特性](#十一特性) - * [面向对象三大特性](#面向对象三大特性) * [Java 各版本的新特性](#java-各版本的新特性) * [Java 与 C++ 的区别](#java-与-c-的区别) * [JRE or JDK](#jre-or-jdk) @@ -33,748 +39,7 @@ -# 一、关键字 - -## final - -**1. 数据** - -声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。 - -- 对于基本类型,final 使数值不变; -- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 - -```java -final int x = 1; -// x = 2; // cannot assign value to final variable 'x' -final A y = new A(); -y.a = 1; -``` - -**2. 方法** - -声明方法不能被子类覆盖。 - -private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是在子类中定义了一个新的方法。 - -**3. 类** - -声明类不允许被继承。 - -## static - -**1. 静态变量** - -静态变量在内存中只存在一份,只在类初始化时赋值一次。 - -- 静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问它; -- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 - -```java -public class A { - private int x; // 实例变量 - public static int y; // 静态变量 -} -``` - -**2. 静态方法** - -静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。 - -**3. 静态语句块** - -静态语句块在类初始化时运行一次。 - -**4. 静态内部类** - -内部类的一种,静态内部类不依赖外部类,且不能访问外部类的非静态的变量和方法。 - -**5. 静态导包** - -```java -import static com.xxx.ClassName.* -``` - -在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。 - -**6. 变量赋值顺序** - -静态变量的赋值和静态语句块的运行优先于实例变量的赋值和普通语句块的运行,静态变量的赋值和静态语句块的运行哪个先执行取决于它们在代码中的顺序。 - -```java -public static String staticField = "静态变量"; -``` - -```java -static { - System.out.println("静态语句块"); -} -``` - -```java -public String field = "实例变量"; -``` - -```java -{ - System.out.println("普通语句块"); -} -``` - -最后才运行构造函数 - -```java -public InitialOrderTest() { - System.out.println("构造函数"); -} -``` - -存在继承的情况下,初始化顺序为: - -- 父类(静态变量、静态语句块) -- 子类(静态变量、静态语句块) -- 父类(实例变量、普通语句块) -- 父类(构造函数) -- 子类(实例变量、普通语句块) -- 子类(构造函数) - -# 二、Object 通用方法 - -## 概览 - -```java -public final native Class getClass() - -public native int hashCode() - -public boolean equals(Object obj) - -protected native Object clone() throws CloneNotSupportedException - -public String toString() - -public final native void notify() - -public final native void notifyAll() - -public final native void wait(long timeout) throws InterruptedException - -public final void wait(long timeout, int nanos) throws InterruptedException - -public final void wait() throws InterruptedException - -protected void finalize() throws Throwable {} -``` - -## equals() - -**1. equals() 与 == 的区别** - -- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。 -- 对于引用类型,== 判断两个实例是否引用同一个对象,而 equals() 判断引用的对象是否等价。 - -```java -Integer x = new Integer(1); -Integer y = new Integer(1); -System.out.println(x.equals(y)); // true -System.out.println(x == y); // false -``` - -**2. 等价关系** - -(一)自反性 - -```java -x.equals(x); // true -``` - -(二)对称性 - -```java -x.equals(y) == y.equals(x); // true -``` - -(三)传递性 - -```java -if (x.equals(y) && y.equals(z)) - x.equals(z); // true; -``` - -(四)一致性 - -多次调用 equals() 方法结果不变 - -```java -x.equals(y) == x.equals(y); // true -``` - -(五)与 null 的比较 - -对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false - -```java -x.euqals(null); // false; -``` - -**3. 实现** - -- 检查是否为同一个对象的引用,如果是直接返回 true; -- 检查是否是同一个类型,如果不是,直接返回 false; -- 将 Object 实例进行转型; -- 判断每个关键域是否相等。 - -```java -public class EqualExample { - private int x; - private int y; - private int z; - - public EqualExample(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - EqualExample that = (EqualExample) o; - - if (x != that.x) return false; - if (y != that.y) return false; - return z == that.z; - } -} -``` - -## hashCode() - -hasCode() 返回散列值,而 equals() 是用来判断两个实例是否等价。等价的两个实例散列值一定要相同,但是散列值相同的两个实例不一定等价。 - -在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个实例散列值也相等。 - -下面的代码中,新建了两个等价的实例,并将它们添加到 HashSet 中。我们希望将这两个实例当成一样的,只在集合中添加一个实例,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个实例的散列值是不同的,最终导致集合添加了两个等价的实例。 - -```java -EqualExample e1 = new EqualExample(1, 1, 1); -EqualExample e2 = new EqualExample(1, 1, 1); -System.out.println(e1.equals(e2)); // true -HashSet set = new HashSet<>(); -set.add(e1); -set.add(e2); -System.out.println(set.size()); // 2 -``` - -理想的散列函数应当具有均匀性,即不相等的实例应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。 - -一个数与 31 相乘可以转换成移位和减法:`31\*x == (x<<5)-x`,编译器会自动进行这个优化。 - -```java -@Override -public int hashCode() { - int result = 17; - result = 31 * result + x; - result = 31 * result + y; - result = 31 * result + z; - return result; -} -``` - -## toString() - -默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。 - -```java -public class ToStringExample { - private int number; - - public ToStringExample(int number) { - this.number = number; - } -} -``` - -```java -ToStringExample example = new ToStringExample(123); -System.out.println(example.toString()); -``` - -```html -ToStringExample@4554617c -``` - -## clone() - -**1. cloneable** - -clone() 是 Object 的受保护方法,这意味着,如果一个类不显式去覆盖 clone() 就没有这个方法。 - -```java -public class CloneExample { - private int a; - private int b; -} -``` - -```java -CloneExample e1 = new CloneExample(); -// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object' -``` - -接下来覆盖 Object 的 clone() 得到以下实现: - -```java -public class CloneExample { - private int a; - private int b; - - @Override - protected CloneExample clone() throws CloneNotSupportedException { - return (CloneExample)super.clone(); - } -} -``` - -```java -CloneExample e1 = new CloneExample(); -try { - CloneExample e2 = e1.clone(); -} catch (CloneNotSupportedException e) { - e.printStackTrace(); -} -``` - -```html -java.lang.CloneNotSupportedException: CloneTest -``` - -以上抛出了 CloneNotSupportedException,这是因为 CloneTest 没有实现 Cloneable 接口。 - -```java -public class CloneExample implements Cloneable { - private int a; - private int b; - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } -} -``` - -应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。 - -**2. 深拷贝与浅拷贝** - -- 浅拷贝:拷贝实例和原始实例的引用类型引用同一个对象; -- 深拷贝:拷贝实例和原始实例的引用类型引用不同对象。 - -```java -public class ShallowCloneExample implements Cloneable { - private int[] arr; - - public ShallowCloneExample() { - arr = new int[10]; - for (int i = 0; i < arr.length; i++) { - arr[i] = i; - } - } - - public void set(int index, int value) { - arr[index] = value; - } - - public int get(int index) { - return arr[index]; - } - - @Override - protected ShallowCloneExample clone() throws CloneNotSupportedException { - return (ShallowCloneExample) super.clone(); - } -} -``` - -```java -ShallowCloneExample e1 = new ShallowCloneExample(); -ShallowCloneExample e2 = null; -try { - e2 = e1.clone(); -} catch (CloneNotSupportedException e) { - e.printStackTrace(); -} -e1.set(2, 222); -System.out.println(e2.get(2)); // 222 -``` - -```java -public class DeepCloneExample implements Cloneable { - private int[] arr; - - public DeepCloneExample() { - arr = new int[10]; - for (int i = 0; i < arr.length; i++) { - arr[i] = i; - } - } - - public void set(int index, int value) { - arr[index] = value; - } - - public int get(int index) { - return arr[index]; - } - - @Override - protected DeepCloneExample clone() throws CloneNotSupportedException { - DeepCloneExample result = (DeepCloneExample) super.clone(); - result.arr = new int[arr.length]; - for (int i = 0; i < arr.length; i++) { - result.arr[i] = arr[i]; - } - return result; - } -} -``` - -```java -DeepCloneExample e1 = new DeepCloneExample(); -DeepCloneExample e2 = null; -try { - e2 = e1.clone(); -} catch (CloneNotSupportedException e) { - e.printStackTrace(); -} -e1.set(2, 222); -System.out.println(e2.get(2)); // 2 -``` - -使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。 - -```java -public class CloneConstructorExample { - private int[] arr; - - public CloneConstructorExample() { - arr = new int[10]; - for (int i = 0; i < arr.length; i++) { - arr[i] = i; - } - } - - public CloneConstructorExample(CloneConstructorExample original) { - arr = new int[original.arr.length]; - for (int i = 0; i < original.arr.length; i++) { - arr[i] = original.arr[i]; - } - } - - public void set(int index, int value) { - arr[index] = value; - } - - public int get(int index) { - return arr[index]; - } -} -``` - -```java -CloneConstructorExample e1 = new CloneConstructorExample(); -CloneConstructorExample e2 = new CloneConstructorExample(e1); -e1.set(2, 222); -System.out.println(e2.get(2)); // 2 -``` - -# 四、继承 - -## 访问权限 - -Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。 - -可以对类或类中的成员(字段以及方法)加上访问修饰符。 - -- 成员可见表示其它类可以用这个类的实例访问到该成员; -- 类可见表示其它类可以用这个类创建对象。 - -protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。 - -设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。 - -如果子类的方法覆盖了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。 - -字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。可以使用公有的 getter 和 setter 方法来替换公有字段。 - -```java -public class AccessExample { - public int x; -} -``` - -```java -public class AccessExample { - private int x; - - public int getX() { - return x; - } - - public void setX(int x) { - this.x = x; - } -} -``` - -但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。 - -```java -public class AccessWithInnerClassExample { - private class InnerClass { - int x; - } - - private InnerClass innerClass; - - public AccessWithInnerClassExample() { - innerClass = new InnerClass(); - } - - public int getValue() { - return innerClass.x; // 直接访问 - } -} -``` - -## 抽象类与接口 - -**1. 抽象类** - -抽象类和抽象方法都使用 abstract 进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。 - -抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。 - -```java -public abstract class AbstractClassExample { - - protected int x; - private int y; - - public abstract void func1(); - - public void func2() { - System.out.println("func2"); - } -} -``` - -```java -public class AbstractExtendClassExample extends AbstractClassExample{ - @Override - public void func1() { - System.out.println("func1"); - } -} -``` - -```java -// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated -AbstractClassExample ac2 = new AbstractExtendClassExample(); -ac2.func1(); -``` - -**2. 接口** - -接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。 - -从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。 - -接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。 - -接口的字段默认都是 static 和 final 的。 - -```java -public interface InterfaceExample { - void func1(); - - default void func2(){ - System.out.println("func2"); - } - - int x = 123; - // int y; // Variable 'y' might not have been initialized - public int z = 0; // Modifier 'public' is redundant for interface fields - // private int k = 0; // Modifier 'private' not allowed here - // protected int l = 0; // Modifier 'protected' not allowed here - // private void fun3(); // Modifier 'private' not allowed here -} -``` - -```java -public class InterfaceImplementExample implements InterfaceExample { - @Override - public void func1() { - System.out.println("func1"); - } -} -``` - -```java -// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated -InterfaceExample ie2 = new InterfaceImplementExample(); -ie2.func1(); -System.out.println(InterfaceExample.x); -``` - -**3. 比较** - -- 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。 -- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。 -- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。 -- 接口的方法只能是 public 的,而抽象类的方法可以由多种访问权限。 - -**4. 使用选择** - -使用抽象类: - -- 需要在几个相关的类中共享代码。 -- 需要能控制继承来的方法和域的访问权限,而不是都为 public。 -- 需要继承非静态(non-static)和非常量(non-final)字段。 - -使用接口: - -- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法; -- 需要使用多重继承。 - -在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 - -> [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/)
[When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface) - -## super - -- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而完成一些初始化的工作。 -- 访问父类的成员:如果子类覆盖了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。 - -```java -public class SuperExample { - protected int x; - protected int y; - - public SuperExample(int x, int y) { - this.x = x; - this.y = y; - } - - public void func() { - System.out.println("SuperExample.func()"); - } -} -``` - -```java -public class SuperExtendExample extends SuperExample { - private int z; - - public SuperExtendExample(int x, int y, int z) { - super(x, y); - this.z = z; - } - - @Override - public void func() { - super.func(); - System.out.println("SuperExtendExample.func()"); - } -} -``` - -```java -SuperExample e = new SuperExtendExample(1, 2, 3); -e.func(); -``` - -```html -SuperExample.func() -SuperExtendExample.func() -``` - -> [Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html) - -## 覆盖与重载 - -- 覆盖(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法; - -- 重载(Overload)存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。 - -# 五、String - -## String, StringBuffer and StringBuilder - -**1. 是否可变** - -- String 不可变 -- StringBuffer 和 StringBuilder 可变 - -**2. 是否线程安全** - -- String 不可变,因此是线程安全的 -- StringBuilder 不是线程安全的 -- StringBuffer 是线程安全的,内部使用 synchronized 来同步 - -> [String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) - -## String 不可变的原因 - -**1. 可以缓存 hash 值** - -因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。 - -**2. String Pool 的需要** - -如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。 - -

- -**3. 安全性** - -String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。 - -**4. 线程安全** - -String 不可变性天生具备线程安全,可以在多个线程中安全地使用。 - -> [Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/) - -## String.intern() - -使用 String.intern() 可以保证相同内容的字符串实例引用相同的内存对象。 - -下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用,这个方法首先把 s1 引用的对象放到 String Poll(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。 - -```java -String s1 = new String("aaa"); -String s2 = new String("aaa"); -System.out.println(s1 == s2); // false -String s3 = s1.intern(); -System.out.println(s1.intern() == s3); // true -``` - -如果是采用 "bbb" 这种使用双引号的形式创建字符串实例,会自动地将新建的对象放入 String Poll 中。 - -```java -String s4 = "bbb"; -String s5 = "bbb"; -System.out.println(s4 == s5); // true -``` - -在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被放在堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 - -> [What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
[深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) - -# 六、基本类型与运算 +# 一、数据类型 ## 包装类型 @@ -796,7 +61,12 @@ Integer x = 2; // 装箱 int y = x; // 拆箱 ``` -new Integer(123) 与 Integer.valueOf(123) 的区别在于,new Integer(123) 每次都会新建一个对象,而 Integer.valueOf(123) 可能会使用缓存对象,因此多次使用 Integer.valueOf(123) 会取得同一个对象的引用。 +## 缓存池 + +new Integer(123) 与 Integer.valueOf(123) 的区别在于: + +- new Integer(123) 每次都会新建一个对象; +- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。 ```java Integer x = new Integer(123); @@ -807,15 +77,7 @@ Integer k = Integer.valueOf(123); System.out.println(z == k); // true ``` -编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。 - -```java -Integer m = 123; -Integer n = 123; -System.out.println(m == n); // true -``` - -valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接使用缓存池的内容。 +valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。 ```java public static Integer valueOf(int i) { @@ -859,7 +121,15 @@ static { } ``` -Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些: +编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。 + +```java +Integer m = 123; +Integer n = 123; +System.out.println(m == n); // true +``` + +基本类型对应的缓冲池如下: - boolean values true and false - all byte values @@ -867,11 +137,255 @@ Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些 - int values between -128 and 127 - char in the range \u0000 to \u007F -因此在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。 +在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。 -> [Differences between new Integer(123), Integer.valueOf(123) and just 123 +[StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123 ](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123) +# 二、String + +## 概览 + +String 被声明为 final,因此它不可被继承。 + +内部使用 char 数组存储数据,该数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。 + +```java +public final class String + implements java.io.Serializable, Comparable, CharSequence { + /** The value is used for character storage. */ + private final char value[]; +``` + +## 不可变的好处 + +**1. 可以缓存 hash 值** + +因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。 + +**2. String Pool 的需要** + +如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。 + +

+ +**3. 安全性** + +String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。 + +**4. 线程安全** + +String 不可变性天生具备线程安全,可以在多个线程中安全地使用。 + +[Program Creek : Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/) + +## String, StringBuffer and StringBuilder + +**1. 可变性** + +- String 不可变 +- StringBuffer 和 StringBuilder 可变 + +**2. 线程安全** + +- String 不可变,因此是线程安全的 +- StringBuilder 不是线程安全的 +- StringBuffer 是线程安全的,内部使用 synchronized 进行同步 + +[StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) + +## String Pool + +字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。 + +当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。 + +下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。 + +```java +String s1 = new String("aaa"); +String s2 = new String("aaa"); +System.out.println(s1 == s2); // false +String s3 = s1.intern(); +String s4 = s1.intern(); +System.out.println(s3 == s4); // true +``` + +如果是采用 "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。 + +```java +String s5 = "bbb"; +String s6 = "bbb"; +System.out.println(s5 == s6); // true +``` + +在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 + +- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning) +- [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) + +## new String("abc") + +使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。 + +- "abc" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量; +- 而使用 new 的方式会在堆中创建一个字符串对象。 + +创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。 + +```java +public class NewStringTest { + public static void main(String[] args) { + String s = new String("abc"); + } +} +``` + +使用 javap -verbose 进行反编译,得到以下内容: + +```java +// ... +Constant pool: +// ... + #2 = Class #18 // java/lang/String + #3 = String #19 // abc +// ... + #18 = Utf8 java/lang/String + #19 = Utf8 abc +// ... + + public static void main(java.lang.String[]); + descriptor: ([Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=2, args_size=1 + 0: new #2 // class java/lang/String + 3: dup + 4: ldc #3 // String abc + 6: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V + 9: astore_1 +// ... +``` + +在 Constant Pool 中,#19 存储这字符串字面量 "abc",#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。 + +以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。 + +```java +public String(String original) { + this.value = original.value; + this.hash = original.hash; +} +``` + +# 三、运算 + +## 参数传递 + +Java 的参数是以值传递的形式传入方法中,而不是引用传递。 + +以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响。 + +```java +public class Dog { + + String name; + + Dog(String name) { + this.name = name; + } + + String getName() { + return this.name; + } + + void setName(String name) { + this.name = name; + } + + String getObjectAddress() { + return super.toString(); + } +} +``` + +```java +public class PassByValueExample { + public static void main(String[] args) { + Dog dog = new Dog("A"); + System.out.println(dog.getObjectAddress()); // Dog@4554617c + func(dog); + System.out.println(dog.getObjectAddress()); // Dog@4554617c + System.out.println(dog.getName()); // A + } + + private static void func(Dog dog) { + System.out.println(dog.getObjectAddress()); // Dog@4554617c + dog = new Dog("B"); + System.out.println(dog.getObjectAddress()); // Dog@74a14482 + System.out.println(dog.getName()); // B + } +} +``` + +但是如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。 + +```java +class PassByValueExample { + public static void main(String[] args) { + Dog dog = new Dog("A"); + func(dog); + System.out.println(dog.getName()); // B + } + + private static void func(Dog dog) { + dog.setName("B"); + } +} +``` + +[StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value) + +## float 与 double + +Java 不能隐式执行向下转型,因为这会使得精度降低。 + +1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。 + +```java +// float f = 1.1; +``` + +1.1f 字面量才是 float 类型。 + +```java +float f = 1.1f; +``` + +## 隐式类型转换 + +因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。 + +```java +short s1 = 1; +// s1 = s1 + 1; +``` + +但是使用 += 或者 ++ 运算符可以执行隐式类型转换。 + +```java +s1 += 1; +// s1++; +``` + +上面的语句相当于将 s1 + 1 的计算结果进行了向下转型: + +```java +s1 = (short) (s1 + 1); +``` + +[StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?](https://stackoverflow.com/questions/8710619/why-dont-javas-compound-assignment-operators-require-casting) + ## switch 从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。 @@ -888,7 +402,7 @@ switch (s) { } ``` -switch 不支持 long,是因为 swicth 的设计初衷是为那些只需要对少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。 +switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。 ```java // long x = 111; @@ -902,23 +416,798 @@ switch 不支持 long,是因为 swicth 的设计初衷是为那些只需要对 // } ``` -> [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) +[StackOverflow : 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) + +# 四、继承 + +## 访问权限 + +Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。 + +可以对类或类中的成员(字段以及方法)加上访问修饰符。 + +- 类可见表示其它类可以用这个类创建实例对象。 +- 成员可见表示其它类可以用这个类的实例对象访问到该成员; + +protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。 + +设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。 + +如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。 + +字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 共有字段,如果在某个时刻,我们想要使用 int 去存储 id 字段,那么就需要去修改所有的客户端代码。 + +```java +public class AccessExample { + public String id; +} +``` + +可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。 + + +```java +public class AccessExample { + + private int id; + + public String getId() { + return id + ""; + } + + public void setId(String id) { + this.id = Integer.valueOf(id); + } +} +``` + +但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。 + +```java +public class AccessWithInnerClassExample { + + private class InnerClass { + int x; + } + + private InnerClass innerClass; + + public AccessWithInnerClassExample() { + innerClass = new InnerClass(); + } + + public int getValue() { + return innerClass.x; // 直接访问 + } +} +``` + +## 抽象类与接口 + +**1. 抽象类** + +抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。 + +抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。 + +```java +public abstract class AbstractClassExample { + + protected int x; + private int y; + + public abstract void func1(); + + public void func2() { + System.out.println("func2"); + } +} +``` + +```java +public class AbstractExtendClassExample extends AbstractClassExample { + @Override + public void func1() { + System.out.println("func1"); + } +} +``` + +```java +// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated +AbstractClassExample ac2 = new AbstractExtendClassExample(); +ac2.func1(); +``` + +**2. 接口** + +接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。 + +从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。 + +接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。 + +接口的字段默认都是 static 和 final 的。 + +```java +public interface InterfaceExample { + + void func1(); + + default void func2(){ + System.out.println("func2"); + } + + int x = 123; + // int y; // Variable 'y' might not have been initialized + public int z = 0; // Modifier 'public' is redundant for interface fields + // private int k = 0; // Modifier 'private' not allowed here + // protected int l = 0; // Modifier 'protected' not allowed here + // private void fun3(); // Modifier 'private' not allowed here +} +``` + +```java +public class InterfaceImplementExample implements InterfaceExample { + @Override + public void func1() { + System.out.println("func1"); + } +} +``` + +```java +// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated +InterfaceExample ie2 = new InterfaceImplementExample(); +ie2.func1(); +System.out.println(InterfaceExample.x); +``` + +**3. 比较** + +- 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。 +- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。 +- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。 +- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。 + +**4. 使用选择** + +使用接口: + +- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法; +- 需要使用多重继承。 + +使用抽象类: + +- 需要在几个相关的类中共享代码。 +- 需要能控制继承来的成员的访问权限,而不是都为 public。 +- 需要继承非静态和非常量字段。 + +在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 + +- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/) +- [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface) + +## super + +- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。 +- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。 + +```java +public class SuperExample { + + protected int x; + protected int y; + + public SuperExample(int x, int y) { + this.x = x; + this.y = y; + } + + public void func() { + System.out.println("SuperExample.func()"); + } +} +``` + +```java +public class SuperExtendExample extends SuperExample { + + private int z; + + public SuperExtendExample(int x, int y, int z) { + super(x, y); + this.z = z; + } + + @Override + public void func() { + super.func(); + System.out.println("SuperExtendExample.func()"); + } +} +``` + +```java +SuperExample e = new SuperExtendExample(1, 2, 3); +e.func(); +``` + +```html +SuperExample.func() +SuperExtendExample.func() +``` + +[Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html) + +## 重写与重载 + +**1. 重写(Override)** + +存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。 + +为了满足里式替换原则,重写有有以下两个限制: + +- 子类方法的访问权限必须大于等于父类方法; +- 子类方法的返回类型必须是父类方法返回类型或为其子类型。 + +使用 @Override 注解,可以让编译器帮忙检查是否满足上面的两个限制条件。 + +**2. 重载(Overload)** + +存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。 + +应该注意的是,返回值不同,其它都相同不算是重载。 + +# 五、Object 通用方法 + +## 概览 + +```java + +public native int hashCode() + +public boolean equals(Object obj) + +protected native Object clone() throws CloneNotSupportedException + +public String toString() + +public final native Class getClass() + +protected void finalize() throws Throwable {} + +public final native void notify() + +public final native void notifyAll() + +public final native void wait(long timeout) throws InterruptedException + +public final void wait(long timeout, int nanos) throws InterruptedException + +public final void wait() throws InterruptedException +``` + +## equals() + +**1. 等价关系** + +Ⅰ 自反性 + +```java +x.equals(x); // true +``` + +Ⅱ 对称性 + +```java +x.equals(y) == y.equals(x); // true +``` + +Ⅲ 传递性 + +```java +if (x.equals(y) && y.equals(z)) + x.equals(z); // true; +``` + +Ⅳ 一致性 + +多次调用 equals() 方法结果不变 + +```java +x.equals(y) == x.equals(y); // true +``` + +Ⅴ 与 null 的比较 + +对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false + +```java +x.equals(null); // false; +``` + +**2. 等价与相等** + +- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。 +- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。 + +```java +Integer x = new Integer(1); +Integer y = new Integer(1); +System.out.println(x.equals(y)); // true +System.out.println(x == y); // false +``` + +**3. 实现** + +- 检查是否为同一个对象的引用,如果是直接返回 true; +- 检查是否是同一个类型,如果不是,直接返回 false; +- 将 Object 对象进行转型; +- 判断每个关键域是否相等。 + +```java +public class EqualExample { + + private int x; + private int y; + private int z; + + public EqualExample(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EqualExample that = (EqualExample) o; + + if (x != that.x) return false; + if (y != that.y) return false; + return z == that.z; + } +} +``` + +## hashCode() + +hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。 + +在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。 + +下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。 + +```java +EqualExample e1 = new EqualExample(1, 1, 1); +EqualExample e2 = new EqualExample(1, 1, 1); +System.out.println(e1.equals(e2)); // true +HashSet set = new HashSet<>(); +set.add(e1); +set.add(e2); +System.out.println(set.size()); // 2 +``` + +理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。 + +一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。 + +```java +@Override +public int hashCode() { + int result = 17; + result = 31 * result + x; + result = 31 * result + y; + result = 31 * result + z; + return result; +} +``` + +## toString() + +默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。 + +```java +public class ToStringExample { + + private int number; + + public ToStringExample(int number) { + this.number = number; + } +} +``` + +```java +ToStringExample example = new ToStringExample(123); +System.out.println(example.toString()); +``` + +```html +ToStringExample@4554617c +``` + +## clone() + +**1. cloneable** + +clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。 + +```java +public class CloneExample { + private int a; + private int b; +} +``` + +```java +CloneExample e1 = new CloneExample(); +// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object' +``` + +重写 clone() 得到以下实现: + +```java +public class CloneExample { + private int a; + private int b; + + @Override + public CloneExample clone() throws CloneNotSupportedException { + return (CloneExample)super.clone(); + } +} +``` + +```java +CloneExample e1 = new CloneExample(); +try { + CloneExample e2 = e1.clone(); +} catch (CloneNotSupportedException e) { + e.printStackTrace(); +} +``` + +```html +java.lang.CloneNotSupportedException: CloneExample +``` + +以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。 + +应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。 + +```java +public class CloneExample implements Cloneable { + private int a; + private int b; + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } +} +``` + +**2. 浅拷贝** + +拷贝对象和原始对象的引用类型引用同一个对象。 + +```java +public class ShallowCloneExample implements Cloneable { + + private int[] arr; + + public ShallowCloneExample() { + arr = new int[10]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + + public void set(int index, int value) { + arr[index] = value; + } + + public int get(int index) { + return arr[index]; + } + + @Override + protected ShallowCloneExample clone() throws CloneNotSupportedException { + return (ShallowCloneExample) super.clone(); + } +} +``` + +```java +ShallowCloneExample e1 = new ShallowCloneExample(); +ShallowCloneExample e2 = null; +try { + e2 = e1.clone(); +} catch (CloneNotSupportedException e) { + e.printStackTrace(); +} +e1.set(2, 222); +System.out.println(e2.get(2)); // 222 +``` + +**3. 深拷贝** + +拷贝对象和原始对象的引用类型引用不同对象。 + +```java +public class DeepCloneExample implements Cloneable { + + private int[] arr; + + public DeepCloneExample() { + arr = new int[10]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + + public void set(int index, int value) { + arr[index] = value; + } + + public int get(int index) { + return arr[index]; + } + + @Override + protected DeepCloneExample clone() throws CloneNotSupportedException { + DeepCloneExample result = (DeepCloneExample) super.clone(); + result.arr = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + result.arr[i] = arr[i]; + } + return result; + } +} +``` + +```java +DeepCloneExample e1 = new DeepCloneExample(); +DeepCloneExample e2 = null; +try { + e2 = e1.clone(); +} catch (CloneNotSupportedException e) { + e.printStackTrace(); +} +e1.set(2, 222); +System.out.println(e2.get(2)); // 2 +``` + +**4. clone() 的替代方案** + +使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。 + +```java +public class CloneConstructorExample { + + private int[] arr; + + public CloneConstructorExample() { + arr = new int[10]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + + public CloneConstructorExample(CloneConstructorExample original) { + arr = new int[original.arr.length]; + for (int i = 0; i < original.arr.length; i++) { + arr[i] = original.arr[i]; + } + } + + public void set(int index, int value) { + arr[index] = value; + } + + public int get(int index) { + return arr[index]; + } +} +``` + +```java +CloneConstructorExample e1 = new CloneConstructorExample(); +CloneConstructorExample e2 = new CloneConstructorExample(e1); +e1.set(2, 222); +System.out.println(e2.get(2)); // 2 +``` + +# 六、关键字 + +## final + +**1. 数据** + +声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。 + +- 对于基本类型,final 使数值不变; +- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 + +```java +final int x = 1; +// x = 2; // cannot assign value to final variable 'x' +final A y = new A(); +y.a = 1; +``` + +**2. 方法** + +声明方法不能被子类重写。 + +private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。 + +**3. 类** + +声明类不允许被继承。 + +## static + +**1. 静态变量** + +- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。 +- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 + +```java +public class A { + + private int x; // 实例变量 + private static int y; // 静态变量 + + public static void main(String[] args) { + // int x = A.x; // Non-static field 'x' cannot be referenced from a static context + A a = new A(); + int x = a.x; + int y = A.y; + } +} +``` + +**2. 静态方法** + +静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。 + +```java +public abstract class A { + public static void func1(){ + } + // public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static' +} +``` + +只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。 + +```java +public class A { + + private static int x; + private int y; + + public static void func1(){ + int a = x; + // int b = y; // Non-static field 'y' cannot be referenced from a static context + // int b = this.y; // 'A.this' cannot be referenced from a static context + } +} +``` + +**3. 静态语句块** + +静态语句块在类初始化时运行一次。 + +```java +public class A { + static { + System.out.println("123"); + } + + public static void main(String[] args) { + A a1 = new A(); + A a2 = new A(); + } +} +``` + +```html +123 +``` + +**4. 静态内部类** + +非静态内部类依赖于外部类的实例,而静态内部类不需要。 + +```java +public class OuterClass { + + class InnerClass { + } + + static class StaticInnerClass { + } + + public static void main(String[] args) { + // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context + OuterClass outerClass = new OuterClass(); + InnerClass innerClass = outerClass.new InnerClass(); + StaticInnerClass staticInnerClass = new StaticInnerClass(); + } +} +``` + +静态内部类不能访问外部类的非静态的变量和方法。 + +**5. 静态导包** + +在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。 + +```java +import static com.xxx.ClassName.* +``` + +**6. 初始化顺序** + +静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。 + +```java +public static String staticField = "静态变量"; +``` + +```java +static { + System.out.println("静态语句块"); +} +``` + +```java +public String field = "实例变量"; +``` + +```java +{ + System.out.println("普通语句块"); +} +``` + +最后才是构造函数的初始化。 + +```java +public InitialOrderTest() { + System.out.println("构造函数"); +} +``` + +存在继承的情况下,初始化顺序为: + +- 父类(静态变量、静态语句块) +- 子类(静态变量、静态语句块) +- 父类(实例变量、普通语句块) +- 父类(构造函数) +- 子类(实例变量、普通语句块) +- 子类(构造函数) + # 七、反射 每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 -类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象。 +类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 `Class.forName("com.mysql.jdbc.Driver")` 这种方式来控制类的加载,该方法会返回一个 Class 对象。 反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。 Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类: -1. **Field** :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段; -2. **Method** :可以使用 invoke() 方法调用与 Method 对象关联的方法; -3. **Constructor** :可以用 Constructor 创建新的对象。 - -IDE 使用反射机制获取类的信息,在使用一个类的对象时,能够把类的字段、方法和构造函数等信息列出来供用户选择。 +- **Field** :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段; +- **Method** :可以使用 invoke() 方法调用与 Method 对象关联的方法; +- **Constructor** :可以用 Constructor 创建新的对象。 **Advantages of Using Reflection:** @@ -934,18 +1223,21 @@ Reflection is powerful, but should not be used indiscriminately. If it is possib - **Security Restrictions** : Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet. - **Exposure of Internals** :Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform. -> [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html)
[深入解析 Java 反射(1)- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/) + +- [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html) +- [深入解析 Java 反射(1)- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/) # 八、异常 Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error** 和 **Exception**。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种: -1. **受检异常** :需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复; -2. **非受检异常** :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序奔溃并且无法恢复。 +- **受检异常** :需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复; +- **非受检异常** :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序崩溃并且无法恢复。

-> [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception)
[Java 异常的面试问题及答案 -Part 1](http://www.importnew.com/7383.html) +- [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception) +- [Java 异常的面试问题及答案 -Part 1](http://www.importnew.com/7383.html) # 九、泛型 @@ -958,20 +1250,17 @@ public class Box { } ``` -> [Java 泛型详解](http://www.importnew.com/24029.html)
[10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693) +- [Java 泛型详解](http://www.importnew.com/24029.html) +- [10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693) # 十、注解 Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。 -> [注解 Annotation 实现原理与自定义注解例子](https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html) +[注解 Annotation 实现原理与自定义注解例子](https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html) # 十一、特性 -## 面向对象三大特性 - -> [封装、继承、多态](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#%E5%B0%81%E8%A3%85%E7%BB%A7%E6%89%BF%E5%A4%9A%E6%80%81) - ## Java 各版本的新特性 **New highlights in Java SE 8** @@ -997,27 +1286,21 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译 7. Binary Literals, Underscore in literals 8. Diamond Syntax -> [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17)
[Java 8 特性 ](http://www.importnew.com/19345.html) +- [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17) +- [Java 8 特性](http://www.importnew.com/19345.html) ## Java 与 C++ 的区别 -Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。 +- Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。 +- Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。 +- Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。 +- Java 支持自动垃圾回收,而 C++ 需要手动回收。 +- Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。 +- Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。 +- Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。 +- Java 不支持条件编译,C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。 -| Java | C++ | -| -- | -- | -| Java does not support pointers, templates, unions, operator overloading, structures etc. The Java language promoters initially said "No pointers!", but when many programmers questioned how you can work without pointers, the promoters began saying "Restricted pointers." Java supports what it calls "references". References act a lot like pointers in C++ languages but you cannot perform arithmetic on pointers in Java. References have types, and they're type-safe. These references cannot be interpreted as raw address and unsafe conversion is not allowed. | C++ supports structures, unions, templates, operator overloading, pointers and pointer arithmetic.| -| Java support automatic garbage collection. It does not support destructors as C++ does. | C++ support destructors, which is automatically invoked when the object is destroyed. | -| Java does not support conditional compilation and inclusion. | Conditional inclusion (#ifdef #ifndef type) is one of the main features of C++. | -| Java has built in support for threads. In Java, there is a `Thread` class that you inherit to create a new thread and override the `run()` method. | C++ has no built in support for threads. C++ relies on non-standard third-party libraries for thread support. | -| Java does not support default arguments. There is no scope resolution operator (::) in Java. The method definitions must always occur within a class, so there is no need for scope resolution there either. | C++ supports default arguments. C++ has scope resolution operator (::) which is used to to define a method outside a class and to access a global variable within from the scope where a local variable also exists with the same name. | -| There is no _goto_ statement in Java. The keywords `const` and `goto` are reserved, even though they are not used. | C++ has _goto_ statement. However, it is not considered good practice to use of _goto_ statement. | -| Java doesn't provide multiple inheritance, at least not in the same sense that C++ does. | C++ does support multiple inheritance. The keyword `virtual` is used to resolve ambiguities during multiple inheritance if there is any. | -| Exception handling in Java is different because there are no destructors. Also, in Java, try/catch must be defined if the function declares that it may throw an exception. | While in C++, you may not include the try/catch even if the function throws an exception. | -| Java has method overloading, but no operator overloading. The `String` class does use the `+` and `+=` operators to concatenate strings and `String`expressions use automatic type conversion, but that's a special built-in case. | C++ supports both method overloading and operator overloading. | -| Java has built-in support for documentation comments (`/** ... */`); therefore, Java source files can contain their own documentation, which is read by a separate tool usually `javadoc` and reformatted into HTML. This helps keeping documentation maintained in easy way. | C++ does not support documentation comments. | -| Java is interpreted for the most part and hence platform independent. | C++ generates object code and the same code may not run on different platforms. | - -> [What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php) +[What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php) ## JRE or JDK diff --git a/notes/Java 容器.md b/notes/Java 容器.md index b34caa09..c039d6a8 100644 --- a/notes/Java 容器.md +++ b/notes/Java 容器.md @@ -8,62 +8,65 @@ * [三、源码分析](#三源码分析) * [ArrayList](#arraylist) * [Vector](#vector) + * [CopyOnWriteArrayList](#copyonwritearraylist) * [LinkedList](#linkedlist) * [HashMap](#hashmap) * [ConcurrentHashMap](#concurrenthashmap) * [LinkedHashMap](#linkedhashmap) - * [TreeMap](#treemap) + * [WeakHashMap](#weakhashmap) +* [附录](#附录) * [参考资料](#参考资料) # 一、概览 -容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。 +容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。 ## Collection -

+

### 1. Set -- HashSet:基于哈希实现,支持快速查找,但不支持有序性操作,例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的; +- TreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。 -- TreeSet:基于红黑树实现,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找时间复杂度为 O(1),TreeSet 则为 O(logN); +- HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。 -- LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序。 +- LinkedHashSet:具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。 ### 2. List -- ArrayList:基于动态数组实现,支持随机访问; +- ArrayList:基于动态数组实现,支持随机访问。 -- Vector:和 ArrayList 类似,但它是线程安全的; +- Vector:和 ArrayList 类似,但它是线程安全的。 - LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。 ### 3. Queue -- LinkedList:可以用它来支持双向队列; +- LinkedList:可以用它来实现双向队列。 - PriorityQueue:基于堆结构实现,可以用它来实现优先队列。 ## Map -

+

-- HashMap:基于哈希实现; +- TreeMap:基于红黑树实现。 + +- HashMap:基于哈希表实现。 - HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。 -- LinkedHashMap:使用链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。 +- LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。 -- TreeMap:基于红黑树实现。 # 二、容器中的设计模式 ## 迭代器模式 -

+

Collection 实现了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。 @@ -87,34 +90,30 @@ java.util.Arrays#asList() 可以把数组类型转换为 List 类型。 public static List asList(T... a) ``` -如果要将数组类型转换为 List 类型,应该注意的是 asList() 的参数为泛型的变长参数,因此不能使用基本类型数组作为参数,只能使用相应的包装类型数组。 +应该注意的是 asList() 的参数为泛型的变长参数,不能使用基本类型数组作为参数,只能使用相应的包装类型数组。 ```java Integer[] arr = {1, 2, 3}; List list = Arrays.asList(arr); ``` -也可以使用以下方式生成 List。 +也可以使用以下方式调用 asList(): ```java -List list = Arrays.asList(1,2,3); +List list = Arrays.asList(1, 2, 3); ``` # 三、源码分析 -建议先阅读 [算法-查找](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E6%9F%A5%E6%89%BE) 部分,对容器类源码的理解有很大帮助。 +如果没有特别说明,以下源码分析基于 JDK 1.8。 -至于 ConcurrentHashMap 的理解,需要有并发方面的知识,建议先阅读:[Java 并发](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E5%B9%B6%E5%8F%91.md) - -以下源码从 JDK 1.8 提取而来,下载地址:[JDK-Source-Code](https://github.com/CyC2018/JDK-Source-Code)。 +在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码。 ## ArrayList -[ArrayList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/ArrayList.java) - ### 1. 概览 -实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。 +实现了 RandomAccess 接口,因此支持随机访问。这是理所当然的,因为 ArrayList 是基于数组实现的。 ```java public class ArrayList extends AbstractList @@ -127,19 +126,11 @@ public class ArrayList extends AbstractList private static final int DEFAULT_CAPACITY = 10; ``` -### 2. 序列化 - -基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。 - -```java -transient Object[] elementData; // non-private to simplify nested class access -``` - -### 3. 扩容 +### 2. 扩容 添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。 -扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。 +扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。 ```java public boolean add(E e) { @@ -152,13 +143,11 @@ private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } - ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; - // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); @@ -177,27 +166,24 @@ private void grow(int minCapacity) { } ``` -### 4. 删除元素 +### 3. 删除元素 -需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,复制的代价很高。 +需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。 ```java public E remove(int index) { rangeCheck(index); - modCount++; E oldValue = elementData(index); - int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work - return oldValue; } ``` -### 5. Fail-Fast +### 4. Fail-Fast modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。 @@ -224,9 +210,72 @@ private void writeObject(java.io.ObjectOutputStream s) } ``` -## Vector +### 5. 序列化 -[Vector.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/Vector.java) +ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。 + +保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。 + +```java +transient Object[] elementData; // non-private to simplify nested class access +``` + +ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。 + +```java +private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + elementData = EMPTY_ELEMENTDATA; + + // Read in size, and any hidden stuff + s.defaultReadObject(); + + // Read in capacity + s.readInt(); // ignored + + if (size > 0) { + // be like clone(), allocate array based upon size not capacity + ensureCapacityInternal(size); + + Object[] a = elementData; + // Read in all elements in the proper order. + for (int i=0; i