From ce2b13c854498d1cc0727fc930632facdc77521b Mon Sep 17 00:00:00 2001 From: ShengGuoZhou <18112570002@163.com> Date: Thu, 30 May 2019 17:56:57 +0800 Subject: [PATCH 001/143] Update platform-independent.md --- basics/java-basic/platform-independent.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/basics/java-basic/platform-independent.md b/basics/java-basic/platform-independent.md index 6f9c64fe..8b2ab0d3 100644 --- a/basics/java-basic/platform-independent.md +++ b/basics/java-basic/platform-independent.md @@ -46,7 +46,7 @@ **Java虚拟机** -所谓平台无关性,就是说要能够做到可以在多个平台上都能无缝对接。但是,对于不通的平台,硬件和操作系统肯定都是不一样的。 +所谓平台无关性,就是说要能够做到可以在多个平台上都能无缝对接。但是,对于不同的平台,硬件和操作系统肯定都是不一样的。 对于不同的硬件和操作系统,最主要的区别就是指令不同。比如同样执行a+b,A操作系统对应的二进制指令可能是10001000,而B操作系统对应的指令可能是11101110。那么,想要做到跨平台,最重要的就是可以根据对应的硬件和操作系统生成对应的二进制指令。 @@ -119,4 +119,4 @@ Java的平台无关性是建立在Java虚拟机的平台有关性基础之上的 [6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539297082025.jpg [7]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539303829914.jpg [8]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539319645205.jpg - [9]: https://www.hollischuang.com/archives/2938 \ No newline at end of file + [9]: https://www.hollischuang.com/archives/2938 From 628f4f89ca252f07d2c854c228d49c8da4b23211 Mon Sep 17 00:00:00 2001 From: starkfang Date: Tue, 4 Jun 2019 10:55:45 +0800 Subject: [PATCH 002/143] =?UTF-8?q?=E7=BA=A0=E6=AD=A3=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- basics/java-basic/platform-independent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basics/java-basic/platform-independent.md b/basics/java-basic/platform-independent.md index 6f9c64fe..08a9bf04 100644 --- a/basics/java-basic/platform-independent.md +++ b/basics/java-basic/platform-independent.md @@ -88,7 +88,7 @@ ### 小结 -对于Java的平台无关性的支持是分布在整个Java体系结构中的。其中扮演者重要的角色的有Java语言规范、Class文件、Java虚拟机等。 +对于Java的平台无关性的支持是分布在整个Java体系结构中的。其中扮演着重要角色的有Java语言规范、Class文件、Java虚拟机等。 * Java语言规范 * 通过规定Java语言中基本数据类型的取值范围和行为 From 0b8ac5d2579fcaaaa7de2ea12c61c5d85d225815 Mon Sep 17 00:00:00 2001 From: wangxing1986 <52741381+wangxing1986@users.noreply.github.com> Date: Fri, 12 Jul 2019 17:56:04 +0900 Subject: [PATCH 003/143] =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97=E6=9B=B4?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【什么是多态】章节,文字校验 --- basics/java-basic/polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basics/java-basic/polymorphism.md b/basics/java-basic/polymorphism.md index e67ec859..1d49b0b5 100644 --- a/basics/java-basic/polymorphism.md +++ b/basics/java-basic/polymorphism.md @@ -41,7 +41,7 @@ 比如Spring 中的IOC出来的对象,你在使用的时候就不知道他是谁,或者说你可以不用关心他是谁。根据具体情况而定。 -另外,还有一种说法,包括维基百科也说明,动态还分为动态多态和静态多态。 +另外,还有一种说法,包括维基百科也说明,多态还分为动态多态和静态多态。 上面提到的那种动态绑定认为是动态多态,因为只有在运行期才能知道真正调用的是哪个类的方法。 还有一种静态多态,一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法、 From f2eae0603be2d48b8d3b2951791f18d8c6fb4009 Mon Sep 17 00:00:00 2001 From: "hollis.zhl" Date: Sun, 21 Jul 2019 17:55:59 +0800 Subject: [PATCH 004/143] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BA=8F=E5=88=97?= =?UTF-8?q?=E5=8C=96=E3=80=81SPI=E7=9B=B8=E5=85=B3=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 8 +- basics/java-basic/api-vs-spi.md | 10 + basics/java-basic/create-spi.md | 38 +++ basics/java-basic/serialize-principle.md | 375 +++++++++++++++++++++++ basics/java-basic/serialize-singleton.md | 231 ++++++++++++++ basics/java-basic/serialize.md | 5 + basics/java-basic/spi-principle.md | 49 +++ mind-map/.DS_Store | Bin 6148 -> 0 bytes 9 files changed, 713 insertions(+), 4 deletions(-) create mode 100644 basics/java-basic/api-vs-spi.md create mode 100644 basics/java-basic/create-spi.md create mode 100644 basics/java-basic/serialize-principle.md create mode 100644 basics/java-basic/serialize-singleton.md create mode 100644 basics/java-basic/serialize.md create mode 100644 basics/java-basic/spi-principle.md delete mode 100644 mind-map/.DS_Store diff --git a/.gitignore b/.gitignore index 4725d0c4..5927d83f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ .idea/**/common_info.xml .idea/** + # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids diff --git a/README.md b/README.md index 2d29842b..26ca1051 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ apache集合处理工具类的使用、 #### 序列化 -什么是序列化与反序列化、为什么序列化、序列化底层原理、序列化与单例模式、protobuf、为什么说序列化并不安全 +[什么是序列化与反序列化](/basics/java-basic/serialize.md)、为什么序列化、[序列化底层原理](/basics/java-basic/serialize-principle.md)、[序列化与单例模式](/basics/java-basic/serialize-singleton.md)、protobuf、为什么说序列化并不安全 #### 注解 @@ -189,7 +189,7 @@ Spring常用注解 #### 泛型 -泛型与继承、类型擦除、泛型中K T V E ? [object等的含义](/basics/java-basic/k-t-v-e.md)、泛型各种用法 +泛型与继承、类型擦除、[泛型中K T V E ? object等的含义](/basics/java-basic/k-t-v-e.md)、泛型各种用法 限定通配符和非限定通配符、上下界限定符extends 和 super @@ -211,9 +211,9 @@ junit、mock、mockito、内存数据库(h2) #### API&SPI -API、API和SPI的关系和区别 +API、[API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md) -如何定义SPI、SPI的实现原理 +[如何定义SPI](/basics/java-basic/create-spi.md)、[SPI的实现原理](/basics/java-basic/spi-principle.md) #### 异常 diff --git a/basics/java-basic/api-vs-spi.md b/basics/java-basic/api-vs-spi.md new file mode 100644 index 00000000..6459290a --- /dev/null +++ b/basics/java-basic/api-vs-spi.md @@ -0,0 +1,10 @@ +Java 中区分 API 和 SPI,通俗的讲:API 和 SPI 都是相对的概念,他们的差别只在语义上,API 直接被应用开发人员使用,SPI 被框架扩展人员使用 + + +API Application Programming Interface + +大多数情况下,都是实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现。 + +SPI Service Provider Interface + +而如果是调用方来制定接口,实现方来针对接口来实现不同的实现。调用方来选择自己需要的实现方。 \ No newline at end of file diff --git a/basics/java-basic/create-spi.md b/basics/java-basic/create-spi.md new file mode 100644 index 00000000..7fd226ae --- /dev/null +++ b/basics/java-basic/create-spi.md @@ -0,0 +1,38 @@ +步骤1、定义一组接口 (假设是org.foo.demo.IShout),并写出接口的一个或多个实现,(假设是org.foo.demo.animal.Dog、org.foo.demo.animal.Cat)。 + + public interface IShout { + void shout(); + } + public class Cat implements IShout { + @Override + public void shout() { + System.out.println("miao miao"); + } + } + public class Dog implements IShout { + @Override + public void shout() { + System.out.println("wang wang"); + } + } + +步骤2、在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 (org.foo.demo.IShout文件),内容是要应用的实现类(这里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat,每行一个类)。 + + org.foo.demo.animal.Dog + org.foo.demo.animal.Cat + +步骤3、使用 ServiceLoader 来加载配置文件中指定的实现。 + + public class SPIMain { + public static void main(String[] args) { + ServiceLoader shouts = ServiceLoader.load(IShout.class); + for (IShout s : shouts) { + s.shout(); + } + } + } + +代码输出: + + wang wang + miao miao \ No newline at end of file diff --git a/basics/java-basic/serialize-principle.md b/basics/java-basic/serialize-principle.md new file mode 100644 index 00000000..31a4a1c4 --- /dev/null +++ b/basics/java-basic/serialize-principle.md @@ -0,0 +1,375 @@ +## 序列化与反序列化 + +序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。 + +## Java对象的序列化与反序列化 + +在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。但是,我们创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止运行,这些对象的状态也就随之而丢失了。 + +但是在真实的应用场景中,我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。 + +对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。 + +在Java中,对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中。 + +## 相关接口及类 + +Java为了方便开发人员将Java对象进行序列化及反序列化提供了一套方便的API来支持。其中包括以下接口和类: + +> java.io.Serializable +> +> java.io.Externalizable +> +> ObjectOutput +> +> ObjectInput +> +> ObjectOutputStream +> +> ObjectInputStream + +## Serializable 接口 + +类通过实现 `java.io.Serializable` 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。**序列化接口没有方法或字段,仅用于标识可序列化的语义。** ([该接口并没有方法和字段,为什么只有实现了该接口的类的对象才能被序列化呢?][1]) + +当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出 `NotSerializableException`。 + +如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成`java.io.Serializable`接口。 + +下面是一个实现了`java.io.Serializable`接口的类 + + package com.hollischaung.serialization.SerializableDemos; + import java.io.Serializable; + /** + * Created by hollis on 16/2/17. + * 实现Serializable接口 + */ + public class User1 implements Serializable { + + private String name; + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "User{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } + } + + +通过下面的代码进行序列化及反序列化 + + package com.hollischaung.serialization.SerializableDemos; + + import org.apache.commons.io.FileUtils; + import org.apache.commons.io.IOUtils; + + import java.io.*; + /** + * Created by hollis on 16/2/17. + * SerializableDemo1 结合SerializableDemo2说明 一个类要想被序列化必须实现Serializable接口 + */ + public class SerializableDemo1 { + + public static void main(String[] args) { + //Initializes The Object + User1 user = new User1(); + user.setName("hollis"); + user.setAge(23); + System.out.println(user); + + //Write Obj to File + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(new FileOutputStream("tempFile")); + oos.writeObject(user); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(oos); + } + + //Read Obj from File + File file = new File("tempFile"); + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new FileInputStream(file)); + User1 newUser = (User1) ois.readObject(); + System.out.println(newUser); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(ois); + try { + FileUtils.forceDelete(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + } + + //OutPut: + //User{name='hollis', age=23} + //User{name='hollis', age=23} + + +更多关于Serializable的使用,请参考[代码实例][2] + +## Externalizable接口 + +除了Serializable 之外,java中还提供了另一个序列化接口`Externalizable` + +为了了解Externalizable接口和Serializable接口的区别,先来看代码,我们把上面的代码改成使用Externalizable的形式。 + + package com.hollischaung.serialization.ExternalizableDemos; + + import java.io.Externalizable; + import java.io.IOException; + import java.io.ObjectInput; + import java.io.ObjectOutput; + + /** + * Created by hollis on 16/2/17. + * 实现Externalizable接口 + */ + public class User1 implements Externalizable { + + private String name; + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public void writeExternal(ObjectOutput out) throws IOException { + + } + + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + + } + + @Override + public String toString() { + return "User{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } + } + + +  + + package com.hollischaung.serialization.ExternalizableDemos; + + import java.io.*; + + /** + * Created by hollis on 16/2/17. + */ + public class ExternalizableDemo1 { + + //为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记 + //IOException直接抛出 + public static void main(String[] args) throws IOException, ClassNotFoundException { + //Write Obj to file + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")); + User1 user = new User1(); + user.setName("hollis"); + user.setAge(23); + oos.writeObject(user); + //Read Obj from file + File file = new File("tempFile"); + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); + User1 newInstance = (User1) ois.readObject(); + //output + System.out.println(newInstance); + } + } + //OutPut: + //User{name='null', age=0} + + +通过上面的实例可以发现,对User1类进行序列化及反序列化之后得到的对象的所有属性的值都变成了默认值。也就是说,之前的那个对象的状态并没有被持久化下来。这就是Externalizable接口和Serializable接口的区别: + +Externalizable继承了Serializable,该接口中定义了两个抽象方法:`writeExternal()`与`readExternal()`。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写`writeExternal()`与`readExternal()`方法。由于上面的代码中,并没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。 + +按照要求修改之后代码如下: + + package com.hollischaung.serialization.ExternalizableDemos; + + import java.io.Externalizable; + import java.io.IOException; + import java.io.ObjectInput; + import java.io.ObjectOutput; + + /** + * Created by hollis on 16/2/17. + * 实现Externalizable接口,并实现writeExternal和readExternal方法 + */ + public class User2 implements Externalizable { + + private String name; + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public void writeExternal(ObjectOutput out) throws IOException { + out.writeObject(name); + out.writeInt(age); + } + + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + name = (String) in.readObject(); + age = in.readInt(); + } + + @Override + public String toString() { + return "User{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } + } + + +  + + package com.hollischaung.serialization.ExternalizableDemos; + + import java.io.*; + + /** + * Created by hollis on 16/2/17. + */ + public class ExternalizableDemo2 { + + //为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记 + //IOException直接抛出 + public static void main(String[] args) throws IOException, ClassNotFoundException { + //Write Obj to file + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")); + User2 user = new User2(); + user.setName("hollis"); + user.setAge(23); + oos.writeObject(user); + //Read Obj from file + File file = new File("tempFile"); + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); + User2 newInstance = (User2) ois.readObject(); + //output + System.out.println(newInstance); + } + } + //OutPut: + //User{name='hollis', age=23} + + +这次,就可以把之前的对象状态持久化下来了。 + +> 如果User类中没有无参数的构造函数,在运行时会抛出异常:`java.io.InvalidClassException` + +更多Externalizable接口使用实例请参考[代码实例][3] + +## ObjectOutput和ObjectInput 接口 + +ObjectInput接口 扩展自 DataInput 接口以包含对象的读操作。 + +> DataInput 接口用于从二进制流中读取字节,并根据所有 Java 基本类型数据进行重构。同时还提供根据 UTF-8 修改版格式的数据重构 String 的工具。 +> +> 对于此接口中的所有数据读取例程来说,如果在读取所需字节数之前已经到达文件末尾 (end of file),则将抛出 EOFException(IOException 的一种)。如果因为到达文件末尾以外的其他原因无法读取字节,则将抛出 IOException 而不是 EOFException。尤其是,在输入流已关闭的情况下,将抛出 IOException。 + +ObjectOutput 扩展 DataOutput 接口以包含对象的写入操作。 + +> DataOutput 接口用于将数据从任意 Java 基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。 +> +> 对于此接口中写入字节的所有方法,如果由于某种原因无法写入某个字节,则抛出 IOException。 + +## ObjectOutputStream类和ObjectInputStream类 + +通过前面的代码片段中我们也能知道,我们一般使用ObjectOutputStream的`writeObject`方法把一个对象进行持久化。再使用ObjectInputStream的`readObject`从持久化存储中把对象读取出来。 + +更多关于ObjectInputStream和ObjectOutputStream的相关知识欢迎阅读我的另外两篇博文:[深入分析Java的序列化与反序列化][4]、[单例与序列化的那些事儿][5] + +## Transient 关键字 + +Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。关于Transient 关键字的拓展知识欢迎阅读[深入分析Java的序列化与反序列化][4] + +## 序列化ID + +虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 `private static final long serialVersionUID`) + +序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。 + +## 参考资料 + +[维基百科][6] + +[理解Java对象序列化][7] + +[Java 序列化的高级认识][8] + +## 推荐阅读 + +[深入分析Java的序列化与反序列化][4] + +[单例与序列化的那些事儿][5] + + [1]: http://www.hollischuang.com/archives/1140#What%20Serializable%20Did + [2]: https://github.com/hollischuang/java-demo/tree/master/src/main/java/com/hollischaung/serialization/SerializableDemos + [3]: https://github.com/hollischuang/java-demo/tree/master/src/main/java/com/hollischaung/serialization/ExternalizableDemos + [4]: http://www.hollischuang.com/archives/1140 + [5]: http://www.hollischuang.com/archives/1144 + [6]: https://zh.wikipedia.org/wiki/序列化 + [7]: http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html + [8]: https://www.ibm.com/developerworks/cn/java/j-lo-serial/ \ No newline at end of file diff --git a/basics/java-basic/serialize-singleton.md b/basics/java-basic/serialize-singleton.md new file mode 100644 index 00000000..c5078c3e --- /dev/null +++ b/basics/java-basic/serialize-singleton.md @@ -0,0 +1,231 @@ +本文将通过实例+阅读Java源码的方式介绍序列化是如何破坏单例模式的,以及如何避免序列化对单例的破坏。 + +> 单例模式,是设计模式中最简单的一种。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。关于单例模式的使用方式,可以阅读[单例模式的七种写法][1] + +但是,单例模式真的能够实现实例的唯一性吗? + +答案是否定的,很多人都知道使用反射可以破坏单例模式,除了反射以外,使用序列化与反序列化也同样会破坏单例。 + +## 序列化对单例的破坏 + +首先来写一个单例的类: + +code 1 + + package com.hollis; + import java.io.Serializable; + /** + * Created by hollis on 16/2/5. + * 使用双重校验锁方式实现单例 + */ + public class Singleton implements Serializable{ + private volatile static Singleton singleton; + private Singleton (){} + public static Singleton getSingleton() { + if (singleton == null) { + synchronized (Singleton.class) { + if (singleton == null) { + singleton = new Singleton(); + } + } + } + return singleton; + } + } + + +接下来是一个测试类: + +code 2 + + package com.hollis; + import java.io.*; + /** + * Created by hollis on 16/2/5. + */ + public class SerializableDemo1 { + //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记 + //Exception直接抛出 + public static void main(String[] args) throws IOException, ClassNotFoundException { + //Write Obj to file + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")); + oos.writeObject(Singleton.getSingleton()); + //Read Obj from file + File file = new File("tempFile"); + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); + Singleton newInstance = (Singleton) ois.readObject(); + //判断是否是同一个对象 + System.out.println(newInstance == Singleton.getSingleton()); + } + } + //false + + +输出结构为false,说明: + +> 通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。 + +这里,在介绍如何解决这个问题之前,我们先来深入分析一下,为什么会这样?在反序列化的过程中到底发生了什么。 + +## ObjectInputStream + +对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,分析一下ObjectInputputStream 的`readObject` 方法执行情况到底是怎样的。 + +为了节省篇幅,这里给出ObjectInputStream的`readObject`的调用栈: + + + +这里看一下重点代码,`readOrdinaryObject`方法的代码片段: code 3 + + private Object readOrdinaryObject(boolean unshared) + throws IOException + { + //此处省略部分代码 + + Object obj; + try { + obj = desc.isInstantiable() ? desc.newInstance() : null; + } catch (Exception ex) { + throw (IOException) new InvalidClassException( + desc.forClass().getName(), + "unable to create instance").initCause(ex); + } + + //此处省略部分代码 + + if (obj != null && + handles.lookupException(passHandle) == null && + desc.hasReadResolveMethod()) + { + Object rep = desc.invokeReadResolve(obj); + if (unshared && rep.getClass().isArray()) { + rep = cloneArray(rep); + } + if (rep != obj) { + handles.setObject(passHandle, obj = rep); + } + } + + return obj; + } + + +code 3 中主要贴出两部分代码。先分析第一部分: + +code 3.1 + + Object obj; + try { + obj = desc.isInstantiable() ? desc.newInstance() : null; + } catch (Exception ex) { + throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex); + } + + +这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的`readObject`返回的对象。 + + + +> `isInstantiable`:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。 +> +> `desc.newInstance`:该方法通过反射的方式调用无参构造方法新建一个对象。 + +所以。到目前为止,也就可以解释,为什么序列化可以破坏单例了? + +> 答:序列化会通过反射调用无参数的构造方法创建一个新的对象。 + +那么,接下来我们再看刚开始留下的问题,如何防止序列化/反序列化破坏单例模式。 + +## 防止序列化破坏单例模式 + +先给出解决方案,然后再具体分析原理: + +只要在Singleton类中定义`readResolve`就可以解决该问题: + +code 4 + + package com.hollis; + import java.io.Serializable; + /** + * Created by hollis on 16/2/5. + * 使用双重校验锁方式实现单例 + */ + public class Singleton implements Serializable{ + private volatile static Singleton singleton; + private Singleton (){} + public static Singleton getSingleton() { + if (singleton == null) { + synchronized (Singleton.class) { + if (singleton == null) { + singleton = new Singleton(); + } + } + } + return singleton; + } + + private Object readResolve() { + return singleton; + } + } + + +还是运行以下测试类: + + package com.hollis; + import java.io.*; + /** + * Created by hollis on 16/2/5. + */ + public class SerializableDemo1 { + //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记 + //Exception直接抛出 + public static void main(String[] args) throws IOException, ClassNotFoundException { + //Write Obj to file + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")); + oos.writeObject(Singleton.getSingleton()); + //Read Obj from file + File file = new File("tempFile"); + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); + Singleton newInstance = (Singleton) ois.readObject(); + //判断是否是同一个对象 + System.out.println(newInstance == Singleton.getSingleton()); + } + } + //true + + +本次输出结果为true。具体原理,我们回过头继续分析code 3中的第二段代码: + +code 3.2 + + if (obj != null && + handles.lookupException(passHandle) == null && + desc.hasReadResolveMethod()) + { + Object rep = desc.invokeReadResolve(obj); + if (unshared && rep.getClass().isArray()) { + rep = cloneArray(rep); + } + if (rep != obj) { + handles.setObject(passHandle, obj = rep); + } + } + + +`hasReadResolveMethod`:如果实现了serializable 或者 externalizable接口的类中包含`readResolve`则返回true + +`invokeReadResolve`:通过反射的方式调用要被反序列化的类的readResolve方法。 + +所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。 + +## 总结 + +在涉及到序列化的场景时,要格外注意他对单例的破坏。 + +## 推荐阅读 + +[深入分析Java的序列化与反序列化][2] + + [1]: http://www.hollischuang.com/archives/205 + [2]: http://www.hollischuang.com/archives/1140 \ No newline at end of file diff --git a/basics/java-basic/serialize.md b/basics/java-basic/serialize.md new file mode 100644 index 00000000..1d34aaff --- /dev/null +++ b/basics/java-basic/serialize.md @@ -0,0 +1,5 @@ +序列化是将对象转换为可传输格式的过程。 是一种数据的持久化手段。一般广泛应用于网络传输,RMI和RPC等场景中。 + +反序列化是序列化的逆操作。 + +序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或XML格式传输。而字节码或XML编码格式可以还原为完全相等的对象。这个相反的过程称为反序列化。 \ No newline at end of file diff --git a/basics/java-basic/spi-principle.md b/basics/java-basic/spi-principle.md new file mode 100644 index 00000000..0880a1e3 --- /dev/null +++ b/basics/java-basic/spi-principle.md @@ -0,0 +1,49 @@ +看ServiceLoader类的签名类的成员变量: + + public final class ServiceLoader implements Iterable{ + private static final String PREFIX = "META-INF/services/"; + + // 代表被加载的类或者接口 + private final Class service; + + // 用于定位,加载和实例化providers的类加载器 + private final ClassLoader loader; + + // 创建ServiceLoader时采用的访问控制上下文 + private final AccessControlContext acc; + + // 缓存providers,按实例化的顺序排列 + private LinkedHashMap providers = new LinkedHashMap<>(); + + // 懒查找迭代器 + private LazyIterator lookupIterator; + + ...... + } + +参考具体源码,梳理了一下,实现的流程如下: + +1 应用程序调用ServiceLoader.load方法 + +ServiceLoader.load方法内先创建一个新的ServiceLoader,并实例化该类中的成员变量,包括: + +loader(ClassLoader类型,类加载器) + +acc(AccessControlContext类型,访问控制器) + +providers(LinkedHashMap类型,用于缓存加载成功的类) + +lookupIterator(实现迭代器功能) + +2 应用程序通过迭代器接口获取对象实例 + +ServiceLoader先判断成员变量providers对象中(LinkedHashMap类型)是否有缓存实例对象,如果有缓存,直接返回。 +如果没有缓存,执行类的装载: + +读取META-INF/services/下的配置文件,获得所有能被实例化的类的名称 + +通过反射方法Class.forName()加载类对象,并用instance()方法将类实例化 + +把实例化后的类缓存到providers对象中(LinkedHashMap类型) + +然后返回实例对象。 \ No newline at end of file diff --git a/mind-map/.DS_Store b/mind-map/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Tue, 23 Jul 2019 16:26:37 +0800 Subject: [PATCH 005/143] Update object-oriented-vs-procedure-oriented.md --- .../object-oriented-vs-procedure-oriented.md | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/basics/java-basic/object-oriented-vs-procedure-oriented.md b/basics/java-basic/object-oriented-vs-procedure-oriented.md index cc99c9a1..39f0555b 100644 --- a/basics/java-basic/object-oriented-vs-procedure-oriented.md +++ b/basics/java-basic/object-oriented-vs-procedure-oriented.md @@ -1,5 +1,7 @@ ### 什么是面向过程? +####概述: 自顶而下的编程模式. + 把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。 就是说,在进行面向过程编程的时候,不需要考虑那么多,上来先定义一个函数,然后使用各种诸如if-else、for-each等方式进行代码执行。 @@ -8,8 +10,28 @@ ### 什么是面向对象? + +####概述: 将事务高度抽象化的编程模式. + 将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。 就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现。 -比如想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。 \ No newline at end of file +比如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。 + +#### 举例说明区别 + +同样一个象棋设计. + +面向对象:创建黑白双方的对象负责演算,棋盘的对象负责画布,规则的对象负责判断,例子可以看出,面向对象更重视不重复造轮子,即创建一次,重复使用. + +面向过程:开始—黑走—棋盘—判断—白走—棋盘—判断—循环。只需要关注每一步怎么实现即可. + +#### 优劣对比 + +面向对象:占用资源相对高,速度相对慢 + +面向过程:占用资源相对低,速度相对快 + + + From c4b0466679e2e80906090f7e226d8f07b77270ca Mon Sep 17 00:00:00 2001 From: miles <46307640+miles-yan@users.noreply.github.com> Date: Wed, 24 Jul 2019 15:48:21 +0800 Subject: [PATCH 006/143] Update platform-independent.md --- basics/java-basic/platform-independent.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/basics/java-basic/platform-independent.md b/basics/java-basic/platform-independent.md index 6f9c64fe..dd5d47c4 100644 --- a/basics/java-basic/platform-independent.md +++ b/basics/java-basic/platform-independent.md @@ -64,7 +64,7 @@ **字节码** -各种不同的平台的虚拟机都使用统一的程序存储格式——字节码(ByteCode)是构成平台无关性的另一个基石。Java虚拟机只与由自己码组成的Class文件进行交互。 +各种不同的平台的虚拟机都使用统一的程序存储格式——字节码(ByteCode)是构成平台无关性的另一个基石。Java虚拟机只与由字节码组成的Class文件进行交互。 我们说Java语言可以Write Once ,Run Anywhere。这里的Write其实指的就是生成Class文件的过程。 @@ -119,4 +119,4 @@ Java的平台无关性是建立在Java虚拟机的平台有关性基础之上的 [6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539297082025.jpg [7]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539303829914.jpg [8]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539319645205.jpg - [9]: https://www.hollischuang.com/archives/2938 \ No newline at end of file + [9]: https://www.hollischuang.com/archives/2938 From 2288b7da1dc1c269a388ee97988361ec6145de61 Mon Sep 17 00:00:00 2001 From: Junyan Qi Date: Sat, 3 Aug 2019 17:51:32 -0400 Subject: [PATCH 007/143] Update principle.md Substitution typo - missing 't' --- basics/java-basic/principle.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/basics/java-basic/principle.md b/basics/java-basic/principle.md index a7ed3044..4d3beff3 100644 --- a/basics/java-basic/principle.md +++ b/basics/java-basic/principle.md @@ -11,7 +11,7 @@ “需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。 -### Liskov替换原则(Liskov-Substituion Principle) +### Liskov替换原则(Liskov-Substitution Principle) 其核心思想是:子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。 Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。 @@ -34,4 +34,4 @@ Liskov替换原则能够保证系统具有良好的拓展性,同时实现基 接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。 分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。 -以上就是5个基本的面向对象设计原则,它们就像面向对象程序设计中的金科玉律,遵守它们可以使我们的代码更加鲜活,易于复用,易于拓展,灵活优雅。不同的设计模式对应不同的需求,而设计原则则代表永恒的灵魂,需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。” \ No newline at end of file +以上就是5个基本的面向对象设计原则,它们就像面向对象程序设计中的金科玉律,遵守它们可以使我们的代码更加鲜活,易于复用,易于拓展,灵活优雅。不同的设计模式对应不同的需求,而设计原则则代表永恒的灵魂,需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。” From e51557a75e732bbe42418f529504598870e7442c Mon Sep 17 00:00:00 2001 From: lostsheep <45201628+lostsheephelpless@users.noreply.github.com> Date: Sat, 10 Aug 2019 14:34:03 +0800 Subject: [PATCH 008/143] Update polymorphism.md --- basics/java-basic/polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basics/java-basic/polymorphism.md b/basics/java-basic/polymorphism.md index e67ec859..2e9e8b1d 100644 --- a/basics/java-basic/polymorphism.md +++ b/basics/java-basic/polymorphism.md @@ -1,4 +1,4 @@ -###什么是多态,多态有什么好处,多态的必要条件是什么、Java中多态的实现方式 +### 什么是多态,多态有什么好处,多态的必要条件是什么、Java中多态的实现方式 多态的概念呢比较简单,就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。 From 79ae02a73a52a12b158baf7b4d571188e6e40aa2 Mon Sep 17 00:00:00 2001 From: "hollis.zhl" Date: Sun, 11 Aug 2019 19:57:13 +0800 Subject: [PATCH 009/143] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E4=B8=8E=E6=B3=9B=E5=9E=8B=E7=9B=B8=E5=85=B3=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++-- basics/java-basic/annotation-in-spring.md | 42 +++++++++++++++++++ basics/java-basic/create-annotation.md | 42 +++++++++++++++++++ basics/java-basic/custom-annotation.md | 3 ++ basics/java-basic/genericity-list-wildcard.md | 1 + basics/java-basic/genericity-list.md | 5 +++ basics/java-basic/meta-annotation.md | 14 +++++++ 7 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 basics/java-basic/annotation-in-spring.md create mode 100644 basics/java-basic/create-annotation.md create mode 100644 basics/java-basic/custom-annotation.md create mode 100644 basics/java-basic/genericity-list-wildcard.md create mode 100644 basics/java-basic/genericity-list.md create mode 100644 basics/java-basic/meta-annotation.md diff --git a/README.md b/README.md index 26ca1051..61d7268f 100644 --- a/README.md +++ b/README.md @@ -175,9 +175,11 @@ apache集合处理工具类的使用、 #### 注解 -元注解、自定义注解、Java中常用注解使用、注解与反射的结合 +[元注解](/basics/java-basic/meta-annotation.md)、[自定义注解](/basics/java-basic/custom-annotation.md)、Java中常用注解使用、注解与反射的结合 -Spring常用注解 +[如何自定义一个注解?](/basics/java-basic/create-annotation.md) + +[Spring常用注解](/basics/java-basic/annotation-in-spring.md) #### JMS @@ -193,9 +195,9 @@ Spring常用注解 限定通配符和非限定通配符、上下界限定符extends 和 super -List和原始类型List之间的区别? +[List和原始类型List之间的区别?](/basics/java-basic/genericity-list.md) -List和List之间的区别是什么? +[List和List之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md) #### 单元测试 diff --git a/basics/java-basic/annotation-in-spring.md b/basics/java-basic/annotation-in-spring.md new file mode 100644 index 00000000..4d93fd1c --- /dev/null +++ b/basics/java-basic/annotation-in-spring.md @@ -0,0 +1,42 @@ +@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。 + +@Scope注解 作用域 + +@Lazy(true) 表示延迟初始化 + +@Service用于标注业务层组件、 + +@Controller用于标注控制层组件@Repository用于标注数据访问组件,即DAO组件。 + +@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。 + +@Scope用于指定scope作用域的(用在类上) + +@PostConstruct用于指定初始化方法(用在方法上) + +@PreDestory用于指定销毁方法(用在方法上) + +@DependsOn:定义Bean初始化及销毁时的顺序 + +@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常 + +@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下: + +@Autowired @Qualifier("personDaoBean") 存在多个实例配合使用 + +@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。 + +@PostConstruct 初始化注解 + +@PreDestroy 摧毁注解 默认 单例 启动就加载 + + +### Spring中的这几个注解有什么区别:@Component 、@Repository、@Service、@Controller + + +1. @Component指的是组件, + +@Controller,@Repository和@Service 注解都被@Component修饰,用于代码中区分表现层,持久层和业务层的组件,代码中组件不好归类时可以使用@Component来标注 + +2. 当前版本只有区分的作用,未来版本可能会添加更丰富的功能 + diff --git a/basics/java-basic/create-annotation.md b/basics/java-basic/create-annotation.md new file mode 100644 index 00000000..5a89135d --- /dev/null +++ b/basics/java-basic/create-annotation.md @@ -0,0 +1,42 @@ +在Java中,类使用class定义,接口使用interface定义,注解和接口的定义差不多,增加了一个@符号,即@interface,代码如下: + + public @interface EnableAuth { + + } + +注解中可以定义成员变量,用于信息的描述,跟接口中方法的定义类似,代码如下: + + public @interface EnableAuth { + String name(); + } + +还可以添加默认值: + + public @interface EnableAuth { + String name() default "猿天地"; + } + +上面的介绍只是完成了自定义注解的第一步,开发中日常使用注解大部分是用在类上,方法上,字段上,示列代码如下: + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @Documented + public @interface EnableAuth { + + } + +Target + +用于指定被修饰的注解修饰哪些程序单元,也就是上面说的类,方法,字段 + +Retention + +用于指定被修饰的注解被保留多长时间,分别SOURCE(注解仅存在于源码中,在class字节码文件中不包含),CLASS(默认的保留策略,注解会在class字节码文件中存在,但运行时无法获取),RUNTIME(注解会在class字节码文件中存在,在运行时可以通过反射获取到)三种类型,如果想要在程序运行过程中通过反射来获取注解的信息需要将Retention设置为RUNTIME + +Documented + +用于指定被修饰的注解类将被javadoc工具提取成文档 + +Inherited + +用于指定被修饰的注解类将具有继承性 \ No newline at end of file diff --git a/basics/java-basic/custom-annotation.md b/basics/java-basic/custom-annotation.md new file mode 100644 index 00000000..650ecc39 --- /dev/null +++ b/basics/java-basic/custom-annotation.md @@ -0,0 +1,3 @@ +除了元注解,都是自定义注解。通过元注解定义出来的注解。 +如我们常用的Override 、Autowire等。 +日常开发中也可以自定义一个注解,这些都是自定义注解。 \ No newline at end of file diff --git a/basics/java-basic/genericity-list-wildcard.md b/basics/java-basic/genericity-list-wildcard.md new file mode 100644 index 00000000..e9b24d8d --- /dev/null +++ b/basics/java-basic/genericity-list-wildcard.md @@ -0,0 +1 @@ +List 是一个未知类型的List,而List 其实是任意类型的List。你可以把List, List赋值给List,却不能把List赋值给 List。 diff --git a/basics/java-basic/genericity-list.md b/basics/java-basic/genericity-list.md new file mode 100644 index 00000000..a6850276 --- /dev/null +++ b/basics/java-basic/genericity-list.md @@ -0,0 +1,5 @@ +原始类型List和带参数类型之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查。 + +通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。 + +它们之间的第二点区别是,你可以把任何带参数的类型传递给原始类型List,但却不能把List传递给接受 List的方法,因为会产生编译错误。 \ No newline at end of file diff --git a/basics/java-basic/meta-annotation.md b/basics/java-basic/meta-annotation.md new file mode 100644 index 00000000..8c0f5823 --- /dev/null +++ b/basics/java-basic/meta-annotation.md @@ -0,0 +1,14 @@ +说简单点,就是 定义其他注解的注解 。 +比如Override这个注解,就不是一个元注解。而是通过元注解定义出来的。 + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + public @interface Override { + } + +这里面的 +@Target +@Retention +就是元注解。 + +元注解有四个:@Target(表示该注解可以用于什么地方)、@Retention(表示再什么级别保存该注解信息)、@Documented(将此注解包含再javadoc中)、@Inherited(允许子类继承父类中的注解)。 \ No newline at end of file From 154987666dc3116b32279720c48e76ec6e37f15a Mon Sep 17 00:00:00 2001 From: "hollis.zhl" Date: Sun, 11 Aug 2019 20:00:19 +0800 Subject: [PATCH 010/143] =?UTF-8?q?=E6=8C=87=E4=BB=A4=E9=87=8D=20=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=20=E6=8C=87=E4=BB=A4=E9=87=8D=E6=8E=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61d7268f..fc0e3d58 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,7 @@ synchronized和原子性、可见性和有序性之间的关系 #### volatile -happens-before、内存屏障、编译器指令重排和CPU指令重 +happens-before、内存屏障、编译器指令重排和CPU指令重排 volatile的实现原理 From 37e140b6bca523323c5c626353fa9b6c3a643df8 Mon Sep 17 00:00:00 2001 From: zzj Date: Fri, 16 Aug 2019 17:27:10 +0800 Subject: [PATCH 011/143] Update enum-serializable.md --- basics/java-basic/enum-serializable.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/basics/java-basic/enum-serializable.md b/basics/java-basic/enum-serializable.md index 7775c584..b588b37e 100644 --- a/basics/java-basic/enum-serializable.md +++ b/basics/java-basic/enum-serializable.md @@ -51,7 +51,7 @@ } -通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。当我们使用`enmu`来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,我们看到这个类中有几个属性和方法。 +通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。当我们使用`enum`来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,我们看到这个类中有几个属性和方法。 我们可以看到: @@ -114,4 +114,4 @@ **3\.枚举实例创建是thread-safe(线程安全的)** -> 我们在深度分析Java的ClassLoader机制(源码级别)Java类的加载、链接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,**创建一个enum类型是线程安全的**。 \ No newline at end of file +> 我们在深度分析Java的ClassLoader机制(源码级别)Java类的加载、链接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,**创建一个enum类型是线程安全的**。 From 78e1051ee7fd3d144b4ce5370f6b774f2dd9aae8 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Sun, 18 Aug 2019 02:06:56 +0800 Subject: [PATCH 012/143] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=9A=84=E8=AF=AD=E6=B3=95=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- basics/java-basic/jvm-language.md | 74 +++++++++++++++++-------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/basics/java-basic/jvm-language.md b/basics/java-basic/jvm-language.md index 66448bcf..c547533e 100644 --- a/basics/java-basic/jvm-language.md +++ b/basics/java-basic/jvm-language.md @@ -18,10 +18,11 @@ Kotlin是一种在Java虚拟机上运行的静态类型编程语言,它也可 #### Hello World In Kotlin - fun main(args: Array) { - println("Hello, world!") - } - +```kotlin +fun main(args: Array) { + println("Hello, world!") +} +``` ### Groovy @@ -31,10 +32,11 @@ Apache的Groovy是Java平台上设计的面向对象编程语言。它的语法 #### Hello World In Groovy - static void main(String[] args) { - println('Hello, world!'); - } - +```groovy +static void main(String[] args) { + println('Hello, world!'); +} +``` ### Scala @@ -44,12 +46,13 @@ Scala经常被我们描述为多模式的编程语言,因为它混合了来自 #### Hello World In Scala - object HelloWorld { - def main(args: Array[String]) { - System.out.println("Hello, world!"); - } - } - +```scala +object HelloWorld { + def main(args: Array[String]) { + System.out.println("Hello, world!"); + } + } +``` ### Jruby @@ -57,8 +60,9 @@ JRuby是用来桥接Java与Ruby的,它是使用比Groovy更加简短的语法 #### Hello World In Jruby - "puts 'Hello, world!'" - +```ruby +puts 'Hello, world!' +``` ### Jython @@ -66,8 +70,9 @@ Jython,是一个用Java语言写的Python解释器。Jython能够用Python语 #### Hello World In Jython - print "Hello, world!" - +```py +print "Hello, world!" +``` ### Fantom @@ -77,11 +82,11 @@ Fantom是与Groovy以及JRuby差不多的一样面向对 象的编程语言, #### Hello World In Fantom - class Hello - { - static Void main() { echo("Hello, world!") } - } - +```fantom +class Hello { + static Void main() { echo("Hello, world!") } +} +``` ### Clojure @@ -91,9 +96,10 @@ Clojure是Lisp编程语言在Java平台上的现代、函数式及动态方言 #### Hello World In Clojure - (defn -main [& args] - (println "Hello, World!")) - +```clojure +(defn -main [& args] + (println "Hello, World!")) +``` ### Rhino @@ -103,8 +109,9 @@ Rhino的特点是为JavaScript加了个壳,然后嵌入到Java中,这样能 #### Hello World In Rhino - print('Hello, world!') - +```js +print('Hello, world!') +``` ### Ceylon @@ -112,13 +119,14 @@ Ceylon是一种面向对象,强烈静态类型的编程语言,强调不变 #### Hello World In Ceylon - shared void run() { - print("Hello, world!"); - } - +```ceylon +shared void run() { + print("Hello, world!"); +} +``` ### 总结 好啦,以上就是目前主流的可以在JVM上面运行的9种语言。加上Java正好10种。如果你是一个Java开发,那么有必要掌握以上9中的一种,这样可以在一些有特殊需求的场景中有更多的选择。推荐在Groovy、Scala、Kotlin中选一个。 - [1]: https://www.hollischuang.com/archives/2322 \ No newline at end of file +[1]: https://www.hollischuang.com/archives/2322 From eab1f765ea97d286713fbdbd91d37340bbb020b2 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Sun, 18 Aug 2019 02:09:32 +0800 Subject: [PATCH 013/143] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=9A=84=E9=AB=98=E5=AE=BD=E8=AE=BE=E7=BD=AE=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E6=AF=94=E4=BE=8B=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- basics/java-basic/jvm-language.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basics/java-basic/jvm-language.md b/basics/java-basic/jvm-language.md index c547533e..fabf7b62 100644 --- a/basics/java-basic/jvm-language.md +++ b/basics/java-basic/jvm-language.md @@ -8,7 +8,7 @@ 经常使用IDE的开发者可能会发现,当我们在Intelij IDEA中,鼠标右键想要创建Java类的时候,IDE还会提示创建其他类型的文件,这就是IDE默认支持的一些可以运行在JVM上面的语言,没有提示的,可以通过插件来支持。 - + 目前,可以直接在JVM上运行的语言有很多,今天介绍其中比较重要的九种。每种语言通过一段『HelloWorld』代码进行演示,看看不同语言的语法有何不同。 From 04229e7efa07df81976c1bd5c74072c423aabe30 Mon Sep 17 00:00:00 2001 From: Dream Date: Mon, 19 Aug 2019 10:32:36 +0800 Subject: [PATCH 014/143] Update platform-independent.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改错别字 --- basics/java-basic/platform-independent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basics/java-basic/platform-independent.md b/basics/java-basic/platform-independent.md index 1ae0c19e..073686a2 100644 --- a/basics/java-basic/platform-independent.md +++ b/basics/java-basic/platform-independent.md @@ -105,7 +105,7 @@ Java的平台无关性是建立在Java虚拟机的平台有关性基础之上的 前面我们提到过。JVM其实并不是和Java文件进行交互的,而是和Class文件,也就是说,其实JVM运行的时候,并不依赖于Java语言。 -时至今日,商业机构和开源机构已经在Java语言之外发展出一大批可以在JVM上运行的语言了,如Groovy、Scala、Jython等。之所以可以支持,就是因为这些语言也可以被编译成字节码(Class文锦啊)。而虚拟机并不关心字节码是有哪种语言编译而来的。详见[牛逼了,教你用九种语言在JVM上输出HelloWorld][9] +时至今日,商业机构和开源机构已经在Java语言之外发展出一大批可以在JVM上运行的语言了,如Groovy、Scala、Jython等。之所以可以支持,就是因为这些语言也可以被编译成字节码(Class文件)。而虚拟机并不关心字节码是有哪种语言编译而来的。详见[牛逼了,教你用九种语言在JVM上输出HelloWorld][9] 参考资料 From 1e614bf90b34b453b04682da9a8684ef97cbff11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=98=9F?= <52741381+wangxing1986@users.noreply.github.com> Date: Tue, 20 Aug 2019 19:04:11 +0900 Subject: [PATCH 015/143] =?UTF-8?q?integer-scope.md=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=8F=82=E8=80=83=E7=B1=BB=E5=AE=B9=E6=94=B9=E4=B8=BA=E8=B6=85?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E5=BD=A2=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把“参考:Java中,为什么byte类型的取值范围为-128~127? - CSDN博客”改为超链接形式 --- basics/java-basic/integer-scope.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/basics/java-basic/integer-scope.md b/basics/java-basic/integer-scope.md index 4461d010..7ed5f264 100644 --- a/basics/java-basic/integer-scope.md +++ b/basics/java-basic/integer-scope.md @@ -5,7 +5,7 @@ Java中的整型主要包含byte、short、int和long这四种,表示的数字 先来看计算中8bit可以表示的数字: 最小值:10000000 (-128)(-2^7) 最大值:01111111(127)(2^7-1) -具体计算方式参考:Java中,为什么byte类型的取值范围为-128~127? - CSDN博客 +具体计算方式参考:[Java中,为什么byte类型的取值范围为-128~127? - CSDN博客](https://blog.csdn.net/qq_23418393/article/details/57421688) 整型的这几个类型中, @@ -27,4 +27,4 @@ Java中的整型主要包含byte、short、int和long这四种,表示的数字 输出结果:`i (2147483647) + j (2147483647) = k (-2)` -这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。所以,在程序中,使用同类型的数据进行运算的时候,一定要注意数据溢出的问题。 \ No newline at end of file +这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。所以,在程序中,使用同类型的数据进行运算的时候,一定要注意数据溢出的问题。 From 25a50167f260c0012a663755fd215040b93d5b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=88=98?= <527734434@qq.com> Date: Tue, 27 Aug 2019 20:26:27 +0800 Subject: [PATCH 016/143] Update constructor.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 勘误 --- basics/java-basic/constructor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basics/java-basic/constructor.md b/basics/java-basic/constructor.md index deb5972d..a0b3c011 100644 --- a/basics/java-basic/constructor.md +++ b/basics/java-basic/constructor.md @@ -2,4 +2,4 @@ 构造函数跟一般的实例方法十分相似;但是与其它方法不同,构造器没有返回类型,不会被继承,且可以有范围修饰符。构造器的函数名称必须和它所属的类的名称相同。 它承担着初始化对象数据成员的任务。 -如果在编写一个可实例化的类时没有专门编写构造函数,多数编程语言会自动生成缺省构造器(默认构造函数)。默认构造函数一般会把成员变量的值初始化为默认值,如int -> 0,Integet -> null。 \ No newline at end of file +如果在编写一个可实例化的类时没有专门编写构造函数,多数编程语言会自动生成缺省构造器(默认构造函数)。默认构造函数一般会把成员变量的值初始化为默认值,如int -> 0,Integer -> null。 From c0989abae9d1392e659b8792399f16493600e58c Mon Sep 17 00:00:00 2001 From: ICEZz <43428866+ICEZz@users.noreply.github.com> Date: Thu, 29 Aug 2019 15:30:52 +0800 Subject: [PATCH 017/143] Update java-pass-by.md Better to understand. --- basics/java-basic/java-pass-by.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basics/java-basic/java-pass-by.md b/basics/java-basic/java-pass-by.md index 0c9044e1..72e98ddd 100644 --- a/basics/java-basic/java-pass-by.md +++ b/basics/java-basic/java-pass-by.md @@ -51,7 +51,7 @@ public static void main(String[] args) { ParamTest pt = new ParamTest(); int i = 10; - pt.pass(10); + pt.pass(i); System.out.println("print in main , i is " + i); } From 1eeb3abddf458bbb203f2e32e2c0deb75dd6d3d7 Mon Sep 17 00:00:00 2001 From: "hollis.zhl" Date: Sun, 1 Dec 2019 21:12:59 +0800 Subject: [PATCH 018/143] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- basics/java-basic/polymorphism.md | 2 +- .../simpledateformat-thread-safe.md | 280 +++++++ basics/java-basic/stack-alloc.md | 166 ++++ basics/java-basic/syntactic-sugar.md | 767 ++++++++++++++++++ basics/jvm/stack-alloc.md | 166 ++++ 6 files changed, 1384 insertions(+), 5 deletions(-) create mode 100644 basics/java-basic/simpledateformat-thread-safe.md create mode 100644 basics/java-basic/stack-alloc.md create mode 100644 basics/java-basic/syntactic-sugar.md create mode 100644 basics/jvm/stack-alloc.md diff --git a/README.md b/README.md index fc0e3d58..3484d7c2 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ finally和return的执行顺序 格林威治时间、CET,UTC,GMT,CST几种常见时间的含义和关系 -SimpleDateFormat的线程安全性问题 +[SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md) Java 8中的时间处理 @@ -253,9 +253,9 @@ URL编解码、Big Endian和Little Endian #### 语法糖 -Java中语法糖原理、解语法糖 +[Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md) -语法糖:switch 支持 String 与枚举、泛型、自动装箱与拆箱、方法变长参数、枚举、内部类、条件编译、 断言、数值字面量、for-each、try-with-resource、Lambda表达式、 +[语法糖:switch 支持 String 与枚举、泛型、自动装箱与拆箱、方法变长参数、枚举、内部类、条件编译、 断言、数值字面量、for-each、try-with-resource、Lambda表达式](/basics/java-basic/syntactic-sugar.md) ### 阅读源代码 @@ -345,7 +345,7 @@ class文件格式、运行时数据区:堆、栈、方法区、直接内存、 堆和栈区别 -Java中的对象一定在堆上分配吗? +[Java中的对象一定在堆上分配吗?](/basics/jvm/stack-alloc.md) #### Java内存模型 diff --git a/basics/java-basic/polymorphism.md b/basics/java-basic/polymorphism.md index e67ec859..2e9e8b1d 100644 --- a/basics/java-basic/polymorphism.md +++ b/basics/java-basic/polymorphism.md @@ -1,4 +1,4 @@ -###什么是多态,多态有什么好处,多态的必要条件是什么、Java中多态的实现方式 +### 什么是多态,多态有什么好处,多态的必要条件是什么、Java中多态的实现方式 多态的概念呢比较简单,就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。 diff --git a/basics/java-basic/simpledateformat-thread-safe.md b/basics/java-basic/simpledateformat-thread-safe.md new file mode 100644 index 00000000..7e8e4244 --- /dev/null +++ b/basics/java-basic/simpledateformat-thread-safe.md @@ -0,0 +1,280 @@ +在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但是不同的方法获取到的时间的格式都不尽相同,这时候就需要一种格式化工具,把时间显示成我们需要的格式。 + +最常用的方法就是使用SimpleDateFormat类。这是一个看上去功能比较简单的类,但是,一旦使用不当也有可能导致很大的问题。 + +在阿里巴巴Java开发手册中,有如下明确规定: + + + +那么,本文就围绕SimpleDateFormat的用法、原理等来深入分析下如何以正确的姿势使用它。 + +### SimpleDateFormat用法 + +SimpleDateFormat是Java提供的一个格式化和解析日期的工具类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。 + +在Java中,可以使用SimpleDateFormat的format方法,将一个Date类型转化成String类型,并且可以指定输出格式。 + + // Date转String + Date data = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String dataStr = sdf.format(data); + System.out.println(dataStr); + + +以上代码,转换的结果是:2018-11-25 13:00:00,日期和时间格式由"日期和时间模式"字符串指定。如果你想要转换成其他格式,只要指定不同的时间模式就行了。 + +在Java中,可以使用SimpleDateFormat的parse方法,将一个String类型转化成Date类型。 + + // String转Data + System.out.println(sdf.parse(dataStr)); + + +#### 日期和时间模式表达方法 + +在使用SimpleDateFormat的时候,需要通过字母来描述时间元素,并组装成想要的日期和时间模式。常用的时间元素和字母的对应表如下: + +![-w717][1] + +模式字母通常是重复的,其数量确定其精确表示。如下表是常用的输出格式的表示方法。 + +![-w535][2] + +#### 输出不同时区的时间 + +时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。 + +世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。 + +现今全球共分为24个时区。由于实用上常常1个国家,或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。例如,中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。 + +由于不同的时区的时间是不一样的,甚至同一个国家的不同城市时间都可能不一样,所以,在Java中想要获取时间的时候,要重点关注一下时区问题。 + +默认情况下,如果不指明,在创建日期的时候,会使用当前计算机所在的时区作为默认时区,这也是为什么我们通过只要使用`new Date()`就可以获取中国的当前时间的原因。 + +那么,如何在Java代码中获取不同时区的时间呢?SimpleDateFormat可以实现这个功能。 + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); + System.out.println(sdf.format(Calendar.getInstance().getTime())); + + +以上代码,转换的结果是: 2018-11-24 21:00:00 。既中国的时间是11月25日的13点,而美国洛杉矶时间比中国北京时间慢了16个小时(这还和冬夏令时有关系,就不详细展开了)。 + +> 如果你感兴趣,你还可以尝试打印一下美国纽约时间(America/New_York)。纽约时间是2018-11-25 00:00:00。纽约时间比中国北京时间早了13个小时。 + +当然,这不是显示其他时区的唯一方法,不过本文主要为了介绍SimpleDateFormat,其他方法暂不介绍了。 + +## SimpleDateFormat线程安全性 + +由于SimpleDateFormat比较常用,而且在一般情况下,一个应用中的时间显示模式都是一样的,所以很多人愿意使用如下方式定义SimpleDateFormat: + + public class Main { + + private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public static void main(String[] args) { + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York")); + System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime())); + } + } + + +**这种定义方式,存在很大的安全隐患。** + +#### 问题重现 + +我们来看一段代码,以下代码使用线程池来执行时间输出。 + + /** * @author Hollis */ + public class Main { + + /** + * 定义一个全局的SimpleDateFormat + */ + private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + /** + * 使用ThreadFactoryBuilder定义一个线程池 + */ + private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() + .setNameFormat("demo-pool-%d").build(); + + private static ExecutorService pool = new ThreadPoolExecutor(5, 200, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); + + /** + * 定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行 + */ + private static CountDownLatch countDownLatch = new CountDownLatch(100); + + public static void main(String[] args) { + //定义一个线程安全的HashSet + Set dates = Collections.synchronizedSet(new HashSet()); + for (int i = 0; i < 100; i++) { + //获取当前时间 + Calendar calendar = Calendar.getInstance(); + int finalI = i; + pool.execute(() -> { + //时间增加 + calendar.add(Calendar.DATE, finalI); + //通过simpleDateFormat把时间转换成字符串 + String dateString = simpleDateFormat.format(calendar.getTime()); + //把字符串放入Set中 + dates.add(dateString); + //countDown + countDownLatch.countDown(); + }); + } + //阻塞,直到countDown数量为0 + countDownLatch.await(); + //输出去重后的时间个数 + System.out.println(dates.size()); + } + } + + +以上代码,其实比较简单,很容易理解。就是循环一百次,每次循环的时候都在当前时间基础上增加一个天数(这个天数随着循环次数而变化),然后把所有日期放入一个**线程安全的**、**带有去重功能**的Set中,然后输出Set中元素个数。 + +> 上面的例子我特意写的稍微复杂了一些,不过我几乎都加了注释。这里面涉及到了[线程池的创建][3]、[CountDownLatch][4]、lambda表达式、线程安全的HashSet等知识。感兴趣的朋友可以逐一了解一下。 + +正常情况下,以上代码输出结果应该是100。但是实际执行结果是一个小于100的数字。 + +原因就是因为SimpleDateFormat作为一个非线程安全的类,被当做了共享变量在多个线程中进行使用,这就出现了线程安全问题。 + +在阿里巴巴Java开发手册的第一章第六节——并发处理中关于这一点也有明确说明: + + + +那么,接下来我们就来看下到底是为什么,以及该如何解决。 + +#### 线程不安全原因 + +通过以上代码,我们发现了在并发场景中使用SimpleDateFormat会有线程安全问题。其实,JDK文档中已经明确表明了SimpleDateFormat不应该用在多线程场景中: + +> Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally. + +那么接下来分析下为什么会出现这种问题,SimpleDateFormat底层到底是怎么实现的? + +我们跟一下SimpleDateFormat类中format方法的实现其实就能发现端倪。 + +![][5] + +SimpleDateFormat中的format方法在执行过程中,会使用一个成员变量calendar来保存时间。这其实就是问题的关键。 + +由于我们在声明SimpleDateFormat的时候,使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量,随之,SimpleDateFormat中的calendar也就可以被多个线程访问到。 + +假设线程1刚刚执行完`calendar.setTime`把时间设置成2018-11-11,还没等执行完,线程2又执行了`calendar.setTime`把时间改成了2018-12-12。这时候线程1继续往下执行,拿到的`calendar.getTime`得到的时间就是线程2改过之后的。 + +除了format方法以外,SimpleDateFormat的parse方法也有同样的问题。 + +所以,不要把SimpleDateFormat作为一个共享变量使用。 + +#### 如何解决 + +前面介绍过了SimpleDateFormat存在的问题以及问题存在的原因,那么有什么办法解决这种问题呢? + +解决方法有很多,这里介绍三个比较常用的方法。 + +**使用局部变量** + + for (int i = 0; i < 100; i++) { + //获取当前时间 + Calendar calendar = Calendar.getInstance(); + int finalI = i; + pool.execute(() -> { + // SimpleDateFormat声明成局部变量 + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + //时间增加 + calendar.add(Calendar.DATE, finalI); + //通过simpleDateFormat把时间转换成字符串 + String dateString = simpleDateFormat.format(calendar.getTime()); + //把字符串放入Set中 + dates.add(dateString); + //countDown + countDownLatch.countDown(); + }); + } + + +SimpleDateFormat变成了局部变量,就不会被多个线程同时访问到了,就避免了线程安全问题。 + +**加同步锁** + +除了改成局部变量以外,还有一种方法大家可能比较熟悉的,就是对于共享变量进行加锁。 + + for (int i = 0; i < 100; i++) { + //获取当前时间 + Calendar calendar = Calendar.getInstance(); + int finalI = i; + pool.execute(() -> { + //加锁 + synchronized (simpleDateFormat) { + //时间增加 + calendar.add(Calendar.DATE, finalI); + //通过simpleDateFormat把时间转换成字符串 + String dateString = simpleDateFormat.format(calendar.getTime()); + //把字符串放入Set中 + dates.add(dateString); + //countDown + countDownLatch.countDown(); + } + }); + } + + +通过加锁,使多个线程排队顺序执行。避免了并发导致的线程安全问题。 + +其实以上代码还有可以改进的地方,就是可以把锁的粒度再设置的小一点,可以只对`simpleDateFormat.format`这一行加锁,这样效率更高一些。 + +**使用ThreadLocal** + +第三种方式,就是使用 ThreadLocal。 ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,那么自然也就不存在竞争问题了。 + + /** + * 使用ThreadLocal定义一个全局的SimpleDateFormat + */ + private static ThreadLocal simpleDateFormatThreadLocal = new ThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + } + }; + + //用法 + String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime()); + + +用 ThreadLocal 来实现其实是有点类似于缓存的思路,每个线程都有一个独享的对象,避免了频繁创建对象,也避免了多线程的竞争。 + +当然,以上代码也有改进空间,就是,其实SimpleDateFormat的创建过程可以改为延迟加载。这里就不详细介绍了。 + +**使用DateTimeFormatter** + +如果是Java8应用,可以使用DateTimeFormatter代替SimpleDateFormat,这是一个线程安全的格式化工具类。就像官方文档中说的,这个类 simple beautiful strong immutable thread-safe。 + + //解析日期 + String dateStr= "2016年10月25日"; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); + LocalDate date= LocalDate.parse(dateStr, formatter); + + //日期转换为字符串 + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm a"); + String nowStr = now .format(format); + System.out.println(nowStr); + + +### 总结 + +本文介绍了SimpleDateFormat的用法,SimpleDateFormat主要可以在String和Date之间做转换,还可以将时间转换成不同时区输出。同时提到在并发场景中SimpleDateFormat是不能保证线程安全的,需要开发者自己来保证其安全性。 + +主要的几个手段有改为局部变量、使用synchronized加锁、使用Threadlocal为每一个线程单独创建一个等。 + +希望通过此文,你可以在使用SimpleDateFormat的时候更加得心应手。 + + [1]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431240092595.jpg + [2]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431240361504.jpg + [3]: https://www.hollischuang.com/archives/2888 + [4]: https://www.hollischuang.com/archives/290 + [5]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431313894397.jpg \ No newline at end of file diff --git a/basics/java-basic/stack-alloc.md b/basics/java-basic/stack-alloc.md new file mode 100644 index 00000000..eb00b219 --- /dev/null +++ b/basics/java-basic/stack-alloc.md @@ -0,0 +1,166 @@ +### JVM内存分配策略 + +关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识: + +1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、堆、程序计数器等。 + +2、我们通常认为JVM中运行时数据存储包括堆和栈。这里所提到的栈其实指的是虚拟机栈,或者说是虚拟栈中的局部变量表。 + +3、栈中存放一些基本类型的变量数据(int/short/long/byte/float/double/Boolean/char)和对象引用。 + +4、堆中主要存放对象,即通过new关键字创建的对象。 + +5、数组引用变量是存放在栈内存中,数组元素是存放在堆内存中。 + +在《深入理解Java虚拟机中》关于Java堆内存有这样一段描述: + +但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。 + +这里只是简单提了一句,并没有深入分析,很多人看到这里由于对JIT、逃逸分析等技术不了解,所以也无法真正理解上面这段话的含义。 + +**PS:这里默认大家都了解什么是JIT,不了解的朋友可以先自行Google了解下,或者加入我的知识星球,阅读那篇球友专享文章。** + +其实,在编译期间,JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做**逃逸分析**。 + +### 逃逸分析 + +逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。 + +逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。 + +例如: + + public static StringBuffer craeteStringBuffer(String s1, String s2) { + StringBuffer sb = new StringBuffer(); + sb.append(s1); + sb.append(s2); + return sb; + } + + +StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。 + +上述代码如果想要StringBuffer sb不逃出方法,可以这样写: + + public static String createStringBuffer(String s1, String s2) { + StringBuffer sb = new StringBuffer(); + sb.append(s1); + sb.append(s2); + return sb.toString(); + } + + +不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。 + +使用逃逸分析,编译器可以对代码做如下优化: + +一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。 + +二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。 + +三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。 + +上面的关于同步省略的内容,我在《[深入理解多线程(五)—— Java虚拟机的锁优化技术][1]》中有介绍过,即锁优化中的锁消除技术,依赖的也是逃逸分析技术。 + +本文,主要来介绍逃逸分析的第二个用途:将堆分配转化为栈分配。 + +> 其实,以上三种优化中,栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点,这里就不展开介绍了。如果大家感兴趣,我后面专门出一篇文章,全面介绍下逃逸分析。 + +在Java代码运行时,通过JVM参数可指定是否开启逃逸分析, `-XX:+DoEscapeAnalysis` : 表示开启逃逸分析 `-XX:-DoEscapeAnalysis` : 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定`-XX:-DoEscapeAnalysis` + +### 对象的栈上内存分配 + +我们知道,在一般情况下,对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。 + +我们来看以下代码: + + public static void main(String[] args) { + long a1 = System.currentTimeMillis(); + for (int i = 0; i < 1000000; i++) { + alloc(); + } + // 查看执行时间 + long a2 = System.currentTimeMillis(); + System.out.println("cost " + (a2 - a1) + " ms"); + // 为了方便查看堆内存中对象个数,线程sleep + try { + Thread.sleep(100000); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + + private static void alloc() { + User user = new User(); + } + + static class User { + + } + + +其实代码内容很简单,就是使用for循环,在代码中创建100万个User对象。 + +**我们在alloc方法中定义了User对象,但是并没有在方法外部引用他。也就是说,这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后,就可以对其内存分配进行优化。** + +我们指定以下JVM参数并运行: + + -Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError + + +在程序打印出 `cost XX ms` 后,代码运行结束之前,我们使用`[jmap][1]`命令,来查看下当前堆内存中有多少个User对象: + + ➜ ~ jps + 2809 StackAllocTest + 2810 Jps + ➜ ~ jmap -histo 2809 + + num #instances #bytes class name + ---------------------------------------------- + 1: 524 87282184 [I + 2: 1000000 16000000 StackAllocTest$User + 3: 6806 2093136 [B + 4: 8006 1320872 [C + 5: 4188 100512 java.lang.String + 6: 581 66304 java.lang.Class + + +从上面的jmap执行结果中我们可以看到,堆中共创建了100万个`StackAllocTest$User`实例。 + +在关闭逃避分析的情况下(-XX:-DoEscapeAnalysis),虽然在alloc方法中创建的User对象并没有逃逸到方法外部,但是还是被分配在堆内存中。也就说,如果没有JIT编译器优化,没有逃逸分析技术,正常情况下就应该是这样的。即所有对象都分配到堆内存中。 + +接下来,我们开启逃逸分析,再来执行下以上代码。 + + -Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError + + +在程序打印出 `cost XX ms` 后,代码运行结束之前,我们使用`jmap`命令,来查看下当前堆内存中有多少个User对象: + + ➜ ~ jps + 709 + 2858 Launcher + 2859 StackAllocTest + 2860 Jps + ➜ ~ jmap -histo 2859 + + num #instances #bytes class name + ---------------------------------------------- + 1: 524 101944280 [I + 2: 6806 2093136 [B + 3: 83619 1337904 StackAllocTest$User + 4: 8006 1320872 [C + 5: 4188 100512 java.lang.String + 6: 581 66304 java.lang.Class + + +从以上打印结果中可以发现,开启了逃逸分析之后(-XX:+DoEscapeAnalysis),在堆内存中只有8万多个`StackAllocTest$User`对象。也就是说在经过JIT优化之后,堆内存中分配的对象数量,从100万降到了8万。 + +> 除了以上通过jmap验证对象个数的方法以外,读者还可以尝试将堆内存调小,然后执行以上代码,根据GC的次数来分析,也能发现,开启了逃逸分析之后,在运行期间,GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配,所以GC次数有了明显的减少。 + +### 总结 + +所以,如果以后再有人问你:是不是所有的对象和数组都会在堆内存分配空间? + +那么你可以告诉他:不一定,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有User对象都没有在堆上分配。 + + [1]: http://www.hollischuang.com/archives/2344 \ No newline at end of file diff --git a/basics/java-basic/syntactic-sugar.md b/basics/java-basic/syntactic-sugar.md new file mode 100644 index 00000000..0a31bf11 --- /dev/null +++ b/basics/java-basic/syntactic-sugar.md @@ -0,0 +1,767 @@ + + +语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。 + +本 Chat 从 Java 编译原理角度,深入字节码及 class 文件,抽丝剥茧,了解 Java 中的语法糖原理及用法,帮助大家在学会如何使用 Java 语法糖的同时,了解这些语法糖背后的原理,主要内容如下: + +什么是语法糖 糖块一 —— switch 支持 String 与枚举 糖块二 —— 泛型与类型擦除 糖块三 —— 自动装箱与拆箱 ...... 糖块十一 —— try-with-resource 糖块十二 —— lambda 表达式 糖衣炮弹 —— 语法糖使用过程中需要注意的点 综合应用 + + +### 语法糖 + +语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。简而言之,语法糖让程序更加简洁,有更高的可读性。 + +> 有意思的是,在编程领域,除了语法糖,还有语法盐和语法糖精的说法,篇幅有限这里不做扩展了。 + +我们所熟知的编程语言中几乎都有语法糖。作者认为,语法糖的多少是评判一个语言够不够牛逼的标准之一。很多人说Java是一个“低糖语言”,其实从Java 7开始Java语言层面上一直在添加各种糖,主要是在“Project Coin”项目下研发。尽管现在Java有人还是认为现在的Java是低糖,未来还会持续向着“高糖”的方向发展。 + +### 解语法糖 + +前面提到过,语法糖的存在主要是方便开发人员使用。但其实,Java虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。 + +说到编译,大家肯定都知道,Java语言中,`javac`命令可以将后缀名为`.java`的源文件编译为后缀名为`.class`的可以运行于Java虚拟机的字节码。如果你去看`com.sun.tools.javac.main.JavaCompiler`的源码,你会发现在`compile()`中有一个步骤就是调用`desugar()`,这个方法就是负责解语法糖的实现的。 + +Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。本文主要来分析下这些语法糖背后的原理。一步一步剥去糖衣,看看其本质。 + +### 糖块一、 switch 支持 String 与枚举 + +前面提到过,从Java 7 开始,Java语言中的语法糖在逐渐丰富,其中一个比较重要的就是Java 7中`switch`开始支持`String`。 + +在开始coding之前先科普下,Java中的`swith`自身原本就支持基本类型。比如`int`、`char`等。对于`int`类型,直接进行数值的比较。对于`char`类型则是比较其ascii码。所以,对于编译器来说,`switch`中其实只能使用整型,任何类型的比较都要转换成整型。比如`byte`。`short`,`char`(ackii码是整型)以及`int`。 + +那么接下来看下`switch`对`String`得支持,有以下代码: + + public class switchDemoString { + public static void main(String[] args) { + String str = "world"; + switch (str) { + case "hello": + System.out.println("hello"); + break; + case "world": + System.out.println("world"); + break; + default: + break; + } + } + } + + +[反编译][1]后内容如下: + + public class switchDemoString + { + public switchDemoString() + { + } + public static void main(String args[]) + { + String str = "world"; + String s; + switch((s = str).hashCode()) + { + default: + break; + case 99162322: + if(s.equals("hello")) + System.out.println("hello"); + break; + case 113318802: + if(s.equals("world")) + System.out.println("world"); + break; + } + } + } + + +看到这个代码,你知道原来**字符串的switch是通过`equals()`和`hashCode()`方法来实现的。**还好`hashCode()`方法返回的是`int`,而不是`long`。 + +> 仔细看下可以发现,进行`switch`的实际是哈希值,然后通过使用`equals`方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。 + +### 糖块二、 泛型 + +我们都知道,很多语言都是支持泛型的,但是很多人不知道的是,不同的编译器对于泛型的处理方式是不同的,通常情况下,一个编译器处理泛型有两种方式:`Code specialization`和`Code sharing`。C++和C#是使用`Code specialization`的处理机制,而Java使用的是`Code sharing`的机制。 + +> Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(`type erasue`)实现的。 + +也就是说,**对于Java虚拟机来说,他根本不认识`Map map`这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖。** + +类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。 2.移除所有的类型参数。 + +以下代码: + + Map map = new HashMap(); + map.put("name", "hollis"); + map.put("wechat", "Hollis"); + map.put("blog", "www.hollischuang.com"); + + +解语法糖之后会变成: + + Map map = new HashMap(); + map.put("name", "hollis"); + map.put("wechat", "Hollis"); + map.put("blog", "www.hollischuang.com"); + + +以下代码: + + public static > A max(Collection xs) { + Iterator xi = xs.iterator(); + A w = xi.next(); + while (xi.hasNext()) { + A x = xi.next(); + if (w.compareTo(x) < 0) + w = x; + } + return w; + } + + +类型擦除后会变成: + + public static Comparable max(Collection xs){ + Iterator xi = xs.iterator(); + Comparable w = (Comparable)xi.next(); + while(xi.hasNext()) + { + Comparable x = (Comparable)xi.next(); + if(w.compareTo(x) < 0) + w = x; + } + return w; + } + + +**虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的`Class`类对象。比如并不存在`List.class`或是`List.class`,而只有`List.class`。** + +### 糖块三、 自动装箱与拆箱 + +自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型byte, short, char, int, long, float, double 和 boolean 对应的封装类为Byte, Short, Character, Integer, Long, Float, Double, Boolean。 + +先来看个自动装箱的代码: + + public static void main(String[] args) { + int i = 10; + Integer n = i; + } + + +反编译后代码如下: + + public static void main(String args[]) + { + int i = 10; + Integer n = Integer.valueOf(i); + } + + +再来看个自动拆箱的代码: + + public static void main(String[] args) { + + Integer i = 10; + int n = i; + } + + +反编译后代码如下: + + public static void main(String args[]) + { + Integer i = Integer.valueOf(10); + int n = i.intValue(); + } + + +从反编译得到内容可以看出,在装箱的时候自动调用的是`Integer`的`valueOf(int)`方法。而在拆箱的时候自动调用的是`Integer`的`intValue`方法。 + +所以,**装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。** + +### 糖块四 、 方法变长参数 + +可变参数(`variable arguments`)是在Java 1.5中引入的一个特性。它允许一个方法把任意数量的值作为参数。 + +看下以下可变参数代码,其中print方法接收可变参数: + + public static void main(String[] args) + { + print("Holis", "公众号:Hollis", "博客:www.hollischuang.com", "QQ:907607222"); + } + + public static void print(String... strs) + { + for (int i = 0; i < strs.length; i++) + { + System.out.println(strs[i]); + } + } + + +反编译后代码: + + public static void main(String args[]) + { + print(new String[] { + "Holis", "\u516C\u4F17\u53F7:Hollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com", "QQ\uFF1A907607222" + }); + } + + public static transient void print(String strs[]) + { + for(int i = 0; i < strs.length; i++) + System.out.println(strs[i]); + + } + + +从反编译后代码可以看出,可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法是传递的实参的个数,然后再把参数值全部放到这个数组当中,然后再把这个数组作为参数传递到被调用的方法中。 + +> PS:反编译后的print方法声明中有一个transient标识,是不是很奇怪?transient不是不可以修饰方法吗?transient不是和序列化有关么?transient在这里的作用是什么?因为这个与本文关系不大,这里不做深入分析了。相了解的同学可以关注我微信公众号或者博客。 + +### 糖块五 、 枚举 + +Java SE5提供了一种新的类型-Java的枚举类型,关键字`enum`可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。 + +要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是`enum`吗?答案很明显不是,`enum`就和`class`一样,只是一个关键字,他并不是一个类,那么枚举是由什么类维护的呢,我们简单的写一个枚举: + + public enum t { + SPRING,SUMMER; + } + + +然后我们使用反编译,看看这段代码到底是怎么实现的,反编译后代码内容如下: + + public final class T extends Enum + { + private T(String s, int i) + { + super(s, i); + } + public static T[] values() + { + T at[]; + int i; + T at1[]; + System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i); + return at1; + } + + public static T valueOf(String s) + { + return (T)Enum.valueOf(demo/T, s); + } + + public static final T SPRING; + public static final T SUMMER; + private static final T ENUM$VALUES[]; + static + { + SPRING = new T("SPRING", 0); + SUMMER = new T("SUMMER", 1); + ENUM$VALUES = (new T[] { + SPRING, SUMMER + }); + } + } + + +通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了`Enum`类的,同时`final`关键字告诉我们,这个类也是不能被继承的。**当我们使用`enmu`来定义一个枚举类型的时候,编译器会自动帮我们创建一个`final`类型的类继承`Enum`类,所以枚举类型不能被继承。** + +### 糖块六 、 内部类 + +内部类又称为嵌套类,可以把内部类理解为外部类的一个普通成员。 + +**内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,`outer.java`里面定义了一个内部类`inner`,一旦编译成功,就会生成两个完全不同的`.class`文件了,分别是`outer.class`和`outer$inner.class`。所以内部类的名字完全可以和它的外部类名字相同。** + + public class OutterClass { + private String userName; + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public static void main(String[] args) { + + } + + class InnerClass{ + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + } + + +以上代码编译后会生成两个class文件:`OutterClass$InnerClass.class` 、`OutterClass.class` 。当我们尝试对`OutterClass.class`文件进行反编译的时候,命令行会打印以下内容:`Parsing OutterClass.class...Parsing inner class OutterClass$InnerClass.class... Generating OutterClass.jad` 。他会把两个文件全部进行反编译,然后一起生成一个`OutterClass.jad`文件。文件内容如下: + + public class OutterClass + { + class InnerClass + { + public String getName() + { + return name; + } + public void setName(String name) + { + this.name = name; + } + private String name; + final OutterClass this$0; + + InnerClass() + { + this.this$0 = OutterClass.this; + super(); + } + } + + public OutterClass() + { + } + public String getUserName() + { + return userName; + } + public void setUserName(String userName){ + this.userName = userName; + } + public static void main(String args1[]) + { + } + private String userName; + } + + +### 糖块七 、条件编译 + +—般情况下,程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑,希望只对其中一部分内容进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译。 + +如在C或CPP中,可以通过预处理语句来实现条件编译。其实在Java中也可实现条件编译。我们先来看一段代码: + + public class ConditionalCompilation { + public static void main(String[] args) { + final boolean DEBUG = true; + if(DEBUG) { + System.out.println("Hello, DEBUG!"); + } + + final boolean ONLINE = false; + + if(ONLINE){ + System.out.println("Hello, ONLINE!"); + } + } + } + + +反编译后代码如下: + + public class ConditionalCompilation + { + + public ConditionalCompilation() + { + } + + public static void main(String args[]) + { + boolean DEBUG = true; + System.out.println("Hello, DEBUG!"); + boolean ONLINE = false; + } + } + + +首先,我们发现,在反编译后的代码中没有`System.out.println("Hello, ONLINE!");`,这其实就是条件编译。当`if(ONLINE)`为false的时候,编译器就没有对其内的代码进行编译。 + +所以,**Java语法的条件编译,是通过判断条件为常量的if语句实现的。其原理也是Java语言的语法糖。根据if判断条件的真假,编译器直接把分支为false的代码块消除。通过该方式实现的条件编译,必须在方法体内实现,而无法在正整个Java类的结构或者类的属性上进行条件编译,这与C/C++的条件编译相比,确实更有局限性。在Java语言设计之初并没有引入条件编译的功能,虽有局限,但是总比没有更强。** + +### 糖块八 、 断言 + +在Java中,`assert`关键字是从JAVA SE 1.4 引入的,为了避免和老版本的Java代码中使用了`assert`关键字导致错误,Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略!),如果要开启断言检查,则需要用开关`-enableassertions`或`-ea`来开启。 + +看一段包含断言的代码: + + public class AssertTest { + public static void main(String args[]) { + int a = 1; + int b = 1; + assert a == b; + System.out.println("公众号:Hollis"); + assert a != b : "Hollis"; + System.out.println("博客:www.hollischuang.com"); + } + } + + +反编译后代码如下: + + public class AssertTest { + public AssertTest() + { + } + public static void main(String args[]) + { + int a = 1; + int b = 1; + if(!$assertionsDisabled && a != b) + throw new AssertionError(); + System.out.println("\u516C\u4F17\u53F7\uFF1AHollis"); + if(!$assertionsDisabled && a == b) + { + throw new AssertionError("Hollis"); + } else + { + System.out.println("\u535A\u5BA2\uFF1Awww.hollischuang.com"); + return; + } + } + + static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus(); + + + } + + +很明显,反编译之后的代码要比我们自己的代码复杂的多。所以,使用了assert这个语法糖我们节省了很多代码。**其实断言的底层实现就是if语言,如果断言结果为true,则什么都不做,程序继续执行,如果断言结果为false,则程序抛出AssertError来打断程序的执行。**`-enableassertions`会设置$assertionsDisabled字段的值。 + +### 糖块九 、 数值字面量 + +在java 7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。 + +比如: + + public class Test { + public static void main(String... args) { + int i = 10_000; + System.out.println(i); + } + } + + +反编译后: + + public class Test + { + public static void main(String[] args) + { + int i = 10000; + System.out.println(i); + } + } + + +反编译后就是把`_`删除了。也就是说 **编译器并不认识在数字字面量中的`_`,需要在编译阶段把他去掉。** + +### 糖块十 、 for-each + +增强for循环(`for-each`)相信大家都不陌生,日常开发经常会用到的,他会比for循环要少写很多代码,那么这个语法糖背后是如何实现的呢? + + public static void main(String... args) { + String[] strs = {"Hollis", "公众号:Hollis", "博客:www.hollischuang.com"}; + for (String s : strs) { + System.out.println(s); + } + List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com"); + for (String s : strList) { + System.out.println(s); + } + } + + +反编译后代码如下: + + public static transient void main(String args[]) + { + String strs[] = { + "Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com" + }; + String args1[] = strs; + int i = args1.length; + for(int j = 0; j < i; j++) + { + String s = args1[j]; + System.out.println(s); + } + + List strList = ImmutableList.of("Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com"); + String s; + for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s)) + s = (String)iterator.next(); + + } + + +代码很简单,**for-each的实现原理其实就是使用了普通的for循环和迭代器。** + +### 糖块十一 、 try-with-resource + +Java里,对于文件操作IO流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。 + +关闭资源的常用方式就是在`finally`块里是释放,即调用`close`方法。比如,我们经常会写这样的代码: + + public static void main(String[] args) { + BufferedReader br = null; + try { + String line; + br = new BufferedReader(new FileReader("d:\\hollischuang.xml")); + while ((line = br.readLine()) != null) { + System.out.println(line); + } + } catch (IOException e) { + // handle exception + } finally { + try { + if (br != null) { + br.close(); + } + } catch (IOException ex) { + // handle exception + } + } + } + + +从Java 7开始,jdk提供了一种更好的方式关闭资源,使用`try-with-resources`语句,改写一下上面的代码,效果如下: + + public static void main(String... args) { + try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) { + String line; + while ((line = br.readLine()) != null) { + System.out.println(line); + } + } catch (IOException e) { + // handle exception + } + } + + +看,这简直是一大福音啊,虽然我之前一般使用`IOUtils`去关闭流,并不会使用在`finally`中写很多代码的方式,但是这种新的语法糖看上去好像优雅很多呢。看下他的背后: + + public static transient void main(String args[]) + { + BufferedReader br; + Throwable throwable; + br = new BufferedReader(new FileReader("d:\\ hollischuang.xml")); + throwable = null; + String line; + try + { + while((line = br.readLine()) != null) + System.out.println(line); + } + catch(Throwable throwable2) + { + throwable = throwable2; + throw throwable2; + } + if(br != null) + if(throwable != null) + try + { + br.close(); + } + catch(Throwable throwable1) + { + throwable.addSuppressed(throwable1); + } + else + br.close(); + break MISSING_BLOCK_LABEL_113; + Exception exception; + exception; + if(br != null) + if(throwable != null) + try + { + br.close(); + } + catch(Throwable throwable3) + { + throwable.addSuppressed(throwable3); + } + else + br.close(); + throw exception; + IOException ioexception; + ioexception; + } + } + + +**其实背后的原理也很简单,那些我们没有做的关闭资源的操作,编译器都帮我们做了。所以,再次印证了,语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言。** + +### 糖块十二、Lambda表达式 + +关于lambda表达式,有人可能会有质疑,因为网上有人说他并不是语法糖。其实我想纠正下这个说法。**Labmda表达式不是匿名内部类的语法糖,但是他也是一个语法糖。实现方式其实是依赖了几个JVM底层提供的lambda相关api。** + +先来看一个简单的lambda表达式。遍历一个list: + + public static void main(String... args) { + List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com"); + + strList.forEach( s -> { System.out.println(s); } ); + } + + +为啥说他并不是内部类的语法糖呢,前面讲内部类我们说过,内部类在编译之后会有两个class文件,但是,包含lambda表达式的类编译后只有一个文件。 + +反编译后代码如下: + + public static /* varargs */ void main(String ... args) { + ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com"); + strList.forEach((Consumer)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)()); + } + + private static /* synthetic */ void lambda$main$0(String s) { + System.out.println(s); + } + + +可以看到,在`forEach`方法中,其实是调用了`java.lang.invoke.LambdaMetafactory#metafactory`方法,该方法的第四个参数implMethod指定了方法实现。可以看到这里其实是调用了一个`lambda$main$0`方法进行了输出。 + +再来看一个稍微复杂一点的,先对List进行过滤,然后再输出: + + public static void main(String... args) { + List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com"); + + List HollisList = strList.stream().filter(string -> string.contains("Hollis")).collect(Collectors.toList()); + + HollisList.forEach( s -> { System.out.println(s); } ); + } + + +反编译后代码如下: + + public static /* varargs */ void main(String ... args) { + ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com"); + List HollisList = strList.stream().filter((Predicate)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList()); + HollisList.forEach((Consumer)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)()); + } + + private static /* synthetic */ void lambda$main$1(Object s) { + System.out.println(s); + } + + private static /* synthetic */ boolean lambda$main$0(String string) { + return string.contains("Hollis"); + } + + +两个lambda表达式分别调用了`lambda$main$1`和`lambda$main$0`两个方法。 + +**所以,lambda表达式的实现其实是依赖了一些底层的api,在编译阶段,编译器会把lambda表达式进行解糖,转换成调用内部api的方式。** + +### 可能遇到的坑 + +#### 泛型 + +**一、当泛型遇到重载** public class GenericTypes { + + public static void method(List list) { + System.out.println("invoke method(List list)"); + } + + public static void method(List list) { + System.out.println("invoke method(List list)"); + } + } + + +上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List另一个是List ,但是,这段代码是编译通不过的。因为我们前面讲过,参数List和List编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。 + +**二、当泛型遇到catch** 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型`MyException`和`MyException`的 + +**三、当泛型内包含静态变量** + + public class StaticTest{ + public static void main(String[] args){ + GT gti = new GT(); + gti.var=1; + GT gts = new GT(); + gts.var=2; + System.out.println(gti.var); + } + } + class GT{ + public static int var=0; + public void nothing(T x){} + } + + +以上代码输出结果为:2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。 + +#### 自动装箱与拆箱 + +**对象相等比较** + +public class BoxingTest { + + public static void main(String[] args) { + Integer a = 1000; + Integer b = 1000; + Integer c = 100; + Integer d = 100; + System.out.println("a == b is " + (a == b)); + System.out.println(("c == d is " + (c == d))); + } + + +输出结果: + + a == b is false + c == d is true + + +在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。 + +> 适用于整数值区间-128 至 +127。 +> +> 只适用于自动装箱。使用构造函数创建对象不适用。 + +#### 增强for循环 + +**ConcurrentModificationException** + + for (Student stu : students) { + if (stu.getId() == 2) + students.remove(stu); + } + + +会抛出`ConcurrentModificationException`异常。 + +Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出`java.util.ConcurrentModificationException`异常。 + +所以 `Iterator` 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 `Iterator` 本身的方法`remove()`来删除对象,`Iterator.remove()` 方法会在删除当前迭代对象的同时维护索引的一致性。 + +### 总结 + +前面介绍了12种Java中常用的语法糖。所谓语法糖就是提供给开发人员便于开发的一种语法而已。但是这种语法只有开发人员认识。要想被执行,需要进行解糖,即转成JVM认识的语法。当我们把语法糖解糖之后,你就会发现其实我们日常使用的这些方便的语法,其实都是一些其他更简单的语法构成的。 + +有了这些语法糖,我们在日常开发的时候可以大大提升效率,但是同时也要避免过渡使用。使用之前最好了解下原理,避免掉坑。 + +参考资料: [Java的反编译][1] [Java中的Switch对整型、字符型、字符串型的具体实现细节][2] [深度分析Java的枚举类型—-枚举的线程安全性及序列化问题][3] [Java的枚举类型用法介绍][4] [Java中的增强for循环(for each)的实现原理与坑][5] [Java中泛型的理解][6] [Java中整型的缓存机制][7] [Java中的可变参数][8] + + [1]: http://www.hollischuang.com/archives/58 + [2]: http://www.hollischuang.com/archives/61 + [3]: http://www.hollischuang.com/archives/197 + [4]: http://www.hollischuang.com/archives/195 + [5]: http://www.hollischuang.com/archives/1776 + [6]: http://www.hollischuang.com/archives/230 + [7]: http://www.hollischuang.com/archives/1174 + [8]: http://www.hollischuang.com/archives/1271 \ No newline at end of file diff --git a/basics/jvm/stack-alloc.md b/basics/jvm/stack-alloc.md new file mode 100644 index 00000000..eb00b219 --- /dev/null +++ b/basics/jvm/stack-alloc.md @@ -0,0 +1,166 @@ +### JVM内存分配策略 + +关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识: + +1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、堆、程序计数器等。 + +2、我们通常认为JVM中运行时数据存储包括堆和栈。这里所提到的栈其实指的是虚拟机栈,或者说是虚拟栈中的局部变量表。 + +3、栈中存放一些基本类型的变量数据(int/short/long/byte/float/double/Boolean/char)和对象引用。 + +4、堆中主要存放对象,即通过new关键字创建的对象。 + +5、数组引用变量是存放在栈内存中,数组元素是存放在堆内存中。 + +在《深入理解Java虚拟机中》关于Java堆内存有这样一段描述: + +但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。 + +这里只是简单提了一句,并没有深入分析,很多人看到这里由于对JIT、逃逸分析等技术不了解,所以也无法真正理解上面这段话的含义。 + +**PS:这里默认大家都了解什么是JIT,不了解的朋友可以先自行Google了解下,或者加入我的知识星球,阅读那篇球友专享文章。** + +其实,在编译期间,JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做**逃逸分析**。 + +### 逃逸分析 + +逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。 + +逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。 + +例如: + + public static StringBuffer craeteStringBuffer(String s1, String s2) { + StringBuffer sb = new StringBuffer(); + sb.append(s1); + sb.append(s2); + return sb; + } + + +StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。 + +上述代码如果想要StringBuffer sb不逃出方法,可以这样写: + + public static String createStringBuffer(String s1, String s2) { + StringBuffer sb = new StringBuffer(); + sb.append(s1); + sb.append(s2); + return sb.toString(); + } + + +不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。 + +使用逃逸分析,编译器可以对代码做如下优化: + +一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。 + +二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。 + +三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。 + +上面的关于同步省略的内容,我在《[深入理解多线程(五)—— Java虚拟机的锁优化技术][1]》中有介绍过,即锁优化中的锁消除技术,依赖的也是逃逸分析技术。 + +本文,主要来介绍逃逸分析的第二个用途:将堆分配转化为栈分配。 + +> 其实,以上三种优化中,栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点,这里就不展开介绍了。如果大家感兴趣,我后面专门出一篇文章,全面介绍下逃逸分析。 + +在Java代码运行时,通过JVM参数可指定是否开启逃逸分析, `-XX:+DoEscapeAnalysis` : 表示开启逃逸分析 `-XX:-DoEscapeAnalysis` : 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定`-XX:-DoEscapeAnalysis` + +### 对象的栈上内存分配 + +我们知道,在一般情况下,对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。 + +我们来看以下代码: + + public static void main(String[] args) { + long a1 = System.currentTimeMillis(); + for (int i = 0; i < 1000000; i++) { + alloc(); + } + // 查看执行时间 + long a2 = System.currentTimeMillis(); + System.out.println("cost " + (a2 - a1) + " ms"); + // 为了方便查看堆内存中对象个数,线程sleep + try { + Thread.sleep(100000); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + + private static void alloc() { + User user = new User(); + } + + static class User { + + } + + +其实代码内容很简单,就是使用for循环,在代码中创建100万个User对象。 + +**我们在alloc方法中定义了User对象,但是并没有在方法外部引用他。也就是说,这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后,就可以对其内存分配进行优化。** + +我们指定以下JVM参数并运行: + + -Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError + + +在程序打印出 `cost XX ms` 后,代码运行结束之前,我们使用`[jmap][1]`命令,来查看下当前堆内存中有多少个User对象: + + ➜ ~ jps + 2809 StackAllocTest + 2810 Jps + ➜ ~ jmap -histo 2809 + + num #instances #bytes class name + ---------------------------------------------- + 1: 524 87282184 [I + 2: 1000000 16000000 StackAllocTest$User + 3: 6806 2093136 [B + 4: 8006 1320872 [C + 5: 4188 100512 java.lang.String + 6: 581 66304 java.lang.Class + + +从上面的jmap执行结果中我们可以看到,堆中共创建了100万个`StackAllocTest$User`实例。 + +在关闭逃避分析的情况下(-XX:-DoEscapeAnalysis),虽然在alloc方法中创建的User对象并没有逃逸到方法外部,但是还是被分配在堆内存中。也就说,如果没有JIT编译器优化,没有逃逸分析技术,正常情况下就应该是这样的。即所有对象都分配到堆内存中。 + +接下来,我们开启逃逸分析,再来执行下以上代码。 + + -Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError + + +在程序打印出 `cost XX ms` 后,代码运行结束之前,我们使用`jmap`命令,来查看下当前堆内存中有多少个User对象: + + ➜ ~ jps + 709 + 2858 Launcher + 2859 StackAllocTest + 2860 Jps + ➜ ~ jmap -histo 2859 + + num #instances #bytes class name + ---------------------------------------------- + 1: 524 101944280 [I + 2: 6806 2093136 [B + 3: 83619 1337904 StackAllocTest$User + 4: 8006 1320872 [C + 5: 4188 100512 java.lang.String + 6: 581 66304 java.lang.Class + + +从以上打印结果中可以发现,开启了逃逸分析之后(-XX:+DoEscapeAnalysis),在堆内存中只有8万多个`StackAllocTest$User`对象。也就是说在经过JIT优化之后,堆内存中分配的对象数量,从100万降到了8万。 + +> 除了以上通过jmap验证对象个数的方法以外,读者还可以尝试将堆内存调小,然后执行以上代码,根据GC的次数来分析,也能发现,开启了逃逸分析之后,在运行期间,GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配,所以GC次数有了明显的减少。 + +### 总结 + +所以,如果以后再有人问你:是不是所有的对象和数组都会在堆内存分配空间? + +那么你可以告诉他:不一定,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有User对象都没有在堆上分配。 + + [1]: http://www.hollischuang.com/archives/2344 \ No newline at end of file From 6e5b964f3af653d256adbdd7157d9c0bec5185bf Mon Sep 17 00:00:00 2001 From: Focus Date: Mon, 2 Dec 2019 17:11:43 +0800 Subject: [PATCH 019/143] Bug fix delete duplicate word. --- basics/java-basic/principle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basics/java-basic/principle.md b/basics/java-basic/principle.md index 4d3beff3..b21c8c4e 100644 --- a/basics/java-basic/principle.md +++ b/basics/java-basic/principle.md @@ -7,7 +7,7 @@ ### 开放封闭原则(Open-Closed principle) 其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。开放封闭原则主要体现在两个方面1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。 -实现开开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。 +实现开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。 “需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。 From 7f4638efec22ecd7e1abc0ae29ec6b921448eb17 Mon Sep 17 00:00:00 2001 From: dearsn Date: Fri, 28 Feb 2020 15:43:55 +0800 Subject: [PATCH 020/143] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8B=BC=E5=86=99?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改拼写错误 --- basics/java-basic/syntactic-sugar.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/basics/java-basic/syntactic-sugar.md b/basics/java-basic/syntactic-sugar.md index 0a31bf11..f1dc7f5b 100644 --- a/basics/java-basic/syntactic-sugar.md +++ b/basics/java-basic/syntactic-sugar.md @@ -27,7 +27,7 @@ Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自 前面提到过,从Java 7 开始,Java语言中的语法糖在逐渐丰富,其中一个比较重要的就是Java 7中`switch`开始支持`String`。 -在开始coding之前先科普下,Java中的`swith`自身原本就支持基本类型。比如`int`、`char`等。对于`int`类型,直接进行数值的比较。对于`char`类型则是比较其ascii码。所以,对于编译器来说,`switch`中其实只能使用整型,任何类型的比较都要转换成整型。比如`byte`。`short`,`char`(ackii码是整型)以及`int`。 +在开始coding之前先科普下,Java中的`switch`自身原本就支持基本类型。比如`int`、`char`等。对于`int`类型,直接进行数值的比较。对于`char`类型则是比较其ascii码。所以,对于编译器来说,`switch`中其实只能使用整型,任何类型的比较都要转换成整型。比如`byte`。`short`,`char`(ackii码是整型)以及`int`。 那么接下来看下`switch`对`String`得支持,有以下代码: @@ -764,4 +764,4 @@ Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 I [5]: http://www.hollischuang.com/archives/1776 [6]: http://www.hollischuang.com/archives/230 [7]: http://www.hollischuang.com/archives/1174 - [8]: http://www.hollischuang.com/archives/1271 \ No newline at end of file + [8]: http://www.hollischuang.com/archives/1271 From f0919b01bc8565fe80accfeba7a9cc9869e5e922 Mon Sep 17 00:00:00 2001 From: "hollis.zhl" Date: Sat, 29 Feb 2020 16:47:58 +0800 Subject: [PATCH 021/143] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E4=B8=8E=E7=BC=96=E7=A0=81=E7=9B=B8=E5=85=B3=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++++++++------ basics/java-basic/CET-UTC-GMT-CST.md | 20 +++++++++++++++++++ basics/java-basic/GMT.md | 7 +++++++ .../StandardTime-vs-daylightSavingTime.md | 9 +++++++++ basics/java-basic/UNICODE.md | 11 ++++++++++ basics/java-basic/UTF8-UTF16-UTF32.md | 9 +++++++++ .../java-basic/big-endian-vs-little-endian.md | 17 ++++++++++++++++ basics/java-basic/gbk-gb2312-gb18030.md | 15 ++++++++++++++ basics/java-basic/time-zone.md | 9 +++++++++ basics/java-basic/timestamp.md | 3 +++ basics/java-basic/url-encode.md | 9 +++++++++ basics/java-basic/why-gbk.md | 0 basics/java-basic/why-utf8.md | 17 ++++++++++++++++ 13 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 basics/java-basic/CET-UTC-GMT-CST.md create mode 100644 basics/java-basic/GMT.md create mode 100644 basics/java-basic/StandardTime-vs-daylightSavingTime.md create mode 100644 basics/java-basic/UNICODE.md create mode 100644 basics/java-basic/UTF8-UTF16-UTF32.md create mode 100644 basics/java-basic/big-endian-vs-little-endian.md create mode 100644 basics/java-basic/gbk-gb2312-gb18030.md create mode 100644 basics/java-basic/time-zone.md create mode 100644 basics/java-basic/timestamp.md create mode 100644 basics/java-basic/url-encode.md create mode 100644 basics/java-basic/why-gbk.md create mode 100644 basics/java-basic/why-utf8.md diff --git a/README.md b/README.md index 3484d7c2..93fdff83 100644 --- a/README.md +++ b/README.md @@ -229,9 +229,9 @@ finally和return的执行顺序 #### 时间处理 -时区、冬令时和夏令时、时间戳、Java中时间API +[时区](/basics/java-basic/time-zone.md)、[冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md)、[时间戳](/basics/java-basic/timestamp.md)、Java中时间API -格林威治时间、CET,UTC,GMT,CST几种常见时间的含义和关系 +[格林威治时间](/basics/java-basic/GMT.md)、[CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md) [SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md) @@ -241,13 +241,17 @@ Java 8中的时间处理 #### 编码方式 -Unicode、有了Unicode为啥还需要UTF-8 +什么是ASCII? -GBK、GB2312、GB18030之间的区别 +[Unicode](/basics/java-basic/UNICODE.md)、[有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md) -UTF8、UTF16、UTF32区别 +[UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md) -URL编解码、Big Endian和Little Endian +有了UTF8为什么还需要GBK? + +[GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md) + +[URL编解码](/basics/java-basic/url-encode.md)、[Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md) 如何解决乱码问题 diff --git a/basics/java-basic/CET-UTC-GMT-CST.md b/basics/java-basic/CET-UTC-GMT-CST.md new file mode 100644 index 00000000..37e0402b --- /dev/null +++ b/basics/java-basic/CET-UTC-GMT-CST.md @@ -0,0 +1,20 @@ +### CET +欧洲中部时间(英語:Central European Time,CET)是比世界标准时间(UTC)早一个小时的时区名称之一。它被大部分欧洲国家和部分北非国家采用。冬季时间为UTC+1,夏季欧洲夏令时为UTC+2。 + + +### UTC +协调世界时,又称世界标准时间或世界协调时间,简称UTC,从英文“Coordinated Universal Time”/法文“Temps Universel Cordonné”而来。台湾采用CNS 7648的《资料元及交换格式–资讯交换–日期及时间的表示法》(与ISO 8601类似)称之为世界统一时间。中国大陆采用ISO 8601-1988的国标《数据元和交换格式信息交换日期和时间表示法》(GB/T 7408)中称之为国际协调时间。协调世界时是以原子时秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统。 + +### GMT +格林尼治标准时间(旧译格林尼治平均时间或格林威治标准时间;英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。 + + +### CST +北京时间,China Standard Time,又名中国标准时间,是中国的标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8,与中华民国国家标准时间(旧称“中原标准时间”)、香港时间和澳门时间和相同。當格林威治時間為凌晨0:00時,中國標準時間剛好為上午8:00。 + + +### 关系 + +CET=UTC/GMT + 1小时 +CST=UTC/GMT +8 小时 +CST=CET+9 \ No newline at end of file diff --git a/basics/java-basic/GMT.md b/basics/java-basic/GMT.md new file mode 100644 index 00000000..fcd97dd8 --- /dev/null +++ b/basics/java-basic/GMT.md @@ -0,0 +1,7 @@ +格林尼治平时(英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。 + +自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。 + +格林尼治平时的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代。 + +一般使用GMT+8表示中国的时间,是因为中国位于东八区,时间上比格林威治时间快8个小时。 \ No newline at end of file diff --git a/basics/java-basic/StandardTime-vs-daylightSavingTime.md b/basics/java-basic/StandardTime-vs-daylightSavingTime.md new file mode 100644 index 00000000..225eb96d --- /dev/null +++ b/basics/java-basic/StandardTime-vs-daylightSavingTime.md @@ -0,0 +1,9 @@ +夏令时、冬令时的出现,是为了充分利用夏天的日照,所以时钟要往前拨快一小时,冬天再把表往回拨一小时。其中夏令时从3月第二个周日持续到11月第一个周日。 + +冬令时: +北京和洛杉矶时差:16 +北京和纽约时差:13 + +夏令时 +北京和洛杉矶时差:12 +北京和纽约时差:15 \ No newline at end of file diff --git a/basics/java-basic/UNICODE.md b/basics/java-basic/UNICODE.md new file mode 100644 index 00000000..7d057389 --- /dev/null +++ b/basics/java-basic/UNICODE.md @@ -0,0 +1,11 @@ +ASCII码,只有256个字符,美国人倒是没啥问题了,他们用到的字符几乎都包括了,但是世界上不只有美国程序员啊,所以需要一种更加全面的字符集。 + +Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得计算机可以用更为简单的方式来呈现和处理文字。 + +Unicode伴随着通用字符集的标准而发展,同时也以书本的形式对外发表。Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2018年6月5日公布的11.0.0,已经收录超过13万个字符(第十万个字符在2005年获采纳)。Unicode涵盖的数据除了视觉上的字形、编码方法、标准的字符编码外,还包含了字符特性,如大小写字母。 + +Unicode发展由非营利机构统一码联盟负责,该机构致力于让Unicode方案取代既有的字符编码方案。因为既有的方案往往空间非常有限,亦不适用于多语环境。 + +Unicode备受认可,并广泛地应用于计算机软件的国际化与本地化过程。有很多新科技,如可扩展置标语言(Extensible Markup Language,简称:XML)、Java编程语言以及现代的操作系统,都采用Unicode编码。 + +Unicode可以表示中文。 \ No newline at end of file diff --git a/basics/java-basic/UTF8-UTF16-UTF32.md b/basics/java-basic/UTF8-UTF16-UTF32.md new file mode 100644 index 00000000..85ad8567 --- /dev/null +++ b/basics/java-basic/UTF8-UTF16-UTF32.md @@ -0,0 +1,9 @@ +Unicode 是容纳世界所有文字符号的国际标准编码,使用四个字节为每个字符编码。 + +UTF 是英文 Unicode Transformation Format 的缩写,意为把 Unicode 字符转换为某种格式。UTF 系列编码方案(UTF-8、UTF-16、UTF-32)均是由 Unicode 编码方案衍变而来,以适应不同的数据存储或传递,它们都可以完全表示 Unicode 标准中的所有字符。目前,这些衍变方案中 UTF-8 被广泛使用,而 UTF-16 和 UTF-32 则很少被使用。 + +UTF-8 使用一至四个字节为每个字符编码,其中大部分汉字采用三个字节编码,少量不常用汉字采用四个字节编码。因为 UTF-8 是可变长度的编码方式,相对于 Unicode 编码可以减少存储占用的空间,所以被广泛使用。 + +UTF-16 使用二或四个字节为每个字符编码,其中大部分汉字采用两个字节编码,少量不常用汉字采用四个字节编码。UTF-16 编码有大尾序和小尾序之别,即 UTF-16BE 和 UTF-16LE,在编码前会放置一个 U+FEFF 或 U+FFFE(UTF-16BE 以 FEFF 代表,UTF-16LE 以 FFFE 代表),其中 U+FEFF 字符在 Unicode 中代表的意义是 ZERO WIDTH NO-BREAK SPACE,顾名思义,它是个没有宽度也没有断字的空白。 + +UTF-32 使用四个字节为每个字符编码,使得 UTF-32 占用空间通常会是其它编码的二到四倍。UTF-32 与 UTF-16 一样有大尾序和小尾序之别,编码前会放置 U+0000FEFF 或 U+0000FFFE 以区分。 \ No newline at end of file diff --git a/basics/java-basic/big-endian-vs-little-endian.md b/basics/java-basic/big-endian-vs-little-endian.md new file mode 100644 index 00000000..bd770559 --- /dev/null +++ b/basics/java-basic/big-endian-vs-little-endian.md @@ -0,0 +1,17 @@ +字节序,也就是字节的顺序,指的是多字节的数据在内存中的存放顺序。 + +在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如:如果C/C++中的一个int型变量 a 的起始地址是&a = 0x100,那么 a 的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。 + +根据整数 a 在连续的 4 byte 内存中的存储顺序,字节序被分为大端序(Big Endian) 与 小端序(Little Endian)两类。 + +Big Endian 是指低地址端 存放 高位字节。 +Little Endian 是指低地址端 存放 低位字节。 + +比如数字0x12345678在两种不同字节序CPU中的存储顺序: + +Big Endian:12345678 +Little Endian : 78563412 + +Java采用Big Endian来存储数据、C\C++采用Little Endian。在网络传输一般采用的网络字节序是BIG-ENDIAN。和Java是一致的。 + +所以在用C/C++写通信程序时,在发送数据前务必把整型和短整型的数据进行从主机字节序到网络字节序的转换,而接收数据后对于整型和短整型数据则必须实现从网络字节序到主机字节序的转换。如果通信的一方是JAVA程序、一方是C/C++程序时,则需要在C/C++一侧使用以上几个方法进行字节序的转换,而JAVA一侧,则不需要做任何处理,因为JAVA字节序与网络字节序都是BIG-ENDIAN,只要C/C++一侧能正确进行转换即可(发送前从主机序到网络序,接收时反变换)。如果通信的双方都是JAVA,则根本不用考虑字节序的问题了。 \ No newline at end of file diff --git a/basics/java-basic/gbk-gb2312-gb18030.md b/basics/java-basic/gbk-gb2312-gb18030.md new file mode 100644 index 00000000..0c68a5f3 --- /dev/null +++ b/basics/java-basic/gbk-gb2312-gb18030.md @@ -0,0 +1,15 @@ +三者都是支持中文字符的编码方式,最常用的是GBK。 + +以下内容来自CSDN,介绍的比较详细。 + +GB2312(1980年):16位字符集,收录有6763个简体汉字,682个符号,共7445个字符; +优点:适用于简体中文环境,属于中国国家标准,通行于大陆,新加坡等地也使用此编码; +缺点:不兼容繁体中文,其汉字集合过少。 + +GBK(1995年):16位字符集,收录有21003个汉字,883个符号,共21886个字符; +优点:适用于简繁中文共存的环境,为简体Windows所使用(代码页cp936),向下完全兼容gb2312,向上支持 ISO-10646 国际标准 ;所有字符都可以一对一映射到unicode2.0上; +缺点:不属于官方标准,和big5之间需要转换;很多搜索引擎都不能很好地支持GBK汉字。 + +GB18030(2000年):32位字符集;收录了27484个汉字,同时收录了藏文、蒙文、维吾尔文等主要的少数民族文字。 +优点:可以收录所有你能想到的文字和符号,属于中国最新的国家标准; +缺点:目前支持它的软件较少。 \ No newline at end of file diff --git a/basics/java-basic/time-zone.md b/basics/java-basic/time-zone.md new file mode 100644 index 00000000..61e2e747 --- /dev/null +++ b/basics/java-basic/time-zone.md @@ -0,0 +1,9 @@ +时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。 + +世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。 + + +为了照顾到各地区的使用方便,又使其他地方的人容易将本地的时间换算到别的地方时间上去。有关国际会议决定将地球表面按经线从东到西,划成一个个区域,并且规定相邻区域的时间相差1小时。在同一区域内的东端和西端的人看到太阳升起的时间最多相差不过1小时。当人们跨过一个区域,就将自己的时钟校正1小时(向西减1小时,向东加1小时),跨过几个区域就加或减几小时。这样使用起来就很方便。现今全球共分为24个时区。由于实用上常常1个国家,或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。例如,中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。 + +北京时间比洛杉矶时间早15或者16个小时。具体和时令有关。 +北京时间比纽约时间早12或者13个小时。具体和时令有关。 \ No newline at end of file diff --git a/basics/java-basic/timestamp.md b/basics/java-basic/timestamp.md new file mode 100644 index 00000000..28166c78 --- /dev/null +++ b/basics/java-basic/timestamp.md @@ -0,0 +1,3 @@ +时间戳(timestamp),一个能表示一份数据在某个特定时间之前已经存在的、 完整的、 可验证的数据,通常是一个字符序列,唯一地标识某一刻的时间。 + +时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。通俗的讲, 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。 \ No newline at end of file diff --git a/basics/java-basic/url-encode.md b/basics/java-basic/url-encode.md new file mode 100644 index 00000000..2cc68f03 --- /dev/null +++ b/basics/java-basic/url-encode.md @@ -0,0 +1,9 @@ +网络标准RFC 1738做了硬性规定 :只有字母和数字[0-9a-zA-Z]、一些特殊符号“$-_.+!*'(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL; + +除此以外的字符是无法在URL中展示的,所以,遇到这种字符,如中文,就需要进行编码。 + +所以,把带有特殊字符的URL转成可以显示的URL过程,称之为URL编码。 + +反之,就是解码。 + +URL编码可以使用不同的方式,如escape,URLEncode,encodeURIComponent。 \ No newline at end of file diff --git a/basics/java-basic/why-gbk.md b/basics/java-basic/why-gbk.md new file mode 100644 index 00000000..e69de29b diff --git a/basics/java-basic/why-utf8.md b/basics/java-basic/why-utf8.md new file mode 100644 index 00000000..1c724824 --- /dev/null +++ b/basics/java-basic/why-utf8.md @@ -0,0 +1,17 @@ +广义的 Unicode 是一个标准,定义了一个字符集以及一系列的编码规则,即 Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码规则。 + +Unicode 是字符集。UTF-8 是编码规则。 + +unicode虽然统一了全世界字符的二进制编码,但没有规定如何存储。 + +如果Unicode统一规定,每个符号就要用三个或四个字节表示,因为字符太多,只能用这么多字节才能表示完全。 + +一旦这么规定,那么每个英文字母前都必然有二到三个字节是0,因为所有英文字母在ASCII中都有,都可以用一个字节表示,剩余字节位置就要补充0。 + +如果这样,文本文件的大小会因此大出二三倍,这对于存储来说是极大的浪费。这样导致一个后果:出现了Unicode的多种存储方式。 + +UTF-8就是Unicode的一个使用方式,通过他的英文名Unicode Tranformation Format就可以知道。 + +UTF-8使用可变长度字节来储存 Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。 + +一般情况下,同一个地区只会出现一种文字类型,比如中文地区一般很少出现韩文,日文等。所以使用这种编码方式可以大大节省空间。比如纯英文网站就要比纯中文网站占用的存储下一些。 \ No newline at end of file From 8005a5106353a7f7db6159d1de32a450ca6e0ce9 Mon Sep 17 00:00:00 2001 From: Hollis Date: Mon, 9 Mar 2020 16:52:14 +0800 Subject: [PATCH 022/143] Update README.md 7 -> 8 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93fdff83..5f2bfc6b 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Java的继承与实现 #### 基本数据类型 -[7种基本数据类型:整型、浮点型、布尔型、字符型](/basics/java-basic/basic-data-types.md) +[8种基本数据类型:整型、浮点型、布尔型、字符型](/basics/java-basic/basic-data-types.md) [整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md) From 6773d12c44d27a51cb38923e0af6eba139ca8dab Mon Sep 17 00:00:00 2001 From: "hollis.zhl" Date: Thu, 26 Mar 2020 19:49:38 +0800 Subject: [PATCH 023/143] =?UTF-8?q?=E5=A2=9E=E5=8A=A0docsify=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.nojekyll | 0 docs/README.md | 10 ++++++++ docs/_coverpage.md | 21 ++++++++++++++++ docs/_sidebar.md | 12 +++++++++ docs/icon/icon.JPG | Bin 0 -> 49885 bytes docs/index.html | 59 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+) create mode 100644 docs/.nojekyll create mode 100644 docs/README.md create mode 100644 docs/_coverpage.md create mode 100644 docs/_sidebar.md create mode 100644 docs/icon/icon.JPG create mode 100644 docs/index.html diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..3d17608b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +## To Be Top Javaer - Java工程师成神之路 + +![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg) + + +| 主要版本 | 更新时间 | 备注 | +| ---- | ---------- | -------------- | +| v1.0 | 2015-08-01 | 首次发布 | +| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 | +| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通;
进一步完善知识体系;
新技术补充;| diff --git a/docs/_coverpage.md b/docs/_coverpage.md new file mode 100644 index 00000000..0ba84928 --- /dev/null +++ b/docs/_coverpage.md @@ -0,0 +1,21 @@ +
+ +
+ +

+To Be Top Javaer - Java工程师成神之路

+ + + +![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg) + + + + 👁️本页总访问次数: + + + | 🧑总访客数: + + + +Get Started

diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 00000000..73240027 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,12 @@ + +## 一、基础篇 + +### 面向对象 + +#### 什么是面向对象 + +* [面向对象、面向过程](../basics/java-basic/object-oriented-vs-procedure-oriented.md) + +* [面向对象的三大基本特征](/basics/java-basic/characteristics.md) + +* [五大基本原则](/basics/java-basic/principle.md) diff --git a/docs/icon/icon.JPG b/docs/icon/icon.JPG new file mode 100644 index 0000000000000000000000000000000000000000..9d276d627d16fed5ff93eff1ffac5c0e14596d85 GIT binary patch literal 49885 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 ncmC7kf6I3Jf9g6;{(o>Cr~W^f + + + + Document + + + + + + +
+ + + + + + + + From f4f1cb4b42bdd0d25f58df12dab64bc45a3c742d Mon Sep 17 00:00:00 2001 From: "hollis.zhl" Date: Sun, 29 Mar 2020 19:33:26 +0800 Subject: [PATCH 024/143] =?UTF-8?q?V3.0=20,=E7=9F=A5=E8=AF=86=E4=BD=93?= =?UTF-8?q?=E7=B3=BB=E5=AE=8C=E5=96=84=EF=BC=8C=E9=80=9A=E8=BF=87GitHub=20?= =?UTF-8?q?Pages=E6=8F=90=E4=BE=9B=E6=9B=B4=E5=A5=BD=E7=9A=84=E9=98=85?= =?UTF-8?q?=E8=AF=BB=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 990 +----------- basics/java-basic/basic-data-types.md | 11 - basics/java-basic/data-structure/list.md | 0 basics/jvm/java-object-model.md | 14 - basics/jvm/jvm-memory-structure.md | 28 - catalog/catalog.md | 88 -- docs/README.md | 43 +- docs/_coverpage.md | 4 +- docs/_sidebar.md | 1349 ++++++++++++++++- docs/basement/_sidebar.md | 89 ++ docs/basement/jvm/_sidebar.md | 68 + .../basement}/jvm/java-memory-model.md | 0 .../basement/jvm}/stack-alloc.md | 0 docs/basics/_sidebar.md | 415 +++++ docs/basics/concurrent-coding/_sidebar.md | 102 ++ .../concurrent-vs-parallel.md | 7 + docs/basics/concurrent-coding/concurrent.md | 13 + .../concurrent-coding}/deadlock-java-level.md | 0 docs/basics/concurrent-coding/parallel.md | 2 + .../basics/concurrent-coding}/synchronized.md | 0 docs/basics/java-basic/ASCII.md | 16 + .../basics}/java-basic/Arrays-asList.md | 0 .../basics}/java-basic/CET-UTC-GMT-CST.md | 0 {basics => docs/basics}/java-basic/Class.md | 0 .../java-basic/Collection-vs-Collections.md | 0 .../java-basic/ConcurrentSkipListMap.md | 0 .../java-basic/CopyOnWriteArrayList.md | 0 .../java-basic/Enumeration-vs-Iterator.md | 0 {basics => docs/basics}/java-basic/GMT.md | 0 .../HashMap-HashTable-ConcurrentHashMap.md | 0 docs/basics/java-basic/README.md | 12 + .../StandardTime-vs-daylightSavingTime.md | 0 {basics => docs/basics}/java-basic/UNICODE.md | 0 .../basics}/java-basic/UTF8-UTF16-UTF32.md | 0 docs/basics/java-basic/Wildcard-Character.md | 10 + docs/basics/java-basic/YYYY-vs-yyyy.md | 57 + docs/basics/java-basic/_sidebar.md | 274 ++++ .../java-basic/annotation-in-spring.md | 0 .../basics}/java-basic/aop-vs-proxy.md | 0 .../basics}/java-basic/api-vs-spi.md | 0 .../arraylist-vs-linkedlist-vs-vector.md | 0 docs/basics/java-basic/basic-data-types.md | 17 + .../java-basic/big-endian-vs-little-endian.md | 0 .../basics}/java-basic/bio-vs-nio-vs-aio.md | 0 .../java-basic/block-vs-non-blocking.md | 0 .../basics}/java-basic/boxing-unboxing.md | 0 .../byte-stream-vs-character-stream.md | 0 .../basics}/java-basic/constructor.md | 0 .../basics}/java-basic/create-annotation.md | 0 .../basics}/java-basic/create-spi.md | 0 .../basics}/java-basic/custom-annotation.md | 0 docs/basics/java-basic/define-exception.md | 6 + .../diff-serializable-vs-externalizable.md | 17 + .../dynamic-proxy-implementation.md | 0 .../java-basic/dynamic-proxy-vs-reflection.md | 0 .../basics}/java-basic/dynamic-proxy.md | 0 .../basics}/java-basic/enum-compare.md | 0 .../basics}/java-basic/enum-impl.md | 0 .../basics}/java-basic/enum-serializable.md | 0 .../basics}/java-basic/enum-singleton.md | 0 .../basics}/java-basic/enum-switch.md | 0 .../basics}/java-basic/enum-thread-safe.md | 0 .../basics}/java-basic/enum-usage.md | 0 docs/basics/java-basic/error-vs-exception.md | 6 + docs/basics/java-basic/exception-chain.md | 26 + docs/basics/java-basic/exception-type.md | 20 + docs/basics/java-basic/extends-vs-super.md | 48 + .../java-basic/fail-fast-vs-fail-safe.md | 0 .../basics}/java-basic/final-string.md | 0 .../basics}/java-basic/float-amount.md | 0 {basics => docs/basics}/java-basic/float.md | 0 .../basics}/java-basic/gbk-gb2312-gb18030.md | 0 .../java-basic/genericity-list-wildcard.md | 0 .../basics}/java-basic/genericity-list.md | 0 docs/basics/java-basic/generics-problem.md | 42 + docs/basics/java-basic/generics.md | 5 + .../basics/java-basic/get-los_angeles-time.md | 52 + docs/basics/java-basic/handle-exception.md | 6 + .../java-basic/inheritance-composition.md | 0 .../input-stream-vs-output-stream.md | 0 .../basics}/java-basic/integer-cache.md | 0 .../basics}/java-basic/integer-scope.md | 0 {basics => docs/basics}/java-basic/k-t-v-e.md | 0 .../java-basic/keyword-about-exception.md | 11 + .../basics}/java-basic/linux-io.md | 0 .../basics}/java-basic/meta-annotation.md | 0 .../java-basic/order-about-finllly-return.md | 6 + docs/basics/java-basic/protobuf.md | 9 + .../basics}/java-basic/reflection.md | 0 {basics => docs/basics}/java-basic/scope.md | 0 .../java-basic/serialVersionUID-modify.md | 278 ++++ docs/basics/java-basic/serialVersionUID.md | 21 + .../basics/java-basic/serialize-in-java.md | 35 - docs/basics/java-basic/serialize-principle.md | 397 +++++ .../basics}/java-basic/serialize-singleton.md | 0 .../basics}/java-basic/serialize.md | 0 .../basics}/java-basic/set-repetition.md | 0 .../basics}/java-basic/set-vs-list.md | 0 .../simpledateformat-thread-safe.md | 0 .../basics}/java-basic/single-double-float.md | 0 .../basics}/java-basic/spi-principle.md | 0 .../basics/java-basic}/stack-alloc.md | 0 .../basics}/java-basic/static-proxy.md | 0 {basics => docs/basics}/java-basic/stream.md | 0 .../basics}/java-basic/string-append.md | 0 .../basics}/java-basic/string-concat.md | 0 .../basics}/java-basic/substring.md | 0 .../basics}/java-basic/switch-string.md | 0 .../synchronized-vs-asynchronization.md | 0 .../java-basic/synchronizedlist-vector.md | 0 .../basics}/java-basic/syntactic-sugar.md | 0 docs/basics/java-basic/time-in-java8.md | 57 + .../basics}/java-basic/time-zone.md | 0 .../basics}/java-basic/timestamp.md | 0 docs/basics/java-basic/transient.md | 1 + docs/basics/java-basic/try-with-resources.md | 91 ++ docs/basics/java-basic/type-erasue.md | 199 +++ .../basics}/java-basic/url-encode.md | 0 .../basics}/java-basic/usage-of-reflection.md | 0 .../java-basic/value-of-vs-to-string.md | 0 .../basics}/java-basic/variable.md | 0 {basics => docs/basics}/java-basic/why-gbk.md | 0 .../basics}/java-basic/why-utf8.md | 0 docs/basics/object-oriented/_sidebar.md | 32 + .../object-oriented}/characteristics.md | 8 +- docs/basics/object-oriented/constructor.md | 5 + .../inheritance-composition.md | 67 + docs/basics/object-oriented/java-pass-by.md | 57 + .../basics/object-oriented}/jvm-language.md | 0 .../object-oriented-vs-procedure-oriented.md | 10 +- .../overloading-vs-overriding.md | 0 .../object-oriented}/platform-independent.md | 20 +- .../basics/object-oriented}/polymorphism.md | 7 +- .../basics/object-oriented}/principle.md | 3 + docs/basics/object-oriented/scope.md | 11 + docs/basics/object-oriented/variable.md | 27 + .../object-oriented/why-pass-by-reference.md | 139 +- docs/contact/wechat-hollis.jpg | Bin 0 -> 8333 bytes docs/css/my.css | 3 + docs/index.html | 85 +- 140 files changed, 4079 insertions(+), 1337 deletions(-) delete mode 100644 basics/java-basic/basic-data-types.md delete mode 100644 basics/java-basic/data-structure/list.md delete mode 100644 basics/jvm/java-object-model.md delete mode 100644 basics/jvm/jvm-memory-structure.md delete mode 100644 catalog/catalog.md create mode 100644 docs/basement/_sidebar.md create mode 100644 docs/basement/jvm/_sidebar.md rename {basics => docs/basement}/jvm/java-memory-model.md (100%) rename {basics/java-basic => docs/basement/jvm}/stack-alloc.md (100%) create mode 100644 docs/basics/_sidebar.md create mode 100644 docs/basics/concurrent-coding/_sidebar.md create mode 100644 docs/basics/concurrent-coding/concurrent-vs-parallel.md create mode 100644 docs/basics/concurrent-coding/concurrent.md rename {basics/java-basic => docs/basics/concurrent-coding}/deadlock-java-level.md (100%) create mode 100644 docs/basics/concurrent-coding/parallel.md rename {basics/java-basic => docs/basics/concurrent-coding}/synchronized.md (100%) create mode 100644 docs/basics/java-basic/ASCII.md rename {basics => docs/basics}/java-basic/Arrays-asList.md (100%) rename {basics => docs/basics}/java-basic/CET-UTC-GMT-CST.md (100%) rename {basics => docs/basics}/java-basic/Class.md (100%) rename {basics => docs/basics}/java-basic/Collection-vs-Collections.md (100%) rename {basics => docs/basics}/java-basic/ConcurrentSkipListMap.md (100%) rename {basics => docs/basics}/java-basic/CopyOnWriteArrayList.md (100%) rename {basics => docs/basics}/java-basic/Enumeration-vs-Iterator.md (100%) rename {basics => docs/basics}/java-basic/GMT.md (100%) rename {basics => docs/basics}/java-basic/HashMap-HashTable-ConcurrentHashMap.md (100%) create mode 100644 docs/basics/java-basic/README.md rename {basics => docs/basics}/java-basic/StandardTime-vs-daylightSavingTime.md (100%) rename {basics => docs/basics}/java-basic/UNICODE.md (100%) rename {basics => docs/basics}/java-basic/UTF8-UTF16-UTF32.md (100%) create mode 100644 docs/basics/java-basic/Wildcard-Character.md create mode 100644 docs/basics/java-basic/YYYY-vs-yyyy.md create mode 100644 docs/basics/java-basic/_sidebar.md rename {basics => docs/basics}/java-basic/annotation-in-spring.md (100%) rename {basics => docs/basics}/java-basic/aop-vs-proxy.md (100%) rename {basics => docs/basics}/java-basic/api-vs-spi.md (100%) rename {basics => docs/basics}/java-basic/arraylist-vs-linkedlist-vs-vector.md (100%) create mode 100644 docs/basics/java-basic/basic-data-types.md rename {basics => docs/basics}/java-basic/big-endian-vs-little-endian.md (100%) rename {basics => docs/basics}/java-basic/bio-vs-nio-vs-aio.md (100%) rename {basics => docs/basics}/java-basic/block-vs-non-blocking.md (100%) rename {basics => docs/basics}/java-basic/boxing-unboxing.md (100%) rename {basics => docs/basics}/java-basic/byte-stream-vs-character-stream.md (100%) rename {basics => docs/basics}/java-basic/constructor.md (100%) rename {basics => docs/basics}/java-basic/create-annotation.md (100%) rename {basics => docs/basics}/java-basic/create-spi.md (100%) rename {basics => docs/basics}/java-basic/custom-annotation.md (100%) create mode 100644 docs/basics/java-basic/define-exception.md create mode 100644 docs/basics/java-basic/diff-serializable-vs-externalizable.md rename {basics => docs/basics}/java-basic/dynamic-proxy-implementation.md (100%) rename {basics => docs/basics}/java-basic/dynamic-proxy-vs-reflection.md (100%) rename {basics => docs/basics}/java-basic/dynamic-proxy.md (100%) rename {basics => docs/basics}/java-basic/enum-compare.md (100%) rename {basics => docs/basics}/java-basic/enum-impl.md (100%) rename {basics => docs/basics}/java-basic/enum-serializable.md (100%) rename {basics => docs/basics}/java-basic/enum-singleton.md (100%) rename {basics => docs/basics}/java-basic/enum-switch.md (100%) rename {basics => docs/basics}/java-basic/enum-thread-safe.md (100%) rename {basics => docs/basics}/java-basic/enum-usage.md (100%) create mode 100644 docs/basics/java-basic/error-vs-exception.md create mode 100644 docs/basics/java-basic/exception-chain.md create mode 100644 docs/basics/java-basic/exception-type.md create mode 100644 docs/basics/java-basic/extends-vs-super.md rename {basics => docs/basics}/java-basic/fail-fast-vs-fail-safe.md (100%) rename {basics => docs/basics}/java-basic/final-string.md (100%) rename {basics => docs/basics}/java-basic/float-amount.md (100%) rename {basics => docs/basics}/java-basic/float.md (100%) rename {basics => docs/basics}/java-basic/gbk-gb2312-gb18030.md (100%) rename {basics => docs/basics}/java-basic/genericity-list-wildcard.md (100%) rename {basics => docs/basics}/java-basic/genericity-list.md (100%) create mode 100644 docs/basics/java-basic/generics-problem.md create mode 100644 docs/basics/java-basic/generics.md create mode 100644 docs/basics/java-basic/get-los_angeles-time.md create mode 100644 docs/basics/java-basic/handle-exception.md rename {basics => docs/basics}/java-basic/inheritance-composition.md (100%) rename {basics => docs/basics}/java-basic/input-stream-vs-output-stream.md (100%) rename {basics => docs/basics}/java-basic/integer-cache.md (100%) rename {basics => docs/basics}/java-basic/integer-scope.md (100%) rename {basics => docs/basics}/java-basic/k-t-v-e.md (100%) create mode 100644 docs/basics/java-basic/keyword-about-exception.md rename {basics => docs/basics}/java-basic/linux-io.md (100%) rename {basics => docs/basics}/java-basic/meta-annotation.md (100%) create mode 100644 docs/basics/java-basic/order-about-finllly-return.md create mode 100644 docs/basics/java-basic/protobuf.md rename {basics => docs/basics}/java-basic/reflection.md (100%) rename {basics => docs/basics}/java-basic/scope.md (100%) create mode 100644 docs/basics/java-basic/serialVersionUID-modify.md create mode 100644 docs/basics/java-basic/serialVersionUID.md rename basics/java-basic/serialize-principle.md => docs/basics/java-basic/serialize-in-java.md (78%) create mode 100644 docs/basics/java-basic/serialize-principle.md rename {basics => docs/basics}/java-basic/serialize-singleton.md (100%) rename {basics => docs/basics}/java-basic/serialize.md (100%) rename {basics => docs/basics}/java-basic/set-repetition.md (100%) rename {basics => docs/basics}/java-basic/set-vs-list.md (100%) rename {basics => docs/basics}/java-basic/simpledateformat-thread-safe.md (100%) rename {basics => docs/basics}/java-basic/single-double-float.md (100%) rename {basics => docs/basics}/java-basic/spi-principle.md (100%) rename {basics/jvm => docs/basics/java-basic}/stack-alloc.md (100%) rename {basics => docs/basics}/java-basic/static-proxy.md (100%) rename {basics => docs/basics}/java-basic/stream.md (100%) rename {basics => docs/basics}/java-basic/string-append.md (100%) rename {basics => docs/basics}/java-basic/string-concat.md (100%) rename {basics => docs/basics}/java-basic/substring.md (100%) rename {basics => docs/basics}/java-basic/switch-string.md (100%) rename {basics => docs/basics}/java-basic/synchronized-vs-asynchronization.md (100%) rename {basics => docs/basics}/java-basic/synchronizedlist-vector.md (100%) rename {basics => docs/basics}/java-basic/syntactic-sugar.md (100%) create mode 100644 docs/basics/java-basic/time-in-java8.md rename {basics => docs/basics}/java-basic/time-zone.md (100%) rename {basics => docs/basics}/java-basic/timestamp.md (100%) create mode 100644 docs/basics/java-basic/transient.md create mode 100644 docs/basics/java-basic/try-with-resources.md create mode 100644 docs/basics/java-basic/type-erasue.md rename {basics => docs/basics}/java-basic/url-encode.md (100%) rename {basics => docs/basics}/java-basic/usage-of-reflection.md (100%) rename {basics => docs/basics}/java-basic/value-of-vs-to-string.md (100%) rename {basics => docs/basics}/java-basic/variable.md (100%) rename {basics => docs/basics}/java-basic/why-gbk.md (100%) rename {basics => docs/basics}/java-basic/why-utf8.md (100%) create mode 100644 docs/basics/object-oriented/_sidebar.md rename {basics/java-basic => docs/basics/object-oriented}/characteristics.md (94%) create mode 100644 docs/basics/object-oriented/constructor.md create mode 100644 docs/basics/object-oriented/inheritance-composition.md create mode 100644 docs/basics/object-oriented/java-pass-by.md rename {basics/java-basic => docs/basics/object-oriented}/jvm-language.md (100%) rename {basics/java-basic => docs/basics/object-oriented}/object-oriented-vs-procedure-oriented.md (90%) rename {basics/java-basic => docs/basics/object-oriented}/overloading-vs-overriding.md (100%) rename {basics/java-basic => docs/basics/object-oriented}/platform-independent.md (97%) rename {basics/java-basic => docs/basics/object-oriented}/polymorphism.md (96%) rename {basics/java-basic => docs/basics/object-oriented}/principle.md (99%) create mode 100644 docs/basics/object-oriented/scope.md create mode 100644 docs/basics/object-oriented/variable.md rename basics/java-basic/java-pass-by.md => docs/basics/object-oriented/why-pass-by-reference.md (55%) create mode 100644 docs/contact/wechat-hollis.jpg create mode 100644 docs/css/my.css diff --git a/README.md b/README.md index 5f2bfc6b..e986fba5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[![](http://www.hollischuang.com/wp-content/uploads/2018/10/Hollis.png)](https://www.hollischuang.com) - ## To Be Top Javaer - Java工程师成神之路 ![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg) @@ -7,993 +5,47 @@ | 主要版本 | 更新时间 | 备注 | | ---- | ---------- | -------------- | -| v1.0 | 2015-08-01 | 首次发布 | -| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 | +| v3.0 | 2020-03-29 | 知识体系完善
通过GitHub Page搭建,便于阅读| | v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通;
进一步完善知识体系;
新技术补充;| +| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 | +| v1.0 | 2015-08-01 | 首次发布 | -欢迎关注[Java之道]公众号,最新内容均会在该公众号中同步发出! -![](pics/wechat.png) - - -[全套思维导图](/mind-map.md) - -## 一、基础篇 - -### 面向对象 - -#### 什么是面向对象 - -[面向对象、面向过程](/basics/java-basic/object-oriented-vs-procedure-oriented.md) - -[面向对象的三大基本特征](/basics/java-basic/characteristics.md)和[五大基本原则](/basics/java-basic/principle.md) - -#### 平台无关性 - -[Java如何实现的平台无关性的](/basics/java-basic/platform-independent.md) - -[JVM还支持哪些语言(Kotlin、Groovy、JRuby、Jython、Scala)](/basics/java-basic/jvm-language.md) - -#### 值传递 - -[值传递、引用传递](/basics/java-basic/java-pass-by.md) - -[为什么说Java中只有值传递](/basics/java-basic/java-pass-by.md) - -#### 封装、继承、多态 - -[什么是多态](/basics/java-basic/polymorphism.md)、[方法重写与重载](/basics/java-basic/overloading-vs-overriding.md) - -Java的继承与实现 - -[Java的继承与组合](/basics/java-basic/inheritance-composition.md) - -[构造函数与默认构造函数](/basics/java-basic/constructor.md) - -[类变量、成员变量和局部变量](/basics/java-basic/variable.md) - -[成员变量和方法作用域](/basics/java-basic/scope.md) - -### Java基础知识 - -#### 基本数据类型 - -[8种基本数据类型:整型、浮点型、布尔型、字符型](/basics/java-basic/basic-data-types.md) - -[整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md) - -[什么是浮点型?](/basics/java-basic/float.md) - -[什么是单精度和双精度?](/basics/java-basic/single-double-float.md) - -[为什么不能用浮点型表示金额?](/basics/java-basic/float-amount.md) - -#### 自动拆装箱 - -[什么是包装类型、什么是基本类型、什么是自动拆装箱](/basics/java-basic/boxing-unboxing.md) - -[Integer的缓存机制](/basics/java-basic/integer-cache.md) - -#### String - -[字符串的不可变性](/basics/java-basic/final-string.md) - -[JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md) - -replaceFirst、replaceAll、replace区别、 - -[String对“+”的重载](/basics/java-basic/string-append.md) - -[字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md) - -[String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md) - -[switch对String的支持](/basics/java-basic/switch-string.md) - -字符串池、常量池(运行时常量池、Class常量池)、intern - -#### 熟悉Java中各种关键字 - -transient、instanceof、volatile、synchronized、final、static、const 原理及用法。 - -#### 集合类 - -常用集合类的使用 - -[ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md) - -[SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md)、 - -[HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md) - -[Set和List区别?](/basics/java-basic/set-vs-list.md) - -[Set如何保证元素不重复?](/basics/java-basic/set-repetition.md) - -[Java 8中stream相关用法](/basics/java-basic/stream.md)、 - -apache集合处理工具类的使用、 - -不同版本的JDK中HashMap的实现的区别以及原因 - -[Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md) - -[Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md) - -[Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md) - -[fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md) - -[CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md) - -[ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md) - -#### 枚举 - -[枚举的用法](/basics/java-basic/enum-usage.md) - -[枚举的实现](/basics/java-basic/enum-impl.md) - -[枚举与单例](/basics/java-basic/enum-singleton.md)、Enum类 - -[Java枚举如何比较](/basics/java-basic/enum-compare.md) - -[switch对枚举的支持](/basics/java-basic/enum-switch.md) - -[枚举的序列化如何实现](/basics/java-basic/enum-serializable.md) - -[枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md) - -#### IO - -[字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md)、[输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md) - -[同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md)、[阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md)、[Linux 5种IO模型](/basics/java-basic/linux-io.md) - -[BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md)、netty - -#### Java反射与javassist - -[反射](/basics/java-basic/reflection.md)与工厂模式、 [反射有什么作用](/basics/java-basic/usage-of-reflection.md) - -[Class类](/basics/java-basic/Class.md) - -`java.lang.reflect.*` - -#### 动态代理 - -[静态代理](/basics/java-basic/static-proxy.md)、[动态代理](/basics/java-basic/dynamic-proxy.md) - -[动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md) - -[动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md) - -[AOP](/basics/java-basic/aop-vs-proxy.md) - -#### 序列化 - -[什么是序列化与反序列化](/basics/java-basic/serialize.md)、为什么序列化、[序列化底层原理](/basics/java-basic/serialize-principle.md)、[序列化与单例模式](/basics/java-basic/serialize-singleton.md)、protobuf、为什么说序列化并不安全 - -#### 注解 - -[元注解](/basics/java-basic/meta-annotation.md)、[自定义注解](/basics/java-basic/custom-annotation.md)、Java中常用注解使用、注解与反射的结合 - -[如何自定义一个注解?](/basics/java-basic/create-annotation.md) - -[Spring常用注解](/basics/java-basic/annotation-in-spring.md) - -#### JMS - -什么是Java消息服务、JMS消息传送模型 - -#### JMX - -`java.lang.management.*`、 `javax.management.*` - -#### 泛型 - -泛型与继承、类型擦除、[泛型中K T V E ? object等的含义](/basics/java-basic/k-t-v-e.md)、泛型各种用法 - -限定通配符和非限定通配符、上下界限定符extends 和 super - -[List和原始类型List之间的区别?](/basics/java-basic/genericity-list.md) - -[List和List之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md) - -#### 单元测试 - -junit、mock、mockito、内存数据库(h2) - -#### 正则表达式 - -`java.lang.util.regex.*` - -#### 常用的Java工具库 - -`commons.lang`, `commons.*...` `guava-libraries` `netty` - -#### API&SPI - -API、[API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md) - -[如何定义SPI](/basics/java-basic/create-spi.md)、[SPI的实现原理](/basics/java-basic/spi-principle.md) - -#### 异常 - -异常类型、正确处理异常、自定义异常 - -Error和Exception - -异常链、try-with-resources - -finally和return的执行顺序 - -#### 时间处理 - -[时区](/basics/java-basic/time-zone.md)、[冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md)、[时间戳](/basics/java-basic/timestamp.md)、Java中时间API - -[格林威治时间](/basics/java-basic/GMT.md)、[CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md) - -[SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md) - -Java 8中的时间处理 - -如何在东八区的计算机上获取美国时间 - -#### 编码方式 - -什么是ASCII? - -[Unicode](/basics/java-basic/UNICODE.md)、[有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md) - -[UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md) - -有了UTF8为什么还需要GBK? - -[GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md) - -[URL编解码](/basics/java-basic/url-encode.md)、[Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md) - -如何解决乱码问题 - -#### 语法糖 - -[Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md) - -[语法糖:switch 支持 String 与枚举、泛型、自动装箱与拆箱、方法变长参数、枚举、内部类、条件编译、 断言、数值字面量、for-each、try-with-resource、Lambda表达式](/basics/java-basic/syntactic-sugar.md) - -### 阅读源代码 - -String、Integer、Long、Enum、BigDecimal、ThreadLocal、ClassLoader & URLClassLoader、ArrayList & LinkedList、 HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap、HashSet & LinkedHashSet & TreeSet - -### Java并发编程 - -#### 并发与并行 - -什么是并发 - -什么是并行 - -并发与并行的区别 - -#### 线程 - -线程的实现、线程的状态、优先级、线程调度、创建线程的多种方式、守护线程 - -线程与进程的区别 - -#### 线程池 - -自己设计线程池、submit() 和 execute()、线程池原理 - -为什么不允许使用Executors创建线程池 - -#### 线程安全 - -[死锁?](/basics/java-basic/deadlock-java-level.md)、死锁如何排查、线程安全和内存模型的关系 - -#### 锁 - -CAS、乐观锁与悲观锁、数据库相关锁机制、分布式锁、偏向锁、轻量级锁、重量级锁、monitor、 - -锁优化、锁消除、锁粗化、自旋锁、可重入锁、阻塞锁、死锁 - -#### 死锁 - -死锁的原因 - -死锁的解决办法 - -#### synchronized - -[synchronized是如何实现的?](/basics/java-basic/synchronized.md) - -synchronized和lock之间关系、不使用synchronized如何实现一个线程安全的单例 - -synchronized和原子性、可见性和有序性之间的关系 - -#### volatile - -happens-before、内存屏障、编译器指令重排和CPU指令重排 - -volatile的实现原理 - -volatile和原子性、可见性和有序性之间的关系 - -有了symchronized为什么还需要volatile - -#### sleep 和 wait - -#### wait 和 notify - -#### notify 和 notifyAll - -#### ThreadLocal - -#### 写一个死锁的程序 - -#### 写代码来解决生产者消费者问题 - -### 并发包 - -#### 阅读源代码,并学会使用 - -Thread、Runnable、Callable、ReentrantLock、ReentrantReadWriteLock、Atomic*、Semaphore、CountDownLatch、、ConcurrentHashMap、Executors - -## 二、底层篇 - -### JVM - -#### JVM内存结构 - -class文件格式、运行时数据区:堆、栈、方法区、直接内存、运行时常量池、 - -堆和栈区别 - -[Java中的对象一定在堆上分配吗?](/basics/jvm/stack-alloc.md) - -#### Java内存模型 - -计算机内存模型、缓存一致性、MESI协议 - -可见性、原子性、顺序性、happens-before、 - -内存屏障、synchronized、volatile、final、锁 - -#### 垃圾回收 - -GC算法:标记清除、引用计数、复制、标记压缩、分代回收、增量式回收 - -GC参数、对象存活的判定、垃圾收集器(CMS、G1、ZGC、Epsilon) - -#### JVM参数及调优 - --Xmx、-Xmn、-Xms、Xss、-XX:SurvivorRatio、 - --XX:PermSize、-XX:MaxPermSize、-XX:MaxTenuringThreshold - -#### Java对象模型 - -oop-klass、对象头 - -#### HotSpot - -即时编译器、编译优化 - -#### 虚拟机性能监控与故障处理工具 - -jps, jstack, jmap、jstat, jconsole, jinfo, jhat, javap, btrace、TProfiler - -Arthas - -### 类加载机制 - -classLoader、类加载过程、双亲委派(破坏双亲委派)、模块化(jboss modules、osgi、jigsaw) - -### 编译与反编译 - -什么是编译(前端编译、后端编译)、什么是反编译 - -JIT、JIT优化(逃逸分析、栈上分配、标量替换、锁优化) - -编译工具:javac - -反编译工具:javap 、jad 、CRF - -## 三、 进阶篇 - -### Java底层知识 - -#### 字节码、class文件格式 - -#### CPU缓存,L1,L2,L3和伪共享 - -#### 尾递归 - -#### 位运算 - -用位运算实现加、减、乘、除、取余 - -### 设计模式 - -设计模式的六大原则: - -开闭原则(Open Close Principle)、里氏代换原则(Liskov Substitution Principle)、依赖倒转原则(Dependence Inversion Principle) - -接口隔离原则(Interface Segregation Principle)、迪米特法则(最少知道原则)(Demeter Principle)、合成复用原则(Composite Reuse Principle) - -#### 了解23种设计模式 - -创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。 - -结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。 - -行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。 - -#### 会使用常用设计模式 - -单例的七种写法:懒汉——线程不安全、懒汉——线程安全、饿汉、饿汉——变种、静态内部类、枚举、双重校验锁 - -工厂模式、适配器模式、策略模式、模板方法模式、观察者模式、外观模式、代理模式等必会 - -#### 不用synchronized和lock,实现线程安全的单例模式 - -#### 实现AOP - -#### 实现IOC - -#### nio和reactor设计模式 - -### 网络编程知识 - -#### tcp、udp、http、https等常用协议 - -三次握手与四次关闭、流量控制和拥塞控制、OSI七层模型、tcp粘包与拆包 - -#### http/1.0 http/1.1 http/2之间的区别 - -http中 get和post区别 - -常见的web请求返回的状态码 - -404、302、301、500分别代表什么 - -#### http/3 - -#### Java RMI,Socket,HttpClient - -#### cookie 与 session - -cookie被禁用,如何实现session - -#### 用Java写一个简单的静态文件的HTTP服务器 - -#### 了解nginx和apache服务器的特性并搭建一个对应的服务器 - -#### 用Java实现FTP、SMTP协议 - -#### 进程间通讯的方式 - -#### 什么是CDN?如果实现? - -#### DNS? - -什么是DNS 、记录类型:A记录、CNAME记录、AAAA记录等 - -域名解析、根域名服务器 - -DNS污染、DNS劫持、公共DNS:114 DNS、Google DNS、OpenDNS - -#### 反向代理 - -正向代理、反向代理 - -反向代理服务器 - -### 框架知识 - -#### Servlet - -生命周期 - -线程安全问题 - -filter和listener - -web.xml中常用配置及作用 - -#### Hibernate - -什么是OR Mapping - -Hibernate的缓存机制 - -Hibernate的懒加载 - -Hibernate/Ibatis/MyBatis之间的区别 - -#### Spring - -Bean的初始化 - -AOP原理 - -实现Spring的IOC - -spring四种依赖注入方式 - -#### Spring MVC - -什么是MVC - -Spring mvc与Struts mvc的区别 - -#### Spring Boot - -Spring Boot 2.0、起步依赖、自动配置、 - -Spring Boot的starter原理,自己实现一个starter - -#### Spring Security - -### Spring Cloud - -服务发现与注册:Eureka、Zookeeper、Consul - -负载均衡:Feign、Spring Cloud Loadbalance - -服务配置:Spring Cloud Config - -服务限流与熔断:Hystrix - -服务链路追踪:Dapper - -服务网关、安全、消息 - -### 应用服务器知识 - -#### JBoss - -#### tomcat - -#### jetty - -#### Weblogic - -### 工具 - -#### git & svn - -#### maven & gradle - -#### Intellij IDEA - -常用插件:Maven Helper 、FindBugs-IDEA、阿里巴巴代码规约检测、GsonFormat - -Lombok plugin、.ignore、Mybatis plugin - -## 四、 高级篇 - -### 新技术 - -#### Java 8 - -lambda表达式、Stream API、时间API - -#### Java 9 - -Jigsaw、Jshell、Reactive Streams - -#### Java 10 - -局部变量类型推断、G1的并行Full GC、ThreadLocal握手机制 - -#### Java 11 - -ZGC、Epsilon、增强var、 - -#### Spring 5 - -响应式编程 - -#### Spring Boot 2.0 - -### http/2 - -### http/3 - -### 性能优化 - -使用单例、使用Future模式、使用线程池、选择就绪、减少上下文切换、减少锁粒度、数据压缩、结果缓存 - -### 线上问题分析 - -#### dump获取 - -线程Dump、内存Dump、gc情况 - -#### dump分析 - -分析死锁、分析内存泄露 - -#### dump分析及获取工具 - -jstack、jstat、jmap、jhat、Arthas - -#### 自己编写各种outofmemory,stackoverflow程序 - -HeapOutOfMemory、 Young OutOfMemory、MethodArea OutOfMemory、ConstantPool OutOfMemory、DirectMemory OutOfMemory、Stack OutOfMemory Stack OverFlow - -#### Arthas - -jvm相关、class/classloader相关、monitor/watch/trace相关、 - -options、管道、后台异步任务 - -文档:https://alibaba.github.io/arthas/advanced-use.html - -#### 常见问题解决思路 - -内存溢出、线程死锁、类加载冲突 - -#### 使用工具尝试解决以下问题,并写下总结 - -当一个Java程序响应很慢时如何查找问题、 - -当一个Java程序频繁FullGC时如何解决问题、 - -如何查看垃圾回收日志、 - -当一个Java应用发生OutOfMemory时该如何解决、 - -如何判断是否出现死锁、 - -如何判断是否存在内存泄露 - -使用Arthas快速排查Spring Boot应用404/401问题 - -使用Arthas排查线上应用日志打满问题 - -利用Arthas排查Spring Boot应用NoSuchMethodError - -### 编译原理知识 - -#### 编译与反编译 - -#### Java代码的编译与反编译 - -#### Java的反编译工具 - -javap 、jad 、CRF - -#### 即时编译器 - -#### 词法分析,语法分析(LL算法,递归下降算法,LR算法),语义分析,运行时环境,中间代码,代码生成,代码优化 - -### 操作系统知识 - -#### Linux的常用命令 - -#### 进程间通信 - -#### 进程同步 - -生产者消费者问题、哲学家就餐问题、读者写者问题 - -#### 缓冲区溢出 - -#### 分段和分页 - -#### 虚拟内存与主存 - -#### 虚拟内存管理 - -#### 换页算法 - -### 数据库知识 - -#### MySql 执行引擎 - -#### MySQL 执行计划 - -如何查看执行计划,如何根据执行计划进行SQL优化 - -#### 索引 - -Hash索引、B树索引(B+树、和B树、R树) - -普通索引、唯一索引 - -覆盖索引、最左前缀原则、索引下推 - -#### SQL优化 - -#### 数据库事务和隔离级别 - -事务的隔离级别、事务能不能实现锁的功能 - -#### 数据库锁 - -行锁、表锁、使用数据库锁实现乐观锁、 - -#### 连接 - -内连接,左连接,右连接 - -#### 数据库主备搭建 - -#### binlog - -#### redolog - -#### 内存数据库 - -h2 - -#### 分库分表 - -#### 读写分离 - -#### 常用的nosql数据库 - -redis、memcached - -#### 分别使用数据库锁、NoSql实现分布式锁 - -#### 性能调优 - -#### 数据库连接池 - -### 数据结构与算法知识 - -#### 简单的数据结构 - -栈、队列、链表、数组、哈希表、 - -栈和队列的相同和不同之处 - -栈通常采用的两种存储结构 - -#### 树 - -二叉树、字典树、平衡树、排序树、B树、B+树、R树、多路树、红黑树 - -#### 堆 - -大根堆、小根堆 - -#### 图 - -有向图、无向图、拓扑 - -#### 排序算法 - -稳定的排序:冒泡排序、插入排序、鸡尾酒排序、桶排序、计数排序、归并排序、原地归并排序、二叉排序树排序、鸽巢排序、基数排序、侏儒排序、图书馆排序、块排序 - -不稳定的排序:选择排序、希尔排序、Clover排序算法、梳排序、堆排序、平滑排序、快速排序、内省排序、耐心排序 - -各种排序算法和时间复杂度 - -#### 深度优先和广度优先搜索 - -#### 全排列、贪心算法、KMP算法、hash算法 - -#### 海量数据处理 - -分治,hash映射,堆排序,双层桶划分,Bloom Filter,bitmap,数据库索引,mapreduce等。 - -#### 两个栈实现队列,和两个队列实现栈 - -### 大数据知识 - -#### Zookeeper - -基本概念、常见用法 - -#### Solr,Lucene,ElasticSearch - -在linux上部署solr,solrcloud,,新增、删除、查询索引 - -#### Storm,流式计算,了解Spark,S4 - -在linux上部署storm,用zookeeper做协调,运行storm hello world,local和remote模式运行调试storm topology。 - -#### Hadoop,离线计算 - -HDFS、MapReduce - -#### 分布式日志收集flume,kafka,logstash - -#### 数据挖掘,mahout - -### 网络安全知识 - -#### XSS - -XSS的防御 - -#### CSRF - -#### 注入攻击 - -SQL注入、XML注入、CRLF注入 - -#### 文件上传漏洞 - -#### 加密与解密 - -对称加密、非对称加密、哈希算法、加盐哈希算法 - -MD5,SHA1、DES、AES、RSA、DSA - -彩虹表 - -#### DDOS攻击 - -DOS攻击、DDOS攻击 - -memcached为什么可以导致DDos攻击、什么是反射型DDoS - -如何通过Hash碰撞进行DOS攻击 - -#### SSL、TLS,HTTPS - -#### 用openssl签一个证书部署到apache或nginx - -## 五、架构篇 - -### 分布式 - -数据一致性、服务治理、服务降级 - -#### 分布式事务 - -2PC、3PC、CAP、BASE、 可靠消息最终一致性、最大努力通知、TCC - -#### Dubbo - -服务注册、服务发现,服务治理 - -http://dubbo.apache.org/zh-cn/ - -#### 分布式数据库 - -怎样打造一个分布式数据库、什么时候需要分布式数据库、mycat、otter、HBase - -#### 分布式文件系统 - -mfs、fastdfs - -#### 分布式缓存 - -缓存一致性、缓存命中率、缓存冗余 - -#### 限流降级 - -Hystrix、Sentinal - -#### 算法 - -共识算法、Raft协议、Paxos 算法与 Raft 算法、拜占庭问题与算法 - -2PC、3PC - -### 微服务 - -SOA、康威定律 - -#### ServiceMesh - -sidecar - -#### Docker & Kubernets - -#### Spring Boot - -#### Spring Cloud - -### 高并发 - -#### 分库分表 - -#### CDN技术 - -#### 消息队列 - -ActiveMQ - -### 监控 - -#### 监控什么 - -CPU、内存、磁盘I/O、网络I/O等 - -#### 监控手段 - -进程监控、语义监控、机器资源监控、数据波动 - -#### 监控数据采集 - -日志、埋点 - -#### Dapper - -### 负载均衡 - -tomcat负载均衡、Nginx负载均衡 - -四层负载均衡、七层负载均衡 - -### DNS - -DNS原理、DNS的设计 - -### CDN - -数据一致性 - -## 六、 扩展篇 - -### 云计算 - -IaaS、SaaS、PaaS、虚拟化技术、openstack、Serverlsess - -### 搜索引擎 - -Solr、Lucene、Nutch、Elasticsearch - -### 权限管理 - -Shiro +目前正在更新中... -### 区块链 +欢迎大家参与共建~ -哈希算法、Merkle树、公钥密码算法、共识算法、Raft协议、Paxos 算法与 Raft 算法、拜占庭问题与算法、消息认证码与数字签名 +### 在线阅读地址 -#### 比特币 +GitHub Pages 完整阅读:[进入](https://hollischuang.github.io/toBeTopJavaer/) -挖矿、共识机制、闪电网络、侧链、热点问题、分叉 +Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快) -#### 以太坊 -#### 超级账本 +### 关于作者 -### 人工智能 +Hollis,阿里巴巴技术专家,51CTO专栏作家,CSDN博客专家,掘金优秀作者,《程序员的三门课》联合作者,《Java工程师成神之路》系列文章作者;热衷于分享计算机编程相关技术,博文全网阅读量上千万。 -数学基础、机器学习、人工神经网络、深度学习、应用场景。 -#### 常用框架 +### 开源协议 -TensorFlow、DeepLearning4J +本着互联网的开放精神,本项目采用开放的[GPL]协议进行许可。 -### IoT -### 量子计算 +### 参与共建 -### AR & VR +如果您对本项目中的内容有建议或者意见 -### 其他语言 +如果你对本项目中未完成的章节感兴趣 -Groovy、Python、Go、NodeJs、Swift、Rust +欢迎提出专业方面的修改建议及供稿,供稿只接受原创 -## 六、 推荐书籍 +请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出 -《深入理解Java虚拟机》 -《Effective Java》 -《深入分析Java Web技术内幕》 -《大型网站技术架构》 -《代码整洁之道》 -《架构整洁之道》 -《Head First设计模式》 -《maven实战》 -《区块链原理、设计与应用》 -《Java并发编程实战》 -《鸟哥的Linux私房菜》 -《从Paxos到Zookeeper》 -《架构即未来》 +如果本项目中的内容侵犯了您的任何权益,欢迎通过邮箱(hollischuang@gmail)与我联系 -------------- -扫描二维码,关注作者微信 +### 联系我们 -![](http://www.hollischuang.com/wp-content/uploads/2018/10/%E4%BA%8C%E7%BB%B4%E7%A0%81%E7%BE%8E%E5%8C%96-1.png) +欢迎关注作者的公众号,可以直接后台留言。 +![](/docs/contact/wechat-hollis.jpg) \ No newline at end of file diff --git a/basics/java-basic/basic-data-types.md b/basics/java-basic/basic-data-types.md deleted file mode 100644 index a4ea2d64..00000000 --- a/basics/java-basic/basic-data-types.md +++ /dev/null @@ -1,11 +0,0 @@ -Java中有8种基本数据类型 -分为三大类。 - -字符型:char - -布尔型:boolean - -数值型: -1.整型:byte、short、int、long 2.浮点型:float、double - -String不是基本数据类型,是引用类型。 \ No newline at end of file diff --git a/basics/java-basic/data-structure/list.md b/basics/java-basic/data-structure/list.md deleted file mode 100644 index e69de29b..00000000 diff --git a/basics/jvm/java-object-model.md b/basics/jvm/java-object-model.md deleted file mode 100644 index 3319d312..00000000 --- a/basics/jvm/java-object-model.md +++ /dev/null @@ -1,14 +0,0 @@ -> 本文是[《成神之路系列文章》](/catalog/catalog.md)中的一篇,主要是关于JVM的一些介绍。 -> -> 持续更新中 - -[JVM内存结构 VS Java内存模型 VS Java对象模型][2] - -[深入理解多线程(二)—— Java的对象模型][3] - -[深入理解多线程(三)—— Java的对象头][4] - - [1]: http://www.hollischuang.com/archives/1001 - [2]: http://www.hollischuang.com/archives/2509 - [3]: http://www.hollischuang.com/archives/1910 - [4]: http://www.hollischuang.com/archives/1953 \ No newline at end of file diff --git a/basics/jvm/jvm-memory-structure.md b/basics/jvm/jvm-memory-structure.md deleted file mode 100644 index 994b67d9..00000000 --- a/basics/jvm/jvm-memory-structure.md +++ /dev/null @@ -1,28 +0,0 @@ -## Java内存模型,Java内存管理,Java堆和栈,垃圾回收 - -> 本文是[《成神之路系列文章》](/catalog/catalog.md)中的一篇,主要是关于JVM的一些介绍。 -> -> 持续更新中 - -参考文章: - -[Java虚拟机的内存组成以及堆内存介绍][1] - -[Java堆和栈看这篇就够][2] - -[Java虚拟机的堆、栈、堆栈如何去理解?][3] - -[Java 内存之方法区和运行时常量池][4] - -[从0到1起步-跟我进入堆外内存的奇妙世界][5] - -[JVM内存结构 VS Java内存模型 VS Java对象模型][6] - -参考书籍:《深入理解Java虚拟机》 - - [1]: http://www.hollischuang.com/archives/80 - [2]: https://iamjohnnyzhuang.github.io/java/2016/07/12/Java%E5%A0%86%E5%92%8C%E6%A0%88%E7%9C%8B%E8%BF%99%E7%AF%87%E5%B0%B1%E5%A4%9F.html - [3]: https://www.zhihu.com/question/29833675 - [4]: https://mritd.me/2016/03/22/Java-%E5%86%85%E5%AD%98%E4%B9%8B%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C%E8%BF%90%E8%A1%8C%E6%97%B6%E5%B8%B8%E9%87%8F%E6%B1%A0/ - [5]: https://www.jianshu.com/p/50be08b54bee - [6]: http://www.hollischuang.com/archives/2509 diff --git a/catalog/catalog.md b/catalog/catalog.md deleted file mode 100644 index 86a23569..00000000 --- a/catalog/catalog.md +++ /dev/null @@ -1,88 +0,0 @@ -[Java工程师成神之路](https://github.com/hollischuang/toBeTopJavaer)一文介绍了一个普通的Java工程师想要成神需要学习的所有相关知识点。很多内容介绍都是直接抛了一个链接,并且大部分都是英文文档或者相关技术的官网。 - -本系列文章主要从头开始总结[Java工程师成神之路](https://github.com/hollischuang/toBeTopJavaer)一文中介绍的所有知识点。 - -编程界有一句老话,叫做不要重复造轮子。虽然我并不完全认同这句话。但是本系列文章的原则类似:如果网上有人针对某知识点有十分详尽的介绍,那么就直接附上原文地址(绝对不做伸手党),如果实在没有写的好的文章,那么笔者就尝试着总结一下。 - -总结该系列文章的最主要目的就是和所有Javaer共同学习与进步。该系列文章的进展情况会在本文章和我的微信公众帐号中进行同步。希望和大家共同进步!每一个专题学习完之后欢迎大家通过微信公众号(Hollis)和我一起交流。 - -Here We Go! - -### 目录: - -[《成神之路-基础篇》JVM——JVM内存结构](/basics/jvm/jvm-memory-structure.md) - -[《成神之路-基础篇》JVM——Java内存模型](/basics/jvm/java-memory-model.md) - -[《成神之路-基础篇》JVM——Java对象模型](/basics/jvm/java-object-model.md) - -[《成神之路-基础篇》JVM——HotSpot][5] - -[《成神之路-基础篇》JVM——垃圾回收][6] - -[《成神之路-基础篇》JVM——JVM参数及调优][7] - -[《成神之路-基础篇》JVM——常用Java命令][8] - -[《成神之路-基础篇》编译与反编译][9] - -[《成神之路-基础篇》Java基础知识——阅读源代码][10] - -[《成神之路-基础篇》Java基础知识——String相关][11] - -[《成神之路-基础篇》Java基础知识——Java中各种关键字][12] - -[《成神之路-基础篇》Java基础知识——自动拆装箱][13] - -[《成神之路-基础篇》Java基础知识——枚举][14] - -[《成神之路-基础篇》Java基础知识——反射][15] - -[《成神之路-基础篇》Java基础知识——序列化][16] - -[《成神之路-基础篇》Java基础知识——JMS][17] - -[《成神之路-基础篇》Java基础知识——泛型][18] - -[《成神之路-基础篇》Java基础知识——常用的Java工具库][19] - -[《成神之路-基础篇》Java基础知识——单元测试][20] - -[《成神之路-进阶篇》设计模式——设计模式合集][21] - -[《成神之路-高级篇》Java并发编程——锁][22] - -[《成神之路-高级篇》大数据知识—— Zookeeper合集][23] - -[《成神之路-高级篇》网络安全知识—— 解决webx的xss和csrf漏洞][24] - -[《成神之路-进阶篇》网络编程知识——常用协议][25] - -[《成神之路-扩展篇》分布式—— 分布式合集][26] - - [1]: http://www.hollischuang.com/archives/489 - [2]: http://www.hollischuang.com/archives/2374 - [3]: http://www.hollischuang.com/archives/1003 - [4]: http://www.hollischuang.com/archives/2814 - [5]: http://www.hollischuang.com/archives/2822 - [6]: http://www.hollischuang.com/archives/2376 - [7]: http://www.hollischuang.com/archives/2378 - [8]: http://www.hollischuang.com/archives/1034 - [9]: http://www.hollischuang.com/archives/2817 - [10]: http://www.hollischuang.com/archives/1007 - [11]: http://www.hollischuang.com/archives/1330 - [12]: http://www.hollischuang.com/archives/1327 - [13]: http://www.hollischuang.com/archives/2700 - [14]: http://www.hollischuang.com/archives/2829 - [15]: http://www.hollischuang.com/archives/1163 - [16]: http://www.hollischuang.com/archives/1158 - [17]: http://www.hollischuang.com/archives/1226 - [18]: http://www.hollischuang.com/archives/1182 - [19]: http://www.hollischuang.com/archives/2836 - [20]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95 - [21]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F - [22]: http://www.hollischuang.com/archives/2842 - [23]: http://www.hollischuang.com/?s=Zookeeper - [24]: http://www.hollischuang.com/archives/69 - [25]: http://www.hollischuang.com/archives/2846 - [26]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E5%88%86%E5%B8%83%E5%BC%8F diff --git a/docs/README.md b/docs/README.md index 3d17608b..27ab199e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,6 +5,45 @@ | 主要版本 | 更新时间 | 备注 | | ---- | ---------- | -------------- | -| v1.0 | 2015-08-01 | 首次发布 | -| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 | +| v3.0 | 2020-03-29 | 知识体系完善
通过GitHub Page搭建,便于阅读| | v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通;
进一步完善知识体系;
新技术补充;| +| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 | +| v1.0 | 2015-08-01 | 首次发布 | + +目前正在更新中... + +欢迎大家参与共建~ + +### 关于作者 + +Hollis,阿里巴巴技术专家,51CTO专栏作家,CSDN博客专家,掘金优秀作者,《程序员的三门课》联合作者,《Java工程师成神之路》系列文章作者;热衷于分享计算机编程相关技术,博文全网阅读量上千万。 + + +### 开源协议 + +本着互联网的开放精神,本项目采用开放的[GPL]协议进行许可。 + + +### 参与共建 + +如果您对本项目中的内容有建议或者意见 + +如果你对本项目中未完成的章节感兴趣 + +欢迎提出专业方面的修改建议及供稿,供稿只接受原创 + +请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出 + +如果本项目中的内容侵犯了您的任何权益,欢迎通过邮箱(hollischuang@gmail)与我联系 + +### 在线阅读地址 + +GitHub Pages 完整阅读:[进入](https://hollischuang.github.io/toBeTopJavaer/) + +Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快) + +### 联系我们 + +欢迎关注作者的公众号,可以直接后台留言。 + +![](contact/wechat-hollis.jpg) \ No newline at end of file diff --git a/docs/_coverpage.md b/docs/_coverpage.md index 0ba84928..41a33050 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -1,5 +1,3 @@ -
-

@@ -18,4 +16,4 @@ -Get Started

+开始阅读

diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 73240027..23ed0fe3 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,12 +1,1349 @@ +* 基础篇 -## 一、基础篇 + * 面向对象 + + * 什么是面向对象 + + * [面向对象与面向过程](/basics/object-oriented/object-oriented-vs-procedure-oriented.md) + + * [面向对象的三大基本特征](/basics/object-oriented/characteristics.md) + + * [面向对象的五大基本原则](/basics/object-oriented/principle.md) + * 平台无关性 + + * [Java如何实现的平台无关性的](/basics/object-oriented/platform-independent.md) + + * [JVM还支持哪些语言](/basics/object-oriented/jvm-language.md) + + * 值传递 + + * [值传递、引用传递](/basics/object-oriented/java-pass-by.md) + + * [为什么说Java中只有值传递](/basics/object-oriented/why-pass-by-reference.md) + + * 封装、继承、多态 + * [什么是多态](/basics/object-oriented/polymorphism.md) + + * [方法重写与重载](/basics/object-oriented/overloading-vs-overriding.md) + * Java的继承与实现 + + * [Java的继承与组合](/basics/object-oriented/inheritance-composition.md) + + * [构造函数与默认构造函数](/basics/object-oriented/constructor.md) + + * [类变量、成员变量和局部变量](/basics/object-oriented/variable.md) + + * [成员变量和方法作用域](/basics/object-oriented/scope.md) + + * Java基础知识 + + * 基本数据类型 -### 面向对象 + * [8种基本数据类型](/basics/java-basic/basic-data-types.md) -#### 什么是面向对象 + * [整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md) -* [面向对象、面向过程](../basics/java-basic/object-oriented-vs-procedure-oriented.md) + * [什么是浮点型?](/basics/java-basic/float.md) -* [面向对象的三大基本特征](/basics/java-basic/characteristics.md) + * [什么是单精度和双精度?](/basics/java-basic/single-double-float.md) -* [五大基本原则](/basics/java-basic/principle.md) + * [为什么不能用浮点型表示金额?](/basics/java-basic/float-amount.md) + + * 自动拆装箱 + + * [自动拆装箱](/basics/java-basic/boxing-unboxing.md) + + * [Integer的缓存机制](/basics/java-basic/integer-cache.md) + + * String + + * [字符串的不可变性](/basics/java-basic/final-string.md) + + * [JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md) + + * replaceFirst、replaceAll、replace区别 + + * [String对“+”的重载](/basics/java-basic/string-append.md) + + * [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md) + + * [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md) + + * [switch对String的支持](/basics/java-basic/switch-string.md) + * 字符串池 + * 常量池(运行时常量池、Class常量池) + * intern + * Java中各种关键字 + * transient + * instanceof + * volatile + * synchronized + * final + * static + * const + * 集合类 + + * 常用集合类的使用 + + * [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md) + + * [SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md) + + * [HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md) + + * [Set和List区别?](/basics/java-basic/set-vs-list.md) + + * [Set如何保证元素不重复?](/basics/java-basic/set-repetition.md) + + * [Java 8中stream相关用法](/basics/java-basic/stream.md) + + * Apache集合处理工具类的使用 + + * 不同版本的JDK中HashMap的实现的区别以及原因 + + * [Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md) + + * [Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md) + + * [Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md) + + * [fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md) + + * [CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md) + + * [ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md) + + * 枚举 + + * [枚举的用法](/basics/java-basic/enum-usage.md) + + * [枚举的实现](/basics/java-basic/enum-impl.md) + + * [枚举与单例](/basics/java-basic/enum-singleton.md) + + * Enum类 + + * [Java枚举如何比较](/basics/java-basic/enum-compare.md) + + * [switch对枚举的支持](/basics/java-basic/enum-switch.md) + + * [枚举的序列化如何实现](/basics/java-basic/enum-serializable.md) + + * [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md) + + * IO + + * [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md) + + * [输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md) + + * [同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md) + + * [阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md) + + * [Linux 5种IO模型](/basics/java-basic/linux-io.md) + + * [BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md) + + * netty + + * 反射 + + * [反射](/basics/java-basic/reflection.md)与工厂模式、 + + * [反射有什么作用](/basics/java-basic/usage-of-reflection.md) + + * [Class类](/basics/java-basic/Class.md) + + * `java.lang.reflect.*` + + * 动态代理 + + * [静态代理](/basics/java-basic/static-proxy.md) + + * [动态代理](/basics/java-basic/dynamic-proxy.md) + + * [动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md) + + * [动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md) + + * [AOP](/basics/java-basic/aop-vs-proxy.md) + + * 序列化 + + * [什么是序列化与反序列化](basics/java-basic/serialize.md) + * [Java如何实现序列化与反序列化](basics/java-basic/serialize-in-java.md) + * [Serializable 和 Externalizable 有何不同](basics/java-basic/diff-serializable-vs-externalizable.md) + * 为什么序列化 + * [serialVersionUID](basics/java-basic/serialVersionUID.md) + * [为什么serialVersionUID不能随便改](basics/java-basic/serialVersionUID-modify.md) + * [transient](basics/java-basic/transient.md) + * [序列化底层原理](basics/java-basic/serialize-principle.md) + * [序列化与单例模式](basics/java-basic/serialize-singleton.md) + * [protobuf](basics/java-basic/protobuf.md) + * 为什么说序列化并不安全 + + * 注解 + + * [元注解](/basics/java-basic/meta-annotation.md) + * [自定义注解](/basics/java-basic/custom-annotation.md) + * Java中常用注解使用 + * 注解与反射的结合 + + * [如何自定义一个注解?](/basics/java-basic/create-annotation.md) + + * [Spring常用注解](/basics/java-basic/annotation-in-spring.md) + * JMS + + * 什么是Java消息服务 + * JMS消息传送模型 + + * JMX + + * `java.lang.management.*` + * `javax.management.*` + + * 泛型 + + * [什么是泛型](/basics/java-basic/generics.md) + * [类型擦除](/basics/java-basic/type-erasue.md) + * [泛型带来的问题](/basics/java-basic/generics-problem.md) + * [泛型中K T V E ? object等的含义](/basics/java-basic/k-t-v-e.md) + * 泛型各种用法 + + * [限定通配符和非限定通配符](/basics/java-basic/Wildcard-Character.md) + * [上下界限定符extends 和 super](/basics/java-basic/extends-vs-super.md) + + * [`List`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md) + + * [`List`和`List`之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md) + + * 单元测试 + + * junit + * mock + * mockito + * 内存数据库(h2) + + * 正则表达式 + + * `java.lang.util.regex.*` + + * 常用的Java工具库 + + * `commons.lang` + * `commons.*...` + * `guava-libraries` + * `netty` + + * API&SPI + + * API + * [API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md) + + * [如何定义SPI](/basics/java-basic/create-spi.md) + * [SPI的实现原理](/basics/java-basic/spi-principle.md) + + * 异常 + + * [Error和Exception](/basics/java-basic/error-vs-exception.md) + * [异常类型](/basics/java-basic/exception-type.md) + * [异常相关关键字](/basics/java-basic/keyword-about-exception.md) + * [正确处理异常](/basics/java-basic/handle-exception.md) + * [自定义异常](/basics/java-basic/define-exception.md) + * [异常链](/basics/java-basic/exception-chain.md) + * [try-with-resources](/basics/java-basic/try-with-resources.md) + * [finally和return的执行顺序](/basics/java-basic/order-about-finllly-return.md) + + * 时间处理 + + * [时区](/basics/java-basic/time-zone.md) + * [冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md) + * [时间戳](/basics/java-basic/timestamp.md) + * Java中时间API + + * [格林威治时间](/basics/java-basic/GMT.md) + * [CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md) + + * [SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md) + + * [Java 8中的时间处理](/basics/java-basic/time-in-java8.md) + + * [如何在东八区的计算机上获取美国时间](/basics/java-basic/get-los_angeles-time.md) + + * [yyyy和YYYY有什么区别?](/basics/java-basic/YYYY-vs-yyyy.md) + + * 编码方式 + + * [什么是ASCII?](/basics/java-basic/ASCII.md) + + * [Unicode](/basics/java-basic/UNICODE.md) + + * [有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md) + + * [UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md) + + * 有了UTF8为什么还需要GBK? + + * [GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md) + + * [URL编解码](/basics/java-basic/url-encode.md) + * [Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md) + + * 如何解决乱码问题 + + * 语法糖 + + * [Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md) + + * [语法糖介绍](/basics/java-basic/syntactic-sugar.md) + + * 阅读源代码 + + * String + * Integer + * Long + * Enum + * BigDecimal + * ThreadLocal + * ClassLoader & URLClassLoader + * ArrayList & LinkedList + * HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap + * HashSet & LinkedHashSet & TreeSet + + * Java并发编程 + + * 并发与并行 + + * [什么是并发](/basics/concurrent-coding/concurrent.md) + + * [什么是并行](/basics/concurrent-coding/parallel.md) + + * [并发与并行的区别](/basics/concurrent-coding/concurrent-vs-parallel.md) + + * 线程 + + * 线程的实现 + * 线程的状态 + * 线程优先级 + * 线程调度 + * 创建线程的多种方式 + * 守护线程 + * 线程与进程的区别 + + * 线程池 + + * 自己设计线程池 + * submit() 和 execute() + * 线程池原理 + * 为什么不允许使用Executors创建线程池 + + * 线程安全 + + * [死锁?](/basics/concurrent-coding/deadlock-java-level.md) + * 死锁如何排查 + * 线程安全和内存模型的关系 + + * 锁 + + * CAS + * 乐观锁与悲观锁 + * 数据库相关锁机制 + * 分布式锁 + * 偏向锁 + * 轻量级锁 + * 重量级锁 + * monitor + * 锁优化 + * 锁消除 + * 锁粗化 + * 自旋锁 + * 可重入锁 + * 阻塞锁 + + * 死锁 + + * 死锁的原因 + + * 死锁的解决办法 + + * synchronized + + * [synchronized是如何实现的?](/basics/concurrent-coding/synchronized.md) + + * synchronized和lock之间关系 + * 不使用synchronized如何实现一个线程安全的单例 + * synchronized和原子性、可见性和有序性之间的关系 + + * volatile + + * happens-before + * 内存屏障 + * 编译器指令重排和CPU指令重排 + + * volatile的实现原理 + + * volatile和原子性 + * 可见性和有序性之间的关系 + + * 有了symchronized为什么还需要volatile + + * sleep 和 wait + + * wait 和 notify + + * notify 和 notifyAll + + * ThreadLocal + + * 写一个死锁的程序 + + * 写代码来解决生产者消费者问题 + + * 并发包 + + * 阅读源代码,并学会使用 + + * Thread + * Runnable + * Callable + * ReentrantLock + * ReentrantReadWriteLock + * Atomic* + * Semaphore + * CountDownLatch + * ConcurrentHashMap + * Executors + +* 底层篇 + + * JVM + + * JVM内存结构 + + * class文件格式 + * 运行时数据区 + * 堆和栈区别 + * [Java中的对象一定在堆上分配吗?](/basement/jvm/stack-alloc.md) + + * Java内存模型 + + * 计算机内存模型 + * 缓存一致性 + * MESI协议 + + * 可见性 + * 原子性 + * 顺序性 + * happens-before + + * 内存屏障 + * synchronized + * volatile + * final + * 锁 + + * 垃圾回收 + + * GC算法:标记清除、引用计数、复制、标记压缩、分代回收、增量式回收 + + * GC参数 + * 对象存活的判定 + * 垃圾收集器(CMS、G1、ZGC、Epsilon) + + * JVM参数及调优 + + * -Xmx + * -Xmn + * -Xms + * Xss + * -XX:SurvivorRatio + + * -XX:PermSize + * -XX:MaxPermSize + * -XX:MaxTenuringThreshold + + * Java对象模型 + + * oop-klass + * 对象头 + + * HotSpot + + * 即时编译器 + * 编译优化 + + * 虚拟机性能监控与故障处理工具 + + * jps + * jstack + * jmap + * jstat + * jconsole + * jinfo + * jhat + * javap + * btrace + * TProfiler + * jlink + * Arthas + + * 类加载机制 + + * classLoader + * 类加载过程 + * 双亲委派(破坏双亲委派) + * 模块化(jboss modules、osgi、jigsaw) + + * 编译与反编译 + + * 什么是编译(前端编译、后端编译) + * 什么是反编译 + + * JIT + * JIT优化(逃逸分析、栈上分配、标量替换、锁优化) + + * 编译工具:javac + + * 反编译工具:javap 、jad 、CRF + +* 进阶篇 + * Java底层知识 + + * 字节码 + + * class文件格式 + + * CPU缓存,L1,L2,L3和伪共享 + + * 尾递归 + + * 位运算 + + 用位运算实现加、减、乘、除、取余 + + * 设计模式 + + * 设计模式的六大原则 + + * 开闭原则(Open Close Principle) + * 里氏代换原则(Liskov Substitution Principle) + * 依赖倒转原则(Dependence Inversion Principle) + * 接口隔离原则(Interface Segregation Principle) + * 迪米特法则(最少知道原则)(Demeter Principle) + * 合成复用原则(Composite Reuse Principle) + + * 了解23种设计模式 + + * 创建型模式 + * 单例模式 + * 抽象工厂模式 + * 建造者模式 + * 工厂模式 + * 原型模式 + + * 结构型模式 + * 适配器模式 + * 桥接模式 + * 装饰模式 + * 组合模式 + * 外观模式 + * 享元模式 + * 代理模式 + + * 行为型模式 + * 模版方法模式 + * 命令模式 + * 迭代器模式 + * 观察者模式 + * 中介者模式 + * 备忘录模式 + * 解释器模式 + * 状态模式 + * 策略模式 + * 责任链模式 + * 访问者模式 + + * 会使用常用设计模式 + + * 单例的七种写法 + * 懒汉——线程不安全 + * 懒汉——线程安全 + * 饿汉 + * 饿汉——变种 + * 静态内部类 + * 枚举 + * 双重校验锁 + + * 工厂模式 + * 适配器模式 + * 策略模式 + * 模板方法模式 + * 观察者模式 + * 外观模式 + * 代理模式 + + * 不用synchronized和lock,实现线程安全的单例模式 + + * 实现AOP + + * 实现IOC + + * nio和reactor设计模式 + + * 网络编程知识 + + * tcp、udp、http、https等常用协议 + + * 三次握手与四次关闭 + * 流量控制和拥塞控制 + * OSI七层模型 + * tcp粘包与拆包 + + * http/1.0 http/1.1 http/2之间的区别 + + * http中 get和post区别 + + * 常见的web请求返回的状态码 + + * 404、302、301、500分别代表什么 + + * http/2 + + * Java RMI,Socket,HttpClient + + * cookie 与 session + + * cookie被禁用,如何实现session + + * 用Java写一个简单的静态文件的HTTP服务器 + + * 了解nginx和apache服务器的特性并搭建一个对应的服务器 + + * 用Java实现FTP、SMTP协议 + + * 进程间通讯的方式 + + * 什么是CDN?如果实现? + + * DNS? + + * 什么是DNS + * 记录类型:A记录、CNAME记录、AAAA记录等 + + * 域名解析 + * 根域名服务器 + + * DNS污染 + * DNS劫持 + * 公共DNS:114 DNS、Google DNS、OpenDNS + + * 反向代理 + + * 正向代理 + * 反向代理 + + * 反向代理服务器 + + * 框架知识 + + * Servlet + + * 生命周期 + + * 线程安全问题 + + * filter和listener + + * web.xml中常用配置及作用 + + * Hibernate + + * 什么是OR Mapping + + * Hibernate的缓存机制 + + * Hibernate的懒加载 + + * Hibernate/Ibatis/MyBatis之间的区别 + + * Spring + + * Bean的初始化 + + * AOP原理 + + * 实现Spring的IOC + + * spring四种依赖注入方式 + + * Spring MVC + + * 什么是MVC + + * Spring mvc与Struts mvc的区别 + + * Spring Boot + + * Spring Boot 2.0 + * 起步依赖 + * 自动配置 + + * Spring Boot的starter原理 + * 自己实现一个starter + + * Spring Security + + * Spring Cloud + + * 服务发现与注册:Eureka、Zookeeper、Consul + + * 负载均衡:Feign、Spring Cloud Loadbalance + + * 服务配置:Spring Cloud Config + + * 服务限流与熔断:Hystrix + + * 服务链路追踪:Dapper + + * 服务网关、安全、消息 + + * 应用服务器知识 + + * JBoss + + * tomcat + + * jetty + + * Weblogic + + * 工具 + + * git & svn + + * maven & gradle + + * Intellij IDEA + + * 常用插件: + * Maven Helper + * FindBugs-IDEA + * 阿里巴巴代码规约检测 + * GsonFormat + + * Lombok plugin + * .ignore + * Mybatis plugin + +* 高级篇 + + * 新技术 + + * Java 8 + + * lambda表达式 + * Stream API + * 时间API + + * Java 9 + + * Jigsaw + * Jshell + * Reactive Streams + + * Java 10 + + * 局部变量类型推断 + * G1的并行Full GC + * ThreadLocal握手机制 + + * Java 11 + + * ZGC + * Epsilon + * 增强var + * Java 12 + + * Switch 表达式 + + * Java 13 + + * Text Blocks + * Dynamic CDS Archives + + * Java 14 + + * Java打包工具 + * 更有价值的NullPointerException + * record类型 + + * Spring 5 + + * 响应式编程 + + * Spring Boot 2.0 + + * http/2 + + * http/3 + + * 性能优化 + + * 使用单例 + * 使用Future模式 + * 使用线程池 + * 选择就绪 + * 减少上下文切换 + * 减少锁粒度 + * 数据压缩 + * 结果缓存 + + * 线上问题分析 + + * dump获取 + + * 线程Dump + * 内存Dump + * gc情况 + + * dump分析 + + * 分析死锁 + * 分析内存泄露 + + * dump分析及获取工具 + + * jstack + * jstat + * jmap + * jhat + * Arthas + + * 自己编写各种outofmemory,stackoverflow程序 + + * HeapOutOfMemory + * Young OutOfMemory + * MethodArea OutOfMemory + * ConstantPool OutOfMemory + * DirectMemory OutOfMemory + * Stack OutOfMemory Stack OverFlow + + * Arthas + + * jvm相关 + * class/classloader相关 + * monitor/watch/trace相关 + + * options + * 管道 + * 后台异步任务 + + * 常见问题解决思路 + + * 内存溢出 + * 线程死锁 + * 类加载冲突 + + * 使用工具尝试解决以下问题,并写下总结 + + * 当一个Java程序响应很慢时如何查找问题 + + * 当一个Java程序频繁FullGC时如何解决问题 + + * 如何查看垃圾回收日志 + + * 当一个Java应用发生OutOfMemory时该如何解决 + + * 如何判断是否出现死锁 + + * 如何判断是否存在内存泄露 + + * 使用Arthas快速排查Spring Boot应用404/401问题 + + * 使用Arthas排查线上应用日志打满问题 + + * 利用Arthas排查Spring Boot应用NoSuchMethodError + + * 编译原理知识 + + * 编译与反编译 + + * Java代码的编译与反编译 + + * Java的反编译工具 + + * javap + * jad + * CRF + + * 即时编译器 + + * 词法分析,语法分析(LL算法,递归下降算法,LR算法),语义分析,运行时环境,中间代码,代码生成,代码优化 + + * 操作系统知识 + + * Linux的常用命令 + + * 进程间通信 + + * 进程同步 + + * 生产者消费者问题 + * 哲学家就餐问题 + * 读者写者问题 + + * 缓冲区溢出 + + * 分段和分页 + + * 虚拟内存与主存 + + * 虚拟内存管理 + + * 换页算法 + + * 数据库知识 + + * MySql 执行引擎 + + * MySQL 执行计划 + + * 如何查看执行计划 + * 如何根据执行计划进行SQL优化 + + * 索引 + + * Hash索引 + * B树索引(B+树、和B树、R树) + + * 普通索引 + * 唯一索引 + + * 覆盖索引 + * 最左前缀原则 + * 索引下推 + + * SQL优化 + + * 数据库事务和隔离级别 + + * 事务的隔离级别 + * 事务能不能实现锁的功能 + + * 数据库锁 + + * 行锁 + * 表锁 + * 使用数据库锁实现乐观锁 + + * 连接 + + * 内连接 + * 左连接 + * 右连接 + + * 数据库主备搭建 + + * binlog + + * redolog + + * 内存数据库 + + * h2 + + * 分库分表 + + * 读写分离 + + * 常用的nosql数据库 + + * redis + * memcached + + * 分别使用数据库锁、NoSql实现分布式锁 + + * 性能调优 + + * 数据库连接池 + + * 数据结构与算法知识 + + * 简单的数据结构 + + * 栈 + * 队列 + * 链表 + * 数组* 哈希表 + + * 栈和队列的相同和不同之处 + + * 栈通常采用的两种存储结构 + + * 树 + + * 二叉树 + * 字典树 + * 平衡树 + * 排序树 + * B树 + * B+树 + * R树 + * 多路树 + * 红黑树 + + * 堆 + + * 大根堆 + * 小根堆 + + * 图 + + * 有向图 + * 无向图 + * 拓扑 + + * 排序算法 + + * 稳定的排序 + * 冒泡排序 + * 插入排序 + * 鸡尾酒排序 + * 桶排序 + * 计数排序 + * 归并排序 + * 原地归并排序 + * 二叉排序树排序 + * 鸽巢排序 + * 基数排序 + * 侏儒排序 + * 图书馆排序 + * 块排序 + + * 不稳定的排序 + * 选择排序 + * 希尔排序 + * Clover排序算法 + * 梳排序 + * 堆排序 + * 平滑排序 + * 快速排序 + * 内省排序 + * 耐心排序 + + * 各种排序算法和时间复杂度 + + * 深度优先和广度优先搜索 + + * 全排列 + * 贪心算法 + * KMP算法 + * hash算法 + + * 海量数据处理 + + * 分治 + * hash映射 + * 堆排序 + * 双层桶划分 + * Bloom Filter + * bitmap + * 数据库索引 + * mapreduce等。 + + * 两个栈实现队列,和两个队列实现栈 + + * 大数据知识 + + * Zookeeper + + * 基本概念 + * 常见用法 + + * Solr,Lucene,ElasticSearch + + * 在linux上部署solr + * solrcloud,新增、删除、查询索引 + + * Storm,流式计算,了解Spark,S4 + + * 在linux上部署storm + * 用zookeeper做协调 + * 运行storm hello world + * local和remote模式运行调试storm topology + + * Hadoop,离线计算 + + * HDFS + * MapReduce + + * 分布式日志收集flume,kafka,logstash + + * 数据挖掘,mahout + + * 网络安全知识 + + * XSS + + * XSS的防御 + + * CSRF + + * 注入攻击 + + * SQL注入 + * XML注入 + * CRLF注入 + + * 文件上传漏洞 + + * 加密与解密 + + * 对称加密 + * 非对称加密 + * 哈希算法 + * 加盐哈希算法 + + * MD5,SHA1、DES、AES、RSA、DSA + + * 彩虹表 + + * DDOS攻击 + + * DOS攻击 + * DDOS攻击 + + * memcached为什么可以导致DDos攻击 + * 什么是反射型DDoS + + * 如何通过Hash碰撞进行DOS攻击 + + * SSL、TLS,HTTPS + + * 用openssl签一个证书部署到apache或nginx + +* 架构篇 + + * 分布式 + + * 数据一致性 + * 服务治理 + * 服务降级 + * 分布式理论 + * 2PC + * 3PC + * CAP + * BASE + + * 分布式事务 + * 可靠消息最终一致性 + * 最大努力通知 + * TCC + + * Dubbo + + * 服务注册 + * 服务发现 + * 服务治理 + + * 分布式数据库 + + * 怎样打造一个分布式数据库 + * 什么时候需要分布式数据库 + * mycat + * otter + * HBase + + * 分布式文件系统 + + * mfs + * fastdfs + + * 分布式缓存 + + * 缓存一致性 + * 缓存命中率 + * 缓存冗余 + + * 限流降级 + + * Hystrix + * Sentinal + + * 算法 + + * 共识算法 + * Raft协议 + * Paxos 算法与 Raft 算法 + * 拜占庭问题与算法 + + * 2PC + * 3PC + + * 微服务 + + * SOA + * 康威定律 + + * ServiceMesh + + * sidecar + + * Docker & Kubernets + + * Spring Boot + + * Spring Cloud + + * 高并发 + + * 分库分表 + + * CDN技术 + + * 消息队列 + + * ActiveMQ + + * 监控 + + * 监控什么 + + * CPU + * 内存 + * 磁盘I/O + * 网络I/O等 + + * 监控手段 + + * 进程监控 + * 语义监控 + * 机器资源监控 + * 数据波动 + + * 监控数据采集 + + * 日志 + * 埋点 + + * Dapper + + * 负载均衡 + + * tomcat负载均衡 + * Nginx负载均衡 + + * 四层负载均衡 + * 七层负载均衡 + + * DNS + + * DNS原理 + * DNS的设计 + + * CDN + + * 数据一致性 + +* 扩展篇 + + * 云计算 + + * IaaS + * SaaS + * PaaS + * 虚拟化技术 + * openstack + * Serverlsess + + * 搜索引擎 + + * Solr + * Lucene + * Nutch + * Elasticsearch + + * 权限管理 + + * Shiro + + * 区块链 + + * 哈希算法 + * Merkle树 + * 公钥密码算法 + * 共识算法 + * Raft协议 + * Paxos 算法与 Raft 算法 + * 拜占庭问题与算法 + * 消息认证码与数字签名 + + * 比特币 + + * 挖矿 + * 共识机制 + * 闪电网络 + * 侧链 + * 热点问题 + * 分叉 + + * 以太坊 + + * 超级账本 + + * 人工智能 + + * 数学基础 + * 机器学习 + * 人工神经网络 + * 深度学习 + * 应用场景 + + * 常用框架 + + * TensorFlow + * DeepLearning4J + + * IoT + + * 量子计算 + + * AR & VR + + * 其他语言 + + * Groovy + * Kotlin + * Python + * Go + * NodeJs + * Swift + * Rust +* 推荐书籍 + + *《深入理解Java虚拟机》 + + *《Effective Java》 + + *《深入分析Java Web技术内幕》 + + *《大型网站技术架构》 + + *《代码整洁之道》 + + *《架构整洁之道》 + + *《Head First设计模式》 + + *《maven实战》 + + *《区块链原理、设计与应用》 + + *《Java并发编程实战》 + + *《鸟哥的Linux私房菜》 + + *《从Paxos到Zookeeper》 + + *《架构即未来》 diff --git a/docs/basement/_sidebar.md b/docs/basement/_sidebar.md new file mode 100644 index 00000000..5129de0e --- /dev/null +++ b/docs/basement/_sidebar.md @@ -0,0 +1,89 @@ +* JVM + + * JVM内存结构 + + * class文件格式 + * 运行时数据区 + * 堆和栈区别 + * [Java中的对象一定在堆上分配吗?](/basement/jvm/stack-alloc.md) + + * Java内存模型 + + * 计算机内存模型 + * 缓存一致性 + * MESI协议 + + * 可见性 + * 原子性 + * 顺序性 + * happens-before + + * 内存屏障 + * synchronized + * volatile + * final + * 锁 + + * 垃圾回收 + + * GC算法:标记清除、引用计数、复制、标记压缩、分代回收、增量式回收 + + * GC参数 + * 对象存活的判定 + * 垃圾收集器(CMS、G1、ZGC、Epsilon) + + * JVM参数及调优 + + * -Xmx + * -Xmn + * -Xms + * Xss + * -XX:SurvivorRatio + + * -XX:PermSize + * -XX:MaxPermSize + * -XX:MaxTenuringThreshold + + * Java对象模型 + + * oop-klass + * 对象头 + + * HotSpot + + * 即时编译器 + * 编译优化 + + * 虚拟机性能监控与故障处理工具 + + * jps + * jstack + * jmap + * jstat + * jconsole + * jinfo + * jhat + * javap + * btrace + * TProfiler + * jlink + * Arthas + +* 类加载机制 + + * classLoader + * 类加载过程 + * 双亲委派(破坏双亲委派) + * 模块化(jboss modules、osgi、jigsaw) + +* 编译与反编译 + + * 什么是编译(前端编译、后端编译) + * 什么是反编译 + + * JIT + * JIT优化(逃逸分析、栈上分配、标量替换、锁优化) + + * 编译工具:javac + + * 反编译工具:javap 、jad 、CRF \ No newline at end of file diff --git a/docs/basement/jvm/_sidebar.md b/docs/basement/jvm/_sidebar.md new file mode 100644 index 00000000..57ba225e --- /dev/null +++ b/docs/basement/jvm/_sidebar.md @@ -0,0 +1,68 @@ +* JVM内存结构 + + * class文件格式 + * 运行时数据区 + * 堆和栈区别 + * [Java中的对象一定在堆上分配吗?](/basement/jvm/stack-alloc.md) + +* Java内存模型 + + * 计算机内存模型 + * 缓存一致性 + * MESI协议 + + * 可见性 + * 原子性 + * 顺序性 + * happens-before + + * 内存屏障 + * synchronized + * volatile + * final + * 锁 + +* 垃圾回收 + + * GC算法:标记清除、引用计数、复制、标记压缩、分代回收、增量式回收 + + * GC参数 + * 对象存活的判定 + * 垃圾收集器(CMS、G1、ZGC、Epsilon) + +* JVM参数及调优 + + * -Xmx + * -Xmn + * -Xms + * Xss + * -XX:SurvivorRatio + + * -XX:PermSize + * -XX:MaxPermSize + * -XX:MaxTenuringThreshold + +* Java对象模型 + + * oop-klass + * 对象头 + +* HotSpot + + * 即时编译器 + * 编译优化 + +* 虚拟机性能监控与故障处理工具 + + * jps + * jstack + * jmap + * jstat + * jconsole + * jinfo + * jhat + * javap + * btrace + * TProfiler + * jlink + * Arthas \ No newline at end of file diff --git a/basics/jvm/java-memory-model.md b/docs/basement/jvm/java-memory-model.md similarity index 100% rename from basics/jvm/java-memory-model.md rename to docs/basement/jvm/java-memory-model.md diff --git a/basics/java-basic/stack-alloc.md b/docs/basement/jvm/stack-alloc.md similarity index 100% rename from basics/java-basic/stack-alloc.md rename to docs/basement/jvm/stack-alloc.md diff --git a/docs/basics/_sidebar.md b/docs/basics/_sidebar.md new file mode 100644 index 00000000..bd228502 --- /dev/null +++ b/docs/basics/_sidebar.md @@ -0,0 +1,415 @@ +* 面向对象 + + * 什么是面向对象 + + * [面向对象与面向过程](/basics/object-oriented/object-oriented-vs-procedure-oriented.md) + + * [面向对象的三大基本特征](/basics/object-oriented/characteristics.md) + + * [面向对象的五大基本原则](/basics/object-oriented/principle.md) + * 平台无关性 + + * [Java如何实现的平台无关性的](/basics/object-oriented/platform-independent.md) + + * [JVM还支持哪些语言](/basics/object-oriented/jvm-language.md) + + * 值传递 + + * [值传递、引用传递](/basics/object-oriented/java-pass-by.md) + + * [为什么说Java中只有值传递](/basics/object-oriented/why-pass-by-reference.md) + + * 封装、继承、多态 + * [什么是多态](/basics/object-oriented/polymorphism.md) + + * [方法重写与重载](/basics/object-oriented/overloading-vs-overriding.md) + * Java的继承与实现 + + * [Java的继承与组合](/basics/object-oriented/inheritance-composition.md) + + * [构造函数与默认构造函数](/basics/object-oriented/constructor.md) + + * [类变量、成员变量和局部变量](/basics/object-oriented/variable.md) + + * [成员变量和方法作用域](/basics/object-oriented/scope.md) + +* Java基础知识 + + * 基本数据类型 + + * [8种基本数据类型](/basics/java-basic/basic-data-types.md) + + * [整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md) + + * [什么是浮点型?](/basics/java-basic/float.md) + + * [什么是单精度和双精度?](/basics/java-basic/single-double-float.md) + + * [为什么不能用浮点型表示金额?](/basics/java-basic/float-amount.md) + + * 自动拆装箱 + + * [自动拆装箱](/basics/java-basic/boxing-unboxing.md) + + * [Integer的缓存机制](/basics/java-basic/integer-cache.md) + + * String + + * [字符串的不可变性](/basics/java-basic/final-string.md) + + * [JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md) + + * replaceFirst、replaceAll、replace区别 + + * [String对“+”的重载](/basics/java-basic/string-append.md) + + * [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md) + + * [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md) + + * [switch对String的支持](/basics/java-basic/switch-string.md) + * 字符串池 + * 常量池(运行时常量池、Class常量池) + * intern + * Java中各种关键字 + * transient + * instanceof + * volatile + * synchronized + * final + * static + * const + * 集合类 + + * 常用集合类的使用 + + * [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md) + + * [SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md) + + * [HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md) + + * [Set和List区别?](/basics/java-basic/set-vs-list.md) + + * [Set如何保证元素不重复?](/basics/java-basic/set-repetition.md) + + * [Java 8中stream相关用法](/basics/java-basic/stream.md) + + * Apache集合处理工具类的使用 + + * 不同版本的JDK中HashMap的实现的区别以及原因 + + * [Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md) + + * [Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md) + + * [Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md) + + * [fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md) + + * [CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md) + + * [ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md) + + * 枚举 + + * [枚举的用法](/basics/java-basic/enum-usage.md) + + * [枚举的实现](/basics/java-basic/enum-impl.md) + + * [枚举与单例](/basics/java-basic/enum-singleton.md) + + * Enum类 + + * [Java枚举如何比较](/basics/java-basic/enum-compare.md) + + * [switch对枚举的支持](/basics/java-basic/enum-switch.md) + + * [枚举的序列化如何实现](/basics/java-basic/enum-serializable.md) + + * [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md) + + * IO + + * [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md) + + * [输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md) + + * [同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md) + + * [阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md) + + * [Linux 5种IO模型](/basics/java-basic/linux-io.md) + + * [BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md) + + * netty + + * 反射 + + * [反射](/basics/java-basic/reflection.md)与工厂模式、 + + * [反射有什么作用](/basics/java-basic/usage-of-reflection.md) + + * [Class类](/basics/java-basic/Class.md) + + * `java.lang.reflect.*` + + * 动态代理 + + * [静态代理](/basics/java-basic/static-proxy.md) + + * [动态代理](/basics/java-basic/dynamic-proxy.md) + + * [动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md) + + * [动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md) + + * [AOP](/basics/java-basic/aop-vs-proxy.md) + + * 序列化 + + * [什么是序列化与反序列化](basics/java-basic/serialize.md) + * [Java如何实现序列化与反序列化](basics/java-basic/serialize-in-java.md) + * [Serializable 和 Externalizable 有何不同](basics/java-basic/diff-serializable-vs-externalizable.md) + * 为什么序列化 + * [serialVersionUID](basics/java-basic/serialVersionUID.md) + * [为什么serialVersionUID不能随便改](basics/java-basic/serialVersionUID-modify.md) + * [transient](basics/java-basic/transient.md) + * [序列化底层原理](basics/java-basic/serialize-principle.md) + * [序列化与单例模式](basics/java-basic/serialize-singleton.md) + * [protobuf](basics/java-basic/protobuf.md) + * 为什么说序列化并不安全 + + * 注解 + + * [元注解](/basics/java-basic/meta-annotation.md) + * [自定义注解](/basics/java-basic/custom-annotation.md) + * Java中常用注解使用 + * 注解与反射的结合 + + * [如何自定义一个注解?](/basics/java-basic/create-annotation.md) + + * [Spring常用注解](/basics/java-basic/annotation-in-spring.md) + * JMS + + * 什么是Java消息服务 + * JMS消息传送模型 + + * JMX + + * `java.lang.management.*` + * `javax.management.*` + + * 泛型 + + * [什么是泛型](/basics/java-basic/generics.md) + * [类型擦除](/basics/java-basic/type-erasue.md) + * [泛型带来的问题](/basics/java-basic/generics-problem.md) + * [泛型中K T V E ? object等的含义](/basics/java-basic/k-t-v-e.md) + * 泛型各种用法 + + * [限定通配符和非限定通配符](/basics/java-basic/Wildcard-Character.md) + * [上下界限定符extends 和 super](/basics/java-basic/extends-vs-super.md) + + * [`List`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md) + + * [`List`和`List`之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md) + + * 单元测试 + + * junit + * mock + * mockito + * 内存数据库(h2) + + * 正则表达式 + + * `java.lang.util.regex.*` + + * 常用的Java工具库 + + * `commons.lang` + * `commons.*...` + * `guava-libraries` + * `netty` + + * API&SPI + + * API + * [API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md) + + * [如何定义SPI](/basics/java-basic/create-spi.md) + * [SPI的实现原理](/basics/java-basic/spi-principle.md) + + * 异常 + + * [Error和Exception](/basics/java-basic/error-vs-exception.md) + * [异常类型](/basics/java-basic/exception-type.md) + * [异常相关关键字](/basics/java-basic/keyword-about-exception.md) + * [正确处理异常](/basics/java-basic/handle-exception.md) + * [自定义异常](/basics/java-basic/define-exception.md) + * [异常链](/basics/java-basic/exception-chain.md) + * [try-with-resources](/basics/java-basic/try-with-resources.md) + * [finally和return的执行顺序](/basics/java-basic/order-about-finllly-return.md) + + * 时间处理 + + * [时区](/basics/java-basic/time-zone.md) + * [冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md) + * [时间戳](/basics/java-basic/timestamp.md) + * Java中时间API + + * [格林威治时间](/basics/java-basic/GMT.md) + * [CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md) + + * [SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md) + + * [Java 8中的时间处理](/basics/java-basic/time-in-java8.md) + + * [如何在东八区的计算机上获取美国时间](/basics/java-basic/get-los_angeles-time.md) + + * [yyyy和YYYY有什么区别?](/basics/java-basic/YYYY-vs-yyyy.md) + + * 编码方式 + + * [什么是ASCII?](/basics/java-basic/ASCII.md) + + * [Unicode](/basics/java-basic/UNICODE.md) + + * [有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md) + + * [UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md) + + * 有了UTF8为什么还需要GBK? + + * [GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md) + + * [URL编解码](/basics/java-basic/url-encode.md) + * [Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md) + + * 如何解决乱码问题 + + * 语法糖 + + * [Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md) + + * [语法糖介绍](/basics/java-basic/syntactic-sugar.md) + + * 阅读源代码 + + * String + * Integer + * Long + * Enum + * BigDecimal + * ThreadLocal + * ClassLoader & URLClassLoader + * ArrayList & LinkedList + * HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap + * HashSet & LinkedHashSet & TreeSet + +* Java并发编程 + + * 并发与并行 + + * [什么是并发](/basics/concurrent-coding/concurrent.md) + + * [什么是并行](/basics/concurrent-coding/parallel.md) + + * [并发与并行的区别](/basics/concurrent-coding/concurrent-vs-parallel.md) + + * 线程 + + * 线程的实现 + * 线程的状态 + * 线程优先级 + * 线程调度 + * 创建线程的多种方式 + * 守护线程 + * 线程与进程的区别 + + * 线程池 + + * 自己设计线程池 + * submit() 和 execute() + * 线程池原理 + * 为什么不允许使用Executors创建线程池 + + * 线程安全 + + * [死锁?](/basics/concurrent-coding/deadlock-java-level.md) + * 死锁如何排查 + * 线程安全和内存模型的关系 + + * 锁 + + * CAS + * 乐观锁与悲观锁 + * 数据库相关锁机制 + * 分布式锁 + * 偏向锁 + * 轻量级锁 + * 重量级锁 + * monitor + * 锁优化 + * 锁消除 + * 锁粗化 + * 自旋锁 + * 可重入锁 + * 阻塞锁 + + * 死锁 + + * 死锁的原因 + + * 死锁的解决办法 + + * synchronized + + * [synchronized是如何实现的?](/basics/concurrent-coding/synchronized.md) + + * synchronized和lock之间关系 + * 不使用synchronized如何实现一个线程安全的单例 + * synchronized和原子性、可见性和有序性之间的关系 + + * volatile + + * happens-before + * 内存屏障 + * 编译器指令重排和CPU指令重排 + + * volatile的实现原理 + + * volatile和原子性 + * 可见性和有序性之间的关系 + + * 有了symchronized为什么还需要volatile + + * sleep 和 wait + + * wait 和 notify + + * notify 和 notifyAll + + * ThreadLocal + + * 写一个死锁的程序 + + * 写代码来解决生产者消费者问题 + +* 并发包 + + * 阅读源代码,并学会使用 + + * Thread + * Runnable + * Callable + * ReentrantLock + * ReentrantReadWriteLock + * Atomic* + * Semaphore + * CountDownLatch + * ConcurrentHashMap + * Executors + \ No newline at end of file diff --git a/docs/basics/concurrent-coding/_sidebar.md b/docs/basics/concurrent-coding/_sidebar.md new file mode 100644 index 00000000..c0144d92 --- /dev/null +++ b/docs/basics/concurrent-coding/_sidebar.md @@ -0,0 +1,102 @@ +* 并发与并行 + + * [什么是并发](/basics/concurrent-coding/concurrent.md) + + * [什么是并行](/basics/concurrent-coding/parallel.md) + + * [并发与并行的区别](/basics/concurrent-coding/concurrent-vs-parallel.md) + +* 线程 + + * 线程的实现 + * 线程的状态 + * 线程优先级 + * 线程调度 + * 创建线程的多种方式 + * 守护线程 + * 线程与进程的区别 + +* 线程池 + + * 自己设计线程池 + * submit() 和 execute() + * 线程池原理 + * 为什么不允许使用Executors创建线程池 + +* 线程安全 + + * [死锁?](/basics/concurrent-coding/deadlock-java-level.md) + * 死锁如何排查 + * 线程安全和内存模型的关系 + +* 锁 + + * CAS + * 乐观锁与悲观锁 + * 数据库相关锁机制 + * 分布式锁 + * 偏向锁 + * 轻量级锁 + * 重量级锁 + * monitor + * 锁优化 + * 锁消除 + * 锁粗化 + * 自旋锁 + * 可重入锁 + * 阻塞锁 + +* 死锁 + + * 死锁的原因 + + * 死锁的解决办法 + +* synchronized + + * [synchronized是如何实现的?](/basics/concurrent-coding/synchronized.md) + + * synchronized和lock之间关系 + * 不使用synchronized如何实现一个线程安全的单例 + * synchronized和原子性、可见性和有序性之间的关系 + +* volatile + + * happens-before + * 内存屏障 + * 编译器指令重排和CPU指令重排 + + * volatile的实现原理 + + * volatile和原子性 + * 可见性和有序性之间的关系 + + * 有了symchronized为什么还需要volatile + +* sleep 和 wait + +* wait 和 notify + +* notify 和 notifyAll + +* ThreadLocal + +* 写一个死锁的程序 + +* 写代码来解决生产者消费者问题 + +* 并发包 + +* 阅读源代码,并学会使用 + + * Thread + * Runnable + * Callable + * ReentrantLock + * ReentrantReadWriteLock + * Atomic* + * Semaphore + * CountDownLatch + * ConcurrentHashMap + * Executors + \ No newline at end of file diff --git a/docs/basics/concurrent-coding/concurrent-vs-parallel.md b/docs/basics/concurrent-coding/concurrent-vs-parallel.md new file mode 100644 index 00000000..d815168d --- /dev/null +++ b/docs/basics/concurrent-coding/concurrent-vs-parallel.md @@ -0,0 +1,7 @@ +Erlang 之父 Joe Armstrong 用一张比较形象的图解释了并发与并行的区别: + +![](http://www.hollischuang.com/wp-content/uploads/2018/12/CON.jpg) + +并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。 + +映射到计算机系统中,上图中的咖啡机就是CPU,两个队伍指的就是两个进程。 diff --git a/docs/basics/concurrent-coding/concurrent.md b/docs/basics/concurrent-coding/concurrent.md new file mode 100644 index 00000000..4f14ebbf --- /dev/null +++ b/docs/basics/concurrent-coding/concurrent.md @@ -0,0 +1,13 @@ +并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。 + +那么,操作系统视如何实现这种并发的呢? + +现在我们用到操作系统,无论是Windows、Linux还是MacOS等其实都是**多用户多任务分时操作系统**。使用这些操作系统的用户是可以“同时”干多件事的。 + +但是实际上,对于单CPU的计算机来说,在CPU中,同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。 + +如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。 + +由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所”停顿”,但用户察觉不出来,好像整个系统全由它”独占”似的。 + +所以,在单CPU的计算机中,我们看起来“同时干多件事”,其实是通过CPU时间片技术,并发完成的。 diff --git a/basics/java-basic/deadlock-java-level.md b/docs/basics/concurrent-coding/deadlock-java-level.md similarity index 100% rename from basics/java-basic/deadlock-java-level.md rename to docs/basics/concurrent-coding/deadlock-java-level.md diff --git a/docs/basics/concurrent-coding/parallel.md b/docs/basics/concurrent-coding/parallel.md new file mode 100644 index 00000000..2ab6b0d0 --- /dev/null +++ b/docs/basics/concurrent-coding/parallel.md @@ -0,0 +1,2 @@ + +并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。 \ No newline at end of file diff --git a/basics/java-basic/synchronized.md b/docs/basics/concurrent-coding/synchronized.md similarity index 100% rename from basics/java-basic/synchronized.md rename to docs/basics/concurrent-coding/synchronized.md diff --git a/docs/basics/java-basic/ASCII.md b/docs/basics/java-basic/ASCII.md new file mode 100644 index 00000000..ddfe639f --- /dev/null +++ b/docs/basics/java-basic/ASCII.md @@ -0,0 +1,16 @@ +ASCII( American Standard Code for InformationInterchange, 美国信息交换标准代码) 是基于拉丁字母的⼀套电脑编码系统, 主要⽤于显⽰现代英语和其他西欧语⾔。 + +它是现今最通⽤的单字节编码系统, 并等同于国际标准ISO/IEC646。 + +标准ASCII 码也叫基础ASCII码, 使⽤7 位⼆进制数( 剩下的1位⼆进制为0) 来表⽰所有的⼤写和⼩写字母, 数字0 到9、 标点符号, 以及在美式英语中使⽤的特殊控制字符。 + + +其中: + +0~31及127(共33个)是控制字符或通信专⽤字符( 其余为可显⽰字符) , 如控制符: LF( 换⾏) 、 CR( 回车) 、 FF( 换页) 、 DEL( 删除) 、 BS( 退格)、 BEL( 响铃) 等; 通信专⽤字符: SOH( ⽂头) 、 EOT( ⽂尾) 、 ACK( 确认) 等; + +ASCII值为8、 9、 10 和13 分别转换为退格、 制表、 换⾏和回车字符。 它们并没有特定的图形显⽰, 但会依不同的应⽤程序,⽽对⽂本显⽰有不同的影响 + +32~126(共95个)是字符(32是空格) , 其中48~57为0到9⼗个阿拉伯数字。 + +65~90为26个⼤写英⽂字母, 97~122号为26个⼩写英⽂字母, 其余为⼀些标点符号、 运算符号等。 \ No newline at end of file diff --git a/basics/java-basic/Arrays-asList.md b/docs/basics/java-basic/Arrays-asList.md similarity index 100% rename from basics/java-basic/Arrays-asList.md rename to docs/basics/java-basic/Arrays-asList.md diff --git a/basics/java-basic/CET-UTC-GMT-CST.md b/docs/basics/java-basic/CET-UTC-GMT-CST.md similarity index 100% rename from basics/java-basic/CET-UTC-GMT-CST.md rename to docs/basics/java-basic/CET-UTC-GMT-CST.md diff --git a/basics/java-basic/Class.md b/docs/basics/java-basic/Class.md similarity index 100% rename from basics/java-basic/Class.md rename to docs/basics/java-basic/Class.md diff --git a/basics/java-basic/Collection-vs-Collections.md b/docs/basics/java-basic/Collection-vs-Collections.md similarity index 100% rename from basics/java-basic/Collection-vs-Collections.md rename to docs/basics/java-basic/Collection-vs-Collections.md diff --git a/basics/java-basic/ConcurrentSkipListMap.md b/docs/basics/java-basic/ConcurrentSkipListMap.md similarity index 100% rename from basics/java-basic/ConcurrentSkipListMap.md rename to docs/basics/java-basic/ConcurrentSkipListMap.md diff --git a/basics/java-basic/CopyOnWriteArrayList.md b/docs/basics/java-basic/CopyOnWriteArrayList.md similarity index 100% rename from basics/java-basic/CopyOnWriteArrayList.md rename to docs/basics/java-basic/CopyOnWriteArrayList.md diff --git a/basics/java-basic/Enumeration-vs-Iterator.md b/docs/basics/java-basic/Enumeration-vs-Iterator.md similarity index 100% rename from basics/java-basic/Enumeration-vs-Iterator.md rename to docs/basics/java-basic/Enumeration-vs-Iterator.md diff --git a/basics/java-basic/GMT.md b/docs/basics/java-basic/GMT.md similarity index 100% rename from basics/java-basic/GMT.md rename to docs/basics/java-basic/GMT.md diff --git a/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md b/docs/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md similarity index 100% rename from basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md rename to docs/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md diff --git a/docs/basics/java-basic/README.md b/docs/basics/java-basic/README.md new file mode 100644 index 00000000..e6fef945 --- /dev/null +++ b/docs/basics/java-basic/README.md @@ -0,0 +1,12 @@ +## To Be Top Javaer - Java工程师成神之路 + +![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg) + + +| 主要版本 | 更新时间 | 备注 | +| ---- | ---------- | -------------- | +| v1.0 | 2015-08-01 | 首次发布 | +| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 | +| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通;
进一步完善知识体系;
新技术补充;| + +Java 基础 \ No newline at end of file diff --git a/basics/java-basic/StandardTime-vs-daylightSavingTime.md b/docs/basics/java-basic/StandardTime-vs-daylightSavingTime.md similarity index 100% rename from basics/java-basic/StandardTime-vs-daylightSavingTime.md rename to docs/basics/java-basic/StandardTime-vs-daylightSavingTime.md diff --git a/basics/java-basic/UNICODE.md b/docs/basics/java-basic/UNICODE.md similarity index 100% rename from basics/java-basic/UNICODE.md rename to docs/basics/java-basic/UNICODE.md diff --git a/basics/java-basic/UTF8-UTF16-UTF32.md b/docs/basics/java-basic/UTF8-UTF16-UTF32.md similarity index 100% rename from basics/java-basic/UTF8-UTF16-UTF32.md rename to docs/basics/java-basic/UTF8-UTF16-UTF32.md diff --git a/docs/basics/java-basic/Wildcard-Character.md b/docs/basics/java-basic/Wildcard-Character.md new file mode 100644 index 00000000..b9e2a1b0 --- /dev/null +++ b/docs/basics/java-basic/Wildcard-Character.md @@ -0,0 +1,10 @@ +`限定通配符`对类型进⾏限制, 泛型中有两种限定通配符: + +表示类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类 +表示类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类 + +泛型类型必须⽤限定内的类型来进⾏初始化,否则会导致编译错误。 + + + +`⾮限定通配符`表⽰可以⽤任意泛型类型来替代,类型为 \ No newline at end of file diff --git a/docs/basics/java-basic/YYYY-vs-yyyy.md b/docs/basics/java-basic/YYYY-vs-yyyy.md new file mode 100644 index 00000000..cfc55287 --- /dev/null +++ b/docs/basics/java-basic/YYYY-vs-yyyy.md @@ -0,0 +1,57 @@ +在使用SimpleDateFormat的时候,需要通过字母来描述时间元素,并组装成想要的日期和时间模式。常用的时间元素和字母的对应表(JDK 1.8)如下: + +![](http://www.hollischuang.com/wp-content/uploads/2020/01/15781278483147.jpg) + +可以看到,*y表示Year ,而Y表示Week Year* + + + +### 什么是Week Year + +我们知道,不同的国家对于一周的开始和结束的定义是不同的。如在中国,我们把星期一作为一周的第一天,而在美国,他们把星期日作为一周的第一天。 + +同样,如何定义哪一周是一年当中的第一周?这也是一个问题,有很多种方式。 + +比如下图是2019年12月-2020年1月的一份日历。 + +![](http://www.hollischuang.com/wp-content/uploads/2020/01/15781286552869.jpg) + + + +到底哪一周才算2020年的第一周呢?不同的地区和国家,甚至不同的人,都有不同的理解。 + +* 1、1月1日是周三,到下周三(1月8日),这7天算作这一年的第一周。 +* 2、因为周日(周一)才是一周的第一天,所以,要从2020年的第一个周日(周一)开始往后推7天才算这一年的第一周。 +* 3、因为12.29、12.30、12.31是2019年,而1.1、1.2、1.3才是2020年,而1.4周日是下一周的开始,所以,第一周应该只有1.1、1.2、1.3这三天。 + +#### ISO 8601 + +因为不同人对于日期和时间的表示方法有不同的理解,于是,大家就共同制定了了一个国际规范:ISO 8601 。 + +国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。 + +在 ISO 8601中。对于一年的第一个日历星期有以下四种等效说法: +* 1,本年度第一个星期四所在的星期; +* 2,1月4日所在的星期; +* 3,本年度第一个至少有4天在同一星期内的星期; +* 4,星期一在去年12月29日至今年1月4日以内的星期; + +根据这个标准,我们可以推算出: + +2020年第一周:2019.12.29-2020.1.4 + +所以,根据ISO 8601标准,2019年12月29日、2019年12月30日、2019年12月31日这两天,其实不属于2019年的最后一周,而是属于2020年的第一周。 + + +#### JDK针对ISO 8601提供的支持 + +根据ISO 8601中关于日历星期和日表示法的定义,2019.12.29-2020.1.4是2020年的第一周。 + +我们希望输入一个日期,然后程序告诉我们,根据ISO 8601中关于日历日期的定义,这个日期到底属于哪一年。 + +比如我输入2019-12-20,他告诉我是2019;而我输入2019-12-30的时候,他告诉我是2020。 + +为了提供这样的数据,Java 7引入了「YYYY」作为一个新的日期模式来作为标识。使用「YYYY」作为标识,。再通过SimpleDateFormat就可以得到一个日期所属的周属于哪一年了 + + +所以,当我们要表示日期的时候,一定要使用 yyyy-MM-dd 而不是 YYYY-MM-dd ,这两者的返回结果大多数情况下都一样,但是极端情况就会有问题了。 \ No newline at end of file diff --git a/docs/basics/java-basic/_sidebar.md b/docs/basics/java-basic/_sidebar.md new file mode 100644 index 00000000..7b28c6ca --- /dev/null +++ b/docs/basics/java-basic/_sidebar.md @@ -0,0 +1,274 @@ +* 基本数据类型 + + * [8种基本数据类型](/basics/java-basic/basic-data-types.md) + + * [整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md) + + * [什么是浮点型?](/basics/java-basic/float.md) + + * [什么是单精度和双精度?](/basics/java-basic/single-double-float.md) + + * [为什么不能用浮点型表示金额?](/basics/java-basic/float-amount.md) + +* 自动拆装箱 + + * [自动拆装箱](/basics/java-basic/boxing-unboxing.md) + + * [Integer的缓存机制](/basics/java-basic/integer-cache.md) + +* String + + * [字符串的不可变性](/basics/java-basic/final-string.md) + + * [JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md) + + * replaceFirst、replaceAll、replace区别 + + * [String对“+”的重载](/basics/java-basic/string-append.md) + + * [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md) + + * [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md) + + * [switch对String的支持](/basics/java-basic/switch-string.md) + * 字符串池 + * 常量池(运行时常量池、Class常量池) + * intern +* Java中各种关键字 + * transient + * instanceof + * volatile + * synchronized + * final + * static + * const +* 集合类 + + * 常用集合类的使用 + + * [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md) + + * [SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md) + + * [HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md) + + * [Set和List区别?](/basics/java-basic/set-vs-list.md) + + * [Set如何保证元素不重复?](/basics/java-basic/set-repetition.md) + + * [Java 8中stream相关用法](/basics/java-basic/stream.md) + + * Apache集合处理工具类的使用 + + * 不同版本的JDK中HashMap的实现的区别以及原因 + + * [Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md) + + * [Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md) + + * [Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md) + + * [fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md) + + * [CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md) + + * [ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md) + +* 枚举 + + * [枚举的用法](/basics/java-basic/enum-usage.md) + + * [枚举的实现](/basics/java-basic/enum-impl.md) + + * [枚举与单例](/basics/java-basic/enum-singleton.md) + + * Enum类 + + * [Java枚举如何比较](/basics/java-basic/enum-compare.md) + + * [switch对枚举的支持](/basics/java-basic/enum-switch.md) + + * [枚举的序列化如何实现](/basics/java-basic/enum-serializable.md) + + * [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md) + +* IO + + * [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md) + + * [输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md) + + * [同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md) + + * [阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md) + + * [Linux 5种IO模型](/basics/java-basic/linux-io.md) + + * [BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md) + + * netty + +* 反射 + + * [反射](/basics/java-basic/reflection.md)与工厂模式、 + + * [反射有什么作用](/basics/java-basic/usage-of-reflection.md) + + * [Class类](/basics/java-basic/Class.md) + + * `java.lang.reflect.*` + +* 动态代理 + + * [静态代理](/basics/java-basic/static-proxy.md) + + * [动态代理](/basics/java-basic/dynamic-proxy.md) + + * [动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md) + + * [动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md) + + * [AOP](/basics/java-basic/aop-vs-proxy.md) + +* 序列化 + + * [什么是序列化与反序列化](basics/java-basic/serialize.md) + * [Java如何实现序列化与反序列化](basics/java-basic/serialize-in-java.md) + * [Serializable 和 Externalizable 有何不同](basics/java-basic/diff-serializable-vs-externalizable.md) + * 为什么序列化 + * [serialVersionUID](basics/java-basic/serialVersionUID.md) + * [为什么serialVersionUID不能随便改](basics/java-basic/serialVersionUID-modify.md) + * [transient](basics/java-basic/transient.md) + * [序列化底层原理](basics/java-basic/serialize-principle.md) + * [序列化与单例模式](basics/java-basic/serialize-singleton.md) + * [protobuf](basics/java-basic/protobuf.md) + * 为什么说序列化并不安全 + +* 注解 + + * [元注解](/basics/java-basic/meta-annotation.md) + * [自定义注解](/basics/java-basic/custom-annotation.md) + * Java中常用注解使用 + * 注解与反射的结合 + + * [如何自定义一个注解?](/basics/java-basic/create-annotation.md) + + * [Spring常用注解](/basics/java-basic/annotation-in-spring.md) +* JMS + + * 什么是Java消息服务 + * JMS消息传送模型 + +* JMX + + * `java.lang.management.*` + * `javax.management.*` + +* 泛型 + + * [什么是泛型](/basics/java-basic/generics.md) + * [类型擦除](/basics/java-basic/type-erasue.md) + * [泛型带来的问题](/basics/java-basic/generics-problem.md) + * [泛型中K T V E ? object等的含义](/basics/java-basic/k-t-v-e.md) + * 泛型各种用法 + + * [限定通配符和非限定通配符](/basics/java-basic/Wildcard-Character.md) + * [上下界限定符extends 和 super](/basics/java-basic/extends-vs-super.md) + + * [`List`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md) + + * [`List`和`List`之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md) + +* 单元测试 + + * junit + * mock + * mockito + * 内存数据库(h2) + +* 正则表达式 + + * `java.lang.util.regex.*` + +* 常用的Java工具库 + + * `commons.lang` + * `commons.*...` + * `guava-libraries` + * `netty` + +* API&SPI + + * API + * [API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md) + + * [如何定义SPI](/basics/java-basic/create-spi.md) + * [SPI的实现原理](/basics/java-basic/spi-principle.md) + +* 异常 + + * [Error和Exception](/basics/java-basic/error-vs-exception.md) + * [异常类型](/basics/java-basic/exception-type.md) + * [异常相关关键字](/basics/java-basic/keyword-about-exception.md) + * [正确处理异常](/basics/java-basic/handle-exception.md) + * [自定义异常](/basics/java-basic/define-exception.md) + * [异常链](/basics/java-basic/exception-chain.md) + * [try-with-resources](/basics/java-basic/try-with-resources.md) + * [finally和return的执行顺序](/basics/java-basic/order-about-finllly-return.md) + +* 时间处理 + + * [时区](/basics/java-basic/time-zone.md) + * [冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md) + * [时间戳](/basics/java-basic/timestamp.md) + * Java中时间API + + * [格林威治时间](/basics/java-basic/GMT.md) + * [CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md) + + * [SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md) + + * [Java 8中的时间处理](/basics/java-basic/time-in-java8.md) + + * [如何在东八区的计算机上获取美国时间](/basics/java-basic/get-los_angeles-time.md) + + * [yyyy和YYYY有什么区别?](/basics/java-basic/YYYY-vs-yyyy.md) + +* 编码方式 + + * [什么是ASCII?](/basics/java-basic/ASCII.md) + + * [Unicode](/basics/java-basic/UNICODE.md) + + * [有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md) + + * [UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md) + + * 有了UTF8为什么还需要GBK? + + * [GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md) + + * [URL编解码](/basics/java-basic/url-encode.md) + * [Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md) + + * 如何解决乱码问题 + +* 语法糖 + + * [Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md) + + * [语法糖介绍](/basics/java-basic/syntactic-sugar.md) + +* 阅读源代码 + + * String + * Integer + * Long + * Enum + * BigDecimal + * ThreadLocal + * ClassLoader & URLClassLoader + * ArrayList & LinkedList + * HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap + * HashSet & LinkedHashSet & TreeSet + \ No newline at end of file diff --git a/basics/java-basic/annotation-in-spring.md b/docs/basics/java-basic/annotation-in-spring.md similarity index 100% rename from basics/java-basic/annotation-in-spring.md rename to docs/basics/java-basic/annotation-in-spring.md diff --git a/basics/java-basic/aop-vs-proxy.md b/docs/basics/java-basic/aop-vs-proxy.md similarity index 100% rename from basics/java-basic/aop-vs-proxy.md rename to docs/basics/java-basic/aop-vs-proxy.md diff --git a/basics/java-basic/api-vs-spi.md b/docs/basics/java-basic/api-vs-spi.md similarity index 100% rename from basics/java-basic/api-vs-spi.md rename to docs/basics/java-basic/api-vs-spi.md diff --git a/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md b/docs/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md similarity index 100% rename from basics/java-basic/arraylist-vs-linkedlist-vs-vector.md rename to docs/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md diff --git a/docs/basics/java-basic/basic-data-types.md b/docs/basics/java-basic/basic-data-types.md new file mode 100644 index 00000000..a54b406b --- /dev/null +++ b/docs/basics/java-basic/basic-data-types.md @@ -0,0 +1,17 @@ +Java中有8种基本数据类型分为三大类。 + +### 字符型 + +char + +### 布尔型 + +boolean + +### 数值型 + +1.整型:byte、short、int、long + +2.浮点型:float、double + +*String不是基本数据类型,是引用类型。* \ No newline at end of file diff --git a/basics/java-basic/big-endian-vs-little-endian.md b/docs/basics/java-basic/big-endian-vs-little-endian.md similarity index 100% rename from basics/java-basic/big-endian-vs-little-endian.md rename to docs/basics/java-basic/big-endian-vs-little-endian.md diff --git a/basics/java-basic/bio-vs-nio-vs-aio.md b/docs/basics/java-basic/bio-vs-nio-vs-aio.md similarity index 100% rename from basics/java-basic/bio-vs-nio-vs-aio.md rename to docs/basics/java-basic/bio-vs-nio-vs-aio.md diff --git a/basics/java-basic/block-vs-non-blocking.md b/docs/basics/java-basic/block-vs-non-blocking.md similarity index 100% rename from basics/java-basic/block-vs-non-blocking.md rename to docs/basics/java-basic/block-vs-non-blocking.md diff --git a/basics/java-basic/boxing-unboxing.md b/docs/basics/java-basic/boxing-unboxing.md similarity index 100% rename from basics/java-basic/boxing-unboxing.md rename to docs/basics/java-basic/boxing-unboxing.md diff --git a/basics/java-basic/byte-stream-vs-character-stream.md b/docs/basics/java-basic/byte-stream-vs-character-stream.md similarity index 100% rename from basics/java-basic/byte-stream-vs-character-stream.md rename to docs/basics/java-basic/byte-stream-vs-character-stream.md diff --git a/basics/java-basic/constructor.md b/docs/basics/java-basic/constructor.md similarity index 100% rename from basics/java-basic/constructor.md rename to docs/basics/java-basic/constructor.md diff --git a/basics/java-basic/create-annotation.md b/docs/basics/java-basic/create-annotation.md similarity index 100% rename from basics/java-basic/create-annotation.md rename to docs/basics/java-basic/create-annotation.md diff --git a/basics/java-basic/create-spi.md b/docs/basics/java-basic/create-spi.md similarity index 100% rename from basics/java-basic/create-spi.md rename to docs/basics/java-basic/create-spi.md diff --git a/basics/java-basic/custom-annotation.md b/docs/basics/java-basic/custom-annotation.md similarity index 100% rename from basics/java-basic/custom-annotation.md rename to docs/basics/java-basic/custom-annotation.md diff --git a/docs/basics/java-basic/define-exception.md b/docs/basics/java-basic/define-exception.md new file mode 100644 index 00000000..489811f6 --- /dev/null +++ b/docs/basics/java-basic/define-exception.md @@ -0,0 +1,6 @@ +⾃定义异常就是开发⼈员⾃⼰定义的异常, ⼀般通过继承`Exception`的⼦类的⽅式实现。 + + +编写⾃定义异常类实际上是继承⼀个API标准异常类, ⽤新定义的异常处理信息覆盖原有信息的过程。 + +这种⽤法在Web开发中也⽐较常见, ⼀般可以⽤来⾃定义业务异常。 如余额不⾜、 重复提交等。 这种⾃定义异常有业务含义, 更容易让上层理解和处理 \ No newline at end of file diff --git a/docs/basics/java-basic/diff-serializable-vs-externalizable.md b/docs/basics/java-basic/diff-serializable-vs-externalizable.md new file mode 100644 index 00000000..5b2ed9a7 --- /dev/null +++ b/docs/basics/java-basic/diff-serializable-vs-externalizable.md @@ -0,0 +1,17 @@ +Java中的类通过实现 `java.io.Serializable` 接口以启⽤其序列化功能。 未实现此接⼜的类将⽆法使其任何状态序列化或反序列化。 + +可序列化类的所有⼦类型本⾝都是可序列化的。 + +序列化接⼜没有⽅法或字段, 仅⽤于标识可序列化的语义。 + +当试图对⼀个对象进⾏序列化的时候, 如果遇到不⽀持`Serializable` 接口的对象。 在此情况下, 将抛`NotSerializableException`。 + +如果要序列化的类有⽗类, 要想同时将在⽗类中定义过的变量持久化下来, 那么⽗类也应该集成`java.io.Serializable`接口。 + +`Externalizable`继承了`Serializable`, 该接⼜中定义了两个抽象⽅法:`writeExternal()`与`readExternal()`。 当使⽤`Externalizable`接口来进⾏序列化与反序列化的时候需要开发⼈员重写writeExternal()与readExternal()⽅法。 + +如果没有在这两个⽅法中定义序列化实现细节, 那么序列化之后, 对象内容为空。 + +实现`Externalizable`接⼜的类必须要提供⼀个`public`的⽆参的构造器。 + +所以, 实现`Externalizable`, 并实现`writeExternal()`和`readExternal()`⽅法可以指定序列化哪些属性。 diff --git a/basics/java-basic/dynamic-proxy-implementation.md b/docs/basics/java-basic/dynamic-proxy-implementation.md similarity index 100% rename from basics/java-basic/dynamic-proxy-implementation.md rename to docs/basics/java-basic/dynamic-proxy-implementation.md diff --git a/basics/java-basic/dynamic-proxy-vs-reflection.md b/docs/basics/java-basic/dynamic-proxy-vs-reflection.md similarity index 100% rename from basics/java-basic/dynamic-proxy-vs-reflection.md rename to docs/basics/java-basic/dynamic-proxy-vs-reflection.md diff --git a/basics/java-basic/dynamic-proxy.md b/docs/basics/java-basic/dynamic-proxy.md similarity index 100% rename from basics/java-basic/dynamic-proxy.md rename to docs/basics/java-basic/dynamic-proxy.md diff --git a/basics/java-basic/enum-compare.md b/docs/basics/java-basic/enum-compare.md similarity index 100% rename from basics/java-basic/enum-compare.md rename to docs/basics/java-basic/enum-compare.md diff --git a/basics/java-basic/enum-impl.md b/docs/basics/java-basic/enum-impl.md similarity index 100% rename from basics/java-basic/enum-impl.md rename to docs/basics/java-basic/enum-impl.md diff --git a/basics/java-basic/enum-serializable.md b/docs/basics/java-basic/enum-serializable.md similarity index 100% rename from basics/java-basic/enum-serializable.md rename to docs/basics/java-basic/enum-serializable.md diff --git a/basics/java-basic/enum-singleton.md b/docs/basics/java-basic/enum-singleton.md similarity index 100% rename from basics/java-basic/enum-singleton.md rename to docs/basics/java-basic/enum-singleton.md diff --git a/basics/java-basic/enum-switch.md b/docs/basics/java-basic/enum-switch.md similarity index 100% rename from basics/java-basic/enum-switch.md rename to docs/basics/java-basic/enum-switch.md diff --git a/basics/java-basic/enum-thread-safe.md b/docs/basics/java-basic/enum-thread-safe.md similarity index 100% rename from basics/java-basic/enum-thread-safe.md rename to docs/basics/java-basic/enum-thread-safe.md diff --git a/basics/java-basic/enum-usage.md b/docs/basics/java-basic/enum-usage.md similarity index 100% rename from basics/java-basic/enum-usage.md rename to docs/basics/java-basic/enum-usage.md diff --git a/docs/basics/java-basic/error-vs-exception.md b/docs/basics/java-basic/error-vs-exception.md new file mode 100644 index 00000000..0e13030b --- /dev/null +++ b/docs/basics/java-basic/error-vs-exception.md @@ -0,0 +1,6 @@ +Exception和 Error, ⼆者都是 Java异常处理的重要⼦类, 各⾃都包含⼤量⼦类。均继承自Throwable类。 + + +Error表⽰系统级的错误, 是java运⾏环境内部错误或者硬件问题, 不能指望程序来处理这样的问题, 除了退出运⾏外别⽆选择, 它是Java虚拟机抛出的。 + +Exception 表⽰程序需要捕捉、 需要处理的常, 是由与程序设计的不完善⽽出现的问题, 程序必须处理的问题。 \ No newline at end of file diff --git a/docs/basics/java-basic/exception-chain.md b/docs/basics/java-basic/exception-chain.md new file mode 100644 index 00000000..44e6e32a --- /dev/null +++ b/docs/basics/java-basic/exception-chain.md @@ -0,0 +1,26 @@ +“异常链”是Java中⾮常流⾏的异常处理概念, 是指在进⾏⼀个异常处理时抛出了另外⼀个异常, 由此产⽣了⼀个异常链条。 + +该技术⼤多⽤于将“ 受检查异常” ( checked exception) 封装成为“⾮受检查异常”( unchecked exception)或者RuntimeException。 + +顺便说⼀下, 如果因为因为异常你决定抛出⼀个新的异常, 你⼀定要包含原有的异常, 这样, 处理程序才可以通过getCause()和initCause()⽅法来访问异常最终的根源。 + +从 Java 1.4版本开始,几乎所有的异常都支持异常链。 + +以下是Throwable中支持异常链的方法和构造函数。 + + Throwable getCause() + Throwable initCause(Throwable) + Throwable(String, Throwable) + Throwable(Throwable) + +initCause和Throwable构造函数的Throwable参数是导致当前异常的异常。 getCause返回导致当前异常的异常,initCause设置当前异常的原因。 + +以下示例显示如何使用异常链。 + + try { + + } catch (IOException e) { + throw new SampleException("Other IOException", e); + } + +在此示例中,当捕获到IOException时,将创建一个新的SampleException异常,并附加原始的异常原因,并将异常链抛出到下一个更高级别的异常处理程序。 \ No newline at end of file diff --git a/docs/basics/java-basic/exception-type.md b/docs/basics/java-basic/exception-type.md new file mode 100644 index 00000000..7e8de55c --- /dev/null +++ b/docs/basics/java-basic/exception-type.md @@ -0,0 +1,20 @@ +Java中的异常, 主要可以分为两⼤类, 即受检异常( checked exception) 和 ⾮受检异常( unchecked exception) + +### 受检异常 +对于受检异常来说, 如果⼀个⽅法在声明的过程中证明了其要有受检异常抛出: + + public void test() throw new Exception{ } + +那么,当我们在程序中调⽤他的时候, ⼀定要对该异常进⾏处理( 捕获或者向上抛出) , 否则是⽆法编译通过的。 这是⼀种强制规范。 + +这种异常在IO操作中⽐较多。 ⽐如FileNotFoundException , 当我们使⽤IO流处理⼀个⽂件的时候, 有⼀种特殊情况, 就是⽂件不存在, 所以, 在⽂件处理的接⼜定义时他会显⽰抛出FileNotFoundException, ⽬的就是告诉这个⽅法的调⽤者,我这个⽅法不保证⼀定可以成功, 是有可能找不到对应的⽂件 +的, 你要明确的对这种情况做特殊处理哦。 + +所以说, 当我们希望我们的⽅法调⽤者, 明确的处理⼀些特殊情况的时候, 就应该使⽤受检异常。 + +### 非受检异常 +对于⾮受检异常来说, ⼀般是运⾏时异常, 继承⾃RuntimeException。 在编写代码的时候, 不需要显⽰的捕获,但是如果不捕获, 在运⾏期如果发⽣异常就会中断程序的执⾏。 + +这种异常⼀般可以理解为是代码原因导致的。 ⽐如发⽣空指针、 数组越界等。 所以, 只要代码写的没问题, 这些异常都是可以避免的。 也就不需要我们显⽰的进⾏处理。 + +试想⼀下, 如果你要对所有可能发⽣空指针的地⽅做异常处理的话, 那相当于你的所有代码都需要做这件事。 \ No newline at end of file diff --git a/docs/basics/java-basic/extends-vs-super.md b/docs/basics/java-basic/extends-vs-super.md new file mode 100644 index 00000000..e52fcbee --- /dev/null +++ b/docs/basics/java-basic/extends-vs-super.md @@ -0,0 +1,48 @@ +``和``是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。 + +``:是指 “上界通配符(Upper Bounds Wildcards)”,即泛型中的类必须为当前类的子类或当前类。 + +``:是指 “下界通配符(Lower Bounds Wildcards)”,即泛型中的类必须为当前类或者其父类。 + + +先看一个列子: + + public class Food {} + public class Fruit extends Food {} + public class Apple extends Fruit {} + public class Banana extends Fruit{} + + public class GenericTest { + + public void testExtends(List list){ + + //报错,extends为上界通配符,只能取值,不能放. + //因为Fruit的子类不只有Apple还有Banana,这里不能确定具体的泛型到底是Apple还是Banana,所以放入任何一种类型都会报错 + //list.add(new Apple()); + + //可以正常获取 + Fruit fruit = list.get(1); + } + + public void testSuper(List list){ + + //super为下界通配符,可以存放元素,但是也只能存放当前类或者子类的实例,以当前的例子来讲, + //无法确定Fruit的父类是否只有Food一个(Object是超级父类) + //因此放入Food的实例编译不通过 + list.add(new Apple()); + // list.add(new Food()); + + Object object = list.get(1); + } + } + +在testExtends方法中,因为泛型中用的是extends,在向list中存放元素的时候,我们并不能确定List中的元素的具体类型,即可能是Apple也可能是Banana。因此调用add方法时,不论传入new Apple()还是new Banana(),都会出现编译错误。 + + +理解了extends之后,再看super就很容易理解了,即我们不能确定testSuper方法的参数中的泛型是Fruit的哪个父类,因此在调用get方法时只能返回Object类型。结合extends可见,在获取泛型元素时,使用extends获取到的是泛型中的上边界的类型(本例子中为Fruit),范围更小。 + +在使用泛型时,存取元素时用super,获取元素时,用extends。 + +频繁往外读取内容的,适合用上界Extends。经常往里插入的,适合用下界Super。 + +本文来源:https://juejin.im/post/5c653fe06fb9a049e3089d88 \ No newline at end of file diff --git a/basics/java-basic/fail-fast-vs-fail-safe.md b/docs/basics/java-basic/fail-fast-vs-fail-safe.md similarity index 100% rename from basics/java-basic/fail-fast-vs-fail-safe.md rename to docs/basics/java-basic/fail-fast-vs-fail-safe.md diff --git a/basics/java-basic/final-string.md b/docs/basics/java-basic/final-string.md similarity index 100% rename from basics/java-basic/final-string.md rename to docs/basics/java-basic/final-string.md diff --git a/basics/java-basic/float-amount.md b/docs/basics/java-basic/float-amount.md similarity index 100% rename from basics/java-basic/float-amount.md rename to docs/basics/java-basic/float-amount.md diff --git a/basics/java-basic/float.md b/docs/basics/java-basic/float.md similarity index 100% rename from basics/java-basic/float.md rename to docs/basics/java-basic/float.md diff --git a/basics/java-basic/gbk-gb2312-gb18030.md b/docs/basics/java-basic/gbk-gb2312-gb18030.md similarity index 100% rename from basics/java-basic/gbk-gb2312-gb18030.md rename to docs/basics/java-basic/gbk-gb2312-gb18030.md diff --git a/basics/java-basic/genericity-list-wildcard.md b/docs/basics/java-basic/genericity-list-wildcard.md similarity index 100% rename from basics/java-basic/genericity-list-wildcard.md rename to docs/basics/java-basic/genericity-list-wildcard.md diff --git a/basics/java-basic/genericity-list.md b/docs/basics/java-basic/genericity-list.md similarity index 100% rename from basics/java-basic/genericity-list.md rename to docs/basics/java-basic/genericity-list.md diff --git a/docs/basics/java-basic/generics-problem.md b/docs/basics/java-basic/generics-problem.md new file mode 100644 index 00000000..ccbd2ab6 --- /dev/null +++ b/docs/basics/java-basic/generics-problem.md @@ -0,0 +1,42 @@ + + +###一、当泛型遇到重载 + + public class GenericTypes { + + public static void method(List list) { + System.out.println("invoke method(List list)"); + } + + public static void method(List list) { + System.out.println("invoke method(List list)"); + } + } + + +上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是`List`另一个是`List` ,但是,这段代码是编译通不过的。因为我们前面讲过,参数`List`和`List`编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。 + +### 二、当泛型遇到catch + +如果我们自定义了一个泛型异常类GenericException,那么,不要尝试用多个catch取匹配不同的异常类型,例如你想要分别捕获GenericException、GenericException,这也是有问题的。 + +### 三、当泛型内包含静态变量 + + public class StaticTest{ + public static void main(String[] args){ + GT gti = new GT(); + gti.var=1; + GT gts = new GT(); + gts.var=2; + System.out.println(gti.var); + } + } + class GT{ + public static int var=0; + public void nothing(T x){} + } + + +答案是——2! + +由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。 diff --git a/docs/basics/java-basic/generics.md b/docs/basics/java-basic/generics.md new file mode 100644 index 00000000..2ba7a3ec --- /dev/null +++ b/docs/basics/java-basic/generics.md @@ -0,0 +1,5 @@ +Java泛型( generics) 是JDK 5中引⼊的⼀个新特性, 允许在定义类和接⼜的时候使⽤类型参数( type parameter) 。 + +声明的类型参数在使⽤时⽤具体的类型来替换。 泛型最主要的应⽤是在JDK 5中的新集合类框架中。 + +泛型最⼤的好处是可以提⾼代码的复⽤性。 以List接⼜为例,我们可以将String、 Integer等类型放⼊List中, 如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题。 \ No newline at end of file diff --git a/docs/basics/java-basic/get-los_angeles-time.md b/docs/basics/java-basic/get-los_angeles-time.md new file mode 100644 index 00000000..2517a9e5 --- /dev/null +++ b/docs/basics/java-basic/get-los_angeles-time.md @@ -0,0 +1,52 @@ +了解Java8 的朋友可能都知道,Java8提供了一套新的时间处理API,这套API比以前的时间处理API要友好的多。 + +Java8 中加入了对时区的支持,带时区的时间为分别为:`ZonedDate`、`ZonedTime`、`ZonedDateTime`。 + +其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式,如`Asia/Shanghai`、`America/Los_Angeles`等。 + +在Java8中,直接使用以下代码即可输出美国洛杉矶的时间: + + LocalDateTime now = LocalDateTime.now(ZoneId.of("America/Los_Angeles")); + System.out.println(now); + + + +为什么以下代码无法获得美国时间呢? + + System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")).getTime()); + +当我们使用System.out.println来输出一个时间的时候,他会调用Date类的toString方法,而该方法会读取操作系统的默认时区来进行时间的转换。 + + public String toString() { + // "EEE MMM dd HH:mm:ss zzz yyyy"; + BaseCalendar.Date date = normalize(); + ... + } + + private final BaseCalendar.Date normalize() { + ... + TimeZone tz = TimeZone.getDefaultRef(); + if (tz != cdate.getZone()) { + cdate.setZone(tz); + CalendarSystem cal = getCalendarSystem(cdate); + cal.getCalendarDate(fastTime, cdate); + } + return cdate; + } + + static TimeZone getDefaultRef() { + TimeZone defaultZone = defaultTimeZone; + if (defaultZone == null) { + // Need to initialize the default time zone. + defaultZone = setDefaultZone(); + assert defaultZone != null; + } + // Don't clone here. + return defaultZone; + } + +主要代码如上。也就是说如果我们想要通过`System.out.println`输出一个Date类的时候,输出美国洛杉矶时间的话,就需要想办法把`defaultTimeZone`改为`America/Los_Angeles` + +但是,通过阅读Calendar的源码,我们可以发现,getInstance方法虽然有一个参数可以传入时区,但是并没有将默认时区设置成传入的时区。 + +而在Calendar.getInstance.getTime后得到的时间只是一个时间戳,其中未保留任何和时区有关的信息,所以,在输出时,还是显示的是当前系统默认时区的时间。 \ No newline at end of file diff --git a/docs/basics/java-basic/handle-exception.md b/docs/basics/java-basic/handle-exception.md new file mode 100644 index 00000000..ec5c24d2 --- /dev/null +++ b/docs/basics/java-basic/handle-exception.md @@ -0,0 +1,6 @@ +异常的处理⽅式有两种。 1、 ⾃⼰处理。 2、 向上抛, 交给调⽤者处理。 + + +异常, 千万不能捕获了之后什么也不做。 或者只是使⽤`e.printStacktrace`。 + +具体的处理⽅式的选择其实原则⽐较简明: ⾃⼰明确的知道如何处理的, 就要处理掉。 不知道如何处理的, 就交给调⽤者处理。 \ No newline at end of file diff --git a/basics/java-basic/inheritance-composition.md b/docs/basics/java-basic/inheritance-composition.md similarity index 100% rename from basics/java-basic/inheritance-composition.md rename to docs/basics/java-basic/inheritance-composition.md diff --git a/basics/java-basic/input-stream-vs-output-stream.md b/docs/basics/java-basic/input-stream-vs-output-stream.md similarity index 100% rename from basics/java-basic/input-stream-vs-output-stream.md rename to docs/basics/java-basic/input-stream-vs-output-stream.md diff --git a/basics/java-basic/integer-cache.md b/docs/basics/java-basic/integer-cache.md similarity index 100% rename from basics/java-basic/integer-cache.md rename to docs/basics/java-basic/integer-cache.md diff --git a/basics/java-basic/integer-scope.md b/docs/basics/java-basic/integer-scope.md similarity index 100% rename from basics/java-basic/integer-scope.md rename to docs/basics/java-basic/integer-scope.md diff --git a/basics/java-basic/k-t-v-e.md b/docs/basics/java-basic/k-t-v-e.md similarity index 100% rename from basics/java-basic/k-t-v-e.md rename to docs/basics/java-basic/k-t-v-e.md diff --git a/docs/basics/java-basic/keyword-about-exception.md b/docs/basics/java-basic/keyword-about-exception.md new file mode 100644 index 00000000..1a416304 --- /dev/null +++ b/docs/basics/java-basic/keyword-about-exception.md @@ -0,0 +1,11 @@ +throws、 throw、 try、 catch、 finally + +try⽤来指定⼀块预防所有异常的程序; + +catch⼦句紧跟在try块后⾯, ⽤来指定你想要捕获的异常的类型; + +finally为确保⼀段代码不管发⽣什么异常状况都要被执⾏; + +throw语句⽤来明确地抛出⼀个异常; + +throws⽤来声明⼀个⽅法可能抛出的各种异常; \ No newline at end of file diff --git a/basics/java-basic/linux-io.md b/docs/basics/java-basic/linux-io.md similarity index 100% rename from basics/java-basic/linux-io.md rename to docs/basics/java-basic/linux-io.md diff --git a/basics/java-basic/meta-annotation.md b/docs/basics/java-basic/meta-annotation.md similarity index 100% rename from basics/java-basic/meta-annotation.md rename to docs/basics/java-basic/meta-annotation.md diff --git a/docs/basics/java-basic/order-about-finllly-return.md b/docs/basics/java-basic/order-about-finllly-return.md new file mode 100644 index 00000000..6f9de993 --- /dev/null +++ b/docs/basics/java-basic/order-about-finllly-return.md @@ -0,0 +1,6 @@ + + + `try()` ⾥⾯有⼀个`return`语句, 那么后⾯的`finally{}`⾥⾯的code会不会被执⾏, 什么时候执⾏, 是在`return`前还是`return`后? + + +如果try中有return语句, 那么finally中的代码还是会执⾏。因为return表⽰的是要整个⽅法体返回, 所以,finally中的语句会在return之前执⾏。 \ No newline at end of file diff --git a/docs/basics/java-basic/protobuf.md b/docs/basics/java-basic/protobuf.md new file mode 100644 index 00000000..f8c8738c --- /dev/null +++ b/docs/basics/java-basic/protobuf.md @@ -0,0 +1,9 @@ + +Protocol Buffer (简称Protobuf) 是Google出品的性能优异、跨语言、跨平台的序列化库。 + +2001年初,Protobuf首先在Google内部创建, 我们把它称之为 proto1,一直以来在Google的内部使用,其中也不断的演化,根据使用者的需求也添加很多新的功能,一些内部库依赖它。几乎每个Google的开发者都会使用到它。 + +Google开始开源它的内部项目时,因为依赖的关系,所以他们决定首先把Protobuf开源出去。 + +目前Protobuf的稳定版本是3.9.2,于2019年9月23日发布。由于很多公司很早的就采用了Protobuf,所以很多项目还在使用proto2协议,目前是proto2和proto3同时在使用的状态。 + diff --git a/basics/java-basic/reflection.md b/docs/basics/java-basic/reflection.md similarity index 100% rename from basics/java-basic/reflection.md rename to docs/basics/java-basic/reflection.md diff --git a/basics/java-basic/scope.md b/docs/basics/java-basic/scope.md similarity index 100% rename from basics/java-basic/scope.md rename to docs/basics/java-basic/scope.md diff --git a/docs/basics/java-basic/serialVersionUID-modify.md b/docs/basics/java-basic/serialVersionUID-modify.md new file mode 100644 index 00000000..6b1e26ac --- /dev/null +++ b/docs/basics/java-basic/serialVersionUID-modify.md @@ -0,0 +1,278 @@ +关于`serialVersionUID` 。这个字段到底有什么用?如果不设置会怎么样?为什么《阿里巴巴Java开发手册》中有以下规定: + +![-w934][4] + +### 背景知识 + +**Serializable 和 Externalizable** + +类通过实现 `java.io.Serializable` 接口以启用其序列化功能。**未实现此接口的类将无法进行序列化或反序列化。**可序列化类的所有子类型本身都是可序列化的。 + +如果读者看过`Serializable`的源码,就会发现,他只是一个空的接口,里面什么东西都没有。**Serializable接口没有方法或字段,仅用于标识可序列化的语义。**但是,如果一个类没有实现这个接口,想要被序列化的话,就会抛出`java.io.NotSerializableException`异常。 + +它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢? + +原因是在执行序列化的过程中,会执行到以下代码: + + if (obj instanceof String) { + writeString((String) obj, unshared); + } else if (cl.isArray()) { + writeArray(obj, desc, unshared); + } else if (obj instanceof Enum) { + writeEnum((Enum) obj, desc, unshared); + } else if (obj instanceof Serializable) { + writeOrdinaryObject(obj, desc, unshared); + } else { + if (extendedDebugInfo) { + throw new NotSerializableException( + cl.getName() + "\n" + debugInfoStack.toString()); + } else { + throw new NotSerializableException(cl.getName()); + } + } + + +在进行序列化操作时,会判断要被序列化的类是否是`Enum`、`Array`和`Serializable`类型,如果都不是则直接抛出`NotSerializableException`。 + +Java中还提供了`Externalizable`接口,也可以实现它来提供序列化能力。 + +`Externalizable`继承自`Serializable`,该接口中定义了两个抽象方法:`writeExternal()`与`readExternal()`。 + +当使用`Externalizable`接口来进行序列化与反序列化的时候需要开发人员重写`writeExternal()`与`readExternal()`方法。否则所有变量的值都会变成默认值。 + +**transient** + +`transient` 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,`transient` 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。 + +**自定义序列化策略** + +在序列化过程中,如果被序列化的类中定义了`writeObject` 和 `readObject` 方法,虚拟机会试图调用对象类里的 `writeObject` 和 `readObject` 方法,进行用户自定义的序列化和反序列化。 + +如果没有这样的方法,则默认调用是 `ObjectOutputStream` 的 `defaultWriteObject` 方法以及 `ObjectInputStream` 的 `defaultReadObject` 方法。 + +用户自定义的 `writeObject` 和 `readObject` 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。 + +所以,对于一些特殊字段需要定义序列化的策略的时候,可以考虑使用transient修饰,并自己重写`writeObject` 和 `readObject` 方法,如`java.util.ArrayList`中就有这样的实现。 + +我们随便找几个Java中实现了序列化接口的类,如String、Integer等,我们可以发现一个细节,那就是这些类除了实现了`Serializable`外,还定义了一个`serialVersionUID` ![][5] + +那么,到底什么是`serialVersionUID`呢?为什么要设置这样一个字段呢? + +### 什么是serialVersionUID + +序列化是将对象的状态信息转换为可存储或传输的形式的过程。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。 + +而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。 + +虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致,这个所谓的序列化ID,就是我们在代码中定义的`serialVersionUID`。 + +### 如果serialVersionUID变了会怎样 + +我们举个例子吧,看看如果`serialVersionUID`被修改了会发生什么? + + public class SerializableDemo1 { + public static void main(String[] args) { + //Initializes The Object + User1 user = new User1(); + user.setName("hollis"); + //Write Obj to File + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(new FileOutputStream("tempFile")); + oos.writeObject(user); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(oos); + } + } + } + + class User1 implements Serializable { + private static final long serialVersionUID = 1L; + private String name; + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + +我们先执行以上代码,把一个User1对象写入到文件中。然后我们修改一下User1类,把`serialVersionUID`的值改为`2L`。 + + class User1 implements Serializable { + private static final long serialVersionUID = 2L; + private String name; + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + +然后执行以下代码,把文件中的对象反序列化出来: + + public class SerializableDemo2 { + public static void main(String[] args) { + //Read Obj from File + File file = new File("tempFile"); + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new FileInputStream(file)); + User1 newUser = (User1) ois.readObject(); + System.out.println(newUser); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(ois); + try { + FileUtils.forceDelete(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + +执行结果如下: + + java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2 + + +可以发现,以上代码抛出了一个`java.io.InvalidClassException`,并且指出`serialVersionUID`不一致。 + +这是因为,在进行反序列化时,JVM会把传来的字节流中的`serialVersionUID`与本地相应实体类的`serialVersionUID`进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是`InvalidCastException`。 + +这也是《阿里巴巴Java开发手册》中规定,在兼容性升级中,在修改类的时候,不要修改`serialVersionUID`的原因。**除非是完全不兼容的两个版本**。所以,**`serialVersionUID`其实是验证版本一致性的。** + +如果读者感兴趣,可以把各个版本的JDK代码都拿出来看一下,那些向下兼容的类的`serialVersionUID`是没有变化过的。比如String类的`serialVersionUID`一直都是`-6849794470754667710L`。 + +但是,作者认为,这个规范其实还可以再严格一些,那就是规定: + +如果一个类实现了`Serializable`接口,就必须手动添加一个`private static final long serialVersionUID`变量,并且设置初始值。 + +### 为什么要明确定一个serialVersionUID + +如果我们没有在类中明确的定义一个`serialVersionUID`的话,看看会发生什么。 + +尝试修改上面的demo代码,先使用以下类定义一个对象,该类中不定义`serialVersionUID`,将其写入文件。 + + class User1 implements Serializable { + private String name; + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + +然后我们修改User1类,向其中增加一个属性。在尝试将其从文件中读取出来,并进行反序列化。 + + class User1 implements Serializable { + private String name; + private int age; + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } + } + + +执行结果: `java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = -2986778152837257883, local class serialVersionUID = 7961728318907695402` + +同样,抛出了`InvalidClassException`,并且指出两个`serialVersionUID`不同,分别是`-2986778152837257883`和`7961728318907695402`。 + +从这里可以看出,系统自己添加了一个`serialVersionUID`。 + +所以,一旦类实现了`Serializable`,就建议明确的定义一个`serialVersionUID`。不然在修改类的时候,就会发生异常。 + +`serialVersionUID`有两种显示的生成方式: +一是默认的1L,比如:`private static final long serialVersionUID = 1L;` +二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如: +`private static final long serialVersionUID = xxxxL;` + +后面这种方式,可以借助IDE生成,后面会介绍。 + +### 背后原理 + +知其然,要知其所以然,我们再来看看源码,分析一下为什么`serialVersionUID`改变的时候会抛异常?在没有明确定义的情况下,默认的`serialVersionUID`是怎么来的? + +为了简化代码量,反序列化的调用链如下: + +`ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> ObjectStreamClass.initNonProxy` + +在`initNonProxy`中 ,关键代码如下: + +![][6] + +在反序列化过程中,对`serialVersionUID`做了比较,如果发现不相等,则直接抛出异常。 + +深入看一下`getSerialVersionUID`方法: + + public long getSerialVersionUID() { + // REMIND: synchronize instead of relying on volatile? + if (suid == null) { + suid = AccessController.doPrivileged( + new PrivilegedAction() { + public Long run() { + return computeDefaultSUID(cl); + } + } + ); + } + return suid.longValue(); + } + + +在没有定义`serialVersionUID`的时候,会调用`computeDefaultSUID` 方法,生成一个默认的`serialVersionUID`。 + +这也就找到了以上两个问题的根源,其实是代码中做了严格的校验。 + +### IDEA提示 + +为了确保我们不会忘记定义`serialVersionUID`,可以调节一下Intellij IDEA的配置,在实现`Serializable`接口后,如果没定义`serialVersionUID`的话,IDEA(eclipse一样)会进行提示: ![][7] + +并且可以一键生成一个: + +![][8] + +当然,这个配置并不是默认生效的,需要手动到IDEA中设置一下: + +![][9] + +在图中标号3的地方(Serializable class without serialVersionUID的配置),打上勾,保存即可。 + +### 总结 + +`serialVersionUID`是用来验证版本一致性的。所以在做兼容性升级的时候,不要改变类中`serialVersionUID`的值。 + +如果一个类实现了Serializable接口,一定要记得定义`serialVersionUID`,否则会发生异常。可以在IDE中通过设置,让他帮忙提示,并且可以一键快速生成一个`serialVersionUID`。 + +之所以会发生异常,是因为反序列化过程中做了校验,并且如果没有明确定义的话,会根据类的属性自动生成一个。 + + [1]: http://www.hollischuang.com/archives/1150 + [2]: http://www.hollischuang.com/archives/1140 + [3]: http://www.hollischuang.com/archives/1144 + [4]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455608799770.jpg + [5]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455622116411.jpg + [6]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455655236269.jpg + [7]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455657868672.jpg + [8]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455658088098.jpg + [9]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455659620042.jpg \ No newline at end of file diff --git a/docs/basics/java-basic/serialVersionUID.md b/docs/basics/java-basic/serialVersionUID.md new file mode 100644 index 00000000..dc533181 --- /dev/null +++ b/docs/basics/java-basic/serialVersionUID.md @@ -0,0 +1,21 @@ +序列化是将对象的状态信息转换为可存储或传输的形式的过程。 + +我们都知道, Java对象是保存在JVM的堆内存中的, 也就是说, 如果JVM堆不存在了, 那么对象也就跟着消失了。 + +⽽序列化提供了⼀种⽅案, 可以让你在即使JVM停机的情况下也能把对象保存下来的⽅案。 就像我们平时⽤的U盘⼀样。 把Java对象序列化成可存储或传输的形式( 如⼆进制流) , ⽐如保存在⽂件中。 这样, 当再次需要这个对象的时候, 从⽂件中读取出⼆进制流, 再从⼆进制流中反序列化出对象。 + + +但是, 虚拟机是否允许反序列化, 不仅取决于类路径和功能代码是否⼀致, ⼀个⾮常重要的⼀点是两个类的序列化 ID 是否⼀致, 即`serialVersionUID`要求⼀致。 + + +在进⾏反序列化时, JVM会把传来的字节流中的`serialVersionUID`与本地相应实体类的`serialVersionUID`进⾏⽐较, 如果相同就认为是⼀致的, 可以进⾏反序列化, 否则就会出现序列化版本不⼀致的异常, 即是`InvalidCastException`。 + +这样做是为了保证安全, 因为⽂件存储中的内容可能被篡改。 + + +当实现`java.io.Serializable`接口的类没有显式地定义⼀个`serialVersionUID`变量时候, Java序列化机制会根据编译的Class⾃动⽣成⼀个`serialVersionUID`作序列化版本⽐较⽤, 这种情况下, 如果Class⽂件没有发⽣变化, 就算再编译多 +次, serialVersionUID也不会变化的。 + +但是, 如果发⽣了变化,那么这个⽂件对应的`serialVersionUID`也就会发⽣变化。 + +基于以上原理, 如果我们⼀个类实现了Serializable接口, 但是没有定义`serialVersionUID`, 然后序列化。 在序列化之后, 由于某些原因, 我们对该类做了变更, 重新启动应⽤后, 我们相对之前序列化过的对象进⾏反序列化的话就会报错 \ No newline at end of file diff --git a/basics/java-basic/serialize-principle.md b/docs/basics/java-basic/serialize-in-java.md similarity index 78% rename from basics/java-basic/serialize-principle.md rename to docs/basics/java-basic/serialize-in-java.md index 31a4a1c4..bf982c84 100644 --- a/basics/java-basic/serialize-principle.md +++ b/docs/basics/java-basic/serialize-in-java.md @@ -1,6 +1,3 @@ -## 序列化与反序列化 - -序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。 ## Java对象的序列化与反序列化 @@ -135,7 +132,6 @@ Java为了方便开发人员将Java对象进行序列化及反序列化提供了 //User{name='hollis', age=23} -更多关于Serializable的使用,请参考[代码实例][2] ## Externalizable接口 @@ -319,37 +315,6 @@ Externalizable继承了Serializable,该接口中定义了两个抽象方法: > 如果User类中没有无参数的构造函数,在运行时会抛出异常:`java.io.InvalidClassException` -更多Externalizable接口使用实例请参考[代码实例][3] - -## ObjectOutput和ObjectInput 接口 - -ObjectInput接口 扩展自 DataInput 接口以包含对象的读操作。 - -> DataInput 接口用于从二进制流中读取字节,并根据所有 Java 基本类型数据进行重构。同时还提供根据 UTF-8 修改版格式的数据重构 String 的工具。 -> -> 对于此接口中的所有数据读取例程来说,如果在读取所需字节数之前已经到达文件末尾 (end of file),则将抛出 EOFException(IOException 的一种)。如果因为到达文件末尾以外的其他原因无法读取字节,则将抛出 IOException 而不是 EOFException。尤其是,在输入流已关闭的情况下,将抛出 IOException。 - -ObjectOutput 扩展 DataOutput 接口以包含对象的写入操作。 - -> DataOutput 接口用于将数据从任意 Java 基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。 -> -> 对于此接口中写入字节的所有方法,如果由于某种原因无法写入某个字节,则抛出 IOException。 - -## ObjectOutputStream类和ObjectInputStream类 - -通过前面的代码片段中我们也能知道,我们一般使用ObjectOutputStream的`writeObject`方法把一个对象进行持久化。再使用ObjectInputStream的`readObject`从持久化存储中把对象读取出来。 - -更多关于ObjectInputStream和ObjectOutputStream的相关知识欢迎阅读我的另外两篇博文:[深入分析Java的序列化与反序列化][4]、[单例与序列化的那些事儿][5] - -## Transient 关键字 - -Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。关于Transient 关键字的拓展知识欢迎阅读[深入分析Java的序列化与反序列化][4] - -## 序列化ID - -虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 `private static final long serialVersionUID`) - -序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。 ## 参考资料 diff --git a/docs/basics/java-basic/serialize-principle.md b/docs/basics/java-basic/serialize-principle.md new file mode 100644 index 00000000..993ae5b3 --- /dev/null +++ b/docs/basics/java-basic/serialize-principle.md @@ -0,0 +1,397 @@ +序列化是一种对象持久化的手段。普遍应用在网络传输、RMI等场景中。本文通过分析ArrayList的序列化来介绍Java序列化的相关内容。主要涉及到以下几个问题: + +> 怎么实现Java的序列化 +> +> 为什么实现了java.io.Serializable接口才能被序列化 +> +> transient的作用是什么 +> +> 怎么自定义序列化策略 +> +> 自定义的序列化策略是如何被调用的 +> +> ArrayList对序列化的实现有什么好处 + +## Java对象的序列化 + +Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。 + +使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,**对象序列化不会关注类中的静态变量**。 + +除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。 + +## 如何对Java对象进行序列化与反序列化 + +在Java中,只要一个类实现了`java.io.Serializable`接口,那么它就可以被序列化。这里先来一段代码: + +code 1 创建一个User类,用于序列化及反序列化 + + package com.hollis; + import java.io.Serializable; + import java.util.Date; + + /** + * Created by hollis on 16/2/2. + */ + public class User implements Serializable{ + private String name; + private int age; + private Date birthday; + private transient String gender; + private static final long serialVersionUID = -6849794470754667710L; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public Date getBirthday() { + return birthday; + } + + public void setBirthday(Date birthday) { + this.birthday = birthday; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + @Override + public String toString() { + return "User{" + + "name='" + name + '\'' + + ", age=" + age + + ", gender=" + gender + + ", birthday=" + birthday + + '}'; + } + } + + +code 2 对User进行序列化及反序列化的Demo + + package com.hollis; + import org.apache.commons.io.FileUtils; + import org.apache.commons.io.IOUtils; + import java.io.*; + import java.util.Date; + + /** + * Created by hollis on 16/2/2. + */ + public class SerializableDemo { + + public static void main(String[] args) { + //Initializes The Object + User user = new User(); + user.setName("hollis"); + user.setGender("male"); + user.setAge(23); + user.setBirthday(new Date()); + System.out.println(user); + + //Write Obj to File + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(new FileOutputStream("tempFile")); + oos.writeObject(user); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(oos); + } + + //Read Obj from File + File file = new File("tempFile"); + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new FileInputStream(file)); + User newUser = (User) ois.readObject(); + System.out.println(newUser); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(ois); + try { + FileUtils.forceDelete(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + } + //output + //User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016} + //User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016} + + +## 序列化及反序列化相关知识 + +1、在Java中,只要一个类实现了`java.io.Serializable`接口,那么它就可以被序列化。 + +2、通过`ObjectOutputStream`和`ObjectInputStream`对对象进行序列化及反序列化 + +3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 `private static final long serialVersionUID`) + +4、序列化并不保存静态变量。 + +5、要想将父类对象也序列化,就需要让父类也实现`Serializable` 接口。 + +6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。 + +7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。 + +## ArrayList的序列化 + +在介绍ArrayList序列化之前,先来考虑一个问题: + +> **如何自定义的序列化和反序列化策略** + +带着这个问题,我们来看`java.util.ArrayList`的源码 + +code 3 + + public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable + { + private static final long serialVersionUID = 8683452581122892189L; + transient Object[] elementData; // non-private to simplify nested class access + private int size; + } + + +笔者省略了其他成员变量,从上面的代码中可以知道ArrayList实现了`java.io.Serializable`接口,那么我们就可以对它进行序列化及反序列化。因为elementData是`transient`的,所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo,验证一下我们的想法: + +code 4 + + public static void main(String[] args) throws IOException, ClassNotFoundException { + List stringList = new ArrayList(); + stringList.add("hello"); + stringList.add("world"); + stringList.add("hollis"); + stringList.add("chuang"); + System.out.println("init StringList" + stringList); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist")); + objectOutputStream.writeObject(stringList); + + IOUtils.close(objectOutputStream); + File file = new File("stringlist"); + ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); + List newStringList = (List)objectInputStream.readObject(); + IOUtils.close(objectInputStream); + if(file.exists()){ + file.delete(); + } + System.out.println("new StringList" + newStringList); + } + //init StringList[hello, world, hollis, chuang] + //new StringList[hello, world, hollis, chuang] + + +了解ArrayList的人都知道,ArrayList底层是通过数组实现的。那么数组`elementData`其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢? + +### writeObject和readObject方法 + +在ArrayList中定义了来个方法: `writeObject`和`readObject`。 + +这里先给出结论: + +> 在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。 +> +> 如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。 +> +> 用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。 + +来看一下这两个方法的具体实现: + +code 5 + + 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