map) {
+ for (String type : map.keySet()) {
+ String methodPrefixList = map.get(type);
+ if (methodPrefixList != null) {
+ METHOD_TYPE_MAP.put(type, Arrays.asList(methodPrefixList.split(",")));
+ }
+ }
+ }
+}
+```
+
+它的主要功能是,本来我们只配置一个数据源,因此`Spring`动态代理DAO接口时直接使用该数据源,现在我们有了读、写两个数据源,我们需要加入一些自己的逻辑来告诉调用哪个接口使用哪个数据源(读数据的接口使用`slave`,写数据的接口使用`master`。这个告诉`Spring`该使用哪个数据源的类就是`AbstractRoutingDataSource`,必须重写的方法`determineCurrentLookupKey`返回数据源的标识,结合`spring`配置文件(下段代码的5,6两行)
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+如果`determineCurrentLookupKey`返回`read`那么使用`slaveDataSource`,如果返回`write`就使用`masterDataSource`。
+
+##### DataSourceHandler
+
+```java
+package top.zhenganwen.mysqloptimize.dataSource;
+
+/**
+ * DataSourceHandler class
+ *
+ * 将数据源与线程绑定,需要时根据线程获取
+ *
+ * @author zhenganwen, blog:zhenganwen.top
+ * @date 2018/12/29
+ */
+public class DataSourceHandler {
+
+ /**
+ * 绑定的是read或write,表示使用读或写数据源
+ */
+ private static final ThreadLocal holder = new ThreadLocal();
+
+ public static void setDataSource(String dataSource) {
+ System.out.println(Thread.currentThread().getName()+"设置了数据源类型");
+ holder.set(dataSource);
+ }
+
+ public static String getDataSource() {
+ System.out.println(Thread.currentThread().getName()+"获取了数据源类型");
+ return holder.get();
+ }
+}
+```
+
+##### DataSourceAspect
+
+```java
+package top.zhenganwen.mysqloptimize.dataSource;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Set;
+
+import static top.zhenganwen.mysqloptimize.dataSource.RoutingDataSourceImpl.METHOD_TYPE_MAP;
+
+/**
+ * DataSourceAspect class
+ *
+ * 配置切面,根据方法前缀设置读、写数据源
+ * 项目启动时会加载该bean,并按照配置的切面(哪些切入点、如何增强)确定动态代理逻辑
+ * @author zhenganwen,blog:zhenganwen.top
+ * @date 2018/12/29
+ */
+@Component
+//声明这是一个切面,这样Spring才会做相应的配置,否则只会当做简单的bean注入
+@Aspect
+@EnableAspectJAutoProxy
+public class DataSourceAspect {
+
+ /**
+ * 配置切入点:DAO包下的所有类的所有方法
+ */
+ @Pointcut("execution(* top.zhenganwen.mysqloptimize.mapper.*.*(..))")
+ public void aspect() {
+
+ }
+
+ /**
+ * 配置前置增强,对象是aspect()方法上配置的切入点
+ */
+ @Before("aspect()")
+ public void before(JoinPoint point) {
+ String className = point.getTarget().getClass().getName();
+ String invokedMethod = point.getSignature().getName();
+ System.out.println("对 "+className+"$"+invokedMethod+" 做了前置增强,确定了要使用的数据源类型");
+
+ Set dataSourceType = METHOD_TYPE_MAP.keySet();
+ for (String type : dataSourceType) {
+ List prefixList = METHOD_TYPE_MAP.get(type);
+ for (String prefix : prefixList) {
+ if (invokedMethod.startsWith(prefix)) {
+ DataSourceHandler.setDataSource(type);
+ System.out.println("数据源为:"+type);
+ return;
+ }
+ }
+ }
+ }
+}
+```
+
+#### 测试读写分离
+
+> 如何测试读是从`slave`中读的呢?可以将写后复制到`slave`中的数据更改,再读该数据就知道是从`slave`中读了。==注意==,一但对`slave`做了写操作就要重新手动将`slave`与`master`同步一下,否则主从复制就会失效。
+
+```java
+package top.zhenganwen.mysqloptimize.dataSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import top.zhenganwen.mysqloptimize.entity.Article;
+import top.zhenganwen.mysqloptimize.mapper.ArticleMapper;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = "classpath:spring-mybatis.xml")
+public class RoutingDataSourceTest {
+
+ @Autowired
+ ArticleMapper articleMapper;
+
+ @Test
+ public void testRead() {
+ System.out.println(articleMapper.findAll());
+ }
+
+ @Test
+ public void testAdd() {
+ Article article = new Article(0, "我是新插入的文章", "测试是否能够写到master并且复制到slave中");
+ articleMapper.add(article);
+ }
+
+ @Test
+ public void testDelete() {
+ articleMapper.delete(2);
+ }
+}
+```
+
+## 负载均衡
+
+### 负载均衡算法
+
+- 轮询
+- 加权轮询:按照处理能力来加权
+- 负载分配:依据当前的空闲状态(但是测试每个节点的内存使用率、CPU利用率等,再做比较选出最闲的那个,效率太低)
+
+## 高可用
+
+在服务器架构时,为了保证服务器7x24不宕机在线状态,需要为每台单点服务器(由一台服务器提供服务的服务器,如写服务器、数据库中间件)提供冗余机。
+
+对于写服务器来说,需要提供一台同样的写-冗余服务器,当写服务器健康时(写-冗余通过心跳检测),写-冗余作为一个从机的角色复制写服务器的内容与其做一个同步;当写服务器宕机时,写-冗余服务器便顶上来作为写服务器继续提供服务。对外界来说这个处理过程是透明的,即外界仅通过一个IP访问服务。
+
+# 典型SQL
+
+## 线上DDL
+
+DDL(Database Definition Language)是指数据库表结构的定义(`create table`)和维护(`alter table`)的语言。在线上执行DDL,在低于`MySQL5.6`版本时会导致全表被独占锁定,此时表处于维护、不可操作状态,这会导致该期间对该表的所有访问无法响应。但是在`MySQL5.6`之后,支持`Online DDL`,大大缩短了锁定时间。
+
+优化技巧是采用的维护表结构的DDL(比如增加一列,或者增加一个索引),是==copy==策略。思路:创建一个满足新结构的新表,将旧表数据==逐条==导入(复制)到新表中,以保证==一次性锁定的内容少==(锁定的是正在导入的数据),同时旧表上可以执行其他任务。导入的过程中,将对旧表的所有操作以日志的形式记录下来,导入完毕后,将更新日志在新表上再执行一遍(确保一致性)。最后,新表替换旧表(在应用程序中完成,或者是数据库的rename,视图完成)。
+
+但随着MySQL的升级,这个问题几乎淡化了。
+
+## 数据库导入语句
+
+在恢复数据时,可能会导入大量的数据。此时为了快速导入,需要掌握一些技巧:
+
+1. 导入时==先禁用索引和约束==:
+
+```sql
+alter table table-name disable keys
+```
+
+待数据导入完成之后,再开启索引和约束,一次性创建索引
+
+```sql
+alter table table-name enable keys
+```
+
+2. 数据库如果使用的引擎是`Innodb`,那么它==默认会给每条写指令加上事务==(这也会消耗一定的时间),因此建议先手动开启事务,再执行一定量的批量导入,最后手动提交事务。
+3. 如果批量导入的SQL指令格式相同只是数据不同,那么你应该先`prepare`==预编译==一下,这样也能节省很多重复编译的时间。
+
+## limit offset,rows
+
+尽量保证不要出现大的`offset`,比如`limit 10000,10`相当于对已查询出来的行数弃掉前`10000`行后再取`10`行,完全可以加一些条件过滤一下(完成筛选),而不应该使用`limit`跳过已查询到的数据。这是一个==`offset`做无用功==的问题。对应实际工程中,要避免出现大页码的情况,尽量引导用户做条件过滤。
+
+## select * 要少用
+
+即尽量选择自己需要的字段`select`,但这个影响不是很大,因为网络传输多了几十上百字节也没多少延时,并且现在流行的ORM框架都是用的`select *`,只是我们在设计表的时候注意将大数据量的字段分离,比如商品详情可以单独抽离出一张商品详情表,这样在查看商品简略页面时的加载速度就不会有影响了。
+
+## order by rand()不要用
+
+它的逻辑就是随机排序(为每条数据生成一个随机数,然后根据随机数大小进行排序)。如`select * from student order by rand() limit 5`的执行效率就很低,因为它为表中的每条数据都生成随机数并进行排序,而我们只要前5条。
+
+解决思路:在应用程序中,将随机的主键生成好,去数据库中利用主键检索。
+
+## 单表和多表查询
+
+多表查询:`join`、子查询都是涉及到多表的查询。如果你使用`explain`分析执行计划你会发现多表查询也是一个表一个表的处理,最后合并结果。因此可以说单表查询将计算压力放在了应用程序上,而多表查询将计算压力放在了数据库上。
+
+现在有ORM框架帮我们解决了单表查询带来的对象映射问题(查询单表时,如果发现有外键自动再去查询关联表,是一个表一个表查的)。
+
+## count(*)
+
+在`MyISAM`存储引擎中,会自动记录表的行数,因此使用`count(*)`能够快速返回。而`Innodb`内部没有这样一个计数器,需要我们手动统计记录数量,解决思路就是单独使用一张表:
+
+| id | table | count |
+| ---- | ------- | ----- |
+| 1 | student | 100 |
+
+## limit 1
+
+如果可以确定仅仅检索一条,建议加上`limit 1`,其实ORM框架帮我们做到了这一点(查询单条的操作都会自动加上`limit 1`)。
+
+# 慢查询日志
+
+> 用于记录执行时间超过某个临界值的SQL日志,用于快速定位慢查询,为我们的优化做参考。
+
+## 开启慢查询日志
+
+配置项:`slow_query_log`
+
+可以使用`show variables like ‘slov_query_log’`查看是否开启,如果状态值为`OFF`,可以使用`set GLOBAL slow_query_log = on`来开启,它会在`datadir`下产生一个`xxx-slow.log`的文件。
+
+## 设置临界时间
+
+配置项:`long_query_time`
+
+查看:`show VARIABLES like 'long_query_time'`,单位秒
+
+设置:`set long_query_time=0.5`
+
+实操时应该从长时间设置到短的时间,即将最慢的SQL优化掉
+
+## 查看日志
+
+一旦SQL超过了我们设置的临界时间就会被记录到`xxx-slow.log`中
+
+# profile信息
+
+配置项:`profiling`
+
+## 开启profile
+
+`set profiling=on`
+
+开启后,所有的SQL执行的详细信息都会被自动记录下来
+
+```sql
+mysql> show variables like 'profiling';
++---------------+-------+
+| Variable_name | Value |
++---------------+-------+
+| profiling | OFF |
++---------------+-------+
+1 row in set, 1 warning (0.00 sec)
+
+mysql> set profiling=on;
+Query OK, 0 rows affected, 1 warning (0.00 sec)
+```
+
+## 查看profile信息
+
+`show profiles`
+
+```sql
+mysql> show variables like 'profiling';
++---------------+-------+
+| Variable_name | Value |
++---------------+-------+
+| profiling | ON |
++---------------+-------+
+1 row in set, 1 warning (0.00 sec)
+
+mysql> insert into article values (null,'test profile',':)');
+Query OK, 1 row affected (0.15 sec)
+
+mysql> show profiles;
++----------+------------+-------------------------------------------------------+
+| Query_ID | Duration | Query |
++----------+------------+-------------------------------------------------------+
+| 1 | 0.00086150 | show variables like 'profiling' |
+| 2 | 0.15027550 | insert into article values (null,'test profile',':)') |
++----------+------------+-------------------------------------------------------+
+```
+
+## 通过Query_ID查看某条SQL所有详细步骤的时间
+
+`show profile for query Query_ID`
+
+上面`show profiles`的结果中,每个SQL有一个`Query_ID`,可以通过它查看执行该SQL经过了哪些步骤,各消耗了多场时间
+
+```sql
+
+```
+
+# 典型的服务器配置
+
+> 以下的配置全都取决于实际的运行环境
+
+- `max_connections`,最大客户端连接数
+
+ ```sql
+ mysql> show variables like 'max_connections';
+ +-----------------+-------+
+ | Variable_name | Value |
+ +-----------------+-------+
+ | max_connections | 151 |
+ +-----------------+-------+
+ ```
+
+- `table_open_cache`,表文件句柄缓存(表数据是存储在磁盘上的,缓存磁盘文件的句柄方便打开文件读取数据)
+
+ ```sql
+ mysql> show variables like 'table_open_cache';
+ +------------------+-------+
+ | Variable_name | Value |
+ +------------------+-------+
+ | table_open_cache | 2000 |
+ +------------------+-------+
+ ```
+
+- `key_buffer_size`,索引缓存大小(将从磁盘上读取的索引缓存到内存,可以设置大一些,有利于快速检索)
+
+ ```sql
+ mysql> show variables like 'key_buffer_size';
+ +-----------------+---------+
+ | Variable_name | Value |
+ +-----------------+---------+
+ | key_buffer_size | 8388608 |
+ +-----------------+---------+
+ ```
+
+- `innodb_buffer_pool_size`,`Innodb`存储引擎缓存池大小(对于`Innodb`来说最重要的一个配置,如果所有的表用的都是`Innodb`,那么甚至建议将该值设置到物理内存的80%,`Innodb`的很多性能提升如索引都是依靠这个)
+
+ ```sql
+ mysql> show variables like 'innodb_buffer_pool_size';
+ +-------------------------+---------+
+ | Variable_name | Value |
+ +-------------------------+---------+
+ | innodb_buffer_pool_size | 8388608 |
+ +-------------------------+---------+
+ ```
+
+- `innodb_file_per_table`(`innodb`中,表数据存放在`.ibd`文件中,如果将该配置项设置为`ON`,那么一个表对应一个`ibd`文件,否则所有`innodb`共享表空间)
+
+# 压测工具mysqlslap
+
+安装MySQL时附带了一个压力测试工具`mysqlslap`(位于`bin`目录下)
+
+## 自动生成sql测试
+
+```shell
+C:\Users\zaw>mysqlslap --auto-generate-sql -uroot -proot
+mysqlslap: [Warning] Using a password on the command line interface can be insecure.
+Benchmark
+ Average number of seconds to run all queries: 1.219 seconds
+ Minimum number of seconds to run all queries: 1.219 seconds
+ Maximum number of seconds to run all queries: 1.219 seconds
+ Number of clients running queries: 1
+ Average number of queries per client: 0
+```
+
+## 并发测试
+
+```shell
+C:\Users\zaw>mysqlslap --auto-generate-sql --concurrency=100 -uroot -proot
+mysqlslap: [Warning] Using a password on the command line interface can be insecure.
+Benchmark
+ Average number of seconds to run all queries: 3.578 seconds
+ Minimum number of seconds to run all queries: 3.578 seconds
+ Maximum number of seconds to run all queries: 3.578 seconds
+ Number of clients running queries: 100
+ Average number of queries per client: 0
+
+C:\Users\zaw>mysqlslap --auto-generate-sql --concurrency=150 -uroot -proot
+mysqlslap: [Warning] Using a password on the command line interface can be insecure.
+Benchmark
+ Average number of seconds to run all queries: 5.718 seconds
+ Minimum number of seconds to run all queries: 5.718 seconds
+ Maximum number of seconds to run all queries: 5.718 seconds
+ Number of clients running queries: 150
+ Average number of queries per client: 0
+```
+
+## 多轮测试
+
+```shell
+C:\Users\zaw>mysqlslap --auto-generate-sql --concurrency=150 --iterations=10 -uroot -proot
+mysqlslap: [Warning] Using a password on the command line interface can be insecure.
+Benchmark
+ Average number of seconds to run all queries: 5.398 seconds
+ Minimum number of seconds to run all queries: 4.313 seconds
+ Maximum number of seconds to run all queries: 6.265 seconds
+ Number of clients running queries: 150
+ Average number of queries per client: 0
+```
+
+## 存储引擎测试
+
+```shell
+C:\Users\zaw>mysqlslap --auto-generate-sql --concurrency=150 --iterations=3 --engine=innodb -uroot -proot
+mysqlslap: [Warning] Using a password on the command line interface can be insecure.
+Benchmark
+ Running for engine innodb
+ Average number of seconds to run all queries: 5.911 seconds
+ Minimum number of seconds to run all queries: 5.485 seconds
+ Maximum number of seconds to run all queries: 6.703 seconds
+ Number of clients running queries: 150
+ Average number of queries per client: 0
+```
+
+```shell
+C:\Users\zaw>mysqlslap --auto-generate-sql --concurrency=150 --iterations=3 --engine=myisam -uroot -proot
+mysqlslap: [Warning] Using a password on the command line interface can be insecure.
+Benchmark
+ Running for engine myisam
+ Average number of seconds to run all queries: 53.104 seconds
+ Minimum number of seconds to run all queries: 46.843 seconds
+ Maximum number of seconds to run all queries: 60.781 seconds
+ Number of clients running queries: 150
+ Average number of queries per client: 0
+```
+
diff --git a/README.md b/README.md
index 1b23ad4c..afade8b5 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,8 @@
-# Java-Interview
-本仓库会持续更新,欢迎Star给一个鼓励!
-
-Java 面试必会 直通BAT
-
-最近上班有点忙,会抽时间持续更新本仓库。
-
-java面试需要的各个方向和面试题会持续更新,可以先关注一波
-
-我的公众号 **程序员乔戈里**
-
-整理了这个仓库java的面试题和答案面试题和答案的pdf的可以在我的公众号后台回复 面经 自己耗时一个月整理的面试题和答案,后续也会持续更新,
+- 本仓库会持续更新,欢迎Star给一个鼓励!
+- 本github最初的版本是一份word文档,目前只是把word刚刚搬上来了,但是有些图片、排版还没来得急整理,看起来可能还是有点困难
+- 所以可以先关注一下我的公众号,在我的公众号[程序员乔戈里]后台回复 **888** 获取这个github仓库的PDF版本,左侧有导航栏,方便大家阅读。
+>github必须md格式才能看得舒服些,花了很多时间找word转md的工具,找了几款不太好用,于是自己手动把word改成md格式,后来发现有些重复性工作可以写个程序处理,就写了个程序,把word中的标题、代码都变成md格式,虽然能处理不少,但是还是需要人工校对,还有图片需要上传,真的超级费事,要搞吐了。。。各位也别抱怨我的github格式不好了,毕竟也还没完全处理完,体谅一下~
+
diff --git "a/docs/Java\345\237\272\347\241\200\345\255\246\344\271\240.md" "b/docs/Java\345\237\272\347\241\200\345\255\246\344\271\240.md"
new file mode 100644
index 00000000..43b7f2a1
--- /dev/null
+++ "b/docs/Java\345\237\272\347\241\200\345\255\246\344\271\240.md"
@@ -0,0 +1,24 @@
+# Java
+
+Oracle JDK有部分源码是闭源的,如果确实需要可以查看OpenJDK的源码,可以在该网站获取。
+
+http://grepcode.com/snapshot/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/
+
+http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/73d5bcd0585d/src
+上面这个还可以查看native方法。
+
+### JDK&JRE&JVM
+
+JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具(编译、开发工具)和Java核心类库。
+Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
+JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。
+
+
+
+JDK包含JRE和Java编译、开发工具;
+JRE包含JVM和Java核心类库;
+运行Java仅需要JRE;而开发Java需要JDK。
+
+### 跨平台
+
+字节码是在虚拟机上运行的,而不是编译器。换而言之,是因为JVM能跨平台安装,所以相应JAVA字节码便可以跟着在任何平台上运行。只要JVM自身的代码能在相应平台上运行,即JVM可行,则JAVA的程序员就可以不用考虑所写的程序要在哪里运行,反正都是在虚拟机上运行,然后变成相应平台的机器语言,而这个转变并不是程序员应该关心的。
diff --git "a/docs/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230\345\217\212\347\255\224\346\241\210.md" "b/docs/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230\345\217\212\347\255\224\346\241\210.md"
new file mode 100644
index 00000000..c55e05bd
--- /dev/null
+++ "b/docs/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230\345\217\212\347\255\224\346\241\210.md"
@@ -0,0 +1,322 @@
+
+# 一. HashMap
+https://blog.csdn.net/jiary5201314/article/details/51439982
+## 1. hashMap的原理
+hashmap 是数组和链表的结合体,数组每个元素存的是链表的头结点 往 hashmap
+里面放键值对的时候先得到 key 的 hashcode,然后重新计算 hashcode, (让 1 分布均匀因为如果分布不均匀,低位全是 0,则后来计算数组下标的时候会 冲突),然后与 length-1 按位与,计算数组出数组下标 如果该下标对应的链表为空,则直接把键值对作为链表头结点,如果不为空,则 遍历链表看是否有 key 值相同的,有就把 value 替换, 没有就把该对象最为链表的第 一个节点,原有的节点最为他的后续节点
+## 2. hashcode的计算
+https://www.zhihu.com/question/20733617/answer/111577937
+https://blog.csdn.net/justloveyou_/article/details/62893086
+Key.hashcode是key的自带的hascode函数是一个int值32位
+
+hashmap jdk1.8 中 hashmap 重计算 hashcode 方法改动: 高 16 位异或低 16 位
+>return (key == null) ? 0 : h = key.hashCode() ^ (h >>> 16);
+首先确认:当 length 总是 2 的n 次方时, h & (length - 1) 等价于 hash 对length 取模 , 但是&比%具有更高的效率;
+>Jdk1.7 之前:h & (length - 1);//第三步,取模运算
+
+
+
+## 3. hashMap参数以及扩容机制
+初始容量 16,达到阀值扩容,阀值等于最大容量*负载因子,扩容每次 2 倍,总是 2 的n 次方
+扩容机制:
+使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有 Entry 数组的元素拷贝到新的 Entry 数组里,Java1.重新计算每个元素在数组中的位置。Java1.8 中不是重新计算,而是用了一种更巧妙的方式。
+## 4. get()方法
+**整个过程都不需要加锁**
+
+## 5. put()方法
+
+这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现
+
+## 6. HashMap问题 jdk1.8优化
+(1)HashMap如果有很多相同key,后面的链很长的话,你会怎么优化?或者你会用什么数据结构来存储?针对HashMap中某个Entry链太长,查找的时间复杂度可能达到O(n),怎么优化?
+Java8 做的改变:
+- HashMap 是数组+链表+红黑树(JDK1.8 增加了红黑树部分),当链表长度>=8 时转化为红黑树
+在 JDK1.8 版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过 8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高 HashMap 的性能,其中会用到红黑树的插入、删除、查找等算法。
+- java8 中对 hashmap 扩容不是重新计算所有元素在数组的位置,而是我们使用的是 2 次幂的扩展(指长度扩为原来 2 倍),所以,元素的位置要么是在原位置,要么是在原位置再移动 2 次幂的位置在扩充 HashMap 的时候,不需要像 JDK1.7 的实现那样重新计算 hash, 只需要看看原来的 hash 值新增的那个 bit 是 1 还是 0 就好了,是 0 的话索引没变,是 1 的话索引变成“原索引+oldCap”。
+
+
+## 7. 一些面试题
+
+### 7.1 HashMap 和 TreeMap 的区别
+Hashmap 使用的是数组+链表,treemap 是红黑树
+
+### 7.2 hashmap 为什么可以插入空值?
+HashMap 中添加 key==null 的 Entry 时会调用 putForNullKey 方法直接去遍历
+table[0]Entry 链表,寻找 e.key==null 的 Entry 或者没有找到遍历结束
+
+如果找到了 e.key==null,就保存 null 值对应的原值 oldValue,然后覆盖原值,并返回oldValue
+如果在 table[0]Entry 链表中没有找到就调用addEntry 方法添加一个 key 为 null 的 Entry
+### 7.3 Hashmap 为什么线程不安全:(hash 碰撞和扩容导致)
+- HashMap 底层是一个Entry 数组,当发生 hash 冲突的时候,hashmap 是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。假如 A 线程和B 线程同时对同一个数组位置调用 addEntry,两个线程会同时得到现在的头结点,然后 A 写入新的头结点之后,B 也写入新的头结点,那 B 的写入操作就会覆盖A 的写入操作造成A 的写入操作丢失
+- 删除键值对的代码如上:当多个线程同时操作同一个数组位置的时候,也都会先取得现在状态下该位置存储的头结点,然后各自去进行计算操作,之后再把结果写会到该数组位置去, 其实写回的时候可能其他的线程已经就把这个位置给修改过了,就会覆盖其他线程的修改
+- 当多个线程同时检测到总数量超过门限值的时候就会同时调用 resize 操作,各自生成新的数组并 rehash 后赋给该map 底层的数组 table,结果最终只有最后一个线程生成的新数组被赋给 table 变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的 table 作为原始数组,这样也会有问题。
+### 7.4 Hashmap 碰撞严重
+可以自定义重写 hash——多 hash 函数
+### 7.4 HashMap 高并发情况下会出现什么问题?
+扩容问题
+### 7.5 HashMap 的存放自定义类时,需要实现自定义类的什么方法?
+答:hashCode 和equals。通过 hash(hashCode)然后模运算(其实是与的位操作)定位在Entry 数组中的下标,然后遍历这之后的链表,通过 equals 比较有没有相同的 key,如果有直接覆盖 value,如果没有就重新创建一个 Entry。
+
+### 7.6 Hashmap为什么线程不安全
+hash碰撞和扩容导致,HashMap扩容的的时候可能会形成环形链表,造成死循环。
+### 7.7 Hashmap中的key可以为任意对象或数据类型吗?
+可以为null但不能是可变对象,如果是可变对象的话,对象中的属性改变,则对象 HashCode也进行相应的改变,导致下次无法查找到己存在Map中的效据
+如果可变对象在 HashMap中被用作键,时就要小心在改变对象状态的时候,不要改变它的哈希值了。我们只需要保证成员变量的改变能保证该对象的哈希值不变即可.
+
+# 二. CurrentHashMap
+http://www.importnew.com/21781.html
+https://blog.csdn.net/dingji_ping/article/details/51005799
+https://www.cnblogs.com/chengxiao/p/6842045.html
+http://ifeve.com/hashmap-concurrenthashmap-%E7%9B%B8%E4%BF%A1%E7%9C%8B%E5%AE%8C%E8%BF%99%E7%AF%87%E6%B2%A1%E4%BA%BA%E8%83%BD%E9%9A%BE%E4%BD%8F%E4%BD%A0%EF%BC%81/
+## 1. 概述
+
+
+一个ConcurrentHashMap维护一个Segment数组,一个Segment维护一个HashEntry数组。
+## 2. JDK1.7 ConCurrentHashMap原理
+其中 Segment 继承于 ReentrantLock
+ConcurrentHashMap 使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问, 能够实现真正的并发访问。
+
+
+
+Segment继承了ReentrantLock,表明每个segment都可以当做一个锁。这样对每个segment中的数据需要同步操作的话都是使用每个segment容器对象自身的锁来实现。只有对全局需要改变时锁定的是所有的segment。
+
+## 3. JDK1.7 Get
+
+- CurrentHashMap是否使用了锁???
+
+它也没有使用锁来同步,只是判断获取的entry的value是否为null,为null时才使用加锁的方式再次去获取。 这里可以看出并没有使用锁,但是value的值为null时候才是使用了加锁!!!
+
+- Get原理:
+第一步,先判断一下 count != 0;count变量表示segment中存在entry的个数。如果为0就不用找了。假设这个时候恰好另一个线程put或者remove了这个segment中的一个entry,会不会导致两个线程看到的count值不一致呢?看一下count变量的定义: transient volatile int count;
+它使用了volatile来修改。我们前文说过,Java5之后,JMM实现了对volatile的保证:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。所以,每次判断count变量的时候,即使恰好其他线程改变了segment也会体现出来
+
+
+### 3.1 在get代码的①和②之间,另一个线程新增了一个entry
+如果另一个线程新增的这个entry又恰好是我们要get的,这事儿就比较微妙了。下图大致描述了put 一个新的entry的过程。
+
+
+
+因为每个HashEntry中的next也是final的,没法对链表最后一个元素增加一个后续entry所以新增一个entry的实现方式只能通过头结点来插入了。newEntry对象是通过 new HashEntry(K k , V v, HashEntry next) 来创建的。如果另一个线程刚好new 这个对象时,当前线程来get它。因为没有同步,就可能会出现当前线程得到的newEntry对象是一个没有完全构造好的对象引用。 如果在这个new的对象的后面,则完全不影响,如果刚好是这个new的对象,那么当刚好这个对象没有完全构造好,也就是说这个对象的value值为null,就出现了如下所示的代码,需要重新加锁再次读取这个值!
+
+### 3.2 在get代码的①和②之间,另一个线程修改了一个entry的value
+value是用volitale修饰的,可以保证读取时获取到的是修改后的值。
+### 3.3 在get代码的①之后,另一个线程删除了一个entry
+假设我们的链表元素是:e1-> e2 -> e3 -> e4 我们要删除 e3这个entry,因为HashEntry中next的不可变,所以我们无法直接把e2的next指向e4,而是将要删除的节点之前的节点复制一份,形成新的链表。它的实现大致如下图所示:
+
+如果我们get的也恰巧是e3,可能我们顺着链表刚找到e1,这时另一个线程就执行了删除e3的操作,而我们线程还会继续沿着旧的链表找到e3返回。这里没有办法实时保证了,也就是说没办法看到最新的。
+我们第①处就判断了count变量,它保障了在 ①处能看到其他线程修改后的。①之后到②之间,如果再次发生了其他线程再删除了entry节点,就没法保证看到最新的了,这时候的get的实际上是未更新过的!!!。
+不过这也没什么关系,即使我们返回e3的时候,它被其他线程删除了,暴漏出去的e3也不会对我们新的链表造成影响。
+
+### 4. JDK1.7 PUT
+- 1.将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
+- 2.遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
+- 3.不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
+- 4.最后会解除在 1 中所获取当前 Segment 的锁。
+- 5.可以说是首先找到segment,确定是哪一个segment,然后在这个segment中遍历查找 key值是要查找的key值得entry,如果找到,那么就修改该key,如果没找到,那么就在头部新加一个entry.
+
+
+
+### 5. JDK1.7 Remove
+
+
+
+### 6. JDK1.7 & JDK1.8 size()
+
+```
+public int size() {
+ long n = sumCount();
+ return ((n < 0L) ? 0 :
+ (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
+ (int)n);
+}
+```
+volatile 保证内存可见,最大是65535.
+
+### 5.JDK 1.8 CurrentHashMap概述
+
+1.其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。
+2.大于8的时候才去红黑树链表转红黑树的阀值,当table[i]下面的链表长度大于8时就转化为红黑树结构。
+### 6. JDK1.8 put
+
+- 根据 key 计算出 hashcode 。
+- 判断是否需要进行初始化。
+- f即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
+- 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
+- 如果都不满足,则利用 synchronized 锁写入数据(分为链表写入和红黑树写入)。
+- 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。
+
+### 7. JDK1.8 get方法
+
+- 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
+- 如果是红黑树那就按照树的方式获取值。
+- 就不满足那就按照链表的方式遍历获取值。
+### 8.rehash过程
+- Redis rehash :dictRehash每次增量rehash n个元素,由于在自动调整大小时已设置好了ht[1]的大小,因此rehash的主要过程就是遍历ht[0],取得key,然后将该key按ht[1]的 桶的大小重新rehash,并在rehash完后将ht[0]指向ht[1],然后将ht[0]清空。在这个过程中rehashidx非常重要,它表示上次rehash时在ht[0]的下标位置。
+- 可以看到,redis对dict的rehash是分批进行的,这样不会阻塞请求,设计的比较优雅。
+- 但是在调用dictFind的时候,可能需要对两张dict表做查询。唯一的优化判断是,当key在ht[0]不存在且不在rehashing状态时,可以速度返回空。如果在rehashing状态,当在ht[0]没值的时候,还需要在ht[1]里查找。
+- dictAdd的时候,如果状态是rehashing,则把值插入到ht[1],否则ht[0]
+
+# 三 Hashtable
+https://blog.csdn.net/ns_code/article/details/36191279
+## 1.参数
+-(1)table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。
+-(2)count是Hashtable的大小,它是Hashtable保存的键值对的数量。
+- (3)threshold是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值="容量*加载因子"。
+- (4)loadFactor就是加载因子。
+- (5)modCount是用来实现fail-fast机制的
+## 1.put
+从下面的代码中我们可以看出,Hashtable中的key和value是不允许为空的,当我们想要想Hashtable中添加元素的时候,首先计算key的hash值,然
+后通过hash值确定在table数组中的索引位置,最后将value值替换或者插入新的元素,如果容器的数量达到阈值,就会进行扩充。
+
+## 2.get
+
+## 3.Remove
+ 在下面代码中,如果prev为null了,那么说明第一个元素就是要删除的元素,那么就直接指向第一个元素的下一个即可。
+
+## 4.扩容
+- 默认初始容量为11
+- 线程安全,但是速度慢,不允许key/value为null
+- 加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容
+- 扩容增量:2*原数组长度+1如 HashTable的容量为11,一次扩容后是容量为23
+
+# 四. 一些面试题
+## 4.1 hashtable和hashmap的区别
+
+## 4.2 HashMap和ConCurrentHashMap区别
+
+## 4.3 ConcurrentHashMap和HashTable区别
+- ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
+- hashtable(同一把锁):使用synchronized来保证线程安全,但效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用put添加元素,另一个线程不能使用put添加元素,也不能使用get,竞争会越来越激烈效率越低。
+- concurrenthashmap(分段锁):(锁分段技术)每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
+- concurrenthashmap是由Segment数组结构和HahEntry数组结构组成。Segment是一种可重入锁ReentrantLock,扮演锁的角色。HashEntry用于存储键值对数据。一个concurrenthashmap里包含一个Segment数组。Segment的结构和Hashmap类似,是一种数组和链表结构,一个Segment包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得对应的Segment。
+## 4.4 linkedHashMap
+https://blog.csdn.net/justloveyou_/article/details/71713781
+
+## 4.5 Linkedhashmap与 hashmap的区别
+- 1.LinkedHashMap是HashMap的子类
+-2.LinkedHashMap中的Entry增加了两个指针 before 和 after,它们分别用于维护双向链接列表。
+- 3.在put操作上,虽然LinkedHashMap完全继承了HashMap的put操作,但是在细节上还是做了一定的调整,比如,在LinkedHashMap中向哈希表中插入新Entry的同时,还会通过Entry的addBefore方法将其链入到双向链表中。
+- 4.在扩容操作上,虽然LinkedHashMap完全继承了HashMap的resize操作,但是鉴于性能和LinkedHashMap自身特点的考量,LinkedHashMap对其中的重哈希过程(transfer方法)进行了重写
+- 5.在读取操作上,LinkedHashMap中重写了HashMap中的get方法,通过HashMap中的getEntry方法获取Entry对象。在此基础上,进一步获取指定键对应的值。
+## 4.6 HashSet
+>对于HashSet而言,它是基于HashMap实现的
+Hashset源码 http://zhangshixi.iteye.com/blog/673143
+
+**Hashset 如何保证集合的没有重复元素?**
+可以看出hashset底层是hashmap但是存储的是一个对象,hashset实际将该元素e作为key放入hashmap,当key值(该元素e)相同时,只是进行更新value,并不会新增加,所以set中的元素不会进行改变。
+
+
+
+## 4.7 hashmap与hashset区别
+
+
+## 4.8 Collections.sort 内部原理
+
+重写 Collections.sort()
+```
+import java.util.*;
+class xd{
+ int a;
+ int b;
+ xd(int a,int b){
+ this.a = a;
+ this.b = b;
+ }
+}
+public class Main {
+ public static void main(String[] arg) {
+ xd a = new xd(2,3);
+ xd b = new xd(4,1);
+ xd c = new xd(1,2);
+ ArrayList array = new ArrayList<>();
+ array.add(a);
+ array.add(b);
+ array.add(c);
+ Collections.sort(array, new Comparator() {
+ @Override
+ public int compare(xd o1, xd o2) {
+ if(o1.a > o2.a)
+ return 1;
+ else if(o1.a < o2.a)
+ return -1;
+ return 0;
+ }
+ });
+ for(int i=0;i快速失败和安全失败是对迭代器而言的。 快速失败:当在迭代一个集合的时候,如果有另外一个线程在修改这个集合,就会抛出ConcurrentModification异常,java.util下都是快速失败。 安全失败:在迭代时候会在集合二层做一个拷贝,所以在修改集合上层元素不会影响下层。在java.util.concurrent下都是安全失败
+
+### Iterator和ListIterator的区别是什么?
+答:Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
+ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
+
+### 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
+答:Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
+
+### Enumeration接口和Iterator接口的区别有哪些?
+答:Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..685fceb5
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,7 @@
+
+- 本github最初的版本是一份word文档,目前只是把word刚刚搬上来了,但是有些图片、排版还没来得急整理,看起来可能还是有点困难
+- 所以可以先关注一下我的公众号,在我的公众号后台回复 **888** 获取这个github仓库的PDF版本,左侧有导航栏,方便大家阅读。
+
+>github必须md格式才能看得舒服些,花了很多时间找word转md的工具,找了几款不太好用,于是自己手动把word改成md格式,后来发现有些重复性工作可以写个程序处理,就写了个程序,把word中的标题、代码都变成md格式,虽然能处理不少,但是还是需要人工校对,还有图片需要上传,真的超级费事,要搞吐了。。。各位也别抱怨我的github格式不好了,毕竟也还没完全处理完,体谅一下~
+
+
diff --git "a/docs/\344\270\200\343\200\201Java\345\237\272\347\241\200.md" "b/docs/\344\270\200\343\200\201Java\345\237\272\347\241\200.md"
new file mode 100644
index 00000000..bd0e0eac
--- /dev/null
+++ "b/docs/\344\270\200\343\200\201Java\345\237\272\347\241\200.md"
@@ -0,0 +1,5371 @@
+- 本github最初的版本是一份word文档,目前只是把word刚刚搬上来了,但是有些图片、排版还没来得急整理,看起来可能还是有点困难
+- 所以可以先关注一下我的公众号,在我的公众号后台回复 **888** 获取这个github仓库的PDF版本,左侧有导航栏,方便大家阅读。
+
+
+
+# Java
+- Oracle JDK有部分源码是闭源的,如果确实需要可以查看OpenJDK的源码,可以在该网站获取。
+- http://grepcode.com/snapshot/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/
+- http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/73d5bcd0585d/src
+- 上面这个还可以查看native方法。
+# 1.1 JDK&JRE&JVM
+- JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具(编译、开发工具)和Java核心类库。
+- Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
+- JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。
+
+- JDK包含JRE和Java编译、开发工具;
+- JRE包含JVM和Java核心类库;
+- 运行Java仅需要JRE;而开发Java需要JDK。
+# 1.2 跨平台
+- 字节码是在虚拟机上运行的,而不是编译器。换而言之,是因为JVM能跨平台安装,所以相应JAVA字节码便可以跟着在任何平台上运行。只要JVM自身的代码能在相应平台上运行,即JVM可行,则JAVA的程序员就可以不用考虑所写的程序要在哪里运行,反正都是在虚拟机上运行,然后变成相应平台的机器语言,而这个转变并不是程序员应该关心的。
+# 1.3 基础数据类型
+- 第一类:整型 byte short int long
+- 第二类:浮点型 float double
+- 第三类:逻辑型 boolean(它只有两个值可取true false)
+- 第四类:字符型 char
+
+ - byte(1)的取值范围为-128~127(-2的7次方到2的7次方-1)
+ - short(2)的取值范围为-32768~32767(-2的15次方到2的15次方-1)
+ - int(4)的取值范围为(-2147483648~2147483647)(-2的31次方到2的31次方-1)
+ - long(8)的取值范围为(-9223372036854774808~9223372036854774807)(-2的63次方到2的63次方-1)
+ - float(4)
+ - double(8)
+ - char(2)
+ - boolean(1/8)
+
+- 内码是程序内部使用的字符编码,特别是某种语言实现其char或String类型在内存里用的内部编码;外码是程序与外部交互时外部使用的字符编码。“外部”相对“内部”而言;不是char或String在内存里用的内部编码的地方都可以认为是“外部”。例如,外部可以是序列化之后的char或String,或者外部的文件、命令行参数之类的。
+- Java语言规范规定,Java的char类型是UTF-16的code unit,也就是一定是16位(2字节),然后字符串是UTF-16 code unit的序列。
+- Java规定了字符的内码要用UTF-16编码。或者至少要让用户无法感知到String内部采用了非UTF-16的编码。
+
+- String.getBytes()是一个用于将String的内码转换为指定的外码的方法。无参数版使用平台的默认编码作为外码,有参数版使用参数指定的编码作为外码;将String的内容用外码编码好,结果放在一个新byte[]返回。调用了String.getBytes()之后得到的byte[]只能表明该外码的性质,而无法碰触到String内码的任何特质。
+
+ - Java标准库实现的对char与String的序列化规定使用UTF-8作为外码。Java的Class文件中的字符串常量与符号名字也都规定用UTF-8编码。这大概是当时设计者为了平衡运行时的时间效率(采用定长编码的UTF-16)与外部存储的空间效率(采用变长的UTF-8编码)而做的取舍。
+
+# 1.4 引用类型
+- 类、接口、数组都是引用类型
+## 四种引用
+- 目的:避免对象长期占用内存,
+
+### 强引用
+- StringReference GC时不回收
+- 当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
+### 软引用
+- SoftReference GC时如果JVM内存不足时会回收
+- 软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
+### 弱引用
+- WeakReference GC时立即回收
+- 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
+- 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
+### 虚引用
+- PhantomReference
+- 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
+
+- 在Java集合中有一种特殊的Map类型:WeakHashMap, 在这种Map中存放了键对象的弱引用,当一个键对象被垃圾回收,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。
+## 基础数据类型包装类
+### 为什么需要
+- 由于基本数据类型不是对象,所以java并不是纯面向对象的语言,好处是效率较高(全部包装为对象效率较低)。
+- Java是一个面向对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
+-
+
+### 有哪些
+ 基本类型 包装器类型
+boolean Boolean
+char Character
+int Integer
+byte Byte
+short Short
+long Long
+float Float
+double Double
+- Number是所有数字包装类的父类
+### 自动装箱、自动拆箱(编译器行为)
+- 自动装箱:可以将基础数据类型包装成对应的包装类
+- Integer i = 10000; // 编译器会改为new Integer(10000)
+- 自动拆箱:可以将包装类转为对应的基础数据类型
+- int i = new Integer(1000);//编译器会修改为 int i = new Integer(1000).intValue();
+
+- 自动拆箱时如果包装类是null,那么会抛出NPE
+### Integer.valueOf
+
+```
+public static Integer valueOf(int i) {
+ if (i >= IntegerCache.low && i <= IntegerCache.high)
+ return IntegerCache.cache[i + (-IntegerCache.low)];
+ return new Integer(i);
+}
+```
+
+
+- 调用Integer.valueOf时-128~127的对象被缓存起来。
+- 所以在此访问内的Integer对象使用==和equals结果是一样的。
+- 如果Integer的值一致,且在此范围内,因为是同一个对象,所以==返回true;但此访问之外的对象==比较的是内存地址,值相同,也是返回false。
+
+# 1.5 Object
+
+## == 与 equals的区别
+- 如果两个引用类型变量使用==运算符,那么比较的是地址,它们分别指向的是否是同一地址的对象。结果一定是false,因为两个对象不可能存放在同一地址处。
+- 要求是两个对象都不是能空值,与空值比较返回false。
+- ==不能实现比较对象的值是否相同。
+- 所有对象都有equals方法,默认是Object类的equals,其结果与==一样。
+- 如果希望比较对象的值相同,必须重写equals方法。
+## hashCode与equals的区别
+- Object中的equals:
+
+```
+public boolean equals(Object obj) {
+ return (this == obj);
+}
+```
+
+- equals 方法要求满足:
+- 自反性 a.equals(a)
+- 对称性 x.equals(y) y.equals(x)
+- 一致性 x.equals(y) 多次调用结果一致
+- 对于任意非空引用x,x.equals(null) 应该返回false
+
+- Object中的hashCode:
+
+```
+public native int hashCode();
+```
+
+- 它是一个本地方法,它的实现与本地机器有关,这里我们暂且认为他返回的是对象存储的物理位置。
+- 当equals方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规约定:值相同的对象必须有相同的hashCode。
+ - object1.equals(object2)为true,hashCode也相同;
+ - hashCode不同时,object1.equals(object2)为false;
+ - hashCode相同时,object1.equals(object2)不一定为true;
+
+- 当我们向一个Hash结构的集合中添加某个元素,集合会首先调用hashCode方法,这样就可以直接定位它所存储的位置,若该处没有其他元素,则直接保存。若该处已经有元素存在,就调用equals方法来匹配这两个元素是否相同,相同则不存,不同则链到后面(如果是链地址法)。
+- 先调用hashCode,唯一则存储,不唯一则再调用equals,结果相同则不再存储,结果不同则散列到其他位置。因为hashCode效率更高(仅为一个int值),比较起来更快。
+
+- HashMap#put源码
+- hash是key的hash值,当该hash对应的位置已有元素时会执行以下代码(hashCode相同)
+- if (p.hash == hash &&
+ ((k = p.key) == key || (key != null && key.equals(k))))
+ e = p;
+- 如果equals返回结果相同,则值一定相同,不再存入。
+## 如果重写equals不重写hashCode会怎样
+- 两个值不同的对象的hashCode一定不一样,那么执行equals,结果为true,HashSet或HashMap的键会放入值相同的对象。
+# 1.6 String&StringBuffer&StringBuilder
+- 都是final类,不允许继承;
+- String长度不可变,StringBuffer、StringBuilder长度可变;
+
+## String
+
+```
+public final class String
+ implements java.io.Serializable, Comparable, CharSequence {}
+```
+
+### equals&hashCode
+- String重写了Object的hashCode和equals。
+
+```
+public boolean equals(Object anObject) {
+ if (this == anObject) {
+ return true;
+ }
+ if (anObject instanceof String) {
+ String anotherString = (String)anObject;
+ int n = value.length;
+ if (n == anotherString.value.length) {
+ char v1[] = value;
+ char v2[] = anotherString.value;
+ int i = 0;
+ while (n-- != 0) {
+ if (v1[i] != v2[i])
+ return false;
+ i++;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+```
+
+### 添加功能
+- String是final类,不可被继承,也不可重写一个java.lang.String(类加载机制)。
+- 一般是使用StringUtils来增强String的功能。
+
+- 为什么只加载系统通过的java.lang.String类而不加载用户自定义的java.lang.String类呢?
+- 双亲委派机制
+- 因加载某个类时,优先使用父类加载器加载需要使用的类。如果我们自定义了java.lang.String这个类,
+- 加载该自定义的String类,该自定义String类使用的加载器是AppClassLoader,根据优先使用父类加载器原理,
+- AppClassLoader加载器的父类为ExtClassLoader,所以这时加载String使用的类加载器是ExtClassLoader,
+- 但是类加载器ExtClassLoader在jre/lib/ext目录下没有找到String.class类。然后使用ExtClassLoader父类的加载器BootStrap,
+- 父类加载器BootStrap在JRE/lib目录的rt.jar找到了String.class,将其加载到内存中。这就是类加载器的委托机制。
+- 所以,用户自定义的java.lang.String不被加载,也就是不会被使用。
+### + substring
+- 会创建一个新的字符串;
+- 编译时会将+转为StringBuilder的append方法。
+- 注意新的字符串是在运行时在堆里创建的。
+- String str1 = “ABC”;可能创建一个或者不创建对象,如果”ABC”这个字符串在java String池里不存在,会在java String池里创建一个创建一个String对象(“ABC”),然后str1指向这个内存地址,无论以后用这种方式创建多少个值为”ABC”的字符串对象,始终只有一个内存地址被分配,之后的都是String的拷贝,Java中称为“字符串驻留”,所有的字符串常量都会在编译之后自动地驻留。
+-
+- 注意只有字符串常量是共享的,+和substring等操作的结果不是共享的,substring也会在堆中重新创建字符串。
+
+
+```
+public String substring(int beginIndex, int endIndex) {
+ if (beginIndex < 0) {
+ throw new StringIndexOutOfBoundsException(beginIndex);
+ }
+ if (endIndex > value.length) {
+ throw new StringIndexOutOfBoundsException(endIndex);
+ }
+ int subLen = endIndex - beginIndex;
+ if (subLen < 0) {
+ throw new StringIndexOutOfBoundsException(subLen);
+ }
+ return ((beginIndex == 0) && (endIndex == value.length)) ? this
+ : new String(value, beginIndex, subLen);
+}
+```
+
+
+
+```
+public String(char value[], int offset, int count) {
+ if (offset < 0) {
+ throw new StringIndexOutOfBoundsException(offset);
+ }
+ if (count <= 0) {
+ if (count < 0) {
+ throw new StringIndexOutOfBoundsException(count);
+ }
+ if (offset <= value.length) {
+ this.value = "".value;
+ return;
+ }
+ }
+ // Note: offset or count might be near -1>>>1.
+ if (offset > value.length - count) {
+ throw new StringIndexOutOfBoundsException(offset + count);
+ }
+ this.value = Arrays.copyOfRange(value, offset, offset+count);
+}
+```
+
+
+### 常量池
+- String str = new String(“ABC”);
+- 至少创建一个对象,也可能两个。因为用到new关键字,肯定会在heap中创建一个str2的String对象,它的value是“ABC”。同时如果这个字符串在字符串常量池里不存在,会在池里创建这个String对象“ABC”。
+- String s1= “a”;
+- String s2 = “a”;
+- 此时s1 == s2 返回true
+
+- String s1= new String(“a”);
+- String s2 = new String(“a”);
+- 此时s1 == s2 返回false
+
+- ""创建的字符串在字符串池中。
+- 如果引号中字符串存在在常量池中,则仅在堆中拷贝一份(new String);
+- 如果不在,那么会先在常量池中创建一份("abc"),然后在堆中创建一份(new String),共创建两个对象。
+-
+
+### 编译优化
+- 字面量,final 都会在编译期被优化,并且会被直接运算好。
+-
+
+ - 1)注意c和d中,final变量b已经被替换为其字符串常量了。
+ - 2)注意f、g中,b被替换为其字符串常量,并且在编译时字符串常量的+运算会被执行,返回拼接后的字符串常量
+ - 3)注意j,a1作为final变量,在编译时被替换为其字符串常量
+
+- 解释 c == h / d == h/ e== h为false:c是运行时使用+拼接,创建了一个新的堆中的字符串ab,与ab字符串常量不是同一个对象;
+- 解释f == h/ g == h为true:f编译时进行优化,其值即为字符串常量ab,h也是,指向字符串常量池中的同一个对象;
+
+
+- String#intern(JDK1.7之后)
+- JDK1.7之后JVM里字符串常量池放入了堆中,之前是放在方法区。
+
+- intern()方法设计的初衷,就是重用String对象,以节省内存消耗。
+- 一定是new得到的字符串才会调用intern,字符串常量没有必要去intern。
+- 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,常量池中直接存储堆中该字符串的引用(1.7之前是常量池中再保存一份该字符串)。
+
+- 源码
+
+```
+public native String intern();
+```
+
+
+- 实例一:
+
+ - String s = new String("1");
+s.intern();
+String s2 = "1";
+System.out.println(s == s2);// false
+
+String s3 = new String("1") + new String("1");
+s3.intern();
+String s4 = "11";
+System.out.println(s3 == s4);// true
+
+- String s = newString("1"),生成了常量池中的“1” 和堆空间中的字符串对象。
+- s.intern(),这一行的作用是s对象去常量池中寻找后发现"1"已经存在于常量池中了。
+- String s2 = "1",这行代码是生成一个s2的引用指向常量池中的“1”对象。
+- 结果就是 s 和 s2 的引用地址明显不同。因此返回了false。
+
+- String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
+- s3.intern(),这一行代码,是将 s3中的“11”字符串放入 String 常量池中,此时常量池中不存在“11”字符串,JDK1.6的做法是直接在常量池中生成一个 "11" 的对象。
+- 但是在JDK1.7中,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用直接指向 s3 引用的对象,也就是说s3.intern() ==s3会返回true。
+- String s4 = "11", 这一行代码会直接去常量池中创建,但是发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。因此s3 == s4返回了true。
+
+- 实例二:
+
+ - String s3 = new String("1") + new String("1");
+String s4 = "11";
+s3.intern();
+System.out.println(s3 == s4);// false
+
+- String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
+- String s4 = "11", 这一行代码会直接去生成常量池中的"11"。
+- s3.intern(),这一行在这里就没什么实际作用了。因为"11"已经存在了。
+- 结果就是 s3 和 s4 的引用地址明显不同。因此返回了false。
+
+- 实例三:
+
+ - String str1 = new String("SEU") + new String("Calvin");
+System.out.println(str1.intern() == str1);// true
+System.out.println(str1 == "SEUCalvin");// true
+
+- str1.intern() == str1就是上面例子中的情况,str1.intern()发现常量池中不存在“SEUCalvin”,因此指向了str1。 "SEUCalvin"在常量池中创建时,也就直接指向了str1了。两个都返回true就理所当然啦。
+
+
+- 实例四:
+
+ - String str2 = "SEUCalvin";//新加的一行代码,其余不变
+String str1 = new String("SEU") + new String("Calvin");
+System.out.println(str1.intern() == str1);// false
+System.out.println(str1 == "SEUCalvin");// false
+
+- 在实例三的基础上加了第一行
+- str2先在常量池中创建了“SEUCalvin”,那么str1.intern()当然就直接指向了str2,你可以去验证它们两个是返回的true。后面的"SEUCalvin"也一样指向str2。所以谁都不搭理在堆空间中的str1了,所以都返回了false。
+## StringBuffer&StringBuilder
+- StringBuffer是线程安全的,StringBuilder不是线程安全的,但它们两个中的所有方法都是相同的。StringBuffer在StringBuilder的方法之上添加了synchronized,保证线程安全。
+- StringBuilder比StringBuffer性能更好。
+
+-
+
+# 1.7 面向对象
+## 抽象类与接口
+- 区别:
+ - 1)抽象类中方法可以不是抽象的;接口中的方法必须是抽象方法;
+ - 2)抽象类中可以有普通的成员变量;接口中的变量必须是 static final 类型的,必须被初始化 , 接口中只有常量,没有变量。
+ - 3)抽象类只能单继承,接口可以继承多个父接口;
+ - 4)Java8 中接口中会有 default 方法,即方法可以被实现。
+
+- 使用场景:
+- 如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。
+- 如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。
+## 三大特性
+- 面向对象的三个特性:封装;继承;多态
+- 封装:将数据与操作数据的方法绑定起来,隐藏实现细节,对外提供接口。
+- 继承:代码重用;可扩展性
+- 多态:允许不同子类对象对同一消息做出不同响应
+
+- 多态的三个必要条件:继承、方法的重写、父类引用指向子类对象
+## 重写和重载
+- 根据对象对方法进行选择,称为分派
+- 编译期的静态多分派:overloading重载 根据调用引用类型和方法参数决定调用哪个方法(编译器)
+- 运行期的动态单分派:overriding 重写 根据指向对象的类型决定调用哪个方法(JVM)
+
+-
+
+# 1.8 关键类
+## ThreadLocal(线程局部变量)
+- 在线程之间共享变量是存在风险的,有时可能要避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例。
+- 例如有一个静态变量
+
+```
+public static final SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd”);
+```
+
+- 如果两个线程同时调用sdf.format(…)
+- 那么可能会很混乱,因为sdf使用的内部数据结构可能会被并发的访问所破坏。当然可以使用线程同步,但是开销很大;或者也可以在需要时构造一个局部SImpleDateFormat对象。但这很浪费。
+- 希望为每一个线程构造一个对象,即使该线程调用多次方法,也只需要构造一次,不必在局部每次都构造。
+
+```
+public static final ThreadLocal sdf = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyy-MM-dd");
+ }
+};
+```
+
+
+- 实现原理:
+### 1)每个线程的变量副本是存储在哪里的
+- ThreadLocal的get方法就是从当前线程的ThreadLocalMap中取出当前线程对应的变量的副本。该Map的key是ThreadLocal对象,value是当前线程对应的变量。
+
+```
+public T get() {
+ Thread t = Thread.currentThread();
+ ThreadLocalMap map = getMap(t);
+ if (map != null) {
+ ThreadLocalMap.Entry e = map.getEntry(this);
+ if (e != null) {
+ @SuppressWarnings("unchecked")
+ T result = (T)e.value;
+ return result;
+ }
+ }
+ return setInitialValue();
+}
+```
+
+
+- ThreadLocalMap getMap(Thread t) {
+ return t.threadLocals;
+}
+- 【注意,变量是保存在线程中的,而不是保存在ThreadLocal变量中】。当前线程中,有一个变量引用名字是threadLocals,这个引用是在ThreadLocal类中createmap函数内初始化的。
+- void createMap(Thread t, T firstValue) {
+ t.threadLocals = new ThreadLocalMap(this, firstValue);
+}
+- 每个线程都有一个这样的名为threadLocals 的ThreadLocalMap,以ThreadLocal和ThreadLocal对象声明的变量类型作为key和value。
+- Thread
+- ThreadLocal.ThreadLocalMap threadLocals = null;
+- 这样,我们所使用的ThreadLocal变量的实际数据,通过get方法取值的时候,就是通过取出Thread中threadLocals引用的map,然后从这个map中根据当前threadLocal作为参数,取出数据。现在,变量的副本从哪里取出来的(本文章提出的第一个问题)已经确认解决了。
+
+- 每个线程内部都会维护一个类似 HashMap 的对象,称为 ThreadLocalMap,里边会包含若干了 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程;
+- Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的作用即是:为其属主线程建立起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系;
+- Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。
+### 2)为什么ThreadLocalMap的Key是弱引用
+- 如果是强引用,ThreadLocal将无法被释放内存。
+- 因为如果这里使用普通的key-value形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。
+### 3)ThreadLocalMap是何时初始化的(setInitialValue)
+- 在get时最后一行调用了setInitialValue,它又调用了我们自己重写的initialValue方法获得要线程局部变量对象。ThreadLocalMap没有被初始化的话,便初始化,并设置firstKey和firstValue;如果已经被初始化,那么将key和value放入map。
+
+```
+private T setInitialValue() {
+ T value = initialValue();
+ Thread t = Thread.currentThread();
+ ThreadLocalMap map = getMap(t);
+ if (map != null)
+ map.set(this, value);
+ else
+ createMap(t, value);
+ return value;
+}
+```
+
+### 4)ThreadLocalMap 原理
+
+```
+static class Entry extends WeakReference> {
+ /** The value associated with this ThreadLocal. */
+ Object value;
+
+ Entry(ThreadLocal> k, Object v) {
+ super(k);
+ value = v;
+ }
+}
+```
+
+
+- 它也是一个类似HashMap的数据结构,但是并没实现Map接口。
+- 也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
+- ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。
+#### 构造方法
+- ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
+ - // 表的大小始终为2的幂次
+ table = new Entry[INITIAL_CAPACITY];
+ int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
+ table[i] = new Entry(firstKey, firstValue);
+ size = 1;
+- // 设定扩容阈值
+ setThreshold(INITIAL_CAPACITY);
+}
+ - 在ThreadLocalMap中,形如key.threadLocalHashCode & (table.length - 1)(其中key为一个ThreadLocal实例)这样的代码片段实质上就是在求一个ThreadLocal实例的哈希值,只是在源码实现中没有将其抽为一个公用函数。
+ - 对于& (INITIAL_CAPACITY - 1),相对于2的幂作为模数取模,可以用&(2^n-1)来替代%2^n,位运算比取模效率高很多。至于为什么,因为对2^n取模,只要不是低n位对结果的贡献显然都是0,会影响结果的只能是低n位。
+
+```
+private void setThreshold(int len) {
+ threshold = len * 2 / 3;
+}
+```
+
+
+- getEntry(由ThreadLocal#get调用)
+
+```
+private Entry getEntry(ThreadLocal> key) {
+ int i = key.threadLocalHashCode & (table.length - 1);
+ Entry e = table[i];
+ if (e != null && e.get() == key)
+ return e;
+ else
+```
+
+- // 因为用的是线性探测,所以往后找还是有可能能够找到目标Entry的。
+ return getEntryAfterMiss(key, i, e);
+}
+
+
+
+```
+private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) {
+ Entry[] tab = table;
+ int len = tab.length;
+
+ while (e != null) {
+ ThreadLocal> k = e.get();
+ if (k == key)
+ return e;
+ if (k == null)
+```
+
+- // 该entry对应的ThreadLocal已经被回收,调用expungeStaleEntry来清理无效的entry
+- expungeStaleEntry(i);
+ else
+ i = nextIndex(i, len);
+ e = tab[i];
+ }
+ return null;
+}
+
+- i是位置
+- 从staleSlot开始遍历,将无效key(弱引用指向对象被回收)清理,即对应entry中的value置为null,将指向这个entry的table[i]置为null,直到扫到空entry。
+- 另外,在过程中还会对非空的entry作rehash。
+- 可以说这个函数的作用就是从staleSlot开始清理连续段中的slot(断开强引用,rehash slot等)
+
+```
+private int expungeStaleEntry(int staleSlot) {
+ Entry[] tab = table;
+ int len = tab.length;
+
+ // expunge entry at staleSlot
+ tab[staleSlot].value = null;
+ tab[staleSlot] = null;
+ size--;
+
+ // Rehash until we encounter null
+ Entry e;
+ int i;
+ for (i = nextIndex(staleSlot, len);
+ (e = tab[i]) != null;
+ i = nextIndex(i, len)) {
+ ThreadLocal> k = e.get();
+ if (k == null) {
+ e.value = null;
+ tab[i] = null;
+ size--;
+ } else {
+```
+
+- // 对于还没有被回收的情况,需要做一次rehash。
+- 如果对应的ThreadLocal的ID对len取模出来的索引h不为当前位置i,
+ - 则从h向后线性探测到第一个空的slot,把当前的entry给挪过去。
+ int h = k.threadLocalHashCode & (len - 1);
+ if (h != i) {
+ tab[i] = null;
+
+ // Unlike Knuth 6.4 Algorithm R, we must scan until
+ // null because multiple entries could have been stale.
+ while (tab[h] != null)
+ h = nextIndex(h, len);
+ tab[h] = e;
+ }
+ }
+ }
+ return i;
+}
+
+#### set(线性探测法解决hash冲突)
+
+```
+private void set(ThreadLocal> key, Object value) {
+
+ // We don't use a fast path as with get() because it is at
+ // least as common to use set() to create new entries as
+ // it is to replace existing ones, in which case, a fast
+ // path would fail more often than not.
+
+ Entry[] tab = table;
+ int len = tab.length;
+ // 计算key的hash值
+```
+
+ - int i = key.threadLocalHashCode & (len-1);
+ for (Entry e = tab[i];
+ e != null;
+ e = tab[i = nextIndex(i, len)]) {
+ ThreadLocal> k = e.get();
+
+ if (k == key) {
+- // 同一个ThreadLocal赋了新值,则替换原值为新值
+ e.value = value;
+ return;
+ }
+
+ if (k == null) {
+- // 该位置的TheadLocal已经被回收,那么会清理slot并在此位置放入当前key和value(stale:陈旧的)
+ replaceStaleEntry(key, value, i);
+ return;
+ }
+ }
+ // 下一个位置为空,那么就放到该位置上
+ tab[i] = new Entry(key, value);
+ int sz = ++size;
+- // 启发式地清理一些slot,并判断是否是否需要扩容
+ if (!cleanSomeSlots(i, sz) && sz >= threshold)
+ rehash();
+}
+
+- 每个ThreadLocal对象都有一个hash值 threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小 0x61c88647。
+
+```
+private final int threadLocalHashCode = nextHashCode();
+```
+
+
+```
+private static final int HASH_INCREMENT = 0x61c88647;
+```
+
+
+```
+private static int nextHashCode() {
+ return nextHashCode.getAndAdd(HASH_INCREMENT);
+}
+```
+
+- 由于ThreadLocalMap使用线性探测法来解决散列冲突,所以实际上Entry[]数组在程序逻辑上是作为一个环形存在的。
+
+```
+private static int nextIndex(int i, int len) {
+ return ((i + 1 < len) ? i + 1 : 0);
+}
+```
+
+
+- 在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程如下:
+- 1、如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;
+- 2、不巧,位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;
+- 3、很不巧,位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;
+- 这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置
+
+- 可以发现,set和get如果冲突严重的话,效率很低,因为ThreadLoalMap是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为。
+#### cleanSomeSlots(启发式地清理slot)
+- i是当前位置,n是元素个数
+- i对应entry是非无效(指向的ThreadLocal没被回收,或者entry本身为空)
+- n是用于控制控制扫描次数的
+- 正常情况下如果log n次扫描没有发现无效slot,函数就结束了
+
+- 但是如果发现了无效的slot,将n置为table的长度len,做一次连续段的清理
+- 再从下一个空的slot开始继续扫描
+- 这个函数有两处地方会被调用,一处是插入的时候可能会被调用,另外个是在替换无效slot的时候可能会被调用,
+- 区别是前者传入的n为元素个数,后者为table的容量
+
+```
+private boolean cleanSomeSlots(int i, int n) {
+ boolean removed = false;
+ Entry[] tab = table;
+ int len = tab.length;
+ do {
+ i = nextIndex(i, len);
+ Entry e = tab[i];
+ if (e != null && e.get() == null) {
+ n = len;
+ removed = true;
+ i = expungeStaleEntry(i);
+ }
+ } while ( (n >>>= 1) != 0);
+ return removed;
+}
+```
+
+
+#### rehash
+- 先全量清理,如果清理后现有元素个数超过负载,那么扩容
+
+```
+private void rehash() {
+```
+
+ - // 进行一次全量清理
+ expungeStaleEntries();
+
+ // Use lower threshold for doubling to avoid hysteresis
+ if (size >= threshold - threshold / 4)
+ resize();
+}
+
+- 全量清理
+
+```
+private void expungeStaleEntries() {
+ Entry[] tab = table;
+ int len = tab.length;
+ for (int j = 0; j < len; j++) {
+ Entry e = tab[j];
+ if (e != null && e.get() == null)
+ expungeStaleEntry(j);
+ }
+}
+```
+
+
+- 扩容,因为需要保证table的容量len为2的幂,所以扩容即扩大2倍
+
+```
+private void resize() {
+ Entry[] oldTab = table;
+ int oldLen = oldTab.length;
+ int newLen = oldLen * 2;
+ Entry[] newTab = new Entry[newLen];
+ int count = 0;
+
+ for (int j = 0; j < oldLen; ++j) {
+ Entry e = oldTab[j];
+ if (e != null) {
+ ThreadLocal> k = e.get();
+ if (k == null) {
+ e.value = null; // Help the GC
+ } else {
+ int h = k.threadLocalHashCode & (newLen - 1);
+ while (newTab[h] != null)
+ h = nextIndex(h, newLen);
+ newTab[h] = e;
+ count++;
+ }
+ }
+ }
+
+ setThreshold(newLen);
+ size = count;
+ table = newTab;
+}
+```
+
+
+#### remove
+
+```
+private void remove(ThreadLocal> key) {
+ Entry[] tab = table;
+ int len = tab.length;
+ int i = key.threadLocalHashCode & (len-1);
+ for (Entry e = tab[i];
+ e != null;
+ e = tab[i = nextIndex(i, len)]) {
+ if (e.get() == key) {
+```
+
+- // 显式断开弱引用
+ e.clear();
+- // 进行段清理
+ expungeStaleEntry(i);
+ return;
+ }
+ }
+}
+
+- Reference#clear
+
+```
+public void clear() {
+ this.referent = null;
+}
+```
+
+
+#### 内存泄露
+- 只有调用TheadLocal的remove或者get、set时才会采取措施去清理被回收的ThreadLocal对应的value(但也未必会清理所有的需要被回收的value)。假如一个局部的ThreadLocal不再需要,如果没有去调用remove方法清除,那么有可能会发生内存泄露。
+
+- 既然已经发现有内存泄露的隐患,自然有应对的策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。
+
+- 如果使用ThreadLocal的set方法之后,没有显式的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
+
+
+```
+JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
+```
+
+-
+
+## Iterator / ListIterator / Iterable
+- 普通for循环时不能删除元素,否则会抛出异常;Iterator可以
+
+
+```
+public interface Collection extends Iterable {}
+```
+
+- Collection接口继承了Iterable,Iterable接口定义了iterator抽象方法和forEach default方法。所以ArrayList、LinkedList都可以使用迭代器和forEach,包括增强for循环(编译时转为迭代器)。
+
+
+```
+public interface Iterable {
+ Iterator iterator();
+ default void forEach(Consumer super T> action) {
+ Objects.requireNonNull(action);
+ for (T t : this) {
+ action.accept(t);
+ }
+ }
+```
+
+- default Spliterator spliterator() {
+ return Spliterators.spliteratorUnknownSize(iterator(), 0);
+ }
+- }
+
+-
+
+- 注意这些具体的容器类返回的迭代器对象是各不相同的,主要是因为不同的容器遍历方式不同,但是这些迭代器对象都实现Iterator接口,都可以使用一个Iterator对象来统一指向这些不同的子类对象。
+- ArrayList#iterator
+
+```
+public Iterator iterator() {
+ return new Itr();
+}
+```
+
+
+- ArrayList#Itr
+
+```
+private class Itr implements Iterator {
+ int cursor; // index of next element to return
+ int lastRet = -1; // index of last element returned; -1 if no such
+ int expectedModCount = modCount;
+
+ public boolean hasNext() {
+ return cursor != size;
+ }
+
+ @SuppressWarnings("unchecked")
+ public E next() {
+ checkForComodification();
+ int i = cursor;
+ if (i >= size)
+ throw new NoSuchElementException();
+ Object[] elementData = ArrayList.this.elementData;
+ if (i >= elementData.length)
+ throw new ConcurrentModificationException();
+ cursor = i + 1;
+ return (E) elementData[lastRet = i];
+ }
+
+ public void remove() {
+ if (lastRet < 0)
+ throw new IllegalStateException();
+ checkForComodification();
+
+ try {
+ ArrayList.this.remove(lastRet);
+ cursor = lastRet;
+ lastRet = -1;
+ expectedModCount = modCount;
+ } catch (IndexOutOfBoundsException ex) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void forEachRemaining(Consumer super E> consumer) {
+ Objects.requireNonNull(consumer);
+ final int size = ArrayList.this.size;
+ int i = cursor;
+ if (i >= size) {
+ return;
+ }
+ final Object[] elementData = ArrayList.this.elementData;
+ if (i >= elementData.length) {
+ throw new ConcurrentModificationException();
+ }
+ while (i != size && modCount == expectedModCount) {
+ consumer.accept((E) elementData[i++]);
+ }
+ // update once at end of iteration to reduce heap write traffic
+ cursor = i;
+ lastRet = i - 1;
+ checkForComodification();
+ }
+
+ final void checkForComodification() {
+ if (modCount != expectedModCount)
+ throw new ConcurrentModificationException();
+ }
+}
+```
+
+
+- ArrayList#listIterator
+
+```
+public ListIterator listIterator() {
+ return new ListItr(0);
+}
+```
+
+- ArrayList#ListItr
+
+```
+private class ListItr extends Itr implements ListIterator {
+ ListItr(int index) {
+ super();
+ cursor = index;
+ }
+
+ public boolean hasPrevious() {
+ return cursor != 0;
+ }
+
+ public int nextIndex() {
+ return cursor;
+ }
+
+ public int previousIndex() {
+ return cursor - 1;
+ }
+
+ @SuppressWarnings("unchecked")
+ public E previous() {
+ checkForComodification();
+ int i = cursor - 1;
+ if (i < 0)
+ throw new NoSuchElementException();
+ Object[] elementData = ArrayList.this.elementData;
+ if (i >= elementData.length)
+ throw new ConcurrentModificationException();
+ cursor = i;
+ return (E) elementData[lastRet = i];
+ }
+
+ public void set(E e) {
+ if (lastRet < 0)
+ throw new IllegalStateException();
+ checkForComodification();
+
+ try {
+ ArrayList.this.set(lastRet, e);
+ } catch (IndexOutOfBoundsException ex) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ public void add(E e) {
+ checkForComodification();
+
+ try {
+ int i = cursor;
+ ArrayList.this.add(i, e);
+ cursor = i + 1;
+ lastRet = -1;
+ expectedModCount = modCount;
+ } catch (IndexOutOfBoundsException ex) {
+ throw new ConcurrentModificationException();
+ }
+ }
+}
+```
+
+## for /增强for/ forEach
+For-each loop Equivalent for loop
+for (type var : arr) {
+ body-of-loop
+} for (int i = 0; i < arr.length; i++) {
+ type var = arr[i];
+ body-of-loop
+}
+for (type var : coll) {
+ body-of-loop
+} for (Iterator iter = coll.iterator(); iter.hasNext(); ) {
+ type var = iter.next();
+ body-of-loop
+}
+- 增强for循环在编译时被修改为for循环:数组会被修改为下标式的循环;集合会被修改为Iterator循环。
+
+- 增强for循环不适合以下情况:(过滤、转换、平行迭代)
+- 对collection或数组中的元素不能做赋值操作;
+- 只能正向遍历,不能反向遍历;
+- 遍历过程中,collection或数组中同时只有一个元素可见,即只有“当前遍历到的元素”可见,而前一个或后一个元素是不可见的;
+
+- forEach
+- ArrayList#forEach继承自
+- Iterable接口的default方法
+- default void forEach(Consumer super T> action) {
+ Objects.requireNonNull(action);
+ for (T t : this) {
+ action.accept(t);
+ }
+ }
+
+-
+
+## Comparable与Comparator
+
+- 基本数据类型包装类和String类均已实现了Comparable接口。
+- 实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序,默认为升序。
+
+
+- 可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如TreeSet,TreeMap)的顺序。
+-
+
+# 1.9 继承
+
+```
+子类继承父类所有的成员变量(即使是private变量,有所有权,但是没有使用权,不能访问父类的private的成员变量)。
+```
+
+
+
+```
+子类中可以直接调用父类非private方法,也可以用super.父类方法的形式调用。
+```
+
+- 子类构造方法中如果没有显式使用super(父类构造方法参数)去构造父类对象的话(如果有必须是方法的第一行),编译器会在第一行添加super()。
+
+- 子类的构造函数可否不使用super(父类构造方法参数)调用超类的构造方法?
+- 可以不用显式的写出super,但前提是“父类中有多个构造方法,且有一个是显式写出的无参的构造方法”。
+
+-
+
+# 1.10 内部类
+- 在另一个类的里面定义的类就是内部类
+- 内部类是编译器现象,与虚拟机无关。
+- 编译器会将内部类编译成用$分割外部类名和内部类名的常规类文件,而虚拟机对此一无所知。
+
+
+```
+内部类可以是static的,也可用public,default,protected和private修饰。(而外部类即类名和文件名相同的只能使用public和default)。
+```
+
+
+## 优点
+- 每个内部类都能独立地继承一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
+- 接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
+
+- 用内部类还能够为我们带来如下特性:
+- 1、内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外部对象的信息相互独立。
+- 2、在单个外部类中,可以让多个内部类实现不同的接口,或者继承不同的类。外部类想要多继承的类可以分别由内部类继承,并进行Override或者直接复用。然后外部类通过创建内部类的对象来使用该内部对象的方法和成员,从而达到复用的目的,这样外部内就具有多个父类的所有特征。
+- 3、创建内部类对象的时刻并不依赖于外部类对象的创建。
+- 4、内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
+- 5、内部类提供了更好的封装,除了该外部类,其他类都不能访问
+
+- 只有静态内部类可以同时拥有静态成员和非静态成员,其他内部类只有拥有非静态成员。
+
+## 成员内部类:就像外部类的一个成员变量
+
+
+- 注意内部类的对象总有一个外部类的引用
+- 当创建内部类对象时,会自动将外部类的this引用传递给当前的内部类的构造方法。
+
+## 静态内部类:就像外部类的一个静态成员变量
+
+
+```
+public class OuterClass {
+
+ private static class StaticInnerClass {
+ int id;
+ static int increment = 1;
+ }
+}
+//调用方式:
+//外部类.内部类 instanceName = new 外部类.内部类();
+```
+
+
+## 局部内部类:定义在一个方法或者一个块作用域里面的类
+- 想创建一个类来辅助我们的解决方案,又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。
+
+- JDK1.8之前不能访问非final的局部变量!
+- 生命周期不一致:
+- 方法在栈中,对象在堆中;方法执行完,对象并没有死亡
+- 如果可以使用方法的局部变量,如果方法执行完毕,就会访问一个不存在的内存区域。
+- 而final是常量,就可以将该常量的值复制一份,即使不存在也不影响。
+
+```
+public Destination destination(String str) {
+ class PDestination implements Destination {
+ private String label;
+
+ private PDestination(String whereTo) {
+ label = whereTo;
+ }
+ public String readLabel() {
+ return label;
+ }
+ }
+ return new PDestination(str);
+}
+```
+
+
+## 匿名内部类:必须继承一个父类或实现一个接口
+
+- 匿名内部类和局部内部类在JDK1.8 之前都不能访问一个非final的局部变量,只能访问final的局部变量,原因是生命周期不同,可能栈中的局部变量已经被销毁,而堆中的对象仍存活,此时会访问一个不存在的内存区域。假如是final的变量,那么编译时会将其拷贝一份,延长其生命周期。
+- 拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。
+- 但在JDK1.8之后可以访问一个非final的局部变量了,前提是非final的局部变量没有修改,表现得和final变量一样才可以!
+
+```
+interface AnonymousInner {
+ int add();
+}
+public class AnonymousOuter {
+ public AnonymousInner getAnonymousInner(){
+ int x = 100;
+ return new AnonymousInner() {
+ int y = 100;
+ @Override
+ public int add() {
+ return x + y;
+ }
+ };
+ }
+}
+```
+
+
+# 1.11 关键字
+## final
+
+## try-finally-return
+- 1、不管有没有出现异常,finally块中代码都会执行;
+- 2、当try和catch中有return时,finally仍然会执行;无论try里执行了return语句、break语句、还是continue语句,finally语句块还会继续执行;如果执行try和catch时JVM退出(比如System.exit(0)),那么finally不会被执行;
+- finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前确定的;
+- 【
+- 如果try语句里有return,那么代码的行为如下:
+1.如果有返回值,就把返回值保存到局部变量中
+2.执行jsr指令跳到finally语句里执行
+3.执行完finally语句后,返回之前保存在局部变量表里的值
+- 】
+- 3、当try和finally里都有return时,会忽略try的return,而使用finally的return。
+- 4、如果try块中抛出异常,执行finally块时又抛出异常,此时原始异常信息会丢失,只抛出在finally代码块中的异常。
+
+- 实例一:
+
+```
+public static int test() {
+ int x = 1;
+ try {
+ x++;
+ return x; // 2
+ } finally {
+ x++;
+ }
+}
+```
+
+
+- 实例二:
+
+```
+private static int test2() {
+ try {
+ System.out.println("try...");
+ return 80;
+ } finally {
+ System.out.println("finally...");
+ return 100; // 100
+ }
+}
+```
+
+
+## static
+- static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。
+### 1)修饰成员方法:静态成员方法
+- 在静态方法中不能访问类的非静态成员变量和非静态成员方法;
+- 在非静态成员方法中是可以访问静态成员方法/变量的;
+- 即使没有显式地声明为static,类的构造器实际上也是静态方法
+
+### 2)修饰成员变量:静态成员变量
+- 静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
+
+- 静态成员变量并发下不是线程安全的,并且对象是单例的情况下,非静态成员变量也不是线程安全的。
+
+- 怎么保证变量的线程安全?
+- 只有一个线程写,其他线程都是读的时候,加volatile;线程既读又写,可以考虑Atomic原子类和线程安全的集合类;或者考虑ThreadLocal
+### 3)修饰代码块:静态代码块
+- 用来构造静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
+
+### 4)修饰内部类:静态内部类
+- 成员内部类和静态内部类的区别:
+ - 1)前者只能拥有非静态成员;后者既可拥有静态成员,又可拥有非静态成员
+ - 2)前者持有外部类的的引用,可以访问外部类的静态成员和非静态成员;后者不持有外部类的引用,只能访问外部类的静态成员
+ - 3)前者不能脱离外部类而存在;后者可以
+### 5)修饰import:静态导包
+-
+
+## switch
+### switch字符串实现原理
+- 对比反编译之后的结果:
+
+- 编译后switch还是基于整数,该整数来自于String的hashCode。
+- 先比较字符串的hashCode,因为hashCode相同未必值相同,又再次检查了equals是否相同。
+
+-
+
+### 字节码实现原理(tableswitch / lookupswitch)
+- 编译器会使用tableswitch和lookupswitch指令来生成switch语句的编译代码。当switch语句中的case分支的条件值比较稀疏时,tableswitch指令的空间使用率偏低。这种情况下将使用lookupswitch指令来替代。lookupswitch指令的索引表由int类型的键(来源于case语句块后面的数值)与对应的目标语句偏移量所构成。当lookupswitch指令执行时,switch语句的条件值将和索引表中的键进行比较,如果某个键和条件值相符,那么将转移到这个键对应的分支偏移量继续执行,如果没有键值符合,执行将在default分支执行。
+
+## abstract
+- 只要含有抽象方法,这个类必须添加abstract关键字,定义为抽象类。
+- 只要父类是抽象类,内含抽象方法,那么继承这个类的子类的相对应的方法必须重写。如果不重写,就需要把父类的声明抽象方法再写一遍,留给这个子类的子类去实现。同时将这个子类也定义为抽象类。
+- 注意抽象类中可以有抽象方法,也可以有具体实现方法(当然也可以没有)。
+- 抽象方法须加abstract关键字,而具体方法不可加
+- 只要是抽象类,就不能存在这个类的对象(不可以new一个这个类的对象)。
+## this & super
+- this
+- 自身引用;访问成员变量与方法;调用其他构造方法
+- 1. 通过this调用另一个构造方法,用法是this(参数列表),这个仅在类的构造方法中可以使用
+- 2. 函数参数或者函数中的局部变量和成员变量同名的情况下,成员变量被屏蔽,此时要访问成员变量则需要用“this.成员变量名”的方式来引用成员变量。
+- 3. 需要引用当前对象时候,直接用this(自身引用)
+
+- super
+- 父类引用;访问父类成员变量与方法;调用父类构造方法
+- super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
+- super有三种用法:
+- 1.普通的直接引用
+- 与this类似,super相当于是指向当前对象的父类,这样就可以用super.xxx来引用父类的成员,如果不冲突的话也可以不加super。
+- 2.子类中的成员变量或方法与父类中的成员变量或方法同名时,为了区别,调用父类的成员必须要加super
+- 3.调用父类的构造函数
+## 访问权限
+
+# 1.12 枚举
+## JDK实现
+- 实例:
+
+```
+public enum Labels0 {
+
+ ENVIRONMENT("环保"), TRAFFIC("交通"), PHONE("手机");
+
+ private String name;
+
+ private Labels0(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
+```
+
+
+- 编译后生成的字节码反编译:
+
+- 可以清晰地看到枚举被编译后其实就是一个类,该类被声明成 final,说明其不能被继承,同时它继承了 Enum 类。枚举里面的元素被声明成 static final ,另外生成一个静态代码块 static{},最后还会生成 values 和 valueOf 两个方法。下面以最简单的 Labels 为例,一个一个模块来看。
+
+### Enum 类
+- Enum 类是一个抽象类,主要有 name 和 ordinal 两个属性,分别用于表示枚举元素的名称和枚举元素的位置索引,而构造函数传入的两个变量刚好与之对应。
+
+- toString 方法直接返回 name。
+- equals 方法直接用 == 比较两个对象。
+- hashCode 方法调用的是父类的 hashCode 方法。
+- 枚举不支持 clone、finalize 和 readObject 方法。
+- compareTo 方法可以看到就是比较 ordinal 的大小。
+- valueOf 方法,根据传入的字符串 name 来返回对应的枚举元素。
+
+### 静态代码块的实现
+- 在静态代码块中创建对象,对象是单例的!
+- 可以看到静态代码块主要完成的工作就是先分别创建 Labels 对象,然后将“ENVIRONMENT”、“TRAFFIC”和“PHONE”字符串作为 name ,按照顺序分别分配位置索引0、1、2作为 ordinal,然后将其值设置给创建的三个 Labels 对象的 name 和 ordinal 属性,此外还会创建一个大小为3的 Labels 数组 ENUM$VALUES,将前面创建出来的 Labels 对象分别赋值给数组。
+### values的实现
+- 可以看到它是一个静态方法,主要是使用了前面静态代码块中的 Labels 数组 ENUM$VALUES,调用 System.arraycopy 对其进行复制,然后返回该数组。所以通过 Labels.values()[2]就能获取到数组中索引为2的元素。
+### valueOf 方法
+- 该方法同样是个静态方法,可以看到该方法的实现是间接调用了父类 Enum 类的 valueOf 方法,根据传入的字符串 name 来返回对应的枚举元素,比如可以通过 Labels.valueOf("ENVIRONMENT")获取 Labels.ENVIRONMENT。
+
+- 枚举本质其实也是一个类,而且都会继承java.lang.Enum类,同时还会生成一个静态代码块 static{},并且还会生成 values 和 valueOf 两个方法。而上述的工作都需要由编译器来完成,然后我们就可以像使用我们熟悉的类那样去使用枚举了。
+-
+
+## 用enum代替int常量
+- 将int枚举常量翻译成可打印的字符串,没有很便利的方法。
+- 要遍历一个枚举组中的所有int 枚举常量,甚至获得int枚举组的大小。
+
+- 使用枚举类型的values方法可以获得该枚举类型的数组
+- 枚举类型没有可以访问的构造器,是真正的final;是实例受控的,它们是单例的泛型化;本质上是单元素的枚举;提供了编译时的类型安全。
+- 单元素的枚举是实现单例的最佳方法!
+
+- 可以在枚举类型中放入这段代码,可以实现String2Enum。
+- 注意Operation是枚举类型名。
+
+## 用实例域代替序数
+
+- 这种实现不好,不推荐使用ordinal方法,推荐使用下面这种实现:
+
+
+## 用EnumSet代替位域
+- 位域是将几个常量合并到一个集合中,我们推荐用枚举代替常量,用EnumSet代替集合
+ - EnumSet.of(enum1,enum2) -> Set<枚举>
+## 用EnumMap代替序数索引
+
+
+
+- 将一个枚举类型的值与一个元素(或一组)对应起来,推荐使用EnumMap数据结构
+- 如果是两个维度的变化,那么可以使用EnumMap>
+
+
+
+
+-
+
+# 1.13 序列化
+## JDK序列化(Serizalizable)
+- 定义:将实现了Serializable接口(标记型接口)的对象转换成一个字节数组,并可以将该字节数组转为原来的对象。
+
+- ObjectOutputStream 是专门用来输出对象的输出流;
+- ObjectOutputStream 将 Java 对象写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。
+
+## serialVersionUID
+- Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)。
+
+ - 1)如果没有添加serialVersionUID,进行了序列化,而在反序列化的时候,修改了类的结构(添加或删除成员变量,修改成员变量的命名),此时会报错。
+ - 2)如果添加serialVersionUID,进行了序列化,而在反序列化的时候,修改了类的结构(添加或删除成员变量,修改成员变量的命名),那么可能会恢复部分数据,或者恢复不了数据。
+
+- 如果设置了serialVersionUID并且一致,那么可能会反序列化部分数据;如果没有设置,那么只要属性不同,那么无法反序列化。
+
+## 其他序列化工具
+- XML/JSON
+- Thrift/Protobuf
+
+## 对象深拷贝与浅拷贝
+- 当拷贝一个变量时,原始引用和拷贝的引用指向同一个对象,改变一个引用所指向的对象会对另一个引用产生影响。
+- 如果需要创建一个对象的浅拷贝,那么需要调用clone方法。
+- Object 类本身不实现接口 Cloneable,直接调用clone会抛出异常。
+
+```
+如果要在自己定义类中调用clone方法,必须实现Cloneable接口(标记型接口),因为Object类中的clone方法为protected,所以需要自己重写clone方法,设置为public。
+```
+
+- protected native Object clone() throws CloneNotSupportedException;
+
+
+
+```
+public class Person implements Cloneable {
+ private int age;
+ private String name;
+ private Company company;
+ @Override
+ public Person clone() throws CloneNotSupportedException {
+ return (Person) super.clone();
+ }
+```
+
+- }
+
+
+```
+public class Company implements Cloneable{
+ private String name;
+```
+
+
+```
+ @Override
+public Company clone() throws CloneNotSupportedException {
+ return (Company) super.clone();
+}
+```
+
+- }
+- 使用super(即Object)的clone方法只能进行浅拷贝。
+- 如果希望实现深拷贝,需要修改实现,比如修改为:
+
+```
+@Override
+public Person clone() throws CloneNotSupportedException {
+ Person person = (Person) super.clone();
+ person.setCompany(company.clone()); // 一个新的Company
+ return person;
+}
+```
+
+- 假如说Company中还有持有其他对象的引用,那么Company中也要像Person这样做。
+- 可以说:想要深拷贝一个子类,那么它的所有父类都必须可以实现深拷贝。
+
+- 另一种实现对象深拷贝的方式是序列化。
+- @Override
+protected Object clone() {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream os = new ObjectOutputStream(baos);
+ os.writeObject(this);
+ os.close();
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream in = new ObjectInputStream(bais);
+ Object ret = in.readObject();
+ in.close();
+ return ret;
+ }catch(Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+}
+
+-
+
+# 1.14 异常
+
+
+## Error、Exception
+- Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
+- Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
+## 常见RuntimeException
+- IllegalArgumentException - 方法的参数无效
+- NullPointerException - 试图访问一空对象的变量、方法或空数组的元素
+- ArrayIndexOutOfBoundsException - 数组越界访问
+- ClassCastException - 类型转换异常
+- NumberFormatException 继承IllegalArgumentException,字符串转换为数字时出现。比如int i= Integer.parseInt("ab3");
+-
+
+## RuntimeException与非Runtime Exception
+- RuntimeException是运行时异常,也称为未检查异常;
+- 非RuntimeException 也称为CheckedException 受检异常
+
+- 前者可以不必进行try-catch,后者必须要进行try-catch或者throw。
+## 异常包装
+- 在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型
+- try{
+- …
+- }catch(SQLException e){
+- throw new ServletException(e.getMessage());
+- }
+- 这样的话ServletException会取代SQLException。
+
+- 有一种更好的方法,可以保存原有异常的信息,将原始异常设置为新的异常的原因
+- try{
+- …
+- }catch(SQLException e){
+- Throwable se = new ServletException(e.getMessage());
+- se.initCause(e);
+- throw se;
+- }
+- 当捕获到异常时,可以使用getCause方法来重新得到原始异常
+- Throwable e = se.getCause();
+- 建议使用这种包装技术,可以抛出系统的高级异常(自己new的),又不会丢失原始异常的细节。
+
+- 早抛出,晚捕获。
+-
+
+# 1.15 泛型
+- 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
+## 泛型接口/类/方法
+## 泛型继承、实现
+
+- 父类使用泛型,子类要么去指定具体类型参数,要么继续使用泛型
+
+## 泛型的约束和局限性
+ - 1)只能使用包装器类型,不能使用基本数据类型;
+
+ - 2)运行时类型查询只适用于原始类型,不适用于带类型参数的类型;
+- if(a instanceof Pair) //error
+
+ - 3)不能创建带有类型参数的泛型类的数组
+- Pair [] pairs = new Pair[10];//error
+- 只能使用反射来创建泛型数组
+
+```
+public static T[] minmax(T… a){
+```
+
+- T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(),个数);
+- …复制
+- }
+## 通配符
+
+- ? 未知类型 只可以用于声明时,声明类型或方法参数,不能用于定义时(指定类型参数时)
+- List> unknownList;
+- List extends Number> unknownNumberList;
+- List super Integer> unknownBaseLineIntgerList;
+
+- 对于参数值是未知类型的容器类,只能读取其中元素,不能向其中添加元素, 因为,其类型是未知,所以编译器无法识别添加元素的类型和容器的类型是否兼容,唯一的例外是null。
+
+- 通配符类型 List> 与原始类型 List 和具体类型 List都不相同,List>表示这个list内的每个元素的类型都相同,但是这种类型具体是什么我们却不知道。注意,List>和List