学习Java日志框架之——搞懂JUL(java.util.logging)
文章目录
- 系列文章目录
- 一、JUL简介
- 二、JUL组件介绍
- 三、代码实例
- 1、入门案例
- 2、日志级别
- (1)默认日志级别源码分析
- 3、自定义日志级别
- 4、将日志输出到文件中
- 5、Logger的父子关系
- (1)父子关系源码分析
- 6、使用配置文件
- (1)默认配置文件位置
- (2)默认配置文件(去掉注释)
- (3)配置文件解析
- (4)自定义读取配置文件
- (5)自定义日志配置文件
- 四、总结
系列文章目录
学习Java日志框架之——搞懂JUL(java.util.logging)
学习Java日志框架之——搞懂log4j
学习Java日志框架之——搞懂日志门面(JCL+SLF4J)
学习日志框架之——搞懂logback
学习日志框架之——log4j2入门
log4j2扩展——打印自定义日志输出格式,将日志输出为json或自定义
一、JUL简介
JUL全称 Java Util Logging,核心类在java.util.logging包下,它是java原生的日志框架,使用时不需要另外引用第三方的类库,相对其他的框架使用方便,学习简单,主要是使用在小型应用中。
二、JUL组件介绍
Logger:被称为记录器,应用程序通过获取Logger对象,调用其API来发布日志信息。Logger通常被认为是访问日志系统的入口程序。
Handler:处理器,每个Logger都会关联一个或者是一组Handler,Logger会将日志交给关联的Handler去做处理,由Handler负责将日志做记录。Handler具体实现了日志的输出位置,比如可以输出到控制台或者是文件中等等。
Filter:过滤器,根据需要定制哪些信息会被记录,哪些信息会被略过。
Formatter:格式化组件,它负责对日志中的数据和信息进行转换和格式化,所以它决定了我们输出日志最终的形式。
Level:日志的输出级别,每条日志消息都有一个关联的级别。我们根据输出级别的设置,用来展现最终所呈现的日志信息。根据不同的需求,去设置不同的级别。
三、代码实例
本文使用的类,都是java.util.logging包下的类。
1、入门案例
// 入门案例
public static void test01() {// Logger创建方式,参数为当前类全路径字符串com.demo.logger.jul.JULTestLogger logger = Logger.getLogger(JULTest.class.getCanonicalName());/*第一种方式:直接调用日志级别相关的方法,方法中传递日志输出信息假设现在我们要输出info级别的日志信息*/logger.info("输出info信息");/*输出内容:三月 20, 2023 9:09:38 下午 com.demo.logger.jul.JULTest test01信息: 输出info信息*//*第二种方式:调用通用的log方法,然后在里面通过level类型来定义日志的级别参数,以及搭配日志输出信息的参数*/logger.log(Level.WARNING, "输出warning信息");/*输出内容:三月 20, 2023 9:14:09 下午 com.demo.logger.jul.JULTest test01警告: 输出warning信息*/// 动态输出数据,生产日志,使用占位符的方式进行操作String name = "zhangsan";int age = 23;// logger.log(Level.INFO, "姓名:" + name + "年龄:" + age);logger.log(Level.INFO, "姓名:{0}年龄:{1}", new Object[]{name, age});/*{0}和 {1}分别代表第一个占位符和第二个占位符,同时传递一个数组,代表参数的集合输出内容:三月 20, 2023 9:18:16 下午 com.demo.logger.jul.JULTest test01信息: 姓名:zhangsan年龄:23*/
}
2、日志级别
// 日志级别
public static void test02() {/*日志的级别,总共七级Level.SEVERE:(最高级)错误Level.WARNING:警告Level.INFO:(默认级别)消息Level.CONFIG:配置级别Level.FINE:详细信息(少)Level.FINER:详细信息(中)Level.FINEST:(最低级)详细信息(多)两个特殊的级别:Level.OFF;可用来关闭日志记录Level.ALL:启用所有日志记录对于日志的级别,我们重点关注的是new对象时第二个参数,是一个数值:public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle);public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle);public static final Level WARNING = new Level("WARNING", 900, defaultBundle);public static final Level INFO = new Level("INFO", 800, defaultBundle);public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle);public static final Level FINE = new Level("FINE", 500, defaultBundle);public static final Level FINER = new Level("FINER", 400, defaultBundle);public static final Level FINEST = new Level("FINEST", 300, defaultBundle);public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle);这个数值的意义在于:如果我们设置的日志级别是800,那么最终展现的日志信息,比如是数值大于800的所有日志信息。*/Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());logger.severe("severe信息");logger.warning("warning信息");logger.info("info信息");logger.config("config信息");logger.fine("fine信息");logger.finer("finer信息");logger.finest("finest信息");/*输出内容:我们看到,默认是输出info及比info信息级别高的信息三月 20, 2023 9:47:27 下午 com.demo.logger.jul.JULTest test02严重: severe信息三月 20, 2023 9:47:27 下午 com.demo.logger.jul.JULTest test02警告: warning信息三月 20, 2023 9:47:27 下午 com.demo.logger.jul.JULTest test02信息: info信息*/
}
(1)默认日志级别源码分析
我们进入Logger的getLogger方法:
// java.util.logging.Logger#getLogger(java.lang.String)
@CallerSensitive
public static Logger getLogger(String name) {// This method is intentionally not a wrapper around a call// to getLogger(name, resourceBundleName). If it were then// this sequence://// getLogger("Foo", "resourceBundleForFoo");// getLogger("Foo");//// would throw an IllegalArgumentException in the second call// because the wrapper would result in an attempt to replace// the existing "resourceBundleForFoo" with null.return demandLogger(name, null, Reflection.getCallerClass());
}// java.util.logging.Logger#demandLogger
private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {LogManager manager = LogManager.getLogManager(); // 获取LogManagerSecurityManager sm = System.getSecurityManager();if (sm != null && !SystemLoggerHelper.disableCallerCheck) {if (caller.getClassLoader() == null) {return manager.demandSystemLogger(name, resourceBundleName);}}return manager.demandLogger(name, resourceBundleName, caller);// ends up calling new Logger(name, resourceBundleName, caller)// iff the logger doesn't exist already
}
在初始化LogManager时,初始化了默认的日志级别为Level.INFO:
// java.util.logging.LogManager#ensureLogManagerInitialized
final void ensureLogManagerInitialized() {final LogManager owner = this;if (initializationDone || owner != manager) {// we don't want to do this twice, and we don't want to do// this on private manager instances.return;}// Maybe another thread has called ensureLogManagerInitialized()// before us and is still executing it. If so we will block until// the log manager has finished initialized, then acquire the monitor,// notice that initializationDone is now true and return.// Otherwise - we have come here first! We will acquire the monitor,// see that initializationDone is still false, and perform the// initialization.//synchronized(this) {// If initializedCalled is true it means that we're already in// the process of initializing the LogManager in this thread.// There has been a recursive call to ensureLogManagerInitialized().final boolean isRecursiveInitialization = (initializedCalled == true);assert initializedCalled || !initializationDone: "Initialization can't be done if initialized has not been called!";if (isRecursiveInitialization || initializationDone) {// If isRecursiveInitialization is true it means that we're// already in the process of initializing the LogManager in// this thread. There has been a recursive call to// ensureLogManagerInitialized(). We should not proceed as// it would lead to infinite recursion.//// If initializationDone is true then it means the manager// has finished initializing; just return: we're done.return;}// Calling addLogger below will in turn call requiresDefaultLogger()// which will call ensureLogManagerInitialized().// We use initializedCalled to break the recursion.initializedCalled = true;try {AccessController.doPrivileged(new PrivilegedAction<Object>() {@Overridepublic Object run() {assert rootLogger == null;assert initializedCalled && !initializationDone;// Read configuration.owner.readPrimordialConfiguration();// Create and retain Logger for the root of the namespace.owner.rootLogger = owner.new RootLogger();owner.addLogger(owner.rootLogger);// 设置默认的日志级别Level defaultLevel = Level.INFO;if (!owner.rootLogger.isLevelInitialized()) {owner.rootLogger.setLevel(defaultLevel);}// Adding the global Logger.// Do not call Logger.getGlobal() here as this might trigger// subtle inter-dependency issues.@SuppressWarnings("deprecation")final Logger global = Logger.global;// Make sure the global logger will be registered in the// global managerowner.addLogger(global);return null;}});} finally {initializationDone = true;}}
}
3、自定义日志级别
JUL对于日志级别的自定义有些麻烦,要先排除掉默认的Handler,然后替换为自定义的Handler。
// 自定义日志级别
public static void test03(){//日志记录器Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());//将默认的日志打印方式关闭掉//参数设置为false,我们打印日志的方式就不会按照父logger默认的方式去进行操作logger.setUseParentHandlers(false);//处理器Handler//在此我们使用的是控制台日志处理器,取得处理器对象ConsoleHandler handler = new ConsoleHandler();//创建日志格式化组件对象SimpleFormatter formatter = new SimpleFormatter();//在处理器中设置输出格式handler.setFormatter(formatter);//在记录器中添加处理器logger.addHandler(handler);//设置日志的打印级别//此处必须将日志记录器和处理器的级别进行统一的设置,才会达到日志显示相应级别的效果//logger.setLevel(Level.CONFIG);//handler.setLevel(Level.CONFIG);logger.setLevel(Level.ALL);handler.setLevel(Level.ALL);logger.severe("severe信息");logger.warning("warning信息");logger.info("info信息");logger.config("config信息");logger.fine("fine信息");logger.finer("finer信息");logger.finest("finest信息");/*输出结果:三月 21, 2023 10:48:40 上午 com.demo.logger.jul.JULTest test03严重: severe信息三月 21, 2023 10:48:40 上午 com.demo.logger.jul.JULTest test03警告: warning信息三月 21, 2023 10:48:40 上午 com.demo.logger.jul.JULTest test03信息: info信息三月 21, 2023 10:48:40 上午 com.demo.logger.jul.JULTest test03配置: config信息三月 21, 2023 10:48:40 上午 com.demo.logger.jul.JULTest test03详细: fine信息三月 21, 2023 10:48:40 上午 com.demo.logger.jul.JULTest test03较详细: finer信息三月 21, 2023 10:48:40 上午 com.demo.logger.jul.JULTest test03非常详细: finest信息*/
}
4、将日志输出到文件中
用户使用Logger来进行日志的记录,Logger可以持有多个处理器Handler(日志的记录使用的是Logger,日志的输出使用的是Handler)
添加了哪些handler对象,就相当于需要根据所添加的handler将日志输出到指定的位置上,例如控制台、文件等
public static void test04() throws IOException {/*将日志输出到具体的磁盘文件中这样做相当于是做了日志的持久化操作*/Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());logger.setUseParentHandlers(false);//文件日志处理器,输出到指定目录下FileHandler handler = new FileHandler("D:\\test\\myLogTest.log");SimpleFormatter formatter = new SimpleFormatter();handler.setFormatter(formatter);logger.addHandler(handler);//也可以同时在控制台和文件中进行打印ConsoleHandler handler2 = new ConsoleHandler();handler2.setFormatter(formatter);logger.addHandler(handler2); //可以在记录器中同时添加多个处理器logger.setLevel(Level.ALL);handler.setLevel(Level.ALL); // 文件中的日志级别为ALLhandler2.setLevel(Level.CONFIG); // 控制台的日志级别为CONFIGlogger.severe("severe信息");logger.warning("warning信息");logger.info("info信息");logger.config("config信息");logger.fine("fine信息");logger.finer("finer信息");logger.finest("finest信息");
}
5、Logger的父子关系
JUL中Logger之间是存在"父子"关系的,值得注意的是,这种父子关系不是我们普遍认为的类之间的继承关系,这种关系是通过树状结构存储的。
public static void test05(){/*从下面创建的两个logger对象看来我们可以认为logger1是logger2的父亲*///父亲是RootLogger,名称默认是一个空的字符串//RootLogger可以被称之为所有logger对象的顶层loggerLogger logger1 = Logger.getLogger("com.demo.test");Logger logger2 = Logger.getLogger("com.demo.test.JULTest");System.out.println(logger2.getParent()==logger1); //true// logger1的父Logger引用为:java.util.logging.LogManager$RootLogger@31ef45e3; 名称为com.demo.test; 父亲的名称为System.out.println("logger1的父Logger引用为:"+logger1.getParent()+"; 名称为"+logger1.getName()+"; 父亲的名称为"+logger1.getParent().getName());// logger2的父Logger引用为:java.util.logging.Logger@598067a5; 名称为com.demo.test.JULTest; 父亲的名称为com.demo.testSystem.out.println("logger2的父Logger引用为:"+logger2.getParent()+"; 名称为"+logger2.getName()+"; 父亲的名称为"+logger2.getParent().getName());/*父亲所做的设置,也能够同时作用于儿子对logger1做日志打印相关的设置,然后我们使用logger2进行日志的打印*///父亲做设置logger1.setUseParentHandlers(false);ConsoleHandler handler = new ConsoleHandler();SimpleFormatter formatter = new SimpleFormatter();handler.setFormatter(formatter);logger1.addHandler(handler);handler.setLevel(Level.ALL);logger1.setLevel(Level.ALL);//儿子做打印,会输出ALLlogger2.severe("severe信息");logger2.warning("warning信息");logger2.info("info信息");logger2.config("config信息");logger2.fine("fine信息");logger2.finer("finer信息");logger2.finest("finest信息");
}
(1)父子关系源码分析
JUL在初始化时会创建一个顶层RootLogger作为所有Logger的父Logger:
//java.util.logging.LogManager#ensureLogManagerInitialized
owner.rootLogger = owner.new RootLogger();
owner.addLogger(owner.rootLogger);
if (!owner.rootLogger.isLevelInitialized()) {owner.rootLogger.setLevel(defaultLevel);
}
RootLogger其实是LogManager的内部类,默认的名称是空字符串。
以上的RootLogger对象作为树状结构的根节点存在的,将来自定义的父子关系通过路径来进行关联,父子关系,同时也是节点之间的挂载关系。
通过owner.addLogger(owner.rootLogger);来进行根节点的挂载。
// java.util.logging.LogManager#addLogger
public boolean addLogger(Logger logger) {final String name = logger.getName();if (name == null) {throw new NullPointerException();}drainLoggerRefQueueBounded();LoggerContext cx = getUserContext(); // 用来保存节点的Map关系if (cx.addLocalLogger(logger)) {// Do we have a per logger handler too?// Note: this will add a 200ms penaltyloadLoggerHandlers(logger, name, name + ".handlers");return true;} else {return false;}
}
class LoggerContext {// Table of named Loggers that maps names to Loggers.private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();// Tree of named Loggersprivate final LogNode root;private LoggerContext() {this.root = new LogNode(null, this);}final class LoggerWeakRef extends WeakReference<Logger> {private String name; // for namedLoggers cleanupprivate LogNode node; // for loggerRef cleanupprivate WeakReference<Logger> parentRef; // for kids cleanupprivate boolean disposed = false; // avoid calling dispose twiceLoggerWeakRef(Logger logger) {super(logger, loggerRefQueue);name = logger.getName(); // save for namedLoggers cleanup}private static class LogNode {HashMap<String,LogNode> children;LoggerWeakRef loggerRef;LogNode parent;final LoggerContext context;
由LoggerContext 的存储数据结构我们也可以看出,是存在父子关系的。
6、使用配置文件
(1)默认配置文件位置
如果我们没有自己添加配置文件,则会使用系统默认的配置文件:
在java.util.logging.LogManager#ensureLogManagerInitialized方法中,执行了owner.readPrimordialConfiguration();方法:
// java.util.logging.LogManager#readPrimordialConfiguration
private void readPrimordialConfiguration() {if (!readPrimordialConfiguration) {synchronized (this) {if (!readPrimordialConfiguration) {// If System.in/out/err are null, it's a good// indication that we're still in the// bootstrapping phaseif (System.out == null) {return;}readPrimordialConfiguration = true;try {AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {@Overridepublic Void run() throws Exception {readConfiguration(); // 读取配置// Platform loggers begin to delegate to java.util.logging.Loggersun.util.logging.PlatformLogger.redirectPlatformLoggers();return null;}});} catch (Exception ex) {assert false : "Exception raised while reading logging configuration: " + ex;}}}}
}// java.util.logging.LogManager#readConfiguration()
public void readConfiguration() throws IOException, SecurityException {checkPermission();// if a configuration class is specified, load it and use it.String cname = System.getProperty("java.util.logging.config.class");if (cname != null) {try {// Instantiate the named class. It is its constructor's// responsibility to initialize the logging configuration, by// calling readConfiguration(InputStream) with a suitable stream.try {Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);clz.newInstance();return;} catch (ClassNotFoundException ex) {Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass(cname);clz.newInstance();return;}} catch (Exception ex) {System.err.println("Logging configuration class \"" + cname + "\" failed");System.err.println("" + ex);// keep going and useful config file.}}String fname = System.getProperty("java.util.logging.config.file");if (fname == null) { // 如果配置为null,就会找java.home --> 找到jre文件夹 --> lib --> logging.propertiesfname = System.getProperty("java.home");if (fname == null) {throw new Error("Can't find java.home ??");}File f = new File(fname, "lib");f = new File(f, "logging.properties");fname = f.getCanonicalPath();}try (final InputStream in = new FileInputStream(fname)) {final BufferedInputStream bin = new BufferedInputStream(in);readConfiguration(bin);}
}
也就是说,如果我们没有指定配置文件的话,JUL也是会读取默认的配置文件,读取的文件是java.homo目录中,jre文件夹下,lib目录中的logging.properties文件。
(2)默认配置文件(去掉注释)
handlers= java.util.logging.ConsoleHandler.level= INFOjava.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.XMLFormatterjava.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormattercom.xyz.foo.level = SEVERE
(3)配置文件解析
# RootLogger使用的处理器,在获取RootLogger对象时进行的设置
# 默认情况下,配置的是控制台处理器,只能在控制台上进行输出操作
# 如果想要其他的处理器,在处理器类后面通过以逗号的形式进行分割
handlers= java.util.logging.ConsoleHandler# 根节点RootLogger的日志级别
# 默认情况下,这是全局的日志级别,如果不手动配置其他的日志级别,则默认输出下述配置的级别及更高的级别
.level= INFO# 文件处理器属性的设置
# 输出日志文件的路径
java.util.logging.FileHandler.pattern = %h/java%u.log
# 输出日志文件的限制(默认50000个字节)
java.util.logging.FileHandler.limit = 50000
# 设置日志文件的数量
java.util.logging.FileHandler.count = 1
# 输出日志的格式
# 默认是以XML的方式输出
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter# 控制台处理器的属性设置
# 控制台输出默认级别
java.util.logging.ConsoleHandler.level = INFO
# 控制台默认输出日志的格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter# 也可以将日志级别设定到具体的某个包下
#com.xyz.foo.level = SEVERE
(4)自定义读取配置文件
可以将logging.properties文件稍作修改,验证有效性。
InputStream input = new FileInputStream("E:\\logging.properties");//取得日志管理器对象
LogManager logManager = LogManager.getLogManager();//读取自定义的配置文件
logManager.readConfiguration(input);Logger logger = Logger.getLogger(JULTest.class.getCanonicalName());logger.severe("severe信息");
logger.warning("warning信息");
logger.info("info信息");
logger.config("config信息");
logger.fine("fine信息");
logger.finer("finer信息");
logger.finest("finest信息");
(5)自定义日志配置文件
以下配置可以自定义FileHandler的设置:
# 自定义Logger
com.demo.logger.jul.handlers=java.util.logging.FileHandler
# 自定义Logger日志等级
com.demo.logger.jul.level=CONFIG
# 屏蔽掉父Logger的日志设置
com.demo.logger.jul.useParentHandlers=false
默认使用的是XMLFormatter,我们可以改成容易读的:
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
文件默认保存在用户的home目录的java0.log文件。
默认的日志文件是覆盖的方式,我们可以设置为追加的方式:
# 输出日志文件,是否追加
java.util.logging.FileHandler.append=true
四、总结
1.初始化LogManager,加载logging.properties配置文件,将Logger添加到LogManager中。
2.从单例的LogManager获取Logger。
// 构造方法是protected的
protected LogManager() {this(checkSubclassPermissions());
}
// 只能通过静态方法获取唯一实例
public static LogManager getLogManager() {if (manager != null) {manager.ensureLogManagerInitialized();}return manager;
}
3.设置日志级别Level,在打印的过程中使用到了日志记录的LogRecord类。
public void log(Level level, String msg) {if (!isLoggable(level)) {return;}LogRecord lr = new LogRecord(level, msg);doLog(lr);
}
4.Filter作为过滤器提供了日志级别之外更细粒度的控制。
5.Handler日志处理器,决定日志的输出位置,例如控制台、文件…
6.Formatter是用来格式化输出的。
学习Java日志框架之——搞懂JUL(java.util.logging)相关推荐
- 学习Java日志框架之——搞懂日志门面(JCL+SLF4J)
文章目录 系列文章目录 一.什么是日志门面 1.门面模式(外观模式) 2.日志门面 二.了解JCL 1.JCL组件结构 2.JCL案例 (1)JCL默认实现 (2)导入log4j测试原有程序 三.SL ...
- 学习日志框架之——搞懂logback
文章目录 系列文章目录 一.logback概述 1.Logback简介 2.Logback中的组件 3.Logback配置文件 4.日志输出格式 二.使用示例 1.依赖导入 2.入门案例 3.配置文件 ...
- Java日志框架学习--JUL和Log4j--上
Java日志框架学习--JUL和Log4j--上 引言 日志框架 市面流行的日志框架 日志门面和日志框架的区别 JUL JUL简介 JUL组件介绍 实际使用 Logger之间的父子关系 默认配置文件位 ...
- java日志框架JUL、JCL、Slf4j、Log4j、Log4j2、Logback 一网打尽
为什么程序需要记录日志 我们不可能实时的24小时对系统进行人工监控,那么如果程序出现异常错误时要如何排查呢?并且系统在运行时做了哪些事情我们又从何得知呢?这个时候日志这个概念就出现了,日志的出现对系统 ...
- Java日志框架 -- 日志框架介绍、日志门面技术、JUL日志(JUL架构、JUL入门示例、JUL日志级别、JUL日志的配置文件)
1. 日志的概念 日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志.具有处理历史数据.诊断问题的追踪以及理解系统的活动等重要作用. 2. Java日志框架 问题: 控制日志输出的内容 ...
- Java日志框架学习笔记
Java日志框架学习笔记 文章目录 0 主流Java日志框架 1 log4j 1.1 理论知识 1.1.1 Loggers日志记录器 1.1.2 Appenders输出端 1.1.3 Layout日志 ...
- Java 日志框架 JUL
文章目录 日志文件的重要性 常见日志框架 什么是JUL JUL架构介绍 入门案例 JUL日志级别 Logger之间的父子关系 日志的配置文件 日志原理解析 日志文件的重要性 做开发最怕的就是线上系统出 ...
- 最牛逼的 Java 日志框架,还不学习。。。
最牛逼的 Java 日志框架,性能无敌,横扫所有对手- Logback 算是JAVA 里一个老牌的日志框架,从06年开始第一个版本,迭代至今也十几年了.不过logback最近一个稳定版本还停留在 20 ...
- 多种java 日志框架【超详细图文】
一.目标 日志的作用和目的 日志的框架 JUL的使用 LOG4J的使用 JCL的使用 二.日志的概念 2.1 日志文件 日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志.具有处理历史 ...
最新文章
- erp服务器哪个稳定,选择erp服务器需要注意的几大问题
- Android分享功能,微博、QQ、QQ空间等社交平台分享之入门与进阶
- 阿德:工作与发财之间的秘密
- 使用script命令自动录屏用户操作
- leetcode 592. Fraction Addition and Subtraction | 592. 分数加减运算(最大公因数gcd,最小公倍数lcm)
- PX4 的 ECL EKF 公式推导及代码解析
- c语言 0494-方程求根,C语言:作业一 选择结构.doc
- 第三章 3.2 DI依赖循环 --《跟我学Spring》笔记 张开涛
- LaTeX数学公式环境
- 三种嵌入式操作系统(Palm OS 、Windows CE 和Linux)的深入分析与比较
- java 异常 ppt_Java程序设计基础与实践 第6章 异常处理.ppt
- 抖音创作规范_抖音创作内容调整提示怎么办
- 支付宝面试:说说序列和反序列?
- C语言中ASCII的应用
- Linux可加载内核模块(LKM)(转载)
- OC中,类的基础知识
- sha算法 哈希算法_SHA1哈希算法教程及其用法示例
- Windows操作系统发展简史【图】
- SenseTime Ace Coder Challenge 暨 商汤在线编程挑战赛-白色相簿
- 光大证券5名高管被罚260万元
热门文章
- execute、executeUpdate、executeQuery三者的区别(及返回值)
- js小案例-九宫格抽奖
- ACRA崩溃报告详解
- jasypt配置文件加解密
- Android Camera HAL3 -架构设计
- 一个低学历程序员开发逆袭大厂的心路历程,看完真心给跪了!
- 珍宝鸭的力扣练习(17):有限状态机
- android 手势旋转,android中手势操作图片的平移、缩放、旋转
- 【C++学习】类与对象(一)——类的定义与对象实例
- 无贡献、无创新、无思路,ML领域准博士求助:论文到底要怎么创新? | Reddit热议...