前面的几篇文章。我们具体介绍了HBase中HRegion上MemStore的flsuh流程,以及HRegionServer上MemStore的flush处理流程。那么,flush究竟是在什么情况下触发的呢?本文我们将具体探究下HBase中MemStore的flush流程的发起时机。看看究竟都有哪些操作。或者哪些后台服务进程会触发MemStore的flush。

首先。在《HBase源代码分析之HRegionServer上MemStore的flush处理流程(一)》《HBase源代码分析之HRegionServer上MemStore的flush处理流程(二)》两篇文章中,我们了解了HRegionServer上MemStore的flush处理流程,知道了这么一个事实:flush的请求会通过requestFlush()或requestDelayedFlush()方法被加入到MemStoreFlusher的flushQueue队列中。然后由其内部的FlushHandler线程组消费。对须要flush的HRegion进行处理。所以。我们首先能够知道,调用MemStoreFlusher这两个方法的地方。肯定就会是MemStore发起flush的部分时机,另外。也许会存在部分操作或者内部流程直接调用HRegion的flushcache()方法而触发flush。以下,我们開始总结下,都有哪些操作或者内部流程触发MemStore的flush,以及须要flush的推断条件,包含其它一些方面的详情。

一、通过将RequestFlush请求加入到MemStoreFlusher的flushQueue队列

(一)单个Put操作

在HRegion中处理Put操作的put(Put put)方法中。在開始运行操作前,首先会调用checkResources()方法检查资源,这个checkResources()实际上就是检查HRegion的MemStore大小是否超过一定的阈值,假设超过,则会调用requestFlush()方法发起对该HRegion的MemStore进行flush的请求。并抛出RegionTooBusyException异常。阻止该操作继续,兴许将要讲的Delete、Append等数据更新操作也是如此,在開始运行操作前都会调用这个checkResources()方法来检查资源。checkResources()方法代码例如以下:

/*
* Check if resources to support an update.
* 检測是否有足够的资源支持一个Put、Append等数据更新操作
*
* We throw RegionTooBusyException if above memstore limit
* and expect client to retry using some kind of backoff
* 假设超过memstore的限制,我们抛出RegionTooBusyException这个异常。而且期望客户端使用某种补偿进行重试
*/
private void checkResources()
throws RegionTooBusyException {
// If catalog region, do not impose resource constraints or block updates.
// 假设是Meta Region。不实施资源约束或堵塞更新
if (this.getRegionInfo().isMetaRegion()) return; // 假设Region当前内存大小超过阈值
// 这个memstoreSize是当前时刻HRegion上MemStore的大小。它是在Put、Append等操作中调用addAndGetGlobalMemstoreSize()方法实时更新的。
// 而blockingMemStoreSize是HRegion上设定的MemStore的一个阈值。当MemStore的大小超过这个阈值时,将会堵塞数据更新操作
if (this.memstoreSize.get() > this.blockingMemStoreSize) {
// 更新堵塞请求计数器
blockedRequestsCount.increment();
// 请求刷新Region
requestFlush(); // 抛出RegionTooBusyException异常
throw new RegionTooBusyException("Above memstore limit, " +
"regionName=" + (this.getRegionInfo() == null ? "unknown" :
this.getRegionInfo().getRegionNameAsString()) +
", server=" + (this.getRegionServerServices() == null ? "unknown" :
this.getRegionServerServices().getServerName()) +
", memstoreSize=" + memstoreSize.get() +
", blockingMemStoreSize=" + blockingMemStoreSize);
}
}

首先。假设是Meta Region,不实施资源约束或堵塞更新;

然后,假设Region当前内存大小memstoreSize超过阈值blockingMemStoreSize,则更新堵塞请求计数器。发起刷新MemStore请求,并抛出RegionTooBusyException异常。堵塞数据更新操作。

我们首先来看下memstoreSize,这个memstoreSize是当前时刻HRegion上MemStore的大小,它是在Put、Append等操作中调用addAndGetGlobalMemstoreSize()方法实时更新的。代码例如以下:

  /**
* Increase the size of mem store in this region and the size of global mem
* store
* @return the size of memstore in this region
*/
public long addAndGetGlobalMemstoreSize(long memStoreSize) {
if (this.rsAccounting != null) {
rsAccounting.addAndGetGlobalMemstoreSize(memStoreSize);
}
return this.memstoreSize.addAndGet(memStoreSize);
}

