提高Python运行效率的六个窍门

Python性能优化的20条建议

http://segmentfault.com/a/1190000000666603
  1. 优化算法时间复杂度

    算法的时间复杂度对程序的执行效率影响最大,在Python中可以通过选择合适的数据结构来优化时间复杂度,如list和set查找某一个元素的时间复杂度分别是O(n)和O(1)。不同的场景有不同的优化方式,总得来说,一般有分治,分支界限,贪心,动态规划等思想。

  2. 减少冗余数据

    如用上三角或下三角的方式去保存一个大的对称矩阵。在0元素占大多数的矩阵里使用稀疏矩阵表示。

  3. 合理使用copy与deepcopy

    对于dict和list等数据结构的对象,直接赋值使用的是引用的方式。而有些情况下需要复制整个对象,这时可以使用copy包里的copy和deepcopy,这两个函数的不同之处在于后者是递归复制的。效率也不一样:(以下程序在ipython中运行)

    import copy
    a = range(100000)
    %timeit -n 10 copy.copy(a) # 运行10次 copy.copy(a)
    %timeit -n 10 copy.deepcopy(a) 10 loops, best of 3: 1.55 ms per loop 10 loops, best of 3: 151 ms per loop 

    timeit后面的-n表示运行的次数,后两行对应的是两个timeit的输出,下同。由此可见后者慢一个数量级。

  4. 使用dict或set查找元素

    python dict和set都是使用hash表来实现(类似c++11标准库中unordered_map),查找元素的时间复杂度是O(1)

    a = range(1000)
    s = set(a)
    d = dict((i,1) for i in a)
    %timeit -n 10000 100 in d %timeit -n 10000 100 in s 10000 loops, best of 3: 43.5 ns per loop 10000 loops, best of 3: 49.6 ns per loop 

    dict的效率略高(占用的空间也多一些)。

  5. 合理使用生成器(generator)和yield

    %timeit -n 100 a = (i for i in range(100000))
    %timeit -n 100 b = [i for i in range(100000)] 100 loops, best of 3: 1.54 ms per loop 100 loops, best of 3: 4.56 ms per loop 

    使用()得到的是一个generator对象,所需要的内存空间与列表的大小无关,所以效率会高一些。在具体应用上,比如set(i for i in range(100000))会比set([i for i in range(100000)])快。

    但是对于需要循环遍历的情况:

    %timeit -n 10 for x in (i for i in range(100000)): pass %timeit -n 10 for x in [i for i in range(100000)]: pass 10 loops, best of 3: 6.51 ms per loop 10 loops, best of 3: 5.54 ms per loop 

    后者的效率反而更高,但是如果循环里有break,用generator的好处是显而易见的。yield也是用于创建generator:

    def yield_func(ls): for i in ls: yield i+1 def not_yield_func(ls): return [i+1 for i in ls] ls = range(1000000) %timeit -n 10 for i in yield_func(ls):pass %timeit -n 10 for i in not_yield_func(ls):pass 10 loops, best of 3: 63.8 ms per loop 10 loops, best of 3: 62.9 ms per loop 

    对于内存不是非常大的list,可以直接返回一个list,但是可读性yield更佳(人个喜好)。

    python2.x内置generator功能的有xrange函数、itertools包等。

  6. 优化循环

    循环之外能做的事不要放在循环内,比如下面的优化可以快一倍:

    a = range(10000)
    size_a = len(a)
    %timeit -n 1000 for i in a: k = len(a) %timeit -n 1000 for i in a: k = size_a 1000 loops, best of 3: 569 µs per loop 1000 loops, best of 3: 256 µs per loop 
  7. 优化包含多个判断表达式的顺序

    对于and,应该把满足条件少的放在前面,对于or,把满足条件多的放在前面。如:

    a = range(2000)  
    %timeit -n 100 [i for i in a if 10 < i < 20 or 1000 < i < 2000] %timeit -n 100 [i for i in a if 1000 < i < 2000 or 100 < i < 20] %timeit -n 100 [i for i in a if i % 2 == 0 and i > 1900] %timeit -n 100 [i for i in a if i > 1900 and i % 2 == 0] 100 loops, best of 3: 287 µs per loop 100 loops, best of 3: 214 µs per loop 100 loops, best of 3: 128 µs per loop 100 loops, best of 3: 56.1 µs per loop 
  8. 使用join合并迭代器中的字符串

    In [1]: %%timeit...: s = ''...: for i in a: ...: s += i ...: 10000 loops, best of 3: 59.8 µs per loop In [2]: %%timeit s = ''.join(a) ...: 100000 loops, best of 3: 11.8 µs per loop 

    join对于累加的方式,有大约5倍的提升。

  9. 选择合适的格式化字符方式

    s1, s2 = 'ax', 'bx'
    %timeit -n 100000 'abc%s%s' % (s1, s2)
    %timeit -n 100000 'abc{0}{1}'.format(s1, s2) %timeit -n 100000 'abc' + s1 + s2 100000 loops, best of 3: 183 ns per loop 100000 loops, best of 3: 169 ns per loop 100000 loops, best of 3: 103 ns per loop 

    三种情况中,%的方式是最慢的,但是三者的差距并不大(都非常快)。(个人觉得%的可读性最好)

  10. 不借助中间变量交换两个变量的值

    In [3]: %%timeit -n 10000a,b=1,2....: c=a;a=b;b=c;....:
    10000 loops, best of 3: 172 ns per loop In [4]: %%timeit -n 10000 a,b=1,2 a,b=b,a ....: 10000 loops, best of 3: 86 ns per loop 

    使用a,b=b,a而不是c=a;a=b;b=c;来交换a,b的值,可以快1倍以上。

  11. 使用if is

    a = range(10000)
    %timeit -n 100 [i for i in a if i == True] %timeit -n 100 [i for i in a if i is True] 100 loops, best of 3: 531 µs per loop 100 loops, best of 3: 362 µs per loop 

    使用 if is True 比 if == True 将近快一倍。

  12. 使用级联比较x < y < z

    x, y, z = 1,2,3
    %timeit -n 1000000 if x < y < z:pass %timeit -n 1000000 if x < y and y < z:pass 1000000 loops, best of 3: 101 ns per loop 1000000 loops, best of 3: 121 ns per loop 

    x < y < z效率略高,而且可读性更好。

  13. while 1 比 while True 更快

    def while_1(): n = 100000 while 1: n -= 1 if n <= 0: break def while_true(): n = 100000 while True: n -= 1 if n <= 0: break m, n = 1000000, 1000000 %timeit -n 100 while_1() %timeit -n 100 while_true() 100 loops, best of 3: 3.69 ms per loop 100 loops, best of 3: 5.61 ms per loop 

    while 1 比 while true快很多,原因是在python2.x中,True是一个全局变量,而非关键字。

  14. 使用**而不是pow

    %timeit -n 10000 c = pow(2,20)
    %timeit -n 10000 c = 2**20 10000 loops, best of 3: 284 ns per loop 10000 loops, best of 3: 16.9 ns per loop 

    **就是快10倍以上!

  15. 使用 cProfile, cStringIO 和 cPickle等用c实现相同功能(分别对应profile, StringIO, pickle)的包

    import cPickle
    import pickle
    a = range(10000)
    %timeit -n 100 x = cPickle.dumps(a)
    %timeit -n 100 x = pickle.dumps(a) 100 loops, best of 3: 1.58 ms per loop 100 loops, best of 3: 17 ms per loop 

    由c实现的包,速度快10倍以上!

  16. 使用最佳的反序列化方式

    下面比较了eval, cPickle, json方式三种对相应字符串反序列化的效率:

    import json
    import cPickle
    a = range(10000)
    s1 = str(a)
    s2 = cPickle.dumps(a)
    s3 = json.dumps(a)
    %timeit -n 100 x = eval(s1)
    %timeit -n 100 x = cPickle.loads(s2) %timeit -n 100 x = json.loads(s3) 100 loops, best of 3: 16.8 ms per loop 100 loops, best of 3: 2.02 ms per loop 100 loops, best of 3: 798 µs per loop 

    可见json比cPickle快近3倍,比eval快20多倍。

  17. 使用C扩展(Extension)

    目前主要有CPython(python最常见的实现的方式)原生API, ctypes,Cython,cffi三种方式,它们的作用是使得Python程序可以调用由C编译成的动态链接库,其特点分别是:

    CPython原生API: 通过引入Python.h头文件,对应的C程序中可以直接使用Python的数据结构。实现过程相对繁琐,但是有比较大的适用范围。

    ctypes: 通常用于封装(wrap)C程序,让纯Python程序调用动态链接库(Windows中的dll或Unix中的so文件)中的函数。如果想要在python中使用已经有C类库,使用ctypes是很好的选择,有一些基准测试下,python2+ctypes是性能最好的方式。

    Cython: Cython是CPython的超集,用于简化编写C扩展的过程。Cython的优点是语法简洁,可以很好地兼容numpy等包含大量C扩展的库。Cython的使得场景一般是针对项目中某个算法或过程的优化。在某些测试中,可以有几百倍的性能提升。

    cffi: cffi的就是ctypes在pypy(详见下文)中的实现,同进也兼容CPython。cffi提供了在python使用C类库的方式,可以直接在python代码中编写C代码,同时支持链接到已有的C类库。

    使用这些优化方式一般是针对已有项目性能瓶颈模块的优化,可以在少量改动原有项目的情况下大幅度地提高整个程序的运行效率。

  18. 并行编程

    因为GIL的存在,Python很难充分利用多核CPU的优势。但是,可以通过内置的模块multiprocessing实现下面几种并行模式:

    多进程:对于CPU密集型的程序,可以使用multiprocessing的Process,Pool等封装好的类,通过多进程的方式实现并行计算。但是因为进程中的通信成本比较大,对于进程之间需要大量数据交互的程序效率未必有大的提高。

    多线程:对于IO密集型的程序,multiprocessing.dummy模块使用multiprocessing的接口封装threading,使得多线程编程也变得非常轻松(比如可以使用Pool的map接口,简洁高效)。

    分布式:multiprocessing中的Managers类提供了可以在不同进程之共享数据的方式,可以在此基础上开发出分布式的程序。

    不同的业务场景可以选择其中的一种或几种的组合实现程序性能的优化。

  19. 终级大杀器:PyPy

    PyPy是用RPython(CPython的子集)实现的Python,根据官网的基准测试数据,它比CPython实现的Python要快6倍以上。快的原因是使用了Just-in-Time(JIT)编译器,即动态编译器,与静态编译器(如gcc,javac等)不同,它是利用程序运行的过程的数据进行优化。由于历史原因,目前pypy中还保留着GIL,不过正在进行的STM项目试图将PyPy变成没有GIL的Python。

    如果python程序中含有C扩展(非cffi的方式),JIT的优化效果会大打折扣,甚至比CPython慢(比Numpy)。所以在PyPy中最好用纯Python或使用cffi扩展。

    随着STM,Numpy等项目的完善,相信PyPy将会替代CPython。

  20. 使用性能分析工具

    除了上面在ipython使用到的timeit模块,还有cProfile。cProfile的使用方式也非常简单: python -m cProfile filename.pyfilename.py 是要运行程序的文件名,可以在标准输出中看到每一个函数被调用的次数和运行的时间,从而找到程序的性能瓶颈,然后可以有针对性地优化。

