1.记一次源码追踪分析,码分从Java到JNI,码分再到JVM的码分C++:fileChannel.map()为什么快;源码分析map方法,put方法
2.JDK源码分析-Queue,码分 Deque
3.JDK成长记7:3张图搞懂HashMap底层原理!
4.JDK源码分析Timer/TimerTask 源码分析
5.jdk源码剖析手册?
6.面试官:从源码分析一下TreeSet(基于jdk1.8)
记一次源码追踪分析,码分从Java到JNI,码分互动易爬虫源码再到JVM的码分C++:fileChannel.map()为什么快;源码分析map方法,put方法
前言
在系统IO相关的码分系统调用有read/write,mmap,码分sendfile等这些。码分
其中read/write是码分普通的读写,每次都需要将buffer从用户空间拷贝到内核空间;
而mmap使用的码分是内存映射,会将磁盘文件对应的码分页映射(拷贝)到内核空间的page cache,并记录到用户进程的码分页表中,使得用户空间也可以像操作用户空间一样操作该文件的码分映射,最后再由操作系统来讲该映射(脏页)回写到磁盘;
sendfile则使用的是零拷贝技术,在mmap的基础上,当发送数据的时候只拷贝fd和offset等元数据信息,而将数据主体直接拷贝至protocol buffer,实现了内核数据零冗余的零拷贝技术
本文地址:/post//
问题/目的问题1Java中哪些API使用到了mmap问题2怎么知道该API使用到了mmap,如何追踪程序的系统调用目的1源码中分析验证,从Java到JNI,再到C++:fileChannel.map()使用的是系统调用mmap目的2源码验证分析:调用mmapedByteBuffer.put(Byte[])时JVM在搞些什么?mmap比普通的read/write快在哪?揭晓答案1mmap在Java NIO中的体现/使用看一个例子
// 1GBpublic static final int _GB = 1**;File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer mmapedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB);for (int i = 0; i < _GB; i++) { count++;mmapedByteBuffer.put((byte)0);}其中fileChannel.map()底层使用的就是系统调用mmap,函数签名为: public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException
答案2程序执行的系统调用追踪/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}把上面这段代码编译后把“.class”文件拉到linux执行,并用linux上的strace工具记录其系统调用日志,拿到日志文件我们可以在日志中看到以下信息(关于怎么拿到日志可以参照我的博文:无(代写)):
注:日志有多行,这里只选取我们关注的
// ...// 看到了我们打的开始标志openat(AT_FDCWD, "start1.txt", O_RDONLY) = -1 ENOENT (No such file or directory)// ... // 打开文件,文件描述符fd为6openat(AT_FDCWD, "filename", O_RDWR|O_CREAT, ) = 6// 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// ... // 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// 进行内存映射mmap(NULL, , PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x7f2fd6cd// ...// 程序退出exit(0)// 看到了我们打的结束标志openat(AT_FDCWD, "end.txt", O_RDONLY) = -1 ENOENT (No such file or directory)在上面程序的系统调用日志中我们确实看到了我们打的开始标志,结束标志。在开始标志和结束标志之间我们看到了我们的文件"filename"确实被打开了,文件描述符fd = 6;在打开文件后紧接着又执行了系统调用mmap,这一点我们Java代码一致,这样,我们就验证了我们答案1中的结论,可以开始我们的下文了
源码追踪分析,从Java到JNI,再到JVM的C++目的1寻源之旅:fileChannel.map()我们知道我们执行Java代码fileChannel.map()确实会在底层调用系统调用,那怎么在源码中得到验证呢?怎么落脚于源码进行分析呢?下面开始我们的寻源之旅
FileChannelImpl.map() 注:由于代码较长,这里代码中略去了一些我们不关注的,比如异常捕获等
public MappedByteBuffer map(MapMode mode, long position, long size)throws IOException{ // ...try { // ...synchronized (positionLock) { // ...long mapPosition = position - pagePosition;mapSize = size + pagePosition;try { // !我们要找的语句就在这!addr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError x) { // 如果内存不足,先尝试进行GCSystem.gc();try { Thread.sleep();} catch (InterruptedException y) { Thread.currentThread().interrupt();}try { // 再次试着mmapaddr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError y) { // After a second OOME, failthrow new IOException("Map failed", y);}}} // ...} finally { // ...}}上面函数源码中真正执行mmap的语句是在addr = map0(imode, mapPosition, mapSize),于是我们寻着这里继续追踪
FileChannelImpl.map0()
// Creates a new mappingprivate native long map0(int prot, long position, long length)throws IOException;可以看到,该方法是一个native方法,所以后面的源码我们需要到这个FileChannelImpl.class对应的fileChannelImpl.c中去看,所以我们需要去找到JDK的源码
在JDK源码中我们找到fileChannelImpl.c文件
fileChannelImpl.c 根据JNI的对应规则,我们找到该文件内对应的Java_sun_nio_ch_FileChannelImpl_map0方法,其源码如下:
JNIEXPORT jlong JNICALLJava_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this, jint prot, jlong off, jlong len){ void *mapAddress = 0;jobject fdo = (*env)->GetObjectField(env, this, chan_fd);jint fd = fdval(env, fdo);int protections = 0;int flags = 0;if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) { protections = PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) { protections = PROT_WRITE | PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) { protections =PROT_WRITE | PROT_READ;flags = MAP_PRIVATE;}// !我们要找的语句就在这里!mapAddress = mmap(0,/* Let OS decide location */len,/* Number of bytes to map */protections,/* File permissions */flags,/* Changes are shared */fd, /* File descriptor of mapped file */off); /* Offset into file */if (mapAddress == MAP_FAILED) { if (errno == ENOMEM) { JNU_ThrowOutOfMemoryError(env, "Map failed");return IOS_THROWN;}return handle(env, -1, "Map failed");}return ((jlong) (unsigned long) mapAddress);}我们要找的语句就上面代码中的mapAddress = mmap(0,len,protections,flags,fd,off),至于为什么不是直接的mmap,而是mmap,是因为这里的mmap是一个宏,在文件上方有其定义,如下:
#define mmap mmap至此,我们就在源码中得到验证了我们问题2中的安卓源码去哪里下载结论:fileChannelImpl.map()底层使用的是mmap系统调用
目的2寻源之旅:mmapedByteBuffer.put(Byte[ ])接着我们来看看当我们调用mmapedByteBuffer.put(Byte[])JVM底层在搞些什么动作
MappedByteBuffer ?首先我们得知道,当我们执行MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB)时,实际返回的对象是DirectByteBuffer类的实例,因为MappedByteBuffer为抽象类,且只有DirectByteBuffer继承了它,看下面两图就明白了
DirectByteBuffer 于是我们找到DirectByteBuffer内的put(Byte[ ])方法
public ByteBuffer put(byte x) { unsafe.putByte(ix(nextPutIndex()), ((x)));return this;}可以看到该方法内实际是调用Unsafe类内的putByte方法来实现功能的,所以我们还得去看Unsafe类
Unsafe.class
public native voidputByte(long address, byte x);该方法在Unsafe内是一个native方法,所以所以我们还得去看unsafe.cpp文件内对应的实现
unsafe.cpp
在JDK源码中,我们找到unsafe.cpp
在这份源码内,没有使用JNI内普通加前缀的方法来形成对应关系
不过我们还是能顺着源码的蛛丝轨迹找到我们要找的方法
注意到源码中有这样的注册机制,所以我们可以知道我们要找的代码就是上图中标注的代码
顺藤摸瓜,我们就找到了该方法的定义
UNSAFE_ENTRY(void, Unsafe_SetNative##Type(JNIEnv *env, jobject unsafe, jlong addr, java_type x)) \UnsafeWrapper("Unsafe_SetNative"#Type); \JavaThread* t = JavaThread::current(); \t->set_doing_unsafe_access(true); \void* p = addr_from_java(addr); \*(volatile native_type*)p = x; \t->set_doing_unsafe_access(false); \UNSAFE_END \该方法内主要的逻辑语句就是以下两句:
/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}0至此,我们就知道:其实我们调用mmapedByteBuffer.put(Byte[ ])时,JVM底层并不需要涉及到系统调用(这里也可以用strace工具追踪从而得到验证)。也就是说通过mmap映射的空间在内核空间和用户空间是共享的,我们在用户空间只需要像平时使用用户空间那样就行了————获取地址,设置值,而不涉及用户态,内核态的切换
总结fileChannelImpl.map()底层用调用系统函数mmap
fileChannelImpl.map()返回的其实不是MappedByteBuffer类对象,而是DirectByteBuffer类对象
在linux上可以通过strace来追踪系统调用
JNI中“.class”文件内方法与“.cpp”文件内函数的对应关系不止是前缀对应的方法,还可以是注册的方式,这一点的追寻代码的时候有很大帮助
directByteBuffer.put()方法底层并没有涉及系统调用,也就不需要涉及切态的性能开销(其底层知识执行获取地址,设置值的操作),所以mmap的性能就比普通读写read/write好
...
原文:/post/JDK源码分析-Queue, Deque
Queue 和 Deque 是 Java 中的两个接口,分别代表队列和双端队列。
Queue 接口提供了基本的队列操作:入队(enqueue)和出队(dequeue)。同时,Queue 接口有 6 个方法,分为入队、出队和遍历三类。与之不同的是,当队列为空时,element() 方法会抛出异常,而 peek() 方法则会返回 null。
Deque 接口继承自 Queue 接口,表示双端队列,具备「队列」和「栈」的特性。双端队列可以分别从两端插入和移除元素,而一般队列只能从尾部插入元素、头部移除元素。Deque 接口定义了入队、出队、遍历以及独有的一些操作方法。Deque 作为双端队列,不仅继承了 Queue 的方法,还提供了额外的双端操作。
综上,Queue 提供了基本的队列功能,而 Deque 在 Queue 的基础上增加了双端操作,使其兼具队列和栈的特性。在实际应用中,根据需求选择合适的接口可以提高代码的灵活性和效率。
JDK成长记7:3张图搞懂HashMap底层原理!有jar包怎么获得源码
一句话讲, HashMap底层数据结构,JDK1.7数组+单向链表、JDK1.8数组+单向链表+红黑树。
在看过了ArrayList、LinkedList的底层源码后,相信你对阅读JDK源码已经轻车熟路了。除了List很多时候你使用最多的还有Map和Set。接下来我将用三张图和你一起来探索下HashMap的底层核心原理到底有哪些?
首先你应该知道HashMap的核心方法之一就是put。我们带着如下几个问题来看下图:
如上图所示,put方法调用了putVal方法,之后主要脉络是:
如何计算hash值?
计算hash值的算法就在第一步,对key值进行hashCode()后,对hashCode的值进行无符号右移位和hashCode值进行了异或操作。为什么这么做呢?其实涉及了很多数学知识,简单的说就是尽可能让高和低位参与运算,可以减少hash值的冲突。
默认容量和扩容阈值是多少?
如上图所示,很明显第二步回调用resize方法,获取到默认容量为,这个在源码里是1<<4得到的,1左移4位得到的。之后由于默认扩容因子是0.,所以两者相乘就是扩容大小阈值*0.=。之后就分配了一个大小为的Node[]数组,作为Key-Value对存放的数据结构。
最后一问题是,如何进行hash寻址的?
hash寻址其实就在数组中找一个位置的意思。用的算法其实也很简单,就是用数组大小和hash值进行n-1&hash运算,这个操作和对hash取模很类似,只不过这样效率更高而已。hash寻址后,就得到了一个位置,可以把key-value的Node元素放入到之前创建好的Node[]数组中了。
当你了解了上面的三个原理后,你还需要掌握如下几个问题:
还是老规矩,看如下图:
当hash值计算一致,比如当hash值都是时,Key-Value对的Node节点还有一个next指针,会以单链表的形式,将冲突的节点挂在数组同样位置。这就是数据结构中所提到解决hash 的冲突方法之一:单链法。当然还有探测法+rehash法有兴趣的人可以回顾《数据结构和算法》相关书籍。
但是当hash冲突严重的时候,单链法会造成原理链接过长,导致HashMap性能下降,因为链表需要逐个遍历性能很差。所以JDK1.8对hash冲突的算法进行了优化。当链表节点数达到8个的时候,会自动转换为红黑树,自平衡的一种二叉树,有很多特点,比如区分红和黑节点等,1314最佳答案源码具体大家可以看小灰算法图解。红黑树的遍历效率是O(logn)肯定比单链表的O(n)要好很多。
总结一句话就是,hash冲突使用单链表法+红黑树来解决的。
上面的图,核心脉络是四步,源码具体的就不粘出来了。当put一个之后,map的size达到扩容阈值,就会触发rehash。你可以看到如下具体思路:
情况1:如果数组位置只有一个值:使用新的容量进行rehash,即e.hash & (newCap - 1)
情况2:如果数组位置有链表,根据 e.hash & oldCap == 0进行判断,结果为0的使用原位置,否则使用index + oldCap位置,放入元素形成新链表,这里不会和情况1新的容量进行rehash与运算了,index + oldCap这样更省性能。
情况3:如果数组位置有红黑树,根据split方法,同样根据 e.hash & oldCap == 0进行树节点个数统计,如果个数小于6,将树的结果恢复为普通Node,否则使用index + oldCap,调整红黑树位置,这里不会和新的容量进行rehash与运算了,index + oldCap这样更省性能。
你有兴趣的话,可以分别画一下这三种情况的图。这里给大家一个图,假设都出发了以上三种情况结果如下所示:
上面源码核心脉络,3个if主要是校验了一堆,没做什么事情,之后赋值了扩容因子,不传递使用默认值0.,扩容阈值threshold通过tableSizeFor(initialCapacity);进行计算。注意这里只是计算了扩容阈值,没有初始化数组。代码如下:
竟然不是大小*扩容因子?
n |= n >>> 1这句话,是在干什么?n |= n >>> 1等价于n = n | n >>>1; 而|表示位运算中的或,n>>>1表示无符号右移1位。遇到这种情况,之前你应该学到了,如果碰见复杂逻辑和算法方法就是画图或者举例子。这里你就可以举个例子:假设现在指定的容量大小是,n=cap-1=,那么计算过程应该如下:
n是int类型,java中一般是4个字节,位。所以的二进制: 。
最后n+1=,方法返回,赋值给threshold=。再次注意这里只是计算了扩容阈值,没有初始化数组。
为什么这么做呢?一句话,佛山孕妇即食燕窝溯源码为了提高hash寻址和扩容计算的的效率。
因为无论扩容计算还是寻址计算,都是二进制的位运算,效率很快。另外之前你还记得取余(%)操作中如果除数是2的幂次方则等同于与其除数减一的与(&)操作。即 hash%size = hash & (size-1)。这个前提条件是除数是2的幂次方。
你可以再回顾下resize代码,看看指定了map容量,第一次put会发生什么。会将扩容阈值threshold,这样在第一次put的时候就会调用newCap = oldThr;使得创建一个容量为threshold的数组,之后从而会计算新的扩容阈值newThr为newCap*0.=*0.=。也就是说map到了个元素就会进行扩容。
除了今天知识,技能的成长,给大家带来一个金句甜点,结束我今天的分享:坚持的三个秘诀之一目标化。
坚持的秘诀除了上一节提到的视觉化,第二个秘诀就是目标化。顾名思义,就是需要给自己定立一个目标。这里要提到的是你的目标不要定的太高了。就比如你想要增加肌肉,给自己定了一个目标,每天5组,每次个俯卧撑,你看到自己胖的身形或者海报,很有刺激,结果开始前两天非常厉害,干劲十足,特别奥利给。但是第三天,你想到要个俯卧撑,你就不想起床,就算起来,可能也会把自己撅死过去......其实你的目标不要一下子定的太大,要从微习惯开始,比如我媳妇从来没有做过俯卧撑,就让她每天从1个开始,不能多,我就怕她收不住,做多了。一开始其实从习惯开始,先变成习惯,再开始慢慢加量。量太大养不成习惯,量小才能养成习惯。很容易做到才能养成,你想想是不是这个道理?
所以,坚持的第二个秘诀就是定一个目标,可以通过小量目标,养成微习惯。比如每天你可以读五分钟书或者5分钟成长记,不要多,我想超过你也会睡着了的.....
最后,大家可以在阅读完源码后,在茶余饭后的时候问问同事或同学,你也可以分享下,讲给他听听。
JDK源码分析Timer/TimerTask 源码分析
在Java中,Timer 类是实现定时任务的常见工具,配合TimerTask 实现定时、延迟或周期性执行。本文将深入剖析其源码结构和工作原理。 Timer 的核心机制涉及关键类,包括TimerThread、Timer、TimerQueue 和 TimerTask。一个Timer 实例对应一个TimerThread,负责执行任务;Timer拥有一个TimerThread和一个TimerQueue,而TimerQueue中存储了多个TimerTask。这样的关系可以总结为:1个 TimerThread -> 1个线程
1个 Timer -> 持有 TimerThread 和 TimerQueue
1个 TimerQueue -> 持有多个 TimerTask
源码分析时,首先创建Timer时,thread和queue会在声明时初始化为final类型,确保它们与Timer的生命周期绑定。接着,任务通过schedule方法进行调度,这个过程会根据TimerTask类型设置不同的period参数。 TimerTask 是一个实现了Runnable接口的抽象类,子类需实现run方法。TimerTask的类型决定了其执行周期。TimerThread的run方法包含一个死循环,类似Android的Handler机制。 TimerQueue作为队列,内部使用完全二叉树结构,add和fixUp方法用于维护最小执行时间的节点在队列前端。purge方法执行后,会调用fixDown方法进行调整。 总之,每个Timer实例由一个线程和一个二叉堆(通过TimerQueue实现)组成,用于管理定时任务的执行顺序。理解这些核心组件的交互,有助于深入理解Timer的工作机制。jdk源码剖析手册?
源码解析-偏向锁撤销流程解读
实现上面atomic-free(表示尽可能减少CAS这样的原子操作)偏向锁的难点就在于如何协调获取偏向锁和撤销偏向锁的过程。
偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“”)或轻量级锁(标志位为“”)的状态。
分析-XX:+PrintSafepointStatistics–XX:PrintSafepointStatisticsCount=1产生的日志信息基本上STW的原因都是RevokeBias或者BulkRevokeBias。这个是撤销偏向锁操作,虽然每次暂停的时间很短,但是特别频繁出现也会很耗时。
学习JAVA的同学都是怎么读源码的?1、面对未知的、茫茫多的源码,我们往往没有足够的时间、经历和耐心去通读所有源码,我们只需要去读我们关注的部分即可(有人可能会说我都不关心,这?)。
2、首先要理清楚代码结构和业务结构(应该有些文档或者大的流程图),这是阅读具体代码的前提。阅读Javaweb项目的代码:你需要找到View层的代码:前端页面、、资源文件都在其中。
3、了解和使用工具很多工具都可以有助于源代码阅读,并且对可视化代码有很大的帮助。在使用过程中,成都IT培训认为IntelliJIdea工具能够导航源代码,允许使用单词的一部分,甚至单词的缩写进行搜索。您还应该学习键盘的快捷键。
4、阅读分析源代码,一些有效的方法是:阅读源代码的说明文档和API文档。如果源代码有用法示例或向导,先阅读这个。了解整个项目的模块结构,可以按模块进行阅读。
如何在Eclipse中查看JDK类库的源代码在Eclipse中查看Java类库的源代码,可以通过Ctrl+鼠标左键的方式来完成;在下载JDK并安装的时候,会有一个src.zip文件,此文件就是Java类库的源码。但是有时候,会出现源码未找到的问题,此时可以通过对其设置来解决。
第一次使用eclipse查看jar包里的源代码时,没有导入jdk的项目源码,所以无法查看源码。查看源代码的方法:打开eclipse,点击window,之后选择Preferences选项。
首先打开eclipse,建立项目:Test,将struts2相关jar包导入到其中。在PackageExplorer标签栏下操作。这里查阅struts2中,struts2-core-1jar下的源代码。
java在eclipse中有两种被运行的方式(jre的方式和jdk的方式)。你看不到源码就是因为你是通过jre的方式运行的。通过eclipse进入菜单Window-Preferences-Java-InstalledJREs。
JDK查看源码可首先从ArrayList、Vector、LinkedList源码比较开始看起。
如何高效阅读源代码?
1、首先要理清楚代码结构和业务结构(应该有些文档或者大的流程图),这是阅读具体代码的前提。阅读Javaweb项目的代码:你需要找到View层的代码:前端页面、、资源文件都在其中。
2、当然有。终于到重点了,隆重推出由官方支持的方式:只需要在代码仓库页面按一下.就可以直接使用VSCode打开,而且支持编辑。也可以通过地址访问,把.com改成.dev,比如:太方便了,太优雅了。
3、查看拦截器,监听器代码,知道拦截了什么请求,这个类完成了怎样的工作。
4、用命令(apktooldxxx.apkxxx_xml)反编译xxx.apk包从xxx_xml文件夹得到xml文件第二步得到的程序源代码和第三步得到的xml文件组合下,即可得到完整的apk源码。
5、先找出功能体系,再分离出功能模块。知道能干什么,再知道怎么干。
6、面对未知的、茫茫多的源码,我们往往没有足够的时间、经历和耐心去通读所有源码,我们只需要去读我们关注的部分即可(有人可能会说我都不关心,这?)。
如何在Eclipse中查看JDK类库的源代码?在Eclipse中查看Java类库的源代码,可以通过Ctrl+鼠标左键的方式来完成;在下载JDK并安装的时候,会有一个src.zip文件,此文件就是Java类库的源码。但是有时候,会出现源码未找到的问题,此时可以通过对其设置来解决。
首先打开eclipse,建立项目:Test,将struts2相关jar包导入到其中。在PackageExplorer标签栏下操作。这里查阅struts2中,struts2-core-1jar下的源代码。
第一次使用eclipse查看jar包里的源代码时,没有导入jdk的项目源码,所以无法查看源码。查看源代码的方法:打开eclipse,点击window,之后选择Preferences选项。
如何查看javaJDK中底层源码1、查看源代码的方法:打开eclipse,点击window,之后选择Preferences选项。找到Java选项,点开,选择InstalledJRES,此时右边是列表窗格,列出了系统中的JRE环境,选择你的JRE,然后点边上的Edit。
2、在你的JDK文件夹下不是有个src.rar包吗?解压出来,把你想看的某个类的源码拖到eclipse中就可以查看了。
3、你安装JDK的目录下,有个src.zip文件,这个就是JDK源代码的java文件。你可以解压来查看,但,最好是关联到IDE如eclipse中(不需解压),然后CTRL+点击就可以查看到源代码了。
4、在安装jdk文件路径下的src.zip可以查看。。在eclipse里面也可以,只要按着ctrl键将鼠标放到想看的类上在点击左键就能进入到定义那个类的里面去了。。
5、在Eclipse中查看Java类库的源代码,可以通过Ctrl+鼠标左键的方式来完成;在下载JDK并安装的时候,会有一个src.zip文件,此文件就是Java类库的源码。但是有时候,会出现源码未找到的问题,此时可以通过对其设置来解决。
6、其实最好的方式是你通过对某一块代码的阅读,进行绘制流程图,VISIO画起流程图来很方便,找出数据流,再加上自己的阅读的注释。在你阅读学习的过程中,会发现其他coder的非常巧妙的做法,这是你应该庆幸,因为你在进步。
面试官:从源码分析一下TreeSet(基于jdk1.8)
面试官可能会询问关于TreeSet(基于JDK1.8)的源码分析,实际上,TreeSet与HashSet类似,都利用了TreeMap底层的红黑树结构。主要特性包括:
1. TreeSet是基于TreeMap的NavigableSet实现,元素存储在TreeMap的key中,value为一个常量对象。
2. 不是直接基于TreeMap,而是NavigableMap,因为TreeMap本身就实现了这个接口。
3. 对于内存节省的疑问,TreeSet在add方法中使用PRESENT对象避免了将null作为value可能导致的逻辑冲突。添加重复元素时,PRESENT确保了插入状态的区分。
4. 构造函数提供了多样化的选项,允许自定义比较器和排序器,基本继承自HashSet的特性。
5. 除了基本的增删操作,TreeSet还提供了如返回子集、头部尾部元素、区间查找等方法。
总结来说,TreeSet在排序上优于HashSet,但插入和查找操作由于树的结构会更复杂,不适用于对速度有极高要求的场景。如果不需要排序,HashSet是更好的选择。
感谢您的关注,关于TreeSet的源码解析就介绍到这里。
惊艳!阿里内部JDK源码剖析知识手册,由浅入深堪称完美
在当前互联网寒冬中,提升核心竞争力显得尤为关键。对于Java开发者来说,深入理解JDK源码是提升自身实力的重要途径。近期,一位阿里架构师花费数月精心整理的《JDK源码剖析知识手册》值得关注,它以8个章节从浅入深解析JDK,涵盖了多线程基础、Atomic类、Lock与Condition、同步工具类、并发容器、线程池与Future、ForkJoinPool以及CompletableFuture等核心内容。
多线程章节强调内存优化和效率提升,Atomic类则带你逐步揭开Concurrent包的层级结构。深入理解Lock与Condition,以及并发工具类背后的实现原理,将有助于编写更优雅、严谨的代码。并发容器的讲解,让你全面掌握包内各类工具的使用。线程池与Future的分析,揭示了高效任务管理的机制,ForkJoinPool和CompletableFuture的探讨则展示了并发编程的深度技巧。
这本手册并非泛泛而谈,而是旨在帮助开发者实现质的飞跃。记住,不断学习和提升是成长的关键。现在,只需点击这里即可获取这份宝贵的资源,开始你的JDK源码探索之旅,为自己增添竞争优势。点击这里,踏上成为更好开发者之路。