而blockingMemStoreSize是HRegion上设定的MemStore的一个阈值。当MemStore的大小超过这个阈值时。将会堵塞数据更新操作。

其定义在HRegion上线被构造时须要调用的一个setHTableSpecificConf()中,部分代码例如以下:

    // blockingMemStoreSize是HRegion上设定的MemStore的一个阈值,当MemStore的大小超过这个阈值时,将会堵塞数据更新操作
// 它的计算是由memstoreFlushSize乘以一个比例。这个比例取自參数hbase.hregion.memstore.block.multiplier,
// 该參数未配置的话。则默觉得4
this.blockingMemStoreSize = this.memstoreFlushSize *
conf.getLong(HConstants.HREGION_MEMSTORE_BLOCK_MULTIPLIER,
HConstants.DEFAULT_HREGION_MEMSTORE_BLOCK_MULTIPLIER);

我们能够知道。它的计算是由memstoreFlushSize乘以一个比例,这个比例取自參数hbase.hregion.memstore.block.multiplier,该參数未配置的话。则默觉得4。

那么memstoreFlushSize是什么呢?memstoreFlushSize为HRegion上设定的一个阈值,当MemStore的大小超过这个阈值时。将会发起flush请求。它的计算首先是由Table决定的。即每一个表能够设定自己的memstoreFlushSize,通过keywordMEMSTORE_FLUSHSIZE来设定,假设表中未设定,则取參数hbase.hregion.memstore.flush.size,假设參数再无配置的话,则默觉得1024*1024*128L,即128M。

代码相同在setHTableSpecificConf()这种方法中。例如以下:

    if (this.htableDescriptor == null) return;
long flushSize = this.htableDescriptor.getMemStoreFlushSize(); if (flushSize <= 0) {
flushSize = conf.getLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE,
HTableDescriptor.DEFAULT_MEMSTORE_FLUSH_SIZE);
} // memstoreFlushSize为HRegion上设定的一个阈值,当MemStore的大小超过这个阈值时,将会发起flush请求
// 它的计算首先是由Table决定的,即每一个表能够设定自己的memstoreFlushSize,通过keywordMEMSTORE_FLUSHSIZE来设定,
// 假设未设定,则取參数hbase.hregion.memstore.flush.size。參数未配置的话。则默觉得1024*1024*128L,即128M
this.memstoreFlushSize = flushSize;

接下来。我们看下requestFlush()是怎样发送的。代码例如以下:

private void requestFlush() {

	// 推断HRegion上的rsServices是否为空,rsServices为HRegionServer提供的服务类。
// HRegion通过持有它,才干够获得能够发起运行诸如flush、compact、split等部分操作的工具对象。
if (this.rsServices == null) {
return;
} // synchronized同步检查writestate的状态。假设writestate的状态为flushRequested。则直接返回,避免反复请求。
// 否则将writestate的flushRequested设置为true,并继续发起flush请求
synchronized (writestate) {
if (this.writestate.isFlushRequested()) {
return;
}
writestate.flushRequested = true;
} // Make request outside of synchronize block; HBASE-818.
// 通过rsServices获得FlushRequester。继而调用其requestFlush()方法,将HRegion自身传入。发起flush请求。
// 这个FlushRequester就是HRegionServer上的cacheFlusher。它的requestFlush()就会将flush请求增加到请求队列中,利用内部工作线程去处理
this.rsServices.getFlushRequester().requestFlush(this);
if (LOG.isDebugEnabled()) {
LOG.debug("Flush requested on " + this);
}
}

首先。须要做一些必要的推断和状态设置。例如以下:

1、推断HRegion上的rsServices是否为空,rsServices为HRegionServer提供的服务类。

HRegion通过持有它,才可以获得可以发起运行诸如flush、compact、split等部分操作的工具对象。

2、检查writestate的状态,假设writestate的状态为flushRequested,则直接返回。避免反复请求,否则将writestate的flushRequested设置为true。并继续发起flush请求。

检查通过后,就会通过rsServices获得FlushRequester,继而调用其requestFlush()方法。将HRegion自身传入,发起flush请求。

这个FlushRequester就是HRegionServer上的cacheFlusher,它的requestFlush()就会将flush请求增加到请求队列中,利用内部工作线程去处理。getFlushRequester()方法代码在实现了RegionServerServices()接口的HRegionServer中,代码例如以下:

  /** @return reference to FlushRequester */
