• Log4j、Log4j 2、Logback、SFL4J、JUL、JCL的比较


    正文

    Log4j

         Log4j = Log for Java.
         author: Ceki Gülcü
         license: Apache License V2.0
         Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、数据库等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
         Log4j有7种不同的log级别,按照等级从低到高依次为:TRACE<DEBUG<INFO<WARN<ERROR<FATAL<OFF。如果配置为OFF级别,表示关闭log。    
         Log4j支持两种格式的配置文件:properties和xml。包含三个主要的组件:Logger、appender、Layout。    
    Example for log4j 1.2     

    复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration PUBLIC "-//LOGGER" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
    <log4j:configuration>    
      <!--an appender is an output destination, such as the console or a file; names of appenders are arbitrarily chosen-->    
      <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
          <param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
        </layout>
      </appender>
    

      <!--loggers of category 'org.springframework' will only log messages of level "info" or higher; if you retrieve Loggers by using the class name (e.g. Logger.getLogger(AClass.class)) and if AClass is part of the org.springframework package, it will belong to this category-->
      <logger name="org.springframework">
        <level value="info"/>
      </logger>

      <!--everything of spring was set to "info" but for class PropertyEditorRegistrySupport we want "debug" logging-->
      <logger name="org.springframework.beans.PropertyEditorRegistrySupport">
        <level value="debug"/>
      </logger>
      
      <root>
      <!--all log messages of level "debug" or higher will be logged, unless defined otherwise all log messages will be logged to the appender "stdout", unless defined otherwise-->
        <level value="debug" />
        <appender-ref ref="stdout" />
      </root>

    </log4j:configuration> 

    复制代码

    SLF4J

         SLF4J = Simple Logging Facade for Java.
         author: Ceki Gülcü
         SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,而是通过Facade Pattern提供一些Java logging API,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。作者创建SLF4J的目的是为了替代Jakarta Commons-Logging。
         实际上,SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类。在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用那个具体的日志系统。SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。
         那么什么时候使用SLF4J比较合适呢?
         如果你开发的是类库或者嵌入式组件,那么就应该考虑采用SLF4J,因为不可能影响最终用户选择哪种日志系统。在另一方面,如果是一个简单或者独立的应用,确定只有一种日志系统,那么就没有使用SLF4J的必要。假设你打算将你使用log4j的产品卖给要求使用JDK 1.4 Logging的用户时,面对成千上万的log4j调用的修改,相信这绝对不是一件轻松的事情。但是如果开始便使用SLF4J,那么这种转换将是非常轻松的事情。 

    Logback

          author: Ceki Gülcü
         licences:EPL v1.0 and LGPL 2.1
         Logback,一个“可靠、通用、快速而又灵活的Java日志框架”。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。
         
         1. logback-core: Joran, Status, context, pattern parsing
         2. logback-classic: developer logging
         3. logback-access: The log generated when a user accesses a web-page on a web server. Integrates seamlessly with Jetty and Tomcat.
    选择logback的理由:(http://logback.qos.ch/reasonsToSwitch.html#fasterImpl )
         1. logback比log4j要快大约10倍,而且消耗更少的内存。
         2. logback-classic模块直接实现了SLF4J的接口,所以我们迁移到logback几乎是零开销的。
         3. logback不仅支持xml格式的配置文件,还支持groovy格式的配置文件。相比之下,Groovy风格的配置文件更加直观,简洁。
         4. logback-classic能够检测到配置文件的更新,并且自动重新加载配置文件。
         5. logback能够优雅的从I/O异常中恢复,从而我们不用重新启动应用程序来恢复logger。
         6. logback能够根据配置文件中设置的上限值,自动删除旧的日志文件。
         7. logback能够自动压缩日志文件。
         8. logback能够在配置文件中加入条件判断(if-then-else)。可以避免不同的开发环境(dev、test、uat...)的配置文件的重复。
         9. logback带来更多的filter。
         10. logback的stack trace中会包含详细的包信息。
         11. logback-access和Jetty、Tomcat集成提供了功能强大的HTTP-access日志。
         配置文件:需要在项目的src目录下建立一个logback.xml。注:(1)logback首先会试着查找logback.groovy文件;(2)当没有找到时,继续试着查找logback-test.xml文件;(3)当没有找到时,继续试着查找logback.xml文件;(4)如果仍然没有找到,则使用默认配置(打印到控制台)。 详细的配置在http://aub.iteye.com/blog/1101222这篇博客中解释的非常清楚。在这里感谢一下原作者(^_^)。

    JUL

         JUL = java.util.logging.

         Java提供了自己的日志框架,类似于Log4J,但是API并不完善,对开发者不是很友好,而且对于日志的级别分类也不是很清晰,比如:SEVERE, WARNING, INFO, CONFIG, FINE,FINER, FINEST。所以不推荐使用这种方式输出日志。

    JCL

         JCL = Jakarta Commons-Logging.
         Jakarta Commons Logging和SLF4J非常类似,也是提供的一套API来掩盖了真正的Logger实现。便于不同的Logger的实现的替换,而不需要重新编译代码。缺点在于它的查找Logger的实现者的算法比较复杂,而且当出现了一些class loader之类的异常时,无法去修复它。

    Log4j2

         已经有很多其他的日志框架对Log4j进行了改良,比如说SLF4J、Logback等。而且Log4j 2在各个方面都与Logback非常相似,那么为什么我们还需要Log4j 2呢?
      1. 插件式结构。Log4j 2支持插件式结构。我们可以根据自己的需要自行扩展Log4j 2. 我们可以实现自己的appender、logger、filter。
      2. 配置文件优化。在配置文件中可以引用属性,还可以直接替代或传递到组件。而且支持json格式的配置文件。不像其他的日志框架,它在重新配置的时候不会丢失之前的日志文件。

    Example for log4j 2    

    复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xml>
    <!-- Log4j 2.x 配置文件。每30秒自动检查和应用配置文件的更新; -->
    <Configuration status="warn" monitorInterval="30" strict="true" schema="Log4J-V2.2.xsd">
        <Appenders>
               <!-- 输出到控制台 -->
               <Console name="Console" target="SYSTEM_OUT">
                   <!-- 需要记录的级别 -->
                   <!-- <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" /> -->
                   <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n" />
              </Console>
    
          <span style="color: #008000;">&lt;!--</span><span style="color: #008000;"> 输出到文件,按天或者超过80MB分割 </span><span style="color: #008000;">--&gt;</span>
          <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">RollingFile </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">="RollingFile"</span><span style="color: #ff0000;"> fileName</span><span style="color: #0000ff;">="../logs/xjj.log"</span><span style="color: #ff0000;">    filePattern</span><span style="color: #0000ff;">="../logs/$${date:yyyy-MM}/xjj-%d{yyyy-MM-dd}-%i.log.gz"</span><span style="color: #0000ff;">&gt;</span>
               <span style="color: #008000;">&lt;!--</span><span style="color: #008000;"> 需要记录的级别 </span><span style="color: #008000;">--&gt;</span>
               <span style="color: #008000;">&lt;!--</span><span style="color: #008000;"> &lt;ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" /&gt; </span><span style="color: #008000;">--&gt;</span>
               <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">PatternLayout </span><span style="color: #ff0000;">pattern</span><span style="color: #0000ff;">="%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"</span> <span style="color: #0000ff;">/&gt;</span>
               <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">Policies</span><span style="color: #0000ff;">&gt;</span>
                    <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">OnStartupTriggeringPolicy </span><span style="color: #0000ff;">/&gt;</span>
                    <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">TimeBasedTriggeringPolicy </span><span style="color: #0000ff;">/&gt;</span>
                    <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">SizeBasedTriggeringPolicy </span><span style="color: #ff0000;">size</span><span style="color: #0000ff;">="80 MB"</span> <span style="color: #0000ff;">/&gt;</span>
               <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">Policies</span><span style="color: #0000ff;">&gt;</span>
          <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">RollingFile</span><span style="color: #0000ff;">&gt;</span>
     <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">Appenders</span><span style="color: #0000ff;">&gt;</span>
     <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">Loggers</span><span style="color: #0000ff;">&gt;</span>
          <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">Root </span><span style="color: #ff0000;">level</span><span style="color: #0000ff;">="info"</span><span style="color: #0000ff;">&gt;</span> <span style="color: #008000;">&lt;!--</span><span style="color: #008000;"> 全局配置 </span><span style="color: #008000;">--&gt;</span>
               <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">AppenderRef </span><span style="color: #ff0000;">ref</span><span style="color: #0000ff;">="Console"</span> <span style="color: #0000ff;">/&gt;</span>
               <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">AppenderRef </span><span style="color: #ff0000;">ref</span><span style="color: #0000ff;">="RollingFile"</span><span style="color: #0000ff;">/&gt;</span>
          <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">Root</span><span style="color: #0000ff;">&gt;</span>
          <span style="color: #008000;">&lt;!--</span><span style="color: #008000;"> 为sql语句配置特殊的Log级别,方便调试 </span><span style="color: #008000;">--&gt;</span>
          <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">Logger </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">="com.xjj.dao"</span><span style="color: #ff0000;"> level</span><span style="color: #0000ff;">="${log.sql.level}"</span><span style="color: #ff0000;"> additivity</span><span style="color: #0000ff;">="false"</span><span style="color: #0000ff;">&gt;</span>
               <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">AppenderRef </span><span style="color: #ff0000;">ref</span><span style="color: #0000ff;">="Console"</span> <span style="color: #0000ff;">/&gt;</span>
          <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">Logger</span><span style="color: #0000ff;">&gt;</span>
     <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">Loggers</span><span style="color: #0000ff;">&gt;</span>
    

    </Configuration>

    复制代码

      3. Java 5的并发性。Log4j 2利用Java 5中的并发特性支持,尽可能地执行最低层次的加锁。解决了在log4j 1.x中存留的死锁的问题。如果你的程序仍然在饱受内存泄露的折磨,请毫不犹豫地试一下log4j 2吧。

      4. 异步logger。Log4j 2是基于LMAX Disruptor库的。在多线程的场景下,和已有的日志框架相比,异步的logger拥有10左右的效率提升。
         还有更多的新特性,在这里就不一一赘述了。了解更多请移步到:http://logging.apache.org/log4j/2.x/manual/index.html。 

    注意:因为Log4j 2使用的是Jackson Data Processor来解析json文件的,所以想要使用json格式的配置文件,必须在项目中加入Jackson的依赖:

    复制代码
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.2.2</version>
    </dependency>
    

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.2.2</version>
    </dependency> 

    复制代码

    slf4j与log4j联合使用

      slf4j是什么?slf4j只是定义了一组日志接口,但并未提供任何实现,既然这样,为什么要用slf4j呢?log4j不是已经满足要求了吗?

      是的,log4j满足了要求,但是,日志框架并不只有log4j一个,你喜欢用log4j,有的人可能更喜欢logback,有的人甚至用jdk自带的日志框架,这种情况下,如果你要依赖别人的jar,整个系统就用了两个日志框架,如果你依赖10个jar,每个jar用的日志框架都不同,岂不是一个工程用了10个日志框架,那就乱了!

      如果你的代码使用slf4j的接口,具体日志实现框架你喜欢用log4j,其他人的代码也用slf4j的接口,具体实现未知,那你依赖其他人jar包时,整个工程就只会用到log4j日志框架,这是一种典型的门面模式应用,与jvm思想相同,我们面向slf4j写日志代码,slf4j处理具体日志实现框架之间的差异,正如我们面向jvm写java代码,jvm处理操作系统之间的差异,结果就是,一处编写,到处运行。况且,现在越来越多的开源工具都在用slf4j了

      那么,怎么用slf4j呢?

      首先,得弄到slf4j的jar包,maven依赖如下,log4j配置过程完全不变

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.21</version>
    </dependency>

    然后,弄到slf4j与log4j的关联jar包,通过这个东西,将对slf4j接口的调用转换为对log4j的调用,不同的日志实现框架,这个转换工具不同

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.21</version>
    </dependency>

    当然了,slf4j-log4j12这个包肯定依赖了slf4j和log4j,所以使用slf4j+log4j的组合只要配置上面这一个依赖就够了

    最后,代码里声明logger要改一下,原来使用log4j是这样的

    复制代码
    import org.apache.log4j.Logger;
    class Test {
        final Logger log = Logger.getLogger(Test.class);
        public void test() {
            log.info("hello this is log4j info log");
        }
    }
    复制代码

    现在要改成这样

    复制代码
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    class Test {
        Logger log = LoggerFactory.getLogger(Test.class);
        public void test() {
            log.info("hello, my name is {}", "chengyi");
        }
    }
    复制代码

    依赖的Logger变了,而且,slf4j的api还能使用占位符,很方便。 

    slf4j与log4j2联合使用

    1、引用依赖包及相关注释:

    复制代码
    <!-- log配置:Log4j2 + Slf4j -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency> <!-- 桥接:告诉Slf4j使用Log4j2 -->
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency> <!-- 桥接:告诉commons logging使用Log4j2 -->
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-jcl</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.10</version>
    </dependency> 
    复制代码

    **注:**log4j-api-2.x 和 log4j-core-2.x是必须的,其他包根据需要引入,如下图所示: 
    这里写图片描述

    2、代码中使用:

    复制代码
    @RunWith(SpringJUnit4ClassRunner.class)  //使用Spring Junit4进行测试  
    @ContextConfiguration ({"classpath:spring/applicationContext.xml"}) //加载配置文件
    public abstract class BaseJunit4Test {
    }
    

    import org.junit.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    mport com.xjj.test.BaseJunit4Test;
    public class Log4j2Test extends BaseJunit4Test {
    static public Logger logger = LoggerFactory.getLogger(Log4j2Test.class);

    @Test
    public void logTC1(){
    logger.error(
    "error");
    logger.debug(
    "debug");
    logger.info(
    "info");
    logger.trace(
    "trace");
    logger.warn(
    "warn");
    logger.error(
    "error {}", "param");
        logger.info("请求处理结束,耗时:{}毫秒", (System.currentTimeMillis() - beginTime)); //第一种用法
    logger.info("请求处理结束,耗时:" + (System.currentTimeMillis() - beginTime) + "毫秒"); //第二种用法

    }
    }
     

    复制代码

    输出结果:

    16:19:28.779 [main] ERROR com.xjj.test.mytest.Log4j2Test - error
    16:19:28.781 [main] ERROR com.xjj.test.mytest.Log4j2Test - error param

    注:如果没有任何配置,Log4j2会使用缺省配置(级别:ERROR):

    root logger:ConsoleAppender
    PatternLayout:"%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"

    Tips:根据官方测试的数据,第一种用法比第二种快47倍! 

    日志级别动态调整

    以下代码演示动态调整log4j的日志级别:

    复制代码
    public class Log4jTest {
        private static final ConcurrentHashMap<String, Object> loggerContainer = new ConcurrentHashMap<>();
        private static final LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
        private static final Logger LOGGER = LoggerFactory.getLogger(Log4jTest.class);
    
    @Test
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> put() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> InterruptedException {
        LOGGER.debug(</span>"log4j debug msg:{}","start"<span style="color: #000000;">);
        LOGGER.info(</span>"log4j info msg:{}","start"<span style="color: #000000;">);
        LOGGER.error(</span>"log4j error msg:{}","start"<span style="color: #000000;">);
    

    setLoggerLevel4Log4j("com.junzi.log", Level.ERROR.toString());

        LOGGER.debug(</span>"log4j debug msg:{}","end"<span style="color: #000000;">);
        LOGGER.info(</span>"log4j info msg:{}","end"<span style="color: #000000;">);
        LOGGER.error(</span>"log4j error msg:{}","end"<span style="color: #000000;">);
    }
    
    @Before
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> init() {
        String type </span>=<span style="color: #000000;"> StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr();
        System.out.println(type);
        Enumeration enumeration </span>=<span style="color: #000000;"> org.apache.log4j.LogManager.getCurrentLoggers();
        </span><span style="color: #0000ff;">while</span><span style="color: #000000;"> (enumeration.hasMoreElements()) {
            org.apache.log4j.Logger logger </span>=<span style="color: #000000;"> (org.apache.log4j.Logger) enumeration.nextElement();
            </span><span style="color: #0000ff;">if</span> (logger.getLevel() != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
                loggerContainer.put(logger.getName(), logger);
            }
        }
        org.apache.log4j.Logger rootLogger </span>=<span style="color: #000000;"> org.apache.log4j.LogManager.getRootLogger();
        loggerContainer.put(rootLogger.getName(), rootLogger);
    }
    
    </span><span style="color: #008000;">/**</span><span style="color: #008000;">
     * 适用于log4j2
     * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> loggerName
     * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> level
     </span><span style="color: #008000;">*/</span>
    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> setLoggerLevel4Log4j2(String loggerName, String level) {
        LoggerConfig loggerConfig </span>=<span style="color: #000000;"> (LoggerConfig) loggerContainer.get(loggerName);
        Level targetLevel </span>= StringUtils.isBlank(level) ?<span style="color: #000000;"> Level.INFO : Level.toLevel(level);
        </span><span style="color: #0000ff;">if</span> (loggerConfig == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
            System.out.println(loggerName </span>+ " logger not exist."<span style="color: #000000;">);
            </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;
        }
        loggerConfig.setLevel(targetLevel);
        loggerContext.updateLoggers();
        System.out.println(</span>"set logger: " + loggerName + ", level: " +<span style="color: #000000;"> targetLevel.name());
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;
    }
    
    </span><span style="color: #008000;">/**</span><span style="color: #008000;">
     * 适用于log4j
     * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> loggerName
     * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> level
     </span><span style="color: #008000;">*/</span>
    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> setLoggerLevel4Log4j(String loggerName, String level) {
        org.apache.log4j.Logger targetLogger </span>=<span style="color: #000000;"> (org.apache.log4j.Logger) loggerContainer.get(loggerName);
        org.apache.log4j.Level targetLevel </span>=<span style="color: #000000;"> org.apache.log4j.Level.toLevel(level);
        targetLogger.setLevel(targetLevel);
    }</span>&nbsp;</pre>
    
    复制代码

    参考资料

    http://tech.lede.com/2017/02/06/rd/server/log4jSearch/

    http://www.cnblogs.com/penghongwei/p/3417179.html

    http://www.cnblogs.com/ywlaker/p/6124067.html

    https://tech.meituan.com/change_log_level.html

    http://hongbing.github.io/posts/blog/17/10/15/dynamic-change-logger-level.html

    原文地址:https://www.cnblogs.com/junzi2099/p/7930268.html
  • 相关阅读:
    C++关键字:explicit
    IFCModel
    Markdown基础语法
    CSS学习笔记(四):传统布局、flex、grid【未完成】
    CSS学习笔记(三):文本、字体、背景、表格、列表
    CSS学习笔记(一):选择器、运作机制
    CSS学习笔记(二):盒子模型
    初等数论与算法(一):整除、最大公约数、最小公倍数、辗转相除法、裴蜀定理
    React Native for Arcgis 地图开发 底图配置(一)
    Python爬虫入门
  • 原文地址:https://www.cnblogs.com/jpfss/p/11044220.html
Copyright © 2020-2023  润新知