likes
comments
collection
share

新手想要学习JDK源码,我推荐你从本篇文章开始

作者站长头像
站长
· 阅读数 5

前言

本篇文章会讲解一部分的JDK 源码 从简单入手、逐渐提高难度

比如 String类、ArrayList类、HashMap类 等典型的类的源码的实现~

我们大概就不讲什么B+树、红黑树啥的了 哈哈 O(∩_∩)O (一方面新手可能没必要了解这些,另一方面作者也是新手 (●'◡'●))

希望大家打开 idea 跟随作者一起去一步一步的去阅读源码

String源码

进入到String源码中

我们首先看到 String 实现了 序列化基础接口(通过序列化,对象的状态可以存储在文件或者网络中传输)

其次 看到 String 实现了 Comparable 接口 该接口定义了一个 compareto() 方法,用于比较两个字符串之间的大小关系。实现该接口的类可以进行排序操作

以及 实现了 CharSequence 接口:该接口定义了一组基本的字符串操作方法,包括获取字符串长度、获取指定位置上的字符、截取子串等操作。实现该接口的类也可以被当做字符串来使用。

在源码中 我们发现

private final char value[];

也就是说明 String 是不可改变的 因为被final 好处就是更加安全 当然虽然string本身不可改变,但我们也可以通过新建String 实现字符串的修改

private int hash; // Default to 0 缓存哈希值

我们都知道hash的作用就是快速判断对象是否相等,如果哈希值不同则对象必不相等。

当然 实际中 Java 中并不是所有的类都适合使用 hash 值来比较对象是否相等,这取决于具体的业务需求和实现方式。比如,在某些场景下,如果两个对象的属性值相同,则认为它们是相等的,而与其哈希值无关。在这种情况下,应该重写 equals() 方法,并根据对象的实际属性来进行比较。

除此之外呢

public int length() {
    return value.length; //返回长度
}
public boolean isEmpty() {
    return value.length == 0; //是否为空 根据长度 是否为0
}
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
    throw new StringIndexOutOfBoundsException(index); // 索引的校验
}
return value[index];  // 返回索引位置的值
}

其中 还有一些类 构造函数

String():创建一个空的字符串对象。

String(char[] value):使用指定的字符数组来创建一个字符串对象。

String(char[] value, int offset, int count):使用指定的字符数组中的一部分来创建一个字符串对象。

String(String original):使用另一个字符串对象来创建一个新的字符串对象,内容与原始字符串相同。

String(byte[] bytes):使用指定的字节数组来创建一个字符串对象,默认使用 UTF-8 编码方式。

String(byte[] bytes, Charset charset):使用指定的编码方式来将字节数组转换成字符串对象。

String(byte[] bytes, int offset, int length):使用指定的字节数组中的一部分来创建一个字符串对象,默认使用 UTF-8 编码方式。

String(byte[] bytes, int offset, int length, Charset charset):使用指定的编码方式来将字节数组的一部分转换成字符串对象。

除此之外,还有一些特殊的字符串构造函数,比如通过 StringBuffer 或 StringBuilder 对象来构造字符串、通过字符或者 Unicode

可以说是非常全面了~~~(考虑的很全面)

总体来说 string 源码阅读起来并不算太难!(感兴趣小伙伴可以点进去看看 相信都能看懂 、最重要的是走出这一步)

ArrayList源码

看到 import util 它是一个util包下的类

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

看上述代码,首先也是集成一个抽象的List类

我们进入List类来看看(注意 一定要跟随作者我一起,比如我现在进行了这一步,你也要这样,作者所讲述的真的非常简单)

public abstract class AbstractList extends AbstractCollection implements List

可以看到 它也是继承的 抽象集合类 实现的List接口

抽象集合类 实现的 集合接口 集合接口 同样的 继承的 Iterable 这是一个迭代器...

个人感觉 乱糟糟的有种这样的感觉...

新手想要学习JDK源码,我推荐你从本篇文章开始

不会画图emmm

总而言之

Collection 是所有集合框架类的根接口,它定义了一些通用的操作方法,例如添加元素、删除元素、判断元素是否存在等;

AbstractCollection 是 Collection 接口的抽象实现类,提供了一些默认的实现方法,可以简化继承自 AbstractCollection 的子类的实现过程;

AbstractList 是 List 接口的抽象实现类,同样提供了一些默认的实现方法,可以简化继承自 AbstractList 的子类的实现过程;

ArrayList 是 List 接口的一个具体实现类,底层通过数组来实现,支持动态扩容。

因此,可以将它们的作用总结如下:

Collection 是集合框架中的根接口,定义了集合的基本操作;

AbstractCollection 和 AbstractList 都是抽象类,为继承它们的子类提供了默认实现,可以减少子类的代码量;

ArrayList 是 List 接口的具体实现类,通过数组来实现动态扩容,可以高效地进行随机访问,但不适合频繁插入和删除元素。

那么为什么搞一个抽象类呢? 就像上面简化代码 那么如何做到简化代码的呢?

其实这是因为抽象类可以包含抽象方法和非抽象方法,其中抽象方法是只有声明而没有实现的方法,需要子类去实现。而非抽象方法是已经实现的方法,可以直接在抽象类中调用。