参考

[1] http://www.ibm.com/developerworks/cn/linux/l-cn-python-optim/

[2] http://maxburstein.com/blog/speeding-up-your-python-code/

=========================

 

http://code.oneapm.com/python/2015/05/18/python-performance-tips/

原文地址:https://blog.newrelic.com/2015/01/21/python-performance-tips/

Python是一门优秀的语言,它能让你在短时间内通过极少量代码就能完成许多操作。不仅如此,它还轻松支持多任务处理,比如多进程。

不喜欢Python的人经常会吐嘈Python运行太慢。但是,事实并非如此。尝试以下六个窍门,来为你的Python应用提速。

窍门一:关键代码使用外部功能包

Python简化了许多编程任务,但是对于一些时间敏感的任务,它的表现经常不尽人意。使用C/C++或机器语言的外部功能包处理时间敏感任务,可以有效提高应用的运行效率。这些功能包往往依附于特定的平台,因此你要根据自己所用的平台选择合适的功能包。简而言之,这个窍门要你牺牲应用的可移植性以换取只有通过对底层主机的直接编程才能获得的运行效率。以下是一些你可以选择用来提升效率的功能包:

  • Cython
  • Pylnlne
  • PyPy
  • Pyrex

这些功能包的用处各有不同。比如说,使用C语言的数据类型,可以使涉及内存操作的任务更高效或者更直观。Pyrex就能帮助Python延展出这样的功能。Pylnline能使你在Python应用中直接使用C代码。内联代码是独立编译的,但是它把所有编译文件都保存在某处,并能充分利用C语言提供的高效率。

