剖析Jive的缓存机制

剖析Jive的缓存机制- -

                                      

作者:张磊 本文选自:开放系统世界 2003年06月06日

  Jive是一个广受欢迎的开放源码的论坛项目,虽然推出了很多年,但至今很多Java程序员还对它津津乐道。从框架结构上看,它采用了很多设计模式,如Factory模式、Proxy模式、Decorator模式、Iterator模式,使得程序易于扩展和移植。从设计细节上看,它采用了很多先进的设计思想和方法,如XML读写配置文件、数据库的缓存和连接池、帖子的过滤和TreeWalk遍历等,使得程序更加强健和高效。本文主要谈的是Jive缓存机制的实现。

  大家知道,在两个存取速度差距很大的对象(比如数据库和内存)之间,通常要加一个缓存来匹配二者的速度。因此,缓存机制在实际项目中还是经常遇到的。同样Jive也使用缓存来加快贴子的显示。如果试图编写一个类似的程序,不妨研究一下Jive源码,可能对你大有帮助。

  在Jive 2.1.2中,涉及Jive缓存机制的Java类大致可以分为以下四个部分(为了简化起见,本文只讨论帖子缓存机制的实现。用户名和权限的存取虽然也用到了缓存,但其实现机制与前者类似,因此不再赘述):

  第一部分,提供HashMap、LinkedListedlist等数据结构,以便实现缓存机制,其中HashMap是JDK提供的,其Key类型为Object。可以在com.jivesoftware.util包中找到这些数据结构。此部分包括Cache类、 LinkedList类、LinkedListNode类、Casheable接口、CacheObject类、CacheableBoolean类、CacheableInt类、CacheableLong类、CacheableLongArray类、CacheableString类、CacheSizes类、CacheTimer类。

  第二部分,提供LongHashMap、LongLinkedListedlist等数据结构以实现缓存机制。与第一部分不同的是,它的HashMap是自己编写的,其Key为Long型,因此被冠以LongHashMap的名称。同样可以在com.jivesoftware.util包中找到它们。该部分包括LongHashMap类、LongCache类、 LongCacheObject类、LongLinkedList类和LongLinkedListNode类。还有第一部分中的Casheable接口,它的各种数据类型的实现、CacheSizes类和CacheTimer类,也可归于这部分。它们可看作是第一部分和第二部分的交集。

  第三部分,调用底层数据结构以提供论坛对象的缓存。可以在com.jivesoftware.forum.database包中找到这些底层数据结构。该部分包括的类主要有DatabaseCacheManager类、DbForumFactory类、DbForum类、DbForumThread类、DbForumMessage 类、DatabaseCache类、ForumCache类、 ForumThreadCache类和ForumMessageCache类;

  第四部分,向Jsp页面提供访问接口,同样可以在com.jivesoftware.forum.database包中找到这些接口。该部分包括的类有ForumThreadBlockIterator类和ForumMessageBlockIterator类,第三部分的DbForum类、DbForumThread类和DbForumMessage 类也可以包括进来。实际上,这三个类是第三部分和第四部分联系的纽带。在com.jivesoftware.util包中还有一个LongList类,它用来将ForumThreadBlockIterator类和ForumMessageBlockIterator类转化成Long型数组,因此也应算在这部分。

  从上面介绍可看出,缓存机制也可以划分为三层,即第一和第二部分的底层数据结构,第三部分的中间层和第四部分的上层访问接口,下面分别讨论它们。

底层数据结构

  Jive缓存机制的原理其实很简单,就是把所要缓存的对象加到HashMap哈希映射表中,用两个LinkedListedlist双向链表分别维持着缓存对象和每个缓存对象的生命周期。如果一个缓存对象被访问到,那么就把它放到链表的最前面,然后不定时地把要缓存的对象加入链表中,把过期对象删除,如此反复。实际上比较第一和第二部分就可以发现,它们的代码几乎完全相同。差别就在第二部分的哈希映射表没有采用JDK提供的类,而是采用了作者自己编写的一个类,将原来哈希映射表的Key类型由Object改为Long。这样做虽然在一定程度上加快了缓存的速度,并减小了缓存的大小,但无形之中也减低了程序的稳定姓和可读姓,因此不推荐仿效。值得一提的是,在Jive 1.0.2版中,所有Forum、Thread、Message的ID和它们内容的缓存都是用第一部分的Java类实现的。它在升级到后面的版本时,其内容采用了第二部分的Java类实现,但其ID仍用第一部分的Java类实现,这是Jive中值得注意的一个地方。下面先来看第一部分的Java类实现。LinkedListNode类的源码为:



