当前位置: 首页 > 编程日记 > 正文

坑系列 --- 时间和空间的平衡

这是系列的最后一弹了,这篇文章非常长,希望你能看完,要是看完有很酣畅的感觉就最好了。这一篇的主要来说说架构中时间和空间的平衡吧,这里的时间指代比较广,可能是开发时间,但大部分指的是执行时间,也就是算法的时间复杂度了,而空间就是算法中经常说的空间换时间中的空间了,一个好的系统,设计出来必然是各种时间复杂度空间复杂度平衡出来的结果,架构设计的过程,并不仅仅是模块的堆叠,在走到岔路口的时候,更多的是时间和空间平衡之后选的一个技术方案,这一篇,我会用一个搜索提示服务设计的实际例子,来说一下架构设计的过程中,时间和空间的各种矛盾,怎么分析,怎么选择,最后淌过这些时空的坑

0. 搜索提示是什么

搜索提示是搜索引擎的重要组成部分,虽然一般是作为一个单独的服务来对外提供服务,但在一个搜索系统中,搜索提示是非常重要的组成部分,我还没看到哪个比较成熟的搜索引擎没有搜索提示功能的。

首先,我们看看搜索提示是什么,大家肯定都用过,就是下面这些个东西

1. 搜索提示的场景和目的

搜索提示一般情况下是为了提高用户的搜索体验,更快的选择合适的搜索词,提高检索的效率的,但是因为搜索框的流量实在是太大了,所以搜索提示也扮演着广告变现的责任,互联网嘛,有流量就有变现,比如下面这个图,明显就是一个广告啦。

图片描述

2. 初步技术选型

2.1 搜索提示的需求

要实现一个搜索提示系统,首先需要确定的是需要提示出来什么东西,有两种提示方式。

  • 一种是提示出其他的搜索词,这也是大部分的搜索提示所做的,提示出其他用户的类似搜索词。

  • 还有一种是提示出现有的结果集有的东西,这种实现方式比较少见,比如一个生鲜类的电商网站,商品数量比较少,那么没必要去提示一些用户的搜索词,直接把商品名称(比如苹果,桃子,橘子)提示出来就行了,这种提示方式我们这里不讨论,因为实现起来比较简单。

2.2 技术栈

既然知道需求了,那么开始选择技术栈了。

  • 首先,既然有其他用户的搜索词,那么必然有一个离线的数据收集和处理的系统来完成其他用户的搜索日志处理,生成需要的数据。

  • 其次,需要一个单独的API服务,来提供搜索提示的功能,输入为不完整的搜索词,输出为根据这个搜索词提示出来的其他搜索词,检索方式的话,一般都是使用前缀匹配的方式了,这个大家都比较认可。

  • 最后,需要前端有个js代码来实时调用后台的API,这个不在我们的讨论范围内。

整个系统的结构图应该是下面这个样子,离线模块处理完日志数据以后,推送到API模块中,给前面的前端提供服务。

图片描述

好了,框框设计好了。也就是架构图完成了哦,真是牛逼的架构啊,三个框,离线,在线,前端全齐了。

接下来,我们来看看在线API部分的设计吧,我们先假设离线数据都已经准备好了,就是一堆用户的搜索词,如何快速的前缀匹配这些词就成了API设计部分的关键了,有这么几种实现方式。

  • 粗暴的短平快方式

用redis保存所有信息,每条信息类似
{KEY:北 VALUE:北京,北京大学,北大,北京遇上西雅图}
{KEY:北京 VALUE:北京,北京大学,北京遇上西雅图}....
每次来了请求的话,直接查询redis给出结果返回,就是占点空间,最好还需要一台单独的服务器。

  • 优雅点的实现方式

前缀匹配嘛,最先想到的数据结构就是Trie树了,所以所有的Key可以用Trie树来保存和检索,速度也挺快的,而且空间占用比较少。

  • 复杂点的实现方式