窍门二:在排序时使用键

Python含有许多古老的排序规则,这些规则在你创建定制的排序方法时会占用很多时间,而这些排序方法运行时也会拖延程序实际的运行速度。最佳的排序方法其实是尽可能多地使用键和内置的sort()方法。譬如,拿下面的代码来说:

    import operatorsomelist = [(1, 5, 8), (6, 2, 4), (9, 7, 5)] somelist.sort(key=operator.itemgetter(0)) somelist #Output = [(1, 5, 8), (6, 2, 4), (9, 7, 5)] somelist.sort(key=operator.itemgetter(1)) somelist #Output = [(6, 2, 4), (1, 5, 8), (9, 7, 5)] somelist.sort(key=operator.itemgetter(2)) somelist #Output = [(6, 2, 4), (9, 7, 5), (1, 5, 8)], 

在每段例子里,list都是根据你选择的用作关键参数的索引进行排序的。这个方法不仅对数值类型有效,还同样适用于字符串类型。

窍门三:针对循环的优化

每一种编程语言都强调最优化的循环方案。当使用Python时,你可以借助丰富的技巧让循环程序跑得更快。然而,开发者们经常遗忘的一个技巧是:尽量避免在循环中访问变量的属性。譬如,拿下面的代码来说:

    lowerlist = ['this', 'is', 'lowercase'] upper = str.upper upperlist = [] append = upperlist.append for word in lowerlist: append(upper(word)) print(upperlist) #Output = ['THIS', 'IS', 'LOWERCASE'] 