当我们从一个抽象类继承并实现它时,如果不需要修改默认实现,就可以直接使用父类中的实现,这样可以减少代码量和开发时间。如果我们需要修改默认实现,可以覆盖父类中的实现。

举个例子,AbstractList 中提供了默认的 add 和 remove 方法的实现,这些方法可以适用于大多数的List 实现,例如 ArrayList、LinkedList等。当我们需要定义一种新的List 实现时,我们可以直接继承 AbstractList 并重写一些特定的方法,这样可以减少我们的代码量,并且可以避免一些常见的错误。

AbstractList 中提供了默认的 add 和 remove 方法的实现, 这就是非抽象方法!!!

回归正题,我们继续来看 ArrayList 的源码

首先第一句话 是实例化版本号

看到第二句话

7/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

没错,这就是大名鼎鼎容量,你们应该听说过什么扩容机制对吧,我们先不深究就说这里,默认的容量为10

嘻嘻看不懂了~~ 就先到这里吧(●'◡'●)

System源码

进入 System 迎面第一句话

private static native void registerNatives();
static {
    registerNatives();
}

这句代码的作用呢就是:

在System类中,静态代码块的作用是在类加载时自动执行registerNatives()方法,从而向JVM注册一些与系统相关的本地方法。这样,当我们调用System类中的某些方法时,就可以直接使用这些本地方法来实现对应的功能,提高了程序的效率和性能。

System.currentTimeMillis() 获取当前时间毫秒数(时间戳)

来看看怎么实现的

public static native long currentTimeMillis(); emm 原来是向jvm注入的本地方法

除此之外针对上面获取时间戳,我们好像还可以使用 Date类

那么就让我们来进入Date类的源码内部看看吧!

我们看到它的初始化

public Date() {
    this(System.currentTimeMillis());
}

public Date(long date) {

fastTime = date;

}

可以看到无参构造函数内部调用了 System.currentTimeMillis() 方法获取当前时间的毫秒数,并将其作为参数传入了有参构造函数。在有参构造函数中,它会将传入的毫秒数直接赋值给类成员变量 fastTime,这个成员变量代表了 Date 对象所表示的时间点的毫秒数。

然后,在 Date 类的其他方法中,比如 toString() 方法,它会根据 fastTime 成员变量的值来计算出对应的年、月、日、时、分、秒等信息,并返回一个字符串形式的日期和时间表示形式。因此,调用 new Date().toString() 会返回一个字符串,其中包含了当前时间的年、月、日、时、分、秒等信息。

里面还有很多 获取当前包含了当前时间的年、月、日、时、分、秒等信息的方法

但是 里面哪些方法 都不好用

比如这里:

public int getYear() { return normalize().getYear() - 1900; }

这是一个Java代码,它定义了一个公共方法名为“getYear()”。这个方法使用了另外一个定义在该类中的方法“normalize()”,并且从中获取一个日期对象。然后它获取这个日期对象的年份,并将其减去1900,然后返回这个值。

需要注意的是,这个方法实际上不太好用,因为它返回的是一个经过特殊处理的年份值(减去了1900)。如果你想要获取普通年份值,那么你可以使用Java Date类中的getYear()方法,但是这个方法已经被标记为过时了,所以最好使用Calendar类中的getInstance()的get(Calendar.YEAR)方法。

所以我们使用日历类 Calendar

Calendar源码

System.out.println(Calendar.getInstance().get(Calendar.YEAR));

解析:

当我们调用Calendar.getInstance()方法时,会返回一个Calendar对象。这个对象是由Calendar类的静态工厂方法创建的,具体实现可以参考下面的代码:

public static Calendar getInstance() {
    return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}

这个方法会通过默认的时区和语言环境创建一个新的Calendar对象。在这里,它使用了默认的时区和语言环境,但是你也可以指定自己需要的时区和语言环境来创建Calendar对象。

接下来,我们可以调用get(Calendar.YEAR)方法来获取Calendar对象所表示的年份。这个方法的实现:

public int get(int field) {
    complete();
    return internalGet(field);
}

这个方法会首先调用complete()方法来确保Calendar对象已经初始化完成,然后再调用internalGet(field)方法来获取指定时间字段的值。

internalGet(field)方法会根据传入的参数field来获取对应时间字段的值。在这里,我们传入了Calendar.YEAR作为参数,它表示取得年份字段的值。internalGet(field)方法的实现如下:

protected int internalGet(int field) {
    return fields[field];
}

这个方法会直接返回fields数组中相应时间字段的值,fields数组存储了Calendar对象所有时间字段的值。因此,Calendar.getInstance().get(Calendar.YEAR)会返回当前时间的年份。

总结

这篇文章也不能算水吧,不过确实很枯燥无味,我个人也不是很喜欢写,所以我把这些定位于新手群体,又或者大家可以把这篇文章当成一篇随笔来看,因为并没有一些很难的技术讲解,主要还是交给大家如何去“阅读源码”

✍写在最后,

本人在山东,目前是一名大三的在校生,想暑假开始寻找一份合适的实习,如果有大佬看到可以给我一次机会。


好文推荐:

转载自:https://juejin.cn/post/7223009159246905402
评论
请登录