既然是检索嘛,就直接用搜索引擎的倒排索引技术来实现嘛,速度也够,而且数据量也可以支持得很大。

3. 时间与空间的平衡一

实际工程应用中,这三种实现方式我都见过,而且有些实现方式是把这三种结合起来使用了,后面的文章我会说到。

具体使用哪一种需要看你的实际场景,这三种实现方式差不多正好对应三种场景。

  • 如果你是个小型的电商或者论坛之类的,每天的搜索量也不是很大,而且在可见的未来也不会变得很大,而且也不差钱,那么直接第一种,说不定一天就能撸出来,速度还不错,但是这种有一些缺陷,首先,value值不能太复杂,影响效率,所以可扩展性不是很强,而现在的电商搜索提示中往往还有很多其他信息需要保存,redis作为缓存服务器提供高并发服务的前提是数据量比较小,最好在2K以内,这样的话用redis就有点不合适了。这种方案是个存空间的选择了,用空间换取了检索时间和开发时间,多亏有redis这种神器。

  • 如果是个大型的搜索引擎或者电商,搜索日志已经是巨量了,而且搜索词多种多样,那么第三种倒排索引技术为基础的实现方式可能是更好的选择,而且既然是大搜,技术都是现成的,索引分片,集群都是现成的,直接改了上就是。这种方式用长期的开发时间和检索速度上稍微的降低换取了内存空间,如果从头开始做的话,时间成本比较高。

  • 大部分时候,第二种实现方式是大家都采用的方式,首先没有第一种那么粗暴,并且能完成方案一的所以功能,单机就能达到较好的效果,也不用索引分片,也不用集群,所以工程复杂性不是很高,也能在较短的时间内实现出来。其次第二种方案可扩展性较强,后面挂个倒排文件就可以变成简化版的第三方案。这种方式用算法换取了内存空间,用O(n)替代了O(1),换取了内存空间,也是标准的计算机领域的时间换空间了。

通过一番分析下来,决定使用第二种实现方式,就是Trie树的方式了,好了,API的基本选型确定了,那么开始设计,准备写代码吧。

4. Trie树的多种结构

既然确定了Trie树的实现方式,那么首先要了解一下Trie树吧,以及Trie树的各种结构,看看具体用哪个吧。

4.1 基本Trie树

Trie树又叫字典树,本质上是一个多叉树,每一个节点就是一个多叉的结构,如果是英文的匹配,那么是一个26叉树,每个节点一个26长度的数组,每个节点的数据结构如下

type TrieNode struct{flag       bool     //是否是一个完整的词hasNext bool    //是否还有后继字符nexts    [26]*TrieNode
}

Trie树画出来就是下面这个样子。

图片描述

从画出来的图,很直观的可以看出来这棵树的构造方法和遍历方法,如果是纯英文的话,每个节点都有一个26长度的数组,来了一个字符,通过字符的编号直接就可以遍历到下一个节点,查找的时候复杂度就是O(K),K表示查找的字符串长度,这种数据结构简单明了,实现起来也很容易。

4.2 优化后的Trie树

基本Trie树的数据结构有个问题,就是内存使用得太多了,如果是中文查找的话,需要把所有的中国字都编号到这个数组中,内存就爆了,于是有一种优化方法,就是把数组变成变长的,这种Trie树的节点数据结构变成下面的样子了,节点查找变成一个顺序查找或者二分查找了。

type TrieNode struct{flag       bool     //是否是一个完整的词hasNext bool    //是否还有后继字符nexts    []*TrieNode //变成变长数组了
}

4.3 双数组Trie树

所谓双数组Trie树,当然就是通过两个数组来实现这棵树了,这两个数组分别叫base数组check数组,一个是基础数组,一个是检查数组。

Trie树实际上是一种有限状态机,通过状态转移矩阵在各个状态之间跳转,双数组Trie树极大的节省了空间,大致就是下面这个样子,我后面会有一篇专门的文章来说Trie树实现的,这里就不详细展开了,实在等不及的可以自己先搜索一下相关资料看看双数组Trie树吧。