每次你调用str.upper, Python都会计算这个式子的值。然而,如果你把这个求值赋值给一个变量,那么求值的结果就能提前知道,Python程序就能运行得更快。因此,关键就是尽可能减小Python在循环中的工作量。因为Python解释执行的特性,在上面的例子中会大大减慢它的速度。

(注意:优化循环的方法还有很多,这只是其中之一。比如,很多程序员会认为,列表推导式是提高循环速度的最佳方法。关键在于,优化循环方案是提高应用程序运行速度的上佳选择。)

窍门四:使用较新的Python版本

如果你在网上搜索Python,你会发现数不尽的信息都是关于如何升级Python版本。通常,每个版本的Python都会包含优化内容,使其运行速度优于之前的版本。但是,限制因素在于,你最喜欢的函数库有没有同步更新支持新的Python版本。与其争论函数库是否应该更新,关键在于新的Python版本是否足够高效来支持这一更新。

你要保证自己的代码在新版本里还能运行。你需要使用新的函数库才能体验新的Python版本,然后你需要在做出关键性的改动时检查自己的应用。只有当你完成必要的修正之后,你才能体会新版本的不同。

然而,如果你只是确保自己的应用在新版本中可以运行,你很可能会错过新版本提供的新特性。一旦你决定更新,请分析你的应用在新版本下的表现,并检查可能出问题的部分,然后优先针对这些部分应用新版本的特性。只有这样,用户才能在更新之初就觉察到应用性能的改观。

窍门五:尝试多种编码方法

每次创建应用时都使用同一种编码方法几乎无一例外会导致应用的运行效率不尽人意。可以在程序分析时尝试一些试验性的办法。譬如说,在处理字典中的数据项时,你既可以使用安全的方法,先确保数据项已经存在再进行更新,也可以直接对数据项进行更新,把不存在的数据项作为特例分开处理。请看下面第一段代码:

    n = 16myDict = {} for i in range(0, n): char = 'abcd'[i%4] if char not in myDict: myDict[char] = 0 myDict[char] += 1 print(myDict) 

当一开始myDict为空时,这段代码会跑得比较快。然而,通常情况下,myDict填满了数据,至少填有大部分数据,这时换另一种方法会更有效率。

    n = 16myDict = {} for i in range(0, n): char = 'abcd'[i%4] try: myDict[char] += 1 except KeyError: myDict[char] = 1 print(myDict) 

在两种方法中输出结果都是一样的。区别在于输出是如何获得的。跳出常规的思维模式,创建新的编程技巧能使你的应用更有效率。

窍门六:交叉编译你的应用

开发者有时会忘记计算机其实并不理解用来创建现代应用程序的编程语言。计算机理解的是机器语言。为了运行你的应用,你借助一个应用将你所编的人类可读的代码转换成机器可读的代码。有时,你用一种诸如Python这样的语言编写应用,再以C++这样的语言运行你的应用,这在运行的角度来说,是可行的。关键在于,你想你的应用完成什么事情,而你的主机系统能提供什么样的资源。