public class LinkedListNode {

public LinkedListNode previous;

public LinkedListNode next;

public Object object;

public long timestamp;

public LinkedListNode(Object object,

  LinkedListNode next,LinkedListNode previous){

this.object = object;

this.next = next;

this.previous = previous;

}

public void remove() {

previous.next = next;

next.previous = previous;

}

public String toString() {

return object.toString();

}

}



  很明显,这是一个双向链表的节点类,previous、next分别记录前后节点的指针,object用于记录所需缓存的对象,timestamp用于记录当前节点被创建时的时间戳。当该时间戳超过该节点的生存周期时,它就会被remove()方法删除掉。该类主要完成的功能就是由LinkedListNode构成LinkedList链表,而由LinkedList类实现getFirst()、getLast()、addFirst()、addLast()、clear()等链表的基本方法。

  再来看Cacheable接口和它的一个实现类CacheableInt的源码:



public interface Cacheable {

public int getSize();

}

public class CacheableInt implements Cacheable {

private int intValue;

public CacheableInt(int intValue) {

this.intValue = intValue;

}

public int getInt() {

return intValue;

}

public int getSize() {

return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt();

}

}



  从上面的代码可以看到,Cacheable接口只有一个方法getSize()。它要求所有继承类实现该方法,并输出占用缓存的大小,以便实施管理。那么为什么CacheableInt. getSize()得到的是sizeOfObject()+sizeOfInt()呢?因为任何类都继承自Object,计算空间时当然也要把它算上了。

  还有一个CacheObject类,它是缓存的基本元素,来看一下它的代码:



public final class CacheObject {

public Cacheable object;

public int size;

public LinkedListNode lastAccessedListNode;

public LinkedListNode ageListNode;

public CacheObject(Cacheable object, int size) {

this.object = object;

this.size = size;

}

}



  lastAccessedListNode记录着一个缓存节点的Key值,是构成lastAccessedList链表的基本元素,在lastAccessedList链表中,经常被访问到的节点总是在最前面。ageListNode记录着缓存节点的加入时间,是构成ageList链表的基本元素。而ageList链表是按时间先后排序,先加入的节点总是在最后面。lastAccessedListNode和ageListNode本来可以分写成两个类,毕竟lastAccessedListNode并不需要ageListNode的成员变量timestamp,但是为了简化程序,Jive把它们写成了一个类。这也是值得注意的一个地方。

  现在来看缓存机制中最关键的一个类Cache的部分代码,其中主要是add()和get()方法。有关这两个方法的介绍请参考代码中的注释。



public class Cache implements Cacheable {

protected static long currentTime = CacheTimer. currentTime;

protected HashMap cachedObjectsHash;

protected LinkedList lastAccessedList;

protected LinkedList ageList;

//缓存元素的最大尺寸128KB,可修改

protected int maxSize = 128 * 1024;

//整个缓存的大小

protected int size = 0;

//缓存元素的最大保存时间,用Cache(long maxLifetime)初始化

protected long maxLifetime = -1;

//记录cache的命中次数和未命中次数

protected long cacheHits, cacheMisses = 0L;

......

//向哈希表中添加一个关键字为Key的缓存对象object

public synchronized void add(Object key, Cacheable object) {

//先把原来的对象remove掉

remove(key);

int objectSize = object.getSize();

//如果对象太大,则不加入缓存

if (objectSize > maxSize * .90) {

return;

}

size += objectSize;

//新建一个缓存对象,并放入哈希表中

CacheObject cacheObject = new CacheObject(object, objectSize);

cachedObjectsHash.put(key, cacheObject);

// 把缓存元素的Key放到lastAccessed List链表的最前面

LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);

cacheObject.lastAccessedListNode = lastAccessedNode;

//把缓存元素的Key放到ageList链表的最前面,并记下当前时间

LinkedListNode ageNode = ageList.addFirst(key);

ageNode.timestamp = System.currentTimeMillis();

cacheObject.ageListNode = ageNode;

// 在cullCache()中,先调用deleteExpiredEntries()把过期对象删掉,

如果缓存还是太满,则调用 remove(lastAccessedList.getLast().object)把

lastAccessedList中不常访问的对象删掉

cullCache();

}

