为什么函数影响性能

在SQL语句中,如果不合理的使用函数(Function)就会严重影响性能,其实这里想说的是PL/SQL中的自定义函数,反而对于一些内置函数而言,影响性能的可能性较小。那么为什么SQL语句当中,不合理的使用函数会影响性能呢?

在SELECT语句中调用函数时,那么查询返回的结果集中的每一行都会调用该函数。如果该函数需要执行1秒,返回的结果集是10行,那么此时SQL语句就需要10秒,如果该函数执行时间需要3秒,返回的结果集是10000条记录,那么这个时间就是30000秒~= 500分钟。是否很恐怖!因为生产环境中自定义函数有时候会出现复杂的业务逻辑,导致自定义函数性能开销较高,如果出现不合理调用,那么很容易就会出现性能问题。 下面我们简单来演示一个例子。

CREATE TABLE TEST

(

   ID  NUMBER

);

 

 

DECLARE RowIndex NUMBER;

BEGIN

RowIndex :=1;

WHILE RowIndex <= 8 LOOP

    INSERT INTO TEST

    SELECT RowIndex  FROM DUAL;

    

     RowIndex := RowIndex +1;

END LOOP;

COMMIT;

END;

/

--创建函数SLOW_FUNCTION,使用DBMS_LOCK.SLEEP休眠2秒,模拟这个函数较慢。

CREATE OR REPLACE FUNCTION SLOW_FUNCTION(p_value  IN NUMBER)

RETURN NUMBER

AS

BEGIN

    DBMS_LOCK.SLEEP(2);

    RETURN p_value+10;

END;

/

SQL> SET TIMING ON; 

SQL> SELECT * FROM TEST;

 

        ID

----------

         1

         2

         3

         4

         5

         6

         7

         8

 

8 rows selected.

 

Elapsed: 00:00:00.00

SQL> SELECT SLOW_FUNCTION(ID) FROM TEST;

 

SLOW_FUNCTION(ID)

-----------------

               11

               12

               13

               14

               15

               16

               17

               18

 

8 rows selected.

 

Elapsed: 00:00:16.01

如果在WHERE当中使用函数,由于有8条记录,而每次调用函数需要Sleep 2秒, 总共耗费2*8=16秒。所以在WHERE条件中,也要谨慎使用自定义函数。

SQL> SET AUTOTRACE OFF;

SQL> 

SQL> SELECT * FROM TEST

  2  WHERE SLOW_FUNCTION(ID)>15;

 

        ID

----------

         6

         7

         8

 

Elapsed: 00:00:16.01

SQL> 

什么情况下函数影响性能

其实自定义函数影响性能,主要在于函数(Function)调用的次数或函数(Function)本身的业务逻辑是否复杂,如果SELECT查询中调用次数很少,影响还是非常小的。如下所示,如果只调用一次的话,这个影响还是非常小的。

 

SQL> SELECT SLOW_FUNCTION(ID) FROM TEST WHERE ID=2;

 

SLOW_FUNCTION(ID)

-----------------

               12

 

Elapsed: 00:00:02.01

其次,如果函数实现的业务逻辑简单,即使调用次数多,对性能影响也很小。我们改写一下下面函数,通过实验来验证测试一下,如下所示:

CREATE OR REPLACE FUNCTION SLOW_FUNCTION(p_value  IN NUMBER)

RETURN NUMBER

AS

BEGIN

    RETURN p_value+10;

END;

/

然后创建一个存储过程,来测试一下循环次数对性能的影响。

CREATE OR REPLACE PROCEDURE TEST_SLOW_FUNCTION(ITER IN NUMBER)

AS RESULT VARCHAR2(60);

BEGIN

    FOR I IN 1 .. ITER LOOP

    SELECT SLOW_FUNCTION(I) INTO RESULT FROM DUAL;

    END LOOP;

END TEST_SLOW_FUNCTION;

/

如下所示,当函数业务逻辑简单,性能开销很低时,循环次数对性能的影响反而很小。10次循环调用跟1000000次循环调用差别是3秒多。可见如果自定义函数的业务逻辑简单,循环次数对性能影响较小。

SQL> EXEC TEST_SLOW_FUNCTION(10);

 

PL/SQL procedure successfully completed.

 

Elapsed: 00:00:00.01

SQL> EXEC TEST_SLOW_FUNCTION(10000);

 

PL/SQL procedure successfully completed.

 