图片描述

5. 时间与空间的平衡二

OK,三种Trie树的实现方式都说了,现在要开始抉择了,我们先看看这三种数据结构的时间和空间。

第一种空间占用大,特别是中文的情况,检索的时间效率为O(n),其中n为每次请求的字符串的长度,这种实现方式基本上属于新人练手的水平,纯粹为了了解这个数据结构或者大学生做做课程设计,工程化的可能性几乎为0。

第二种空间基本不浪费,但检索的时间效率如果按照二分进行每个节点的查找的话,每个节点的查找时间变成了O(lg(n)),整体的查找时间变成K*O(log(n)),同样插入效率也变低了。

第三种情况空间不浪费,时间效率也为O(n)。

初看,肯定选第三种了,但是!!第三种实现方式有个致命的缺陷,就是无法向下遍历(具体可以自己看看双数组的实现方式),也就是说我输入北京,找不到北京大学,北京爱上西雅图,因为它已经不是一个树型结构了,无法向下遍历了。所以如果不对第三种结构进行改造的话,是无法满足我们的功能的。

要改造,最简单的办法就是在每个词后面挂一个链表,表示这个词的后继词都是什么,像下图这样。

图片描述

如果按上图那么来的话,需要辅助的空间来存储后继词,那么问题又来了,又是一次时间和空间的抉择了,是选择K*O(log(n))的第二种方案,然后后继词实时遍历树来获取(又要耗费一定的时间),还是选择选择第三种方案,用空间换取时间呢?

好,既然这样,我们来仔细算算这个账,我们以每个节点都存一个中文来算,虽然常用的汉字大概2500个,但其中最常用的才500左右。

先看第二种方案,那么我们大概估算出,每个节点的平均数组长度大概600(实际上除了第一层的节点,后面的节点数组长度完全达不到这个量级,用600属于极限估算了),600的二分查找大约需要7到8次,取个平均值4次,那么每次查询的时间就是4*K(K是字符串的长度),如果我们定好最长的提示词不超过8个字(太长也没意义),那么首先这个树的高度就是8了,如果50万的词量的话,使用多少内存大概能算出来,然后每次遍历下级节点的时间就是600^(8-K)(如果数组的每个元素都有值),我去,这么大,吓死了,好,我们即便假设每个节点的数组长度平均为60,要遍历完也要60^(8-K),也吓尿了,所以实时遍历所有子节点的方式不可取,而且后继词最多也就提示出10个,遍历出这么多词还要排序,遍历全部节点实在是没有必要,所以,第二种方案要么放弃,要么也要改造,如何改造呢?

因为词基本上都是离线算好的,稍微把节点的数据结构优化一下,在节点中加一个字段,表示哪个子节点有需要的数据(排序前10的词),这样往下遍历的时候就直接遍历相应的下标就可以了,就能把60^(8-K)这种遍历减少到几十次,从而找到10个提示词,我们把这个结构叫二次优化的Trie树

这一轮的时间和空间的比拼,第三个方案感觉就要胜利了,但第二个方案的优化版貌似也还能接受,一个耗费空间,查询速度快,一个节省空间,查询速度慢点。

这里多说一下,其实上面只是预估的办法比较搓,这么写是为了说预估的技能,最直接的就是拿着日志统计一遍,得到一堆不超过8位长度的搜索词,同时也能算法两个方案的内存使用规模和大概的查找效率,这样的预估办法最准确,但是在大部分时候我们并没有这么多数据,所以只能做一些基本的预估。

6. 离线数据处理