Nuitka是一款有趣的交叉编译器,能将你的Python代码转化成C++代码。这样,你就可以在native模式下执行自己的应用,而无需依赖于解释器程序。你会发现自己的应用运行效率有了较大的提高,但是这会因平台和任务的差异而有所不同。

(注意:Nuitka现在还处在测试阶段,所以在实际应用中请多加注意。实际上,当下最好还是把它用于实验。此外,关于交叉编译是否为提高运行效率的最佳方法还存在讨论的空间。开发者已经使用交叉编译多年,用来提高应用的速度。记住,每一种解决办法都有利有弊,在把它用于生产环境之前请仔细权衡。)

在使用交叉编译器时,记得确保它支持你所用的Python版本。Nuitka支持Python2.6, 2.7, 3.2和3.3。为了让解决方案生效,你需要一个Python解释器和一个C++编译器。Nuitka支持许多C++编译器,其中包括Microsoft Visual Studio,MinGW 和 Clang/LLVM。

交叉编译可能造成一些严重问题。比如,在使用Nuitka时,你会发现即便是一个小程序也会消耗巨大的驱动空间。因为Nuitka借助一系列的动态链接库(DDLs)来执行Python的功能。因此,如果你用的是一个资源很有限的系统,这种方法或许不太可行。

结论

前文所述的六个窍门都能帮助你创建运行更有效率的Python应用。但是银弹是不存在的。上述的这些窍门不一定每次都能奏效。在特定的Python的版本下,有的窍门或许比其他的表现更好,但这有时候甚至取决于平台的差异。你需要总结分析你的应用,找到它效率低下的部分,然后尝试这些窍门,找到解决问题的最佳方法。

转载于:https://www.cnblogs.com/zhizhan/p/4883806.html

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

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

相关文章

ruby hash方法_Hash.fetch()方法以及Ruby中的示例

ruby hash方法Hash.fetch()方法 (Hash.fetch() Method) In this article, we will study about Hash.fetch() Method. The working of this method can be predicted with the help of its name but it is not as simple as it seems. Well, we will understand this method wi…

MySQL 死锁了,怎么办?

作者&#xff1a;小林coding提纲如下&#xff1a;正文有个业务主要逻辑就是新增订单、修改订单、查询订单等操作。然后因为订单是不能重复的&#xff0c;所以当时在新增订单的时候做了幂等性校验&#xff0c;做法就是在新增订单记录之前&#xff0c;先通过 select ... for upda…

lcfirst_PHP lcfirst()函数与示例

lcfirstPHP lcfirst()函数 (PHP lcfirst() function) lcfirst() function is a string function, it is used to convert first character to lowercase. It accepts string and returns string with first lowercase character. lcfirst()函数是一个字符串函数&#xff0c;用于…

在notepad++中运行python代码

#在notepad中运行python代码1、安装插件pyNPP&#xff0c; 2、允许插件pyNPP中的第一个和第二个选项即可&#xff0c;如果代码过少代码执行一闪而过&#xff0c;可能无法看到&#xff0c;可加入少量sleep时间即可 方法二&#xff1a;1、安装插件NppExec2、打开NppExec--Execute…

10 张图搞懂服务注册发现机制

在微服务架构或分布式环境下&#xff0c;服务注册与发现技术不可或缺&#xff0c;这也是程序员进阶之路必须要掌握的核心技术之一&#xff0c;本文通过图解的方式带领大家轻轻松松掌握。引入服务注册与发现组件的原因先来看一个问题&#xff0c;假如现在我们要做一个商城项目&a…

c# datetime._C#| DateTime.GetHashCode()方法与示例

c# datetime.DateTime.GetHashCode()方法 (DateTime.GetHashCode() Method) DateTime.GetHashCode() method is used get the 32-bit signed integer hash code of DateTime class object. DateTime.GetHashCode()方法用于获取DateTime类对象的32位带符号整数哈希码。 Syntax:…

ASP.NET 5 Beta8 已经发布

Microsoft ASP.NET and Web Tools 2015 (Beta8) http://www.microsoft.com/en-us/download/details.aspx?id49442 .net core 完成了98%&#xff0c;绝大部分类库完成了跨平台开发&#xff0c;已经基本可用&#xff0c;下一版本为RC&#xff0c;发布时间为12月&#xff0c;将可…

