引言
在学习《Java
编程的逻辑》一书时记录的一些笔记,扫清了一些Java
基础的知识盲区,感谢作者马俊昌老师。
数据类型
序号 | 数据类型 | 大小(位) | 包装类 | 默认值 | 数据范围 |
---|---|---|---|---|---|
1 | byte | 8 | Byte | 0 | -128 ~ 127 |
2 | short | 16 | Short | 0 | -32768 ~ 32767 |
3 | int | 32 | Integer | 0 | -2147483648 ~ 2147483647 |
4 | long | 64 | Long | 0L | -9223372036854775808 ~ 9223372036854775807 |
5 | boolean | 8 | Boolean | false | true/false |
6 | char | 16 | Character | 空 | 0 ~ 65535 |
7 | float | 32 | Float | 0.0F | 1.4E-45 ~ 3.4028235E38 |
8 | double | 64 | Double | 0.0D | 4.9E-324 ~ 1.7976931348623157E308 |
装/拆箱
装箱:将基本类型转换为包装类的过程。
拆箱:将包装类型转换为基本类型的过程。
Java 5
以后引入了自动装箱和拆箱技术:
Integer a = 100;
int b = a;
自动装箱/拆箱是Java
编译器提供的能力,背后,它会替换为调用对应的valueOf/xxx-Value
方法,比如,上面的代码会被Java
编译器替换为:
Integer a = Integer.valueOf(100);
int b = a.intValue();
valueOf
一般建议使用valueOf
方法。
new
每次都会创建一个新对象,而除了Float
和Double
外的其他包装类,都会缓存包装类对象,减少需要创建对象的次数,节省空间,提升性能。
缓存
IntegerCache
表示Integer
缓存,其中的cache
变量是一个静态Integer
数组,在静态初始化代码块中被初始化。
默认情况下,保存了-128~127
共256
个整数对应的Integer
对象。
在valueOf
代码中,如果数值位于被缓存的范围,即默认-128~127
,则直接从Integer-Cache
中获取已预先创建的Integer
对象,只有不在缓存范围时,才通过new
创建对象。
序号 | 数据类型 | 数据缓存 |
---|---|---|
1 | Boolean | true, false |
2 | Byte | -128 ~ 127 |
3 | Short | -128 ~ 127 |
4 | Integer | -128 ~ 127 |
5 | Long | -128 ~ 127 |
6 | Character | 0 ~ 127 |
7 | Float | 无缓存 |
8 | Double | 无缓存 |
Switch语句
支持的数据类型
-
Java 5
之前,只支持byte
、short
、int
、char
四种。 -
Java 5
中,引入了枚举类型。因为枚举类的ordinal
返回一个int
值。 -
Java 7
中,支持String
类型,使用String
的hashCode
方法。
原因
switch
的效率相对较高,编译后使用跳转表实现。
跳转表有序,可以二分查找,所以支持的数据类型都是整数,枚举和String
也是转换为int
值。
跳转表的存储空间是32
位,容纳不下long
,所以switch
不支持long
类型。
接口
接口中变量修饰符是public static final
。
接口中方法修饰符是public abstract
。
java 8
允许在接口里定义默认方法default
和静态方法static
。
java 9
允许默认方法和静态方法可以是private
。
序列化
条件
一个类的对象要想序列化成功,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口。 - 该类的所有属性必须是可序列化的。如果不想序列化,则将该属性注明为
transient
。
否则会抛出NotSerializableException
异常。
注意
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID
是否一致。
序列化并不保存静态变量。
父类如果没有实现Serializable
接口时,反序列化时,会调用父类无参的构造函数。
Externalizable
Externalizable
接口继承自java.io.Serializable
。
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
实现Externalizable
接口后,序列化的细节由开发人员自己实现。
并且Externalizable
的优先级比Serializable
的优先级高。
异常
try/catch/finally
语法中,catch
不是必需的,也就是可以只有try
和finally
,表示不捕获异常,异常自动向上传递,但finally
中的代码在异常发生后也执行。
java 7
开始支持:多个异常之间可以用|
操作符:
try {
// 可能抛出ExceptionA和ExceptionB
} catch (ExceptionA | ExceptionB e) {
// 异常处理逻辑
}
try-with-resources
java 7
提供了一种新的语法,称之为try-with-resources
。
这种语法针对实现了java.lang.AutoCloseable
接口的对象,接口定义:
public interface AutoCloseable {
void close() throws Exception;
}
语法形式如下:
try (AutoCloseable resource = new FileInputStream("yunzhi.txt")) {
// 使用资源
}
资源resource
的声明和初始化放在try
语句内,不用再调用finally
,在执行完try
语句后,会自动调用资源的close
方法。
String
String
类内部用一个字符数组表示字符串,实例变量定义为:
private final char value[];
常量字符串
System.out.println("yunzhi.club".length());
System.out.println("yunzhi.club".contains("yunzhi"));
System.out.println("yunzhi.club".indexOf("yunzhi"));
实际上,这些常量就是String
类型的对象,在内存中,它们被放在一个共享的地方,这个地方称为字符串常量池,它保存所有的常量字符串,每个常量只会保存一份,被所有使用者共享。
当通过常量的形式使用一个字符串的时候,使用的就是常量池中的那个对应的String
类型的对象。
+/+=
Java
中,String
可以直接使用+
和+=
运算符,这是Java
编译器提供的支持,背后,Java
编译器一般会生成StringBuilder
,+
和+=
操作会转换为append
。
String hello = "hello";
hello += ", world";
System.out.println(hello);
背后,Java
编译器一般会转换为:
StringBuilder hello = new StringBuilder("hello");
hello.append(", world");
System.out.println(hello.toString());
对于简单的情况,可以直接使用String
的+
和+=
,对于复杂的情况,尤其是有循环的时候,应该直接使用StringBuilder
。
为什么要定义为不可变类呢?
不可变使得程序更为简单安全,因为不用操心数据被意外改写的可能,可以安全地共享数据,尤其是在多线程的环境下。
Map和Set
HashMap
- 效率高。
- 无序,因为
hash
值是无序的。
默认采用单链表解决冲突,如果链表长度超过8
,将单链表转换为红黑树。
负荷系数
static final float DEFAULT_LOAD_FACTOR = 0.75f;
为什么容量是2的幂
- 申请内存时,减少内存碎片。
- 进行移位操作,效率高。
-
tab[i = (n - 1) & hash]
,使用n-1
的&
运算,提高效率。
其他
扩容的条件:实际节点数大于等于容量的0.75
,就是负荷系数。
因为不同版本的JDK
计算的hash
值可能是不同的。只存储了数组的容量、实际节点数量和各个节点的key value
值。
HashSet
- 元素不重复。
- 添加、删除、判断元素是否存在都是
O(1)
的复杂度。 - 无序。
HashSet
内部是调用HashMap
来实现的,核心就是利用HashMap
中的key
不能相同进行去重,key
就是Set
中的值,value
存了一个空对象。
TreeMap
TreeMap
按键有序,为了实现有序,要求要么键实现Comparable
接口,要么创建TreeMap
时传递一个Comparator
对象。
内部使用红黑树实现,存储映射。
红黑树
红黑树是一种平衡二叉树,但它不是高度平衡的,而是大致平衡的。确保任意一条从根到叶子节点的路径,没有任何一条的路径的长度会比其他路径长过两倍。
红黑树减弱了对平衡的要求,降低了保持树平衡需要的开销,在实际应用中,统计性能超过平衡二叉树。
TreeSet
TreeSet
内部是基于TreeMap
的,实现了有序。
LinkedHashMap
HashMap
的子类,内部使用双向链表维护键值对的顺序,每个键值对既位于哈希表中,也位于这个双向链表中。
支持插入顺序维护或访问顺序维护。
插入顺序:先添加的在前面,后添加的在后面,修改操作不影响顺序。
访问顺序:对一个键值对进行get/put
操作,该键值对会移动到链表末尾,最末尾的是最近访问的,最开始的是最久没有访问的。
LinkedHashSet
HashSet
的子类,内部采用LinkedHashMap
实现。
EnumMap
一个key
是枚举类型的Map
。
内部使用了两个长度相同的数组,一个存键,一个存储对应的值,值为null
表示没有该键值对。键都有一个对应的索引,根据索引可直接访问和操作其键的值,效率高。
EnumSet
EnumSet
的实现和EnumMap
没有任何关系!
采用位向量实现。0
代表没有存这个枚举值,1
代表存了这个枚举值,效率很高。
PriorityQueue
优先级队列,内部采用堆实现。和其他需要比较的操作一样,要么实现Comparable
接口,要么创建时传递一个Comparator
对象。
适用场景
TOP K问题
维护一个最小堆,堆的根节点是最小的,即第K
大元素。
每次有新数据来,和根节点比,如果小,不操作,如果大,替换根节点,调整堆。调整的复杂度为O(logK)
。
求中值元素
维护一个最大堆和最小堆,假设当前中位数为M
,最大堆维护<=M
的元素,最小堆维护>=M
的元素,但两堆中都不包含M
。
新数据来时,如果<=M
,放到最大堆中,如果>=M
,放到最小堆中。
数据加入之后,如果两堆元素相差>=2
,将M
加入到元素少的堆中,元素多的堆的根节点移除作为新的中值元素。
集合关系图
之前一直很怕这张图,但是随着积累,图中的类我们也逐渐地学会并掌握了。
Collections
Collections
常用方法。
查找和替换
方法名 | 方法描述 |
---|---|
binarySearch |
二分查找 |
max |
最大值 |
min |
最小值 |
frequency |
集合中元素出现次数 |
indexOfSubList lastIndexOfSubList
|
在原List 中查找目标List 的位置 |
disjoint |
集合是否有交集 |
replaceAll |
List 中元素替换 |
排序和调整顺序
方法名 | 方法描述 |
---|---|
sort |
排序 |
swap |
元素交换 |
reverse |
列表反转 |
shuffle |
洗牌,随机打乱顺序 |
rotate |
循环移位 |
适配器
方法名 | 方法描述 |
---|---|
emptyList emptySet emptyMap emptyIterator
|
返回空的集合,使用时比返回null 更合理,更安全 |
singleton singletonList singletonMap
|
将单一对象转化为标准容器接口对象 |
装饰器
方法名 | 方法描述 |
---|---|
unmodifiableCollection unmodifiableList unmodifiableMap unmodifiableSet
|
让容器只读 |
synchronizedCollection synchronizedList synchronizedMap synchronizedSet
|
让容器线程安全,不是最优实现。 |
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。