接前文跟我一起读postgresql源码(九)——Executor(查询执行模块之——Scan节点(上)) ,本篇把剩下的七个Scan节点结束掉。
T_SubqueryScanState,
T_FunctionScanState,
T_ValuesScanState,
T_CteScanState,
T_WorkTableScanState,
T_ForeignScanState,
T_CustomScanState,
8.SubqueryScan 节点
SubqueryScan节点的作用是以另一个査询计划树(子计划)为扫描对象进行元组的扫描,其扫描过程最终被转换为子计划的执行。
Postgres子查询主要包含如下几个关键字: EXISTS, IN, NOT IN, ANY/SOME, ALL,详细介绍可以看看:http://www.postgres.cn/docs/9.5/functions-subquery.html
举例子:
postgres=# explain select id from test_new where exists (select id from test_dm);
QUERY PLAN
-------------------------------------------------------------------------
Result (cost=0.02..35.52 rows=2550 width=4)
One-Time Filter: $0
InitPlan 1 (returns $0)
-> Seq Scan on test_dm (cost=0.00..22346.00 rows=1000000 width=0)
-> Seq Scan on test_new (cost=0.00..35.50 rows=2550 width=4)
(5 行)
下面这个查询虽然也是子查询,但是在查询编译阶段被优化了(提升子连接,主要是把ANY和EXIST子句转换为半连接)
postgres=# explain select id from test_new where exists (select id from test_dm where id = test_new.id);
QUERY PLAN
-----------------------------------------------------------------------------
Hash Semi Join (cost=38753.00..42736.38 rows=1275 width=4)
Hash Cond: (test_new.id = test_dm.id)
-> Seq Scan on test_new (cost=0.00..35.50 rows=2550 width=4)
-> Hash (cost=22346.00..22346.00 rows=1000000 width=4)
-> Seq Scan on test_dm (cost=0.00..22346.00 rows=1000000 width=4)
(5 行)
有关内容,这里有一篇讲得很好:PostgreSQL查询优化之子查询优化
SubqueryScan节点在Scan节点之上扩展定义了子计划的根节点指针(subplan字段),而subrtable字段是査询编译器使用的结构,执行器运行时其值为空。
typedef struct SubqueryScan
{
Scan scan;
Plan *subplan;
} SubqueryScan;
显然,SubqueryScan节点的初始化过程(ExecInitSubqueryScan函数)会使用ExecInitNode处理SubqueryScan的subplan字段指向的子计划树,并将子计划的PlanStale树根节点指针賦值给SubqueryScanState 的subplan字段。
typedef struct SubqueryScanState
{
ScanState ss; /* its first field is NodeTag */
PlanState *subplan;
} SubqueryScanState;
我认为SubqueryScan节点其实就是一个壳子,为什么这么说呢?因为SubqueryScan节点的执行(ExecSubqueryScan 函数)通过将SubqueryNext 传递给 ExecScan函数处理来实现的。SubqueryNext实际则是调用ExecProcNode处理subplan来获得元组。也就是说,这里SubqueryScan是运行了一个独立的查询计划,然后获取它的结果,而不是自己去扫描表。因此recheck工作就在独立的查询计划里做过了,SubqueryScan节点不必再做。
所以我们可以看到:
static bool
SubqueryRecheck(SubqueryScanState *node, TupleTableSlot *slot)
{
/* nothing to check */
return true;
}
上面说了在执行时调用了ExecProcNode处理subplan,那么在清理过程中,很显然需要额外调用ExecEndNode来清理子计划。
9.FunctionScan 节点
二话不说先上例子:
postgres=# CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text)
postgres-# AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
postgres-# LANGUAGE SQL;
CREATE FUNCTION
postgres=# explain SELECT * FROM dup(42);
QUERY PLAN
-------------------------------------------------------------
Function Scan on dup (cost=0.25..10.25 rows=1000 width=36)
(1 行)
在PostgreSQL中,有一些函数可以返回元组的集合,为了能从这些函数的返回值中获取元组,PostgreSQL定义了 FunctionScan节点,其扫描对象为返回元组集的函数。FunctionScan节点在Scan的基础上扩展定义了:
functions列表字段,里面存放了FuncitonScan涉及的函数;
以及funcordinality字段(是否给返回结果加上序号列)。
When a function in the FROM clause is suffixed by WITH ORDINALITY, a bigint column is appended to the output which starts from 1 and increments by 1 for each row of the function's output. This is most useful in the case of set returning functions such as unnest()
详细看这里:http://www.postgres.cn/docs/9.5/functions-srf.html
typedef struct FunctionScan
{
Scan scan;
List *functions; /* list of RangeTblFunction nodes */
bool funcordinality; /* WITH ORDINALITY */
} FunctionScan;
FunctionScan 节点的初始化过程(ExecInitFunctionScan 函数)会初始化 FunctionScanState 结构,然后根据FunctionScan的字段functions,对每个函数构造运行时的状态节点FunctionScanPerFuncState,如下:
typedef struct FunctionScanPerFuncState
{
ExprState *funcexpr; /* state of the expression being evaluated */
TupleDesc tupdesc; /* desc of the function result type */
int colcount; /* expected number of result columns */
Tuplestorestate *tstore; /* holds the function result set */
int64 rowcount; /* # of rows in result set, -1 if not known */
TupleTableSlot *func_slot; /* function result slot (or NULL) */
} FunctionScanPerFuncState;
这里根据FunctionScan中的functions字段对每一个函数构造用于表达式计算的结构(存储在funcexpr中)和,还要构造函数返回元组的描述符存储在tupdesc中,此时用于存储函数结果集的tuplestoreslate字段为NULL。
上面这些做完以后,就可以根据所涉及的所有函数的FunctionScanPerFuncState结构来构造返回值的TupleDesc(即最后的返回值一定是这几个函数返回值的组合):
例如:
postgres=# SELECT * FROM dup(42) WITH ORDINALITY AS t(ls,n,xxx),increment(42);
ls | n | xxx | increment
----+------------+-----+-----------
42 | 42 is text | 1 | 43
(1 行)
typedef struct FunctionScanState
{
ScanState ss; /* its first field is NodeTag */
int eflags; //node's capability flags
bool ordinality; //is this scan WITH ORDINALITY?
bool simple; //true if we have 1 function and no ordinality
int64 ordinal; //current ordinal column value
int nfuncs; //number of functions being executed
/* per-function execution states (private in nodeFunctionscan.c) */
struct FunctionScanPerFuncState *funcstates; /* array of length nfuncs */
MemoryContext argcontext; //memory context to evaluate function arguments in
} FunctionScanState;
在 FunctionScan 节点的执行过程(ExecFunctionScan 函数)中,将 FunctionNext 传递给 ExecScan函数,FunctionNext函数首先判断tuplestorestate是否为空(首次执行时为空),如果为空则执行函数ExecMakeTableFunctionResult生成所有结果集并存储在tuplestorestate中,此后每次执行节点将调用tuplestore_gettupleslot获取结果集中的一个元组。
最后,FunctionScan节点清理过程需要淸理tuplestorestate结构。
10.ValuesScan 节点
VALUES计算由值表达式指定的一个行值或者一组行值。更常见的是把它用来生成一个大型命令内的"常量表", 但是它也可以被独自使用。
当多于一行被指定时,所有行都必须具有相同数量的元素。结果表的列数据类型 由出现在该列的表达式的显式或者推导类型组合决定,决定的规则与UNION相同。
在大型的命令中,在语法上允许VALUES出现在 SELECT出现的任何地方。因为语法把它当做一个 SELECT,可以为一个VALUES 命令使用ORDER BY、 LIMIT(或者等效的FETCH FIRST) 以及OFFSET子句。
我们举例吧,一个纯粹的VALUES命令:
VALUES (1, 'one'), (2, 'two'), (3, 'three');
将返回一个具有两列、三行的表。
postgres=# VALUES (1, 'one'), (2, 'two'), (3, 'three');
column1 | column2
---------+---------
1 | one
2 | two
3 | three
(3 行)
postgres=# EXPLAIN VALUES (1, 'one'), (2, 'two'), (3, 'three');
QUERY PLAN
--------------------------------------------------------------
Values Scan on "*VALUES*" (cost=0.00..0.04 rows=3 width=36)
(1 行)
更常用地,VALUES可以被用在一个大型 SQL 命令中。 在INSERT中最常用:
postgres=# insert into test values (1,'xxxx');
INSERT 0 1
postgres=# explain insert into test_new values (1);
QUERY PLAN
------------------------------------------------------
Insert on test_new (cost=0.00..0.01 rows=1 width=0)
-> Result (cost=0.00..0.01 rows=1 width=0)
(2 行)
具体的可以看这个:http://www.postgres.cn/docs/9.5/sql-values.html
这样我们就对VALUES子句不陌生了,下面继续说。
ValuesScan节点是用来对VALUES子句给出的元组集合进行扫描(INSERT语句中的VALUES子句走的是RESULT节点)。如下所示,ValuesScan节点中的values_lists存储了VALUES子句中的表达式链表。
typedef struct ValuesScan
{
Scan scan;
List *values_lists; /* list of expression lists */
} ValuesScan;
ValuesScan节点的初始化过程(ExeclnitValuesScan函数)处理values_lists中的表达式生成Values表达式,并存储在ValuesScanState的exprlists数组中,array_len记录数组长度,cuxr_idx和
markedJdx用于存储数组中的偏移量。同时还会分配内存上下文rowconext用于表达式计箅(ss.ps.ps_ExprContext本来就是用来做表达式计算的,但是为了防止对于一个过长的VALUES子句发生的内存泄露,使用rowconext对VALUES每一行做统一处理,在每一行处理完成后就使用rowconext释放该段内存。)。
typedef struct ValuesScanState
{
ScanState ss; /* its first field is NodeTag */
ExprContext *rowcontext; //per-expression-list context
List **exprlists; //array of expression lists being evaluated
int array_len; //size of array
int curr_idx; //current array index (0-based)
} ValuesScanState;
ValuesScan 节点执行过程(ExecValuesScan 函数)调用 ExecScan 实现,ExecScan 通过 ValuesNext获取扫描元组,ValuesNext则通过curr_idx从exprlists中获取需要处理的表达式,并计算出结果元组返回。
由于额外地申请了rowconext上下文,因此在ValuesScan节点清理过程(ExecEndValuesScan函数)中需要释放内存上下文rowcontext。
11.CteScan 节点
WITH提供了一种方式来书写在一个大型查询中使用的辅助语句。这些语句通常被称为公共表表达式或CTE,它们可以被看成是定义只在一个查询中存在的临时表。在WITH子句中的每一个辅助语句可以是一个SELECT、INSERT、UPDATE或DELETE,并且WITH子句本身也可以被附加到一个主语句,主语句也可以是SELECT、INSERT、UPDATE或DELETE
具体可以参考这个:http://www.postgres.cn/docs/9.5/queries-with.html
如果对CTE有所了解,就会知道,CTE一般不会单独存在,而是依附于一个主查询,换言之CTE是作为一个副查询出现的。所以在主查询中就将副查询作为一个子计划Subplan处理。CTE的执行状态树存放到执行器全局状态Estate的es_subplanstates链表中。
typedef struct EState
{
NodeTag type;
...
/* Parameter info: */
ParamListInfo es_param_list_info; /* values of external params */
ParamExecData *es_param_exec_vals; /* values of internal params */
...
List *es_subplanstates; /* List of PlanState for SubPlans */
...
} EState;
并在CteScan中的ctePlanld存储其子计划在该链表中的偏移量,对应于同一个子计划的CteScan的ctePlanld相同。PostgreSQL在实现时,还为每个CTE在一个全局参数链表中分配了一个空间,其偏移量存储在cteParam中,对应同一个CTE的CteScan对应的偏移量也相同。CteScan节点相关数据结构如下所示。
typedef struct CteScan
{
Scan scan;
int ctePlanId; /* ID of init SubPlan for CTE */
int cteParam; /* ID of Param representing CTE output */
} CteScan;
CteScan节点的初始化过程(ExecInitCteScan函数)将首先初始化CteScanState结构,通过ctePlanld在es_subplanstates中找到对应的子计划执行状态树,并存储在CteScanState的cteplanstate字段中。
然后通过cteParam在执行器全局状态Estate的es_param_exec_vals字段中获取参数结构ParamExecData。若ParamExecData中value为NULL,表示没有其他CteScan对此CTE初始化过存储结构,此时会初始化CteScanState的cte_table字段,并将leader和ParamExecData的value賦值为指向当前CteScanState的指针。若ParamExecData中的value不为NULL,则将其值陚值给leader,让其指向第一个CteScan创建的CteScanState,而不为当前的CteScan初始化cte_table。这样对应一个CTE全局只有一个元组缓存结构,所有使用该CTE的CteScan都会共享该缓存。
typedef struct CteScanState
{
ScanState ss; /* its first field is NodeTag */
int eflags; /* capability flags to pass to tuplestore */
int readptr; /* index of my tuplestore read pointer */
PlanState *cteplanstate; /* PlanState for the CTE query itself */
/* Link to the "leader" CteScanState (possibly this same node) */
struct CteScanState *leader;
/* The remaining fields are only valid in the "leader" CteScanState */
Tuplestorestate *cte_table; /* rows already read from the CTE query */
bool eof_cte; /* reached end of CTE query? */
} CteScanState;
最后。在做一些初始化工作,比如初始化处理元组的表达式上下文、子表达式、元组表、结果元组表等等。
在执行CteScan节点时,将首先査看cte_table指向的缓存中是否缓存元组(缓存结构Tuplestorestate),如果有可直接获取,否则需要先执行ctePlanld指向的子计划获取元组。
CteScan节点的清理过程需要清理元组缓存结构,但只需清理leader指向自身的CteScanState。
12.WorkTableScan 节点
这个节点是和RecursiveUnion节点紧密关联的。下面先看例子,一个RecursiveUnion查询:
postgres=# WITH RECURSIVE t(n) AS(
postgres(# VALUES(1)
postgres(# UNION ALL
postgres(# SELECT n+1 FROM t WHERE n<100)
postgres-# SELECT sum(n) FROM t;
sum
------
5050
(1 行)
查询计划
QUERY PLAN
-------------------------------------------------------------------------
Aggregate (cost=3.65..3.66 rows=1 width=4)
CTE t
-> Recursive Union (cost=0.00..2.95 rows=31 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> WorkTable Scan on t t_1 (cost=0.00..0.23 rows=3 width=4)
Filter: (n < 100)
-> CTE Scan on t (cost=0.00..0.62 rows=31 width=4)
(7 行)
对于递归查询求值,流程如下:
1.计算非递归项。对UNION(但不对UNION ALL),抛弃重复行。把所有剩余的行包括在递归查询的结果中,并且也把它们放在一个临时的工作表中。
2.只要工作表不为空,重复下列步骤:
计算递归项,用当前工作表的内容替换递归自引用。对UNION(不是UNION ALL),抛弃重复行以及那些与之前结果行重复的行。将剩下的所有行包括在递归查询的结果中,并且也把它们放在一个临时的中间表中。
用中间表的内容替换工作表的内容,然后清空中间表。
详细可以看这里:http://www.postgres.cn/docs/9.5/queries-with.html
这里的工作表就是WorkTable。
WorkTableScan会与RecursiveUnion共同完成递归合并子査询。RecursiveUnion会缓存一次递归中的所有元组到RecursiveUnionState结构中,WorkTableScan提供了对此缓存的扫描。
如下所示,WorkTableScan节点扩展定义了wtParam用于同RecursiveUnion节点间的通信,而 WorkTableScanState 节点的 rustate 字段中记录了 RecursiveUnionState结构的指针,以便WorkTableScan在执行过程中可以从缓存结构中获取元组。
typedef struct WorkTableScan
{
Scan scan;
int wtParam; /* ID of Param representing work table */
} WorkTableScan;
节点状态:
typedef struct WorkTableScanState
{
ScanState ss; /* its first field is NodeTag */
RecursiveUnionState *rustate;
} WorkTableScanState;
13.ForeignScan节点
如果用过postgres_fdw或者dblink这些PostgreSQL提供了外部数据包装器,那么就大概能知道这个Scan节点的用途:扫描外部Postgresql数据表。
如果你对postgres_fdw有兴趣,这里是网址,拿去不谢:http://www.postgres.cn/docs/9.5/postgres-fdw.html
ForeignScan节点的信息如下,主要在Scan之外扩展了外部数据相关的一些信息。fdw_exprs和fdw_private都在外部数据包装器的控制下,但是fdw_exprs被假定为包含表达式树并且将由规划器相应地进行后处理; fdw_private不会。
fdw_scan_tlist是描述由FDW返回的扫描元组的内容的目标列表;如果扫描元组与外部表的声明行类型匹配,则可以为NIL,这对于简单的外部表扫描来说是正常情况。(如果计划节点表示外部联接,则需要fdw_scan_tlist,因为系统目录中没有可用的rowtype)
fdw_scan_tlist永远不会被执行;它只是持有描述扫描元组列中的内容的表达式树。
fdw_recheck_quals应该包含核心系统传递给FDW但是没有被添加到scan.plan.qual中的条件,也就是说,这些条件需要在FDW中做判断(这些条件是要在recheck中做判断的)。
typedef struct ForeignScan
{
Scan scan;
Oid fs_server; /* OID of foreign server */
List *fdw_exprs; /* expressions that FDW may evaluate */
List *fdw_private; /* private data for FDW */
List *fdw_scan_tlist; /* optional tlist describing scan tuple */
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
Bitmapset *fs_relids; /* RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
} ForeignScan;
还有一个数据结构也要特别关注,它保持了外部数据包装器处理程序返回函数,即它提供供PLANNER和EXECUTOR使用的回调函数的指针。
src/include/foreign/fdwapi.h
typedef struct FdwRoutine
下面是ForeignScan的状态节点ForeignScanState,它在ScanState之外扩展了需要recheck的列表字段fdw_recheck_quals、外部数据包装器处理程序返回函数集合结构体FdwRoutine和外部数据包装器状态fdw_state。
在初始化时,ExecInitForeignScan函数除了做一般的初始化之外,还对ForeignScanState的fdwroutine字段做了初始化,获取函数指针和扫描关系表。
typedef struct ForeignScanState
{
ScanState ss; /* its first field is NodeTag */
List *fdw_recheck_quals; /* original quals not in ss.ps.qual */
/* use struct pointer to avoid including fdwapi.h here */
struct FdwRoutine *fdwroutine;
void *fdw_state; /* foreign-data wrapper can keep state here */
} ForeignScanState;
ForeignScan节点的执行(ExecForeignScan 函数)通过将ForeignNext传递给 ExecScan函数处理来实现的。ForeignNext实际则是调用fdwroutine->IterateForeignScan在外部数据源上扫描每次获得一个元组。
关于函数ForeignRecheck,还记得上面说的fdw_recheck_quals字段么?这里调用ExecQual函数使用fdw_recheck_quals字段中的条件来做recheck。
最后,扫描结束后,调用fdwroutine->EndForeignScan关闭扫描,并且关闭外部表ExecCloseScanRelation(node->ss.ss_currentRelation)。
14.CustomScan节点
从Custom这个单词我们就可以知道,这是postgres开放的自定义Scan方法的接口。这个节点只提供了一个空壳子,我们看下:
typedef struct CustomScan
{
Scan scan;
uint32 flags; /* mask of CUSTOMPATH_* flags, see relation.h */
List *custom_plans; /* list of Plan nodes, if any */
List *custom_exprs; /* expressions that custom code may evaluate */
List *custom_private; /* private data for custom code */
List *custom_scan_tlist; /* optional tlist describing scan
* tuple */
Bitmapset *custom_relids; /* RTIs generated by this scan */
const CustomScanMethods *methods;
} CustomScan;
留给用户自己去扩展,同时,CustomScanState状态节点也是一样,里面只有一些函数指针和预设的一些属性,你可以使用,也可以把CustomScanState作为你要扩展的Scan方法的State的一个属性,可以说还是很灵活的。
因此,不多说这个了,希望能在网上看到关于这方面做扩展的例子~
Scan节点就这么结束了。
跟我一起读postgresql源码(十)——Executor(查询执行模块之——Scan节点(下))的更多相关文章
-
跟我一起读postgresql源码(九)——Executor(查询执行模块之——Scan节点(上))
从前面介绍的可优化语句处理相关的背景知识.实现思想和执行流程,不难发现可优化语句执行的核心内容是对于各种计划节点的处理,由于使用了节点表示.递归调用.统一接口等设计,计划节点的功能相对独立.代码总体流 ...
-
跟我一起读postgresql源码(十一)——Executor(查询执行模块之——Materialization节点(上))
物化节点 顾名思义,物化节点是一类可缓存元组的节点.在执行过程中,很多扩展的物理操作符需要首先获取所有的元组后才能进行操作(例如聚集函数操作.没有索引辅助的排序等),这时要用物化节点将元组缓存起来.下 ...
-
跟我一起读postgresql源码(十三)——Executor(查询执行模块之——Join节点(上))
Join节点 JOIN节点有以下三种: T_NestLoopState, T_MergeJoinState, T_HashJoinState, 连接类型节点对应于关系代数中的连接操作,PostgreS ...
-
跟我一起读postgresql源码(八)——Executor(查询执行模块之——可优化语句的执行)
2.可优化语句的执行 可优化语句的共同特点是它们被查询编译器处理后都会生成査询计划树,这一类语句由执行器(Executor)处理.该模块对外提供了三个接口: ExecutorStart.Executo ...
-
跟我一起读postgresql源码(七)——Executor(查询执行模块之——数据定义语句的执行)
1.数据定义语句的执行 数据定义语句(也就是之前我提到的非可优化语句)是一类用于定义数据模式.函数等的功能性语句.不同于元组增删査改的操作,其处理方式是为每一种类型的描述语句调用相应的处理函数. 数据 ...
-
跟我一起读postgresql源码(六)——Executor(查询执行模块之——查询执行策略)
时光荏苒,岁月如梭.楼主已经很久没有更新了.之前说好的一周一更的没有做到.实在是事出有因,没能静下心来好好看代码.当然这不能作为我不更新的理由,时间挤挤还是有的,拖了这么久,该再写点东西了,不然人就怠 ...
-
跟我一起读postgresql源码(五)——Planer(查询规划模块)(下)
上一篇我们介绍了查询规划模块的总体流程和预处理部分的源码.查询规划模块再执行完预处理之后,可以进入正式的查询规划处理流程了. 查询规划的主要工作由grouping_planner函数完成.在具体实现的 ...
-
跟我一起读postgresql源码(三)——Rewrite(查询重写模块)
上一篇博文我们阅读了postgresql中查询分析模块的源码.查询分析模块对前台送来的命令进行词法分析.语法分析和语义分析后获得对应的查询树(Query).在获得查询树之后,程序开始对查询树进行查询重 ...
-
跟我一起读postgresql源码(四)——Planer(查询规划模块)(上)
时间一晃周末就过完了,时间过得太快,不由得让人倍加珍惜.时间真是不够用哈~ 好的不废话,这次我们开始看查询规划模块的源码吧. 查询规划部分的在整个查询处理模块应该是在一个非常重要的地位上,这一步直接决 ...
随机推荐
-
php实现注册
<?php header("Content-Type:text/html;charset=gb2312"); @mysql_connect('localhost','root ...
-
Mysql Index、B Tree、B+ Tree、SQL Optimization
catalog . 引言 . Mysql索引 . Mysql B/B+ Tree . Mysql SQL Optimization . MySQL Query Execution Process 1. ...
-
cocos2d-x 内存管理浅析
Cocos2d-x用create创建对象, 这个方法已经被引擎封装成一个宏定义了:CREATE_FUNC, 下面是这个宏定义的实现: #define CREATE_FUNC(__TYPE__) \ ...
-
关于header跳转之后的乱码问题
关于header跳转之后的乱码问题 http://www.360doc.com/content/11/0603/19/7052474_121495648.shtml 问题:不同网站的跳转出现乱码,不同 ...
-
JavaScript DOM高级程序设计 3.6 实例 将HTML代码转换成DOM代码(附源码)--我要坚持到底!
作为一名Web开发者,最讨厌的事情就是重复性任务,摆脱乏味的日常重复性事物的一种方法,是借助可重用的对象或者说与你现在建立的ADS库类似的库,另外一种让事情变得有意思,且能够加速开发进程的方式是编写能 ...
-
External file changes sync may be slow: Project files cannot be watched (are they under network mount?)
if some files are on a mounted disk: go to Settings | Notifications | File Watcher Messages and tune ...
-
Java基础知识强化之集合框架笔记55:Map集合之HashMap集合(HashMap<;Integer,String>;)的案例
1. HashMap集合(键是Integer,值是String的案例) 2. 代码示例: package cn.itcast_02; import java.util.HashMap; import ...
-
javascript学习(知识点整理)
有了这个代码,就可以在定义 中增加更多的控制了 后面会举例关于extjs定义的更多控制 此种方案可以解决定义时需要一些函数调用的情况 函数作用域和声明提前: 即由于js是解释性语言,在执行前会 ...
-
[cacti]nginx+php+cacti+mysql+php-fpm 安装小记
网上教程很多,但是nginx不太多,下面安装时候主要参考的篇文章: http://54im.com/linux/linux-cacti-cn-install.html http://www.tecmi ...
-
Scrapy爬虫框架(实战篇)【Scrapy框架对接Splash抓取javaScript动态渲染页面】
(1).前言 动态页面:HTML文档中的部分是由客户端运行JS脚本生成的,即服务器生成部分HTML文档内容,其余的再由客户端生成 静态页面:整个HTML文档是在服务器端生成的,即服务器生成好了,再发送 ...