根据链接 Java 工程师成神之路!的目录复习Java基础,并记录下一些笔记供以后翻阅。
Part1
- Java值传递
无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy)。在求值策略中,还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。Java值传递
按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。
简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。
- 字符串不可变
一个字符串在堆中开辟了空间,就是不可改变的,所有的方法都不可能改变它的值,但是我们可以返回一个新的字符串。
String str1 = "abcd";String str2 = "abcd";两个字符串对象str1和str2其实指向的是同一个字符串对象,当修改一个时,另一个也会变,所以不可变;并且不变的hashcode值便于找到这个字符串。
String 类底层是一个 final 修饰的 char 类型数组,意味着 String 类的对象是不可变的,所以 String 对象可以共享。
String 类中的每一个看起来会修改 String 值的方法,实际上都是创建了一个全新的 String 对象,用来包含修改后的字符串内容,这也可以说明 String 对象具有只读的属性。
- JDK 6 中的 substring 问题
jdk6的问题是大字符串,会被一直引用而没办法被垃圾回收,jdk7解决了这个问题。
-
包装类型自动拆装箱与Integer 的缓存机制
Integer、Long等Interger类型缓存-128到128之间的数值,这个区间中的数值指向的都是同一个对象,当超出范围才新建对象。Double、Float则没有缓存。
- switch 对 String 的支持
将字符串的hash值代替int从而进行判断,本质上还是int。
- HashSet保持元素不重复
在向hashSet中add()元素时,判断元素是否存在的依据,不仅仅是hash码值就能够确定的,同时还要结合equles方法。使用的是HashMap的put方法,HashMap也不重复。
- Collection 和 Collections 区别
Collections是collection的工具类,用来操作collection,不能实例化。collection是集合类的顶级接口。
- Arrays.asList使用避坑
1、使用包装类数组,不能使用基本类型,基本类型没有class属性;2、Arrays.asList返回的List实际是Arrays的一个内部类,不是真正的util.ArrayList,使用如下代码
List<String> myList = new ArrayList<String>(Arrays.asList(myArray));
代替,否则Arrays.asList直接返回的是一个固定大小的List,不具备可扩容性,add和remove方法都会失败。
- Enumeration 和 Iterator 区别
Iterator 遍历时可以删除元素,Enumeration 不可以,但 Enumeration 比 Iterator 快2倍左右。
- fail-fast 和 fail-safe
Iterator 遍历集合的时候,我们对集合结构上(集合的插入和删除是结构改变,修改不是)做出了改变,报 fail-fast(快速失败)错误;fail-safe 在这种情况下,复制一份新的集合进行遍历,不会报错。
- ConcurrentSkipListMap
跳表SkipList,redis也在用这种数据结构,和红黑树效率相当,实现简单。
- CopyOnWrite并发容器
CopyOnWriteArrayList写时复制,当有新元素添加到List时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组,适用于读多写少。写操作多,或者数组很大的时候,拷贝的数组占用内存。
- 枚举与单例模式
枚举实现的单例,写法简单且线程安全,因为枚举类在被虚拟机加载的时候会保证线程安全的被初始化。并且Java中有明确规定,枚举的序列化和反序列化是有特殊定制的,这就可以避免反序列化过程中由于反射而导致的单例被破坏问题。
类加载的loadClass方法使用了同步代码块,所以Java类的加载和初始化过程都是线程安全的。
普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。
枚举在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。
- 枚举的实例化
枚举有严格的实例化控制,final确保不会被克隆,而且序列化机制保证不会因为反序列化而创造新的实例,枚举的反射实例化也是被禁止的。总之,除了定义的枚举常量之外。没有枚举类型实例。
- 枚举的比较
使用 ==,比equals快且能正常工作,运行时安全、编译器安全。
- 字符和字节
java中,一个字符(char),一个字节(byte),GBK编码格式下,一个char是2个字节。
- IO流
InputStream -> OutputStream ; Reader -> Writer 。
字节输入 -> 字节输出 字符输入 -> 字符输出。
- 同步、异步、阻塞、非阻塞、IO多路复用
Part2
- Externalizable 和 Serializable
序列化是针对Java对象的,反序列化(IO输入)与序列化(IO输出),序列化是对象转换成二进制字节码,反序列化相反。String、Array、Enum可以直接序列化。序列化不保存静态变量。序列化会破坏单例。
Serializable接口只用来标识该类是否可序列化,没有其他含义。Externalizable继承了Serializable,当类实现了Externalizable接口时,可以自定义序列化和反序列化的方法。
- 为什么需要序列化呢?
第一种情况是:一般情况下Java
对象的声明周期都比Java
虚拟机的要短,实际应用中我们希望在JVM
停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。
第二种情况是:需要把Java
对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java
对象。
- 外观/门面模式:简化调用
- Protobuf传输协议:PB数据保存小、传输快,结构比JSon复杂。
- 反序列化的安全问题
最常见的是通过修改序列化之后的数据字段,从而进行提权或越权操作,最严重可导致远程代码执行。防御方案是完整性校验,常见的是JWT。
- Java 消息服务
同步消息rpc,异步消息jms/amqp。JMS是指两个应用程序之间的通信。activeMq用法和JMS相似,activeMq需要下载且有可视化界面。
消息传递模型 | 生产者/消费者 | 目的地 | 时间约束 |
点对点 | 一对一 | 队列Queue | 无 |
发布/订阅 | 一对多 | 主题Topic | 订阅者在发布者之前 |
- 泛型
泛型的上限,? extends E,接受E或E的子类型;下限,? super E,接受E或E的父类型。
泛型擦除,泛型只在编译器有用,运行时会将类型擦除。泛型可以在编译器指定类型,消除了用Obejct编写时的类转换错误(运行期错误),而且相比Object不用强制类型转换。
泛型的定义可以参考链接 Java泛型中List、List<Object>、List<?>的区别
- 泛型的KTVE
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
? - 表示不确定的java类型
- List、List<Object>、List<?>的区别
List:原生态类型
List<Object>:参数化的类型,表明List
中可以容纳任意类型的对象
List<?>:无限定通配符类型,表示只能包含某一种未知对象类型
- Netty
Netty封装了JDK的NIO,Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。
- API 和 SPI
- 格林威治时间GMT,世界标准时间
- SimpleDateFormat 的线程安全性
SimpleDateFormat转换日期是通过同一个Calendar对象来操作的,线程不安全;解决方法是给每个线程一个新的SimpleDateFormat对象,也可以使用java8。java.util.Date
和SimpleDateFormatter
都不是线程安全的,而LocalDate
和LocalTime
和最基本的String
一样,是不变类型,不但线程安全,而且不能修改。
说明:01基础篇已粗略复习完,由于内容太多,以后不写在本篇博客里了。