分类 技术 下的文章 - 第 7 页 - 酷游博客
首页
关于
友链
Search
1
阿里的简历多久可以投递一次?次数多了有没有影响?可以同时进行吗?
45 阅读
2
Java中泛型的理解
40 阅读
3
Java 14 发布了,再也不怕 NullPointerException 了!
38 阅读
4
Java中的可变参数
37 阅读
5
该如何创建字符串,使用" "还是构造函数?
30 阅读
技术
登录
/
注册
找到
556
篇与
技术
相关的结果
- 第 7 页
2025-01-22
腾讯都是搜狗最大股东了,为啥还要全资收购!?
北京时间9月29日晚,搜狗发布公告称,公司将与THL A21 Limited、TitanSupernova Limited 和腾讯移动有限公司签订最终私有化协议,搜狗股票将以每股或每ADS(美国存托股)9美元的价格被注销,交易预计将在今年四季度完成。届时,2017年11月登陆纽交所的搜狗,三年美股旅程将终结。 参与搜狗私有化交易的三家公司:THL A21 Limited、Titan、Supernova Limited 以及 腾讯移动有限公司,均为腾讯的子公司。也就是说,交易完成后搜狗将由腾讯全资控股。 7月27日,腾讯准备全资收购搜狗的消息一出,搜狗的股价立即暴涨近50%,市值以33亿美元的股价收盘。此后两个月中,搜狗股价一直维持在高位。 9月29日最终交易协议签订的消息公布后,搜狗股价再度上涨2.31%至8.87美元。 从这则消息最早在7月份被爆出至今,搜狗CEO王小川每次谈及这件事语气都极其隐忍克制,但兴奋难掩,毕竟搜狗今日的状况实在令人堪忧。 即使搜狗在中国搜索行业排行老二,但局势却不乐观,前有百度牢牢占据着龙头老大,后有字节跳动旗下新推出的“头条搜索”,中间还夹击着360搜索,阿里的神马与夸克搜索,所以搜狗近几年营收更是一路下滑。 2019年,搜狗的营收是80亿人民币,净利润7.3亿人民币,可是在增长率上,营收仅仅增长了4%。 基本属于止步不前。 到了2020年,搜狗第一季度净亏损竟高达3110万美元,从首次公开上市市值13美元到最高50美元,再到今天的9美元,搜狗留下的只有一阵唏嘘。 听说为了促进这次收购,张朝阳可是动用了所有的投票权来支持,只有用搜狗来回点血,才不至于双双惨败,搜狐被收购显然是王小川和张朝阳的不二选择。 但是,谁都不知道,在腾讯体系内,搜狗能走多远。 最大股东还要全资收购? 2013年,王小川为了对抗360的收购计划,不得已接受了腾讯的入股,腾讯用4.48亿入成功占有了搜狗36.5%的股份。 入股后的腾讯对搜狗业务提供了巨大的支持,值得一提的是在腾讯系相继推出的新产品中优先使用搜狗服务,对于搜狗内部的业务和决策,腾讯尽量也不参与。 据搜狗2019年年报披露,在腾讯的多个产品中,默认的搜索引擎已经变成了搜狗搜索,而搜狗总搜索流量的约35%都是由腾讯产品引流而来。 根据搜狗发布的最新年报显示,腾讯已经是搜狗最大的股东,持股39.2%,拥有52.3%的投票权,而其老母亲搜狐持股仅有33.8%,投票权44.1%。 有人疑惑,明明掌握着一定话语权的腾讯,为什么还要全资买下搜狗? 值得参考的是2019年腾讯投资了104家企业,收购了4家,花费了1089亿元,基本每个月都要用掉100亿来投资和收购。 拿下搜狗强化搜索,对于腾讯这个互联网巨头来说,只不过是花小钱办大事,而且腾讯也一直对搜索耿耿于怀。 2017年初,借着微信这个庞大的流量池,腾讯开始内测微信搜索功能。2019年初的微信公开课,微信搜索团队首次公开亮相。2019年底,“微信搜索”正式升级为“微信搜一搜”。 可折腾几番后,目前腾讯的核心竞争力,还是在于微信所带来的庞大用户,虽然在微信方面也有着一定的搜索业务,但显然商业化进程缓慢,并且如果微信想要进一步发展内容,搜索的地位更加举足轻重。 现在的搜狗作为一个公司虽然已经狼狈不堪,但如果把搜狗当做整个腾讯生态的一个补充,没人能断言搜狗会爆发出怎样的力量? 更为关键的是搜狗除了自身所附带的搜索和业务外,所附带的AI更是能为腾讯如虎添翼。 近年来,AI正在逐渐成为大势所趋,百度率先领先并为此一掷千金,而阿里更是在阿里云后投资了达摩院,加快AI布局,但腾讯由于布局较晚,即使有优图实验室等AI部门,但还是免不了落在百度阿里的身后。 反观搜狗,技术出身的王小川,在搜狗输入法的最初就已经采取看基于大数据而非基于统计规则的实现方式,让搜狗初步具备看AI雏形,后来的语音输入法,在搜索中大量应用AI,研究出搜狗录音笔,翻译等业务,如此种种,都可以证明搜狗在AI技术方面的硬件能力。 百度何去何从 虽然百度的搜索市场近年来被蚕食的四分五裂,但作为中国的第一搜索引擎,恐怕短时间内没人能撼动百度的位置。 在PC端上,人家是霸主,在移动端上,虽然起步稍晚,但目前百度APP的日活也达到了2亿。 而这数亿活跃用户的“支持”,不仅是百度的活力之源,也是让百度能够潜心研究AI技术的后方储备。 AI对于百度来说,更像是一场赌注,不到最后,谁都不能断言百度局势如何,况且这次面对新的危机,百度不见得会在核心领域束手就擒任人宰割。 但不管如何,这场战争,终究还是BAT之争。 参考资料: https://www.sohu.com/a/422071059_116132 https://m.toutiaocdn.com/i6854695308970951171 https://m.toutiaocdn.com/i6854519358710874635 https://m.toutiaocdn.com/i6854737964170379779 https://www.zhihu.com/answer/1366709747 https://zhuanlan.zhihu.com/p/164742521
技术
# 未分类
酷游
1月22日
0
10
0
2025-01-22
感觉自己不会的东西太多了,不知道如何下手?
如果让我统计下,粉丝问我做多的问题是什么,这个问题肯定可以排前5,问出这个问题的朋友们遍布各个年龄段。 实话说,这个问题同样也困扰过我,大概就是我刚毕业的第一年。 那一年,刚刚离开校园,来到阿里,那时候就感觉自己好像什么都不会,好像很多东西都要学,不知道哪个是重点,不知道该如何下手。 那段时间我也像个无头苍蝇一样尝试过很多办法。  刚开始疯狂买书,《Java编程思想》、《Effective Java》、《深入理解Java虚拟机》等等。 然后想着自己撸一个项目,于是到github上找了很多开源项目,想着可以自己写一遍。刚开始想写个JUnit、然后想着写个SSH的项目,接着考虑自己写个Dubbo框架。 甚至考虑过去报个班,不瞒大家说,我一个阿里的程序员,刚毕业的时候竟然咨询过达内。 总之吧,做过很多尝试。现在我知道了,这就是焦虑。 焦虑是好事 首先,如果你有这种心态,那么完全不要慌。这很正常。 而且,我认为这未尝不是一件好事儿! 我当时之所以像个无头苍蝇一样,主要是因为我想让自己变的刚好。所以,我相信,那些问过我类似问题的他们,也一样。 有焦虑,说明自己有上进心,有上进心,是一个人可以变得更好的一个最基础要素。 说实话,这篇文章的标题很像是软文,会不会有人因为怕是软文广告就错过了,我犹豫了很久要不要换一个。 但是,我想了想,如果这一点努力都不愿意付出的话,就算我当着他的面把这篇文章读给他估计也无济于事。 什么是知识体系 有很多人问我,到底什么是知识体系。  要我说,知识体系这个词根本就没有人能说的清楚它到底是什么。 因为他真的是可大可小。 你可以说加减乘除是知识体系,也可以说数学学科是知识体系,还可以认为整个基础科学领域才是一个知识体系。 所以,没有必要给自己界定一个明确的体系框架。 很多人说,知识太多了,我不知道该学什么。其实,这么问的人,潜意识里是希望自己可以得到一份完整的知识体系大图,可以得到一个完美的知识学习路径。 但是,我不得不泼一盆冷水。就没有所谓的完整的知识体系大图,更没有完美的学习路径。 很多人看多我总结的《Java工程师成神之路》,但是,这并不能算是完整的Java知识体系大图。 以为,这最多算是我的知识体系的一个简单总结,但是,我的这份知识体系必然有局限性,局限性就是我对Java知识的认知和了解程度。 再说完美的知识学习路径是否存在,我觉得这是不可能存在的。 因为技术学习,不像我们在学校的时候学习算数,学算数可以先学加减、再学乘除,然后再学方程等等的。 但是,技术是服务于应用的,学习技术的原动力也肯定是因为我要用,所以我才要学。 不管是工作中要用到,还是面试的时候要用到,出发点都是我要用。 但是,每个人要用的知识怎么可能都一样呢?所以,也不存在完美的学习路径。 没有完整的知识体系,也没有完美的学习路径,怎么办? 适合自己的才是最好的!!!这句话有点像是正确的废话。但是,这是我走过焦虑期之后唯一能传达给你的! 找一个点,先进去 我觉得,学习是一件很简单的事儿,简单到你打开这篇文章的时候就已经在做了,只是你自己没意识到。 说到技术学习,该怎么做? 更简单。就像步步高点读机,哪里不会写哪里。 那怎么知道自己哪里不会呢? 这个更简单了,只要你开始学,那就知道自己哪里不会了。 那么,怎么开始学呢? 有几个方法,简单可实践: 1、随便找一本书,比如《深入理解Java虚拟机》,翻开目录,总能找到一个自己不会的知识点,然后从这个知识点开始看。 2、工作中遇到的一个问题,或者需要用到某个技术,从把他弄清楚开始。 3、通过我总结的《Java工程师成神之路》,翻开这篇文章,找到自己不会的知识点,然后开始看。 我博客中,有很多文章,其实我从写文章的第一天起,一直到现在,都在不断的完善我自己的知识体系。用到的无外乎就是以上这三个方法: 从第一篇文章是我记录了我毕业时候的面试准备和面试题,然后学习工作中用到的webx框架。后来我维护我们事业部的zookeeper集群,然后自己学习了很多zk以及分布式相关的知识。紧接着一次排查线上问题,了解到jdk提供了很多命令,于是开始学。再后来Java 8发布,我学了一些Optional 、Stream等知识。后来买了一本《高性能MYSQL》,于是写了很多和数据库有关的文章。接着是工作中用到了模板方法模式,感觉设计模式很有用,于是开始学。后来看了《Java并发编程实战》,又写了很多和并发有关的知识。接着我就总结了《Java工程师成神之路》,然后就是按照这个又写了很多系列文章…..前段时间,阿里巴巴推出《Java开发手册》,我又写了很多篇解读《手册》的文章…. 还有很多文章,我都忘了当初为什么写了。但是重要的不是从哪来,也不是去哪里,而是在路上!!! 很多人总是想着想要找到一份完整的知识系统或者完美的学习路径才开始。但是,现在我告诉你了,并没有。  所以,按照我说的,或者不按照我说的,找一个点,先进去,先开始学。 就像我之前总结过一篇文章,通过StringBuffer和StringBuilder开始,你都能一直学到并发编程、学到数据锁,学到分布式。 深度优先与广度优先 接着,说一下学习方法。 学习过程中,必然会遇到更多自己不会的知识点,这时候怎么办? 两种办法,1是先不管他,绕过去,回过头再看。2是先停下来把这个关联知识点搞清楚,再继续。 说到底这就是图论中的深入优先搜索和广度优先搜索呀,我一般采用的是深度优先,遇到一个搞清楚一个。 我的博客写过几篇和分布式有关的文章。如果你仔细看。可能会发现我的学习思路。 1.什么是分布式?发现相对于集中式来说的,那什么又是集中式。 2.分布式好像和集群很像。那什么是集群? 3.分布式有啥优点,有啥缺点? 4.好像分布式很难保证数据一致性,那什么是数据一致性。什么是CAP,什么是BASE,CAP和ACID好像挺像? 5.数据一直性问题如何产生?如何解决? 6.什么是2pc,什么是3PC,有了2PC为啥要3PC 7.为啥很少有人用2PC和3PC 8.什么是最终一致性。 9.什么是柔性事务,那什么是又是事务呢? 10.通过事务,又可以学习Java本地事务,全局事务,数据库事务。数据库事务隔离级别怎么回事?脏读,不可重复读这些都是啥?Spring事务咋回事? 11.柔性事务,什么是TCC,什么是消息最终一致性。那又如何实现的? 12.为啥有这么多方案,分布式事务问题还无法解决 13.TCC好像和2PC很像?有啥区别 14.各大公司是如何解决分布式事务的 15.支付宝的XTS到底怎么实现的? 16.有了事务了。那性能怎么保证? 17.缓存咋回事。缓存击穿咋办,热点问题咋解决 18.…………可用性相关?安全性相关?好像负载均衡我还不懂,到底咋回事?负载均衡和Web服务器有关?那tomcat咋实现?jboss和他有啥区别。Nginx呢?卧槽,反向代理是啥?啥是代理?代理模式?啥是设计模式?我以前读过设计模式之禅,但是有些地方之前没懂得,现在好像突然懂了。jboss modules是啥?啥特么又是模块化?osgi?Java9跟模块挂啥关系?Java10呢? 其实,不管是深度优先还是光度优先,总会有把图上的各个点串联成一张图的一天。  这不就是所谓的知识体系么? 鸡汤没毒 借着这个问题,多说几句鸡汤吧。我们大多数人的努力程度,根本还没到和别人拼天赋的程度。 我一直觉得,天赋是决定一个人的下限,而努力才是决定一个人的上限。 最可怕的就是我们并没有尽人事,却埋怨天命。有的时候,从哪开始并不重要,重要的是你要开始啊!!! 我认为,知识这东西,殊途同归。你先看哪本书,后看哪本书,差别没那么大,最终那几本有用的书之前的关系还是会被你发现。那个时候,知识体系就有个大概的框架了。然后查缺补漏呗。 慢慢的,知识体系不知不觉就建立起来了。别想那么远。找一个点,先进去。 相信我,坚持下去,1年后,你会感谢我,2年后,你会感谢你自己! 另外,这篇文章中没有讲关于如何规划时间,如何更好的学习的事情,这个不是最重要的,大家感兴趣的话我后面的文章再分享吧。
技术
# 随笔
酷游
1月22日
0
9
0
2025-01-22
[转]厌倦了NullPointException?Optional拯救你!
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. —Tony Hoare 有人说,当你处理过了空指针异常才真正成为一个Java开发者。抛开玩笑话不谈,空指针确实是很多bug的根源。Java SE 8引入了一个新的叫做java.util.Optional 的类来缓解这个问题。 我们首先看看空指针有什么危险,Computer是一个嵌套的对象,如图: 下面的代码有什么潜在的问题呢? String version = computer.getSoundcard().getUSB().getVersion(); 貌似可行,但是,很多电脑(比如 Raspberry Pi)并没有Soundcard,因此调用getSoundcard会发生什么?毫无疑问,结果自然是在运行时给你抛出一个NullPointException,然后终止程序的执行。 如何避免上面的空指针异常呢?一般的做法就是在调用方法之前进行检测: String version = "UNKNOWN"; if(computer != null){ Soundcard soundcard = computer.getSoundcard(); if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null){ version = usb.getVersion(); } } } 但是,上面嵌套if检测的代码确实不怎么好看。但是没办法,我们需要很多这样死板的没什么意义的代码来避免碰到NullPointException。更恼火的是,这部分代码成了我们业务逻辑的一部分,还降低了代码的可读性。 万一我们忘记对某个可能为null的对象进行非空检测怎么办?使用null来说明某个值缺失是一种错误的方式, 下文将说明这个问题并给出更好的解决办法。 先看看别的编程语言是如何处理这个问题的。 Null的替代物 Grovvy语言有一个?.的操作符,可以安全地处理潜在可能的空引用(C#即将包含这个特性,Java7曾被建议引入这个但是并没有发布。)它是这么用的: String version = computer?.getSoundcard()?.getUSB()?.getVersion(); 如果getSoundcard(),getUSB(),getVersion任意一个返回null,变量version就被赋值为null,不需要额外的复杂的嵌套检测。更好的是,Grovvy还有一个Elvis操作符:?:,可以给类似上面的表达式提供默认值。下面的表达式如果?. 返回了null那么变量version会被赋值为”UNKNOW”: String version = computer?.getSoundcard()?.getUSB()?.getVersion() ?: "UNKNOWN"; 其他的一些函数式编程语言,比如Haskell, Scala,使用了一种别的方式。Haskell有一个Maybe型态,这个型态代表了一种有可选值的类型。Maybe形态的值可能包含一个给定类型的值或者是Nothing(译者注:代表没有值),完全没有空指针的概念。Scala有一种类似的叫做Option[T]的东西来代表类型T的某一个值存在或者没有。因此,你必须显式检测这个值是否存在,如果不存在就不能使用任何Option类型的操作符;这样由于Scala的类型系统,你永远也不会忘记对于空指针的检测。 貌似有点扯远了,那么,Java8给我们提供了什么呢? 果壳里的Optional 受到Haskell和Scala的启发,Java8引入了一个叫做java.util.Optional的类,这一个包含一个可选值的类型,你可以把它当作包含单个值的容器——这个容器要么包含一个值要么什么都没有,如下图: 我们在数据模型里面引入Optional: public class Computer { private Optional soundcard; public Optional getSoundcard() { ... } ... } public class Soundcard { private Optional usb; public Optional getUSB() { ... } } public class USB{ public String getVersion(){ ... } } 用上面的代码,我们一眼就可以看出来一个computer有没有soundcard(他们是optioal,可选的),更进一步,一个声卡也有一个可选的USB端口;新的模型能清晰地反映出一个给定的值是有可能不存在的。这种做法在某些库里面也存在,比如Guava 我们能用Optional对象干什么?Optional对象包含了一些方法来显式地处理某个值是存在还是缺失,Optional类强制你思考值不存在的情况,这样就能避免潜在的空指针异常。 值得一提的是,设计Optional类的目的并不是完全取代null, 它的目的是设计更易理解的API。通过Optional,可以从方法签名就知道这个函数有可能返回一个缺失的值,这样强制你处理这些缺失值的情况。 Optional的正确打开方式 废话扯了这么多,来点实际的例子吧!首先来看看如何使用Optional类来实现传统的空指针检测: String name = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN"); 如果无法理解这段代码,可以复习Java8的lambda和方法引用,见Java8 Lambdas 以及stream pipelining概念,见Processing Data with Java SE 8 Steams 创建Optional对象 如何创建Optional对象呢,有下面几种方式: 空的Optional Optional sc = Optional.empty(); 包含非空值的Optional SoundCard soundcard = new Soundcard(); Optional sc = Optional.of(soundcard); 一旦soundcard是null,这段代码会立即抛出一个NullPointException(而不是等你以后你访问这个空的soundcard对象的时候) 可能为空的Optional Optional sc = Optional.ofNullable(soundcard); 如果soundcard是null那么这个Optional将会是empty. 值存在的时候进行进一步的操作 现在你有了一个Optional对象,你可以显式地处理值存在或者不存在的情况,再也不用想这样如履薄冰地进行空指针检测了: SoundCard soundcard = ...; if(soundcard != null){ System.out.println(soundcard); } 现在,可以使用ifPresent()方法,如下: Optional soundcard = ...; soundcard.ifPresent(System.out::println); 现在,你再也不用显示地进行非空检测了,类型系统帮你干了这件事。如果Optional是empty,上面的代码就不会执行打印了。 你也可以使用isPresent()方法检查某个值是否存在,另外,get 方法可以返回Optional容器里面包含的那个对象,如果没有这个对象,get方法会立即抛出一个NoSuchElementException,这两个方法可以结合起来: if(soundcard.isPresent()){ System.out.println(soundcard.get()); } 但是,并不提倡这样使用Optional。(这么做跟null检测有什么区别?),下面有一些惯用手法,我们来看一下。 默认值和默认操作 在某个操作返回空的时候给出一个默认值也是一个典型的场景,通畅的做法是使用三目运算符(?:) Soundcard soundcard = maybeSoundcard != null ? maybeSoundcard : new Soundcard("basic_sound_card"); 可以使用Optional对象的ifElse方法改进这个代码: Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("defaut")); 如果你想在空值的时候抛出一个异常,可以使用ifElseThrow方法: Soundcard soundcard = maybeSoundCard.orElseThrow(IllegalStateException::new); 使用filter过滤特定值 很多时候你需要调用某个对象的方法并且检查它的一些属性。例如:你可能需要检测一个USB的端口是否是一个特定的版本;如果需要避免空指针异常,通畅的方式是检测非空然后调用getVersion方法,如下: USB usb = ...; if(usb != null && "3.0".equals(usb.getVersion())){ System.out.println("ok"); } 使用Optional的filter可以这么干: Optional maybeUSB = ...; maybeUSB.filter(usb -> "3.0".equals(usb.getVersion()) .ifPresent(() -> System.out.println("ok")); filter方法带有一个Predicate的参数,如果Optional容器里面的对象存在并且满足这个predicate,那么filter返回那个对象,否则就返回empty的Optional。(跟Stream接口的filter类似) 使用map转换值 另外一个比较常见的场景是需要从某个对象里面提取出特定的值。例如:从一个Soundcard对象里面取出一个USB对象然后检测这个usb对象是否是正确的版本。通常可以这么写: if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null && "3.0".equals(usb.getVersion()){ System.out.println("ok"); } } 使用Optional的map方法,如下: Optional usb = maybeSoundcard.map(Soundcard::getUSB); Optional容器里面的值被某个函数(这里是USB的方法引用)作为参数“转换”了,如果Optional是empty那么就什么也不会发生。 结合使用map和filter可以检测某个声卡是否有USB 3.0的接口: maybeSoundcard.map(Soundcard::getUSB) .filter(usb -> "3.0".equals(usb.getVersion()) .ifPresent(() -> System.out.println("ok")); 现在我们的代码看起来比较像是在描述问题了!而且没有任何非空检测,太酷了! 使用flatMap级联Optional 我们已经有一些常见的模式可以通过Optional重构了,那么我们如何用一种安全的方式重构下面的代码呢? String version = computer.getSoundcard().getUSB().getVersion(); 上面的代码都是从一个对象里面取出另外一个对象, 这不正是上文介绍的map吗?我们改写Computer模型对象,让它拥有一个Optional和一个Optional,然后就可以把代码改成这样: String version = computer.map(Computer::getSoundcard) .map(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN"); 但是,这段代码并不能通过编译。为什么? computer变量类型是Optional,因此它调用map方法没有任何问题;但是,getSoundcard()方法的返回类型是Optional这意味着map操作结果的类型是Optional,因此getUsb这个调用是非法的:外面的那个Optional包含的值是另外一个Optional,自然就没有getUsb方法,下图是这个调用的结构(two level Optional): 如何解决这个问题呢?Optional类提供了一个flapMap的方法。这个方法可以对一个Optional使用一个函数转换为一个Optional然后把结果(两个Optional)flatten为一个单个Optional,下图给出了map和flatMap的区别: 用flatMap重写我们的代码: String version = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN"); 第一个flatMap确保返回一个Optioan而不是Optional
技术
# 厌倦了NullPointException?Optional拯救你!
酷游
1月22日
0
6
0
2025-01-22
Java中的迭代与递归
递归 提到迭代,不得不提一个数学表达式: n!=n*(n-1)*(n-2)*...*1 有很多方法来计算阶乘。有一定数学基础的人都知道n!=n*(n-1)!因此,代码的实现可以直接写成: 代码一 int factorial (int n) { if (n == 1) { return 1; } else { return n*factorial(n-1); } } 在执行以上代码的时候,其实机器是要执行一系列乘法的: factorial(n) → factorial(n-1) → factorial(n-2) → … → factorial(1)。所以,需要不断的跟踪(跟踪上次计算的结果)并调用乘法进行计算(构建一个乘法链)。这类不断调用自身的运算形式称之为递归。递归可以进一步的分为线性递归和数形递归。信息量随着算法的输入呈线性增长的递归称之为线性递归。计算n!(阶乘)就是线性递归。因为随着N的增大,计算所需的时间呈线性增长。另外一种信息量随着输入的增长而进行指数增长的称之为树形递归。 迭代 另外一种计算n!的方式是:先计算1乘以2,然后用其结果乘以3,再用的到的结果乘以4….一直乘到N。在程序实现时,可以定义一个计数器,每进行一次乘法,计数器都自增一次,直到计数器的值等于N截至。代码如下: 代码二 int factorial (int n) { int product = 1; for(int i=2; i
技术
# Iteration vs. Recursion in Java
酷游
1月22日
0
22
0
2025-01-22
深入分析事务的隔离级别
本文详细介绍四种事务隔离级别,并通过举例的方式说明不同的级别能解决什么样的读现象。并且介绍了在关系型数据库中不同的隔离级别的实现原理。 在DBMS中,事务保证了一个操作序列可以全部都执行或者全部都不执行(原子性),从一个状态转变到另外一个状态(一致性)。由于事务满足久性。所以一旦事务被提交之后,数据就能够被持久化下来,又因为事务是满足隔离性的,所以,当多个事务同时处理同一个数据的时候,多个事务直接是互不影响的,所以,在多个事务并发操作的过程中,如果控制不好隔离级别,就有可能产生脏读、不可重复读或者幻读等读现象。 在数据库事务的ACID四个属性中,隔离性是一个最常放松的一个。可以在数据操作过程中利用数据库的锁机制或者多版本并发控制机制获取更高的隔离等级。但是,随着数据库隔离级别的提高,数据的并发能力也会有所下降。所以,如何在并发性和隔离性之间做一个很好的权衡就成了一个至关重要的问题。 在软件开发中,几乎每类这样的问题都会有多种最佳实践来供我们参考,很多DBMS定义了多个不同的“事务隔离等级”来控制锁的程度和并发能力。 ANSI/ISO SQL定义的标准隔离级别有四种,从高到底依次为:可序列化(Serializable)、可重复读(Repeatable reads)、提交读(Read committed)、未提交读(Read uncommitted)。 下面将依次介绍这四种事务隔离级别的概念、用法以及解决了哪些问题(读现象) 未提交读(Read uncommitted) 未提交读(READ UNCOMMITTED)是最低的隔离级别。通过名字我们就可以知道,在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。 未提交读的数据库锁情况(实现原理) 事务在读数据的时候并未对数据加锁。 务在修改数据的时候只对数据增加行级共享锁。 现象: 事务1读取某行记录时,事务2也能对这行记录进行读取、更新(因为事务一并未对数据增加任何锁) 当事务2对该记录进行更新时,事务1再次读取该记录,能读到事务2对该记录的修改版本(因为事务二只增加了共享读锁,事务一可以再增加共享读锁读取数据),即使该修改尚未被提交。 事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(因为事务一对数据增加了共享读锁,事务二不能增加排他写锁进行数据的修改) 举例 下面还是借用我在数据库的读现象浅析一文中举的例子来说明在未提交读的隔离级别中两个事务之间的隔离情况。 事务一 事务二 /* Query 1 */SELECT age FROM users WHERE id = 1;/* will read 20 */ /* Query 2 */UPDATE users SET age = 21 WHERE id = 1;/* No commit here */ /* Query 1 */SELECT age FROM users WHERE id = 1;/* will read 21 */ ROLLBACK;/* lock-based DIRTY READ */ 事务一共查询了两次,在两次查询的过程中,事务二对数据进行了修改,并未提交(commit)。但是事务一的第二次查询查到了事务二的修改结果。在数据库的读现象浅析中我们介绍过,这种现象我们称之为脏读。 所以,未提交读会导致脏读 提交读(Read committed) 提交读(READ COMMITTED)也可以翻译成读已提交,通过名字也可以分析出,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。 提交读的数据库锁情况 事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。 现象: 事务1在读取某行记录的整个过程中,事务2都可以对该行记录进行读取(因为事务一对该行记录增加行级共享锁的情况下,事务二同样可以对该数据增加共享锁来读数据。)。 事务1读取某行的一瞬间,事务2不能修改该行数据,但是,只要事务1读取完改行数据,事务2就可以对该行数据进行修改。(事务一在读取的一瞬间会对数据增加共享锁,任何其他事务都不能对该行数据增加排他锁。但是事务一只要读完该行数据,就会释放行级共享锁,一旦锁释放,事务二就可以对数据增加排他锁并修改数据) 事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(事务一在更新数据的时候,会对该行数据增加排他锁,知道事务结束才会释放锁,所以,在事务二没有提交之前,事务一都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象) 举例 事务一 事务二 /* Query 1 */SELECT * FROM users WHERE id = 1; /* Query 2 */UPDATE users SET age = 21 WHERE id = 1;COMMIT;/* in multiversion concurrencycontrol, or lock-based READ COMMITTED */ /* Query 1 */SELECT * FROM users WHERE id = 1;COMMIT; /*lock-based REPEATABLE READ */ 在提交读隔离级别中,在事务二提交之前,事务一不能读取数据。只有在事务二提交之后,事务一才能读数据。 但是从上面的例子中我们也看到,事务一两次读取的结果并不一致,所以提交读不能解决不可重复读的读现象。 简而言之,提交读这种隔离级别保证了读到的任何数据都是提交的数据,避免了脏读(dirty reads)。但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据。 可重复读(Repeatable reads) 可重复读(REPEATABLE READS),由于提交读隔离级别会产生不可重复读的读现象。所以,比提交读更高一个级别的隔离级别就可以解决不可重复读的问题。这种隔离级别就叫可重复读(这名字起的是不是很任性!!) 可重复读的数据库锁情况 事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。 现象 事务1在读取某行记录的整个过程中,事务2都可以对该行记录进行读取(因为事务一对该行记录增加行级共享锁的情况下,事务二同样可以对该数据增加共享锁来读数据。)。 事务1在读取某行记录的整个过程中,事务2都不能修改该行数据(事务一在读取的整个过程会对数据增加共享锁,直到事务提交才会释放锁,所以整个过程中,任何其他事务都不能对该行数据增加排他锁。所以,可重复读能够解决不可重复读的读现象) 事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(事务一在更新数据的时候,会对该行数据增加排他锁,知道事务结束才会释放锁,所以,在事务二没有提交之前,事务一都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象) 举例 事务一 事务二 /* Query 1 */SELECT * FROM users WHERE id = 1;COMMIT; /* Query 2 */UPDATE users SET age = 21 WHERE id = 1;COMMIT;/* in multiversion concurrencycontrol, or lock-based READ COMMITTED */ 在上面的例子中,只有在事务一提交之后,事务二才能更改该行数据。所以,只要在事务一从开始到结束的这段时间内,无论他读取该行数据多少次,结果都是一样的。 从上面的例子中我们可以得到结论:可重复读隔离级别可以解决不可重复读的读现象。但是可重复读这种隔离级别中,还有另外一种读现象他解决不了,那就是幻读。看下面的例子: 事务一 事务二 /* Query 1 */SELECT * FROM usersWHERE age BETWEEN 10 AND 30; /* Query 2 */INSERT INTO users VALUES ( 3, 'Bob', 27 );COMMIT; /* Query 1 */SELECT * FROM usersWHERE age BETWEEN 10 AND 30; 上面的两个事务执行情况及现象如下: 1.事务一的第一次查询条件是age BETWEEN 10 AND 30;如果这是有十条记录符合条件。这时,他会给符合条件的这十条记录增加行级共享锁。任何其他事务无法更改这十条记录。 2.事务二执行一条sql语句,语句的内容是向表中插入一条数据。因为此时没有任何事务对表增加表级锁,所以,该操作可以顺利执行。 3.事务一再次执行SELECT * FROM users WHERE age BETWEEN 10 AND 30;时,结果返回的记录变成了十一条,比刚刚增加了一条,增加的这条正是事务二刚刚插入的那条。 所以,事务一的两次范围查询结果并不相同。这也就是我们提到的幻读。 可序列化(Serializable) 可序列化(Serializable)是最高的隔离级别,前面提到的所有的隔离级别都无法解决的幻读,在可序列化的隔离级别中可以解决。 我们说过,产生幻读的原因是事务一在进行范围查询的时候没有增加范围锁(range-locks:给SELECT 的查询中使用一个“WHERE”子句描述范围加锁),所以导致幻读。 可序列化的数据库锁情况 事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放; 事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。 现象 事务1正在读取A表中的记录时,则事务2也能读取A表,但不能对A表做更新、新增、删除,直到事务1结束。(因为事务一对表增加了表级共享锁,其他事务只能增加共享锁读取数据,不能进行其他任何操作) 事务1正在更新A表中的记录时,则事务2不能读取A表的任意记录,更不可能对A表做更新、新增、删除,直到事务1结束。(事务一对表增加了表级排他锁,其他事务不能对表增加共享锁或排他锁,也就无法进行任何操作) 虽然可序列化解决了脏读、不可重复读、幻读等读现象。但是序列化事务会产生以下效果: 1.无法读取其它事务已修改但未提交的记录。 2.在当前事务完成之前,其它事务不能修改目前事务已读取的记录。 3.在当前事务完成之前,其它事务所插入的新记录,其索引键值不能在当前事务的任何语句所读取的索引键范围中。 四种事务隔离级别从隔离程度上越来越高,但同时在并发性上也就越来越低。之所以有这么几种隔离级别,就是为了方便开发人员在开发过程中根据业务需要选择最合适的隔离级别。 参考资料 维基百科-事务隔离 数据库隔离级别 及 其实现原理
技术
# 数据库
酷游
1月22日
0
6
0
上一页
1
...
6
7
8
...
112
下一页
易航博客