@Override
public FlushRequester getFlushRequester() {
return this.cacheFlusher;
}

继续回到put()方法,继而依次调用doBatchMutate()、batchMutate()、batchMutate()方法,运行Put操作。

而batchMutate()方法的定义,是针对批量的Put、Delete等操作而专门设计的一个方法,仅仅只是单个的Put等操作传入的是一个仅仅包括一个操作的数组。

batchMutate()方法会在批量操作未所有完毕前一直循环,每次循环时,都会调用checkResources()检測MemStore,并调用doMiniBatchMutation()方法完毕操作并同步更新HRegion的MemStore大小,获取其值为newSize。最后通过isFlushSize()方法推断是否须要发起一个flush请求来决定是否调用requestFlush()方法。代码例如以下:

**
* Perform a batch of mutations.
* It supports only Put and Delete mutations and will ignore other types passed.
*
* 完毕一批变化操作。 * 它仅支持Put和Delete操作,将忽略其它类型的操作。
*
* @param batchOp contains the list of mutations
* @return an array of OperationStatus which internally contains the
* OperationStatusCode and the exceptionMessage if any.
* @throws IOException
*/
OperationStatus[] batchMutate(BatchOperationInProgress<?> batchOp) throws IOException {
boolean initialized = false;
// 操作是否为回放,回放的话为REPLAY_BATCH_MUTATE。否则为BATCH_MUTATE
Operation op = batchOp.isInReplay() ? Operation.REPLAY_BATCH_MUTATE : Operation.BATCH_MUTATE;
// 開始Region上的操作
startRegionOperation(op);
try { // 操作未所有完毕前一直循环
while (!batchOp.isDone()) {
// 假设是回放操作,则检測ReadOnly
if (!batchOp.isInReplay()) {
checkReadOnly();
} // 检測相关资源
checkResources(); // 未初始化的话要先初始化
if (!initialized) {
// 更新写请求计数器
this.writeRequestsCount.add(batchOp.operations.length);
// 假设不是日志回放,运行pre钩子方法
if (!batchOp.isInReplay()) {
doPreMutationHook(batchOp);
}
initialized = true;
}
// 运行操作,并返回添加的内存大小
long addedSize = doMiniBatchMutation(batchOp); // 以原子操作的方式添加Region上的MemStore内存大小
long newSize = this.addAndGetGlobalMemstoreSize(addedSize); // 内存超过阈值时,请求flush
if (isFlushSize(newSize)) {
requestFlush();
}
}
} finally {
// 关闭Region上的操作
closeRegionOperation(op);
} // 返回批操作的状态
return batchOp.retCodeDetails;
}

我们来看下isFlushSize()方法。非常easy。它就是通过推断当前MemStore大小newSize是否超过memstoreFlushSize来决定。

代码例如以下:

/*
* @param size
* @return True if size is over the flush threshold
*/
private boolean isFlushSize(final long size) {
return size > this.memstoreFlushSize;
}

至此,单个Put操作讲完了。

(二)单个Delete操作

单个Delete操作与单个Put操作一样,代码例如以下:

//////////////////////////////////////////////////////////////////////////////
// set() methods for client use.
//////////////////////////////////////////////////////////////////////////////
/**
* @param delete delete object
* @throws IOException read exceptions
*/
public void delete(Delete delete)
throws IOException {
checkReadOnly();
checkResources();
startRegionOperation(Operation.DELETE);
try {
delete.getRow();
// All edits for the given row (across all column families) must happen atomically.
doBatchMutate(delete);
} finally {
closeRegionOperation(Operation.DELETE);
}
}

也是先调用checkResources()检查MemStore,再调用doBatchMutate()进行处理,同单个Put操作是一样。

读者可自行分析。

(三)checkAndMutate/checkAndRowMutate操作

checkAndMutate()/checkAndRowMutate()方法中。也是先调用checkResources()检查MemStore,再调用doBatchMutate()进行处理,同单个Put操作是一样。checkAndMutate/checkAndRowMutate操作的特点就是保证改动数据的原子性,也是属于数据更新的操作。

(四)单个Append操作

在HBase中的append()方法中。相同是先调用checkResources()方法检測HRegion的MemStore。然后在处理完append操作后。调用addAndGetGlobalMemstoreSize()方法更新并获取最新的MemStore大小size。继而调用isFlushSize()方法推断是否须要调用requestFlush()方法发起flush请求。处理模式也是同上述的单个Put、单个Delete操作大致相同。