好了,我们先把检索部分放一放,来看看离线数据处理部分吧。我们先要确定一下什么东西需要在离线部分算好,什么东西需要在线处理?

  • 首先,日志的清洗肯定是离线部分了,我们先要把没有搜索结果的词去掉,然后去掉太长的词(假定超过8的都不要),然后保留有一定热度的词(比如每天搜索量超过10次的词),等等一些规则以后,假如剩下了50万的词,那这50万就是我们的基础数据了。

  • 其次,Trie树的构建是离线构建好还是实时往服务推送由服务端去构建呢?

  • 还有,排序的时候是离线给每个搜索词打个分,然后实时排序呢?还是离线把序都排好,服务端直接使用结果呢?

7. 时间与空间的平衡三

虽然是离线处理,但一样有时间和空间的选择。

我们先来看构建部分,Trie树的构建是离线构建好还是实时往服务推送由服务端去构建,首先我们需要确定的是这个搜索提示服务需不需要实时更新,一般情况下,搜索提示没有那么强的实时性要求,一般一天或者两天更新一次体验也不会太差,所以做实时更新的搜索提示,要不就是你实在是太蛋疼了,要不就是遇到了一个特别让人蛋疼的产品经理(卧槽,黑了一下产品经理啊)。所以我们使用离线构建的方式构建好两个数组和辅助的数据结构,都存在磁盘上,服务端启动的时候读取文件就行了,这是用离线时间换取的服务端的时间,是很划得来的。

再来看看排序的部分,很明显,排序离线做好也比较合适,排序的位置基本不会有太大的变化,但是如果排序离线做好的话,那么辅助的数据结构就会比较大了,因为每个前缀后面跟着的10个词都要排好序放在辅助结构中,但如果我们只是把每个词打个分(比如就按热度给个分),然后用第二个方案(优化的Trie树)的存储方式,在线的时候去排序,那么辅助结构就会小很多,两种情况的结构大概就是下面这样的区别。

图片描述

左边的是全排序好了的,直接使用,双数组Trie树+辅助结构方式;右边的是只是打了分的,优化的Trie树,遍历出结果以后实时排序的。

离线排序的空间占用大,即便优化一下,把词都放一个地方单独存着,辅助结构中只保存词的编号,一样也比较占地方,但是查询速度快啊。在线排序的方式不怎么占地方,就是每个节点多了一个分数的字段,需要实时排序一下,虽然是实时排序,但个数就10个,不管是快排还是堆排,都很快的,所以时间效率也慢不到哪去。

8. 整体的时空平衡

综合衡量一看,我个人觉得两种方式都能接受,具体选哪一个就仁者见仁了。

  • 如果搜索词的量比较稳定,不会有太大的变化,那么使用双数组Trie树+辅助数据结构+离线构建Trie树+离线排序的方式更合适。

  • 如果搜索词虽然现在是50万,但很可能会增加得比较多,或者像下图一样,搜索提示的页面还会承载很多其他的数据的话,那么使用二次优化的Trie树+离线构建Trie树+离线打分+实时排序的实现方式更合适,因为能节省更多的内存给后续扩充词语用或者给其他数据用。

  • 还有如果对速度要求苛刻,那么就第一种,如果没那么苛刻,那就第二种

架构设计没有好坏,只有合适不合适。

9. 总结

上面分析了这么一大堆,淌过三个的时间与空间的坑,终于基本确定了技术方案了,这其实也是系统架构设计中经常会要遇到的选择了,架构师们把这些选择做完以后,可以开始细分模块设计开发了,所以,一个小小的系统就这么多选择,各种空间和时间的平衡,你说架构师哪那么好当?呵呵,你以为就画完这篇文章的第一图就架构结束了啊。

这里只是用搜索提示作为一个例子来说明系统设计的时候需要时时刻刻关注时间空间这两个因素的平衡,现在很多人设计系统的时候基本上不太关注时间,因为高配的服务器,几十上百GB的内存随便用,所以大多数都把设计往空间上去靠,用更多的空间来换取执行效率,这本身并没有什么问题,谁不希望更快啊,但是有时候预估一下,有可能虽然牺牲了一点时间效率,但是换来了不少的空间,这样的系统在数据量变大时有更多的可扩展空间,我觉得是非常值得的交换。

