精华内容
下载资源
问答
  • 还是原来的猴子,但多了特效技能。下面我们以下4个方面介绍Python的装饰器:什么是装饰器?为什么Python要引入装饰器?装饰器有利于解决哪些问题?装饰器背后的原理是什么?在回答以上4个问题的过程中,你会学会如何...

    原文链接:https://www.zhihu.com/question/26930016

    131 人赞同了该回答

    装饰器就是把一个猴子,塞入炼丹炉,然后就出来一个还是会吃桃子,但有火眼金睛的猴子;还是原来的猴子,但多了特效技能。


    下面我们以下4个方面介绍Python的装饰器:

    1. 什么是装饰器?
    2. 为什么Python要引入装饰器?
    3. 装饰器有利于解决哪些问题?
    4. 装饰器背后的原理是什么?


    在回答以上4个问题的过程中,你会学会如何使用装饰器

    开始吧


    01 什么是装饰器?

    装饰器可以让一个Python函数拥有原本没有的功能,也就是你可以通过装饰器,让一个平淡无奇的函数变的强大,变的漂亮。


    举几个现实中的例子

    1、你一个男的程序员,穿上女装,戴上假发,你就有了女人的外表(穿女装、戴假发的过程就是新的特效,你拥有了女人的外表,你原来的小jj还在,没有消失)

    2、你新买的毛坯房,装修,买家具后变好看了(装修、家具就是新的特效)

    3、孙悟空被放进炼丹炉装饰了一下,出来后,学会了火眼金睛,以前的本领都还在


    02 为什么Python要引入装饰器?

    因为引入装饰器会便于开发,便于代码复用,可以把烂泥扶上墙,

    装饰器可以让你一秒变女人且可以保住小JJ,当你某天后悔想重新变回男人,只要脱掉女装和假发即可(如果你变女人的时候,给小JJ做了手术(直接修改函数体的内容),想再变回男人可就痛苦了哦)


    03 装饰器有利于解决哪些问题?

    例子1:

    扩展功能

    比如你写了一段代码,当你执行 孙悟空() 就打印出它目前的技能

    # python3支持用中文做函数名,
    # 这里为了方便你理解,就用中文,实际情况为了兼容性,你可别用中文哦
    

    def 孙悟空():
    print(‘吃桃子’)
    孙悟空()
    # 输出:
    # 吃桃子


    现在你希望

    孙悟空这个函数 打印出 ’有火眼金睛了’,该怎么做呢?

    是的,你可以直接在函数里加一段 print(‘有火眼金睛了’)

    但是这样会破坏原来的代码,如果你的代码量很多很多的话,修改起来则是灾难,

    不过别担心,你还可以用装饰器来装饰他,让他在原本基础上,扩展出新的功能

    代码如下


    def 炼丹炉(func): # func就是‘孙悟空’这个函数
    def 变身(*args, kwargs): #args, **kwargs就是‘孙悟空’的参数列表,这里的‘孙悟空’函数没有传参数,我们写上也不影响,建议都写上
    print(‘有火眼金睛了’) # 加特效,增加新功能,比如孙悟空的进了炼丹炉后,有了火眼金睛技能
    return func(args, kwargs) #保留原来的功能,原来孙悟空的技能,如吃桃子
    return 变身 # 炼丹成功,更强大的,有了火眼金睛技能的孙悟空出世

    @炼丹炉
    def 孙悟空():
    print(‘吃桃子’)

    孙悟空()
    # 输出:
    # 有火眼金睛了
    # 吃桃子



    例子2:扩展权限认证

    比如你的代码,默认打开就播放动画片,代码如下

    def play():
    print(‘开始播放动画片 《喜洋洋和灰太狼》’)

    play()
    # 输出
    # 开始播放动画片 《喜洋洋和灰太狼》


    但是突然某天,你突然希望只允许1岁到10才可以看这个动画片,不希望程序员大叔看这个动画片怎么办?

    是的,你可以修改这个代码,加上年龄限制,但如果我们用装饰器的话,就更简单了,就可以不用破坏原来的代码,而且方便扩展到其他函数上

    userAge = 40
    

    def canYou(func):
    def decorator(args, **kwargs):
    if userAge > 1 and userAge < 10:
    return func(args, **kwargs)
    print(‘你的年龄不符合要求,不能看’)
    return decorator

    @canYou
    def play():
    print(‘开始播放动画片 《喜洋洋和灰太狼》’)

    play()
    # 输出
    # 你的年龄不符合要求,不能看
    # 你可以修改上面的 userAge 为9 试试


    你看,是不是很简单,实际情况中,很多时候,你需要对一段代码加上权限认证,加上各种功能;但是又不想,或者不方便破坏原有代码,则可以用装饰器去扩展它


    04 装饰器背后的实现原理是什么?


    原理 代码逆推后如下

    def 炼丹炉(func):
    def 变身(args, **kwargs):
    print(‘有火眼金睛了’)
    return func(args, **kwargs)
    return 变身

    def 孙悟空():
    print(‘吃桃子’)
    _孙悟空 = 炼丹炉(孙悟空) #放入原料,原来的弱小的孙悟空,生成炼丹方案给 新_孙悟空 ,这里也可以把炼丹方案给 原来的‘孙悟空’,为了方便理解,给了新的孙悟空

    _孙悟空() # 执行炼丹程序,新的孙悟空出世


    然后这段代码,写起来有点麻烦,Python官方出了一个快捷代码,也就是语法糖,用了语法糖就变成了下面这样

    def 炼丹炉(func):
    def 变身(args, **kwargs):
    print(‘有火眼金睛了’)
    return func(args, **kwargs)
    return 变身

    @炼丹炉 # 把下面的 ‘孙悟空’ 塞进炼丹炉,并把新的孙悟空复制给下面的函数
    def 孙悟空():
    print(‘吃桃子’)

    孙悟空() # 执行炼丹程序,新的孙悟空出世


    可以一次性在一个函数上用多个装饰器吗?

    当然可以,下面我们给孙悟空,弄个金箍棒,让他学会72变,学会飞


    def 炼丹炉(func):
    def 变身(args, **kwargs):
    print(‘有火眼金睛了’)
    return func(args, **kwargs)
    return 变身

    def 龙宫走一趟(func):
    def 你好(args, **kwargs):
    print(‘有金箍棒了’)
    return func(args, **kwargs)
    return 你好

    def 拜师学艺(func):
    def 师傅(args, **kwargs):
    print(‘学会飞、72变了’)
    return func(args, **kwargs)
    return 师傅

    @拜师学艺
    @龙宫走一趟
    @炼丹炉
    def 孙悟空():
    print(‘吃桃子’)

    孙悟空()
    # 输出
    # 学会飞、72变了
    # 有金箍棒了
    # 有火眼金睛了
    # 吃桃子


    上面代码的等效于 拜师学艺(龙宫走一趟(炼丹炉(孙悟空)))

    代码的执行顺序是 先从内到外

    先执行 炼丹炉,然后是龙宫走一趟,最后是拜师学艺,


    有小伙伴提问:先执行炼丹炉,为什么没有先输出【有火眼金睛了】 ,而是先输出了【拜师学艺】?

    请看下面的链接:

    Python 装饰器执行顺序迷思 - SegmentFaultsegmentfault.com




    展开全文
  • (说明一下,本人非常痛恨教材公式定理漫天飞,实际的讲解却讲得非常枯涩难懂,这种中国式的教育已经延绵了几千年了,现在中国的教材还是这个样子,讲清楚些明白些就那么难么?高中有个老师讲的一句话一直觉得很有...
            
    
    今天在网上看到一个讲动态规划的文章,是以01背包为例的,这文章和书上的讲解非常不一样,令我眼前一亮,于是转载一下下~~~
    (说明一下,本人非常痛恨教材公式定理漫天飞,实际的讲解却讲得非常枯涩难懂,这种中国式的教育已经延绵了几千年了,现在中国的教材还是这个样子,讲清楚些明白些就那么难么?高中有个老师讲的一句话一直觉得很有道理:“教得会天才不是真本事,能把博士生的东西讲到小学生都会用那才是真水平。”)
    附上原文地址:
    http://www.cnblogs.com/sdjl/articles/1274312.html


    通过金矿模型介绍动态规划

    对于动态规划,每个刚接触的人都需要一段时间来理解,特别是第一次接触的时候总是想不通为什么这种方法可行,这篇文章就是为了帮助大家理解动态规划,并通过讲解基本的01背包问题来引导读者如何去思考动态规划。本文力求通俗易懂,无异性,不让读者感到迷惑,引导读者去思考,所以如果你在阅读中发现有不通顺的地方,让你产生错误理解的地方,让你难得读懂的地方,请跟贴指出,谢谢!

    ----第一节----初识动态规划--------

    经典的01背包问题是这样的:
    有一个包和n个物品,包的容量为m,每个物品都有各自的体积和价值,问当从这n个物品中选择多个物品放在包里而物品体积总数不超过包的容量m时,能够得到的最大价值是多少?[对于每个物品不可以取多次,最多只能取一次,之所以叫做01背包,0表示不取,1表示取]

    为了用一种生动又更形象的方式来讲解此题,我把此题用另一种方式来描述,如下:

    有一个国家,所有的国民都非常老实憨厚,某天他们在自己的国家发现了十座金矿,并且这十座金矿在地图上排成一条直线,国王知道这个消息后非常高兴,他希望能够把这些金子都挖出来造福国民,首先他把这些金矿按照在地图上的位置从西至东进行编号,依次为0、1、2、3、4、5、6、7、8、9,然后他命令他的手下去对每一座金矿进行勘测,以便知道挖取每一座金矿需要多少人力以及每座金矿能够挖出多少金子,然后动员国民都来挖金子。

    题目补充1:挖每一座金矿需要的人数是固定的,多一个人少一个人都不行。国王知道每个金矿各需要多少人手,金矿i需要的人数为peopleNeeded。
    题目补充2:每一座金矿所挖出来的金子数是固定的,当第i座金矿有peopleNeeded人去挖的话,就一定能恰好挖出gold个金子。否则一个金子都挖不出来。
    题目补充3:开采一座金矿的人完成开采工作后,他们不会再次去开采其它金矿,因此一个人最多只能使用一次。
    题目补充4:国王在全国范围内仅招募到了10000名愿意为了国家去挖金子的人,因此这些人可能不够把所有的金子都挖出来,但是国王希望挖到的金子越多越好。
    题目补充5:这个国家的每一个人都很老实(包括国王),不会私吞任何金子,也不会弄虚作假,不会说谎话。
    题目补充6:有很多人拿到这个题后的第一反应就是对每一个金矿求出平均每个人能挖出多少金子,然后从高到低进行选择,这里要强调这种方法是错的,如果你也是这样想的,请考虑背包模型,当有一个背包的容量为10,共有3个物品,体积分别是3、3、5,价值分别是6、6、9,那么你的方法取到的是前两个物品,总价值是12,但明显最大值是后两个物品组成的15。
    题目补充7:我们只需要知道最多可以挖出多少金子即可,而不用关心哪些金矿挖哪些金矿不挖。

    那么,国王究竟如何知道在只有10000个人的情况下最多能挖出多少金子呢?国王是如何思考这个问题的呢?

    国王首先来到了第9个金矿的所在地(注意,第9个就是最后一个,因为是从0开始编号的,最西边的那个金矿是第0个),他的臣子告诉他,如果要挖取第9个金矿的话就需要1500个人,并且第9个金矿可以挖出8888个金子。听到这里国王哈哈大笑起来,因为原先他以为要知道十个金矿在仅有10000个人的情况下最多能挖出多少金子是一件很难思考的问题,但是,就在刚才听完他的臣子所说的那句话时,国王已经知道总共最多能挖出多少金子了,国王是如何在不了解其它金矿的情况下知道最多能挖出多少金子的呢?他的臣子们也不知道这个谜,因此他的臣子们就问他了:“最聪明的国王陛下,我们都没有告诉您其它金矿的情况,您是如何知道最终答案的呢?”

    得意的国王笑了笑,然后把他最得意的“左、右手”叫到跟前,说到:“我并不需要考虑最终要挖哪些金矿才能得到最多的金子,我只需要考虑我面前的这座金矿就可以了,对于我面前的这座金矿不外乎仅有两种选择,要么挖,要么不挖,对吧?”

    “当然,当然”大臣们回答倒。

    国王继续说道:“如果我挖取第9座金矿的话那么我现在就能获得8888个金子,而我将用去1500个人,那么我还剩下8500个人。我亲爱的左部下,如果你告诉我当我把所有剩下的8500个人和所有剩下的其它金矿都交给你去开采你最多能给我挖出多少金子的话,那么我不就知道了在第9个金矿一定开采的情况下所能得到的最大金币数吗?”

    国王的左部下听后回答道:“国王陛下,您的意思是如果我能用8500个人在其它金矿最多开采出x个金币的话,那您一共就能够获得 x + 8888个金子,对吗?”

    “是啊,是啊……如果第9座金矿一定开采的话……”大臣们点头说到。

    国王笑着继续对着他的右部下说到:“亲爱的右部下,也许我并不打算开采这第9座金矿,那么我依然拥有10000个人,如果我把这10000个人和剩下的金矿都给你的话,你最多能给我挖出多少个金子呢?”

    国王的右部下聪明地说道:“尊敬的国王陛下,我明白您的意思了,如果我回答最多能购开采出y个金币的话,那您就可以在y和x+8888之间选择一个较大者,而这个较大者就是最终我们能获得的最大金币数,您看我这样理解对吗?”


    国王笑得更灿烂了,问他的左部下:“那么亲爱的左部下,我给你8500个人和其余金矿的话你能告诉我最多能挖出多少金子吗?”

    “请您放心,这个问题难不倒我”。左部下向国王打包票说到。

    国王高兴地继续问他的右部下:“那右部下你呢,如果我给你10000个人和其余金矿的话你能告诉我最多能挖出多少金子吗?”

    “当然能了!交给我吧!”右部下同左部下一样自信地回答道。

    “那就拜托给你们两位了,现在我要回到我那舒适的王宫里去享受了,我期待着你们的答复。”国王说完就开始动身回去等消息了,他是多么地相信他的两个大臣能够给他一个准确的答复,因为国王其实知道他的两位大臣要比他聪明得多。

    故事发展到这里,你是否在想国王的这两个大臣又是如何找到让国王满意的答案的呢?他们为什么能够如此自信呢?事实上他们的确比国王要聪明一些,因为他们从国王的身上学到了一点,就是这一点让他们充满了自信。

    国王走后,国王的左、右部下来到了第8座金矿,早已在那里等待他们的金矿勘测兵向两位大臣报道:“聪明的两位大臣,您们好,第8座金矿需要1000个人才能开采,可以获得7000个金子”。

    因为国王仅给他的左部下8500个人,所以国王的左部下叫来了两个人,对着其中一个人问到:“如果我给你7500个人和除了第8、第9的其它所有金矿的话,你能告诉我你最多能挖出多少金子吗?”

    然后国王的左部下继续问另一个人:“如果我给你8500个人和除了第8、第9的其它所有金矿的话,你能告诉我你最多能挖出多少金子吗?”

    国王的左部下在心里想着:“如果他们俩都能回答我的问题的话,那国王交给我的问题不就解决了吗?哈哈哈!”

    因为国王给了他的右部下10000个人,所以国王的右部下同样也叫来了两个人,对着其中一个人问:“如果我给你9000个人和除了第8、第9的其它所有金矿的话,你能告诉我你最多能挖出多少金子吗?”

    然后国王的右部下继续问他叫来的另一个人:“如果我给你10000个人和除了第8、第9的其它所有金矿的话,你能告诉我你最多能挖出多少金子吗?”

    此时,国王的右部下同左部下一样,他们都在为自己如此聪明而感到满足。

    当然,这四个被叫来的人同样自信地回答没有问题,因为他们同样地从这两个大臣身上学到了相同的一点,而两位自认为自己一样很聪明的大臣得意地笑着回到了他们的府邸,等着别人回答他们提出来的问题,现在你知道了这两个大臣是如何解决国王交待给他们的问题了吗?

    那么你认为被大臣叫去的那四个人又是怎么完成大臣交给他们的问题的呢?答案当然是他们找到了另外八个人!

    没用多少功夫,这个问题已经在全国传开了,更多人的人找到了更更多的人来解决这个问题,而有些人却不需要去另外找两个人帮他,哪些人不需要别人的帮助就可以回答他们的问题呢?

    很明显,当被问到给你z个人和仅有第0座金矿时最多能挖出多少金子时,就不需要别人的帮助,因为你知道,如果z大于等于挖取第0座金矿所需要的人数的话,那么挖出来的最多金子数就是第0座金矿能够挖出来的金子数,如果这z个人不够开采第0座金矿,那么能挖出来的最多金子数就是0,因为这唯一的金矿不够人力去开采。让我们为这些不需要别人的帮助就可以准确地得出答案的人们鼓掌吧,这就是传说中的底层劳动人民!

    故事讲到这里先暂停一下,我们现在重新来分析一下这个故事,让我们对动态规划有个理性认识。

    子问题:
    国王需要根据两个大臣的答案以及第9座金矿的信息才能判断出最多能够开采出多少金子。为了解决自己面临的问题,他需要给别人制造另外两个问题,这两个问题就是子问题。

    思考动态规划的第一点----最优子结构:
    国王相信,只要他的两个大臣能够回答出正确的答案(对于考虑能够开采出的金子数,最多的也就是最优的同时也就是正确的),再加上他的聪明的判断就一定能得到最终的正确答案。我们把这种子问题最优时母问题通过优化选择后一定最优的情况叫做“最优子结构”。

    思考动态规划的第二点----子问题重叠:
    实际上国王也好,大臣也好,所有人面对的都是同样的问题,即给你一定数量的人,给你一定数量的金矿,让你求出能够开采出来的最多金子数。我们把这种母问题与子问题本质上是同一个问题的情况称为“子问题重叠”。然而问题中出现的不同点往往就是被子问题之间传递的参数,比如这里的人数和金矿数。

    思考动态规划的第三点----边界:
    想想如果不存在前面我们提到的那些底层劳动者的话这个问题能解决吗?永远都不可能!我们把这种子问题在一定时候就不再需要提出子子问题的情况叫做边界,没有边界就会出现死循环。

    思考动态规划的第四点----子问题独立:
    要知道,当国王的两个大臣在思考他们自己的问题时他们是不会关心对方是如何计算怎样开采金矿的,因为他们知道,国王只会选择两个人中的一个作为最后方案,另一个人的方案并不会得到实施,因此一个人的决定对另一个人的决定是没有影响的。我们把这种一个母问题在对子问题选择时,当前被选择的子问题两两互不影响的情况叫做“子问题独立”。


    这就是动态规划,具有“最优子结构”、“子问题重叠”、“边界”和“子问题独立”,当你发现你正在思考的问题具备这四个性质的话,那么恭喜你,你基本上已经找到了动态规划的方法。

    有了上面的这几点,我们就可以写出动态规划的转移方程式,现在我们来写出对应这个问题的方程式,如果用gold[mineNum]表示第mineNum个金矿能够挖出的金子数,用peopleNeeded[mineNum]表示挖第mineNum个金矿需要的人数,用函数f(people,mineNum)表示当有people个人和编号为0、1、2、3、……、mineNum的金矿时能够得到的最大金子数的话,f(people,mineNum)等于什么呢?或者说f(people,mineNum)的转移方程是怎样的呢?

    答案是:
    当mineNum = 0且people >= peopleNeeded[mineNum]时 f(people,mineNum) = gold[mineNum]
    当mineNum = 0且people < peopleNeeded[mineNum]时 f(people,mineNum) = 0
    当mineNum != 0时 f(people,mineNum) = f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]与f(people, mineNum-1)中的较大者,前两个式子对应动态规划的“边界”,后一个式子对应动态规划的“最优子结构”请读者弄明白后再继续往下看。






    ----第二节----动态规划的优点--------

    现在我假设读者你已经搞清楚了为什么动态规划是正确的方法,但是我们为什么需要使用动态规划呢?请先继续欣赏这个故事:

    国王得知他的两个手下使用了和他相同的方法去解决交代给他们的问题后,不但没有认为他的两个大臣在偷懒,反而很高兴,因为他知道,他的大臣必然会找更多的人一起解决这个问题,而更多的人会找更更多的人,这样他这个聪明的方法就会在不经意间流传开来,而全国人民都会知道这个聪明的方法是他们伟大的国王想出来的,你说国王能不高兴吗?

    但是国王也有一些担忧,因为他实在不知道这个“工程”要动用到多少人来完成,如果帮助他解决这个问题的人太多的话那么就太劳民伤财了。“会不会影响到今年的收成呢?”国王在心里想着这个问题,于是他请来了整个国家里唯一的两个数学天才,一个叫做小天,另一个叫做小才。

    国王问小天:“小天啊,我发觉这个问题有点严重,我知道其实这可以简单的看成一个组合问题,也就是从十个金矿中选取若干个金矿进行开采,看看哪种组合得到的金子最多,也许用组合方法会更好一些。你能告诉我一共有多少种组合情况吗?”

    “国王陛下,如果用组合方法的话一共要考虑2的10次方种情况,也就是1024种情况。”小天思考了一会回答到。

    “嗯……,如果每一种情况我交给一个人去计算能得到的金子数的话,那我也要1024个人,其实还是挺多的。”国王好像再次感觉到了自己的方法是正确的。

    国王心理期待着小才能够给它一个更好的答案,问到:“小才啊,那么你能告诉我用我的那个方法总共需要多少人吗?其实,我也计算过,好像需要的人数是1+2+4+8+16+32+64+……,毕竟每一个人的确都需要找另外两个人来帮助他们……”

    不辜负国王的期待,小才微笑着说到:“亲爱的国王陛下,其实我们并不需要那么多人,因为有很多问题其实是相同的,而我们只需要为每一个不同的问题使用一个人力便可。”

    国王高兴的问到:“此话如何讲?”

    “打个比方,如果有一个人需要知道1000个人和3个金矿可以开采出多少金子,同时另一个人也需要知道1000个人和3个金矿可以开采出多少金子的话,那么他们可以去询问相同的一个人,而不用各自找不同的人浪费人力了。”

    国王思考着说到:“嗯,很有道理,如果问题是一样的话那么就不需要去询问两个不同的人了,也就是说一个不同的问题仅需要一个人力,那么一共有多少个不同的问题呢?”

    “因为每个问题的人数可以从0取到10000,而金矿数可以从0取到10,所以最多大约有10000 * 10 等于100000个不同的问题。” 小才一边算着一边回答。

    “什么?十万个问题?十万个人力?”国王有点失望。

    “请国王放心,事实上我们需要的人力远远小于这个数的,因为不是每一个问题都会遇到,也许我们仅需要一、两百个人力就可以解决这个问题了,这主要和各个金矿所需要的人数有关。” 小才立刻回答到。

    故事的最后,自然是国王再一次向他的臣民们证明了他是这个国家里最聪明的人,现在我们通过故事的第二部分来考虑动态规划的另外两个思考点。

    思考动态规划的第五点----做备忘录:
    正如上面所说的一样,当我们遇到相同的问题时,我们可以问同一个人。讲的通俗一点就是,我们可以把问题的解放在一个变量中,如果再次遇到这个问题就直接从变量中获得答案,因此每一个问题仅会计算一遍,如果不做备忘的话,动态规划就没有任何优势可言了。

    思考动态规划的第六点----时间分析:
    正如上面所说,如果我们用穷举的方法,至少需要2^n个常数时间,因为总共有2^n种情况需要考虑,如果在背包问题中,包的容量为1000,物品数为100,那么需要考虑2^100种情况,这个数大约为10的30次方。

    而如果用动态规划,最多大概只有1000*100 = 100000个不同的问题,这和10的30次方比起来优势是很明显的。而实际情况并不会出现那么多不同的问题,比如在金矿模型中,如果所有的金矿所需人口都是1000个人,那么问题总数大约只有100个。

    非正式地,我们可以很容易得到动态规划所需时间,如果共有questionCount个相同的子问题,而每一个问题需要面对chooseCount种选择时,我们所需时间就为questionCount * chooseCount个常数。在金矿模型中,子问题最多有大概people * n 个(其中people是用于开采金矿的总人数,n是金矿的总数),因此questionCount = people * n,而就像国王需要考虑是采用左部下的结果还是采用右部下的结果一样,每个问题面对两个选择,因此chooseCount = 2,所以程序运行时间为 T = O(questionCount * chooseCount) =O(people * n),别忘了实际上需要的时间小于这个值,根据所遇到的具体情况有所不同。

    这就是动态规划的魔力,它减少了大量的计算,因此我们需要动态规划!






    ----第三节----动态规划的思考角度----------

    那么什么是动态规划呢?我个人觉得,如果一个解决问题的方法满足上面六个思考点中的前四个,那么这个方法就属于动态规划。而在思考动态规划方法时,后两点同样也是需要考虑的。

    面对问题要寻找动态规划的方法,首先要清楚一点,动态规划不是算法,它是一种方法,它是在一件事情发生的过程中寻找最优值的方法,因此,我们需要对这件事情所发生的过程进行考虑。而通常我们从过程的最后一步开始考虑,而不是先考虑过程的开始。

    打个比方,上面的挖金矿问题,我们可以认为整个开采过程是从西至东进行开采的(也就是从第0座开始),那么总有面对最后一座金矿的时候(第9座),对这座金矿不外乎两个选择,开采与不开采,在最后一步确定时再去确定倒数第二步,直到考虑第0座金矿(过程的开始)。

    而过程的开始,也就是考虑的最后一步,就是边界。

    因此在遇到一个问题想用动态规划的方法去解决时,不妨先思考一下这个过程是怎样的,然后考虑过程的最后一步是如何选择的,通常我们需要自己去构造一个过程,比如后面的练习。







    ----第四节----总结-------

    那么遇到问题如何用动态规划去解决呢?根据上面的分析我们可以按照下面的步骤去考虑:

    1、构造问题所对应的过程。
    2、思考过程的最后一个步骤,看看有哪些选择情况。
    3、找到最后一步的子问题,确保符合“子问题重叠”,把子问题中不相同的地方设置为参数。
    4、使得子问题符合“最优子结构”。
    5、找到边界,考虑边界的各种处理方式。
    6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。
    7、考虑如何做备忘录。
    8、分析所需时间是否满足要求。
    9、写出转移方程式。





    ----第五节----练习-------

    题目一:买书
    有一书店引进了一套书,共有3卷,每卷书定价是60元,书店为了搞促销,推出一个活动,活动如下:

    如果单独购买其中一卷,那么可以打9.5折。
    如果同时购买两卷不同的,那么可以打9折。
    如果同时购买三卷不同的,那么可以打8.5折。

    如果小明希望购买第1卷x本,第2卷y本,第3卷z本,那么至少需要多少钱呢?(x、y、z为三个已知整数)。

    当然,这道题完全可以不用动态规划来解,但是现在我们是要学习动态规划,因此请想想如何用动态规划来做?

    答案:

    1、过程为一次一次的购买,每一次购买也许只买一本(这有三种方案),或者买两本(这也有三种方案),或者三本一起买(这有一种方案),最后直到买完所有需要的书。
    2、最后一步我必然会在7种购买方案中选择一种,因此我要在7种购买方案中选择一个最佳情况。
    3、子问题是,我选择了某个方案后,如何使得购买剩余的书能用最少的钱?并且这个选择不会使得剩余的书为负数。母问题和子问题都是给定三卷书的购买量,求最少需要用的钱,所以有“子问题重叠”,问题中三个购买量设置为参数,分别为i、j、k。
    4、的确符合。
    5、边界是一次购买就可以买完所有的书,处理方式请读者自己考虑。
    6、每次选择最多有7种方案,并且不会同时实施其中多种,因此方案的选择互不影响,所以有“子问题独立”。
    7、我可以用minMoney[j][k]来保存购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱。
    8、共有x * y * z 个问题,每个问题面对7种选择,时间为:O( x * y * z * 7) = O( x * y * z )。
    9、用函数MinMoney(i,j,k)来表示购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱,那么有:
    MinMoney(i,j,k)=min(s1,s2,s3,s4,s5,s6,s7),其中s1,s2,s3,s4,s5,s6,s7分别为对应的7种方案使用的最少金钱:
    s1 = 60 * 0.95 + MinMoney(i-1,j,k)
    s2 = 60 * 0.95 + MinMoney(i,j-1,k)
    s3 = 60 * 0.95 + MinMoney(i,j,k-1)
    s4 = (60 + 60) * 0.9 + MinMoney(i-1,j-1,k)
    s5 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1)
    s6 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1)
    s7 = (60 + 60 + 60) * 0.85 + MinMoney(i-1,j-1,k-1)





    ----第六节----代码参考------


    下面提供金矿问题的程序源代码帮助读者理解,并提供测试数据给大家练习。

    输入文件名为“beibao.in”,因为这个问题实际上就是背包问题,所以测试数据文件名就保留原名吧。
    输入文件第一行有两个数,第一个是国王可用用来开采金矿的总人数,第二个是总共发现的金矿数。
    输入文件的第2至n+1行每行有两个数,第i行的两个数分别表示第i-1个金矿需要的人数和可以得到的金子数。

    输出文件仅一个整数,表示能够得到的最大金子数。

    输入样例:
    100 5
    77 92
    22 22
    29 87
    50 46
    99 90

    输出样例:
    133

    展开全文
  • reactor模型—生动形象

    2019-04-16 23:04:44
    网上有很多讲解reactor模型的,我还是看了比较久才稍微领悟一点意思,那么我就举个例子讲讲自己的理解。 我们首先先结合NIO、多路复用的知识,做一个比喻。 大前提:餐馆的服务员现在假设只有一个(还需要兼职厨师...

    网上有很多讲解reactor模型的,我还是看了比较久才稍微领悟一点意思,那么我就举个例子讲讲自己的理解。

    我们首先先结合NIO、多路复用的知识,做一个比喻。

    大前提:餐馆的服务员现在假设只有一个(还需要兼职厨师),那么当一个客人来了之后,服务员会把菜单给客人,然后就走开去迎接其他客人,当有客人点好餐的时候会把菜单告诉服务员,服务员就会去做事,热按后服务员再把菜给客人,就完成了完整的一次流程。

    那么针对reactor的三种模型。

    一.单线程模式

    那么这个时候就是指餐厅只有一个服务员(兼职厨师),那么这个时候会产生什么问题喃?

    (1)当客人多了的时候,应付不过来。(网络请求多了以后,很多请求其实就是一直在等待)

    (2)当有一个客人的菜特别复杂的时候,例如满汉全席,那其他客人就只能一首凉凉了。(当一个业务处理很久或者死循环了,那么整个服务挂掉)

    所以针对上面的缺点,有了第二种模式。

    二.多线程模式

    那么这个时候就是指餐厅可以请多个厨师了,服务员被解放出来了,只用招呼客人。这样就可以很好地解决单线程模式的困扰。但是这样仍然存在一定的问题。

    (1)当客人特别特别多的时候,服务员忙不过来了。

    (2)厨师高产出下,会把菜交给服务员,结果服务员也忙不过来了。

    为了解决这样的问题,就有了第三种模式。

    三.主从模式

    那么这个时候就是指:老板把这个服务员升级成主管了,然后老板另外请了几个服务员。每次来客人总会经过主管之手,然后主管就随便告诉一个其他的服务员先招呼客人(做主管以前做的事情,即一,二),然后主管就什么都不用管了,这个时候主观的压力就很小了,以前是10个客人的菜都由他传递与接收,现在是平分给了其他几个服务员,这样效率就更高了

    展开全文
  • 我知道关于Handler的文章在网上已经快被写烂了/笑着哭,但是还是想写出来,我觉得只有自己亲自写过这些东西,才会更深入的理解这些。虽然以前也写过Handler,但是当时刚刚接触Handler的源码,所以写的像屎一样。。。...
    我知道关于Handler的文章在网上已经快被写烂了/笑着哭,但是还是想写出来,我觉得只有自己亲自写过这些东西,才会更深入的理解这些。
    虽然以前也写过Handler,但是当时刚刚接触Handler的源码,所以写的像屎一样。。。。(在简书上写的,不忍直视)。现在本人自我感觉关于Handler已经有个相对有一点自己见解了,所以今天会更加细致的写这篇博客。
    好了不多说了,开始我们今天的正题:

    这个东西如果直接从源码讲起来会很乱,所以今天我们先从我们接触到的一些类和方法开始讲。


    Handler使用(简述)

    关于Handler的使用这里就不多说了,相信在看这篇文章的时候大家已经把Handler用烂了。简述一下调用的几个方法:
    handler.postXXX(Runnable,。。。。);

    还有handler.sendXXX(。。。。);这是我们使用Handler的两类方法,大家应该都会用的,不用多说。

    Handler的构造方法中出现的相关方法

    我们使用Handler,首先要有一个Handler的对象,通常我们最简单的方法,就是直接new ,然后重写handlerMessage方法:

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    当然也可以在参数里面加上Callback回调来用(个人喜欢这种,因为第一种warning是一坨黄色的,看着很烦-。+):

    Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            return false;
        }
    });

    先进入Handler的第一个构造方法:

    public Handler() {
        this(null, false);
    }

    这里面的两个参数,我们在Android Studio中可以发现,第一个就是我们的Callback,第二个是说是否异步(我们不管他这个东西)。

    点进this看一下:

    public Handler(Callback callback, boolean async) {
        ......
    
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    我省略掉了上面的一些代码,只要看一下这一段就好:
    这里唯一一个比较陌生的就是Looper,其他的都能看懂。其实应该也不算陌生:因为在学Handler时候,各个书上差不多都有一个简述Handler的工作原理,里面都有提到过Looper,这个Looper称为轮询器,负责不停地轮询,把我们的消息取出来,然后执行。这只是大概工作原理,其实如果真的吃透Handler机制会发现,真正的轮询另有其人(下面会提到)。

    既然他先出来的,那么我们先看一下mLooper方法:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    这个方法返回了和当前线程关联的looper对象。
    先说一下这个sThreadLocal:
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();


    这是在Looper中的一个静态引用,这么看来我们所有的Looper都要共享这个mThreadLocal了,我们可以把这个mThreadLocal理解为一个大箱子,里面放着很多的Looper对象。上面用到了ThreadLocal的get方法,我们看一下:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    他返回值是当前线程的某个值,现在我们把泛型指定为Looper ,所以他肯定是拿到了当前线程的Looper,这个很容易理解。接着我们简单说一下下面出现的这几个类:
    • ThreadLocalMap
    • Entry
    ThreadLocalMap是ThreadLocal的一个静态内部类,而Entry又是ThreadLocalMap的一个静态内部类,(个人感觉他们是组合关系,大家有不同意的可以评论交流一下)

    首先通过getMap方法拿到map对象,我们看一下getMap方法:

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    是返回了Thread中的一个属性,这样看来Thread中有ThreadLocalMap的引用。Thread和ThreadLocalMap可以关联。

    在上述代码中我们注意到最后是从Entry e中拿到了value作为返回值返回,这么推理是Entry中存储了我们的Looper。我们看getEntry方法:

    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

    table是ThreadLocalMap的一个属性:

    private Entry[] table;

    我们通过table拿到Entry对象,然后返回这个e。(在一般不出意外的情况下)

    现在我们跳回到上面的get方法中,目前我们已经拿到了e对象,如果不为空会返回e存储的值(value):

    if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
    }

    这是我们myLooper的方法。简单总结一下:

    我们调用了sThreadLocal的get方法,在get方法中首先获取到当前线程(Thread.currentThread()),然后拿到对应线程的ThreadLocalMap对象(getMap),最后通过ThreadLocalMap拿到Entry对象(getEntry)返回e的value值。


    我们在Handler的构造方法上耗时有点多了,开始扫尾:

    mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;

    跳回我们Handler构造方法:在获取到looper对象之后,我们就是给Handler的属性赋值,这里又出现了一个新的类,mQueue对应的类——MessageQueue,关于这个类我们下面详细讲,现在我们只需要Handler和Looper保留了它的引用。在Handler构造方法中进行了相关属性赋值。到现在我们Handler的构造方法可以过了。

    handler.sendXXX(。。。。);

    上面说到Handler发送消息有两种方式,我这里就那sendMessage方法举例了(其实最好会发现,所有方法都一样-。+):

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

    这是我们点进了sendMessage方法,我们看到他是嵌套了一个方法,我们再点进去看一下:

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    又是嵌套(其实还有好多嵌套呢-。+),接着点:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    也是嵌套,但是这个嵌套有点东西:

    MessageQueue queue = mQueue;
    return enqueueMessage(queue, msg, uptimeMillis);

    注意这两行代码,在接下来的方法中,我们又放入和一个MessageQueue引用做参数,现在我们再点进这个方法看:

        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    好了,现在又有一个Message对象,是不是一个没搞懂,又冒出来好几个的感觉,大家淡定,先分析一下他的逻辑,只有这几个类会在下面详细介绍的:

    我们将msg的target属性设为this,这个方法是在Handler中的,没猜错的话target应该是Handler类型的,点进去看一下:

    /*package*/ Handler target;

    果然如此哈哈!
    最后返回的是MessageQueue的方法了,好我们就此打住。
    总结handler.sendXXX():

    经过层层方法嵌套,最后进入了MessageQueue的enqueueMessage方法。


    接下来我们重点看一下刚才提到的还有在Handler中几个重点了类:

    Message
    MessageQueue
    Looper

    我们分别看一下:

    Message

    目前主要看这几个属性就好:

    /*package*/ Handler target;
    /*package*/ Runnable callback;
    
    /*package*/ long when;
    // sometimes we store linked lists of these things
    /*package*/ Message next;

    第一个是我们刚才看到的target,我们在enqueueMessage方法中,将当前的Handler赋值给了msg的target。

    callback:这个属性在我们通过post方法发送消息的时候会用到,具体大家自己分析完post方法就知道了。
    when:这个是一个用作记录时刻的属性,官方解释为运行回调的绝对时间。
    next:相信大家都知道链表是个什么东西,这个next就是一个指向链表下一元素的指针。

    其他的我们等会儿遇到了再说,现在先知道这三个属性就好。

    MessageQueue

    如果没猜错的话,这个类应该才是我们最应该头疼的一个类了,我们简单看一下它的注释:

    /**
     * Low-level class holding the list of messages to be dispatched by a
     * {@link Looper}.  Messages are not added directly to a MessageQueue,
     * but rather through {@link Handler} objects associated with the Looper.
     *
     * <p>You can retrieve the MessageQueue for the current thread with
     * {@link Looper#myQueue() Looper.myQueue()}.
     */

    我们只看其中一句很重要的:Messages are not added directly to a MessageQueue:我们的Message不是直接储存到MessageQueue,这是神魔意思?我查找了一下MessageQueue的所有属性,顾名思义,如果MessageQueue是用来存储Message的,那么首先肯定有用一个集合相关的属性,来存储Message,可是我看完了所有的属性,。。。。。。真的没有!

    但是我们发现了熟悉的身影:mMessage属性,他是Message类。还记得我们上面提到过的Message.next属性吗?我们是不是可以大胆地猜测一下:MessageQueue中放置了一个Message链表的头结点,然后通过Message元素自身添加和删除,实现Message的链式存储。(目前这个只是咱们的猜测)
    刚才在Handler.sendXXX()中,最后跳转到的就是MessageQueue的enQueueMessage方法,我们来看一下这个方法(前方高能!):

    MessageQueue.enqueueMessage

        boolean enqueueMessage(Message msg, long when) {
            ......
    
            synchronized (this) {
                ......
    
             
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    //原来mMessages为链表头,现在让msg变成了链表头,原来的mMessagges引用的Message对象在msg的后面。
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    ......
    
                    /*
                    * msg不是新的头,所以要按照时间顺序排列链表,查找对应的位置*/
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                ......
            }
            return true;
        }

    为了广大的读者,我已经删除了一部分无用的代码,但是还看着很多。我们逆着想一下:我们handler.sendXXX方法是把我的Message对象放入了队列中,所以最后跳转到的这个方法应该是一个存储Message的过程,首先我们有着这样的一个总体思路,然后接着看:

    首先我们下面所有的代码都写入了同步锁锁住的代码块中,首先给传入msg的when属性赋值,新建了一个Message引用等于mMessages。
    我们的进行了一个简单的if判断,无论链表为空,还是绝对时刻为0,或者是新插入的msg时刻比头结点的时刻小(简单理解是在它之前),这三种情况只要满足其中一种,就把新插入的msg设为新的头结点。这三行代码就是指定新的头结点,自己看去。。。
    如果这三种情况都不是,那么就要查找msg应该对应的位置,标准就是绝对时刻(when):通过遍历队列,直到找到对应位置,如果到最后还没有对应的位置,说明他的绝对时刻最大,所以放到最后。
    最后返回true。
    这是我们enqueueMessage方法的过程。对应代码上又备注。
    MessageQueue还有一个很重要的方法:next()(至于用处一会儿回用到):

    MessageQueue.next():

        Message next() {
            ......
            for (;;) {
                ......
    
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    if (msg != null && msg.target == null) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    
                    // Process the quit message now that all pending messages have been handled.
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                    ......
                }
    
                ......
    
                ......
            }
        }

    又删除了百分之80的垃圾代码-。+。刚才我们说enqueueMessage方法是把Message放入,那么next方法就是将Message取出的。我们来看一下:

        do {
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());

    我们先看这一段代码:首先新建了prevMsg,一个新的Message引用,然msg指向头结点。

    接着把preMsg和msg移动到同步Message位置(因为这段程序整个都在同步锁中,异步的Message不受限制,这段话可以当做放屁-。+)。

        if (msg != null) {
            if (now < msg.when) {
                // Next message is not ready.  Set a timeout to wake up when it is ready.
                nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
            } else {
                // Got a message.
                mBlocked = false;
                if (prevMsg != null) {
                    prevMsg.next = msg.next;
                } else {
                    mMessages = msg.next;
                }
                msg.next = null;
                if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                msg.markInUse();
                return msg;
            }
        }

    这段是将msg取出,然后将链表断开位置重新连接。(详细逻辑如上,大家自己看)


    Looper

    这个类是我们需要说的最后一个类了,感觉很累。。

    我们知道如果在子线程中使用Handler,我们还需要做额外的两件事:Looper.prepare()和Looper.loop();这两个方法有什么作用呢?我们来看一下:

    Looper.prepare()

        public static void prepare() {
            prepare(true);
        }

    接着跳!:

        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }

    相信到这里大家看的应该比较轻松了,因为几乎都是我们刚刚了解的方法:
    首先进行判断,在sThreadLocal中是否存在当前线程的looper对象,在初次创建时候,是没有的。
    然后创建一个新的Looper对象,放入sThreadLocal中。
    我们看一下set方法:

    Looper.set():

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }


    逻辑很清晰:

    1. 获取当前线程对象。
    2. 获取线程的ThreadLocalMap对象。
    3. set方法添加Looper对象。
    我们来看一下这个set方法:

    ThreadLocalMap.set(。。。):

            private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }

    这个不怎么重要,我们只需要知道最后我们把value添加给了Entry[]  table就好。

    Looper.loop():

        public static void loop() {
            final Looper me = myLooper();
            ......
            final MessageQueue queue = me.mQueue;
    
            ......
    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                ......
                ......
                final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
                final long end;
                try {
                    msg.target.dispatchMessage(msg);
                    end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                ......
    
            }
        }


    loop方法是用来轮询的的,在这里我们也看到了一个for死循环,我们看一下方法逻辑:

    1. 获取当前线程的looper对象。
    2. 进入死循环,不断获取当前线程的消息队列中的Message对象。
    3. 分发:msg.target.dispatchMessage(msg);
    如果有消息,我们会通知msg对应的handler执行dispatchMessage方法。我们一起看一下这个方法:

    handler.dispatchMessage(Message msg):

        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }

    这个方法有三条路:

    • 执行msg自己的Callback(这个是post发送消息最后的回调)
    • mCallback接口执行handleMessage方法(这个是我们在构造Handler时候添加和Callback情况下的回调)。
    • handleMessage(这个是我们重写和Handler.handleMessage情况下的回调)。
    相信这几行代码大家已经很亲切了。

    讲到这里,相信大家对我们Handler的运行机制,心里已经透彻了一些,但是还没完,我们要知道我们在主线程中(MainActivity中的实例变量为例),他就不用调用Looper.prepare和Looper.loop方法,我们看一下ActivityThread的main方法就一目了然了:

        public static void main(String[] args) {
            ......
    
            Looper.prepareMainLooper();
    
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
    
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
    
            ......
            Looper.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }

    我只留下了这两个方法,和最后的异常:我们在运行之初,就已经给主线程进行了prepare(prepareMainLooper本质还是调用prepare,大家下去自己去看)和loop方法,所以说一旦loop方法调用之后,不会终止,这是一个真的无效轮询过程。否则就会抛出如上异常。

    大家还记得我之前说的吗:相比looper,真正轮询的工作另有其人。可能现在这么说已经不太对了。就目前来看,我们在运行时有两个无线轮询:loop方法中和next方法中。如果说他们都是轮询的工作的话,那么looper是进行着不同线程之间的轮询(通过获取当前线程来获取当前轮询器,调用对应的消息队列进行消息轮询),而MessageQueue的轮询,是当前线程的消息轮询(如上所述-。=)。

    Handler机制总结:

    非主线程情况,需要调用Looper.prepaore和Looper.loop方法:

    Looper.prepare方法给当前线程绑定一个Looper对象(本质是Thread中有ThreadLocalMap引用,而Looper中也有Thread引用,从而实现了双向关联)。Looper.loop方法进入无限轮询,不断地调用当前线程的Looper对象的mQueue对象的next方法(有点拗口)。当获取到消息时,就分发给对应的handler去执行(通过三种回调,具体看上面)

    几张很有用的图-。+:

    这么多文字,相信大家已经看得想吐了,最后给大家带来几张图吧。
    这张图对Handler的轮询机制是很形象,但是希望大家不要吐槽我的画图能力-。+:

    传送带,电池。。。。都是大家能看懂的东西,领会精神哈!

    这个是刚才我们Looper相关类的类图。

    最后一张送上涵盖我们上述的主要的类的一张类图。(刚学UML,画的可能有点迷,见谅!!)。

    喜欢的朋友希望关注一下,如果有不同的观点或者指出错误欢迎下方留言。


    展开全文
  • 生动形象P2P(转)

    2011-04-16 19:12:00
    在P2P技术之前,网络中的所有...于是不论是通过广告还是别的什么方式,很多人知道了kaka有这个资源的消息。通过网络,他们就可以向kaka建立连接并从他的机子上下载。这样就形成了一个以kaka为中心的小型网络。这当中,k
  • 我之前还跟友人开玩笑说:老人们讲故事,不管是他们听来的还是祖辈传下来的(大多是听别人说的),反正都说得入目三分,感觉他当时就在现场时刻观察着一样。 那样的生动描绘,那样的故事细节塑造能力,着实佩服。 这...
  • 博客: ... ... QQ: 2811132560 ... ...最近被这三个兄弟搞得是晕头转向,然后决定花一番功夫比较深入的去了解一番,本着做为一名资深前端老油条的善良初心,决定还是布道,对还是布道一下,不但是...
  • 刚毕业的时候去了中建,那时候带我的老师傅很忙,天天都让看书,看得头晕眼花,不懂的地方也不好意思去问,后来还是一个同事给了这500个建筑施工3D动画演示,涵盖了多种建筑常见工程,生动形象浅显易懂。今天给大家...
  • 经常做数据分析的人肯定不会对词云图感到陌生,尤其是近几年来非常流行,不管是发布会、行业报告还是新闻,都喜欢使用词云这种形式,将信息的关键词组成形象生动的图案,一下子就能抓住生人眼球,因为词云图这种可视...
  • 读《SaaS架构设计》一书有感

    千次阅读 2015-07-01 21:47:08
    都是阿里巴巴架构师或专家所著,里面把SaaS的架构设计,发展变化以很形象生动的文字表现出来,而且还分析了业界几个知名的应用,给了我很多启发和思考。觉得书看过了基本也就留下些印象,不用就会难以真正消化,但是...
  • VUCA时代:软件架构解决复杂性之道

    千次阅读 2020-02-17 16:29:54
    本文生动形象地从五大方面介绍了VUCA时代下的软件架构设计,小编认认真真听了一下午,还是只能窥探零点一二。 “到底什么是VUCA?” “架构是神马?” 01、软件架构的核心目标 1)系统在某种环境下的一...
  • 本文来源网络: 1、码农何苦为难穷逼 ...7、这样写还是生动形象的 8、客户需求怎么做 9、这个太狠了吧 10、老实的程序员 1、码农何苦为难穷逼 2、删了就无法运行,我也不知道为什么 // 写完这段就...
  • YLProgressWater 是在 TYWaveProgressView 的基础上所做的一些修改,它是一个圆形的容器里面装的水来展示一个加载的进度,动画还比较流畅,可以根据外面传进来的值来改变水纹加载的多少,还是形象生动的.
  • 接口到底有什么用

    2017-07-08 10:38:49
    接口到底有什么用 曾看过很多形象的比喻,... 以下是我在知乎上看到的一个回答,我个人认为很好理解,又形象生动,又有代码可以参考。 推荐大家看看原答案,评论里有一些童鞋提的问题,一起看下来会有更深的理解。我
  • 找了很久的中联系统操作宝典,亲测还是比较全的,涵盖了中联的各个模块,生动形象的勾画出了系统的操作秘籍
  • ">泰山除了深厚的文化内涵,还具有丰富的自然美,如果我们把风景自然美的形象特征概括为雄、奇、险、秀、幽、奥、旷的话,那么泰山除了从总体上和宏观上具有雄伟的特征外,还在雄中蕴含着奇、险、秀、幽、奥、旷等...
  • 云服务器与物理服务器的区别。

    千次阅读 2017-12-14 10:51:09
    很多人在选购服务器时会纠结到底是应该选择云服务器还是租用独立服务器,一方面因为是对两者的概念较为混淆,不清楚其区别与各自优势,另一方面也是没明确好自己的需求,在这篇文章中互联先锋将为您形象生动地解读这...
  • 已经进入Cloud 2.0时代,租用或购买还是个问题吗?Nutanix亚太及日本地区系统工程副总裁Jeff Smith举了一个十分形象生动的例子。经常出差的商务人士,在每一地短期停留时...
  • 很多人在选购服务器时会纠结到底是应该选择云服务器还是租用独立服务器,一方面因为是对两者的概念较为混淆,不清楚其区别与各自优势,另一方面也是没明确好自己的需求,本文将为您形象生动地解读这两者的区别,从而...
  • Raft协议简介

    2018-12-09 23:45:18
    前人曾制作了一篇动画文章,用以简介Raft算法,非常的形象生动。链接在这里:http://thesecretlivesofdata.com/raft/ 本文是对该动画文章的一点总结归纳。真正深入了解Raft协议,还是应该看其原论文:...
  • 教师国培培训心得.doc

    2021-01-15 12:41:32
    教师国培培训心得 在学习过程中,专家们用教学实例,形象生动的语言,一次次撞击我的思维,让我不断思考,不断总结,不断进步。可以说每一节课对我都是受益匪浅。陈燕飞院长的“对中学体育教学中几个问题的碰撞与...
  • 当我们看到一些高手设计的惊艳作品后,总是赞叹不已,觉得他们为什么总能想出这么牛13的...这种思维借助于具体形象来进行思考,具有生动性、实感性等特点的思维活动。形象思维以直觉为基础,通过某一具体事物引发想...
  • 上一篇文章的问题一下子得到了解决 divitus可谓非常形象生动了 div实际是有语义的 可以将文档分割成几个有意义的区域 主要讲解了id和class 前者适用于有唯一性的元素 后者适用于有一堆相似的元素 微格式这块没认真看...
  •  第一:XML肯定是未来的发展趋势,不论是网页设计师还是网络程序员,都应该及时学习和了解,等待只会让你失去机会;  第二:新知识肯定会有很多新概念,尝试理解和接受,您才可能提高。不要害怕和逃避,毕竟我们...
  • 日常工作中,我们总是能听到这样的话:“这次的海报还是缺点创意啊”“再改...产品创意篇将啤酒和自然风光合成,画面形象生动,蜂蜜暗喻口感,可以很好的体现产品香醇的特点。立于山野之间的可口可乐,背景的光影处...
  • 日常工作中,我们总是能听到这样的话:“这次的海报还是缺点创意啊”“再改...产品创意篇将啤酒和自然风光合成,画面形象生动,蜂蜜暗喻口感,可以很好的体现产品香醇的特点。立于山野之间的可口可乐,背景的光影处...
  • Java面向对象之接口(一)距离上篇已经过了好久啦,主要是临近期末,忙着复习,接口方面也看了好多天,查看了很多资料,层次不齐,最终...话不多说,直接给一个形象生动的例子:There are a number of situations in ...
  • 很多人在选购服务器时会纠结到底是应该选择云服务器还是租用独立服务器,一方面因为是对两者的概念较为混淆,不清楚其区别与各自优势,另一方面也是没明确好自己的需求,在这篇文章中星速云将为您形象生动地解读这...
  • 网上有有趣的动态图来表示快速排序,但其实我们大部分程序员都是脑子不太好使那种,即使看了形象生动的动态图,还是想不到具体实现思路。 排序都有基本的步骤,快排也不例外,通常分为这么几步: 1、选择基准值; 2...
  • 这套PPT教学视频包括了很多优秀老师的教学视频,内容非常的简单有趣,形象生动,系统专业,拒绝陈词滥调,枯燥乏味,学习起来劲头十足,很快就能投入进去,让没有基础的学员能够快速掌握知识要领,解决你遇到的PPT...

空空如也

空空如也

1 2 3 4 5 ... 15
收藏数 294
精华内容 117
关键字:

形象生动还是生动形象