新手想要学习JDK源码,我推荐你从本篇文章开始
前言
本篇文章会讲解一部分的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 这是一个迭代器...
个人感觉 乱糟糟的有种这样的感觉...
不会画图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