(五)单个Increment操作

同单个Append操作。在HBase中的increment()方法中,对于MemStore的检查及推断是否须要flush等全然一致,不再赘述。

(六)批量操作

既然单个操作可能会引起flush。那么批量操作更不用说了。批量操作是通过batchMutate()方法实现的,上面已经分析过这种方法了。这里不再赘述。

综上所述,凡是涉及到数据更新的操作,比方Put、Delete、Append、increment等操作,均是先检查MemStore。假设其高于某个阈值。将会发送flush请求。并抛出异常,堵塞数据更新操作。

另外,在操作运行完成后。也会依据MemStore增长情况。推断是否达到了该触发flush的条件。假设条件满足。则会发送flush请求。

二、通过直接调用HRegion的flushcache()方法

(一)外部触发(命令行等)

这样的场景的是通过RegionServer上RSRpcServices的flushRegion()方法发起的。flushRegion()的代码例如以下:

  /**
* Flush a region on the region server.
* 刷新RegionServer上的 一个Region
*
* @param controller the RPC controller
* @param request the request
* @throws ServiceException
*/
@Override
@QosPriority(priority=HConstants.ADMIN_QOS)
public FlushRegionResponse flushRegion(final RpcController controller,
final FlushRegionRequest request) throws ServiceException {
try {
// 检測RegionServer的状态
checkOpen(); // 请求计数器加1
requestCount.increment(); // 获取须要flush的Region
HRegion region = getRegion(request.getRegion());
LOG.info("Flushing " + region.getRegionNameAsString());
boolean shouldFlush = true; // 推断是否须要flush。推断的根据是region上次flush时间小于请求中getIfOlderThanTs()方法的返回值
if (request.hasIfOlderThanTs()) {
shouldFlush = region.getLastFlushTime() < request.getIfOlderThanTs();
} FlushRegionResponse.Builder builder = FlushRegionResponse.newBuilder(); // 假设应该刷新
if (shouldFlush) {
// 開始时间
long startTime = EnvironmentEdgeManager.currentTime();
// 调用Region的flushcache()方法刷新
HRegion.FlushResult flushResult = region.flushcache();
if (flushResult.isFlushSucceeded()) {// 刷新成功的话,记录刷新耗时
long endTime = EnvironmentEdgeManager.currentTime();
regionServer.metricsRegionServer.updateFlushTime(endTime - startTime);
}
boolean result = flushResult.isCompactionNeeded();
if (result) {// 须要合并的话。通过RegionServer的compactSplitThread发起合并请求
regionServer.compactSplitThread.requestSystemCompaction(region,
"Compaction through user triggered flush");
}
builder.setFlushed(result);
}
builder.setLastFlushTime(region.getLastFlushTime());
return builder.build();
} catch (DroppedSnapshotException ex) {
// Cache flush can fail in a few places. If it fails in a critical
// section, we get a DroppedSnapshotException and a replay of wal
// is required. Currently the only way to do this is a restart of
// the server.
regionServer.abort("Replay of WAL required. Forcing server shutdown", ex);
throw new ServiceException(ex);
} catch (IOException ie) {
throw new ServiceException(ie);
}
}

flushRegion()方法内部在推断应该进行flush后,会调用HRegion的flushcache()方法对其MemStore进行flush处理,代码非常easy,这里就不多说了。

(二)Region合并(不是compact。而是两个Region的merge)

在RSRpcServices中存在Region合并时调用的mergeRegions()方法。在其内部会先后调用regionA和regionB的flushcache()方法去flush每一个Region上的MemStore,然后再运行Region合并。关键代码例如以下:

      long startTime = EnvironmentEdgeManager.currentTime();

      // 刷新regionA的MemStore
HRegion.FlushResult flushResult = regionA.flushcache();
if (flushResult.isFlushSucceeded()) {
long endTime = EnvironmentEdgeManager.currentTime();
regionServer.metricsRegionServer.updateFlushTime(endTime - startTime);
}
startTime = EnvironmentEdgeManager.currentTime(); // 刷新regionB的MemStore
flushResult = regionB.flushcache();
if (flushResult.isFlushSucceeded()) {
long endTime = EnvironmentEdgeManager.currentTime();
regionServer.metricsRegionServer.updateFlushTime(endTime - startTime);
}
regionServer.compactSplitThread.requestRegionsMerge(regionA, regionB, forcible,
masterSystemTime);