面试突击65:HTTPS有什么优点?说一下它的执行流程?

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;说到 HTTPS 相信大部分人都是不陌生&#xff0c;因为目前我们使用的绝大数网站都是基于 HTTPS 的&#xff0c;比如以…

nanf flash校验_C ++中带有示例的nanf()函数

nanf flash校验C Nanf()函数 (C nanf() function) nanf() function is a library function of cmath header, it is used to get the NaN value of type float. It accepts an argument (which is an implementation-specific C String – to get NaN value we have to pass a…

Cell.reuseIdentifier 指什么

Cell.reuseIdentifier 指的是 默认为空&#xff0c;如果不定义&#xff0c;在执行 [_tableView registerNib:templateCellNib forCellReuseIdentifier:_templateCell.reuseIdentifier]; 时&#xff0c;提示 must pass a valid reuse identifier to -[UITableView registerNib:f…

缓存穿透、缓存雪崩、缓存击穿?

背景 在现代软件架构中&#xff0c;缓存的应用已经非常普及。缓存的使用在面试和实践中都是避不开的硬技能、硬知识&#xff0c;如果你说还不太熟悉缓存的使用&#xff0c;可能都不好意思说自己是程序员。这篇文章&#xff0c;带大家进一步学习在缓存使用中不得不考虑三个特殊场…

c语言 div ldiv_C ++中带有示例的ldiv()函数

c语言 div ldivC ldiv()函数 (C ldiv() function) ldiv() function is a library function of cstdlib header. It is used for integral division, it accepts two parameters (numerator and denominator) and returns a structure that contains the quot (quotient) and r…

网盘搜索

http://pan.java1234.com1、在http://baidu.com的搜索框中输入&#xff1a;site:http://pan.baidu.com 搜索词&#xff08;回复中林涛同学建议第一条如果用谷歌搜site:百度网盘的话效果会好一点&#xff09;2、壹搜 网盘搜索引擎3、盘易搜 盘易搜-百度网盘搜索4、BD盘搜索 百度…

如何防止订单重复支付?

大家好&#xff0c;我是磊哥&#xff0c;想必大家对在线支付都不陌生&#xff0c;今天和大家聊聊如何防止订单重复支付。看看订单支付流程我们来看看&#xff0c;电商订单支付的简要流程&#xff1a;订单钱包支付流程从下单/计算开始&#xff1a;下单/结算&#xff1a;这一步虽…

c ++atoi函数_atoi()函数以及C ++中的示例

c atoi函数C atoi()函数 (C atoi() function) atoi() function is a library function of cstdlib header. It is used to convert the given string value to the integer value. It accepts a string containing an integer (integral) number and returns its integer valu…

IOS沙盒中的Documents、Library、tmp区别

1.Documents&#xff1a; 用户生成的文件、其他数据及其他程序不能重新创建的文件&#xff0c;iTunes备份和恢复的时候会包括此目录。 2.Library/Caches&#xff1a; 可以重新下载或者重新生成的数据,数据库缓存文件和可下载内容应该保存到这个文件夹,iTunes不会备份此目录&…

3 分钟快速上手 Spring 事件机制

小伙伴们好呀~ 今天来和大家分享下这个 Spring事件机制内容概览image-20210829132019387原理image-20210828184103069这个熟悉 观察者模式 的小伙伴应该一眼就看出来啦~其实就是个简单版的 发布-订阅模式有三个核心类&#x1f447;事件 ApplicationEvent事件发布器 Application…

mis dss gis_MIS中的决策支持系统(DSS)

mis dss gisThe Decision Support System is always helpful to management people to take decisions/decisions and finds the key business insights from available information systems. 决策支持系统始终有助于管理人员做出决策/决策&#xff0c;并从可用的信息系统中找到…

使用Grunt构建自动化开发环境

1、准备工作 1&#xff09;首页确保电脑上网&#xff0c;以及能够访问https://registry.npmjs.org/&#xff0c;因需从此网站中下载安装相应的插件; 2&#xff09;电脑安装Node.js&#xff0c;Grunt及Grunt插件都是基于node.js运行的&#xff1b;如果你电脑上未装node.js&#…

面试突击66:请求转发和请求重定向有什么区别?

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 Java 中&#xff0c;跳转的实现方式有两种&#xff1a;请求转发和请求重定向&#xff0c;但二者是完全不同的&…