Elapsed: 00:00:00.40

SQL> EXEC TEST_SLOW_FUNCTION(100000);

 

PL/SQL procedure successfully completed.

 

Elapsed: 00:00:03.64

 

如何优化解决问题

对SQL中调用的自定义函数,可以通过等价改写成多表关联语句。避免产生大量的递归调用,另外就是想法设法减少函数被调用次数。SQL中尽量避免使用自定义函数(不是不能用,而是要看场合,尽量避免使用的原因:因为你写的函数,可能会被其它人滥用,会偏离当初你写这个函数的初衷),或者尽量避免函数中实现复杂业务逻辑。

另外,如果实在不能避免的话,就尽量减少调用次数。另外,也有一些技巧可以优化自定义函数性能,下面内容基本参考或翻译Efficient Function Calls From SQL这篇文章。

首先我们还是准备测试环境,使用最初的那个模拟函数。

CREATE OR REPLACE FUNCTION SLOW_FUNCTION(p_value  IN NUMBER)

RETURN NUMBER

AS

BEGIN

DBMS_LOCK.SLEEP(2);

RETURN p_value+10;

END;

/

标量子查询缓存(scalar subquery caching)

标量子查询缓存(scalar subquery caching会通过缓存结果减少SQL对函数(Function)的调用次数, ORACLE会在内存中构建一个哈希表来缓存标量子查询的结果。当然前提是有重复值的情况下。如果没有重复值,其实这种方法是没有任何效果的。如下测试所示

然后我们删除源数据,构造重复数据,然后我们测试对比看一下实验结果吧,不同的写法差别10秒,如果在复杂的实际环境中,这种性能差别还会被放大。

TRUNCATE TABLE TEST;

INSERT INTO TEST

SELECT 1 FROM DUAL UNION ALL

SELECT 1 FROM DUAL UNION ALL

SELECT 1 FROM DUAL UNION ALL

SELECT 2 FROM DUAL UNION ALL

SELECT 2 FROM DUAL UNION ALL

SELECT 2 FROM DUAL UNION ALL

SELECT 3 FROM DUAL UNION ALL

SELECT 3 FROM DUAL;

COMMIT;

通俗来将,当使用标量子查询的时候,ORACLE会将子查询结果缓存在哈希表中, 如果后续的记录出现同样的值,优化器通过缓存在哈希表中的值,判断重复值不用重复调用函数,直接使用上次计算结果即可。从而减少调用函数次数,从而达到优化性能的效果。另外在ORACLE 10和11中, 哈希表只包含了255个Buckets,也就是说它能存储255个不同值,如果超过这个范围,就会出现散列冲突,那些出现散列冲突的值就会重复调用函数,即便如此,依然能达到大幅改善性能的效果。

 

DETERMINISTIC关键字

  

ORACLE通过关键字DETERMINISTIC来表明一个函数(Function)是确定性的,确定性函数可以用于创建基于函数的索引。这个仅仅能在ORACLE 10g以后的版本中使用。它会影响函数如何缓存在SQL中。如下测试所示:

新建带有DETERMINISTIC关键字的函数

CREATE OR REPLACE FUNCTION SLOW_FUNCTION_DERM(p_value  IN NUMBER)

RETURN NUMBER

DETERMINISTIC

AS

BEGIN

    DBMS_LOCK.SLEEP(2);

    RETURN p_value+10;

END;

/

 

SQL> SELECT SLOW_FUNCTION(ID) FROM TEST ;

 

SLOW_FUNCTION(ID)

-----------------

               11

               11

               11

               12

               12

               12

               13

               13

 

8 rows selected.

 

Elapsed: 00:00:16.01

SQL> SELECT SLOW_FUNCTION_DERM(ID) FROM TEST ;

 

SLOW_FUNCTION_DERM(ID)

----------------------

                    11

                    11

                    11

                    12

                    12

                    12

                    13

                    13

 

8 rows selected.

 

Elapsed: 00:00:08.00

将函数标记为确定性确实可以提高性能,但DETERMINISTIC缓存受限于每次从服务器fetch多少数据,缓存仅在当前fetch的生命周期内有效(缓存仅适用于获取的整个生命周期。后续查询(或提取)无法访问先前运行的缓存值),而标量子查询是当前查询内有效。因此受到数组大小的影响. 数组大小(array size)的差异产生了截然不同的性能。如下测试所示:

The difference in array size produced drastically different performance, showing that caching is only available for the lifetime of the fetch. Subsequent queries (or fetches) have no access to the cached values of previous runs.

SQL> show arraysize;

arraysize 15

SQL> SELECT SLOW_FUNCTION_DERM(ID) FROM TEST ;

 

SLOW_FUNCTION_DERM(ID)

----------------------

                    11

                    11

                    11

                    12

                    12

                    12

                    13

                    13

 

8 rows selected.

 

Elapsed: 00:00:08.01

SQL> set arraysize 1;

SQL> SELECT SLOW_FUNCTION_DERM(ID) FROM TEST ;

 

SLOW_FUNCTION_DERM(ID)

----------------------

                    11

                    11

                    11

                    12

                    12

                    12

                    13

                    13

 

8 rows selected.

 

Elapsed: 00:00:12.01

关于arraysize摘抄“Oracle arraysize 和 fetch size参数与性能优化说明”博客中这一段资料:

arraysize定义了一次返回到客户端的行数,当扫描了arraysize行后,停止扫描,返回数据,然后继续扫描。

这个过程就是统计信息中的SQL*Net round trips to/from client。因为arraysize 默认是15行,那么就有一个问题,因为我们一个block

中的记录数一般都会超过15行,所以如果按照15行扫描一次,那么每次扫描要多扫描一个数据块,一个数据块也可能就会重复扫描多次。

重复的扫描会增加consistent gets 和 physical reads。 增加physical reads,这个很好理解,扫描的越多,物理的可能性就越大。

consistent gets,这个是从undo里读的数量,ORACLE为了保证数据的一致性,当一个查询很长,在查询之后,数据块被修改,还未提交,

再次查询时候,ORACLE会根据Undo来构建CR块,这个CR块,可以理解成数据块在之前某个时间的状态。这样通过查询出来的数据就是一致的。

那么如果重复扫描的块越多,需要构建的CR块就会越多,这样读Undo的机会就会越多,consistent gets 就会越多。

 

Cross-Session PL/SQL Function Result Cache

ORACLE 11g 中引入了两个新的缓存机制:

跨会话PL/SQL函数结果缓存:用于缓存函数调用的结果。

Cross-Session PL/SQL Function Result Cache : Used for caching the results of function calls.

查询结果缓存:用于缓存查询产生的整个结果集。

Query Result Cache : Used for caching the whole result set produced by a query.

ORACLE 11G提供的PL/SQL函数的缓存机制(对于不同的会话之间可以共用),下面我们使用第一种机制进行查询结果的缓存,如下所,指定关键词RESULT_CACHE来启用Cross-Session PL/SQL Function Result Cache

CREATE OR REPLACE FUNCTION SLOW_FUNCTION(p_value  IN NUMBER)

RETURN NUMBER

RESULT_CACHE

AS

BEGIN

    DBMS_LOCK.SLEEP(2);

    RETURN p_value+10;

END;

/

然后开启一个会话ID为10的会话,测试SQL性能

SQL> SET TIMING ON;

SQL> SELECT * FROM V$MYSTAT WHERE ROWNUM =1;

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

        10          0          0

 

Elapsed: 00:00:00.01

SQL> SELECT slow_function(id)

  2  FROM TEST;

 

SLOW_FUNCTION(ID)

-----------------

               11

               12

               13

               14

               15

               16

               17

               18

 

8 rows selected.

 

Elapsed: 00:00:16.02

另外开启一个会话,对比测试SQL性能

SQL> SET TIMING ON;

SQL> SELECT * FROM V$MYSTAT WHERE ROWNUM =1; 

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

        73          0          0

 

Elapsed: 00:00:00.01

SQL> SELECT slow_function(id)

  2  FROM TEST;

 

SLOW_FUNCTION(ID)

-----------------

               11

               12

               13

               14

               15

               16

               17

               18

 

8 rows selected.

 

Elapsed: 00:00:00.01

从上述实验可以看出,使用上述方式的缓存信息可以被其他的会话使用,它依赖的对象是自动被管理的。关于这个技术的细节特性是另外一个比较大的话题,此处不做展开,我们需要知道的就是,数据库会帮我们自动缓存,从而当其他会话调用时,使用相关缓存结果来减少函数调用次数。从而达到提高性能效果。

使用PL/SQL的集合进行手动的管理缓存信息

 

在ORACLE 11g之前的版本,我们可以手动将函数调用的值缓存在PL/ SQL集合中,如下所示,我们在对函数调用前,我们构建一个缓存层(caching layer)

CREATE OR REPLACE FUNCTION SLOW_FUNCTION(p_value  IN NUMBER)

RETURN NUMBER

AS

BEGIN

    DBMS_LOCK.SLEEP(2);

    RETURN p_value+10;

END;

/

 

CREATE OR REPLACE PACKAGE cached_lookup_api AS

 

FUNCTION get_cached_value (p_id  IN  NUMBER)

  RETURN NUMBER;

 

PROCEDURE clear_cache;

  

END cached_lookup_api;

/

 

 

CREATE OR REPLACE PACKAGE BODY cached_lookup_api AS

 

TYPE t_tab IS TABLE OF NUMBER

  INDEX BY BINARY_INTEGER;

 

g_tab           t_tab;

g_last_use      DATE   := SYSDATE;

g_max_cache_age NUMBER := 10/(24*60); -- 10 minutes

 

-- -----------------------------------------------------------------

FUNCTION get_cached_value (p_id  IN  NUMBER)

  RETURN NUMBER AS

  l_value NUMBER;

BEGIN

  IF (SYSDATE - g_last_use) > g_max_cache_age THEN

    -- Older than 10 minutes. Delete cache.

    g_last_use := SYSDATE;

    clear_cache;

  END IF;

  

  BEGIN

    l_value := g_tab(p_id);

  EXCEPTION

    WHEN NO_DATA_FOUND THEN

      -- Call function and cache data.

      l_value := slow_function(p_id);

      g_tab(p_id) := l_value;

  END;

  

  RETURN l_value;

END get_cached_value;

-- -----------------------------------------------------------------

-- -----------------------------------------------------------------

PROCEDURE clear_cache AS

BEGIN

  g_tab.delete;

END;

-- -----------------------------------------------------------------

END cached_lookup_api;

/

测试如下所示,不过此方法只能对于当前会话,缓存不在数据库会话之间共享

这个方法有几个问题:

缓存中没有依赖关系管理。只是删除了超过十分钟的缓存数据,你可以提高间隔粒度,但是它可能需要额外的工作。

如果会话是连接池的一部分,那么通过多次调用可能会泄漏信息( potential for information bleeding)。这可以通过package reset来解决。

这个方法没有自动管理缓存大小的机制。

缓存不在数据库会话之间共享

 

手动的使用上下文控制缓存(Manual Caching Using Contexts

 

 

创建上下文(contexts)作为ACCESSED GLOBALLY允许在会话之间共享高速缓存,如下脚本所示:

CREATE OR REPLACE CONTEXT cache_context USING cached_lookup_api ACCESSED GLOBALLY;

 

CREATE OR REPLACE PACKAGE cached_lookup_api AS

 

FUNCTION get_cached_value (p_id  IN  NUMBER)

  RETURN NUMBER;

 

PROCEDURE clear_cache;

  

END cached_lookup_api;

/

 

 

CREATE OR REPLACE PACKAGE BODY cached_lookup_api AS

 

g_last_use      DATE         := SYSDATE;

g_max_cache_age NUMBER       := 10/(24*60); -- 10 minutes

g_context_name  VARCHAR2(20) := 'cache_context';

 

-- -----------------------------------------------------------------

FUNCTION get_cached_value (p_id  IN  NUMBER)

  RETURN NUMBER AS

  l_value NUMBER;

BEGIN

  IF (SYSDATE - g_last_use) > g_max_cache_age THEN

    -- Older than 10 minutes. Delete cache.

    g_last_use := SYSDATE;

    clear_cache;

  END IF;

  

  l_value := SYS_CONTEXT(g_context_name, p_id);

  IF l_value IS NULL THEN

    l_value := slow_function(p_id);

    DBMS_SESSION.set_context(g_context_name, p_id, l_value);

  END IF;

  

  RETURN l_value;

END get_cached_value;

-- -----------------------------------------------------------------

-- -----------------------------------------------------------------

PROCEDURE clear_cache AS

BEGIN

  DBMS_SESSION.clear_all_context(g_context_name);

END;

-- -----------------------------------------------------------------

END cached_lookup_api;

/

我们在不同会话测试测试可以发现,这个可以实现ORACLE 11g中的Cross-Session PL/SQL Function Result Cache

SQL> SET TIMING ON;

SQL> SELECT * FROM v$mystat WHERE ROWNUM=1;

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

        10          0          0

 

Elapsed: 00:00:00.01

SQL> SELECT cached_lookup_api.get_cached_value(id)

  2  FROM   test;

 

CACHED_LOOKUP_API.GET_CACHED_VALUE(ID)

--------------------------------------

                                    11

                                    12

                                    13

                                    14

                                    15

                                    16

                                    17

                                    18

 

8 rows selected.

 

Elapsed: 00:00:16.03

SQL> 

 

 

 

 

SQL> SET TIMING ON;

SQL> SELECT * FROM v$mystat WHERE ROWNUM=1;

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

        73          0          0

 

Elapsed: 00:00:00.01

SQL> SELECT cached_lookup_api.get_cached_value(id)

  2  FROM   test;

 

CACHED_LOOKUP_API.GET_CACHED_VALUE(ID)

--------------------------------------

                                    11

                                    12

                                    13

                                    14

                                    15

                                    16

                                    17

                                    18

 

8 rows selected.

 

Elapsed: 00:00:00.01

SQL> 

Scalar Subquery Caching (Revisited)

 

除了标量子查询缓存之外,我们还讨论了许多缓存机制,但是这些缓存方法是否可以替代对标量子查询缓存的需求?答案是否定的,因为标量子查询缓存是有效减少SQL和PL/SQL之间上下文切换次数的唯一机制。 为了说明这一点,我们将建立一个具有相同值的100,000行的新测试表。

DROP TABLE t2;

 

CREATE TABLE t2 (

  id NUMBER

);

 

INSERT /*+ APPEND */ INTO t2

SELECT 1

FROM   dual

CONNECT BY level <= 100000;

COMMIT;

 

 

CREATE OR REPLACE FUNCTION SLOW_FUNCTION(p_value  IN NUMBER)

RETURN NUMBER

RESULT_CACHE

AS

BEGIN

    DBMS_LOCK.SLEEP(2);

    RETURN p_value+10;

END;

/

 

 

SET SERVEROUTPUT ON

DECLARE

  l_start NUMBER;

BEGIN

  l_start := DBMS_UTILITY.get_cpu_time; 

  FOR cur_rec IN (SELECT slow_function(id)

                  FROM   t2)

  LOOP

    NULL;

  END LOOP;

  DBMS_OUTPUT.put_line('Regular Query   (SELECT List): ' ||

                       (DBMS_UTILITY.get_cpu_time - l_start) || ' hsecs CPU Time');

 

  l_start := DBMS_UTILITY.get_cpu_time; 

  FOR cur_rec IN (SELECT (SELECT slow_function(id) FROM dual)

                  FROM   t2)

  LOOP

    NULL;

  END LOOP;

  DBMS_OUTPUT.put_line('Scalar Subquery (SELECT List): ' ||

                       (DBMS_UTILITY.get_cpu_time - l_start) || ' hsecs CPU Time');

END;

/

在WHERE子句中使用标量子查询时,这种CPU使用率的差异也是可见的。

造成CPU使用率差异的原因是什么?除了标量子查询缓存之外,此处讨论的所有其他缓存方法仍需要调用PL/SQL函数,这会导致SQL和PL/SQL之间的上下文切换。这些上下文切换需要额外的CPU负载。

因此,即使在使用可选的缓存功能来提高多次执行之间或会话之间的函数调用的性能,仍应该使用标量子查询缓存来减少上下文切换。

WHERE子句中的函数

 

前面讨论的缓存方法也适用于WHERE子句,特别是用于减少上下文切换的标量子查询缓存。

在查询的WHERE子句中的列上应用函数可能会导致性能较差,因为它会阻止优化程序在该列上使用常规索引。假设查询不能被重写以消除对函数调用的需要,则一种选择是使用基于函数的索引(function based index )。

您还应该考虑在Oracle 11g中引入的虚拟列。

函数调用中的读一致性问题

  

这个可以直接参考Efficient Function Calls From SQL,此处不做翻译或解说。

 

参考资料:

https://oracle-base.com/articles/misc/efficient-function-calls-from-sql

ORACLE当中自定义函数性优化浅析的更多相关文章

  1. Oracle之自定义函数

    数据库中函数包含四个部分:声明.返回值.函数体和异常处理. --没有参数的函数 create or replace function get_user return varchar2 is v_use ...

  2. oracle常用自定义函数集合

    1.Oracle 判断值是否为数字的函数CREATE OR REPLACE FUNCTION ISNUMBER(MyStr VARCHAR2) RETURN NUMBERIS  STR VARCHAR ...

  3. Oracle基础 自定义函数

    一.函数 函数与存储过程相似,也是数据库中存储的已命名PL-SQL程序块.函数的主要特征是它必须有一个返回值.通过return来指定函数的返回类型.在函数的任何地方可以通过return express ...

  4. Oracle数据库自定义函数练习20181031

    --测试函数3 CREATE OR REPLACE FUNCTION FN_TEST3 (NUM IN VARCHAR2) RETURN VARCHAR2 IS TYPE VARCHAR2_ARR ) ...

  5. Oracle 编写自定义函数

    CREATE OR REPLACE function testAdd(js1 in number, js2 in number) return number is v_hj number; v_h ; ...

  6. 重写Oracle的wm_concat函数,自定义分隔符、排序

    oracle中,wm_concat函数是一个聚合函数,和mysql中的group_concat函数类似,不过group_concat函数比较强大,可以定义分隔符和排序,当然所谓强大是相对的,这里假使我 ...

  7. Oracle自定义函数1

    用户定义函数是存储在数据库中的代码块,可以把值返回到调用程序.调用时如同系统函数一样,如max(value)函数,其中,value被称为参数.函数参数有3种类型. IN 参数类型:表示输入给函数的参数 ...

  8. Oracle自定义函数

    核心提示:函数用于返回特定数据.执行时得找一个变量接收函数的返回值; 语法如下: create or replace function function_name ( argu1 [mode1] da ...

  9. Mybatis下配置调用Oracle自定义函数返回的游标结果集

    在ibatis和Mybatis对存储过程和函数函数的调用的配置Xml是不一样的,以下是针对Mybatis 3.2的环境进行操作的. 第一步配置Mapper的xml内容 <mapper names ...

随机推荐

  1. [Swift]LeetCode1026. 节点与其祖先之间的最大差值 | Maximum Difference Between Node and Ancestor

    Given the root of a binary tree, find the maximum value V for which there exists different nodes A a ...

  2. [BASH]获取执行脚本的路径

    SCRIPT=$(readlink -f "$0") #当前执行脚本的真实路径,兼容软链接 basedir=$(dirname "$SCRIPT") #当前执行 ...

  3. linux字符测试以及for循环

    1.字符测试 常用的测试字符的命令: == .=都表示测试字符相等,格式为[ A = B ]需要注意的是变量与等号之间需要有空格,不然测试的结果不正确示例如下 若字符与等号不加空格,假设变量A=ab  ...

  4. Yarn篇--搭建yarn集群

    一.前述 有了上次hadoop集群的搭建,搭建yarn就简单多了.废话不多说,直接来 二.规划 三.配置如下 yarn-site.xml配置 <property>        <n ...

  5. Python内置函数(29)——help

    英文文档: help([object]) Invoke the built-in help system. (This function is intended for interactive use ...

  6. Linux(2)---记录一次线上服务 CPU 100%的排查过程

    Linux(2)---记录一次线上服务 CPU 100%的排查过程 当时产生CPU飙升接近100%的原因是因为项目中的websocket时时断开又重连导致CPU飙升接近100% .如何排查的呢 是通过 ...

  7. asp.net core 系列 13 日志

    一.概述 ASP.NET Core 支持适用于各种内置和第三方日志记录, 供程序的日志记录 API,本文介绍了如何将日志记录 API 与内置提供程序一起使用.对于第三方日志记录提供程序使用,文章最后有 ...

  8. Leetcode 137. 只出现一次的数字 II - 题解

    Leetcode 137. 只出现一次的数字 II - 题解 137. Single Number II 在线提交: https://leetcode.com/problems/single-numb ...

  9. 拥抱单页网站! jQuery全屏滚动插件fullPage.js

    不知道从什么时候开始,单页网站就悄悄走进人们的视线,尤其是国外的网站,更是钟爱单页网站.制作一个全屏滚动的效果,然后每个滚动页弄一个好看的背景色,配上一些描述性的文字,大家都喜欢这么弄,仿佛逼格瞬间可 ...

  10. maven-代码风格检查工具

    目录 checkstyle findbugs pmd 其他 checkstyle checkstyle 用于对代码风格进行检查 checkstyle-maven插件 操作示例 mvn clean co ...