(1)Apache log4j-1.2.17源码学习笔记 | http://blog.csdn.net/zilong_zilong/article/details/78715500 |
(2)Apache log4j-1.2.17问答式学习笔记 | http://blog.csdn.net/zilong_zilong/article/details/78916626 |
(3)JDK Logging源码学习笔记 | http://aperise.iteye.com/blog/2411850 |
1.JDK Logging介绍
断点调试和记录日志,是程序员排查问题的2个有效手段,断点调试需要对全盘代码熟门熟路,费时费力,如果代码不开源那么此种方法就不能使用,相对于断点调试,记录日志提供了另外一种更有效的排错方法,预先植入了有效的日志信息,后期只需通过配置文件即可管理日志,借助工具扫描日志文件内容可以有效的监测当前运行的系统是否运行正常。记录日志作为一个通用的重要的模块,所以开源组织分别推出了自己的日志框架,比如Apache Log4j,Apache Log4j 2、Apache Commons Logging、Slf4j、Logback和JDK Logging,今天我要分享的是JDK Logging日志框架。
JDK Logging的组成及其关系如下图所示:
在上图中,Application代表我们的Java程序,Logger代表用户日志输出logger,每个用户logger上关联着handler,最终通过用户logger的handler将日志输出到外界Outside World(内存、操作系统文件、socket),同时每个handler上也可以配置filter进行过滤filter,特殊的对于MemoryHandler可以将应用程序的日志事件向下一个handler相传,所以MemoryHandler的处理流程如下:
其它详细说明详见JDK官方说明文档,JDK Logging的官方介绍文档链接如下:
在项目中使用JDK Logging举例如下:
package com.wombat; import java.util.logging.*; public class Nose { private static Logger logger = Logger.getLogger("com.wombat.nose"); private static FileHandler fh = new FileHandler("mylog.txt"); public static void main(String argv[]) { // Send logger output to our FileHandler. logger.addHandler(fh); // Request that every detail gets logged. logger.setLevel(Level.ALL); // Log a simple INFO message. logger.info("doing stuff"); try { Wombat.sneeze(); } catch (Exception ex) { logger.log(Level.WARNING, "trouble sneezing", ex); } logger.fine("done"); } }
接下来的环节就从这个代码入手,从源码的角度来逐步分析和学习JDK Logging。
2.LogManager的static代码块源码分析(RootLogger创建过程)
之前在Apache log4j-1.2.17的源码分析中我们看到log4j-1.2.17也是通过类LogManager的静态代码块来进行的,这里对于JDK Logging也是一样,下面将LogManager的static代码块注释如下:
上面代码中有2个JAVA的基础知识这里也顺便提一下:
- AccessController.doPrivileged:属于特权操作,意思是不管此方法由哪个用户发起,都无需对此操作涉及的资源(文件读写特权等等)进行检查
- System.getProperty:用户获取JVM系统变量的值或者由JDK的参数-Dproterty=value设置的属性的值
这两个知识点在众多的开源框架中都被广泛使用,好了回到正题,上面代码中默认获取的java.util.logging.manager为空,所以cname=null,所以默认会通过构造方法new LogManager()创建LogManager对象,之后就是一系列上下文设置操作,但直至代码结束,我们注意到始终未看到在哪里读取过日志配置文件logging.properties(为什么是logging.properties这个接下来会讲到),实际上对于logging.properties的加载是采用延迟加载策略,只会在第一次调用LogManager.getLogManager()才开始解析logging.properties日志配置文件,这里注意和Apache log4j-1.2.17加载log4j.properties类比来增强学习,Apache log4j-1.2.17是直接在静态代码块中就加载解析了log4j.properties文件的,并未采用延迟加载。
3.JDK Logging读取日志配置文件logging.properties代码分析
在此文的开头对于JDK Logging的举例使用中,我们看到对于JDK Logging的使用的第一行代码为:
那么我们就从此作为入口,先看下JDK Logging中Logger.getLogger代码:
上面代码中,当Logger.getLogger被调用时,接着会调用代码Logger.demandLogger,而Logger.demandLogger中正如章节2中提到的,调用了全局静态类LogManager的方法LogManager.getLogManager(),那么接下来就详细看下LogManager.getLogManager()相关代码:
从上面的代码中,我们终于找到了JDK Logging读取日志配置文件的蛛丝马迹,继续跟踪代码Logger.readConfiguration:
上面的代码中我们看到,JDK Logging加载日志配置文件是有先后顺序的:
- 首先通过java.util.logging.config.class配置的类来初始化JDK Logging;
- 如果java.util.logging.config.class找不到,接着通过java.util.logging.config.file配置的文件来初始化JDK Logging;
- 最后如果java.util.logging.config.file找不到,就通过操作系统安装的JDK的jre/lib下的logging.properties来初始化JDK Logging;
- 最后如果连JDK都为安装,直接抛出错误Error("Can't find java.home ??")
4.JDK Logging的用户logger创建过程
在上面我们已经看到了RootLogger的创建过程,RootLogger是所有用户logger的父亲logger,这里继续查看我们的用户logger如何创建的,一般创建用户logger的代码如下:
Logger logger = Logger.getLogger("com.wombat.nose");
那么我们就以此为入口查看logger是如何创建的,类Logger里getLogger方法代码如下:
在上面的代码中,我们看到,最终都会执行如下这行代码:
继续分析这段代码,如下:
我们看到如果用户logger不存在就创建,创建logger的代码如下:
如果用户logger存在就直接从UserContext上下文获取,那么我们继续关注下用户logger创建完毕后,代码addLogger(newLogger)干了点啥,代码如下:
上面的代码告诉我们创建用户logger后,添加logger到usercontext,配置logger的handler以及其level属性,很关键的信息是用户logger只会创建一次。
5.日志输出代码logger.info(String msg)代码分析
上面我们已经看到创建了logger对象,接着就是在需要输出日志的地方调用logger.info(String msg)输出日志,那么我们看下这块的代码逻辑,代码分析如下:
6.JDK Logging的日志级别
我们习惯了Apache log4j-1.2.17,那么这里就讲JDK Logging的日志级别与Apache log4j-1.2.17进行对别如下:
7.JDK Logging的Handlers及其属性配置
JDK Logging的Handlers的类继承关系图如下:
这里在之前的代码中,我们看到对于handler,每次创建,只设置了handler的level属性,那么对于其他属性,是哪里设置的呢?每个handler的具体实现类里面都有个privatevoid configure() 方法,这个方法值得我们特别关注。
7.1 MemoryHandler
MemoryHandler会将LogRecords存储到内存当中,存储到内存的同时清理掉之前的LogRecords,MemoryHandler的属性设置方法private void configure()在构造方法public MemoryHandler()中被调用,代码如下:
从上面的代码我们看到MemoryHandler的配置属性列表如下:
- java.util.logging.MemoryHandler.level 设置handler的level(defaults to Level.ALL).
- java.util.logging.MemoryHandler.filter 设置handler的filter (defaults to no Filter).
- java.util.logging.MemoryHandler.size 设置handler的内存缓冲区数组的大小 (defaults to 1000).
- java.util.logging.MemoryHandler.push 设置push level (defaults to level.SEVERE).
- java.util.logging.MemoryHandler.target 设置目标handler给这个MemoryHandler (no default).
7.2 StreamHandler
流处理handler的基础类,其实现类如下:
StreamHandler的属性设置方法private void configure()在构造方法public StreamHandler()中被调用,代码如下:
从上面可以看到StreamHandler的配置属性如下:
- java.util.logging.StreamHandler.level 设置handler的level (defaults to Level.INFO).
- java.util.logging.StreamHandler.filter 设置handler的filter的类 (defaults to no Filter).
- java.util.logging.StreamHandler.formatter 设置handler的formatter类 (defaults to java.util.logging.SimpleFormatter).
- java.util.logging.StreamHandler.encoding 设置编码格式 (defaults to the default platform encoding).
7.3 ConsoleHandler
ConsoleHandler默认将LogRecords输出到System.err,ConsoleHandler的属性设置方法private void configure()在构造方法public ConsoleHandler()中被调用,代码如下:
从上面可知ConsoleHandler的配置属性如下:
- java.util.logging.ConsoleHandler.level 设置handler的level (defaults to Level.INFO).
- java.util.logging.ConsoleHandler.filter 设置handler使用的filter类 (defaults to no Filter).
- java.util.logging.ConsoleHandler.formatter 设置formatter类 (defaults to java.util.logging.SimpleFormatter).
- java.util.logging.ConsoleHandler.encoding 设置编码格式(defaults to the default platform encoding).
7.4 FileHandler
FileHandler负责将LogRecords输出到操作系统的文件,FileHandler的属性设置方法private void configure()在构造方法public FileHandler()中被调用,代码如下:
从上面可以看出FileHandler的配置属性如下:
- java.util.logging.FileHandler.level 设置handler的level (defaults to Level.ALL).
- java.util.logging.FileHandler.filter 设置handler使用的filter类 (defaults to no Filter).
- java.util.logging.FileHandler.formatter 设置formatter类 (defaults to java.util.logging.XMLFormatter)
- java.util.logging.FileHandler.encoding 设置编码格式 (defaults to the default platform encoding).
- java.util.logging.FileHandler.limit 设置一个日志文件最大文件大小,单位bytes 。设置0表示没有任何限制 (Defaults to no limit).
- java.util.logging.FileHandler.count 设置最多保留几个日志文件 (defaults to 1).
- java.util.logging.FileHandler.pattern 设置产生日志文件的名字的规则(Defaults to "%h/java%u.log").
- java.util.logging.FileHandler.append 设置是否在已经存在的日志文件里最后追加日志,默认false (defaults to false).
7.5 SocketHandler
SocketHandler负责将LogRecords输出到socket接口,SocketHandler的属性设置方法private void configure()在构造方法public SocketHandler()中被调用,代码如下:
从上面可以看到SocketHandler的配置属性如下:
- java.util.logging.SocketHandler.level 设置handler的level(defaults to Level.ALL).
- java.util.logging.SocketHandler.filter 设置handler使用的filter类 (defaults to no Filter).
- java.util.logging.SocketHandler.formatter 设置编formatter类 (defaults to java.util.logging.XMLFormatter).
- java.util.logging.SocketHandler.encoding 设置编码格式 (defaults to the default platform encoding).
- java.util.logging.SocketHandler.host 设置socket接口的IP地址 (no default).
- java.util.logging.SocketHandler.port 设置socket接口的port (no default).
8.JDK Logging的Formatters
Formatters的继承关系图如下:
8.1 SimpleFormatter
SimpleFormatter按照Java使用Java的String.format方法按照格式"%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"来格式化日志进行输出,代码如下:
8.2 XMLFormatter
XMLFormatter负责将LogRecord格式化为如下的XML格式:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE log SYSTEM "logger.dtd"> <log> <record> <date>2000-08-23 19:21:05</date> <millis>967083665789</millis> <sequence>1256</sequence> <logger>kgh.test.fred</logger> <level>INFO</level> <class>kgh.test.XMLTest</class> <method>writeLog</method> <thread>10</thread> <message>Hello world!</message> </record> </log>
XMLFormatter的代码部分注释如下:
public class XMLFormatter extends Formatter { private LogManager manager = LogManager.getLogManager(); // 将小于10的数字前补充0 private void a2(StringBuffer sb, int x) { if (x < 10) { sb.append('0'); } sb.append(x); } // 时间按照ISO 8601标准输出 private void appendISO8601(StringBuffer sb, long millis) { Date date = new Date(millis); sb.append(date.getYear() + 1900); sb.append('-'); a2(sb, date.getMonth() + 1); sb.append('-'); a2(sb, date.getDate()); sb.append('T'); a2(sb, date.getHours()); sb.append(':'); a2(sb, date.getMinutes()); sb.append(':'); a2(sb, date.getSeconds()); } // 将于XML字符<>冲突的字符进行转换,如果字符串为null,则输出<null> private void escape(StringBuffer sb, String text) { if (text == null) { text = "<null>"; } for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); if (ch == '<') { sb.append("<"); } else if (ch == '>') { sb.append(">"); } else if (ch == '&') { sb.append("&"); } else { sb.append(ch); } } } /** * 转换为如下XML格式的日志输出 *<?xml version="1.0" encoding="UTF-8" standalone="no"?> *<!DOCTYPE log SYSTEM "logger.dtd"> *<log> *<record> * <date>2000-08-23 19:21:05</date> * <millis>967083665789</millis> * <sequence>1256</sequence> * <logger>kgh.test.fred</logger> * <level>INFO</level> * <class>kgh.test.XMLTest</class> * <method>writeLog</method> * <thread>10</thread> * <message>Hello world!</message> *</record> *</log> * * @param record 被格式化的日志消息 * @return 返回被格式化后的XML */ public String format(LogRecord record) { StringBuffer sb = new StringBuffer(500); sb.append("<record>\n"); sb.append(" <date>"); appendISO8601(sb, record.getMillis()); sb.append("</date>\n"); sb.append(" <millis>"); sb.append(record.getMillis()); sb.append("</millis>\n"); sb.append(" <sequence>"); sb.append(record.getSequenceNumber()); sb.append("</sequence>\n"); String name = record.getLoggerName(); if (name != null) { sb.append(" <logger>"); escape(sb, name); sb.append("</logger>\n"); } sb.append(" <level>"); escape(sb, record.getLevel().toString()); sb.append("</level>\n"); if (record.getSourceClassName() != null) { sb.append(" <class>"); escape(sb, record.getSourceClassName()); sb.append("</class>\n"); } if (record.getSourceMethodName() != null) { sb.append(" <method>"); escape(sb, record.getSourceMethodName()); sb.append("</method>\n"); } sb.append(" <thread>"); sb.append(record.getThreadID()); sb.append("</thread>\n"); if (record.getMessage() != null) { // Format the message string and its accompanying parameters. String message = formatMessage(record); sb.append(" <message>"); escape(sb, message); sb.append("</message>"); sb.append("\n"); } // 如果消息需要国际化来实现本地化,这里进行处理 ResourceBundle bundle = record.getResourceBundle(); try { if (bundle != null && bundle.getString(record.getMessage()) != null) { sb.append(" <key>"); escape(sb, record.getMessage()); sb.append("</key>\n"); sb.append(" <catalog>"); escape(sb, record.getResourceBundleName()); sb.append("</catalog>\n"); } } catch (Exception ex) { // The message is not in the catalog. Drop through. } Object parameters[] = record.getParameters(); // Check to see if the parameter was not a messagetext format // or was not null or empty if ( parameters != null && parameters.length != 0 && record.getMessage().indexOf("{") == -1 ) { for (int i = 0; i < parameters.length; i++) { sb.append(" <param>"); try { escape(sb, parameters[i].toString()); } catch (Exception ex) { sb.append("???"); } sb.append("</param>\n"); } } if (record.getThrown() != null) { // Report on the state of the throwable. Throwable th = record.getThrown(); sb.append(" <exception>\n"); sb.append(" <message>"); escape(sb, th.toString()); sb.append("</message>\n"); StackTraceElement trace[] = th.getStackTrace(); for (int i = 0; i < trace.length; i++) { StackTraceElement frame = trace[i]; sb.append(" <frame>\n"); sb.append(" <class>"); escape(sb, frame.getClassName()); sb.append("</class>\n"); sb.append(" <method>"); escape(sb, frame.getMethodName()); sb.append("</method>\n"); // Check for a line number. if (frame.getLineNumber() >= 0) { sb.append(" <line>"); sb.append(frame.getLineNumber()); sb.append("</line>\n"); } sb.append(" </frame>\n"); } sb.append(" </exception>\n"); } sb.append("</record>\n"); return sb.toString(); } /** * 返回XML的消息头,也即返回如下格式 *<?xml version="1.0" encoding="UTF-8" standalone="no"?> *<!DOCTYPE log SYSTEM "logger.dtd"> *<log> * * @param h The target handler (can be null) * @return a valid XML string */ public String getHead(Handler h) { StringBuffer sb = new StringBuffer(); String encoding; sb.append("<?xml version=\"1.0\""); if (h != null) { encoding = h.getEncoding(); } else { encoding = null; } if (encoding == null) { // Figure out the default encoding. encoding = java.nio.charset.Charset.defaultCharset().name(); } // Try to map the encoding name to a canonical name. try { Charset cs = Charset.forName(encoding); encoding = cs.name(); } catch (Exception ex) { // We hit problems finding a canonical name. // Just use the raw encoding name. } sb.append(" encoding=\""); sb.append(encoding); sb.append("\""); sb.append(" standalone=\"no\"?>\n"); sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">\n"); sb.append("<log>\n"); return sb.toString(); } /** * 返回XML的尾部,也即返回</log> * * @param h The target handler (can be null) * @return a valid XML string */ public String getTail(Handler h) { return "</log>\n"; } }
其中logger.dtd的定义如下:
<!-- DTD used by the java.util.logging.XMLFormatter --> <!-- This provides an XML formatted log message. --> <!-- The document type is "log" which consists of a sequence of record elements --> <!ELEMENT log (record*)> <!-- Each logging call is described by a record element. --> <!ELEMENT record (date, millis, sequence, logger?, level, class?, method?, thread?, message, key?, catalog?, param*, exception?)> <!-- Date and time when LogRecord was created in ISO 8601 format --> <!ELEMENT date (#PCDATA)> <!-- Time when LogRecord was created in milliseconds since midnight January 1st, 1970, UTC. --> <!ELEMENT millis (#PCDATA)> <!-- Unique sequence number within source VM. --> <!ELEMENT sequence (#PCDATA)> <!-- Name of source Logger object. --> <!ELEMENT logger (#PCDATA)> <!-- Logging level, may be either one of the constant names from java.util.logging.Level (such as "SEVERE" or "WARNING") or an integer value such as "20". --> <!ELEMENT level (#PCDATA)> <!-- Fully qualified name of class that issued logging call, e.g. "javax.marsupial.Wombat". --> <!ELEMENT class (#PCDATA)> <!-- Name of method that issued logging call. It may be either an unqualified method name such as "fred" or it may include argument type information in parenthesis, for example "fred(int,String)". --> <!ELEMENT method (#PCDATA)> <!-- Integer thread ID. --> <!ELEMENT thread (#PCDATA)> <!-- The message element contains the text string of a log message. --> <!ELEMENT message (#PCDATA)> <!-- If the message string was localized, the key element provides the original localization message key. --> <!ELEMENT key (#PCDATA)> <!-- If the message string was localized, the catalog element provides the logger's localization resource bundle name. --> <!ELEMENT catalog (#PCDATA)> <!-- If the message string was localized, each of the param elements provides the String value (obtained using Object.toString()) of the corresponding LogRecord parameter. --> <!ELEMENT param (#PCDATA)> <!-- An exception consists of an optional message string followed by a series of StackFrames. Exception elements are used for Java exceptions and other java Throwables. --> <!ELEMENT exception (message?, frame+)> <!-- A frame describes one line in a Throwable backtrace. --> <!ELEMENT frame (class, method, line?)> <!-- an integer line number within a class's source file. --> <!ELEMENT line (#PCDATA)>
9.logging.properties
logging.properties是JDK Logging的默认配置文件,其默认位置在JDK的安装根路径下的jre/lib下,最近我换成MAC PRO了,所以这里我截图我的logging.properties的配置文件位置如下:
默认的logging.properties配置文件的内容如下:
############################################################ # Default Logging Configuration File # # You can use a different file by specifying a filename # with the java.util.logging.config.file system property. # For example java -Djava.util.logging.config.file=myfile ############################################################ ############################################################ # Global properties ############################################################ # "handlers" specifies a comma separated list of log Handler # classes. These handlers will be installed during VM startup. # Note that these classes must be on the system classpath. # By default we only configure a ConsoleHandler, which will only # show messages at the INFO and above levels. handlers= java.util.logging.ConsoleHandler # To also add the FileHandler, use the following line instead. #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # Default global logging level. # This specifies which kinds of events are logged across # all loggers. For any given facility this global level # can be overriden by a facility specific level # Note that the ConsoleHandler also has a separate level # setting to limit messages printed to the console. .level= INFO ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ # default file output is in user's home directory. java.util.logging.FileHandler.pattern = %h/java%u.log java.util.logging.FileHandler.limit = 50000 java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter # Limit the message that are printed on the console to INFO and above. java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter # Example to customize the SimpleFormatter output format # to print one-line log message like this: # <level>: <log message> [<date/time>] # # java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n ############################################################ # Facility specific properties. # Provides extra control for each logger. ############################################################ # For example, set the com.xyz.foo logger to only log SEVERE # messages: com.xyz.foo.level = SEVERE
相关推荐
JDK源码阅读笔记
JDK源码阅读笔记
《Java JDK7学习笔记》是作者多年来教学实践经验的总结,汇集了教学过程中学生在学习java时遇到的概念、操作、应用或认证考试等问题及解决方案。《Java JDK7学习笔记》针对java se 7新功能全面改版,无论是章节架构...
这个PDF学习笔记是开发者深入理解JDK 7特性和功能的重要参考资料。以下是对Java JDK 7的一些核心知识点的详细阐述: 1. **泛型改进**:在JDK 7中,泛型的使用更加灵活,引入了类型推断(Type Inference)特性,通过...
《java jdk 7学习笔记》针对java se 7新功能全面改版,无论是章节架构或范例程序代码,都做了重新编写与全面翻新。并详细介绍了jvm、jre、java se api、jdk与ide之间的对照关系。必要时从java se api的源代码分析,...
良葛格java jdk 5.0学习笔记,良葛格java jdk 5.0学习笔记.zip,良葛格java jdk 5.0学习笔记.zip,良葛格java jdk 5.0学习笔记.zip,良葛格java jdk 5.0学习笔记.zip,良葛格java jdk 5.0学习笔记.zip。
Java JDK 7学习笔记 此学习笔记适合初学者完成学习总结,加深理解 Java JDK 7学习笔记 此学习笔记适合初学者完成学习总结,加深理解
《林信良 jdk6 java学习笔记》是一本专注于Java编程语言的学习资料,特别针对Java Development Kit (JDK) 6版本进行了深入讲解。作者林信良是一位在Java领域有深厚造诣的专业人士,他的这部作品旨在帮助读者掌握Java...
这份"Java jdk 8 学习笔记 源码.zip"压缩包显然是为了帮助学习者深入理解这一版本的Java编程语言。下面将详细探讨Java JDK 8中的关键知识点。 1. **Lambda表达式**: Lambda表达式是JDK 8最重要的特性之一,它简化...
jdk6 源码jdk6 源码jdk6 源码jdk6 源码jdk6 源码jdk6 源码
Java、JDK6、良葛、林信良、Java学习笔记 我没看到实体书,不知是否相同, 近些日学习一下ruby方面的,顺便看看java方面的, 这个是Java JDK6的学习笔记电子版, 压缩包里包含两个文件, 一个是chm格式(这个还行吧)...
本篇将围绕“Java+JDK6学习笔记”展开,探讨在JDK6环境下Java编程的核心知识点。 1. **JDK6概述**:JDK6是Oracle公司于2006年发布的Java平台标准版(Java SE)的一个重要版本,它的全称是Java SE 6,带来了许多新...
《深入解析JDK 8u60源码》 JDK(Java Development Kit)是Java编程语言的核心组件,包含了编译器、运行时环境、工具集等,是开发者理解和使用Java技术的重要基石。JDK 8u60是Oracle公司发布的一个版本,包含了对...
良葛格————JavaJDK5.0学良葛格————JavaJDK5.0学习笔记PDF.rar习笔记PDF.rar良葛格良葛格————JavaJDK5.0学习笔记PDF.rar————JavaJDK5.0学习笔记PDF.rar良葛格————JavaJDK5.0学习笔记PDF.rar良...
7. **JShell(REPL)**:JShell是JDK9引入的命令行工具,JDK11继续优化,可以用于快速测试和学习Java语法,是理解JDK源码的好帮手。 8. **反射API的优化**:JDK11对反射API进行了改进,如`MethodHandles.Lookup`类...
Java JDK6学习笔记是针对Java编程语言初学者和进阶者的一份宝贵资源,它涵盖了Java的基础语法,并通过经典且易于理解的实例进行讲解。在本文中,我们将深入探讨Java JDK6中的关键概念和特性,以帮助你更好地理解和...
Java+JDK+6学习笔记,Java+JDK+6学习笔记.rar
例如,`java.util.concurrent`包在并发编程中提供了高级工具,如线程池、锁和并发集合,通过源码可以学习到它们的内部工作机制。 3. **异常处理**:源码中展示了如何使用try-catch-finally结构来捕获和处理异常,...
下载后直接去本机jdk目录里替换jdk中的src.zip 再打开idea就能看到中文版的源码注释 示例 https://blog.csdn.net/a7459/article/details/106495622