前语
本篇文章会讲解一部分的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)会回来当时时刻的年份。
总结
这篇文章也不能算水吧,不过的确很枯燥无味,我个人也不是很喜欢写,所以我把这些定坐落新手集体,又或许咱们能够把这篇文章当成一篇漫笔来看,由于并没有一些很难的技术讲解,首要还是交给咱们如何去“阅读源码”
✍写在最终,
本人在山东,目前是一名大三的在校生,想暑假开始寻觅一份适宜的实习,假如有大佬看到能够给我一次时机。
好文推荐:
/post/722207… 最适宜新手的设计形式学习
/post/722172… 一文读懂前后端的交互流程