(三)Region分裂

在RSRpcServices中存在Region分裂时调用的splitRegion()方法,也是先调用flushcache()将Region上的memstore刷新。

代码例如以下:

/**
* Split a region on the region server.
* 分裂RegionServer上的一个Region
*
* @param controller the RPC controller
* @param request the request
* @throws ServiceException
*/
@Override
@QosPriority(priority=HConstants.ADMIN_QOS)
public SplitRegionResponse splitRegion(final RpcController controller,
final SplitRegionRequest request) throws ServiceException {
try { // 检測RegionServer状态
checkOpen();
// 请求计数器加1
requestCount.increment();
// 获取相应的Region
HRegion region = getRegion(request.getRegion());
// Region上开启一个SPLIT_REGION操作
region.startRegionOperation(Operation.SPLIT_REGION);
LOG.info("Splitting " + region.getRegionNameAsString()); // Split開始时间
long startTime = EnvironmentEdgeManager.currentTime(); // 先调用flushcache()将Region上的memstore刷新
HRegion.FlushResult flushResult = region.flushcache();
if (flushResult.isFlushSucceeded()) {
// flush成功的话,RegionServer记录flush耗时
long endTime = EnvironmentEdgeManager.currentTime();
regionServer.metricsRegionServer.updateFlushTime(endTime - startTime);
}
byte[] splitPoint = null;
// 假设请求中有切分点,则取请求中的切分点
if (request.hasSplitPoint()) {
splitPoint = request.getSplitPoint().toByteArray();
} // 设置标志位splitRequest为tue,并设置明白的分裂点explicitSplitPoint
region.forceSplit(splitPoint); // 调用RegionServer上compactSplitThread的requestSplit()方法。请求分裂region
// 调用前,先调用Region的checkSplit()方法。确定能否够切分,并确定切分点
regionServer.compactSplitThread.requestSplit(region, region.checkSplit());
return SplitRegionResponse.newBuilder().build();
} catch (DroppedSnapshotException ex) {
regionServer.abort("Replay of WAL required. Forcing server shutdown", ex);
throw new ServiceException(ex);
} catch (IOException ie) {
throw new ServiceException(ie);
}
}

(四)利用Bulk载入HFile

Bulk是HBase直接载入HFile存储数据的一种快速、有用的手段或工具。在HRegion中的bulkLoadHFiles()方法中,也会调用flushcache()方法刷新HRegion上的MemStore内存。相关代码例如以下:

if (assignSeqId) {
FlushResult fs = this.flushcache();
if (fs.isFlushSucceeded()) {
seqId = fs.flushSequenceId;
} else if (fs.result == FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY) {
seqId = fs.flushSequenceId;
} else {
throw new IOException("Could not bulk load with an assigned sequential ID because the " +
"flush didn't run. Reason for not flushing: " + fs.failureReason);
}
}

(五)做Table的快照

在做表的快照时。会将相应Table中涉及到的Region的MemStore进行flush。做这项工作的是FlushSnapshotSubprocedure类中的RegionSnapshotTask。它在call()方法中。当snapshotSkipFlush为false时。会调用HRegion的flushcache()方法,对MemStore进行flush。相关代码例如以下:

 if (snapshotSkipFlush) {
/*
* This is to take an online-snapshot without force a coordinated flush to prevent pause
* The snapshot type is defined inside the snapshot description. FlushSnapshotSubprocedure
* should be renamed to distributedSnapshotSubprocedure, and the flush() behavior can be
* turned on/off based on the flush type.
* To minimized the code change, class name is not changed.
*/
LOG.debug("take snapshot without flush memstore first");
} else {
LOG.debug("Flush Snapshotting region " + region.toString() + " started...");
region.flushcache();
}

综上所述,凡是涉及到Region个数改变(比方两个变一个的合并、一个变两个的分裂、一个拷贝一个的快照等)。大都须要先flush掉其MemStore。可是有个疑问,为什么非要做flush呢?将内存中的数据也合并、分裂或者拷贝。不是更快吗?难道是由于其不稳定?留待以后再深入分析吧!

好了。MemStore的flush发起时机、推断条件等详情基本上就这些了。假设有遗漏的,或者解说不深入的,以后再慢慢深入吧!