再有,对数据结构和算法的了解以及预估算能力其实是平衡时间和空间的重要技能,也是架构设计中避坑的基本技能,所以有公司的面试题会出现请你估算一下黄河出海口的面积这类估算题,因为预估算能力也是重要的架构技能吧。

10. 更深入一下

上面只是这个系统的一小部分,搜索提示需要做的远不止如此,想想下面几个场景,如果是你,你要如何设计呢?如何平衡时间和空间呢?欢迎讨论哈:)

  • 需要拼音支持,就像这样
    图片描述

  • 需要拼音首字母支持
    图片描述

  • 某些搜索提示需要更加详细的信息
    图片描述

  • 需要对每个用户的搜索历史进行搜索提示【这个比较难点】
    图片描述

11. 后记

这个系列算是结束了,现在我正在做一些推荐广告相关的工作,后续也会分享一些相关的东西给大家,搜索部分也不会停,后面还有分词,相关搜索,分布式的东西会依次出来,欢迎关注哈。


如果你觉得不错,欢迎转发给更多人看到,也欢迎关注我的公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行
图片描述

相关文章:

C#中调用Windows API的要点

在.Net Framework SDK文档中,关于调用Windows API的指示比较零散,并且其中稍全面一点的是针对Visual Basic .net讲述的。本文将C#中调用API的要点汇集如下,希望给未在C#中使用过API的朋友一点帮助。另外如果安装了Visual Studio .net的话&…

线上直播丨Hinton等6位图灵奖得主、百余位顶级学者邀你群聊AI

Geoffrey Hinton等6位图灵奖得主亲临,百余位顶级学者邀请你加入群聊「2020北京智源大会」,深入系统探讨「人工智能的下一个十年」。自2009年深度学习崛起以来,第三波人工智能浪潮席卷全球,推动了新一波技术革命。在这波澜壮阔的11…

ServerSocket

ServerScoket 这个类用于与 Socket 进行通信。 在实例化ServerSocket 的时候,服务器相当于已经开始了,但是还需要通过socket来accept (socket serverSocket.accept())以使服务器选择性与某一Client进行连接。如果有指定了允许连接…

NDK开发 - C/C++ 访问 Java 变量和方法

上一篇有提到 JNI 访问引用数组,涉及了 C/C 访问 Java 实例的方法和变量。虽然在之前的开发中,并没有用到 C/C 范围 Java 层数据,但是这部分内容还是很有用的。传送门:NDK开发 - C/C 访问 Java 变量和方法 C/C 访问 Java 层的方法…

在C#中应用哈希表(Hashtable)