//在哈希表中得到一个关键字为Key的缓存对象object

public synchronized Cacheable get(Object key) {

// 清理过期对象

deleteExpiredEntries();

CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);

if (cacheObject == null) {

//没找到则未命中次数加一

cacheMisses++;

return null;

}

//找到则命中次数加一

cacheHits++;

//将该缓存对象从lastAccessedList链表中取下并插入到链表头部

cacheObject.lastAccessedListNode.remove();

lastAccessedList.addFirst(cacheObject.lastAccessedListNode);

return cacheObject.object;

}

}201282.gif

201283.gif

    图二、Jive缓存机制的中间层

  图2中Forum表示论坛,Thread表示论坛贴子的线索,Message表示论坛贴子,它们的关系是:Forum包括数条Thread,Thread包括数条Message。

  由图2可见,DbForum类、DbForumThread类和DbForumMessage类的实例对象都包含一个 DbForumFactory类的实例对象factory。DbForum类、DbForumThread类和DbForumMessage类被DbForumFactory生产出来,同时它们也通过DbForumFactory来访问缓存。而在DbForumFactory中则包含一个DatabaseCacheManager类的实例对象cacheManager。它负责管理所有的缓存对象,这些缓存对象就是ForumCache类、ForumThreadCache类和ForumMessageCache类的实例。ForumCache类、 ForumThreadCache类和ForumMessageCache类继承自同一个抽象类DatabaseCache,而在DatabaseCache类中,有一个LongCache型的成员变量cache。这样中间层就和低层的数据结构结合起来了。

  现在以thread线索对象的获得为例,说明中间层是如何运作的。请看代码摘要:

[color=#336600]

DbForum.java

public class DbForum implements Forum, Cacheable

{

......

public ForumThread getThread(long threadID)throws

  ForumThreadNotFoundException{

return factory.getThread(threadID, this);

}

......

}

DbForumFactory.java

public class DbForumFactory extends ForumFactory {

......

protected DbForumThread getThread(long threadID, DbForum forum)throws

    ForumThreadNotFoundException{

DbForumThread thread = cacheManager.threadCache.get(threadID);

return thread;

}

......

}

ForumThreadCache.java

public class ForumThreadCache extends DatabaseCache {

......

public DbForumThread get(long threadID)throws

  ForumThreadNotFoundException{ 

//缓存中寻找以threadID为编号的DbForumThread对象

DbForumThread thread = (DbForumThread)cache.get(threadID);

if (thread == null) {

  //如果在缓存中找不到该对象

//新建一个以threadID为编号的DbForumThread对象

thread = new DbForumThread(threadID, factory);

//将新建对象加入缓存

cache.add(threadID, thread);

}

return thread;

}

......

}

DbForumThread.java

public class DbForumThread implements ForumThread, Cacheable {

......

protected DbForumThread(long id, DbForumFactory factory)throws

    ForumThreadNotFoundException{

this.id = id;

this.factory = factory;

//读取数据库,其中id对应数据库中的jiveThreadProp表中的threadID字段

loadFromDb();

isReadyToSave = true;

}

......

}



  从上面的代码可以看到,当调用DbForum类 的getThread(long threadID)方法获得一个编号为threadID的线索对象时,实际上调用的是DbForumFactory类中的getThread(long threadID, DbForum forum)方法,而GetThread方法则是调用ForumThreadCache类的get方法来完成任务的。ForumThreadCache类里get(long threadID)方法则根据threadID到缓存中找相应的线索对象,如果缓存中有该对象就直接得到,没有则新建一个DbForumThread对象,再把该对象放入缓存中。看到这里也许有人会奇怪,好像程序中根本没有连接数据库的语句。我们可以从DbForumThread类的代码中找到答案。原来Jive在新建一个DbForumThread对象时,就已经用loadFromDb()方法把数据读出来了。另一方面,如果在缓存中找到了DbForumThread对象,程序根本就不会新建DbForumThread对象,因而就好象没有数据库的操作,这实际上就是通过缓存机制所要达到的目的。

  Message帖子对象的获得与Thread对象的获得类似,因此就不再重复了。从上面介绍可以看出,只要得到论坛线索的编号threadID,就可以得到对应的线索对象,不管它是从缓存中来,还是从数据库中来。那么threadID是如何从Jsp页面传到中间层的呢?让我们来看上层访问接口的运行机制吧。

上层访问接口

  上层访问接口的主要功能是连接JSP页面和中间层。换句话说,就是把JSP页面中要调用的Thread、Message对象的ID传递到中间层。下面给出访问Thread相关类的类图(访问Message机制图类似,故省略),见图3。其中的forum.jsp是显示论坛内容的页面。在这里,我们把forum.jsp看成是一个特殊的类,它里面有一个ForumThreadIterator类的实例变量threads和DbForum类的实例变量forum,故它和ForumThreadIterator类及DbForum类的关系应是关联关系。

201284.gif

  图三、Jive缓存机制的上层访问接口

  先来看forum.jsp和DbForum 类的部分代码:



forum.jsp

DbForum.java

public class DbForum implements Forum, Cacheable {

......

public ForumThreadIterator threads(ResultFilter resultFilter) {

//生成SQL语句

String query = getThreadListSQL(resultFilter, false);

//得到threadID块

long [] threadBlock = getThreadBlock(query.toString(),

resultFilter.getStartIndex());

......

//返回ForumThreadBlockIterator对象

return new ForumThreadBlockIterator(threadBlock, query.toString(),

startIndex, endIndex, this.id, factory);

}

protected long[] getThreadBlock(String query, int startIndex) {

int blockID = startIndex / THREAD_BLOCK_SIZE;

int blockStart = blockID * THREAD_BLOCK_SIZE;

String key = query + blockID;

//根据Key的值到缓存中取得ThreadID的数组

CacheableLongArray longArray =(CacheableLongArray)threadListCache.get(key);

//在缓存中则返回

if (longArray != null) {

long [] threads = longArray.getLongArray();

return threads;

}

// 否则到数据库中取ThreadID的块,以数组形式返回

else {

LongList threadsList = new LongList(THREAD_BLOCK_SIZE);

Connection con = null;

Statement stmt = null;

...数据库操作 ...

}

long [] threads = threadsList.toArray();

//将 ThreadID的块加入缓存

threadListCache.add(key, new CacheableLongArray(threads));

return threads;

}

......

}



  在forum.jsp中有一个ResultFilter类的实例resultFilter。它给出页面显示Thread的起始位置和数量,并作为参数传入forum.threads()中,用于构造相关的SQL语句。当调用forum.threads(filter)时,程序将生成的SQL语句传入到getThreadBlock()方法中得到一个threadID的块,也就是一组threadID。之所以要读threadID块,是因为显示论坛时并不是显示一条线索就行了,而是一下显示十几条。这样做可以避免反复读数据库,而且threadID不是thread对象,并不占太大空间。

  应该说使用了块以后,减轻了数据库的访问量,因而论坛的效率有了很大的提高。不仅如此,Jive又把块放入了缓存中。在getThreadBlock()方法里,Jive用Cache类的实例对象threadListCache来缓存threadID块,而关键字就是SQL语句加上blockID。也就是说,只要SQL语句和blockID相同,就可以在缓存中取出相同的threadID块。当然,缓存中找不到,还是要到数据库中读出来加入缓存的,这样论坛的效率又得到了进一步的提升。

  ForumThreadBlockIterator类继承自ForumThreadIterator抽象类,而ForumThreadIterator类又实现了Iterator接口,因此得到ForumThreadBlockIterator的实例对象threads后,就可以在用threads.next()方法对它进行编历了。ForumThreadBlockIterator类的功能就是逐个读取ThreadID,然后根据ThreadID返回Thread对象,由此上层访问接口就和中间层衔接起来了。

  Jive的缓存机制值得学习的地方有很多,比如读取线索时不是读一条而是读一个block;显示线索的起始位置和数量用专门的一个类来管理,并且动态生成SQL语句;用一个专门的类来负责管理缓存;把论坛缓存对象的功能抽象出来形成一个缓存的抽象类DatabaseCache,让它去跟低层数据结构联系起来等。这些都体现了面向对象的设计原则,即提高软件的可维护姓和可复用姓。

  同时,Jive也告诉我们,要想编好程序,只懂条件语句和循环语句可不行,还要必须选择好的数据结构,掌握好的面向对象的设计原则,熟悉设计模式思想方法,这样才能编写出强壮、高效的代码。

转载于:https://www.cnblogs.com/sunsonbaby/archive/2004/09/17/44072.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/436694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

主题:的中间层框架 第一节

主题:<插件式>的中间层框架 时间:2004-12-31 15:00 主讲:D10.天地弦 2004-12-31 14:56:15 /:>D10.天地弦(185511468) 开始吧&#xff0c;我们 2004-12-31 14:57:22 /:>D10.天地弦(185511468) 请大家先看图 http://www.jqk.net.cn/dkFrameworkWeb/dkFramework_fi…

qt5.11 linux oracle,Qt5.11.1 + VS2017环境搭建(Qt5.11在windows上的安装)

安装视频&#xff1a;《Qt5.11在windows‘上的编译安装》安装VisualStudio2017VS2017安装包 提取码&#xff1a;7db11&#xff0c;安装vs2017请确保勾选了“通用Windows平台开发”组件&#xff0c;否则VS2017将无法用来进行开发右侧的安装详细信息部分也尽量与上图一致。安装完…

【转】Dynamics 365中的事件框架与事件执行管道(Event execution pipeline)

本文介绍了Microsoft Dynamics 365&#xff08;以下简称D365&#xff09;中的两个概念&#xff0c;事件框架&#xff08;Event Framework&#xff09;与事件执行管道&#xff08;Event execution pipeline&#xff09;。 本文适用于&#xff1a;Applies To: Dynamics 365 (onl…

【转】ABP源码分析二十:ApplicationService

IApplicationService : 空接口&#xff0c;起标识作用。所有实现了IApplicationService 的类都会被自动注入到容器中。同时所有IApplicationService对象都会被注入一些拦截器&#xff08;例如&#xff1a;auditing, UnitOfWork等&#xff09;以实现AOP AbpServiceBase&#xf…

【转】ADFS 概念与基本开发介绍

&#xff08;如您转载本文&#xff0c;必须标明本文作者及出处。如有任何疑问请与我联系 menap7.com&#xff09; ADFS 相关开发技术的中文资料相对匮乏&#xff0c;之前在弄这个东西的时候搞的比较辛苦&#xff0c;因此总结此文档&#xff0c;以解后人之忧。 本文会首先介绍…

【转】使用FiddlerCore来测试WebAPI

大家在调试Web相关的API时&#xff0c;经常会用Fiddler来查看相关的请求&#xff0c;以及返回结果。当然你也可以尝试修改或者重复发送你的请求信息。本文主要介绍如何使用代码来实现fiddler的功能。 Fiddler Core API Fiddler Core几乎实现了你能用fiddler做的所有功能。直接…

【转】程序在内存中的分布

前些天学习到了程序在虚拟内存中分布的一些知识点&#xff0c;结合在网上查阅的一些资料&#xff0c;整理一下知识点。本博客参考博主 hackbuteer1的《程序在内存中的分布》这篇文章。 v 在现代的操作系统中&#xff0c;当我们说到内存&#xff0c;往往需要分两部分来讲&#x…

GARFIELD@04-02-2005

treat 转载于:https://www.cnblogs.com/rexhost/archive/2005/04/02/130985.html

【转】TechEd第一课:新一代关系管理系统XRM**

提起微软的CRM产品&#xff0c;最先想到的是CRM 4.0。除了字面理解上的客户关系管理系统外&#xff0c;微软也赋予这款产品“应用开发平台”的角色&#xff0c;并给它一个新名字——XRM。XRM到底是什么东西&#xff1f;它能用来做什么&#xff1f;这些问题都在今天这节“微软新…

图形工具包 linux,GTK 4.0图形工具包正式发布:时隔四年的重大版本!

GTK是用于创建图形用户界面的工具包&#xff0c;GTK提供了一整套的小部件&#xff0c;适用于从小型一次性工具到完整的应用程序套件的项目。GTK是GNOME开发平台的核心&#xff0c;但是它也可以用于编写其他Linux环境的应用程序&#xff0c;以及针对微软Windows和苹果macOS的应用…

linux设备驱动学习,linux设备驱动学习4

Linux设备驱动程序学习(4)-高级字符驱动程序操作&#xff3b;(1)ioctl and llseek&#xff3d;今天进入《Linux设备驱动程序(第3版)》第六章高级字符驱动程序操作的学习。一、ioctl大部分设备除了读写能力&#xff0c;还可进行超出简单的数据传输之外的操作&#xff0c;所以设备…

[代码阅读] ECS toString实现方法

引言 ECS 提供了一种编程方式来生成以不同标记语言编写的文档。它设计为通过面向对象的抽象来生成所有标签。 ECS 目前版本为1.4.2 &#xff0c;支持 HTML 4.0 和 XML 。 因为工作原因&#xff0c;作者粗略读了ECS的部分原代码&#xff0c;着重了解ECS如果通过toString方法…

【转】设备数据通过Azure Functions 推送到 Power BI 数据大屏进行展示

设备数据通过Azure Functions 推送到 Power BI 数据大屏进行展示&#xff08;1.准备工作&#xff09; 原创 Sean Yu 云计算实战 2019-12-06 本案例适用于开发者入门理解Azure Functions/ IoT Hub / Service Bus / Power BI等几款产品。 主要实战的内容为&#xff1a; 将设备遥…

【转】Azure Messaging-ServiceBus Messaging消息队列技术系列1-基本概念和架构

前段时间研究了Window Azure ServiceBus Messaging消息队列技术&#xff0c;搞了很多技术研究和代码验证&#xff0c;最近准备总结一下&#xff0c;分享给大家。 首先&#xff0c;Windows Azure提供了两种类型的消息队列机制&#xff1a;Azure Queues和ServiceBus Queues。 其…

Atlas应用程序调试技巧

本文为翻译文章&#xff0c;原文地址&#xff1a;http://atlas.asp.net/docs/Overview/debug.aspx “Atlas”程序由服务器端代码和客户端代码组成&#xff0c;并且&#xff0c;浏览器可能会要去异步请求一些数据。那么&#xff0c;怎样才能Debug这样的web程序呢。本文将告诉…

【转】Azure Messaging-ServiceBus Messaging消息队列技术系列2-编程SDK入门

各位&#xff0c;上一篇基本概念和架构中&#xff0c;我们介绍了Window Azure ServiceBus的消息队列技术的概览。接下来&#xff0c;我们进入编程模式和详细功能介绍模式&#xff0c;一点一点把ServiceBus技术研究出来。 本章我们主要介绍ServiceBus的编程SDK编程入门。 首先…

linux octave源码安装,在Linux操作系统上安装Octave的方法

本文介绍在Ubuntu、Debian、Arch Linux、Fedora、OpenSUSE操作系统上安装Octave(也称GNU Octave)的方法&#xff0c;它还支持Flatpak方式安装。简介GNU Octave是一种用于科学和数学计算操作的解释性命令语言&#xff0c;它与Matlab兼容&#xff0c;支持各种扩展&#xff0c;允许…

linux安装静默安装was7,WAS7.0 - 安装并升级WAS7.0.0.31(静默安装)

WAS7.0 - 安装并升级WAS7.0.0.31(静默安装)安装之前需要准备的文件&#xff1a;C1G35ML.tar.gz&#xff1a;WAS7.0安装包C1G36ML.tar.gz&#xff1a;WAS升级工具7.0.0-WS-WASSDK-LinuxX64-FP0000031.pak 7.0.0-WS-WAS-LinuxX64-FP0000031.pak &#xff1a;7.0.0.31升级包&…

【转】Azure云存储及存储账户概述***

本文介绍下Azure云存储及存储账户。 1、什么是微软Azure云存储 微软Azure云存储是微软提供的云端数据存储服务&#xff0c;是一种可伸缩、持久化的数据存储服务&#xff0c;可以在Internet上被访问并按实际存储容量收费。它也可以被REST API访问&#xff0c;可以保存Azure云端…

插入法排序c语言程序,插入排序算法及C语言实现

插入排序算法是所有排序方法中最简单的一种算法&#xff0c;其主要的实现思想是将数据按照一定的顺序一个一个的插入到有序的表中&#xff0c;最终得到的序列就是已经排序好的数据。直接插入排序是插入排序算法中的一种&#xff0c;采用的方法是&#xff1a;在添加新的记录时&a…