HBase源代码分析之MemStore的flush发起时机、推断条件等详情的更多相关文章

  1. HBase源代码分析之MemStore的flush发起时机、推断条件等详情(二)

    在<HBase源代码分析之MemStore的flush发起时机.推断条件等详情>一文中,我们具体介绍了MemStore flush的发起时机.推断条件等详情.主要是两类操作.一是会引起Me ...

  2. HBase源代码分析之HRegionServer上MemStore的flush处理流程(一)

    在<HBase源代码分析之HRegion上MemStore的flsuh流程(一)>.<HBase源代码分析之HRegion上MemStore的flsuh流程(二)>等文中.我们 ...

  3. HBase源代码分析之HRegionServer上MemStore的flush处理流程(二)

    继上篇文章<HBase源代码分析之HRegionServer上MemStore的flush处理流程(一)>遗留的问题之后,本文我们接着研究HRegionServer上MemStore的fl ...

  4. HBase源代码分析之HRegion上MemStore的flsuh流程(二)

    继上篇<HBase源代码分析之HRegion上MemStore的flsuh流程(一)>之后.我们继续分析下HRegion上MemStore flush的核心方法internalFlushc ...

  5. HBase源代码分析之HRegion上MemStore的flsuh流程(一)

    了解HBase架构的用户应该知道,HBase是一种基于LSM模型的分布式数据库.LSM的全称是Log-Structured Merge-Trees.即日志-结构化合并-树. 相比于Oracle普通索引 ...

  6. HBase源代码分析

    http://www.docin.com/p-647062205.html http://blog.csdn.net/luyee2010/article/category/1285622 http:/ ...

  7. Hadoop源代码分析

    http://wenku.baidu.com/link?url=R-QoZXhc918qoO0BX6eXI9_uPU75whF62vFFUBIR-7c5XAYUVxDRX5Rs6QZR9hrBnUdM ...

  8. Hadoop源代码分析(完整版)

    Hadoop源代码分析(一) 关键字: 分布式云计算 Google的核心竞争技术是它的计算平台.Google的大牛们用了下面5篇文章,介绍了它们的计算设施. GoogleCluster:http:// ...

  9. 1、Hbase原理分析

    一.Hbase介绍 1.1.对Hbase的认识 HBase作为面向列的数据库运行在HDFS之上,HDFS缺乏随机读写操作,HBase正是为此而出现. HBase参考 Google 的 Bigtable ...

随机推荐

  1. 如何学好FPGA

    http://bbs.elecfans.com/jishu_278578_1_1.html 掌握FPGA可以找到一份很好的工作,对于有经验的工作人员,使用FPGA可以让设计变得非常有灵活性.掌握了FP ...

  2. 【Android】10.4 卡片视图

    分类:C#.Android.VS2015: 创建日期:2016-02-19 一.简介 Android 从5.0开始包含了一个全新的卡片视图小部件,这个新的小部件默认就像一张带有圆角和轻微阴影的白色卡片 ...

  3. Understanding The Linux Virtual Memory Manager

    http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.588.4660&rep=rep1&type=pdf http://e ...

  4. C#中的 .NET 弱事件模式

    引言 你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处 ...

  5. javascript 图片上传缩略图预览

    <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...

  6. AAuto 快速开发win32小程序

    AAuto编程语言 AAuto是专用于桌面软件快速开发的新一代混合型编程语言 -  具有动态语言轻便.灵活.快速开发的特性,而且又可以同时支持静态类型开发,象静态语言那样使用.AAuto可以直接支持原 ...

  7. js怎么让时间函数的秒数在页面上显示是变化的

    <input type="text" id="showtime" value="" /><script type=&quo ...

  8. Android开发:keytool' 不是内部或外部命令 也不是可运行的程序

    今天在更改keystore密码的时候,发生了这个问题:keytool' 不是内部或外部命令 也不是可运行的程序. 本来以为很简单觉得的问题,在网上搜索了一大堆答案,都不是我想要的,故在此记录下我的解决 ...

  9. ADO中记录集recordSet的使用

    _RecordsetPtr使用方法 _variant_t vUsername,vID,vname; //变量声明_RecordsetPtr m_pRecordset;     //记录集CString ...

  10. 贪心算法or背包问题

    贪心方法:总是对当前的问题作最好的选择,也就是局部寻优.最后得到整体最优. 应用:1:该问题可以通过“局部寻优”逐步过渡到“整体最优”.贪心选择性质与“动态规划”的主要差别. 2:最优子结构性质:某个 ...