1

引言

clipboard.png

在学习《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每次都会创建一个新对象,而除了FloatDouble外的其他包装类,都会缓存包装类对象,减少需要创建对象的次数,节省空间,提升性能。

缓存

IntegerCache表示Integer缓存,其中的cache变量是一个静态Integer数组,在静态初始化代码块中被初始化。

默认情况下,保存了-128~127256个整数对应的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之前,只支持byteshortintchar四种。
  • Java 5中,引入了枚举类型。因为枚举类的ordinal返回一个int值。
  • Java 7中,支持String类型,使用StringhashCode方法。

原因

switch的效率相对较高,编译后使用跳转表实现。

跳转表有序,可以二分查找,所以支持的数据类型都是整数,枚举和String也是转换为int值。

跳转表的存储空间是32位,容纳不下long,所以switch不支持long类型。

接口

接口中变量修饰符是public static final

接口中方法修饰符是public abstract

java 8允许在接口里定义默认方法default和静态方法static

java 9允许默认方法和静态方法可以是private

序列化

条件

一个类的对象要想序列化成功,必须满足两个条件:

  1. 该类必须实现java.io.Serializable接口。
  2. 该类的所有属性必须是可序列化的。如果不想序列化,则将该属性注明为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不是必需的,也就是可以只有tryfinally,表示不捕获异常,异常自动向上传递,但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

  1. 效率高。
  2. 无序,因为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

  1. 元素不重复。
  2. 添加、删除、判断元素是否存在都是O(1)的复杂度。
  3. 无序。

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代表存了这个枚举值,效率很高。

clipboard.png

PriorityQueue

优先级队列,内部采用堆实现。和其他需要比较的操作一样,要么实现Comparable接口,要么创建时传递一个Comparator对象。

clipboard.png

适用场景

TOP K问题

维护一个最小堆,堆的根节点是最小的,即第K大元素。

每次有新数据来,和根节点比,如果小,不操作,如果大,替换根节点,调整堆。调整的复杂度为O(logK)

求中值元素

维护一个最大堆和最小堆,假设当前中位数为M,最大堆维护<=M的元素,最小堆维护>=M的元素,但两堆中都不包含M

新数据来时,如果<=M,放到最大堆中,如果>=M,放到最小堆中。

数据加入之后,如果两堆元素相差>=2,将M加入到元素少的堆中,元素多的堆的根节点移除作为新的中值元素。

clipboard.png

集合关系图

之前一直很怕这张图,但是随着积累,图中的类我们也逐渐地学会并掌握了。

clipboard.png

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 让容器线程安全,不是最优实现。

张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。