一,哈希表(Hashtable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似key/value的键值对,其中key通常可用来快速查找,同时key是区分大小写;value用于存储对应于key的…

俄罗斯自研Elbrus CPU参数曝光,CEO年近九旬仍未退休

导语:俄罗斯自研 CPU 参数最近曝光,虽然比起主流产品仍存在较大差距,但是这也是俄罗斯在自研道路上的一大进展。虽说自研 CPU 并非易事,但为了避免被美国牵制,很多国家都在这方面投入了巨大的人力与资金。战斗民族俄罗…

停止Password Manager Agent服务导致应用程序启动缓慢

在一个实施环境中,部署了Password Manager用来实现单点登录功能,但是由于Password Manager的提示基本都是以英文为主,而且配置也比较麻烦,普通用户看见会比较影响用户体验,所以用户决定暂时关闭Password Manager功能&a…

web 前端常用组件【06】Upload 控件

因为有万恶的IE存在,所以当Web项目初始化并进入开发阶段时。 如果是项目经理,需要知道客户将会用什么浏览器来访问系统。 明确知道限定浏览器的情况下,你才能从容的让手下的封装必要的前端组件。 本篇文章试图从常见的上传方式和组件进行分析…

性能超越最新序列推荐模型,华为诺亚方舟提出记忆增强的图神经网络

作者 | Chen Ma, Liheng Ma等译者 | Rachel出品 | AI科技大本营(ID:rgznai100)用户-商品交互的时间顺序可以揭示出推荐系统中用户行为随时间演进的序列性特征。用户与之交互的商品可能受到用户曾经接触的商品的影响。但是,用户和商…

ASP.net 中的页面继承实现和通用页面的工厂模式的实现

最近用.Net做web项目的时候遇到了一些问题,就是很多的页面的处理一样的,不一样的就是我们写的存储过程不同,为了考虑代码的重复利用和可维护性和可 扩展性,于是写了一个对于单据页面的工厂模式,采用界面的继承技术&…

5502的时钟组

5502有四个时钟组,分别为: C55x Subsystem Clock GroupFast Peripherals Clock GroupSlow Peripherals Clock GroupExternal Memory Interface Clock Group1、C55x Subsystem Clock Group该时钟组包括C55X CPU core、内存(DARAM和ROM&#xf…

遇到的浏览器兼容问题及应对方法

前言: 上周天的时候有个学长找我帮忙做三张页面,因为没有数据交换之类的,只是单纯的前端页面,想着好久没做东西, 看书都看烦了,所以就接了也当是练手。之前因为没有系统的看书,所以其实很多问题…

浅谈权限设计(来自深空老大)

2019独角兽企业重金招聘Python工程师标准>>> By 深空, 2009-09-13 21:45:07 PHPChina的专家版在谈权限设计,苦于没有权限回帖,特发此博文谈谈简单的权限设计。讨论在这里。 最简单的权限验证,应该是登录态的验证,如果登…

5年Python功力,总结了10个开发技巧

作者 | 写代码的明哥来源 |Python编程时光(ID: Cool-Python)如何在运行状态查看源代码?查看函数的源代码,我们通常会使用 IDE 来完成。比如在 PyCharm 中,你可以 Ctrl 鼠标点击 进入函数的源代码。那如果没有 IDE 呢&…

怎样给目录加权限0777

# chmod -R 0777 /var/www/html/子目录

php学习,一个简单的Calendar(2) 一个简单的活动页面

有了前面的基础&#xff0c;后面就是将页面展示出来。 预览图如下&#xff1a;1号和31号分别有活动&#xff0c;会一并显示出来 这里需要搞定几个问题&#xff0c;一个就是数据库的连接&#xff0c;我们用\sys\class\class.db_connect.inc.php <?php /* * 数据库操作&#…

涨见识了,在终端执行 Python 代码的 6 种方式

作者 | BRETT CANNON译者 | 豌豆花下猫Python猫为了我们推出的 VS Code 的 Python 插件[1]&#xff0c;我写了一个简单的脚本来生成变更日志[2]&#xff08;类似于Towncrier[3]&#xff0c;但简单些&#xff0c;支持 Markdown&#xff0c;符合我们的需求&#xff09;。在发布过…

ASP.NET中DataGrid鼠标经过感知以及点击行弹出窗口

选择自 xujh 的 Blog 作者Blog&#xff1a;http://blog.csdn.net/xujh/ 很多人说很难&#xff0c;其实就这几行代码。只要在DataGrid1的ItemDataBound中写入下代码即可 private void DataGrid1_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventAr…

python中的module

Python中的Module是比较重要的概念。常见的情况是&#xff0c;事先写好一个.py文件&#xff0c;在另一个文件中需要import时&#xff0c;将事先写好的.py文件拷贝到当前目录&#xff0c;或者是在sys.path中增加事先写好的.py文件所在的目录&#xff0c;然后import。这样的做法&…

找子串替换(kmp)poj1572

题目链接&#xff1a;http://poj.org/problem?id1572 输入数据时要注意&#xff0c;这里是string型 用getline(cin,origin[i]); #include <string> #include <iostream> #include <algorithm> #include <stdio.h>using namespace std;const int maxn …

dll的概念、dll导出类(转)

1、 DLL的概念DLL(Dynamic Linkable Library)&#xff0c;动态链接库&#xff0c;可以向程序提供一些函数、变量或类。这些可以直接拿来使用。静态链接库与动态链接库的区别&#xff1a;&#xff08;1&#xff09;静态链接库与动态链接库都是共享代码的方式。静态链接库把最后的…

墨奇科技汤林鹏:如何用 AI 技术颠覆指纹识别?

受访者 | 墨奇科技联合创始人& CTO 汤林鹏 记者 | Aholiab&#xff0c;编辑 | Carol 出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09; 随着深度学习等AI技术的成熟&#xff0c;生物识别成为了关注度较高的领域&#xff0c;指纹、人脸、虹膜等识别技术…

ASP.Net ViewState的实现

选择自 timmy3310 的 Blog ViewState是.Net中提出的状态保存的一种新途径&#xff08;实际上也是老瓶装新酒&#xff09;&#xff1b;我们知道&#xff0c;传统的Web程序保存状态的方式有这样几种&#xff1a;1、Application 这是Web应用程序生命期中的全局保存区&#xff0c…

【51CTO学院三周年】遇到

作为一名二流学校的大学生&#xff0c;因为学校的一门嵌入式Linux应用程序开发而喜欢上了嵌入式&#xff0c;但是学校却是只上了一学期的课&#xff0c;无奈只能自己找教程继续学习。在3个月前&#xff0c;无意中找到了朱有鹏老师的嵌入式学习基础视频&#xff0c;通过老师视频…

ASP.NET图象处理详解

作者&#xff1a;未知 请与本人联系在使用ASP的时候&#xff0c;我们时常要借助第三方控件来实现一些图象功能。而现在&#xff0c;ASP.NET的推出&#xff0c;我们已经没有必要再使用第三方控件来实现&#xff0c;因为ASP.NET 已经具有强大的功能来实现一些图象处理。现在&…

IT工作者,你们的爱情是这样的吗?

今天在博客里看到了这篇文章&#xff0c;看完这个视频我随笔写了点自己的感受和看法&#xff0c; 视频链接在下方&#xff1a; http://leidu.blog.51cto.com/3245712/622534 很感谢90 男孩提供&#xff0c;建议IT人员看一下&#xff0c;看完写一下你们的感受吧&#xff01;…

平头哥玄铁处理器Linux新版本,5大亮点速览

来源 | 芯片开放社区为了便于 CPU 评估&#xff0c;系统集成&#xff0c;快速上手玄铁处理器 Linux 操作系统&#xff0c;平头哥更新了玄铁处理器 linux 版本&#xff0c;结合 gitlab 开源 CI/CD 系统&#xff0c;对已发布到开源社区的玄铁架构 CPU 相关的生态软件形成持续保障…

费用保险单,如何失焦时自动补零

费用&#xff0c;如何失焦时自动补零转载于:https://www.cnblogs.com/maojiayan/p/5606247.html

腾讯AI种番茄双丰收:参赛AI全胜专家,辽宁试点净利增千元

6月9日&#xff0c;腾讯宣布了两项AI农业领域进展。在研究侧&#xff0c;腾讯 AI Lab 与荷兰瓦赫宁根大学&#xff08;下称WUR&#xff09;联办的“第二届国际智慧温室种植挑战赛”&#xff08;下称比赛&#xff09;落幕。在全球疫情肆虐之时&#xff0c;复赛的五支队伍挑战用 …

在ASP.NET中值得注意的两个地方

在ASP.NET中ASPX页面的Page_Load事件有两个让人奇怪的地方&#xff0c;你应该记住它们&#xff1a; a.有时Page_Load事件在你的ASP.NET页面里会发生多次。这种情况发生的一个可能的原因是你把ASPX页面的AutoEvenWireup值设置成了True。如果是这样&#xff0c;那么在“Sub Page…