提交 f4f1cb4b 编写于 作者: H hollis.zhl

V3.0 ,知识体系完善,通过GitHub Pages提供更好的阅读体验

上级 c2231402
此差异已折叠。
Java中有8种基本数据类型
分为三大类。
字符型:char
布尔型:boolean
数值型:
1.整型:byte、short、int、long 2.浮点型:float、double
String不是基本数据类型,是引用类型。
\ No newline at end of file
> 本文是[《成神之路系列文章》](/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
## 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
[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
......@@ -5,6 +5,45 @@
| 主要版本 | 更新时间 | 备注 |
| ---- | ---------- | -------------- |
| v1.0 | 2015-08-01 | 首次发布 |
| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |
| v3.0 | 2020-03-29 | 知识体系完善<br>通过GitHub Page搭建,便于阅读|
| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通;<br>进一步完善知识体系; <br>新技术补充;|
| 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
<section class="cover show" style="background: linear-gradient(to left bottom, hsl(220, 100%, 85%) 0%,hsl(103, 100%, 85%) 100%)">
<div class="cover-main"><img width="180px" src="icon/icon.JPG">
<h1 id="toBeTopJavaer">
......@@ -18,4 +16,4 @@
</span>
<a href="#/README">Get Started</a></p></div><div class="mask"></div></section>
<a href="#/README">开始阅读</a></p></div><div class="mask"></div></section>
此差异已折叠。
* 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
* 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
* 面向对象
* 什么是面向对象
* [面向对象与面向过程](/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<Object>`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md)
* [`List<?>`和`List<Object>`之间的区别是什么?](/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
* 并发与并行
* [什么是并发](/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
Erlang 之父 Joe Armstrong 用一张比较形象的图解释了并发与并行的区别:
![](http://www.hollischuang.com/wp-content/uploads/2018/12/CON.jpg)
并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。
映射到计算机系统中,上图中的咖啡机就是CPU,两个队伍指的就是两个进程。
并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
那么,操作系统视如何实现这种并发的呢?
现在我们用到操作系统,无论是Windows、Linux还是MacOS等其实都是**多用户多任务分时操作系统**。使用这些操作系统的用户是可以“同时”干多件事的。
但是实际上,对于单CPU的计算机来说,在CPU中,同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。
如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。
由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所”停顿”,但用户察觉不出来,好像整个系统全由它”独占”似的。
所以,在单CPU的计算机中,我们看起来“同时干多件事”,其实是通过CPU时间片技术,并发完成的。
并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
\ No newline at end of file
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
## 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 | 结构调整,更适合从入门到精通;<br>进一步完善知识体系; <br>新技术补充;|
Java 基础
\ No newline at end of file
`限定通配符`对类型进⾏限制, 泛型中有两种限定通配符:
表示类型的上界,格式为:< extends T>,即类型必须为T类型或者T子类
表示类型的下界,格式为:< super T>,即类型必须为T类型或者T的父类
泛型类型必须⽤限定内的类型来进⾏初始化,否则会导致编译错误。
`⾮限定通配符`表⽰可以⽤任意泛型类型来替代,类型为<T>
\ No newline at end of file
在使用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
* 基本数据类型
* [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<Object>`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md)
* [`List<?>`和`List<Object>`之间的区别是什么?](/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
Java中有8种基本数据类型分为三大类。
### 字符型
char
### 布尔型
boolean
### 数值型
1.整型:byte、short、int、long
2.浮点型:float、double
*String不是基本数据类型,是引用类型。*
\ No newline at end of file
⾃定义异常就是开发⼈员⾃⼰定义的异常, ⼀般通过继承`Exception`的⼦类的⽅式实现。
编写⾃定义异常类实际上是继承⼀个API标准异常类, ⽤新定义的异常处理信息覆盖原有信息的过程。
这种⽤法在Web开发中也⽐较常见, ⼀般可以⽤来⾃定义业务异常。 如余额不⾜、 重复提交等。 这种⾃定义异常有业务含义, 更容易让上层理解和处理
\ No newline at end of file
Java中的类通过实现 `java.io.Serializable` 接口以启⽤其序列化功能。 未实现此接⼜的类将⽆法使其任何状态序列化或反序列化。
可序列化类的所有⼦类型本⾝都是可序列化的。
序列化接⼜没有⽅法或字段, 仅⽤于标识可序列化的语义。
当试图对⼀个对象进⾏序列化的时候, 如果遇到不⽀持`Serializable` 接口的对象。 在此情况下, 将抛`NotSerializableException`
如果要序列化的类有⽗类, 要想同时将在⽗类中定义过的变量持久化下来, 那么⽗类也应该集成`java.io.Serializable`接口。
`Externalizable`继承了`Serializable`, 该接⼜中定义了两个抽象⽅法:`writeExternal()``readExternal()`。 当使⽤`Externalizable`接口来进⾏序列化与反序列化的时候需要开发⼈员重写writeExternal()与readExternal()⽅法。
如果没有在这两个⽅法中定义序列化实现细节, 那么序列化之后, 对象内容为空。
实现`Externalizable`接⼜的类必须要提供⼀个`public`的⽆参的构造器。
所以, 实现`Externalizable`, 并实现`writeExternal()``readExternal()`⽅法可以指定序列化哪些属性。
Exception和 Error, ⼆者都是 Java异常处理的重要⼦类, 各⾃都包含⼤量⼦类。均继承自Throwable类。
Error表⽰系统级的错误, 是java运⾏环境内部错误或者硬件问题, 不能指望程序来处理这样的问题, 除了退出运⾏外别⽆选择, 它是Java虚拟机抛出的。
Exception 表⽰程序需要捕捉、 需要处理的常, 是由与程序设计的不完善⽽出现的问题, 程序必须处理的问题。
\ No newline at end of file
“异常链”是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
Java中的异常, 主要可以分为两⼤类, 即受检异常( checked exception) 和 ⾮受检异常( unchecked exception)
### 受检异常
对于受检异常来说, 如果⼀个⽅法在声明的过程中证明了其要有受检异常抛出:
public void test() throw new Exception{ }
那么,当我们在程序中调⽤他的时候, ⼀定要对该异常进⾏处理( 捕获或者向上抛出) , 否则是⽆法编译通过的。 这是⼀种强制规范。
这种异常在IO操作中⽐较多。 ⽐如FileNotFoundException , 当我们使⽤IO流处理⼀个⽂件的时候, 有⼀种特殊情况, 就是⽂件不存在, 所以, 在⽂件处理的接⼜定义时他会显⽰抛出FileNotFoundException, ⽬的就是告诉这个⽅法的调⽤者,我这个⽅法不保证⼀定可以成功, 是有可能找不到对应的⽂件
的, 你要明确的对这种情况做特殊处理哦。
所以说, 当我们希望我们的⽅法调⽤者, 明确的处理⼀些特殊情况的时候, 就应该使⽤受检异常。
### 非受检异常
对于⾮受检异常来说, ⼀般是运⾏时异常, 继承⾃RuntimeException。 在编写代码的时候, 不需要显⽰的捕获,但是如果不捕获, 在运⾏期如果发⽣异常就会中断程序的执⾏。
这种异常⼀般可以理解为是代码原因导致的。 ⽐如发⽣空指针、 数组越界等。 所以, 只要代码写的没问题, 这些异常都是可以避免的。 也就不需要我们显⽰的进⾏处理。
试想⼀下, 如果你要对所有可能发⽣空指针的地⽅做异常处理的话, 那相当于你的所有代码都需要做这件事。
\ No newline at end of file
`<? extends T>``<? super T>`是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。
`<? extends T>`:是指 “上界通配符(Upper Bounds Wildcards)”,即泛型中的类必须为当前类的子类或当前类。
`<? super T>`:是指 “下界通配符(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<? extends Fruit> list){
//报错,extends为上界通配符,只能取值,不能放.
//因为Fruit的子类不只有Apple还有Banana,这里不能确定具体的泛型到底是Apple还是Banana,所以放入任何一种类型都会报错
//list.add(new Apple());
//可以正常获取
Fruit fruit = list.get(1);
}
public void testSuper(List<? super Fruit> 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
###一、当泛型遇到重载
public class GenericTypes {
public static void method(List<String> list) {
System.out.println("invoke method(List<String> list)");
}
public static void method(List<Integer> list) {
System.out.println("invoke method(List<Integer> list)");
}
}
上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是`List<String>`另一个是`List<Integer>` ,但是,这段代码是编译通不过的。因为我们前面讲过,参数`List<Integer>``List<String>`编译之后都被擦除了,变成了一样的原生类型List<e>,擦除动作导致这两个方法的特征签名变得一模一样。</e>
### 二、当泛型遇到catch
如果我们自定义了一个泛型异常类GenericException<t>,那么,不要尝试用多个catch取匹配不同的异常类型,例如你想要分别捕获GenericException<string>、GenericException<integer>,这也是有问题的。</integer></string></t>
### 三、当泛型内包含静态变量
public class StaticTest{
public static void main(String[] args){
GT<Integer> gti = new GT<Integer>();
gti.var=1;
GT<String> gts = new GT<String>();
gts.var=2;
System.out.println(gti.var);
}
}
class GT<T>{
public static int var=0;
public void nothing(T x){}
}
答案是——2!
由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
Java泛型( generics) 是JDK 5中引⼊的⼀个新特性, 允许在定义类和接⼜的时候使⽤类型参数( type parameter) 。
声明的类型参数在使⽤时⽤具体的类型来替换。 泛型最主要的应⽤是在JDK 5中的新集合类框架中。
泛型最⼤的好处是可以提⾼代码的复⽤性。 以List接⼜为例,我们可以将String、 Integer等类型放⼊List中, 如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题。
\ No newline at end of file
了解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
异常的处理⽅式有两种。 1、 ⾃⼰处理。 2、 向上抛, 交给调⽤者处理。
异常, 千万不能捕获了之后什么也不做。 或者只是使⽤`e.printStacktrace`
具体的处理⽅式的选择其实原则⽐较简明: ⾃⼰明确的知道如何处理的, 就要处理掉。 不知道如何处理的, 就交给调⽤者处理。
\ No newline at end of file
throws、 throw、 try、 catch、 finally
try⽤来指定⼀块预防所有异常的程序;
catch⼦句紧跟在try块后⾯, ⽤来指定你想要捕获的异常的类型;
finally为确保⼀段代码不管发⽣什么异常状况都要被执⾏;
throw语句⽤来明确地抛出⼀个异常;
throws⽤来声明⼀个⽅法可能抛出的各种异常;
\ No newline at end of file
`try()` ⾥⾯有⼀个`return`语句, 那么后⾯的`finally{}`⾥⾯的code会不会被执⾏, 什么时候执⾏, 是在`return`前还是`return`后?
如果try中有return语句, 那么finally中的代码还是会执⾏。因为return表⽰的是要整个⽅法体返回, 所以,finally中的语句会在return之前执⾏。
\ No newline at end of file
Protocol Buffer (简称Protobuf) 是Google出品的性能优异、跨语言、跨平台的序列化库。
2001年初,Protobuf首先在Google内部创建, 我们把它称之为 proto1,一直以来在Google的内部使用,其中也不断的演化,根据使用者的需求也添加很多新的功能,一些内部库依赖它。几乎每个Google的开发者都会使用到它。
Google开始开源它的内部项目时,因为依赖的关系,所以他们决定首先把Protobuf开源出去。
目前Protobuf的稳定版本是3.9.2,于2019年9月23日发布。由于很多公司很早的就采用了Protobuf,所以很多项目还在使用proto2协议,目前是proto2和proto3同时在使用的状态。
关于`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<Long>() {
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
序列化是将对象的状态信息转换为可存储或传输的形式的过程。
我们都知道, 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
## 序列化与反序列化
序列化 (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 可以用来限制某些用户的使用。
## 参考资料
......
序列化是一种对象持久化的手段。普遍应用在网络传输、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<E> extends AbstractList<E>
implements List<E>, 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<String> stringList = new ArrayList<String>();
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<String> newStringList = (List<String>)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<size; i++) {
a[i] = s.readObject();
}
}
}
code 6
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
那么为什么ArrayList要用这种方式来实现序列化呢?
### why transient
ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。
### why writeObject and readObject
前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用`transient`来声明`elementData`。 但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写`writeObject``readObject`方法的方式把其中的元素保留下来。
`writeObject`方法把`elementData`数组中的元素遍历的保存到输出流(ObjectOutputStream)中。
`readObject`方法从输入流(ObjectInputStream)中读出对象并保存赋值到`elementData`数组中。
至此,我们先试着来回答刚刚提出的问题:
> 如何自定义的序列化和反序列化策略
答:可以通过在被序列化的类中增加writeObject 和 readObject方法。那么问题又来了:
> 虽然ArrayList中写了writeObject 和 readObject 方法,但是这两个方法并没有显示的被调用啊。
>
> **那么如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的呢?**
## ObjectOutputStream
从code 4中,我们可以看出,对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,我们来分析一下ArrayList中的writeObject 和 readObject 方法到底是如何被调用的呢?
为了节省篇幅,这里给出ObjectOutputStream的writeObject的调用栈:
`writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject`
这里看一下invokeWriteObject:
void invokeWriteObject(Object obj, ObjectOutputStream out)
throws IOException, UnsupportedOperationException
{
if (writeObjectMethod != null) {
try {
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof IOException) {
throw (IOException) th;
} else {
throwMiscException(th);
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
其中`writeObjectMethod.invoke(obj, new Object[]{ out });`是关键,通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的:
> class-defined writeObject method, or null if none
在我们的例子中,这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了。
至此,我们先试着来回答刚刚提出的问题:
> **如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?**
答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。
* * *
至此,我们已经介绍完了ArrayList的序列化方式。那么,不知道有没有人提出这样的疑问:
<div id="What Serializable Did">
</div>
> **Serializable明明就是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?**
Serializable接口的定义:
public interface Serializable {
}
读者可以尝试把code 1中的继承Serializable的代码去掉,再执行code 2,会抛出`java.io.NotSerializableException`
其实这个问题也很好回答,我们再回到刚刚ObjectOutputStream的writeObject的调用栈:
`writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject`
writeObject0方法中有这么一段代码:
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`
## 总结
1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出`NotSerializableException`异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略
## 参考资料
[Java 序列化的高级认识][1]
[1]: https://www.ibm.com/developerworks/cn/java/j-lo-serial/
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册