游记 张北草原天路自驾之旅

大家好呀,我是 Meng小羽,上个周末与大学同学相约,一起去了张北草原草原天路自驾,因为 8 月份是草原最好看的季节,因为属于花季,漫山的野花与初秋的微凉,给人以心旷神怡之感。我们从天路东侧往西侧反穿,一路上堵车相对来说比较少一些,总体来说,东线比西线的商业化多一些,带小孩可以走东线,西线更适合情侣和朋友结伴同行。时间充足的建议一条线游玩下来。 路线 一共两天时间,第一天时间主要是游玩东线,从桦皮岭附近的入口进入,也就是草原天路东路口,之后沿线一路向西,之后傍晚看完日落之后就驱车前往民宿。第二天驱车前往野狐岭收费站也就是西线入口,但是由于堵车,于是走了一段小路,走了一小段东线,途经经营的花海、白龙洞等景区,之后返程回京。 东线 第一天主要是赏景,因为正值 8 月底,漫山的小花竞相绽放,在即将到来的冬天完成对生命的繁衍,蓝天白云草原风车共同勾勒出初秋的草原。 话不多说,上风景。 第一天主要是看草原、森林等自然风光,东线更多的是让大家登高望远,当然还有沿途的驿站,解决大家的舟车劳顿。 下面的花海是第二天拍摄的,是一个收费的园子,一人 30 块,一个山坡上种的都是各种各样的花,非常值得驻足进去观赏。 西线 西线沿途也都是相同的风景,去白龙洞的途中,从山上往下远眺,远处的道路感觉就像是赛车道一样,蜿蜒曲折。 结 张北草原算是距离北京最近的一个草原了,同时商业化配套齐全,由于处在旅游景区内,消费总体来说和北京差不多,一路上的美景看了不少,夏末秋初,已然没有了夏季的燥热,而是秋高气爽,别忘记带个外套。 最后,今年博客开始记录游记,到本文已经是第三篇了,大家感兴趣的可以去阅读游记系列文章,一起环游中国。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/9/6
articleCard.readMore

免费无版权图片素材网站推荐

大家好呀,我是 Meng小羽,在 21 年的时候我就做过一期分享,就是关于无版权免费图片网站推荐的文章,但是后来由于博客搬家丢失了这个文章,为了大家便于后续查找无版权免费图片素材,特地给大家再次分享一下。 Unsplash Unsplash 是一个免费的图片分享网站,允许用户上传和下载高质量的、可免费用于商业和非商业用途的照片。摄影师可以将自己的照片上传到Unsplash,经过整理后供其他用户免费下载和使用。Unsplash 提供了大量的、不同主题和风格的图片,并且对图片的使用没有版权限制,无需署名。 官网地址:https://unsplash.com Pixabay Pixabay 是一个免费图片、视频、插图和音乐素材网站,提供海量可免费下载和使用的资源,适用于商业和个人项目。Pixabay 上的内容遵循 Pixabay 许可证,类似于知识共享协议,用户可以免费使用、修改和分发这些素材,无需支付版税或标明出处。 官网地址:https://pixabay.com/zh/ Hippopx Hippopx 是一个提供免费、免版权图片的图库网站。它收录了超过20万张以上,且质量很高的授权照片。用户可以免费下载这些图片,用于各种商业用途,比如制作PPT背景、电脑桌面,或是作为设计素材。Hippopx 还提供多语言接口,包括中文,方便用户搜索和使用。 官网地址:https://www.hippopx.com/zh/ Negative Space Negative Space 是一个提供免费高品质图片的网站。它旨在为摄影师提供一个分享作品的平台,并提供给用户免费下载和使用的优质图片资源。 图片涵盖了生活、旅游、商务、美食、工业、技术、环境等多个主题。 Negative Space 的核心理念是“留白”,即在设计中,用空白区域来突出图像、文字或其他元素,从而提升视觉效果和用户体验。网站上的图片可以免费下载和使用,这使得它成为设计师、博主、以及任何需要高质量图片的人的理想选择。 官网地址:https://negativespace.co Vecteezy Vecteezy 是一个提供免费和付费矢量图、照片、视频和设计模板等创意素材的在线平台。它为设计师和创意人员提供了一个丰富的资源库,涵盖了各种主题和风格,包括插图、图标、背景等,并且支持在线编辑和定制。 官网地址:https://www.vecteezy.com Freerange Freerange Stock 是一个提供免费高分辨率图片素材下载的网站。该网站的图片素材来源多样,包括内部摄影、用户提交以及其他渠道。Freerange Stock 上的图片可供免费下载和使用,适用于各种创意项目,甚至可以用于商业用途,具体取决于所使用的许可证。 官网地址:https://freerangestock.com Colorhub Colorhub 是一个提供免费高清无版权图片资源的网站。它收录了大量高质量图片,并支持中文关键词和颜色搜索,方便用户查找所需素材。此外,Colorhub 还提供图片联想功能,可以找到与指定图片相似的图片资源。 官网地址:https://colorhub.me Google Photo 谷歌图片,选择 “工具” -> “知识共享许可” -> “知识共享许可” 即可搜索你想搜索的无版权图片。 官网地址:https://www.google.com AI 其实现在 AI 比较火的就是制作图片或者视频,目前并没有明确生成的图片版权问题,可以放心使用,但是需要注意生成的视频与一些申请版权专利的 IP 冲突。 国内的豆包,国外的 ChatGPT、Grok 等 AI 支持多模态生成的均可以生成图片,目前由于算力成本有次数限制。 好了,今天给大家推荐这几个网站,另外希望你可以把你经常使用的无版权免费网站分享到评论区,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/8/23
articleCard.readMore

首场 Trae Meetup, 国产 AI IDE 有着不一样的思路

序 大家好呀,我是 Meng小羽,今天报名参加了国产 AI 智能编辑器 Trae 的首次线下分享会,今天上午还遇到了一个趣事,一直追番的《凡人修仙传》迎来了韩立的结婴一集,其神识(热度)之强大把哔哩哔哩都整崩了,还把微博也整降级了。本次参加 Meetup,最主要的是想了解 Trae IDE 的发展历程以及后续的开发计划,当然最主要的是 SOLO 模式。 天猪 / Trae 发展历程及 SOLO 模式 作为 Trae IDE 核心开发者,天猪从系统架构与迭代历程入手,梳理并回顾了 Trae IDE 的发展里程碑,包括 2024 年 Trae 上线、2025 年国内外版本发布,以及月活跃用户突破 100 万的关键节点。同时,他阐述了 AI Coding 的三个阶段(辅助编程、结对编程、自主编程),以及 Trae Agent 的架构演进和 SOLO 模式的协作创新。 目前,大部分的公司云端容器还停留在微服务或者微服务到 Faas 过渡的时期,23 年我还做过 FaaS 相关技术的调研:FAAS 调研笔记,设计思路非常的先进,但是需要大量的技术基础能力的建设与迭代,至今我的工作环境大部分还是在微服务阶段,同时 JDK 1.8 似乎是每一个开发者和公司跳不出的大山。 24 年,随着 Idea 辅助编程插件的火热,市场也如雨后春笋冒了出来,一线大厂都推出了自己的辅助编程软件,同样我司 AI 开发团队也推出了相应的工具,这个阶段更可以理解为智能提示 Plus 版本 + AI 集成环境。 成为坚定的 Trae 重度使用用户是因为 Trae IDE 简洁大方的 UI 设计,不知道大家有没有同感,VSCode 和 Cursor 的 UI 设计实在是有些粗糙,Trae 的设计首先从 UI 上就给我带来简洁如 Idea,简约而不简单的感觉,也是我果断放弃使用 VSCode 的一个重大原因,初期,在使用 AI 模型相关辅助编程中,所有的 IDE,除了 Cursor ,基本上智商都处在一个水平。Trae 作为后起之秀,随着模型理解能力的越来越强大加上 Trae 提示及其适配优化的越来越专业,在使用上也逐步媲美 Cursor。 SOLO 模式与 Cursor 和其他众多的 AI IDE 不一样的是,他是从 AI 辅助 IDE,进化成了 AI 主导 IDE,这种设计模式将 Code、Tools、MCP 、浏览器等众多服务变成了为 AI 服务的对象,AI 的角色变成了开发工程师,而开发的角色转变成为了产品经理,通过对话,直接一步到位呈现效果,降低了一个 Idea 转变为产品的成本,但是这种模式也非常考验模型的理解能力与协调能力。 江波 / Trae cue 功能设计与挑战 聚焦 TRAE 的智能编程工具 Cue(Context Understanding Engine),介绍了其核心功能,如代码补全、多点编辑和智能导入。他分析了 Cue 面临的三大挑战:用户意图理解(非线性编辑历史导致的意图偏差)、修改位置确定(兼顾可扩展性与速度)、编辑执行(支持复杂编辑与仓库上下文感知)。此外,他分享了近期优化成果,包括时延从 1s 降至 500ms、融合模型升级,以及支持多语言自动导入,未来有支持基于模型的仓库级跳转预测以更多语言扩展的计划。 Cue 功能其实和 Cursor Tab 功能十分的相似,与 Trae SOLO 模式走的是完全相反的两个方向的道路,SOLO 模式目前更像是给轻度技术者快速实现功能的 IDE,而 cue 模式是现在主流 AI IDE 核心增强的能力。 cue 功能也面临着 AI 模型版本与理解思维差异的问题,Trae 作为模型整合层,需要不断的适配模型的“个性”来满足用户。 cue 功能与 Cursor Tab 功能在我们开发中起到了非常重要的作用,开发效率有了质的提升,但是我认为非常有挑战的就是江波分享的一个点,就是 AI 什么时候该接入帮我们写代码,过度的介入会引来开发者的反感,克制的介入同样也会引起开发者对于 AI 能力的质疑,这个度该如何平衡? 尾 非常有幸报名参加了 Trae 的第一场线下 Meetup,无论是 SOLO 模式还是 cue 功能,对于开发者而言,在我们日常开发中都给我们提供了非常多的便利与效率的提升,Trae IDE 还在高速发展阶段,期待未来可以成为 AI IDE 的顶流产品。 最后,如果你对我的文章及分享感兴趣的话,欢迎你关注我,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/8/17
articleCard.readMore

赞赏

网站现在开启打赏模式,你若是感觉我的文章对你有所帮助,欢迎你通过打赏一杯咖啡的方式激励我继续创作,让我们一起变得更强~ 当然,面对极客,我也支持通过区块链币进行打赏: Ethereum 网络:ETH / USDT 1 0xd06a56704ecb1eee65abcaa3101d88e2a8225a4e Solana 网络:SOL / USDT / V2EX 1 5LupLo8NuTfC411zAPo6UZTuasoTHaueuptVyGKmsdBV 当然,你也可以选择领取支付宝红包,这样你可以获取红包,我也可以得到支付宝的赞赏。 最后,推荐你关注我的公众号,本站文章会第一时间同步推送,再次感谢你的支持,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/8/13
articleCard.readMore

港卡开卡指南

大家好呀,我是 Meng小羽,最近去香港旅游,顺便办了港卡,给大家分享下办理的指南,先说下成果,一共办理了 5 张卡,其中 2 张实体卡有中国银行(香港)卡、汇丰 HSBC 红狮子卡(One 账户),其他 3 张都是数字银行:众安银行、天星银行还有蚂蚁银行。 总的来说开户难度,中银香港开户 > 汇丰 One 账户开户> 数字银行开户: 中银香港的卡是最难办理的,因为需要提前预约,现在港卡办理非常的火热,每个营业点下发的预约数量非常的少,都是需要零点进行预约,实际上可以不用预约,但是需要提前到营业点询问是否可以办理(我就是使用这种方式办理的); 汇丰香港:因为没有达到香港汇丰卓越的标准,一般办理的都是 One 账户,直接可以通过 APP 办理即可; 数字银行也是同样的; 接下来给大家详细介绍下开卡步骤,需要本人到港。 材料准备 临近去香港的前几天,可以准备或者 check 下材料,便于去柜台快速开户下卡: 身份证件:大陆居民身份证 + 港澳通行证(有效期超过半年); 入港凭证:【到港才有】这个是进入到香港海关发的入境凭证,也就是俗称的“过关小票”; 出入境证明:【到港才有】出入境记录 PDF; 微信移民局 12367 小程序 - 中国公民服务 - 出入境记录查询 - 三个月 - 查询 - 记录下载 即可获取; 居住证明:证明自己在大陆内地住址证明(最好是最近一个月的),以下材料任选其一即可: 银行账户证明材料,我是使用招行 APP 在线申请,里面包含“家庭地址”一栏,若不是现居住地址,需要在个人材料设置里面修改成现居住地址,之后再申请即可; 银行信用卡的账单信息材料,各大银行信用卡 APP 就可以申请,需要确保材料上包含“家庭地址”的证明; 家庭缴纳水费、电费凭证信息也可以作为地址证明材料; PS. 这里居住证明最好是自己实际居住的地址,因为数字银行和汇丰邮寄卡片需要; 财务证明:提供大于半年,最好一年的银行卡流水即可,银行 APP 即可申请下载; 投资证明:最好是有股市投资记录,其实支付宝基金持仓证明即可、据说招行朝朝宝、月月宝等银行理财产品也行,只要能开具证明即可,以下材料任选其一或者准备两个即可: 【推荐】A 股对账单:中国结算公众号查询下载即可; 【推荐】当然有港美股投资证明最好,我是用的长桥就可以直接提供月对账单即可; 支付宝持仓证明材料; 银行理财产品持仓证明材料:银行的理财产品(含基金份额的); 个人所得税证明:个人所得税缴纳证明,申请导出最近半年或者更长的证明材料即可; 官方 APP 或者支付宝市民中心或者微信都可以查看下载; APP 准备:提前下载好 BOCHK 中银香港、HSBC HK(汇丰香港)、ZA Bank(众安银行)、天星银行、蚂蚁银行的 APP,省的到地方之后网络状况不好等待好长时间; 众安银行:专属邀请码 J36MK3 ; 天星银行:专属邀请链接; 漫游流量短信:去香港的提前两天,通过支付宝就可以购买香港大湾区的漫游流量,最好给运营商客服打电话确认下开通情况,以及确认下是否开通境外接收短信业务,没有的话让一块办理即可,因为办理需要接收短信,不提前办理的话,到达香港现场办理会变得很麻烦; 中国银行银行卡:用于办理中银香港卡,提前在大陆开通中国银行的银行卡(一类二类卡均可),存入资金养卡即可; 手机带有 NFC 功能; 中银香港开户 中银香港开户目前有两种方式: 一种是到线下网点排队办理,可以现场拿卡; 另外一种是线上申请办卡,后续邮寄; PS. 与线下的区别就是无法开通银行港股投资账户; 我选择的是线下网点办理,我去办理时候正好赶上阴雨天气,银行给我的回复说是由于有人预约没有到场,把预约的机会给了我,实际上基本上到有名额的网点都可以给办理,大家最好早点去并且避开小红书热门网点和旅游路线上的热门网点,网点很多,基本上地铁多坐两站地就可以避开很多办卡高峰网点。 办理的时候快到下午 3 点多了,在酒店看了附近的网点,发现最近的是土瓜湾附近的支行,由于下雨的原因,营业网点人比较少,等了两分钟就开始办理了,把“材料准备”的材料准备好,就等着柜员开始讲解开户的详情及相关材料,之后准备好发送给柜员邮箱就可以了,之后柜员会把材料打印出来,之后一一让打开 APP 验证某天某个时间点的交易记录,确认完之后就可以等待下卡了,我去的网点需要等待半小时左右(柜员说需要等 2 小时左右),剩下的时间就是和柜员闲聊,等待下卡。 最后柜员建议从中国银行“跨境直付通”转到中银香港 5000 人民币,无损按照当日汇率转到中银香港账户激活。 若是柜员问开户原因,基本上就实事求是说储蓄、投资等一类的就行,基本上不会拒。 汇丰 One 账户开户 由于我不满足汇丰香港卓越卡的资金要求,One 账户不要求到网点开卡,于是连接上酒店 Wifi 就开始打开 HSBC HK APP 申请开户了,按照 APP 的流程,会用到上面材料准备的材料,之后按照提示步骤一步一步申请即可。 开户本身没有什么难度,但是由于收入和地址填写错误会导致开户失败,需要注意一下几点: 英文地址 这里的英文地址填写要填写自己居住的地址(电邮地址),后续制卡邮寄的地址。 存入资金 开通账户之后,最好是转入一部分资金(大于港币 100 以上),防止冻结卡,到时候就得远程或者再飞到香港柜台办理解冻了。 邮寄问题 开通完账户之后,大约 7 个工作日左右就可以收到 EMS 平邮的实体卡了。 等待 10 天甚至更多时间,没有收到实体卡,电话联系客服进行联系看是否邮寄以及补寄操作。 内地:4008004818, 香港:22333000 激活问题 拿到实体卡之后,不需要等待密码函邮寄到,从 APP 上客服页面即可申请激活卡片,申请激活等激活成功短信前不要进行任何资金操作,据说会被冻结账户。 密码函 密码函目前我也没有收到,收到同步。 数字银行开户 数字银行开户就比较简单了,目前香港有 8 家数字银行,只需要准备好我上面所述的材料,之后连上 Wifi,就可以一家一家 APP 逐个申请了,最好是到港第一天就申请,因为需要等待一个工作日左右账户才会审核通过,有什么问题也可以有时间处理,我这里推荐办理 众安银行和天星银行的数字银行,开通两家就足够使用。 众安银行 ZA Bank:专属邀请码 J36MK3 ,是香港第一的数字银行,界面简洁,UI 是这些银行中相对友好的; 天星银行 Airstar Bank:专属邀请链接,小米和富途占股银行,港币兑换美元汇率与其他银行相比比较优惠些; 最后,如果你对港美股开户、以及香港银行卡、新加坡银行卡开通感兴趣或者开户有问题,欢迎关注微信公众号「Debug 客栈」并私信留言,我会第一时间给大家解答疑问,另外你有更便捷的开卡方式,推荐分享到评论区,帮助更多的人。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/8/3
articleCard.readMore

游记 港澳深大湾区风球中旅程

大家好呀,我是 Meng小羽,计划去大湾区旅游一直是 25 年的规划之一,一方面计划去香港来一趟 City Walk 和办理港卡,另外一方面计划去深圳水贝购买饰品,因为和我家领导订婚之后由于黄金价格水涨船高,一直没有购置,听说水贝是按照大盘价走的,款式非常多,性价比也比较高,还有就是顺道去一趟赖导视频下的氹仔岛-澳门。于是调休了两天,开始了 4 天 3 夜的特种兵旅程。 序 从 21 年办理完港澳通行证之后,当初不清楚政策,只办理了通行证,并没有办理签注,在小红书攻略了去港澳的细节才发现,第一次签注需要去人工窗口去办理 🤦‍♂️,于是单独抽了一个工作日的下午去出入境大厅办理,因为第一次办理通行证的时候还是长发,还记得当初拍照的时候一直不满足要求,工作人员索性让我留了中分,证件下来的时候看到自己像翻译官的样子苦笑不得,这一次办理签注的时候就索性重新办理了一张,同时连同护照也一块办理了。 本来计划六月份去,由于需要乘坐飞机,于是从天气预报中一直在看北京和香港两地的天气,天公不作美,始终是阴雨雷暴天气,在上周终于迎来了窗口期,看着两地天气都是晴朗,于是就定下来了旅程中的机票,计划从北京直飞香港,之后从香港回深圳,再从深圳自驾前往澳门,最后从澳门回深圳搭乘飞机返回北京。 但是万万没有想到的是,去香港的当天是台风(当地人称为风球)来临前的头一天的放晴,临近香港的时候在南海上,看到了东南方向巨大的云团,没想到是第三天逼近的台风韦帕。于是这次行程也是在风球笼罩下的旅行。 东方之珠 Day 1 入境香港 本来计划飞到深圳,之后去香港两天都坐高铁去,但是由于我家领导户口是新疆边疆地区,签注办理的是一年一次,再次续签在自助机通过不了,只能人工签注,所以修改行程,直飞香港,到香港国际机场的上空,看到了巨大的台风逼近的云团,天空还下着小雨,我是落地香港听工作人员说有风球才意识到可能旅程会与台风相会。 飞机上,邻座的是一位来自北科大的教授,来香港学习交流的,但是临下飞机发现没有网络,我们给他开了 Wifi,在线购买了漫游流量与协助拨打了运营商电话,之后开通了网络后分别而去。 酒店就在地铁旁边,为了方便出行,从机场就出发去酒店,到了酒店之后就感受到了香港的特色就是冷气给的特别足,无论是进酒店还是餐馆商场,超冷,第一天在香港在哪里都不适应,室内冷气十足,室外湿热与大海的鱼腥味混合着,十分不舒服。到了第二天才适应了过来,才体会到了香港冷气足的原因,就是外面太湿热了,只有冷气可以续命。 下午就和对象去酒店旁边的银行去办理港卡,很巧的是遇到了同龄的柜员,应该说也比较幸运,因为我们去的时候就还剩 2 个名额,果断给我们办理,在很随和的沟通的过程中办理了下来,还给我们推荐了附近的特色美食。 临近傍晚,吃完饭,我们就出发去了维多利亚港,香港的繁华在这一瞬间变得具象化了。 维多利亚港的来源是源自于清政府签署《南京条约》之后,英国人以当时在位的女王命名的港湾。不过现在去香港正好遇到了回归 28 周年相关的活动。 晚上 8 点,维港都会绽放着属于她的魅力,维港灯光秀,不愧是世界三大夜景之一。 观看完维港灯光秀之后就返回酒店了,正好遇到了躲避台风的游轮,不得不说,香港的空间真的是狭小。查阅资料才知道,香港是世界人口密度排名前 5 的地区。 Day 2 City Walk 第二天开始了香港一日游的 City Walk,旺角 - 油麻地警署 - 尖沙咀码头 - 天星小轮 - 中环天星码头 - 皇后大道 - 中环扶手电梯至太平山顶(由于下雨没有去)。 旺角,原本计划去著名的香港招牌街景,但是找寻了几条街都没有发现霓虹灯大招牌。 油麻地警署,港片警匪片取景地,当然要来一张。 当然,必然少不了李嘉诚成功人士三件套(紧握财富、财向我来、指定发财)。 此时,台风的核心团状云已经到达香港附近,可以看到乌云变得特别多,但是也没有多想,因为计划今晚就离港回深圳了。 之后体验了 127 年历史的天星小轮,驶向香港岛。 到了天星摩天轮,因为台风即将到来,已经停止营业了,只给我家领导拍了几张照片,就导航去了皇后大街。 皇后大街,街拍 && 街景。 游玩到皇后大街,本来计划着乘坐半山电梯去太平山顶,但是由于开始下雨,就提前结束了香港的旅程,吃过饭后就准备返程了。 返程乘坐的是西九龙的高铁直达深圳北的动车,不得不说中国的基建的厉害之处,整个动车行驶的路线都在地下隧道里穿梭,出了隧道,也就到了深圳。 香港,完。 鹏城深圳 由于到了深圳已是深夜,于是直接到酒店,一晚上的台风风暴,在第二天看到有人发维港现场的视频,已经被风推着走了,狂风大浪,还好,幸运的游完了香港。 Day 3 水贝行 次日早起,原本计划今日租车驱车前往澳门,从导航上得知深中大桥已经封闭,无法前行,于是,先去水贝购买饰品了,两个人没见识到全部都是金店的大场面,于是一张照片没有留存 👀。 澳门之行 Day 4 澳门行 第 4 天早上,查看台风最新的动向,发现台风主体已经过了海南海口,但是还有漩涡状的云向大湾区袭来,但是查看天气情况,说下午会多云,于是使用导航软件,发现可以从深中大桥通过,于是早上 7 点多神州租车上租车就开始了自驾驱车前往珠海拱门口岸。 在路上顺路去了一趟微众银行总部,去领取了一下微众发行的一类卡,听说这张卡可以在任意 ATM 机取现免费,嗯,其实也没有这个需求,就是一个集卡爱好者。 一路上阴雨交加,来到口岸停车场(8 元/h)已经 11 点多,出了停车场就直奔出入境大厅过关了,过关之后到达广场开始乘坐小红书上推荐的“发财车”,去往新葡京,此时雨越下越大,到达新葡京的时候已经开始瓢泼大雨,在附近吃了碗猪脚面,就开始体验特色了。 嗯,分享成果,给了两张体验卡,最后老虎的机器吐出来了 $180 港币,不过不建议玩,因为赢钱是会给人带来上瘾的感觉的,大家要有十赌九输的意识。这样的场所也和宜家的套路一样,乘坐“发财车”,每到一个酒店,都需要让游客穿越这样的场地,来吸引大家留下驻足并从心理乃至行动上为此买单。 威尼斯人城给我的第一感觉就是和电影《楚门的世界》太像了,在这里紫醉金迷,无论外面的天地是阴雨还是天晴、暗夜还是白昼。 其实当天外面台风的尾巴还是扫到了澳门,从吃过饭开始,就一直是中到大雨的一直下,游玩都是在室内,像是大三巴牌坊、澳门会展娱乐中心、妈阁庙、黑沙海滩这些室外的都没有去。 由于晚上需要去赶飞机,半天的行程变得有些急促,就匆匆结束了澳门之行。 尾 现在回想四天三夜的旅程,只能说这次旅行我们是幸运的,在风球来临香港之前还能见识到世界三大夜景之一的震撼与美丽,晚上八点还可以观赏到上百栋高楼大厦带来的灯光秀。台风过境的日子深圳也没有受到太大的影响。在最后一天,还有时间与路况去一趟澳门。 但是这次旅程也见识到了大自然的威力,台风天气所带来的影响与破坏是巨大的,在深圳待的一天,路上随手可见被大风吹折的树木。要是提前得知台风的消息,绝对会取消这一趟行程。还好,大自然让人敬畏也会带来幸运。以后的旅行要多注意天气状况。 最后,感谢你的阅读,你要有不一样的旅程,非常感谢你在评论区分享你的奇遇之旅。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/7/30
articleCard.readMore

我的使用

基本上所有大件物品购买基本上我采用《三天原则》,“连续三天,考虑这个设备我有没有必要买,买了我使用的频率又是怎么样的呢?在第三天的时候还是强烈的需求,则加购,若没有,则放弃购买”。在这个原则下,我还是有很多吃灰的设备和多余的软件或者服务付费,但是已经相对理性了。同时希望大家理性消费。 硬件 目前我的硬件基本上属于 Apple 生态 + 米家生态,用起来比较省心,极具性价比,同时满足我软件开发以及日常使用的大部分场景。 名称 设备 推荐指数 评价 Apple Mac mini M4 16GB+512GB 电脑 🌟🌟🌟🌟🌟 家庭主力机器,性价比极高 Apple MacBook Pro 14″ M4 Pro 24GB+512GB 电脑 🌟🌟🌟🌟 工作主力机器 LG 27UN880 4K 显示器 Ergo 支架 显示器 🌟🌟🌟🌟 搭配 mini 使用,色彩饱和度、对比度相对优秀 小米 智能家庭屏 Mini 音箱 🌟🌟🌟🌟🌟 搭载小爱同学,智能设备一句话的事~ 小米 米家智能显示器挂灯 1S 灯具 🌟🌟🌟🌟🌟 可调亮光,和护眼色,显示器必备搭档 Filco 87 键双模圣手二代 侧刻 红轴 键盘 🌟🌟🌟🌟 机械键盘的中档产品,使用起来很稳定,小贵 AENZR 8 合 1 桌面拓展坞 拓展坞 🌟🌟🌟🌟🌟 mini 最佳配件,导照片神器 小米 无线蓝牙双模鼠标 2 代 鼠标 🌟🌟🌟🌟🌟 极具性价比,稳定不卡顿,不用考虑续航 Apple iPhone 15 Pro Max 256GB 手机 🌟🌟🌟🌟 电量续航无压力,是真的强,就是贵 Apple iPad 10 2022 款 平板 🌟🌟 使用频率低,阅读使用,学习使用 Apple pencil USB-C 触控笔 🌟🌟 由于使用 pad 10,不支持无线充电,充电不太方便,推荐另外一款 Apple Watch Series9 GPS 45mm 手表 🌟🌟🌟🌟🌟 运动必备,每日洗澡的时候充会电,全天使用 Apple AirPods 4 降噪款 耳机 🌟🌟🌟🌟🌟 习惯了过滤这个世界的吵杂的必选降噪,半入耳舒适 小米 磁吸自带线充电宝 1w 33W 充电宝 🌟🌟🌟🌟🌟 苹果配件厂,果然名不虚传 IKEA 波席当 升降桌 工作台 🌟🌟🌟🌟 简约,没有档位记忆,需要手一直按着升降 Sony α6400 + 18-135 镜头 相机 🌟🌟🌟🌟🌟 主力相机,出去玩必带,出片神器,聚焦嘎嘎快 Sony 16-50 饼干镜头 镜头 🌟 买了吃灰,强烈不推荐购买,吃灰的!!! Sony ECM-W3 无线蓝牙麦克风 麦克风 🌟 买了吃灰,本来打算用来拍 Vlog,实际没有开始… 软件 软件由于使用 Apple 生态,也分为两大阵营,一部分是付费软件(增强原生系统,同时要稳定好用),另外一部分来自于开源软件,也算是付费软件的平替。 名称 系统 是否付费 推荐指数 评价 Typora MacOS 付费 🌟🌟🌟🌟🌟 编辑博客文章的主力编辑器,喜欢 Markdown 的一定不要错过。 uPic MacOS 付费 🌟🌟🌟🌟🌟 MacOS 的图片上传工具,主要用来上传博客图片。 Bob Pro 版本 MacOS 付费 🌟🌟🌟🌟🌟 翻译软件,选词、截图翻译,搭配快捷键非常好用。 iShot Pro MacOS 付费 🌟🌟🌟🌟🌟 截图、录屏软件,专业、美观、稳定。 GoodNotes MacOS PadOS IOS 付费 🌟🌟🌟🌟 笔记软件,我会将一些 PDF 文件上传到这个软件上,用来实时笔记。 沉浸式翻译 Chrome 免费 🌟🌟🌟🌟🌟 中英对照翻译,速度及准确度都 👍 ChatGPT MacOS PadOS IOS 免费 🌟🌟🌟🌟🌟 AI 辅助工具,结合 MacOS 深度融合,智能方便就不用说了 No.1。 IINA MacOS 免费 🌟🌟🌟🌟🌟 开源,视频 & 流 播放器,高颜值、功能强悍。 Motrix MacOS 免费 🌟🌟🌟🌟 开源,文件 & 种子下载器。 Folo MacOS PadOS IOS 免费 🌟🌟🌟🌟🌟 开源,RSS 源阅读器,每日必用神器。 Pearcleaner MacOS 免费 🌟🌟🌟🌟🌟 开源,MacOS 软件深度清理卸载软件。 Quantumult X MacOS PadOS IOS 付费 🌟🌟🌟🌟🌟 去广告、代理软件,小贵但省电。 Shadowrocket MacOS PadOS IOS 付费 🌟🌟🌟🌟🌟 去广告、代理软件,性价比高但耗电。 这里的软件指的是一次性买断、免费下载或收费但使用免费的版本。 订阅 订阅的服务都是日常经常使用的,符合“三天原则”且几乎每天都在使用的。 名称 推荐指数 评价 Apple Music(中国大陆区) 🌟🌟🌟🌟🌟 长期订阅,简约简单性价比高 Apple iCloud+ 200GB 版本 🌟🌟🌟🌟 小贵,但是可以享受全生态闭环,等待小米 NAS ing VPN XFlash Cloud 🌟🌟🌟🌟🌟 稳定使用 3 年,节点干净,速度尚可,可以按流量订阅,推荐链接 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/7/26
articleCard.readMore

入职 5 周年,我和小米的 5 周年

大家好呀,我是 Meng小羽,今天是我入职小米 5 周年的一天,也是我正式职业生涯的 5 年,5 年是一个职业发展的节点,接下来给大家分享一下我的 5 年职场的那些事儿。 序 还记得 19 年的那个夏天,那时候与我同年级的其他人不一样,我早早的的就拿到了实习 Offer,实习并没有在小米进行实习,我投递并入职了一家机器人公司实习,那时候我来到了北京,虽说之前两次参加竞赛来过北京,但是来到北京还是非常的激动与兴奋,毕竟在四九城工作,当时对我来说是一件非常值得开心的事情,说明自己在大学期间的努力得到了社会的认可。 来到北京之后,庆幸实验室的学长正好有合租的位置,也省去了上来先找寻租房的烦恼,可以先准备入职,之后在慢慢看,入职的公司虽说是个初创团队,但是老板和二老板都比较的优秀,同事也是对应行业中相对优秀的人才。 实习期间,实习的工作并没有那么多的困难和挑战,自己在工作之余,也在熟悉与学习 Go 语言,那时候感觉这个语言很简洁,同时性能也非常的强大,上手非常容易。于是计划着后续做 Go 语言开发的相关工作,事实也确实如此。 经过 19 年下半年与 20 年上半年的几个月的时间,在实习阶段系统的学习了服务端相关的知识后,于是参加了 20 年的春招,那时候工作还是挺好找的,在 Boss 直聘上传简历并投递后,很快就收到了反馈,最后经过几轮面试,最后得到了几个满意的 Offer,其中就有小米,那时候我对小米的印象还停留在制造手机的手机厂商,对了,还有小爱同学。 20 年春,准备完毕业答辩,毕业季,由于口罩原因,没能回学校参加毕业典礼,还记得毕业证书和学位证书还是通过邮寄的方式“云毕业”的,😂,应该历年来毕业中最简单的一年了。 入职 拿到毕业证之后就和校招组协商入职时间了,于是选择了大家集中入职的日子,在 7 月 8 号,也就是 5 年前的今天。 我入职的时候当初由于是应届生入职,有对应的应届生培养计划,当初叫做管培生计划“YOU 计划”,入职的最初的几个月小米对我们应届生做了系统的培训,小米的入职培训给我们带来的感觉是非常的实用、高效的。同时也去除了很多授课上的繁文缛节,带给大家的更多的是讨论与交流。 那时候还有高管给面对面的培训,还有一些职场基本技能的培训,金字塔原理、结构化思维等等课程,至今对自己的职场包括生活都很有帮助。 集团的培训当然没有技能的相关培训,但是部门内,对应届生有两个阶段的培训,一个是部门的对应届生的集中业务与代码规范的集训,还有就是我们公司的青蓝导师制度,每一个应届生都会安排一名导师,一起带着做项目,Review 代码等相关的辅导,很荣幸遇到了一位很负责和认真的导师文哥。 对我印象比较深刻的是,我们应届生入职后被安排续参观小米手机黑灯工厂、参与小米订单的第一站配送还有让我们连续 3 天去北京的一线门店去站店,去了解一线的工作,目的是让我们了解我们的订单从哪里来,又怎么去流转的。站店的时候我和一块入职的同事选择在了王府井旁边的小米之家,最后一天的站店早早结束之后我们就去了一趟天安门,趁着冬日的夜色一块拍了个合影。 成长 最开始入职小米的时候是在某部门的系统组,小组给每个应届生都分配了一个系统,记得当初还熟悉了部门开源到 GitHub 比较火的数据库中间件 Gaea ,那时候也是第一次去熟悉真正的开源项目(之前参与了一些民间组织的代码维护工作),感叹 Go 语言的强大以及性能的强悍,同时也是第一次接触到企业级的开源项目,熟悉代码还比较吃力,不过最后还是读了下来,至今有很多的代码设计思想也是阅读这个项目学到的。 不过后来在系统组有两个月的时间,我就被重新分配到了业务部门,开始从事业务相关代码的开发工作,最开始从组内基建工作开始做起,之后就是相对来说比较简单的练手的项目,之后就作为机动力量开始支援组内的业务开发,也在积累着相关的技能。 真正自己作为核心开发开始承接项目,算是从第二年,那时候整个小米在战略上专注于服务数字化的建设,也就是服务通专项,自己作为其中的一环担任了一年的核心项目的开发,从 0 到 1 构建起了 C 端服务通的建设。 小米的售后在整个零售行业都是口碑非常好的,不得不说,我们整个项目组在当年还是下了很多功夫去完成的,相比同行 Apple 的售后对比而言,我们加上生态链的品类,是业界相对来说最复杂也最庞大的售后体系。最终完成米粉售后的全链路的闭环。 这个世界不变的就是时刻在变化着,当然对我们的工作职责也是这样,第三年我所在的部门迎来了组织调整,我负责的业务也从售后变成了售前的相关业务。 开始接手与维护开发产品站相关的业务,那时候我们的产品站已经经过了 8 年多时间的沉淀,变得愈发的复杂与难以维护,怎么把这个烫手的山芋消化掉,是当时的重中之重,当时部门还在往 Java 技术栈转型,自己的技术栈的功底还是相对来说比较薄弱的。但是业务需求越来越多,对于产品站的革新变得也越来越迫在眉睫,当时推动领导做出了决定,就是我们要重新设计与规划产品站的设计,便于后续的开发迭代与维护工作,同时也做到高效的开发与成本的降低。于是开发并设计了有向无环图的并发框架 Phoenix 框架,这个框架的思想之前还在博客给大家分享过,通过 Java 实现。 现在也是一样,业务都在不断的变化着,自己负责的业务领域也随着职级的变化在调整着,只有不断的学习与积累经验,才可以应对工作上的难题。 作为职场人,基本的素养就是职业道德,要对自己的工作保持敬畏与认真的态度,之后再有创新思维,这样才能有所提升与获得对应的公司的激励与职级的提升。从 20 年入职到 25 年满 5 年的今天,职级提升了 3 级,从获得部门的项目奖、优秀员工到集团青年工程师与集团科技创新新星奖,只有不断的提高自己的要求与制定对应的目标,才能达到自己理想的结果。 感悟 这是我第一个职业生涯的 5 年,也是在小米的 5 年,是我从学生到职场人的蜕变的历程,也是从职场小白到高级工程师的修炼之旅,5 年的时间也在积累着很多的职场经验,在这里与大家共勉。 时刻准备迎接着变化,引用《凡人歌》中那隽的一句话就是“这个世界唯一不变的就是时刻在变化着”(杂谈:戏剧的是那隽上班的地方就在我公司隔壁)。无论是在工作还是生活,我们永远都不可能知道明天会发生什么,会给我们带来怎样的变化,这些变化有可能对我们来说非常有利,同样也可能让我们陷入困境。就如股市一样,我们无法预知市场明天的走向是对我们利空还是利多。我们只能是做到未雨绸缪,就像我们准备知识技能来应对职场的变化、购买保险来应对未来未知的风险、存储资金来应对可能需要用到钱的地方、扩展交际圈来丰富自己的认知与眼界等等。 身体才是革命的本钱,拥有一个健壮的身体才是奋斗的基石,适当的锻炼与时不时外出旅行或者徒步或者公园遛弯或者冥想,对于我们的健康来说是非常重要的,同时,由于食品安全问题,学会严选食物和水也变得非常重要,对于我而言,周末时间我可以自己做饭就不会选择去吃外卖,少吃或者尽量不吃带有食品添加剂的食品。同时,要找到一件可以使自己变得开心快乐的事情,无论是需要付费体验也好还是免费就可以得到的,一定要让自己的身体有向上的活力,才可以维持自己的健康,稳固好自己的基石。 保持终身学习的习惯,作为互联网行业中一员的我,再也没有比互联网相关技术迭代快的行业了,给非这个行业的人带来最直观的就是自己手机的操作系统和安装应用软件的更新频率,快则一天几个版本、慢则一两个月一个版本迭代。这背后带来的不仅是功能的更新还有底层依赖的创新和迭代。从大学时开始接触到的大数据、到现在的 AI,每年都会有新的知识和领域颠覆我们现在的行业的发展,只有我们主动去拥抱去学习对应的知识,学会使用与利用这些工具,才可以让我们在这个不断更新迭代变化的社会有自己的一席之地。 对工作保持敬畏与认真的态度,大家在互联网上经常调侃到某些系统或者 APP 配置素材错误而称之为“世界是一个巨大的草台班子”,很多的事故的背后大多数源自于负责的人不认真不细致,对上线不存在敬畏之心,从而造成巨大的损失。5 年时间里,除了有几次由于全局把控没到位遇到了不可预知的问题,除此外近上千次的上线操作,我都是保持高度集中,别人检查 1 遍的内容,我要求自己检查 3 遍,从而确保上线没有问题,同时每次操作上线之后对应的业务的页面或者核心购物流程都会去下单校验下是否有问题,同时也会开启日志或者 grafana 监控来避免报错波动。 多参加技术分享与多阅读优秀的博客,我们在当今知识爆炸的年代,要学会站在巨人的肩膀上去做事情。很多时候别人的思维或者创新点在我们工作中都是有借鉴或者参考意义的。只有不断的去汲取优秀的思想、技巧或者经验,才可以让自己得到持续的提升。寻找和发现优秀的博客也非常重要,因为你发现并开发 follow 了一些优秀的人或者团队之后,你会发现自己的思想或者行为也逐渐跟着趋同化了,所谓“近朱者赤,近墨者黑”应该就是这样了。 以上,5 年时间,总结了 5 点小经验,与大家分享和共勉,屏幕前的你可能已经工作了 5 年 10 年可能还没有 5 年时间,也欢迎你把你的经验在评论区分享给大家。 结 5 年时间,即是对 5 年工作的经历做个小小的记录,也是对 5 年职业生涯的总结,所谓“回首过去,展望未来”,未来且让我预测下,AI 与我们的生活会变得息息相关,我所从事的行业也会因为 AI 的发展而变得更加配置化(低代码流程化),代码的编写会变得越来越少,反而带来的是 AI 相关管理制度与创新应用的出现来代替我们现在的部分工作,我们将要变身成为“AI 开发工程师”,积极拥抱 AI 并且会使用 AI 相关衍生工具的一群人才可以继续在这个行业中得到成长与提升。 上学的时候,每天都在做着不同的事情,有很多的时间来消遣,总是感觉时间过得很慢,一天很长,但是当自己步入社会,开始上班后,刚开始接触的都是工作上的新鲜的事情,感觉时间过得也比较的慢,但随着自己工作年份的递增,反而感觉时间度过的飞快。嗯,这个原理之前在一个博主那里看到过,说道:“人类的大脑皮层的构造,只会记录下来在这个世界感知差别大的事物,像是工作,每天进行着的都是重复的事情,大脑就会选择性的遗忘,以至于让人感觉到时间过得非常的快”。不知道大家是否有这种感觉,同时,对应的解决方案就是多去尝试不同的事物、多出去走走旅游、在工作之外的生活中发掘和培养自己的兴趣爱好或者第二职业。 有时候在想,如果当初我没有选择来北京,而是选择在老家找一份计算机相关的行业或者和实验室的同学一样选择了考研或者考公,我的人生又是一个什么样的经历?是在小城市的休闲与淡然,每周都回趟老家陪陪父母?还是考研上岸后,在另外一个城市去完成自己的学业,也在现在抉择着自己的未来?还是考公成功后,过着稳定的生活?还是老家的工作并不如意,心有不甘?还是考研失利,二战的坚持与压力?还是考公失败,自己该何去何从?其实哪有什么当初如果,一切都是在人生十字路口中不断的做出选择罢了,只有不断的向前走,才会看到人生道路上不同的景色。 未来,继续努力,不断的学习与成长,接触与学习新鲜的知识技能与事物,持续积累,让我们下一个 5 年再见。 这就是我第一个 5 年职业生涯,你的第一个 5 年生涯是怎么度过的呢,欢迎在评论区分享你的成长,让我们一起变得更强! 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/7/8
articleCard.readMore

游记 五一环渤海南海岸自驾之旅

大家好呀,我是 Meng 小羽,未来我计划着后续分享下自己的游记,五一回来公司一直在忙着公司项目,推迟了一周的时间终于可以闲下来记录一下了。 序 五一前其实原本计划回一趟对象的甘肃老家,但最后无奈一直抢不到票,只好被迫改了行程、重新规划。这时候离假期只剩下一周,怎么充分利用这短短的几天,就成了我们最重要的问题。可偏偏最近工作特别忙,我们俩一直在加班处理各种任务。 于是我们干脆请 AI 帮我们精挑细选了一番旅游目的地,还把之前去过的地方全部排除掉。综合考虑了时间安排和各地的天气情况,最终决定去看海——目的地:烟台,开启一场环渤海南海岸的自驾之旅。 出发前两天,我们迅速做好了攻略,订好了酒店和景点的大致行程,也规划好了自驾路线,一切准备就绪! 石油之城 由于从北京自驾前往烟台需要 8 到 9 个小时,我们决定在五一假期的第一天,先在中转城市停留一天。一方面是因为长时间高速驾驶对新手来说是个不小的挑战,另一方面也想借此机会去看看黄河入海口。此前我们曾去过黄河上游的兰州,这一次,希望能与黄河在入海处来一次“首尾呼应”的邂逅。 我们选择了相对小众的城市——东营。然而,黄河入海口著名的“鸳鸯锅”景观对天气条件要求颇高。我们第一天下午抵达东营时,天已经开始下起零星小雨,还刮起了风——风浪对海水颜色的影响很大。考虑到这样的天气难以看到理想的景色,2 号一早我们暂时放弃了前往入海口景区的计划,改去了当地较为热门的景点——孤东海堤。 2 号上午,天气依然阴沉,海风格外强劲,海浪也较大。我们来到了这条颇具人气的“网红海堤”。据说,这条防浪堤原本是东营为保护油田开采而专门修建的,没想到却意外成了一个备受欢迎的网红打卡地。 低头一看,海堤上的石块早已布满青苔,在海浪的拍打下,呈现出别样的视觉效果,格外好看。忍不住拍了几张照片,现在看来,每一张都足以用作壁纸。 当天虽然天气不算理想,但海堤与海浪的画面感十足,拍出来的照片意外地出片。 在东营,自然也少不了那标志性的磕头机(石油开采设备)。往返孤东海堤的路上,沿途尽是密集的油田,成千上万台磕头机正在有节奏地运转,场面颇为震撼。 黄河入海 当天的风一直没有停,天气也始终阴沉。看到小红书上大家都没有见到“鸳鸯锅”的景象后,我们决定放弃前往景区坐船的计划,转而出发前往烟台。我们选择了一条靠近黄河入海口的道路,算是与黄河在它的末端再度相会。 烟大海滩 下午,我们驱车前往烟台,计划在养马岛玩一天,再去蓬莱阁景区后返回。当天晚上,驶向烟台的路上,依然是阴雨绵绵,第一次切身感受到横风区的威力,强风吹得车子微微颤抖。 第二天晚上抵达烟台后,与民宿的老板娘沟通了我们的计划,得知养马岛景区是封闭式的,无法自驾前往。于是,我们调整了行程,决定第三天去烟大海滩。 次日,我们前往烟大海滩时,发现海滩旁的停车场已满,于是改为从养马岛向西沿海岸线行驶,最终在一处不知名的海滩停下,迎着海风晒太阳,静享片刻宁静。 渔人码头 在同事的建议下,去渔人码头看了日落,吃过饭后,在这个半岛上溜达了一圈,很出片。 这里推荐下烟台的海肠捞饭还有大生蚝,建议两个人要一个人的量,因为给的太多了。还有八鲜馅的包子,先喝汤再吃馅。 蓬莱秘境 最后一天旅程,去了蓬莱阁景区,不过五一假期,全都是人从众,在蓬莱阁看长岛很清晰,再看远处的群岛,若隐若现犹如仙境一样。 偶遇景区节目,嗯,为了促进结婚也是费心了。 尾 4 号下午就光速回了北京,怕堵在 5 号,北京下着小雨,很多想法一致的道友都堵在了去北京的高速上 😂,最后在凌晨 3 点到达。 总体来说,这次旅行时间比较紧张,可以说是特种兵式的旅行。毕竟烟台距离北京还有一段距离,大部分时间都花在了车里,以后应该不会再这么短时间内去自驾那么远的地方了。 不过,假期过得依然非常愉快,就是时间实在太短了。你的五一假期是怎么度过的呢?评论区期待你的分享。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/5/10
articleCard.readMore

送送送,微信红包封面了,速领~

序 今年给大家制作了 3 个红包主题,分别是金舞银蛇、博主送福、使我心荡漾。 2025 年,乙巳蛇年,祝大家新年快乐、心想事成、平平安安、健健康康、发大财~ 金舞银蛇 使用 ChatGPT 生成的“金舞银蛇”,每年我都会生成一个生肖主题的红包封面,希望你喜欢,祝你新春快乐。 博主送福 第二个红包封面是我公司 AI 小工具基于我的工卡照片生成的卡通形象,希望新的一年,屏幕前的你福气多多,祝你新春快乐。 使我心荡漾 最后一个红包封面,是我和我家领导的专属红包封面,也希望屏幕前的你,没对象的今年找到自己的天命君子或白雪公主,有对象的可以幸福长长久久,修成正果,祝你新春更快乐。 尾 由于微信平台限制,只能在微信公众号文章页面内领取,大家可以先关注下微信公众号“Debug客栈”后,发送“领红包“获取。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2025/1/19
articleCard.readMore

2024 年度总结

2024 年,这一年就像做梦一样的度过,还好,我终是梦醒了,也没有睡过头。 这一年里,我完成了许多人生中的“第一次”:第一次踏上新疆的土地,第一次参加演唱会,第一次深入草原腹地。我亲眼目睹了边疆的辽阔与壮丽,体验了万人狂欢的热烈夜晚,也感受到了“风吹草低见牛羊”的诗意与真实。 我的个人站点也迎来了第 8 个年头。在兢兢业业工作的同时,我不断努力打造个人 IP,将我对编程事业的热爱,与业务摄影、科技探索、硬件发烧友的多重身份分享给更多人。在这过程中,我也结识了许多志同道合的朋友,拓宽了自己的视野与圈子。 接下来是我对 2024 年的总结,想与你分享。 工作 从去年正式转向 Java 技术栈以来,今年我也没有让大家失望。我将自研的 Phoenix 框架开发思路与经验分享给大家,同时还得到了相关领域大佬们的宝贵建议与新颖的想法,受益良多。今年,我更是在双技术栈并行的模式下投入生产,深刻体会到了两种语言各自独特的魅力与设计哲学,这种探索让人充满收获感。 在工作中,我持续保持高质量输出,并不断提升自己的技术影响力。在项目中,我逐渐担任核心研发角色,并成功迈入高级开发工程师的行列,迎来了个人职业发展的重要里程碑。 回望今年,这也是我入职公司的第 4 年多,再过半年,我就能解锁金米兔的成就啦 🎉。这段旅程充满挑战,但也伴随着成长与喜悦。 站点 今年,我的站点终于在静态化的阵营中稳稳站住了脚。经过不断摸索,我逐步完善并打造出了一套专属的部署模式,兼具高效性与性价比,让站点的运维更加从容。 同时,我也在持续努力创作优质文章,不断丰富站点的内容。今年,站点的曝光度与流量都有了显著增长,看到更多人关注与认可我的分享,倍感欣慰与动力十足。 文章 24 年,我一共书写了 9 篇文章,在这里再给大家分享一下: Phoenix框架 从0到1设计业务并发框架 小米商城产品站革新之路 Phoenix框架 从0到1设计业务并发框架 怎么组织设计一个框架 Phoenix框架 从0到1设计业务并发框架 并发线程池的核心设计 Phoenix框架 从0到1设计业务并发框架 自动构建有向无循环图设计 使用 GOTRACEBACK 快速定位你的 Panic Follow|下一代的信息浏览器 Follow |下一代信息浏览器 第二弹来了 Follow 给我空投了 1w 代币,可以无限发码啦~ 答 《博客作者呀,我想采访你这 9 个问题!》 问卷 今年的主要精力都投入在工作上,因此开源方面的分享相对较少。可以说,这一年更多是一个沉淀与积累的过程,专注于提升技术能力和积累宝贵的工作经验,为未来的发展打下更扎实的基础。 开源 今年的开源主要体现在博客和站点的维护上,但其实,分享文章与传播技术,又何尝不是另一种形式的开源呢? 生活 今年,是我人生中最难忘的一年。感恩上帝、菩萨、佛祖的眷顾(我始终相信这个世界有神的存在,只是我不确定祂在人间的表达形式,所以对每一种可能都怀着虔诚与敬畏)。感激家人一直以来的陪伴与关怀,而我最想感谢的,是在我最艰难的时候始终陪伴在身边、鼓励我的她。 这一场如梦般的经历,好在我醒了过来,并且没有睡过头。 今年,我也走过了许多地方,见证了人世间的悲欢离合,用镜头记录下无数动人的瞬间。赛里木湖的湛蓝、《苹果香》中的蓝色小屋、风吹草低的牛羊、第四纪火山的奇特地貌、一望无际的戈壁滩、肉眼可见的深邃银河、万人大合唱的震撼、还有海上的日落与月升……每一处风景都饱含故事,每一帧画面都充满了意义。这一年,旅途不仅拓宽了视野,更丰富了心灵。 在这里分享给大家。 摄影 和往年一样,每年我都会精选图片放在摄影展站点中,欢迎你的访问。 Debug客栈摄影展:https://photo.debuginn.com 阅读 今年阅读的书籍比较少,但是都非常有营养。 感谢同事分享的马伯庸老师的《太白金星有点烦》和《长安的荔枝》,我理解他的作品为神话历史类讽刺小说? 还有一本来自豆瓣高分作品《我在北京送快递》,跟着作者体验最真实的凡人歌。 总结 2024 年,收获了很多,也得到了很多。人生很长、要经历无数悲欢离合、阴晴圆缺,人生又很短、岁岁年年转瞬即逝、不得停留。珍惜当下、过好每一天、与自己的爱人、亲人和朋友。 最后,感谢你的阅读,让我们 2025 年变得更强~ 2025 年,新年快乐! 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/12/31
articleCard.readMore

答 《博客作者呀,我想采访你这 9 个问题!》 问卷

简单介绍下自己或者你的博客? 大家好,我是 Meng小羽,也是 Debug客栈 的博主。这是一个陪伴了我整整八年的博客平台。 最初创办这个网站时,我只是希望用它来记录大学期间的学习笔记,以及算法竞赛中的刷题心得。随着时间推移,博客逐渐成长为一个涵盖多领域的技术分享平台,内容范围也从单一的技术积累拓展到更多元化的话题。 Debug客栈 目前已成为一间“全能型的杂货铺”,在这里你可以看到: 技术积累与分享:深入探讨服务端开发、前沿科技等技术内容; 科技趣闻与产品体验:分享数码产品的试用体验与评测; 热点讨论:关注时事,畅谈社会热点与见解; 好物与软件推荐:推荐高效实用的软件和工具,为你的生活和工作增色。 目前,我的职业是 服务端开发工程师,专注于系统设计、性能优化以及服务架构相关领域。对于我的更详细介绍,欢迎移步到:关于站长 - Debug客栈。 感谢你关注 Debug客栈,也期待与大家在这里共同成长,探索更多有趣、有价值的内容! 什么契机让你开始写博客? 我的大学专业是 计算机科学与技术,相比其他同龄人,我较早接触到了互联网的思想。从学习编程开始,我遇到不懂的知识点或问题时,总是习惯通过 Google 搜索相关答案和解题思路。在这个过程中,我逐渐接触到了个人博客这一领域。 让我印象最深刻的是互联网早期的浪潮,当时人们习惯搭建个人博客,最火的当属新浪博客。而我上大学时,微信公众号迅速崛起,但作为一个计算机相关专业的学生,拥有一个属于自己的主页或站点,不仅能积累知识与技术栈,更是一件非常值得骄傲的事情。 于是,我开始了自己的博客之旅。从第一篇博文 《Sublime Text:崇高的文本编辑器》 开始,一发不可收拾。之后,各种学习笔记和竞赛相关的文章便陆续在博客上发表,为我的知识积累之路画上了一笔又一笔精彩的注脚。 你是如何完成创作的? 最初,我的站点使用的是 WordPress,通过 LNMP(Linux、Nginx、MySQL、PHP)架构进行部署和发布。当时的写作流程很简单:我会先在后台创建好文章标题,根据优先级安排博文的书写,同时反复打磨内容和语言组织。当然,也有不少文章因为各种原因“鸽”了 😄。 到了 2023 年,我的博客完成了 全站静态化升级,采用了 Hugo 静态化生成的方式,并部署在 Cloudflare 上。写作工具方面,我曾用 VSCode 写过一段时间的博文,但总感觉不太适合写作,于是转向了 Obsidian。现在,我的流程是:先在 Obsidian 中创建文章标题,完成初稿后再润色一遍,最后复制到 VSCode 进行发布。 此外,博客内容也会同步发布到微信公众号「Debug客栈」,欢迎关注! 运营博客的过程中是否有失去过动力? 如果有,是为什么恢复的?如果没有,请问您又是如何保持创作的激情? 在运营博客的这 8年 里,我从未想过要放弃。相反,我对折腾充满热情。在这段时间里,我几乎每年都会更换一次博客主题,站点也从动态站点迁移到了静态站点。同时,我始终坚持更新博客文章,虽然 月更 对我来说有点难 😅,但 季更 还是稳稳地保持着。随着时间的推移,我不断提升自己的写作水平,也逐渐积累了一定的影响力。目前,全网关注人数已接近 2万。 对我来说,博客不仅是一个记录的平台,更是展示技术能力与技术影响力的窗口。作为一名互联网从业者,我始终相信博客的价值:它不仅让我系统性地梳理知识,还帮助我结识了许多志同道合的伙伴。在这个过程中,我持续学习,不断成长。 如何搭建博客,以及运营博客每年需要投入的资金? 目前,我的博客搭建既简单又省心,但需要具备一定的编程基础。以下是我发布一篇博文的完整流程,与前面提到的创作步骤有些相似: 在 Obsidian 书写草稿:先完成博文的初步内容; AI 润色与校对:使用 AI 工具润色博文,并检查错别字; 上传文章到 GitHub:博客文章存储在 GitHub 仓库,我通过 VSCode 将文章上传; 替换内部超链接:在 VSCode 中替换超链接,方便文章内链; 提交代码并合并分支:将文章推送到 GitHub 的 dev 分支,随后提交合并到 main 分支; 触发 Hugo 编译:利用 Hugo Actions,将文章生成为静态网页; 部署到 Cloudflare Pages:通过 Cloudflare Pages 获取编译好的网页,并全球分发; 访问文章:完成以上步骤后,文章即可通过网站访问。 目前采用的部署方式非常经济高效。借助 GitHub 和 Cloudflare Pages,博客仓库的部署额度和全球分发额度完全免费。唯一的开销是域名注册费用,每年在 ¥80 左右,可以说是高性价比的博客运营方案。 推荐 1 篇你博客中的文章,聊聊原因 今年,我主要围绕两个方向写了一些文章,哈哈,忍不住要推荐给大家: 技术领域的总结 这一系列文章聚焦于 Phoenix 并发框架 的开发思路,以及我在从 0 到 1 开发框架过程中遇到的问题和解决方案的总结与分享。这个框架已经成功应用到实际业务场景中,非常值得技术爱好者一读! RSS 阅读软件推荐 最近,我一直在推广一款开源的 RSS 信息订阅软件 - Follow。在我用过的 RSS 软件中,它的交互体验是最友好的,使用起来也非常便捷。我强烈推荐这款工具,带你加入非算法化的信息圈子,掌控属于自己的信息流。 下面我把这两个系列的文章都整理出来,大家可以根据自己的兴趣挑选阅读: Phoenix 并发框架系列: Phoenix框架 从0到1设计业务并发框架 小米商城产品站革新之路 Phoenix框架 从0到1设计业务并发框架 怎么组织设计一个框架 Phoenix框架 从0到1设计业务并发框架 并发线程池的核心设计 Phoenix框架 从0到1设计业务并发框架 自动构建有向无循环图设计 Follow 分享系列: Follow|下一代的信息浏览器 Follow |下一代信息浏览器 第二弹来了 Follow 给我空投了 1w 代币,可以无限发码啦~ 2023 年为何我还在使用 RSS 推荐 1 个你喜欢读的博客,聊聊原因 相信许多热爱冲浪、阅读博客的朋友,都或多或少听说过《阮一峰的网络日志》。还记得 2019 年实习时,那时候地铁通勤网络状况不太好,我用了一周时间,把阮一峰老师的文章从头到尾读了一遍。这些文章涵盖了他对社会问题的看法、基础技术的分享,以及他每周更新的 《科技爱好者周刊》 ,让我受益匪浅。 说到周刊,这也是我每周五的必读内容之一。通过阮老师的周刊,我接触到了许多新奇的想法和科技圈的动态,同时还发现了一些由爱好者分享的优质文章和实用软件。5 年下来,这份周刊始终保持着高质量的内容输出,值得推荐给所有感兴趣的朋友。 推荐 1 个近期喜欢的事物? 例如书籍、电影、音乐、工具、软件。 作为互联网分享者,每个方向都给大家推荐一下吧~ 书籍 谈到书籍,不得不给大家推荐下马伯庸老师的《太白金星有点烦》,懂得自然懂,哈哈,佛曰不可说不可说,趁着还可以阅读,推荐给大家,通过这本书,大家也可以了解到社会运转的规律。 大家也可以阅读下马老师的另外一本书《长安的荔枝》,看看荔枝使怎么博得贵妃笑的。 电影 《楚门的世界》,最经典的莫过于在剧情中和最后楚门的台词: Good morning, and in case i don’t see you, good afternoon, good evening, and good night! 早上好,以防我见不着你,所以下午好,晚上好,晚安! 反观楚门的世界,我们又不是无时无刻也活在“楚门的世界”之中呢? 音乐 推荐下最近听的比较多也比较震撼的刀郎老师(罗林)的《如是我闻》音乐专辑,其实是佛教中的《金刚经》,刀郎老师谱曲演唱的,一共有 32 品,推荐给大家聆听。 工具 莫过于笔记工具,也就是我现在书写这篇《答博客作者采访问题》博文的工具,Obsidian。 Markdown 友好,可以基于 iCloud 跨设备同步,界面 UI 美观且功能强大。 软件 不在多说了,请看楼上 Follow 推荐文章。 想做还没有做的事? 谈到还未实现的愿望,我一直想去一趟西藏。2023 年,我曾去过云南,途经香格里拉,深深感受到藏族同胞的淳朴与热情。这段旅程让我更加向往那片神秘的高原,也让我庆幸能生活在拥有“世界第三极”的国家。 如今,阻碍我的似乎只剩下一张通往西藏的“车票”。我希望未来 5 年内,能踏上这片离天空最近的土地,去感受虔诚的信仰,体验雪域高原的热情,感受缺氧中透出的独特幸福感。这是我对雪域之巅的一份憧憬,也是一份内心的向往。 写到这里,闭上你的眼睛,深呼吸几分钟,或是出去溜达一圈,然后回来写任何你想写的东西。 Good morning, and in case i don’t see you, good afternoon, good evening, and good night! 问卷地址:博客作者呀,我想采访你这 9 个问题! - Another Dayu 最后,感谢你的阅读,你也可以在评论区分享着你的生活与所有,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/11/23
articleCard.readMore

Follow 给我空投了 1w 代币,可以无限发码啦~

Follow Airdrop 由于较早的参与 Follow 的内测,现在也当了一回原始股东的感觉,终于分红了~ 今天看官网提供了 9051 $POWER 代币,按照公测到自由下载预估时间,再加上邀请码生成需要有使用时间限制,差不多可以无限发码啦~ 下载地址 1 2 3 官网地址:https://follow.is/ Github: https://github.com/RSSNext/follow 下载地址:https://github.com/RSSNext/Follow/releases 功能简介 Follow |下一代的信息浏览器 Follow |下一代信息浏览器 第二弹来了 2023 年为何我还在使用 RSS 订阅源推荐 1 2 3 4 5 6 7 我的订阅源:https://app.follow.is/share/users/@debuginn Go 语言爱好者:https://app.follow.is/list/60633757623653376 新闻资讯:https://app.follow.is/list/67389023042166784 互联网相关资讯: https://app.follow.is/list/66698003857126400 Web开发关注领域:https://app.follow.is/list/63404832700630016 摄影相关的领域:https://app.follow.is/share/lists/60649442771759104 AI前沿资讯:https://app.follow.is/share/lists/67498814495306752 邀请码 大家最关心的就是邀请码了,今日到账后,我会陆续给大家生成邀请码发送给大家,大家可以关注我的公众号,同时希望把你感兴趣的订阅源在评论区分享出来,分享优质的订阅源将优先获得邀请码哦,最后,感谢您的关注,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/11/18
articleCard.readMore

Follow |下一代信息浏览器 第二弹来了

大家好呀,上篇文章给大家推荐了我近期参与内测的一个软件 Follow,得到了大家热情地互动,今天继续给大家深度的分享一下 Follow 的特色功能,同时由于目前 Follow 处于内测邀请阶段,一码处于严重供不应求的状态,我也会给大家推荐下暂时平替的 RSS 开源软件,同时,大家可以关注我,等 Follow 公测开始,我会第一时间告知大家。 特色功能 RSS 搜索 作为 RSS 阅读器,提供搜索功能,大大降低了大家搜索感兴趣的源的信息,非常便捷方便。 搜索:可以根据关键词搜索订阅感兴趣的方向的信息源; RSS:可以直接输入 RSS 订阅地址预览订阅; RSSHub:作者与另外一个开源作品组成生态,只要是网站信息开放的,那么"万物皆可 RSS"; 收件箱:你敢相信,也可以接收邮件 📧 ; RSS3:下一代的 RSS 信息订阅方式; 还有 RSS 阅读器的导入功能,你可以快速切换到 Follow 上面来; 谁在阅读 阅读器可以把相同订阅并阅读过这篇文章的网友罗列在文章左上方。 同时,点击头像会把订阅者自己公开的订阅罗列出来,通过这个功能我补充了好多订阅源 😄 订阅热点 AI 每天早晚 8 点 AI 基于订阅的源的阅读量来提取 Summary 列表供订阅者阅读。 生成摘要 AI 生成摘要对我来说很重要,目前我订阅了上百个博客和科技媒体,摘要可以让我快速阅读文章核心内容,并确定是否我感兴趣的文章并继续阅读。 双语翻译 AI 应该也是通过 AI 实现的自动翻译,可以对比着阅读,订阅英文专业文章也不吃力。 激励作者 通过区块链技术,发行 POWER 代币,你可以对你感兴趣的文章的作者进行打赏,用来提升作者的影响力和生产出更多高质量的内容,从而实现了良性循环的激励创作体系。 视频订阅 没错,你可以把 YouTube 或者哔哩哔哩上你感兴趣的 UP 主都订阅起来,他们发表的最新视频就可以通过 Follow 的自动化推送来及时提醒,直接可以在软件内播放,非常地便捷。 图库订阅 通过图片瀑布流的形式进行订阅,学习与鉴赏图像给大家带来的视觉冲击与色彩描述。 播客集成 Follow 就是强大,当你不想去阅读文章的时候,你可以通过点击一键生成播客的模式,让文章生成类似播客的方式播放给你听,遥遥领先。 介绍了这么多 Follow 的功能,相信大家已经跃跃欲试了,但是目前 Follow 处于内部测试,属于邀请阶段,我这边 5 天才可以生成一个邀请码,严重供不应求,在公测之前的这个阶段,大家没有拿到邀请码的不要灰心,接下来给大家介绍一个开源免费的 RSS 阅读器,先进行过渡。 平替的软件 苹果系统平替 # NetNewsWire 官网地址:https://netnewswire.com/ Github 地址:https://github.com/Ranchero-Software/NetNewsWire 下载地址:https://github.com/Ranchero-Software/NetNewsWire/releases Windows 平替 # Fluent Reader 官网地址:https://hyliu.me/fluent-reader/ Github 地址:https://github.com/yang991178/fluent-reader 下载地址:https://github.com/yang991178/fluent-reader/releases 由于我的主力机器目前是 Mac,大家有推荐的 windows rss app 可以评论区分享出来哈。 写在最后 最后,给大家推荐下我的订阅信息,大家可以去订阅感兴趣的信息源: https://app.follow.is/profile/@debuginn 同时,已经有 Follow 软件体验的小伙伴,推荐关注我自己收集创建的订阅列表: Go 语言爱好者:https://app.follow.is/list/60633757623653376 Java 语言爱好者:搭建 ing,敬请期待~ 同时,大家可以关注我,等 Follow 公测开始,我会第一时间告知大家~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/10/20
articleCard.readMore

Follow|下一代的信息浏览器

近期体验了一款高效的私有化信息订阅平台,给大家推荐一下,官方则是描述为 Next-gen information browser,下一代的信息浏览器,它不单纯是一个 RSS 订阅软件,因为在传统的 RSS 仅仅订阅的是网站的 feed 流信息进行 follow。 而 Follow 则是与 RSSHub、RSS3 相结合的生态软件,从而扩展了很多的订阅源以供订阅。 Follow Follow 不单单提供了 RSS 的订阅,它的定位更是官网中所描述的 “下一代的信息浏览器”,目前它提供的功能有: Articles 文章订阅:偏向于传统的 RSS 订阅的模式; Social Media 社交媒体:更加偏向于 Follow 自己感兴趣的作者在提供订阅的 APP 上; Pictures 图库订阅:订阅自己感兴趣的图片网站; Videos 视频订阅:订阅自己的在 Youtube、BiliBili 等视频平台的 UP 主; Audio 音频订阅:更像是播客类的订阅 APP; Notifications 通知订阅:订阅新闻媒体的信息; 详见官网地址:https://follow.is/ Github 地址:https://github.com/RSSNext/follow 逆算法化 在如今信息化时代,大家接收到的信息都是经过大数据、算法进行点对点的信息推送,导致大家的信息来源越来越趋向于单一、被动接受、羊群效应,大家少了思考、多了短暂地愉悦。 在 23 年,我就写了一篇文章叫做 《2023 年为何我还在使用 RSS》,文中我列举了 5 点好处,这也是我为什么大学毕业以来一直使用 RSS 阅读器的原因。 其实大家动不动刷抖音、快手、小红书或者微信视频号等短视频平台就会发现,短则半个小时,长则 4-5 个小时的时间都浪费在了这些 APP 上面,回头来看,看过了什么视频,学到了啥,基本上就属于短暂地愉悦,长时间来看就像游戏一种性质。 个人定制化的信息输入才是对自己认知与能力的快速提升。 其实 RSS 订阅为什么一直长久不衰,很大程度而言,是可以 Follow 自己感兴趣的领域以及作者的高质量的内容的输出,另外也可以屏蔽掉现在对大家苦不堪言的广告推送。 亮点功能 目前,Follow 给我带来眼前一亮的功能有如下三个,AI Summary、订阅共享以及邀请制。 AI Summary 通过自动化设置,可以将文章使用 AI 生成摘要,一些复杂难懂且文章比较长的场景,通过 AI 进行总结,可以带来阅读效率的极大提升。 订阅共享 订阅共享功能是 Follow 的特色功能,大家可以通过订阅的文章了解到和自己一样订阅这个文章的人他(她)们订阅了什么信息,从而充实自己的订阅源。 邀请制 相信 Follow 的作者在开发这个软件的时候就有着成熟的产品推广的思路,实际上, Follow 本身是一块很优秀、完成度很高的软件,但是作者实施的邀请制度使得 Follow 在互联网中始终可以保持比较活跃的热度,从而让一款软件更容易地在社区、媒体上带来传播。 不得不说,创新之处,在于作者将区块链币的设计引入到了 Follow 之中,通过签到获取 $POWER 代币,代币目前有两种用途: 邀请:通过代币生成邀请码,进行分享,达到 APP 的裂变传播; 赞赏:对于感兴趣的文章,你可以给认证过的作者打赏代币; 相信未来代币可以在 Follow 中有更多的场景和玩法,同时作者还承诺代币未来的价值,期待 (✧∀✧) 写在最后 Follow 是一款非常成功的软件,在算法推荐为主流的时代,这一款 RSS 软件无疑给 RSS 圈带来了新的希望与活力,目前还在邀请阶段,凭借开源、简洁、RSS3 就已经获得了大家的追捧,希望公测时更加出色。 不过,目前的互联网环境,头部大厂都是尽可能地保留住自己的数字信息,从而选择了私域与闭源的策略,这样其实与最初互联网“开放”的原则相违背,带来的弊端是大家接触信息的成本变得高了起来,同时,RSS 阅读器也因为版权的原因,无法获取高质量的文章。 大家有推荐的 RSS 的订阅源也可以通过评论区分享出来,如果你感觉文章对你有所帮助的话可以关注下我,我会不时分享一些小众但也优秀的软件推荐给大家,让我们一起变得更强。 分享一下我的网站的订阅源与 Follow 的订阅列表: Debug客栈 RSS 订阅地址:https://blog.debuginn.com/index.xml Go 语言爱好者 订阅列表:https://app.follow.is/list/60633757623653376 最后,我会从评论区中抽取一名朋友赠与 Follow 邀请码,感谢大家支持~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/10/13
articleCard.readMore

使用 GOTRACEBACK 快速定位你的 Panic

近期迁移了一个 go 的项目至 k8s 机器上,发现机器不时会自动重启,当想看重启前日志的时候,Goroutine 运行的状态全部都打印了出来,由于公司云平台查看行数限制,看到最后,还是没有想要看到的 panic 的关键堆栈信息。 前期,由于频繁的重启,怀疑是哪里出现了未捕获的 panic 导致的,于是在使用的第三方包 RocketMQ 和 Talos 等 SDK 包进行了生产消费初始化的 recover 并且对项目中 channel 的关键操作中也添加了 recover 捕获,但是并没有解决问题,还是时不时的出现重启。 在查找 Go 官方文档,发现可以设置这个环境变量 GOTRACEBACK 可以控制 panic 发生后堆栈跟踪的打印级别。 GOTRACEBACK Go 运行时使用该环境变量来决定在程序崩溃或出现未处理的 panic 时应该输出多少堆栈跟踪信息,它是在运行 Go 程序时通过环境变量传递给 Go 运行时的。 none 当程序崩溃时,不输出任何堆栈信息; single 仅显示导致崩溃,出现 panic 的 goroutine 的堆栈信息; all 显示所有的 goroutine 的堆栈信息; system 显示所有的 goroutine 的堆栈信息,包括运行时内部 goroutine 的信息; crash 显示所有的 goroutine 的堆栈信息,然后核心转储程序并退出; runtime package - runtime - Go Packages 源代码 基于 Go 1.20 版本 设置 GOTRACEBACK 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 //go:linkname setTraceback runtime/debug.SetTraceback func setTraceback(level string) { var t uint32 switch level { case "none": t = 0 case "single", "": t = 1 << tracebackShift case "all": t = 1<<tracebackShift | tracebackAll case "system": t = 2<<tracebackShift | tracebackAll case "crash": t = 2<<tracebackShift | tracebackAll | tracebackCrash default: t = tracebackAll if n, ok := atoi(level); ok && n == int(uint32(n)) { t |= uint32(n) << tracebackShift } } // when C owns the process, simply exit'ing the process on fatal errors // and panics is surprising. Be louder and abort instead. if islibrary || isarchive { t |= tracebackCrash } t |= traceback_env atomic.Store(&traceback_cache, t) } 获取 GOTRACEBACK 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // Keep a cached value to make gotraceback fast,// since we call it on every call to gentraceback. // The cached value is a uint32 in which the low bits // are the "crash" and "all" settings and the remaining // bits are the traceback value (0 off, 1 on, 2 include system).const ( tracebackCrash = 1 << iota tracebackAll tracebackShift = iota ) var traceback_cache uint32 = 2 << tracebackShift var traceback_env uint32 // gotraceback returns the current traceback settings.// // If level is 0, suppress all tracebacks. // If level is 1, show tracebacks, but exclude runtime frames. // If level is 2, show tracebacks including runtime frames. // If all is set, print all goroutine stacks. Otherwise, print just the current goroutine. // If crash is set, crash (core dump, etc) after tracebacking.// //go:nosplit func gotraceback() (level int32, all, crash bool) { gp := getg() t := atomic.Load(&traceback_cache) crash = t&tracebackCrash != 0 all = gp.m.throwing >= throwTypeUser || t&tracebackAll != 0 if gp.m.traceback != 0 { level = int32(gp.m.traceback) } else if gp.m.throwing >= throwTypeRuntime { // Always include runtime frames in runtime throws unless // otherwise overridden by m.traceback. level = 2 } else { level = int32(t >> tracebackShift) } return } 基于 GOTRACEBACK 打印堆栈信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // gp is the crashing g running on this M, but may be a user G, while getg() is // always g0. func dopanic_m(gp *g, pc, sp uintptr) bool { if gp.sig != 0 { signame := signame(gp.sig) if signame != "" { print("[signal ", signame) } else { print("[signal ", hex(gp.sig)) } print(" code=", hex(gp.sigcode0), " addr=", hex(gp.sigcode1), " pc=", hex(gp.sigpc), "]\n") } level, all, docrash := gotraceback() if level > 0 { if gp != gp.m.curg { all = true } if gp != gp.m.g0 { print("\n") goroutineheader(gp) traceback(pc, sp, 0, gp) } else if level >= 2 || gp.m.throwing >= throwTypeRuntime { print("\nruntime stack:\n") traceback(pc, sp, 0, gp) } if !didothers && all { didothers = true tracebackothers(gp) } } unlock(&paniclk) if panicking.Add(-1) != 0 { // Some other m is panicking too. // Let it print what it needs to print. // Wait forever without chewing up cpu. // It will exit when it's done. lock(&deadlock) lock(&deadlock) } printDebugLog() return docrash } 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package main import ( "fmt" "os" "time") func main() { env := os.Getenv("GOTRACEBACK") fmt.Printf("GOTRACEBACK: %s\n", env) for i := 0; i < 3; i++ { go a() } go b() for i := 0; i < 3; i++ { go a() } time.Sleep(time.Second * 1) } func a() { time.Sleep(time.Millisecond * 1) fmt.Printf("aaaaaaa\n") } func b() { time.Sleep(time.Millisecond * 1) panic("b panic ......") } All 显示所有信息 可以看到,运行时所有的 Goroutine 信息都被打印了出来。 None 不输出任何信息 设置为 none 后,只会将运行信息打印出来,非用户打印信息不会抛出。 Single 只显示导致崩溃的 Goroutine 信息 这个设置参数也是默认的设置: 这里只会显示导致 panic 的 goroutine 的堆栈信息以及运行状态。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/9/9
articleCard.readMore

Phoenix框架 从0到1设计业务并发框架 自动构建有向无循环图设计

从 0 到 1 设计业务并发框架系列: Phoenix 框架 小米商城产品站革新之路 Phoenix 框架 怎么组织设计一个框架 Phoenix 框架 并发线程池的核心设计 Phoenix 自动构建有向无环图的业务并发框架,核心就在于不需要开发人员关心调用分层和依赖互斥的排序问题,通过算法进行自动构建、收集 Task 任务、检测环或者依赖,最后打印并发组分层信息。 本篇文章就讲解下如何构建有向无环图的设计实现方案及遇到的问题。 实现方案 有向无环图的构建采用的是设计模式中的策略模式,首先定义好 Builder 的实现方式,如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * @author debuginn */ public interface PhoenixBuilder { // 过滤 Phoenix API 使用到的 Task 任务 Map<String, ArrayList<TaskObj>> filterApiUsedTask(ArrayList<TransObj> transObjArrayList); // 根据 api 获取需要执行的 trans Map<String, ArrayList<TransObj>> apiTransMap(ArrayList<TransObj> transObjArrayList); // 是否存在依赖关系 void isHaveLoop(Map<String, ArrayList<TaskObj>> apiUsedTaskMap); // 处理并发组分层划分 Map<String, ArrayList<ArrayList<TaskObj>>> processConcurrentGroup(Map<String, ArrayList<TaskObj>> apiUsedTaskMap); // 打印并发组分层信息 void printConcurrentGroup(Map<String, ArrayList<ArrayList<TaskObj>>> phoenixApiArrayListMap); } PhoenixBuilder 需要实现 6 种方法: 首先,将收集上来的 Task,按照 API 进行分组,Task 存在依赖调用的都进行收集; 按照 API 进行收集 Trans,后续 Trans 使用请求线程进行串行执行; 判定每个 API 收集上来的 Task 是否存在相互依赖或循环依赖; 将每个 API 收集上来的 Task 按照先后依赖关系进行分组划分; 打印并发分组信息,用来给开发者调试及校验使用; 由于存在依赖关系,需要进行分层设计,这里可以结合 Phoenix 框架 怎么组织设计一个框架 来看,然而每一层并不需要关系执行的顺序问题,这里采用了最简单的数据结构存储分层信息,Map<String, ArrayList<ArrayList<TaskObj>>> Key 用来标识属于哪个 API 请求的并发分组,Value 则采用最简单的二维数组进行存储,每一维分别存储需要进行执行的 Task 任务。 遇到的问题 怎么判定存在环 由于我们要进行构建的是有向无环图,那么存在相互依赖的 Task,在框架设计逻辑中是行不通的,若存在相互依赖,那么究竟该先执行哪个 Task 呢? 可以看到上图,只要有两个场景: 相互依赖关系:TaskB 与 TaskD 存在相互依赖,那么就不能确定执行顺序; 环状依赖关系:TaskD、TaskF、TaskG 和 TaskE 存在依赖环,也无法确定执行顺序; 相互依赖关系判定比较简单,就是检索一个 TaskA 依赖的 TaskB 是不是也依赖这个 TaskA, 循环依赖判定相对来说比较复杂,需要遍历图的所有路径,若路径存在闭环,则代表着存在环,反之,就是不存在环路,代表就是单向依赖的分支路径。 怎么划分并发分组 划分并发分组,就是将彼此没有依赖关系的 Task 按照依赖的先后顺序进行分组,其实就是按照图的深度遍历。 这里的遍历,由于有依赖关系,可以采用向上遍历或者向下遍历的方式,我们采用了压栈的方式处理: 向上遍历 首先找到没有被依赖的 Task,这是一组,之后存入数组压入栈底; 之后栈底的 Task 收集出来需要依赖的 Task,这些收集上来的 Task 需要再判定是不是被其他 Task 依赖,若是依赖的话,则保存在临时的 Task 数组中,最后将剩下 Task 就是只被栈底 Task 数组依赖的 Task,那么将这个分组继续压入栈内; 重复第 2 步,把栈底的 Task 换成栈内最上层的数组,之后再把临时 Task 追加到收集出来需要依赖的 Task 上,去重,之后重复执行; 最后执行到剩下的 Task 没有依赖的 Task,这就是最后一个并发组,之后压入栈内; 最后程序执行的时候,就是先执行栈顶部的并发组,之后一次出栈执行。 向下遍历 首先找到不依赖其他 Task 的 Task,这是一组,之后存入数组压入栈底; 之后栈底的 Task 收集出来依赖这个分组的 Task,这些收集上来的 Task 判定是不是被其他 Task 依赖,若是依赖,也是保存在临时的 Task 数组中,最后就只剩下只依赖栈底的 Task 数组的 Task,之后将这个数组压入栈内; 重复第 2 步,把栈底的 Task 换成栈内最上层的数组,之后再把临时 Task 追加到收集出来需要依赖的 Task 上,去重,之后重复执行; 最后执行到剩下的 Task 没有任何 Task 依赖,这就是最后一个并发组,之后压入栈内; 此时,这个堆栈存储的是最先执行的 Task 并发分组在栈底,最后执行的在栈顶,需要进行反转操作,之后再依次进行执行。 为何要使用"策略模式" 在开发程序的时候,大家都不约而同地讲究程序的横向扩展能力,将核心的关键的任务拆分成具体执行的子任务,这样不仅可以提高程序的可阅读性,而且还可以扩展不同的遍历算法,用来后续框架的持续优化。 不仅这里,Phoenix 框架尽可能的采用策略模式实现,将核心功能点都进行拆分,做到模块化设计,这样的设计正是 Phoenix 框架的设计初衷,生生不息,持续迭代。 写在最后 本篇文章主要讲了如何进行自动构建有向无循环图的思路及遇到的问题,其实在开发中,这种解决依赖关系的场景还有很多,其实抛开上层的业务实现或者框架需求来看,底层就是最基本的数据结构,算法,图的遍历场景在当今比较火的 AI 场景下也是如此。 感谢你的阅读,你要是有好的方案或者好的 idea 可以与我一起交流,最后,如果你感兴趣,推荐关注公众号或订阅本站,欢迎互动与交流,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/5/13
articleCard.readMore

Phoenix框架 从0到1设计业务并发框架 并发线程池的核心设计

背景 从 0 到 1 设计业务并发框架系列: Phoenix 框架 小米商城产品站革新之路 Phoenix 框架 怎么组织设计一个框架 前两篇文章已经讲述了我设计框架的背景以及抽象设计的细节,今天讲一下并发框架最为关键的并发线程池的核心设计,主要讲一下在设计线程池划分遇到的问题以及最终我采用了哪种方式实现的。 将存在依赖关系的 Task 进行划分分组后,依次执行分组就可以拿到所有想要的结果,但是怎么划分线程池、设置线程池是面临的问题。 接下来,我将实际业务中的复杂度简化设计,将问题具象化呈现给大家。 方案:公用线程池 方案 最开始,我计划将分配的 Task 公用一个线程池,让 Task 去线程池竞争资源,如下图: 但是很快发现,单个线程池一旦请求数量上来,某个 Task 接口变慢就会导致整个接口成功率急速下降,直至不可用的状态。 为什么会出现这种情况呢? 效果 T1 时刻,第 1 波流量进来,之后率先执行 TaskA 或者 TaskB; TaskA 请求的快速递增,接口变得越来越慢; T2 时刻,还有两个 TaskA 并没有执行完毕,之后第二波流量进来: 第 1 波流量开始执行 TaskC 和 TaskD; 第 2 波流量进来,也有 TaskA 和 TaskB 获取到线程执行; T3 时刻,此时已经有 4 个 TaskA 还没有执行完,并且最开始的两个 TaskA 要面临着超时情况: 第 1 波流量执行的 TaskA 面临超时中断的情况; 第 2 波流量执行的 TaskA 也在运行状态中; 第 3 波流量进来,情况变得复杂,新的流量,有 TaskA 和 TaskB 进行执行; 此时第 1 波流量前两层运行完毕,开始执行 TaskE; 此时第 2 波流量的前一层运行完毕,开始执行 TaskC 和 TaskD; 之后按照 TaskA 始终慢的情况继续发展……. Tn 时刻,此时线程池大部分已经被前 n 波流量的 TaskA 占据着,并且大量被中断超时,其他 Task 无法竞争到线程进行执行。 这样的话,接口的可用性完全取决于 TaskA 的可用性,但是还有一个致命的问题就是其他 Task 无法执行或者由于依赖问题,前置该获取用作请求参数大部分为空,也无法正常请求,这样就算是接口返回了数据,也是不全的数据。 这种方案存在共用线程池大量线程等待超时的情况,是不可取的。 方案:分层线程池 方案 公用线程池的情况肯定是有问题的,在此基础上,尝试将分层并发划分不同的并发池,每一层公用线程池,如下图: 上了分层公用线程池之后,压力测试发现效果只有小幅的提升,没有达到预期的目标,甚至来说相差很远,为啥会出现这个问题? 效果 我们还是假设 TaskA 会随着请求量上来会大面积超时来举例。 T1 时刻,第 1 波流量进来,之后率先执行 TaskA 或者 TaskB,此次线程池 2、3 没有执行到; TaskA 请求的快速递增,接口变得越来越慢; T2 时刻,还有两个 TaskA 并没有执行完毕,之后第二波流量进来: 第 1 波流量开始执行线程池 2 的线程 TaskC 和 TaskD; 第 1 波流量存在 TaskC 执行完,陆续开始执行线程池 3 的线程 TaskE; 第 2 波流量进来,也有 TaskA 和 TaskB 获取到线程执行; T3 时刻,此时已经有 4 个 TaskA 还没有执行完,并且最开始的两个 TaskA 要面临着超时情况: 第 1 波流量执行的线程池 1 TaskA 面临超时中断的情况; 第 2 波流量执行的线程池 1 TaskA 也在运行状态中; 第 3 波流量进来,情况变得相对来说比较复杂,新的流量; 此时第 1 波流量前两层运行完毕,开始执行线程池 3 TaskE; 此时第 2 波流量的前一层运行完毕,开始执行线程池 2 TaskC 和 TaskD; 之后按照 TaskA 始终慢的情况继续发展……. Tn 时刻,此时线程池 1 大部分已经被前 n 波流量的 TaskA 占据着,并且大量被中断超时,由于依赖于 TaskA 和 TaskB 的结果作为下层的入参数: TaskA 过慢占据着接近 100% 的线程池 1 的资源; TaskB 竞争不到资源,被超时中断; 最后接口还是发展到不可用的状态,其实和公用线程池的问题一样,也还是存在大量线程等待超时 的情况。 这种公用线程池的现状是不可取的,那么该如何划分线程池来执行呢?其实分而治之的思想就可以解决这个问题,也就带来了 3.0 版本,独立 Task 线程池的方案。 方案:独立线程池 无论怎么公用线程池,都会出现被挤占的情况,只有将每个 Task 划分单独的线程池,才不会出现抢占等待的问题,那么如何设计的呢? 方案 每个 Task 单独创建线程池来承接流量,各个线程池互相不干扰,同时承接流量交给 CPU 抢占资源进行调度运行。 效果 由于是单独承接流量,这种设计满足了高可用的目标,还是依照 TaskA 接口随着并发请求的提升,接口越来越慢直至不可用,之后再加入一个条件,就是 TaskC 的执行条件是 TaskA 执行完毕的结果。 T1 时刻,第一波流量进来,所有线程池的线程都占满,开始进入核心调度执行; T2 时刻,第二波请求进来,第一波请求的 2 个 TaskA 还没有执行完毕,其他线程池的线程逐渐承接第二波请求等待调度; T3 时刻,第三波请求进来,这时候情况比较复杂: 第一波流量的 2 个 TaskA 已经超时被中断了,对应的 TaskC 的线程池的两个 TaskC 线程等待 Task 的执行结果失败,结束任务; 第二波流量的 2 个 TaskA 还没有执行完毕,也濒临超时; 其他线程池执行均正常运行; 就这样过了一段时间 … Tn 时刻: TaskA 已经达到了不可用的状态; 对此有依赖关系的 TaskC 也逐渐达到不可用状态; 其他线程执行正常; 这么看,针对于一个接口调用几十个上百个接口的场景,不会因为一个接口或者有依赖关系的接口可用性降低而影响整个接口的可用性,同时只要对单个线程池做好监控,加上报警即可动态感知哪些上游接口失败而及时通知到对应的系统维护同学,这样就大大的降低了维护成本。 这个版本作为线上生产环境的第一个版本推了上去,单台 8C 8G (k8s) 的配置空跑框架达到了 QPS 在 1.4w,接口可用性在 99.96%(结果仅供参考,根据公司集群部署策略、机器性能等问题会有浮动)。 但是,这种目前还是存在着显而易见的问题,就是每个 Task 执行的接口的接口响应都不是一致的,有的在 50ms 内、有的在 100ms 内、有的比较慢 500ms 内,分配相同的线程池数量是不合理的,因为这样就会造成 CPU 调度不公平,那么怎么让调度运行的比较公平呢? 优化 针对于这个问题,将线程池大小按照权重创建,像是比较慢的接口但是多等待一定时间可以返回的,我们就多分配线程池大小,接口响应很快的,我们就相对减少线程池大小,这样的设计可以在保证接口的可用性兼顾接口返回字段的完整性。 写在最后 本篇文章主要讲框架设计中怎么将划分好的分层并发执行,最终我们采用了独立线程池的方案,并且按照耗时、CPU 核数等权重评估分配每个 Task 任务线程池的大小,让 CPU 线程调度来确保线程都尽可能的公平执行到,最终保证接口的并发需求及高可用的场景。 如果你感兴趣,推荐关注公众号或订阅本站,欢迎互动与交流,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/4/7
articleCard.readMore

Phoenix框架 从0到1设计业务并发框架 怎么组织设计一个框架

上篇文章主要讲了设计 Phoenix 框架前的遇到的问题和设计框架的思路 《 Phoenix 框架 从0到1设计业务并发框架 小米商城产品站革新之路》,本篇文章主要讲一下如何设计框架的。 不死鸟并发框架,是自动构建有向图按照深度进行构建并发组并进行并发调用结果的框架。 产品站业务静态接口与动态接口都需要调用大量的后台服务进行获取数据进行业务编排,而各个并发调用之间又相互存在依赖,采用并发组设计拆解依赖,同时并发控制调用,BO to DTO 采用统一的 Transfer 层进行设计,开发人员只需要关系定义每次调用事件的 Task 和 Transfer 代码逻辑的书写,直接返回业务数据。 名词解释 PhoenixFramework 不死鸟(凤凰)框架,此业务并发框架的名称; Task 在业务并发中定义一次调用,可以是 HTTP、DUBBO 或者是 Redis 获取、MySQL 读库操作; Transfer 在业务定义中是一个子业务模块的转换逻辑将 BO 数据转换为 DTO 数据; Task 与 Trans 注解 怎么定义 Task 在框架设计之初,我们内部有两种方案,一种是继承抽象类实现的方式,Task 通过继承实现 PhoenixTask 类实现定义 Task,另外一种是采用注解的方式,将每个 Task 都定义成具有强约束的 Task ,并且把详细的描述信息在注解中定义,给开发人员一目了然的设计思路。 经过内部讨论,我们选择了 Java 优秀的语言特性,注解的方式声明定义 Task ,这样的定义使得代码简洁明了,也有利于通过 Spring Bean 收集工具来收集我们的定义。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 /** * PhoenixTask * 任务注解 * * @author debuginn */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface PhoenixTask { String taskName(); // 任务名称 String[] beforeTaskName() default {}; // 前置任务 String[] filterPlatform() default {}; // 过滤渠道,黑名单 String[] taskBoName(); // 任务生成的 BO 数据 用来校验冲突 } PhoenixTask 注解定义非常简单: taskName用来标识任务名称,采用枚举,强制约束命名的唯一; beforeTaskName前置任务,前面讲到每个任务都是一次事件,区分前置任务是需要在并发调用的时候等待结果的返回,之后用来作为此 Task 调用的前置参数; filterPlatform 过滤渠道,也就是黑名单的功能,但请求渠道在 Task 中声明了黑名单,在并发执行的时候就自动屏蔽掉执行; taskBoName任务转化为 BO 的数据,通过接口调用或者中间件获取数据,转化为 Transfer 层使用的数据,在框架层做数据参数校验; 怎么定义 Trans Trans 是 Transfer 的简称,同样和 Task 设计一样,也是采用注解的方式定义: 1 2 3 4 5 6 7 8 9 10 11 12 13 /** * PhoenixTrans * 业务编排注解 * * @author debuginn */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface PhoenixTrans { String transName(); // 业务编排块名称 String apiName(); // 执行 使用并发 api String[] tasks() default {}; // 依赖的 task 任务 } PhoenixTrans 注解定义同样非常简单: transName用来标识业务编排块名称; apiName 用来区分这个 Transfer 业务编排是隶属于哪个并发 API 所属的; tasks就是定义依赖的 Task 任务有哪些,一个业务编排可能会利用 n 个 Task 返回的 BO 数据进行编排; 大家在这里可能会比较疑惑,为啥 Task 中没有定义 apiName 而是 Transfer 中定义的,是因为在设计中,为了便于后续 Task 可以被 n 个并发 API 共·用,这样在 Transfer 定义了 apiName,之后通过 tasks 定义依赖的 Task 就可以推断出这个 Task 目前是被哪一个并发 API 使用的。 怎么收集 Task 和 Trans 自定义了 PhoenixTask 和 PhoenixTrans 注解,通过声明一个 AnnotationProcessor 继承 BeanPostProcessor 来进行收集定义的注解。 首先是根据注解类收集上来对应的 Task 和 Trans; 根据不同的 Trans 划分不同的 API,收集不同 API 依赖的 Task; 按照 Trans 是否进行依赖过滤使用到的 Task; 根据 Task 之间的相互依赖关系,将 Task 进行分组; 这样就完成了对框架的分层与自动构建的设计,框架的设计主要的是要思考如何将实际业务中使用的模块抽象化设计,同时要思考框架的扩展性与强约束性。 结尾 本篇文章主要讲解我如何将业务与调用关系进行抽象成 Trans 与 Task 的,接下来我将讲述并发框架并发线程池的核心设计、配置化思考、监控设计以及自动构建算法等系列文章。 如果你感兴趣,推荐关注公众号或订阅本站,欢迎互动与交流,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/3/18
articleCard.readMore

Phoenix框架 从0到1设计业务并发框架 小米商城产品站革新之路

前言 小米商城产品站之前由于历史原因,存在着诸多问题与不便,随着技术的快速变革,技术部中台化的建设,越来越不适用于现在快速迭代的业务需求,接下来我将以技术的视角讲解我们遇到的痛点,以及解决这些痛点的思路,也就是 Phoenix 框架诞生的故事。 为啥要进行设计一个框架,其实是业务发展导向的结果,若是我们不进行设计,那么我们会遇到如下一些问题: 在新的产品需求规划下,无法承接大型项目,只能进行小修小改; 小米网产品站最初,每个端一套代码逻辑,风格各异; 历史沉淀,一个接口函数 2000 多行,熟悉代码逻辑的成本越来越大; 隔离性差,服务可用性严重依赖下游,下游一个接口的抖动都会给我们接口带来恐慌; 技术上整体中台化建设,随着调用接口越来越多,接口越来越慢; 代码没有解耦,特别对新同事而言,新项目上线风险高; 缺少 Go 基础组件的维护,无法对下游接口实时监控; 思考 我们从技术上计划进行重构,那么我们如何将现有的调度逻辑抽象出一套兼顾稳定性、便捷开发、可维护性且可监控的框架模型是我们首先带来的问题。 我去调研了开源的一些并发框架,发现传统的并发调度模型基本上都有依赖关系、超时控制、线程池分配调度、熔断限流、接口监控等功能。 为啥我们没有直接使用开源并发框架进行开发呢? 我调研发现业界 LiteFlow 框架是最受欢迎与好评的框架,于是在 Github 上面去了解框架底层实现的细节,随着深入阅读源码,发现这款框架设计的是真的很优秀,但是也过于庞大、复杂,特别是 EL 规则的写法,相对来说还是有一定的上手成本。 那么我就在思考,我作为业务开发人员的话,我不想关心这么复杂的依赖关系,只需要关心自己产品站业务调用到的中台的接口及其依赖接口即可。特别是大型接口捆绑了几十个下游接口的逻辑,要是理解每个接口的设计细节更是不太可能的,要是依赖关系特别复杂,那么 EL 规则会写的非常复杂且维护成本极高。 那么该如何设计一款轻量、快速、高效、从根本上解决开发人员手动维护依赖关系的并发框架呢? 既然存在依赖关系,那是不是可以通过算法进行自动构建依赖关系呢? 设计 根据产品站的实际场景,我们发现,调用下游接口若干个,且请求接口存在不同的请求协议与不同的中间件。 更重要的是,接口存在着依赖关系,我们梳理接口调用发现,接口依赖正好是有向依赖图的结构, 那么我们就可以进行遍历依赖关系进行编排并发分组。 这样就解决了依赖的问题,我们可以依次并行执行每个并发组的任务,这样就可以得到所有接口或依赖的结果。 那么获取到结果之后,怎么进行业务逻辑的编排,怎么隔离下游接口,其实原理很简单,既然任务可以进行分层,那么我们业务调用、业务编排、防腐蚀层也可以进行分层设计。 Transfer 层的作用是业务逻辑层,用来进行业务编排,将 BO 数据提供给客户端使用; Task 任务层是并发执行的核心设计层,在这里通过并发分组的每个子 Task 在这里进行编排后执行调用,用来进行超时控制、耗时统计等操作; Infrastructure 层作为防腐层设计,用来隔离下游接口的调用,这样的设计提高了接口的稳定性; 写在最后 好了,上文就是给大家讲解的自动构建并发调用图的业务框架,也就是 Phoenix Framework。 Phoenix,最初在周志明老师的网站"凤凰架构"提及,一方面是对周老师的架构设计理解与 Java 相关知识学习的致敬,另一方面,Phoenix 不死鸟,软件的生命周期也是如此,随着业务的快速发展诞生、并随着业务的的收缩而凋亡,生生不息。 最后,我会以系列的方式进行讲解这个框架遇到的问题以及解决思路,感谢大家的阅读,大家要是感兴趣的话推荐大家关注公众号,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2024/3/7
articleCard.readMore

2023 年度总结

工作 今年工作经历了比较大的调整,由于部门技术栈逐渐趋于统一从 Go 技术栈转换到 Java 技术栈,也就是说我会双语言啦,自己也开始从事 Java 相关业务的开发,其实在 22 年底就开始接触 Java 服务了,按照学习计划,其实切换起来,还是相对来说比较简单,也没有太大的阻力,但是随着深入学习,发现自己学习的知识还是太少了,只能是 Java 庞大生态的冰山一角,值得自己骄傲的是,这一年,在 leader 和团队同事的支持和帮助下,我从 0 到 1 实现了一套并发框架,命名为 PhoenixCore,目前已经应用在了小米商城产品站(也就是说,只要你访问过小米商城,这套框架就为你提供过服务 😊),年后我会逐一整理并发表一系列的围绕并发框架的系列文章,来讲解我是如何实现这个框架以及我遇到的相关问题及解决思路。 站点 今年,站点最大的变化就是完成了对站点的静态化建设,可以将重点放在博客文章的输出中去,同时省去了自己维护站点的大量精力。目前站点的运营模式就是 Github 来提供文章的编写及打包发布,使用 Github pages 部署,CDN 使用免费的 Cloudflare 和 jsDelivr,(这也就代表着网站可能会出现访问不稳定 😛),同时将站点域名从 debuginn.cn 转换为 debuginn.com。 今年由于站点域名和子域名的调整,统计开始包含自己的子域名访问记录,由于期间有部分访问记录丢失,环比指标都有下降,不过随着自己开始专注于文章的输出,情况开始变好。 由于自己重新调整了博客链接组织结构,404 的页面一直占据着很高的比例 😂。 文章 今年输出并不是很多,还是和去年一样,都一一陈列一下吧。 2023 年为何我还在使用 RSS 谈谈 ChatGPT 如何接手并维护一个项目 全站静态化升级完毕 FAAS 调研笔记 长桥港美股团办活动 希望我的文章可以带给你帮助,希望我和大家一起变得更强~ 开源 今年由于自己博客迁移到了 Github,所以说活跃多了起来 🐶。 生活 23年去了很多地方旅游,云南(昆明、大理、丽江、香格里拉)、甘肃、天津、河北(张家口)、山东(济宁、威海、青岛)。 3月份去奥森桃花谷看了桃花,人很多、花更美; 4月份和自己的爱人一起去了云南,喂了滇池的海鸥、感受到了苍山洱海的美、看到了大冰的小屋、看到了雄伟的玉龙雪山、体验到了香格里拉藏区人们的淳朴与善良好客; 5月份去了甘肃老家,感受到了大西北的豪情、辽阔,看到了一望无际的荒漠与祁连山脉,看到了《隐入尘烟》中甘肃特色的土房子,品尝到了特色的面卷子与羔羊; 同 5月去了环球影城,感受到了电影世界。去北动看“西直门三太子”萌兰,很遗憾天太热,没看到,只看到了萌大和其他熊猫; 6月去了天津,看了民国风的五大道,听了场相声,还品尝了特色小吃; 7月陪着姐姐姐夫大外甥在北京城逛了一番,很快乐; 10月回了趟家,之后去威海,自驾到了山东最东端 - 始皇东巡处,看到了搁浅的“布鲁维斯号”,去青岛的海鲜市场,买了海鲜,赶了海。 23年,也一直坚持着徒步,参加了 4次活动,其中 2次夜爬活动,看到了星空、看到了日出。 摄影 今年由于旅游和徒步,拍了很多的照片,和往年一样,每年我都会精选十张图片放在摄影展站点中,欢迎你的访问。 Debug客栈摄影展:https://photo.debuginn.com 阅读 今年继续阅读,继续成长~ 文论《毛泽东选集》看了新民主主义革命至抗日战争的相关章节,不得不佩服领袖的远见与决策,推荐阅读; 理财《富爸爸穷爸爸》怎么去积累财富,摆脱穷人思维,跳出“老鼠赛跑”游戏; 技术《Apache Dubbo 微服务从入门到精通》Java 协议相关书籍; 传记《埃隆·马斯克传》遵循“第一性原理”,创造奇迹的人,推荐阅读; 小说《太白金星有点烦》如何规划西天取经,看太白金星与观音菩萨的运作; 总结 23年,是成长的一年,也是幸福的一年,今年工作稳定前行,努力突破自己,生活上稳定前行,这一年去了很多的地方,看到了世间不同的生活方式,24年,继续努力,奔着小目标继续前进,继续收获快乐、幸福与圆满。 2024,新年快乐~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2023/12/31
articleCard.readMore

2023 年为何我还在使用 RSS

什么是 RSS RSS(英文全称:RDF Site Summary 或 Really Simple Syndication),中文译作简易信息聚合,也称聚合内容,是一种消息来源格式规范,用以聚合多个网站更新的内容并自动通知网站订阅者。使用 RSS 后,网站订阅者便无需再手动查看网站是否有新的内容,同时 RSS 可将多个网站更新的内容进行整合,以摘要的形式呈现,有助于订阅者快速获取重要信息,并选择性地点阅查看。 RSS 作为 1999 年诞生的技术,并在 2009 年更新完最后一版本后,没有再进一步更新的技术,为啥在 2023 年,我还在使用这个“原始”的技术? 答案是肯定的,因为 RSS 好用,通过 RSS 我可以聚合我感兴趣领域的博客,每日定时拉取获得博客作者新发布的内容。 RSS 的优点 聚合阅读,通过 RSS 阅读器订阅网站,我可以每日在特定时间来阅读有更新的站点; 订阅免费,订阅的网站,无需进行付费,便可以使用开源的阅读器进行阅读; 稳定输出,订阅的网站,无需借助第三方平台进行数据传输或者聚合,只要网站在,源也就在; 阅读友好,使用 RSS 阅读器阅读网站,可以过滤源网站内的广告和观看各种风格各异的 UI ,更专注于内容; 避免发散,目前大量的短视频 APP 快速产出无营养的视频,不能注意精力去阅读; 怎么订阅 直接订阅 针对你感兴趣的网站,一般网站都会有类似彩虹 🌈 的表示,如下图: 当然,你要是感觉我的内容对你有帮助,欢迎通过 RSS 订阅,订阅地址。 RSSHub 当然,随着这一项技术不再那么流行,一些站点在开发的时候就不再提供 RSS 内容输出的支持,但是由于有大众的阅读,这里就给大家推荐 RSSHub,通过讲源网站输出内容进行 RSS 标准格式的封装来提供 RSS 订阅。 官方网站:https://rsshub.app/ 官方文档:https://docs.rsshub.app/ 订阅工具 Netnewswire Netnewswire 是一款开源的 IOS/MacOS 的阅读器,他支持 iCloud 云备份,可以在 iPhone/iPad/Mac 上随时切换阅读。 官方网站:https://netnewswire.com/ 开源地址:https://github.com/Ranchero-Software/NetNewsWire RSSHub Radar 是一款浏览器插件,目前主流的浏览器都支持安装,可以帮助我快速发现和订阅当前网站的 RSS 和 RSSHub,开源且免费。 插件地址:https://chrome.google.com/webstore/detail/kefjpfngnndepjbopdmoebkipbgkggaa 开源地址:https://github.com/DIYgod/RSSHub-Radar 安装好,通过点击图标,可以查看网站支持的订阅方式以及订阅的内容范围,可以便捷的跳转到阅读器进行订阅。 订阅分享 目前我订阅了 61 个网站源,包含科技、技术、设计、创意、阅读等板块,在这里分享出来,大家喜欢的话就可以点击订阅。 名称 网站地址 订阅地址 类别 备注 爱范儿 https://www.ifanr.com https://www.ifanr.com/feed 科技,IT 财新周刊 https://weekly.caixin.com https://rsshub.app/caixin/weekly 财经 彩虹Smiling™️ https://www.smilingblog.cn https://www.smilingblog.cn/feed 博客 菜鸟Miao https://newbmiao.github.io https://newbmiao.github.io/atom.xml 博客 茶歇驿站 https://maiyang.me https://maiyang.me/index.xml 博客 潮流周刊 https://weekly.tw93.fun https://weekly.tw93.fun/rss.xml 周刊 程序印象 https://www.cn18k.com https://www.cn18k.com/atom.xml 博客 程序员的喵 https://catcoding.me https://catcoding.me/atom.xml 博客 大俊的博客 https://darjun.github.io https://darjun.github.io/index.xml 博客 董泽润的技术笔记 https://mytechshares.com https://mytechshares.com/atom.xml 博客 飞雪无情的博客 https://www.flysnow.org https://www.flysnow.org/index.xml IT,GO 峰云就她了 https://xiaorui.cc https://xiaorui.cc/feed GO 虹线 https://1q43.blog https://1q43.blog/feed IT 极客公园 http://mainssl.geekpark.net https://www.geekpark.net/rss 科技,IT 煎鱼 https://eddycjy.com https://eddycjy.com/index.xml GO 酷 壳 https://coolshell.cn https://coolshell.cn/feed 博客 R.I.P. 李文周的博客 https://www.liwenzhou.com https://www.liwenzhou.com/index.xml GO 刘未鹏 https://mindhacks.cn https://mindhacks.cn/feed 博客 美团技术团队 https://tech.meituan.com https://tech.meituan.com/feed/ 技术 面向信仰编程 https://draveness.me https://draveness.me/feed.xml 计算机 鸟窝 https://colobu.com https://colobu.com/atom.xml GO 苹果fans博客 http://www.mac52ipod.cn https://www.mac52ipod.cn/feed.php/feed Apple 阮一峰的网络日志 http://www.ruanyifeng.com/blog http://www.ruanyifeng.com/blog/atom.xml 周刊 科技爱好者周刊 四火的唠叨 https://www.raychase.net https://www.raychase.net/feed 博客 唐巧的博客 http://blog.devtang.com http://blog.devtang.com/atom.xml 阅读 湾区日报 https://www.wanqu.co https://rsshub.app/wanqu/news 科技 微软亚洲研究院 https://api.feeddd.org/feeds/63744936e11908407781ff61 技术 午夜咖啡 http://jolestar.com https://jolestar.com/feed.xml 技术 徐靖峰 https://lexburner.github.io https://lexburner.github.io/atom.xml Java 阳志平的网志 https://www.yangzhiping.com https://www.yangzhiping.com/feed 技术 一个草根站长的博客 https://www.zz1984.com https://www.zz1984.com/feed/ 博客 月光博客 https://www.williamlong.info https://www.williamlong.info/feed 科技 云风的 BLOG https://blog.codingnow.com https://blog.codingnow.com/atom.xml 博客 知乎每日精选 http://www.zhihu.com https://www.zhihu.com/rss 热点 竹新社 https://t.me/s/tnews365 https://rsshub.app/telegram/channel/tnews365 新闻 AIGC Weekly https://quail.ink/op7418/feed/atom AI Apple Newsroom https://www.apple.com https://www.apple.com/newsroom/rss-feed.rss Apple BMPI https://www.bmpi.dev https://www.bmpi.dev/index.xml 博客 chai2010 的博客 https://chai2010.cn https://chai2010.cn/index.xml 博客 Dave Cheney https://dave.cheney.net https://dave.cheney.net/feed/rss GO Debug客栈 https://blog.debuginn.com https://blog.debuginn.com/index.xml 博客 关注 Go Programming Blog https://www.ardanlabs.com/blog https://www.ardanlabs.com/blog/index.xml GO huxihx https://www.cnblogs.com/huxi2b/rss 博客 idealclover https://idealclover.top https://idealclover.top/feed 博客 Issues on 省流 https://shengliu.tech/issues/ https://shengliu.tech/issues/index.xml 日报 KAIX.IN https://kaix.in https://kaix.in/feed 咖啡,创业 LinkinStar’s Blog https://www.linkinstars.com https://www.linkinstars.com/atom.xml 博客 No Headback http://xargin.com https://xargin.com/feed 博客 OpenAI Blog https://openai.com/blog https://rsshub.app/openai/blog AI Wujunze`s Blog https://wujunze.com/posts https://wujunze.com/posts/index.xml 博客 Pseudoyu https://www.pseudoyu.com/zh https://www.pseudoyu.com/zh/index.xml 周报 Ri Xu Online https://xuri.me https://xuri.me/feed GO SegmentFault行业快讯 https://segmentfault.com/blog/news https://segmentfault.com/feeds/blog/news 周刊,科技 STRRL’s backyard https://strrl.dev https://strrl.dev/index.xml 博客 The Go Blog https://go.dev/blog https://go.dev/blog/feed.atom GO The GoLand Blog https://blog.jetbrains.com https://blog.jetbrains.com/go/feed GoLand Tinyfool的个人网站 https://codechina.org https://codechina.org/feed 博客 Tony Bai https://tonybai.com https://tonybai.com/feed GO OPML 订阅文件:Feed订阅源文件 最后,你要有认为值得推荐的或者有价值的博客,都可以在评论区分享出来,让我们站在巨人的肩膀上变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2023/12/4
articleCard.readMore

谈谈 ChatGPT

前言 说起我真正使用落地的产品是从 22 年 11 月份开始,那时候 ChatGPT 刚发布灰度出来,面对这个对话框一样的界面,我发现这个简简单单甚至有些简陋的页面,竟然是技术变革的入口,看过大模型相关理论以及原理的同学,大家会发现,这个 AI 已经学习了我们近十年的信息,也就是说,他的知识储备已经高于我们在这个世界上的任何一个人,同一时间段,已经看到 V2EX 已经有人开始将高考试题让 GPT 去做了,当时的 GPT 可以考上一所不错的一本院校了,现在的 GPT4 我想应该是在全领域领先于各个行业了。 不出意外,ChatGPT 在全球理所当然的火了起来,他带来的是一场技术的革命,他区别于元宇宙、VR 或者区块链,因为元宇宙、VR 技术的发展到现在,需要的是基础材料的突破,当时现在(23年10月)还没有让人振奋人心的消息传来,区块链延伸而来的比特币,成为了全球热爱博彩的圣地、也成为了进行洗钱的工具,对社会和全球的发展没有进步意义,同时又白白消耗了大量的电力资源。 GPT 是利用现有的计算机科学技术进行突破,商业化速度比预想的要快很多,微软的 Edge 浏览器率先融合进了 GPT,成为了第一款商业化的产品。 工具 从接触使用 GPT 开始,发现自己逐步离不开这个工具,从基本的文章润色到代码的讲解及优化建议、再到提需求做一个小的工具,他的回答都令我非常满意,目前在工作生活中,都在使用了大量的 GPT 衍生工具,在这里也分享给大家: ChatGPT 使用最多的就是自家的网站与 APP,也就是我文章开头所说的 “对话框界面”。 OpenAI 官方网址:https://openai.com/ ChatGPT 官方网址:https://chat.openai.com/ MacGPT 由于现在官方没有发布 Mac 的桌面 ChatGPT APP,这款是目前感觉使用体验最好的软件了,可以在顶部工具栏和侧边栏呼出,使用起来非常便捷。 Chat2DB 这是一款数据库连接工具,通过 ChatGPT 或者其他的 GPT 工具将自然语言处理成 SQL 语言,也可以基于数据生成报表,使用起来十分便捷与方便。 Github 地址:https://github.com/chat2db/Chat2DB/ 感悟 从人类进入了刀耕火种的时代开始,大家就已经学会了使用工具,现在的 GPT 同样也是一种工具,目前,他会一定程度的帮助我们在工作中提升效率,有一些重复性的工作甚至会被他代替,事实也是如此。 目前我们还是将 GPT 的强大功能简单的接入到我们日常使用的工具中,这是最初的阶段,未来,GPT 会衍生出每个人的电子助理,甚至在未来,就如流浪地球的 MOSS 机器人一样,他们将有独立的意识。 不过在使用目前的产品中,发现了一些问题,就是他不知道他不知道,对于一些常识或者一些技能问题的回答,深度使用的话,不难发现,他就是在用关键参数或者关键词在创造答案,这个答案的可信度并不是那么高,甚至就是错误的。 前两天看到博客中有聊到 《ChatGPT 导致了 Stack Overflow 访问量的下降? 》 大家遇到问题不再集中思考来复现问题,发现更深层次的问题,而是通过 GPT 来快问快答,GPT3.5 的知识截止日期是2022年1月,代表着随着大家的产出约来越少,GPT 学习的参数也越来也少,随着系统或者语言的升级,相关文档越来越少,对于更专业的技能知识带来的就是未来 GPT 的准确率也会越来越不精准,目前来说没有看到这个问题的解答。 未来,我的文章将使用 ChatGPT 来帮助我润色文章与统计分析,当然,这篇文章就不是 AI 生成的 。 最后大家要是对 GPT 有什么想法或者有什么好用的工具,可以分享在评论区,让 AI 帮助我们解放生产力,让我们变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2023/11/12
articleCard.readMore

如何接手并维护一个项目

在工作中,接手负责管理别人开发或者前人开发的项目是每个开发人员的工作任务之一,那么,如何快速并且高效的消化接手过来的项目呢,本文主要讲解一些方法与实践技巧,希望可以帮助你快速了解你接手的项目。 系统文档 若是有最开始的包括后续优化的相关技术文档或者系统文档,对于接手过来的项目无疑是最有助于开发人员的方式。但是大家会发现往往接手过来的项目是没有这一类的文档的,交接过来的系统若是对开发有极高追求的,一般都会有文档,并且 README.md 中会有项目介绍包括相关文档,但是…… 往往我们拿到手的系统是纯代码,README.md 可能都没有这个文件,这种往往是最痛苦的,不过也是最锻炼梳理系统这项技能的。 那么我们就需要从下面这几个点来慢慢消化系统。 系统权限 交接过来的系统,一定要开好对应的权限,这对你全面了解系统以及后续维护系统有着至关重要的的作用,若没有权限,当系统出现问题时,领导找到你问问题原因,而你却在向领导申请权限,世纪名场面。 以下是常见的系统权限: Gitlab 仓库权限; Deploy 部署系统权限; Log 日志系统权限; Data 数据库管理系统权限; Alert 系统报警配置权限; Crond 任务调度器权限; Middleware 根据不同系统的中间件权限,包括不限于 Notify、RocketMQ、Redis 等; Auth 依赖系统授权信息权限; Test 测试平台的权限; API API 调试平台的权限; Sys 系统相关注册管理权限; 接手项目,开启权限是第一步也是必须要做的事情。 配置文件 通过配置文件可以看到一个系统的基本信息,比如说: 系统环境配置信息、注册信息、协议相关信息; 系统使用数据库配置信息; 系统使用 Redis 等中间件配置信息; 业务上使用的一些值定义; 库表设计 一般数据库表设计会存放在 dao 模块或者目录下,基本上是一表一文件定义,可以看到表定义的字段,并且可以看到对该表的一些“增删查改”动作。 若是底层系统设计,本身系统就是只提供给外部服务使用的,那么从数据库库表设计基本上就可以反推业务逻辑的设计,删除、更新、新增都是基本的业务逻辑动作,查询或者组合成事务的业务相对来说比较复杂,不过根据业务代码看着理解的话也比较简单一些。 缓存设计 缓存一般有两种,分别是: 被动缓存:一般是用于高并发场景,用于缓解下游中间件或者接口的瞬时压力; 主动缓存:这种是相对高级的缓存策略,用于分布式数据一致数据的返回; 分析刚接手的系统,从 cacheKey 就可以了解业务系统中为什么这样设计的: 1 product.info.pid.XXXX 上面这个例子可以看到记录的是一个产品 pid 为 XXXX 的缓存信息。 协议文件 若是接手过来的系统按照语义命名及划分路由的话,则通过 API 接口文档来看是一个很好的方式,因为通过 API 基本可以确定接手过来的服务有哪些业务,针对不同的业务又有哪些操作。 针对不同的语言、不同的协议,也有一些细微的差别: Java Dubbo 协议 一般 Java Dubbo 协议都是对外提供 API 模块的 pom 依赖的,声明都是使用接口来实现的: 1 2 3 4 5 6 7 8 9 // XXXX 业务模块 public interface XXXX { // 获取列表 Result<DataA> getXXXList(Context ctx, XXRequest req); // 获取列表 基于 uid Result<DataA> getXXXListByUid(Context ctx, XXRequest req); // 基于 uid 删除 XX 信息 Result<Boolean> delXXInfoByUid(Context ctx, XXRequest req); } Go Thrift 协议 Go Thrift 是使用 IDL 语言定义的协议,我们会基于 IDL 声明的接口,定义好接口出入参生成的 SDK 文件,通过看 IDL 定义的接口,就可以了解到接手的项目提供了哪些功能了: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct XXOrderResponse { 1:i64 orderId, 2:string orderInfo; } struct XXOrderRequest { 1:i64 orderId (go.tag = "json:\"order_id\""), } // 接口定义 service OrderService { // XX 订单 XXOrderResponse XXOrderInfo(1:XXOrderRequest params) } Go HTTP 协议 目前公司使用居多的 HTTP 框架是 iris 还有一个自研的 migo 框架(基于 beego 框架开发),都有一个相似的特点,就是会把 router 单独定一个文件,即 router.go : 1 2 3 4 5 6 7 8 9 10 11 12 13 // Init _ func Init(app *iris.Application) { app.Use(middlewares.RecoveryMiddle) app.Use(middlewares.PromeMiddle) app.Use(middlewares.LoggerMiddle()) // 小心打日志的配置 app.Use(innermiddle.CrossOrigin) // 是否使用跨域 app.Use(middlewares.RateLimitMiddleWare) // 配置限流 app.PartyFunc("/product", cproduct.Register) return } 编程语言 从依赖方去挖掘系统,看系统依赖了哪些业务方,也可以看出系统依赖了哪些基础包,还可以看到依赖的包对应版本(有经验的人可以看出是否有 bug )。 Java Maven 目前 Java 主流的依赖方式就是使用 Maven POM 定义依赖并管理依赖: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>product</artifactId> <groupId>cn.debuginn.blog</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>product-server</artifactId> <dependencies> <dependency> <groupId>cn.debuginn.blog</groupId> <artifactId>product-api</artifactId> <version>${product-api-version}</version> </dependency> <!--单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> </project> Go Module 目前 Go 语言也是大一统到 go mod 管理版本依赖了,由于 Go 是源码依赖,使用 go mod tidy 之后就可以通过点击依赖仓库来看源码了: 1 2 3 4 5 6 7 8 9 10 module github.com/debuginn/go-demo go 1.20 require ( github.com/dubbogo/gost v1.11.25 // indirect github.com/go-playground/validator/v10 v10.10.1 github.com/go-redis/redis/v8 v8.11.0 github.com/gobwas/ws v1.0.3 // indirect ) 流程图、泳道图 通过上述方式与方法,最后就可以根据业务代码,画出业务流程图及对应的泳道了,同时,也可以基于依赖关系,画出系统的架构图,梳理一个系统的最好方式就是有所沉淀,通过读代码把代码转化为文档的方式即可以对消化的系统有所输出,又可以提升自己的技术能力。 写在最后 最后,一个好的系统,是由系统设计文档、代码、注释和覆盖率尽可能全面的测试用例组成的,很多情况下大家由于不可抗拒的原因,最后只留下了运行的代码,这也是撰写本文的主要原因,希望对大家有所帮助,最后大家有什么好的方式和方法也可以在评论区分享出来,让我们一起变得更强。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2023/7/10
articleCard.readMore

我的项目

🤠 Hi,我是 Meng小羽 下面是我目前所使用到的技术栈及工具: Projects 项目名称 语言 项目介绍 star douban-movies 如何将豆瓣观影记录实时同步至博客中 - Debug客栈 leetcode 📝 一些算法记录 golang-developer-roadmap-cn 🤔️ 我该怎么才能成为 Go 的开发者? tools 🔧 一些简单的小工具集合 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2023/6/6
articleCard.readMore

全站静态化升级完毕

经历了三个月的整体站点迁移,数据的修复,网站的搭建,目前全站代码已经完成 github 代码托管部署,主站点重新调整设计,以后就不需要关心网站的动态部署问题了,代码托管的部署方式更有利于版本的迭代与维护,同时博客站点使用的是 markdown 文章,不必使用后台,也避免了被暴力破解的风险,接下来持续输出,更加注重文章的质量与水平,多输入多输出。 Debug客栈-主页 https://debuginn.com Debug客栈-博客 https://blog.debuginn.com Debug客栈-笔记 https://notes.debuginn.com Debug客栈-摄影 https://photo.debuginn.com 感谢支持,欢迎你持续访问与评论~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2023/5/14
articleCard.readMore

FAAS 调研笔记

FAAS 是什么 功能即服务 (FAAS) 是一类云计算服务,它提供了一个平台,允许客户开发、运行和管理应用程序功能,而 无需构建和维护通常与开发和启动应用程序相关的基础设施的复杂性。 构建遵循此模型的应用程序是实现“无服务器”体系结构的一种方式,通常在构建微服务应用程序时使用。 FAAS 最初是由 PiCloud 等各种初创企业在2010年左右提供的。 AWS Lambda 是大型公共云供应商提供的第一个 FAAS,随后是 Google Cloud Functions、Microsoft Azure Functions、IBM/Apache 2016年的 OpenWhisk (开源)和 2017 年的 Oracle Cloud FN(开源)。 国内的云厂商近几年也陆续提供 FAAS 产品,有阿里云 Serverless 服务、腾讯云云函数(Serverless Cloud Function,SCF)、华为云函数工作流(FunctionGraph)。 FAAS 优点 降低运营成本,开发人员不需要对服务器根据流量做规划,将部署平台的能力外包; 降低开发成本,Serverless 是整个应用程序组件商品化的结果,将功能相似的函数解耦,统一提供服务,减少重复建轮子; 扩展成本,Serverless 的架构就是将部署环境外包,水平扩张是完全自动、有弹性,并且有提供方来支持管理的; 偶尔的请求,一些提供给运营人员的操作很低频; 不一致的流量,函数扩容速度远远大于容器扩容速度,高效响应突发流量带来的扩容问题; 运营管理更轻松 容器的租户管理使得研发人员无需关心部署系统; 降低打包和部署的复杂性; 专注于业务代码,更快的迭代与部署; FAAS 缺点 控制权的转移,任何的外包策略,都会将部分的系统控制权移交到维护团队或组织,带来的就是不可控的系统停机、意外限制、成本变化、功能丧失、强制 API 升级等问题; 多租户问题,多个客户(租户)的多个软件在同一个机器上运行; 供应商锁定,一旦选择某个供应商或者维护团队,几乎是无法进行迁移的,代码的迁移大概率只能重构; 安全问题,会增加恶意攻击的剖面,增加攻击成功的可能性; 没有服务器内状态,持久化的数据无法在容器存储,只能借助第三方存储组件实现 cache; 测试问题,没有本地环境可以完全模拟云环境; 调试问题,云环境的调试目前还没有提供优秀的 tools; 业内 FAAS 的分支及发展 云服务商 产品 产品介绍 使用场景 客户案例 备注 AWS AWS Lambda AWS Lambda 是一项无服务器事件驱动型计算服务,该服务使您可以运行几乎任何类型的应用程序或后端服务的代码,而无需预置或管理服务器。 文件处理; 流处理;Web 应用程序;IoT 后端;移动后端; 可口可乐 西门子 Netflix Coinbase 阿里云 Serverless 工作流 Serverless 工作流(Serverless Workflow)是用来协调多个分布式任务执行的全托管 Serverless 云服务,简化开发、运行业务流程需要的任务协调、状态管理和错误处理等繁琐工作。用顺序、分支、并行等方式编排分布式任务,服务按照预设顺序协调任务执行,跟踪任务的状态转换,必要时执行用户定义的重试逻辑,确保工作流顺利完成。 多媒体文件处理场景;数据处理流水线场景;自动运维场景;解决运维无法可视化的问题; Serverless 应用引擎 SAE 是一个全托管、免运维、高弹性的通用 PaaS 平台。SAE 支持 Spring Boot、Spring Cloud、Dubbo、HSF、Web 应用和 XXL-JOB、ElasticJob 任务的全托管,零改造迁移、无门槛容器化、并提供了开源侧诸多增强能力和企业级高级特性。 微服务应用托管;弹性扩缩容场景;持续集成与交付; 贵州酒店集团 视野数科 爱奇艺体育 类似 side car ,用来管理应用,承接流量 Serverless 容器服务 ASK 是一款基于阿里云弹性计算基础架构,同时完全兼容 Kubernetes 生态,安全、可靠的容器产品。通过该产品,您无需管理和维护集群即可快速创建 Kubernetes 容器应用,并且根据应用实际使用的 CPU 和内存资源量进行按需付费,从而使您更专注于应用本身,而非运行应用的基础设施。 应用托管;在线业务弹性阔缩容数据计算 低成本支撑CI/CD任务执行 图森未来 越光医疗 腾讯云 云函数(Serverless Cloud Function,SCF) 腾讯云为企业和开发者们提供的无服务器执行环境,帮助您在无需购买和管理服务器的情况下运行代码。您只需使用平台支持的语言编写核心代码并设置代码运行的条件,即可在腾讯云基础设施上弹性、安全地运行代码。云函数是实时文件处理和数据处理等场景下理想的计算平台。 静态网站托管;构建 RESTful API;部署 Serverless 全栈 Web 应用;Serverless 全景录制方案; 腾讯视频 新东方 微信阅 腾讯教育 腾讯相册 百视通 猎豹移动 API网关 腾讯云 API 网关(API Gateway)是腾讯云推出的一种 API 托管服务,能提供 API 的完整生命周期管理,包括创建、维护、发布、运行、下线等。 Serverless HTTP;微服务整合;外部多端统一;业务整合;能力提供及售卖; 人人视频 江娱互动 腾讯视频 英孚教育 内部原理 FAAS 方向 运行架构 常规的一个服务在容器中启动的流程 FAAS 调用启动流程 在传统的服务启动或者是容器化的服务进行启动的是否,都是服务跟随者对应的平台(巨石架构的物理机器或者微服务化的 k8s 容器)的启动而启动,整个生命周期在 pod 的启动开始,在 pod 的关机下线操作结束,整个周期是比较长的,同时必须有实例存活(至少一台)来承接响应,研发人员除了需要关注自己的开发 code,还需要关注容器的大小、容量、数量等运维指标; Serverless 中的 FAAS 将研发人员最重要的业务逻辑抽离了出来,除了这部分需要去管理升级,剩下的都交由 FAAS 提供平台来提供服务,托管后的 FAAS 生命周期从 pod 的启动关机简化到了 执行函数 handler 的 init 以及执行函数时间,并且在一些低频的业务中,一些函数实例可以交由 FAAS 提供服务商进行回收,甚至在某些时间不起函数实例,当有事件进来之后在执行函数初始化及执行逻辑(因为函数初始化到可以服务的启动时间在 100ms 左右,当然不同语言以及不同的服务提供方的实现会影响这里的启动时间); 架构分层 其实理解起来比较简单,可以理解成我们的代码已经是与 PAAS 平台进行强解耦的结果了,我们的代码就是一部电视剧,一个操作系统安装了指定的视频播放器就可以播放我们的电视剧了,同理,我们现在只需要关心我们的函数内业务代码逻辑的定义,只要接口定义的按照封装平台的要求来开发即可,我们不需要关心运行的环境及系统,由于 runtime 已经到了 func 级别,热更新代码以及启动服务都是快速可以响应的。 Mesh 方向 综上,若 FAAS 代表着是“无服务器”架构的话,其实 Service Mesh 严格意义上不能称为是“无服务器”架构,它并不能将容器部署与代码部署隔离开,只是在服务响应中增加了一层代理,用来控制应用程序中服务请求的传递,可以使得服务到服务的通讯快速、可靠和安全。 运行架构 优点: 简化微服务和容器中服务之间的通信; 更容易的诊断通讯错误,发生在自己的基础设施层上; 支持加密、认证和授权等安全特性; 允许更快地开发、测试和部署应用程序; 放置在容器集群的边车代理可以有效的管理网络服务; 缺点: 运行时实例通过使用服务网格而增加; 每次服务的调用都要经过 sidecar proxy; 没有解决与其他服务或者系统的集成,以及路由类型或转换的映射; 网格管理的复杂性被抽象化和集中化; 架构分层 将调用限流、熔断、安全、服务注册与发现、服务管理等非业务逻辑的功能全部都放到 Sidecar 中去,本质上是一个管理性质进程在管理着业务逻辑性质的进程,进程之间的通讯使用的是 UDC(Unix domain socket)。 Reference https://en.wikipedia.org/wiki/Function_as_a_service https://serverless.aliyun.com/ https://cloud.tencent.com/product/scf https://www.huaweicloud.com/product/functiongraph.html https://martinfowler.com/articles/serverless.html https://aws.amazon.com/cn/lambda/ https://time.geekbang.org/column/article/226574 https://www.techtarget.com/searchitoperations/definition/service-mesh https://www.zhaohuabing.com/2018/03/29/what-is-service-mesh-and-istio/ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2023/2/15
articleCard.readMore

长桥港美股团办活动

办理港卡之后,在证券市场找到了一款注重用户交互的证券 APP(长桥证券 APP),使用体验是大家常用港美股证券 APP 中最好的,活动期间通过专属链接注册开户,可以申请港美股终身免佣,推荐大家使用,投资港美股主要就是港卡比较难办,现在长桥与两个银行搞团办活动,大家感兴趣的可以办理注册下,另外现在入金还有奖励,办理好港卡和银行账户,就可以投资港美股了,就可以做世界 Top 公司的股东了。当然要牢记股市有风险,投资需谨慎,理性投资。 注册长桥 办理港卡 准备办卡资料;ps 小米同学建议优先选择民生。 北京民生团办时间:2.21 - 2.24 北京南洋团办时间:暂未确定,火热报名中,预计 3 月底 动态更新 民生香港办卡资料 身份证 + 护照(有效期大于 6 个月); 任意国家多次往返签证; 员工工牌,实体卡; 现场会开民生内地卡,需转入 5w 人民币保留 3 个月后可提取; 南洋香港办卡资料 身份证 + 护照(有效期大于 6 个月); 任意国家多次往返签证; 现场会开南洋内地卡,需转入 1w 人民币,下卡可提取; 签证渠道 如无签证,可淘宝搜索尼泊尔签证,选择销量高的店铺,选择单次停留90天,180天有效期(一般是99元)。 注意:办卡信息和服务由银行提取,长桥不收取任何费用。 入金活动 ps. 根据不同月份活动动态更新。 长桥 2 月开工福利送不停! 开户福利 开户可享港股免佣福利、长桥现金打新免费(新老客户)、100 港币股票现金卡、新客专享美元货币基金,年化收益 4.65% 左右,资金不浪费。 新用户入金 2w 港币福利 600 港币股票现金卡 + 400 港币平台费抵扣卡; 两人拼团,每人额外再送 200 京东卡; 入金 30 天内交易一次美股期权,再送 200 港币股票现金卡; 港股终身免佣; 企业认证 如果你是可支持认证的企业员工的话,建议做一下企业认证,一般有企业邮箱的话直接输入邮箱账号接收验证码就可以完成认证了,企业认证后有需要优惠活动。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2023/2/12
articleCard.readMore

2022 年度总结

今年,时光依旧不饶人,步入社会以来,时间就似乎不会慢下来,都在光速飞逝,很遗憾,今年疫情依旧没有结束,3 月份的时候还作为密切接触者被隔离了 21 天,总的来说,今年有悲伤同时又十分幸运,感觉冥冥之中都是安排好的。 网站数据 2022年统计数据共享链接 摄影专区 今年为了兴趣爱好买了一台微单 索尼 α6400+18135 镜头,同时,也想着把自己的摄影作品呈现给大家,每年我都会更新一批图片到这里,选 10 张自己认为比较好的图片拿来展出,水平不高,但是每一张照片背后都有属于它的专属意义。直达链接 年度事件 疫情 有感动有奇迹 记得在 3 月份的某一天,突然接到领导通知,大家全体居家办公,具体情况听通知,不得外出,下午就接到了确认密接的防疫办的电话,让在家等着去集中隔离,由于密接人数比较多,第二天下午才安排到酒店隔离,一切安排都没有那么混乱,大家都有序进行隔离入住,庆幸的是,隔离的地方还不错,至少可以安心的在隔离期间办公了,隔离期间还下了一场雪,别有一番风景。 隔离期间,大家互相勉励,配合大白检测,大家都很有信心,隔离结束之后,得知大家作为密接都没有被感染 😷,像是一个奇迹,大家都好好滴,同时也传来了确诊同事阳转阴的好消息。 回头想来,这次防疫应该是北京政府做的一轮比较优秀的防疫案例了,做到了透明、快且安全。 是的 我们就这样相遇了 都说红螺寺和雍和宫很灵验,去寺院诚心叩拜了每一个佛祖菩萨,感谢佛祖菩萨,的确如此,是的,我们就这样相遇了,新的一年和她,真好。 新的一年,我们的故事正在续写~ 我坚信真诚的对待感情总会有好的结果,感谢相遇,感谢这神奇的缘分~ ps:不过多介绍,还有下文哦~ 是的 我成杨过了 12月17号在喉咙疼了好几天的情况下,最后还是测量的体温在 38.5 度,我已经感觉中招了,不出意外,接下来连续三天都持续高烧,浑身上下疼痛,之后还流鼻血,应该是高烧烧的,之后就是居家躺尸阶段,没有精神同时伴随有轻微咳嗽,后来做了抗原检测,果然中招,不过目前好了。大家注意防护与提高自身免疫力,很重要的。 好物分享 摄影设备:微单 索尼 α6400+18135 镜头; 键鼠套装:Mac 新一代键盘、触控板套装; 显示设备:目前主力生产使用的 LG HDR 4k 显示器; 辅助灯光:目前使用的是小米屏幕挂灯 1s,搭配米家使用非常流畅; 充电设备:小米无线充电宝+座充底座,既可以室内充当无线充电,又可以户外活动使用; 年度书籍 今年算是奋起阅读的一年了,短短三个月时间就啃下来很多书,很有营养,以后也要给自己补补能量,让自己变得更强。 技术 《Effective Go 中英双语版》bingo,很棒的一本 Go 入门数据; 公司 《一往无前》范海涛,讲解公司创立到十周年发生的故事; 小说 《黄金时代》王小波,自由?背叛?性? 读完没有啥感觉; 散文 《乖,摸摸头》大冰,第二次读,算是疫情三年禁锢肉体的另一种解脱; 技术 《图解 HTTP》上野,用通俗易懂的漫画讲解; 技术 《分布式缓存:原理、架构及Go语言实现》胡世杰,讲的很透彻,数据提供的很透彻; 技术 《深入理解Java虚拟机JVM高级特性与最佳实践》周志明,java 进阶书籍,值得推荐; 技术 《深入设计模式》,设计模式算是 code 中让房子长什么样的设计方法论了; 散文 《保重》大冰,没有读完,算是给自己的青春留下一章吧,保重; 年度文章 本年度很懒,没有出啥文章,就写了两篇,那就算作年度文章吧~ https://blog.debuginn.com/p/go-tools-pprof/ Github 个人页面 今年倾心打造自己的技术主页,术业有专攻,对自己在技术专业能力方面在 23年有更高的要求,同时希望自己在技术圈可以成为有影响力的人,来帮助更多的同行,同时也在不断的提升自己,直达链接。 总结 这一年总的来说是给自己带来很大成长的一年,褪去了年少的青涩,增加了一点成熟的模样,也懂得了珍惜当下,活好每一天,爱人爱己,学会了倾听、学会了表达、学会了接受失去、同时也学会了迎接未来,感恩、感谢、感激、感动,这就是我的 2022年。 2023年,祝大家新年快乐, happy new year ~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/12/31
articleCard.readMore

使用 pprof 对 Go 程序进行分析优化

前言 在生产环境中,偶尔会发生 Go 程序 CPU 暴增的现象,排除某时段并发大的场景外,通过监控面板看不到程序是因为什么原因导致的,Go 语言原生就提供了工具 pprof,Google 对于 pprof 的解释就是一个用于可视化和分析数据的工具。 通过使用 Go pprof 可以对程序的 CPU性能、内存占用、Goroutine wait share resource、mutex lock 做剖面分析,我们可以使用该工具收集运行时的程序性能指标,从而分析出程序中是否由于代码编写不合理导致存在不合理的资源占用情况,从而对程序进行优化用来提升其性能。 功能 Go pprof 提供了以下五种不同维度观测其程序的功能: CPU Profiling:CPU 性能分析,按照指定时间采集监听其 Go 程序 CPU 的使用情况,可以确定 Go 程序在哪个程序段中占用 CPU 耗时长; Memory Profiling:内存性能分析,用来分析程序的内存堆栈区使用情况,用来检测是否存在内存泄漏; Block Profiling:Goroutine 等待共享资源阻塞分析; Mutex Profiling:互斥锁分析,用来报告共享资源使用互斥锁的竞争的情况; Goroutine Profiling:协程性能分析,用来报告对当前运行时的 Goroutine 操作及数量。 使用 Go pprof 工具的使用也是比较简单快捷的,可以使用runtime/pprof包生成一个 profile 文件,网上也有很多的教程,这里不再过多描述了,详细可以看下包提供的函数,上面介绍了使用方法。 目前我们主要使用的是net/http/pprof包,启动一个独立端口号 http 程序单独用来 Go 程序的分析,搭配着 graphviz 组件来可视化程序来分析数据,使用起来也是比较方便的: 第一步,将net/http/pprof包引用到程序中,建议直接放在程序入口处 main.go 文件 1 2 3 import ( _ "net/http/pprof" ) 第二步,若本身是一个 http 的程序,不需要此步骤,若不是 http web 程序或者不想将对应信息暴露在外网,可以单开一个 http web 程序用来专门监听服务: 1 2 3 4 5 6 7 func main() { // 程序逻辑代码 go func() { _ = http.ListenAndServe(":8848", nil) }() } 第三步,运行主程序,访问 pprof 界面: 1 2 3 4 5 6 7 8 9 10 11 http://127.0.0.1:8848/debug/pprof/ # 主界面 http://127.0.0.1:8848/debug/pprof/allocs # 所有过去内存分配的采样 http://127.0.0.1:8848/debug/pprof/block # 导致同步阻塞的堆栈跟踪 http://127.0.0.1:8848/debug/pprof/cmdline # 当前程序的命令行的完整调用路径 http://127.0.0.1:8848/debug/pprof/goroutine # 所有当前 Goroutine 的堆栈跟踪 http://127.0.0.1:8848/debug/pprof/heap # 活动对象的内存分配的采样 http://127.0.0.1:8848/debug/pprof/mutex # 争用互斥锁持有者的堆栈跟踪 http://127.0.0.1:8848/debug/pprof/profile # CPU 配置文件 http://127.0.0.1:8848/debug/pprof/threadcreate # 创建新 OS 线程的堆栈跟踪 http://127.0.0.1:8848/debug/pprof/trace # 当前程序执行的跟踪 后缀加上 ?debug=1 可以可视化查看对应描述,不加就可以下载成 profile 文件,使用 pprof 命令可视化查看对应数据。 第四步,使用 go tool pprof -http=:6001 profile 命令查看分析程序。 分析 上图是针对 CPU 使用做的采集可视化,箭头越粗、方块越大就代表着对应的操作消耗 CPU 大,可以看到占用 CPU 最多的操作就是 json 的序列化和反序列化操作。 同理对应的内存性能、Goroutine 阻塞的分析都可以看出对应的操作。 总结 使用 go pprof 工具可以分析解剖程序运行性能问题,可以快速定位生产环境中遇到的问题,并作出优化或者 fix bug,最后祝大家不会写出 bug code,程序稳定、头发永在。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/5/1
articleCard.readMore

Git 常用命令汇总

常规操作 git push origin test 推送本地分支到远程仓库 git rm -r --cached 文件/文件夹名字 取消文件被版本控制 git reflog 获取执行过的命令 git log --graph 查看分支合并图 git merge --no-ff -m '合并描述' 分支名 不使用Fast forward方式合并,采用这种方式合并可以看到合并记录 git check-ignore -v 文件名 查看忽略规则 git add -f 文件名 强制将文件提交 Git 创建项目仓库 git init 初始化 git remote add origin url 关联远程仓库 git pull git fetch 获取远程仓库中所有的分支到本地 忽略已加入到版本库中的文件 git update-index --assume-unchanged file 忽略单个文件 git rm -r --cached 文件/文件夹名字 (. 忽略全部文件) 取消忽略文件 git update-index --no-assume-unchanged file 拉取、上传免密码 git config --global credential.helper store 分支操作 git branch 创建分支 git branch -b 创建并切换到新建的分支上 git checkout 切换分支 git branch 查看分支列表 git branch -v 查看所有分支的最后一次操作 git branch -vv 查看当前分支 git branch -b 分支名 origin/分支名 创建远程分支到本地 git branch --merged 查看别的分支和当前分支合并过的分支 git branch --no-merged 查看未与当前分支合并的分支 git branch -d 分支名 删除本地分支 git branch -D 分支名 强行删除分支 git push origin --delete 分支名 删除远程仓库分支 git merge 分支名 合并分支到当前分支上 暂存操作 git stash 暂存当前修改 git stash apply 恢复最近的一次暂存 git stash pop 恢复暂存并删除暂存记录 git stash list 查看暂存列表 git stash drop 暂存名(例:stash@{0}) 移除某次暂存 git stash clear 清除暂存 回退操作 git reset --hard HEAD^ 回退到上一个版本 git reset --hard commitId 回退到某个版本 git checkout -- file撤销修改的文件(如果文件加入到了暂存区,则回退到暂存区的,如果文件加入到了版本库,则还原至加入版本库之后的状态) git reset HEAD file 撤回暂存区的文件修改到工作区 标签操作 git tag 标签名 添加标签(默认对当前版本) git tag 标签名 commitId 对某一提交记录打标签 git tag -a 标签名 -m '描述' 创建新标签并增加备注 git tag 列出所有标签列表 git show 标签名 查看标签信息 git tag -d 标签名 删除本地标签 git push origin 标签名 推送标签到远程仓库 git push origin --tags 推送所有标签到远程仓库 git push origin :refs/tags/标签名 从远程仓库中删除标签 远程仓库 git remote -v查看远程仓库地址 git remote show origin查看远程仓库详情信息 查看某个 commit 提交属于哪个分支 git branch -l --contains <commit_id> 本地分支 git branch -r --contains <commit_id> 远程分支 git branch --all --contains <commit_id> 所有分支 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/3/25
articleCard.readMore

PWA 渐进式Web应用程序

简介 PWA是Progressive Web App的简称,是谷歌提出的新型Web技术,并由W3C及谷歌来推广这项技术,其主要目的是为了提升用户对网站原生使用体验,同时又能节省对网站的开启速度。 在我们国内,类似于PWA的技术可以简单地理解为微信主导的小程序,不过小程序的使用需要进行微信小程序前端重构开发,而渐进式Web应用程序开发只需要运用现代Web API以及传统渐进式式策略来构建网站的方式。 特点 由于这项技术是谷歌提出的,目前谷歌系的浏览器都支持PWA, 官方说有三个特点,分别是: 可靠 - 即使在不稳定的网络环境下,也能瞬间加载并展现 体验 - 快速响应,并且有平滑的动画响应用户的操作 粘性 - 像设备上的原生应用,具有沉浸式的用户体验,用户可以添加到桌面 而我感觉使用这个技术最大的特点就是将网站缓存下来,开启网站只需要0.3秒之内,使得网站加载速度异常地快,同时对用户非常友好,增添其用户交互性。 应用 近期对网站进行了PWA升级,只是用一个简单的插件就实现了对网站的APP转化,在开启我的网站的同时,在搜索栏会有加号提醒添加至桌面,如下图: 若是对我的网站感兴趣,可以点击按钮安装PWA,不用安装APP即可获取本站服务,若是手机用户可以在出现的将此页面发送至桌面来进行订阅。 功能 离线和缓存 Service Worker 这项技术主要是解决JS单线程问题,为了减少对浏览器网页开启峰值,页面加载问题。 浏览器中的 javaScript 都是运行在一个单一主线程上的,在同一时间内只能做一件事情。随着 Web 业务不断复杂,我们逐渐在 js 中加了很多耗资源、耗时间的复杂运算过程,这些过程导致的性能问题在 WebApp 的复杂化过程中更加凸显出来。 什么是 Service Worker Service Worker 有以下功能和特性: 一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。 一旦被 install,就永远存在,除非被手动 unregister 用到的时候可以直接唤醒,不用的时候自动睡眠 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态) 离线内容开发者可控 能向客户端推送消息 不能直接操作 DOM 必须在 HTTPS 环境下才能工作 异步实现,内部大都是通过 Promise 实现 Web存储 选择正确的存储机制对于本地设备存储和基于云的服务器存储都非常重要。 良好的存储引擎可确保以可靠的方式保存信息,并减少带宽和提升响应能力。正确的存储缓存策略是实现离线移动网页体验的核心构建基块。 存储分类分为:数据模型、持久化、浏览器支持、事务处理、同步/异步。 强黏贴用户 将站点添加至屏幕 为了吸引并留住用户,不仅仅自己网站需要比较高质量的文章,良好的交互,还需要一些营销手段增强网站的曝光度,提升与用户的交互。 如果用户对你的网站比较感兴趣,最好的方式将站点添加到主屏幕,不但可以省去用户开启浏览器的操作,提高其转化率,这样就可以对用户强黏贴了。 消息通知 使用 service worker 的功能之一:通知 (notification),它允许服务器向用户提示一些信息,并根据用户不同的行为进行一些简单的处理。 用户自动登录 账户是网站必不可少的组成部分。账户体系的存在,可以让网站给用户提供分级服务,同时网站也能够通过收集用户行为实现精准推送。但账号的存在将使得用户不得不多出一步登录的步骤,要知道根据“漏斗模型”理论,从起点到终点,每个环节都会产生用户的流失,依次递减。因此想办法省去烦人的账号密码输入过程,不但能提高用户体验,也能够提高网站转化率。 记住网站传统密码形式; 凭证管理,第三方登陆验证。 网络安全 Web安全; 使用SSL 即 Https; 同源策略; 典型的安全漏洞; CSP(内容安全策略)。 详细内容请移步查看:https://lavas.baidu.com/pwa/web-security/introduction 参考文章 什么是PWA | PWA 文档| Lavas 简单介绍一下Progressive Web App(PWA) 渐进式Web 应用(PWA) | MDN 讲讲PWA - 前端学习- SegmentFault 思否 Progressive Web Apps 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/3/18
articleCard.readMore

浅析悲观锁与乐观锁

在关系型数据库中,悲观锁与乐观锁是解决资源并发场景的解决方案,接下来将详细讲解一下这两个并发解决方案的实际使用及优缺点。 首先定义一下数据库,做一个最简单的库存表,如下设计: 1 2 3 4 5 6 CREATE TABLE `order_stock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', `oid` int(50) NOT NULL COMMENT '商品ID', `quantity` int(20) NOT NULL COMMENT '库存', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; quantity代表着不同商品 oid 的库存,接下来 OCC 及 PCC 使用此数据库进行演示。 乐观锁 OCC 它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。 即“乐观锁”认为拿锁的用户多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。这样就可以避免使用数据库自身定义的行锁,可以避免死锁现象的产生。 1 UPDATE order_stock SET quantity = quantity - 1 WHERE oid = 1 AND quantity - 1 > 0; 乐观并发控制多数用于数据争用不大、冲突较少的环境中,这种环境中,偶尔回滚事务的成本会低于读取数据时锁定数据的成本,因此可以获得比其他并发控制方法更高的吞吐量。 悲观锁 PCC 它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。 这种设计采用了“一锁二查三更新”模式,就是采用数据库中自带 select ... for update 关键字进行对当前事务添加行级锁先将要操作的数据进行锁上,之后执行对应查询数据并执行更新操作。 1 2 3 4 BEGIN SELECT quantity FROM order_stock WHERE oid = 1 FOR UPDATE; UPDATE order_stock SET quantity = 2 WHERE oid = 1; COMMIT; MySQL还有个问题是select ... for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在MySQL中用悲观锁务必要确定走了索引,而不是全表扫描。 悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。 OCC 和 PCC 优缺点 OCC 优点及缺点 【优点】 乐观锁相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁; 可以快速响应事务,随着并发量增加,但会出现大量回滚出现; 效率高,但是要控制好锁的力度。 【缺点】 如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题; 随着并发量增加,但会出现大量回滚出现。 PCC 优点及缺点 【优点】 “先取锁再访问”的保守策略,为数据处理的安全提供了保证; 【缺点】 依赖数据库锁,效率低; 处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会; 降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。 References 【MySQL】悲观锁&乐观锁 LearnKu 浅析乐观锁与悲观锁 维基百科 悲观并发控制 && 乐观并发控制 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/3/16
articleCard.readMore

Gource 版本可视化工具 使用手册

Gource 是一款版本控制可视化的工具,使用这个工具可以将自己的 Git 提交的代码包括对 Mercurial,Bazaar 和 SVN 的内置日志生成可视化支持。Gource 还可以解析由多个第三方工具为 CVS 存储库生成的日志。 提交的代码按照时间轴的顺序动态显示出来,可以使你的工作过程以动画的形式显现,并且 Gource 这个工具可以显示出来不同用户对一个代码库进行同一时间内的修改操作。 官方网站:https://gource.io/ 常用命令 在这里我列举几个经常使用到的命令,PS:你需要先进入到对应项目目录中去,这个很重要,要不然会提示该目录下没有 log 记录。 1 2 3 4 5 6 7 gource # 使用Gource查看版本历史 gource -f -1280×720 # 设置分辨率大小 gource -s 0.5 # 每天以0.5秒的速度播放 gource -o 1.mp4 # 将版本动画导出到 1.mp4 文件中 gource -s 0.1 -o 2.mp4 # 每天以0.1秒的速度导出到 2.mp4 文件中 gource -f -b red # 将背景设置为红色 gource --title “Gource” # 为gource设置title 基本命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ➜ ~ gource -help Gource v0.51 Usage: gource [options] [path] 用法: gource [选项] [路径] Options: -h, --help 帮助 -WIDTHxHEIGHT, --viewport 设定窗口大小 -f, --fullscreen 全屏显示 --screen SCREEN 画面编号 --multi-sampling 启用多重采样 --no-vsync 禁用垂直同步 --start-date 'YYYY-MM-DD hh:mm:ss +tz' 从日期和可选时间开始 --stop-date 'YYYY-MM-DD hh:mm:ss +tz' 停在某个日期和可选时间 -p, --start-position POSITION 从某个位置开始(0.0-1.0 or 'random') --stop-position POSITION 停在某个位置 -t, --stop-at-time SECONDS 在指定的秒数后停止 --stop-at-end 在日志结尾处停止 --dont-stop 在日志结束后继续运行 --loop 在日志末尾循环 -a, --auto-skip-seconds SECONDS 如果没有任何反应,则自动跳至下一个条目 持续几秒钟(default: 3) --disable-auto-skip 禁用自动跳过 -s, --seconds-per-day SECONDS 每天以秒为单位的速度(default: 10) --realtime 实时播放速度 --no-time-travel 如果提交时间是过去的时间请使用上一次提交的时间 -c, --time-scale SCALE 更改模拟时间范围(default: 1.0) -e, --elasticity FLOAT 节点弹性(default: 0.0) --key 显示文件扩展名 --user-image-dir DIRECTORY 包含要用作头像的图像的目录 --default-user-image IMAGE 默认用户图像文件 --colour-images 使用单色图像 -i, --file-idle-time SECONDS 时间文件保持空闲(default: 0) --max-files NUMBER 最大文件数或0(无限制) --max-file-lag SECONDS 提交的最大时间文件可能会出现 --log-command VCS 显示VCS日志命令(git,svn,hg,bzr,cvs2cl) --log-format VCS 指定日志格式(git,svn,hg,bzr,cvs2cl,custom) --load-config CONF_FILE 加载配置文件 --save-config CONF_FILE 使用当前选项保存配置文件 -o, --output-ppm-stream FILE 将PPM流输出到文件 ('-' for STDOUT) -r, --output-framerate FPS 输出帧率(25,30,60) PATH可以是受支持的版本控制目录,日志文件,Gource配置文件或用于读取STDIN的'-'。 如果省略,则gource将尝试从当前目录生成日志。 要查看完整的命令行选项,请使用 “-H” 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/3/16
articleCard.readMore

Linux Tree 树状目录显示工具 使用手册

Tree 命令以树状形状列出目录的内容的一个工具,你时常在 Github 中常看到一些开源项目会将自己的项目目录展现出来,这篇文章的背景图就是展现的开源项目 Laravel 中 app 目录的树状图,接下来介绍一下基本使用语法。 基本语法 1 tree [-aACdDfFgilnNpqstux][-I <范本样式>][-P <范本样式>][目录...] 常用命令 1 2 3 4 5 6 7 8 9 10 11 tree --help 显示帮助信息 tree -d 只显示目录 tree -L n 只显示第n层目录 tree -l 遵循像目录这样的符号链接 tree -f 打印每个文件的完整路径前缀 tree -x 只保留在当前文件系统上 tree -L 级下降深层级目录 tree -R 达到最大等级时重新运行树 tree -P 模式只列出符合给定模式的文件 tree -I 模式不要列出与给定模式匹配的文件 tree -o 文件名输出到文件而不是标准输出 基本命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 [➜ ~ tree --help usage: tree [-acdfghilnpqrstuvxACDFJQNSUX] [-H baseHREF] [-T title ] [-L level [-R]] [-P pattern] [-I pattern] [-o filename] [--version] [--help] [--inodes] [--device] [--noreport] [--nolinks] [--dirsfirst] [--charset charset] [--filelimit[=]#] [--si] [--timefmt[=]<f>] [--sort[=]<name>] [--matchdirs] [--ignore-case] [--fromfile] [--] [<目录列表>] ------- 上市选项 ------- -a 列出所有文件。 -d 仅列出目录。 -l 跟随目录等符号链接。 -f 打印每个文件的完整路径前缀。 -x 仅保留在当前文件系统上。 -L 级别仅下降级别级别的目录。 -R 当达到最大目录级别时,重新运行树。 -P 模式仅列出与给定模式匹配的那些文件。 -I 模式不列出与给定模式匹配的文件。 --ignore-case 模式匹配时忽略大小写。 --matchdirs 在-P模式匹配中包括目录名称。 --noreport 在树列表的末尾关闭文件/目录计数。 --charset X 将charset X用于终端/ HTML和缩进线输出。 --filelimit# 不要使包含超过#个文件的dirs下降。 --timefmt <f>根据<f>格式打印和格式化时间。 -o filename 输出到文件而不是stdout。 ------- 文件选项 ------- -q 将不可打印的字符打印为'?'。 -N 按原样打印不可打印的字符。 -Q 引用双引号的文件名。 -p 打印每个文件的保护。 -u 显示文件所有者或UID号。 -g 显示文件组所有者或GID号。 -s 打印每个文件的大小(以字节为单位)。 -h 以更易于理解的方式打印尺寸。 --si 与-h类似,但以SI单位使用(1000的幂)。 -D 打印上次修改或(-c)状态更改的日期。 -F 附加'/','=','*','@','|'或按ls -F的'>'。 --inodes 打印每个文件的索引节点号。 --device 打印每个文件所属的设备ID号。 ------- 排序选项 ------- -v 按版本字母顺序对文件进行排序。 -t 按上次修改时间对文件排序。 -c 按上次状态更改时间对文件排序。 -U 不排序文件。 -r 颠倒排序顺序。 --dirsfirst 在文件之前列出目录(-U禁用)。 --sort X 选择排序:名称,版本,大小,mtime,ctime。 ------- 图形选项 ------- -i 不打印缩进线。 -A 打印ANSI线图形缩进线。 -S 使用CP437(控制台)图形缩进线打印。 -n 始终关闭着色(-C替代)。 -C 始终打开着色。 ------- XML / HTML / JSON选项 ------- -X 打印树的XML表示形式。 -J 打印树的JSON表示形式。 -H baseHREF打印出以baseHREF作为顶层目录的HTML格式。 -T 字符串用字符串替换默认的HTML标题和H1标头。 --nolinks 关闭HTML输出中的超链接。 ------- 输入选项 ------- --fromfile 从文件中读取路径(。= stdin) ------- 其他选项 ------- --version 打印版本并退出。 --help 打印用法和此帮助消息并退出。 -选项处理终止符。 展示效果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ➜ app tree . ├── Console │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Auth │ │ │ ├── ForgotPasswordController.php │ │ │ ├── LoginController.php │ │ │ ├── RegisterController.php │ │ │ ├── ResetPasswordController.php │ │ │ └── VerificationController.php │ │ ├── Controller.php │ │ └── IndexController.php │ ├── Kernel.php │ └── Middleware │ ├── Authenticate.php │ ├── CheckForMaintenanceMode.php │ ├── EncryptCookies.php │ ├── RedirectIfAuthenticated.php │ ├── TrimStrings.php │ ├── TrustProxies.php │ └── VerifyCsrfToken.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php └── User.php 7 directories, 23 files 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/3/15
articleCard.readMore

Restful API 设计指北

近期学习了 Go 语言,跟着七米在学习,学习过程中了解到了 API 的一个设计规范,也就是本文要讲的 Restful API 设计模式,现在互联网处在前后端分离的阶段,API 的书写及规范化是非常重要的,针对于 API 中 Restful API 中设计比较规范的是 Github API,可以直接访问他们的 https://api.github.com 直接查看 Github 针对与公共接口的链接及使用方法。 此篇文章也是针对于这几天学习 Restful API 做了一个笔记或小结,若有不足之处还望批评指正,谢谢。 使用 HTTPS 协议 这个协议使用本身与这个 API 设计标准没有什么直接联系,使用 HTTPS 协议主要目的是将用户客户端与 API 服务器连接过程中保证其数据的安全性。 注意:由于 API 接口使用 HTTPS 协议,不要让非 SSL 的链接访问重定向到 SSL 的链接。 API 地址和版本问题 为 API 使用专门子域名比较友好,例如使用如下链接使用: 1 https://api.debuginn.com 也可以将 API 放在主域名下,例如: 1 https://debuginn.com/api/ 当然,针对于 API 版本问题针对以上两种方法可以分别使用如下例子: 1 2 3 4 # 针对于 API 子域名方式 api.domain/v1/ https://api.debuginn.com/v1/ # 针对于 主域名目录方式 domain/api/v1/ https://debuginn.com/api/v1/ Schema 响应数据模式 现在前后端分离项目使用的数据响应模式大部分采用的是 JSON 格式数据,也有一些项目采用 XML 格式的数据。 针对于用户客户端请求,服务器响应尽量有 状态码 Status Code 及详细解释。 使用正确的 Method 使用正确的 Method 也就是使用正确的 HTTP 请求动词,即 HTTP 协议规定的常常使用的六种请求动词,并针对请求 SQL 语句辅助理解: 1 2 3 4 5 GET 请求 => SELECT 从服务端获取数据 POST 请求 => CREATE 从服务端创建数据 PUT 请求 => UPDATE 从服务端更新数据(将所有数据元素全部替换掉) PATCH 请求 => UPDATE 从服务端更新数据(将部分数据元素替换掉) DELETE请求 => DELETE 从服务端删除数据 还有两个不常使用的请求: 1 2 HEAD 获取资源的元数据。 OPTIONS 获取信息,关于资源的哪些属性是客户端可以改变的。 注意:更新和创建操作应该返回最新的资源,来通知用户资源的情况;删除资源一般不会返回内容。 过滤信息 针对用户端查询数据,需要服务端查询对应数据,包括了筛选、分页等操作。 筛选操作 1 2 3 4 5 api.domain/user/limit/10 指定返回记录的数量; api.domain/user/offset/10 指定返回记录的开始位置; api.domain/user/animal_type_id/1 指定筛选条件 api.domain/user/page/2/per_page/100 指定第几页,以及每页的记录数; api.domain/user/sortby/name/order/asc 指定返回结果按照哪个属性排序,以及排序顺序 分页操作 当返回某个资源的列表时,如果要返回的数目特别多,比如 github 的 /users,就需要使用分页分批次按照需要来返回特定数量的结果。 分页的实现会用到上面提到的 url query,通过两个参数来控制要返回的资源结果: per_page:每页返回多少资源,如果没提供会使用预设的默认值;这个数量也是有一个最大值,不然用户把它设置成一个非常大的值(比如99999999)也失去了设计的初衷。 page:要获取哪一页的资源,默认是第一页。 状态码 Status Code HTTP 应答中,需要带一个很重要的字段:status code。它说明了请求的大致情况,是否正常完成、需要进一步处理、出现了什么错误,对于客户端非常重要。状态码都是三位的整数,大概分成了几个区间: 2XX:请求正常处理并返回 3XX:重定向,请求的资源位置发生变化 4XX:客户端发送的请求有错误 5XX:服务器端错误 在 HTTP API 设计中,经常用到的状态码以及它们的意义如下表: 状态码 LABEL 解释 200 OK 请求成功接收并处理,一般响应中都会有 body 201 Created 请求已完成,并导致了一个或者多个资源被创建,最常用在 POST 创建资源的时候 202 Accepted 请求已经接收并开始处理,但是处理还没有完成。一般用在异步处理的情况,响应 body 中应该告诉客户端去哪里查看任务的状态 204 No Content 请求已经处理完成,但是没有信息要返回,经常用在 PUT 更新资源的时候(客户端提供资源的所有属性,因此不需要服务端返回)。如果有重要的 metadata,可以放到头部返回 301 Moved Permanently 请求的资源已经永久性地移动到另外一个地方,后续所有的请求都应该直接访问新地址。服务端会把新地址写在 Location 头部字段,方便客户端使用。允许客户端把 POST 请求修改为 GET。 304 Not Modified 请求的资源和之前的版本一样,没有发生改变。用来缓存资源,和条件性请求(conditional request)一起出现 307 Temporary Redirect 目标资源暂时性地移动到新的地址,客户端需要去新地址进行操作,但是不能修改请求的方法。 308 Permanent Redirect 和 301 类似,除了客户端不能修改原请求的方法 400 Bad Request 客户端发送的请求有错误(请求语法错误,body 数据格式有误,body 缺少必须的字段等),导致服务端无法处理 403 Forbidden 服务器端接收到并理解客户端的请求,但是客户端的权限不足。比如,普通用户想操作只有管理员才有权限的资源。 404 Not Found 客户端要访问的资源不存在,链接失效或者客户端伪造 URL 的时候会遇到这个情况 405 Method Not Allowed 服务端接收到了请求,而且要访问的资源也存在,但是不支持对应的方法。服务端必须返回Allow头部,告诉客户端哪些方法是允许的 415 Unsupported Media Type 服务端不支持客户端请求的资源格式,一般是因为客户端在Content-Type或者Content-Encoding中申明了希望的返回格式,但是服务端没有实现。比如,客户端希望收到xml返回,但是服务端支持Json 429 Too Many Requests 客户端在规定的时间里发送了太多请求,在进行限流的时候会用到 500 Internal Server Error 服务器内部错误,导致无法完成请求的内容 503 Service Unavailable 服务器因为负载过高或者维护,暂时无法提供服务。服务器端应该返回 Retry-After 头部,告诉客户端过一段时间再来重试 针对于状态码,请看此文章:HTTP常见状态码 错误处理 如果出错的话,在 response body 中通过 message 给出明确的信息。 比如客户端发送的请求有错误,一般会返回4XX Bad Request结果。这个结果很模糊,给出错误 message 的话,能更好地让客户端知道具体哪里有问题,进行快速修改。 如果请求的 JSON 数据无法解析,会返回Problems parsing JSON; 如果缺少必要的 filed,会返回422 Unprocessable Entity,除了 message 之外,还通过errors给出了哪些 field 缺少了,能够方便调用方快速排错。 基本的思路就是尽可能提供更准确的错误信息:比如数据不是正确的 json,缺少必要的字段,字段的值不符合规定…… 而不是直接说“请求错误”之类的信息。 验证及授权 一般来说,让任何人随意访问公开的 API 是不好的做法。验证和授权是两件事情: 验证(Authentication)是为了确定用户是其声明的身份,比如提供账户的密码。不然的话,任何人伪造成其他身份(比如其他用户或者管理员)是非常危险的 授权(Authorization)是为了保证用户有对请求资源特定操作的权限。比如用户的私人信息只能自己能访问,其他人无法看到;有些特殊的操作只能管理员可以操作,其他用户有只读的权限等等 如果没有通过验证(提供的用户名和密码不匹配,token 不正确等),需要返回401 Unauthorized状态码,并在 body 中说明具体的错误信息;而没有被授权访问的资源操作,需要返回403 Forbidden状态码,还有详细的错误信息。 注意:对某些用户未被授权访问的资源操作返回404 Not Found,目的是为了防止私有资源的泄露(比如黑客可以自动化试探用户的私有资源,返回 403 的话,就等于告诉黑客用户有这些私有的资源,无异于是给黑客提供了方向)。 Hypermedia API RESTful API 最好做到 Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。 比如访问 api.github.com,就可以看到 Github API 支持的资源操作。 易读的 API 接口文档 API 最终是给人使用的,不管是公司内部,还是公开的 API 都是一样。即使我们遵循了上面提到的所有规范,设计的 API 非常优雅,用户还是不知道怎么使用我们的 API。最后一步,但非常重要的一步是:为你的 API 编写优秀的文档。 注意:对每个请求以及返回的参数给出说明,最好给出一个详细而完整的示例。 参考资料 RESTful API 设计指南 - 阮一峰 跟着 Github 学习 Restful HTTP API 设计 REST API Tutorial Representational State Transfer (REST) - Roy Fielding 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/3/13
articleCard.readMore

如何将豆瓣观影记录实时同步至博客中

事情的起因是这样的,前几日在看 idealclover 大佬的博客,不经意间看到了他的豆瓣观影记录,他博客中关于豆瓣观影记录是实时同步的,很好奇是如何实现的,经过查看,他是爬取的豆瓣观影界面来实现的,其实关于豆瓣观影记录,网上也有很多的教程,恰巧自己所学的 Go 语言也可以做简单的爬虫实现其效果,于是开始上手造轮子了,PS:了解到非法爬取网站信息是违法的,之前豆瓣 API 接口,关闭访问,在豆瓣上找了好久,终于在我的主页中找到了对于观影记录的官方提供 RSS 订阅,打开订阅,看到有自己所需要的字段,比较好获取,于是就开始了此项目。 分析 首先,需要获取豆瓣提供的 XML 文件,在我的主页右下角就可以看到 RSS 订阅链接: 找到了订阅地址,点击查看 XML 结构,可以看到豆瓣提供的结构还是挺理想的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"> <channel> <title>Meng小羽 的收藏</title> <link>https://www.douban.com/people/debuginn/</link> <description> <![CDATA[ Meng小羽 的收藏:想看、在看和看过的书和电影,想听、在听和听过的音乐 ]]> </description> <language>zh-cn</language> <copyright>© 2013, douban.com.</copyright> <pubDate>Sat, 30 May 2020 09:14:08 GMT</pubDate> <item> <title>看过黑衣人:全球追缉</title> <link>http://movie.douban.com/subject/19971676/</link> <description> <![CDATA[ <table><tr> <td width="80px"><a href="https://movie.douban.com/subject/19971676/" title="Men in Black International"> <img src="https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2558701068.webp" alt="Men in Black International"></a></td> <td> <p>推荐: 力荐</p> </td></tr></table> ]]> </description> <dc:creator>Meng小羽</dc:creator> <pubDate>Sat, 30 May 2020 09:14:08 GMT</pubDate> <guid isPermaLink="false">https://www.douban.com/people/debuginn/interests/2402808825</guid> </item> ...... <channel> 其实,我们提取的主要就是 item 标签下对应的电影信息内容: 1 2 3 4 5 6 7 8 9 10 <item> <title>看过黑衣人:全球追缉</title> <link>http://movie.douban.com/subject/19971676/</link> <description> <![CDATA[ <table><tr> <td width="80px"><a href="https://movie.douban.com/subject/19971676/" title="Men in Black International"> <img src="https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2558701068.webp" alt="Men in Black International"></a></td> <td> <p>推荐: 力荐</p> </td></tr></table> ]]> </description> <dc:creator>Meng小羽</dc:creator> <pubDate>Sat, 30 May 2020 09:14:08 GMT</pubDate> <guid isPermaLink="false">https://www.douban.com/people/debuginn/interests/2402808825</guid> </item> 设计 根据豆瓣官方提供的 XML 标签数据,可以利用 Go 语言中 encoding/xml 包来进行对数据的映射,可以设计成如下结构体: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 豆瓣 xml 描述结构体 type Attributes struct { XMLName xml.Name `xml:"rss"` Version string `xml:"version,attr"` Channel Channel `xml:"channel"` } // XML 主题结构拆分 type Channel struct { Title string `xml:"title"` Link string `xml:"link"` Description string `xml:"description"` Language string `xml:"language"` Copyright string `xml:"copyright"` Pubdate string `xml:"pubDate"` MovieItem []MovieItem `xml:"item"` } // 豆瓣 电影列表结构体 type MovieItem struct { Title string `xml:"title"` Link string `xml:"link"` Description string `xml:"description"` Pubdate string `xml:"pubDate"` } 可以和 XML 文件对应字段进行匹配,可以从上面的结构体中我们可以看到,最终我们想获取到的数据就是结构体 MovieItem 的数据。 由于是从网上链接获取数据的,在这里首先我们需要将网上豆瓣提供的 XML 文件转换成 []byte 类型的数据: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 获取 xml 文件数据 func getXMLData(url string) (data []byte, err error) { // 读取 xml 文件 client := &http.Client{} req, _ := http.NewRequest("GET", url, nil) // 自定义Header req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)") resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() // 关闭文件 // 读取所有文件内容保存至 []byte data, err = ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return } 上面这个函数实现的就是将 XML 文件保存至 Go 语言的数据结构的操作,现在可以将 XML 文件成功读取出来,接下来就是要进行 XML 字段与上面作出的结构体之间的映射,其实映射至结构体的过程是比较简单的,首先声明 Attributes{} 类型的结构体,之后通过 xml.Unmarshal 来实现映射拷贝,就可以得到对应的结构体类型的数据,由于我们想要的数据是结构体数据中的一部分,即 MovieItem,在得到结构体数据后就可以将想要的这一部分的数据选择抽取出来: 1 2 3 4 5 6 7 v := Attributes{} unMarshalErr := xml.Unmarshal(data, &v) if unMarshalErr != nil { fmt.Printf("xml unmarshal failed, err:%v\n", err) } movieItem := v.Channel.MovieItem Map 转换 在这里我们可以得到结构体中嵌套的结构体,在结构体中有一些字段我们是不想要的,需要进行处理,对于 description 这个字段中,官方提供的是一段 HTML 描述串,其中电影的描述文件是我们所需要的,对于 HTML 字符串的拆分,我们可以借助strings.Split 函数来实现截取,使用 \" 符号截取,虽然可以获取到我们想要的数据了,但是由于这个是嵌套的结构体,我们需要做一个匹配的 map 来进行存储处理好的数据,可以看代码中我的设计: 1 2 3 4 5 6 7 8 9 10 11 MoviesMap := make(map[int]interface{}) for i := 0; i < len(movieItem); i++ { movie := make(map[string]string) description := strings.Split(movieItem[i].Description, "\"") movie["Title"] = string([]rune(movieItem[i].Title)[2:]) movie["Link"] = movieItem[i].Link movie["Img"] = description[7] movie["Pubdate"] = movieItem[i].Pubdate MoviesMap[i] = movie } 外层 map 是采用 map[int]interface{} 类型,在 interface{} 中存储这内层 map map[string]string 类型。 针对于 Img 地址的获取,是现根据特定符号拆分,之后获取制定位置的数据获取的。 1 2 3 0 map[Img:https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2558701068.jpg Link:http://movie.douban.com/subject/19971676/ Pubdate:Sat, 30 May 2020 09:14:08 GMT Title:黑衣人:全球追缉] 1 map[Img:https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2263408369.jpg Link:http://movie.douban.com/subject/1294371/ Pubdate:Thu, 28 May 2020 10:06:23 GMT Title:摩登时代] ...... 最后就是将这个 map 做一下序列化处理,这样就可以返回给前台数据了。 1 data, _ = json.Marshal(MoviesMap) 服务 处理好数据,做了对应的处理,怎么将数据作为服务端提供给前台,在这里需要使用 Web 服务,Go 中可以使用原生 Web,不过我在这里使用的是之前学过的 Gin 框架,来提供服务的: 1 2 3 4 5 r := gin.Default() r.GET("/doubanmovies", func(context *gin.Context) { context.JSON(http.StatusOK, MoviesMap) }) _ = r.Run(":8080") 启动服务,可以得到对应的 json 数据,你若以为现在就可以实现了,那么你错了,远远没有那么简单…… 前台 由于我知晓我的博客采用的前台 UI 技术是 MDUI, 我利用自身的卡片 UI 迅速设计了一个模块,因为后期需要放在我的博客页面上,前端读取数据采用的是 VUE 和 axios 技术: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <div class="mdui-container-fluid" id="app"> <div class="mdui-row"> <div v-for="item in info"> <div class="mdui-col-xs-6 mdui-col-sm-4 mdui-col-md-3 mdui-col-lg-3 mdui-m-b-1 mdui-m-t-1"> <a :href="item.Link" target="_blank"> <div class="mdui-card mdui-hoverable"> <div class="mdui-card-media"> <img :src="item.Img" style="height: 260px;" /> <div class="mdui-card-media-covered"> <div class="mdui-card-primary"> <div class="mdui-card-primary-subtitle">{{ item.Title }}</div> </div> </div> </div> </div> </a> </div> </div> </div> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script src="./static/js/mdui.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.js"></script> <script src="https://cdn.staticfile.org/axios/0.18.0/axios.min.js"></script> <script> new Vue({ el: '#app', data() { return { info: null } }, mounted() { axios .get('http://127.0.0.1:8080/doubanmovies') .then(response => (this.info = response.data)) .catch(function (error) { // 请求失败处理 console.log(error); }); } }) </script> 设计好了以后,访问页面,却加载不出来,emmmmmm CORS 看到了是 CORS 同源策略的原因,接下来就是要解决同源问题了,方法比较简单,就是将 Go 服务端加上 CORS 同源策略就可以了,方法如下: 1 2 3 4 5 6 7 r := gin.Default() r.Use(Cors()) r.GET("/doubanmovies", func(context *gin.Context) { context.JSON(http.StatusOK, MoviesMap) }) _ = r.Run(":8080") 在路由访问中添加 Cors() 函数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // 跨域 func Cors() gin.HandlerFunc { return func(c *gin.Context) { method := c.Request.Method //请求方法 origin := c.Request.Header.Get("Origin") //请求头部 var headerKeys []string // 声明请求头keys for k, _ := range c.Request.Header { headerKeys = append(headerKeys, k) } headerStr := strings.Join(headerKeys, ", ") if headerStr != "" { headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr) } else { headerStr = "access-control-allow-origin, access-control-allow-headers" } if origin != "" { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*") // 这是允许访问所有域 c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求 // header的类型 c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma") // 允许跨域设置 可以返回其他子段 c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析 c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒 c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true c.Set("content-type", "application/json") // 设置返回格式是json } //放行所有OPTIONS方法 if method == "OPTIONS" { c.JSON(http.StatusOK, "Options Request!") } // 处理请求 c.Next() // 处理请求 } } 这样就可以看到结果了,如下图: 看到结果后,心中窃喜,感觉成功了,接下来就需要将 Go 服务部署到我的服务器中去了,部署步骤比较简单,就不过多解释了,最后访问服务器 IP 及对应单口可以呈现结果,最后将前台代码粘贴到新建的页面中,生成预览,emmmm,啥都没有,浏览器居然报 HTTPS 请求 HTTP 资源是不安全的,吐了一口血,解决吧,唉,经过查询资料,得出如下两个解决方案: Gin 框架服务本身使用 SSL 证书,实现 HTTPS 访问,不过需要配置域名; 使用 Nginx 服务做一下代理,将一个特定链接代理到本身服务中去。 作为学生党的我,没有太多的资金去申请过多的 SSL 证书(省着点用),于是我就在我的 debuginn.com 子域名下做了一个代理。 代理 Nginx 代理实现也是比较简单的,就是将前端访问某个接口代理至服务器中某个端口的服务中,表面上看是 Nginx 在做数据处理,实际上是 Nginx 只做了一个代理转发,由于我www.debuginn.com 子域名本身就是 https 的,所以设置好了代理之后,就可以使用固定的代理链接访问了,配置如下: 1 2 3 4 5 6 7 8 9 server{ ..... location /douban_movies { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host:80; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } 这样就可以实现 https 资源访问了:https://debuginn.com/doubanmovies 效果 解决了 HTTPS 访问 HTTP 资源的问题,就解决了所有问题,实现了效果。 具体效果如下:https://debuginn.com/doubanmovies 开源 针对于此小项目,我已经开源至 Github 中,若是你感兴趣或者有什么建议,可以联系我,我们一起改进,同时希望你可以给我一个 Star,万分感谢! https://github.com/debuginn/douban-movies 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2022/3/3
articleCard.readMore

2021 年度总结

今年,时间依旧飞快流逝,转眼间,自己已经毕业了小2年了,渐渐的自己开始习惯了北漂的生活,一个人的北京,自由与孤独同在。也体会到了离家远的遗憾,远的连奶奶最后一面也没有见到,第一次感受到了亲人阴阳两隔的无奈与悲哀。疫情 😷 在全球范围内还在持续,国内偶尔也零星的出现,但愿 22 年疫情结束,全世界人们都可以回归到疫情之前,和爱人、亲人、朋友去想去的地方,去探索多彩的世界。 网站数据 2021年统计数据共享链接 今年,写了一些在工作中使用到的 Golang 的一些技巧及思考,以及一些规范。 让我意外的是大家对 Go 语言入门学习有着很大的兴趣,下面这个文章是今年访问最多的文章,访问量:2785 https://blog.debuginn.com/p/go-dev-design/ 技术导航 经常游荡于各个大厂的技术博客之中,于是做了一个集合导航,后续计划将大佬们也收集到此,大家有好的技术分享网站也可以评论区留言分享一下。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/12/30
articleCard.readMore

我们是如何用 Prometheus 对网关进行监控的

近期,我们对 APP 网关 Gateway 做了升级,由于项目创建时间过早(6年前的项目),那时候还没有好的包管理工具,使用的是最原始的 Go Path 来进行项目的依赖管理,历史包袱比较重,项目中很多的第三方引用都是直接将代码拷贝到项目目录下,升级与维护起来特别麻烦,升级之后就是现在官方主推的是 Go module 包管理方式。 解决了上面的这个痛点,网关程序就可以集成一些业界主流的基础工具,升级与维护起来就简单多了。 言归正传,本文主要是讲的我们是如何用 Prometheus 对网关进行监控的,之前我们的网关程序也是集成了我们公司开源打点监控工具 Open falcon,并且使用 Grafana 进行绘图并查看,但是为啥我们不再继续使用了?之后我们为啥拥抱了 Prometheus 生态?还有一些打点、报警、绘图的思考,还有一些我们在使用的过程中出现的问题以及解决方案,一一讲解一下。 抛弃 Open falcon 拥抱 Prometheus 在决定使用 Prometheus 之前,我们的 Gateway 使用的是 Open falcon,但是一直存在着一个对于我们而言的痛点,就是作为网关程序,历史维护的路由太多了,接口可用性及接口报错无法聚合报警,也就是我们的监控体系存在着盲区,这个对我们而言来说是最为致命的,那个接口出现了问题会直接导致用户的使用,并且我们使用的那些上游服务出现问题我们也无法及时感知。 使用 Prometheus 最主要的是我们可以通过 PromQL 语法进行正则匹配,实现对某个或多个接口的聚合计算并报警,这样就可以解决我们无法聚合报警的一个痛点。 打点、绘图、报警 打点 全面、量小 作为业务使用,怎么设计点位,既可以满足报警使用,对每个接口进行各项指标的监控,同时要保证点位数据是可穷举的(避免出现 OOM)和产生数据量比较小。简而言之,就是“监控要全面、打点数据量要小”,因为数据量大的话在 Prometheus 拉取指标的时间及周期就不得不设置的过大,这样的后果就是造成图的绘制缓慢甚至超时,同时报警也失去了实效性。 我们网关使用的是 http 协议,可以充分利用 Go 的 net/http 特性,使用中间件设计,对请求与返回进行打点,于是我们是这样设计的: 对任意一个请求做一个 qps 的打点记录(无任何的业务参与其中); 对单个路由请求进行打点(区分业务状态码); 对单个路由请求进行耗时打点(区分业务状态码)。 请求路由 按照业界通用的设计:/version/model/action 以上的场景,仅仅使用指标类型中的两种 Counter(计数器) 和 Histogram(直方图)就可以满足我们打点需求。 绘图 清晰、快速 构建一栋房子所需的材料都准备好了,准备建造, building…… 点位指标收集到了,接下来就是对点位进行各个维度的拼装,来呈现我们想要的图,这里解答一下为什么我们要把业务状态码打到指标中去,以及我们是如何使用的:我们的系统设计采用业务封装错误码,只要是传输调用链路没有问题,所有的场景都走业务状态码,类似的返回解决如下: 1 2 3 4 5 6 7 { "code": 0, "desc": "success", "data":{ "result": "ok" } } code 为 0,代表当前请求是正常的,返回数据会封装在 data 中; code 不为 0,代表着当前请求存在业务上可捕获或者自定义的错误。 作为网关程序,与下游微服务采用相同的接口设计,对我们现在的打点设计也是非常友好的。 同样的,有的服务使用的是 Restful API 思想,使用的是 http 标准状态码,那就是 200 代表着成功,非 200 代表着业务或者系统存在错误,当然 5XX 错误可以单独拿出来做可用性或者细化的报警。 之所以打点记录业务状态码,好处如下: 对业务状态码打点,可以对某个业务上的特定错误进行捕捉,看图及报警都是非常便捷的; 不影响对接口可用性进行计算,可以多维度聚合计算可用性(根据业务定义而言)。 当然,打点指标设置的粒度越小,对应的点位的存储大小以及聚合运算的代价也是成倍的提高的。 铺垫了好久,说一下我们是怎么进行绘图的,在打点的时候讲到使用 Counter、Histogram 进行打点,绘图的时候我们主要从以下三点进行可视化: 接口的 qps 看图呈现; 接口可用性(Pxx)看图呈现; 接口请求PXX 耗时统计 看图呈现。 接口 qps 看图绘图 qps 的点位数据怎么打?就是充分利用中间件的设计,在一个请求 prepare 阶段就将该路由记录并获取进行打点。 使用 PromQL 语句就可以实现对对应信息看图的绘制。 1 2 3 4 5 6 // 过去1分钟 每秒请求 qps // sum 求和函数 // rate 计算范围向量中时间序列的每秒平均增长率 // api_request_alert_counter 指标名称 // service_name 和 subject 都是 label kv参数 sum(rate(api_request_alert_counter{service_name="gateway", subject="total"}[1m])) by (subject) 接口可用性看图绘图 接口可用性就是验证当前接口在单位时间内的处理正确的请求数目比上总体的请求数目,在打点的时候也讲到,我们业务代码 0 代表着正确返回,非 0 的代表着存在问题,这样就可以很简单的算出来接口的可用性。 1 2 3 4 5 6 7 8 9 10 11 12 // 过去1分钟 每秒接口可用性 // sum 求和函数 // rate 计算范围向量中时间序列的每秒平均增长率 // api_request_cost_status_count 指标名称 // service_name 和 code 都是 label kv参数 (sum(rate(api_request_cost_status_count{service_name="gateway", code="0"}[1m])) by (handler) / ( sum(rate(api_request_cost_status_count{service_name="gateway", code="0"}[1m])) by (handler) + sum(rate(api_request_cost_status_count{service_name="gateway", code!="0"}[1m])) by (handler)) ) * 100.0 接口 Pxx 耗时统计看图绘图 接口耗时统计打点依赖 prometheus api 中的 histogram 实现,在呈现打点耗时的时候有时候局部的某个耗时过长并不能进行直接反应整体的,我们只需要关注 SLO (服务级别目标)目标下是否达标即可。 1 2 3 4 // 过去1分钟 95% 请求最大耗时统计 // histogram_quantile 1000* histogram_quantile(0.95, sum(rate(api_request_cost_status_bucket{service_name="gateway",handler=~"v1.app.+"}[1m])) by (handler, le)) histogram_quantile(φ float, b instant-vector) 从 bucket 类型的向量 b 中计算 φ (0 ≤ φ ≤ 1) 分位数(百分位数的一般形式)的样本的最大值。(有关 φ 分位数的详细说明以及直方图指标类型的使用,请参阅直方图和摘要)。向量 b 中的样本是每个 bucket 的采样点数量。每个样本的 labels 中必须要有 le 这个 label 来表示每个 bucket 的上边界,没有 le 标签的样本会被忽略。直方图指标类型自动提供带有 _bucket 后缀和相应标签的时间序列。 上面是官方对于 histogram_quantile 函数的解释,关注的是 设置 φ 分位数 对应的 bucket 桶,但是实际中有 分位数计算误差的问题。 Prometheus 官方 histogram 设置的默认 buckets 如下: 1 DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} 这里可以看到我们的接口指标分界时间,每一个请求的耗时都会根据具体的设置的 bucket 的范围落到不同的区间内,这里设置的桶的范围直接影响到计算值的准确度(上面所提到的 分位数计算误差问题)。 报警 及时、准确 使用 Prometheus 的 Alert Manager 就可以对服务进行报警,但是如何及时又准确的报警,已经如何合理设置报警,我们就要引入 SLO 的概念,在实际的业务场景中,我们会发现某个接口某个时间段的耗时是一组离散的点: 我们可以看到大部分的请求可以在 1s 之内就可以快速的返回,只有个别的请求可能由于网络的抖动、应用短暂升级或者其他因素导致过慢,若是我们直接设置接口最大请求耗时超过2s(持续一个时间段),那我们就面临着疯狂的告警轰炸,同时告警也就失去了针对某个接口的异常活动做出提示供开发人员处理的意义。 服务级别目标(Service-level objective,SLO)是指服务提供者向客户作出的服务保证的量化指标。服务级别目标与服务级别协议有所不同。服务级别协议是指服务提供者向客户保证会提供什么样的服务,服务级别目标则是服务的量化说明。 Service-level objective 服务级别目标 比方说我们发现上面的 90% 请求都在 1s 内返回,我们就可以只需要对 90% 请求耗时做监控分析其调用链路并告警。 举个栗子,比方说我们一个首页的接口 /v1/home/page 99% 的请求可以在 500ms 内返回,只有个别的请求超过 2s+ 的时间,大多数情况下我们就不会关心这 1%的请求,那我们就可以定制一个 持续 1分钟首页 99% 请求耗时大于 1s的报警,这样当我们收到报警的时候,我们就可以第一时间知道首页出现了问题,我们就可以根据报警及时处理。 **业务的报警是与接口的实现与调用链路的复杂度是紧密结合在一起的,根据不同的业务场景,配置合理的报警才满足我们及时准确的要求。**反之就是配置过高不灵敏、往往线上已经出现了好久报警就是没有,配置过低,分分钟触发报警,对业务开发人员增加了排查问题的时间成本。 遇到的问题 收集指标过大拉取超时 由于我们是 gateway BFF 层做得指标,本身的路由的基数就比较大,热点路由就有好几百个,再算上对路由的打点、耗时、错误码等等的打点,导致我们每台机器的指标数量都比较庞大,最终指标汇总的时候下游的 prometheus 节点拉取经常出现耗时问题。 前期解决方案比较粗暴,就是修改 prometheus job 的拉取频率及其超时时间,这样可以解决超时问题,但是带来的结果就是最后通过 grafana 看板进行看图包括报警收集上来的点位数据延迟大,并且随着我们指标的设置越来越多的话必然会出现超时问题。 目前的解决方案就是做分布式,采用 prometheus 联邦集群的方式来解决指标收集过大的问题,采用了分布式,就可以将机器分组收集汇总,之后就可以成倍速的缩小 prometheus 拉取的压力。 动态收集机器指标 因为我们机器都是部署在集群上并且会随着活动大促动态调整机器的数量,联邦集群中配置文件最重要的就是配置各个收集节点指标的 IP:Port ,我们不可能每次都去手动维护这个配置,成本比较高,那么我们就需要将配置动态写入,针对此问题,在 leader 的建议下,使用运维服务树拿到该节点下的机器的 Ip,使用脚本程序动态维护起来就非常方便了,默认 Prometheus 是 20s 读取一次配置。 请求的耗时看图与报警不准确 这个问题是在我们的业务中,请求耗时最常见的是在 2s 之内返回,但是通过 Prometheus histogram 对应 1-2s 的请求会落在 le 为 2.5 桶中,导致报警误报,我们看日志中的请求在 1.* s 的都算在 2.5 的桶上,而报警的配置是 大于 2s, emmm 1 DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} 之后根据我们的业务场景调整了一下,使用了自己的 CustomBuckets: 1 CustomBuckets = []float64{.01, .025, .05, .1, .25, .5, 1, 1.5, 2, 3, 4, 8} References Prometheus 官方文档 Prometheus 翻译文档 wiki SLO 服务级别目标 wiki 累积直方图 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/12/11
articleCard.readMore

来小米,一起玩 !!!

永远相信美好的事情即将发生 欢迎大家联系我,让我为你内推吧,小米众多岗位等你来选,不清楚岗位信息的可以联系我(关注微信公众号「Debug客栈」直接发送消息即可),我会给你发对应的内推部门及岗位,也可以联系我查询内推情况,感觉 OK,你就来吧! 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/9/23
articleCard.readMore

Git 命令 reset 和 revert 的区别

前言 在团队开发中,使用 Git 作为版本开发工具,可以便捷地协同多人管理并行开发,但是由于自己或者其他人代码提交污染了远程分支,就需要对远程代码进行恢复操作,Git 提供了 reset 和 revert 两种命令来进行恢复操作,这两种操作效果是截然不同的,不太清楚这个原理的同学需要了解一下,以免在实际的开发过程中翻车,导致线上远程仓库不可逆转的操作。 首先从英文释义来讲,reset 是重置的意思,revert 是恢复、还原的意思,作为 Coder ,第一感觉 reset 的效果比 revert 更猛一些,实际情况也的确如此,让我们一起探讨一下吧。 背景 Git 的每一次提交都是一次 commit,上图可以看到在时间线上有三次提交,此时 HEAD 指向 main 分支,main 分支又指向最新的 Commit3。 HEAD 是指向当前分支的最新提交的指针,可以在任意分支进行切换; main (master)分支,是一个 git 代码仓库的主分支也是默认分支; commit 每一次提交代码都会产生一个 commit id 来标识工作区的变更与改动。 实践出真理 为了直接明白的了解其原理,我这里在 github 上创建一个空白的仓库,按照上图创建三次提交: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 commit b0ef8f9125226af8f06ff1aba7c1f1fc83adea9b (HEAD -> master, origin/master) Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 16:36:39 2021 +0800 feat add 3.go commit 338bf3e30983d34074f37a18b3ff80ea9bca75f0 Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 16:36:09 2021 +0800 feat add 2.go commit 6b166ed34962da08d944e2b1d3f36d9015dd8f35 Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 16:35:16 2021 +0800 feat add 1.go Git Reset git reset 的作用是将 HEAD 指向指定的版本上去: 1 使用 git log 查看提交记录: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 commit b0ef8f9125226af8f06ff1aba7c1f1fc83adea9b (HEAD -> master, origin/master) Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 16:36:39 2021 +0800 feat add 3.go commit 338bf3e30983d34074f37a18b3ff80ea9bca75f0 Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 16:36:09 2021 +0800 feat add 2.go commit 6b166ed34962da08d944e2b1d3f36d9015dd8f35 Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 16:35:16 2021 +0800 feat add 1.go 这里可以看到我们提交了三次记录,我们现在想恢复到第一次 commit 提交的时候。 2 使用 git reset –hard 命令操作: 1 2 ➜ demo git:(master) git reset --hard 6b166ed34962da08d944e2b1d3f36d9015dd8f35 HEAD 现在位于 6b166ed feat add 1.go 再次查看 git log : 1 2 3 4 5 commit 6b166ed34962da08d944e2b1d3f36d9015dd8f35 (HEAD -> master) Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 16:35:16 2021 +0800 feat add 1.go 此时我们可以看到已经恢复到了第一次提交代码的时候,目前我们是使用 git reset --hard 的方式,其实这里存在着三种方式,TODO 下一篇 git 操作讲一下。 这时候我们只是讲本地的 HEAD 指向了 main 分支的 commit 1,但是远程并没有变更,此时需要强行推一下就可以了。 3 使用git push -f 强行推送到远程: 1 2 3 4 ➜ demo git:(master) git push -f 总共 0(差异 0),复用 0(差异 0),包复用 0 To github.com:debuginn/demo.git + b98f95e...6b166ed master -> master (forced update) 此时我们可以看到远程也没有了我们之前提交的三次记录而是只有第一次的提交记录。 在团队合作的共同操作一个仓库的时候, git reset 命令一定要慎重使用,在使用的时候一定要再三确认其他同学的代码是否会被重置操作而导致代码丢失,导致一些提交记录的丢失,这些都是不可逆的,一定要慎重。 Git revert git revert 是用来重做某一个 commit 提交的内容,在我们原始的提交之中,我们会发现分支上面有创建了一个新的 commit 提交,而此时我们对于想重做的某个 commit 提交的内容都不存在了: 1 使用git log查看提交记录: 1 2 3 4 Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 16:36:39 2021 +0800 feat add 3.go 2 使用git revert命令重做操作: 1 2 3 4 5 ➜ demo git:(master) git revert 338bf3e30983d34074f37a18b3ff80ea9bca75f0 删除 2.go [master ef822b7] Revert "feat add 2.go" 1 file changed, 9 deletions(-) delete mode 100644 2.go 再次查看 git log : 1 2 3 4 5 6 7 8 9 10 11 12 13 commit ef822b71c33a2dbbdaa350fddcfa14e8fc55e543 (HEAD -> master, origin/master) Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 17:12:00 2021 +0800 Revert "feat add 2.go" This reverts commit 338bf3e30983d34074f37a18b3ff80ea9bca75f0. commit b0ef8f9125226af8f06ff1aba7c1f1fc83adea9b Author: debuginn <debuginn@icloud.com> Date: Tue Sep 21 17:05:39 2021 +0800 feat add 3.go 可以看到当前已经重做了一下 commit 2 的提交,已经讲 2.go 删除掉了。 可以看到 github 上面有了四次提交记录。 总结 git reset和git revert都是属于重新恢复工作区以及远程提交的方式,但这两种操作有着截然不同的结果: git reset是将之前的提交记录全部抹去,将 HEAD 指向自己重置的提交记录,对应的提交记录都不复存在; git revert 操作是将选择的某一次提交记录 重做,若之后又有提交,提交记录还存在,只是将指定提交的代码给清除掉。 选择合适的方式回滚自己的代码在团队合作中很重要,但是要慎重操作,不要丢失代码哦。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/9/21
articleCard.readMore

使用数字人民币兑换建党100周年纪念币

今天使用数字人民币兑换了建党100周年纪念币,过程比较坎坷,不过最终还是兑换成功了。 预约纪念币成功后,今天中秋假期,正好去兑换纪念币,小雨转中雨 ☁️,作为多年没有使用纸质人民币的我实在是没有钱来兑换纪念币,之后搜索了一下附近可以兑换人民币的营业厅,都在千米之外,算了…… 突然想到前两天美团有一个数字人民币的活动,下载了数字人民币 APP,研究了一下,发现有工商银行支持数字人民币,之后搜寻了一下网点,发现北京地区都是支持数字人民币的了,之后我就去申请了工商银行电子钱包,往里面转了 200 元钱,之后去银行网点ing。 到了之后工作人员引领到专门兑换纪念币柜台,我问了一下是否可以使用数字人民币兑换,好家伙,社死瞬间,一下来了 6 个工作人员看我操作,柜台小姐姐说没有操作过数字人民币付款,之后那我当一下小白鼠 ? ? 操作出来数字人民币支付二维码页面,之后扫描发现不能使用 emmmmm,尴尬,看了提示,原来是让我下载工行的 APP,之后使用上面的数字人民币进行支付,一通下载注册之后,再次去柜台兑换,扫码 => 支付,等了 5s 左右,最终成功兑换了纪念币,现在想想,我应该是第一个使用数字人民币兑换纪念币的第一人了吧。 数字人民币未来由国家导向大力推广,会使人民的支付更加便捷,不过个人建议纸质币保留下来,照顾不会使用手机的老年群体,总之,技术的进步,未来看来我们都是为了一串数字而奋斗喽。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/9/19
articleCard.readMore

订阅本站

Feed 订阅 欢迎订阅下面的 Feed,您可以及时跟踪我的更新: https://blog.debuginn.com/index.xml 推荐使用开源免费颜值极高的 Folo 软件订阅,本博客已经有 订阅者。 公众号订阅 当然,您不习惯上方方式订阅,您可以选择你经常阅读的社区进行订阅,目前仅支持 知乎专栏、腾讯云+社区(Sync)。 视频专区 我还不时制作视频上传至 哔哩哔哩 和 Youtube ,欢迎关注。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/8/13
articleCard.readMore

[译] 方法是否应该在 T 或 *T 上声明

译文原地址:Should methods be declared on T or *T - David 在 Go 中,对于任何的类型 T,都存在一个类型 *T,他是一个表达式的结果,该表达式接收的是类型 T ,例如: 1 2 3 type T struct { a int; b bool } var t T // t's type is T var p = &t // p's type is *T 这两种类型,T 和 *T 是不同的,但 *T 不能代替 T。 你可以在你拥有的任意类型上声明一个方法;也就是说,在您的包中的函数声明的类型。因此,您可以在声明的类型 T 和对应的派生指针类型 *T 上声明方法。另一种说法是,类型上的方法被声明为接收器接收者值的副本,或一个指向其接收者值的指针。所以问题就存在了,究竟是哪种形式最合适? 显然,如果你的方法改变了他的接收者,他应该在 *T 上声明。但是,如果方法不改变他的接收者,在 T 上声明它是安全的么? 事实证明,这样做的话安全的情况非常有限(简单理解就是不安全的)。例如,众所周知,你不应该复制一个 sync.Mutex 的值,因为它打破了互斥量的不变量。由于互斥锁控制对变量(共享资源)的访问,他们经常被包装在一个结构体中,包含他们的控制的值(共享资源): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package counter type Val struct { mu sync.Mutex val int } func (v *Val) Get() int { v.mu.Lock() defer v.mu.Unlock() return v.val } func (v *Val) Add(n int) { v.mu.Lock() defer v.mu.Unlock() v.val += n } 大部分 Gopher 都知道,忘记在指针接收器 *Val 上是声明 Get 或 Add 方法是错误的。然而,任何嵌入 Val 来利用其 0 值的类型,也必须仅在其指针接收器上声明方法,否者可能会无意复制其嵌入类型值的内容: 1 2 3 4 5 6 7 type Stats struct { a, b, c counter.Val } func (s Stats) Sum() int { return s.a.Get() + s.b.Get() + s.c.Get() // whoops(哎呀) } 维护值切片的类型可能会出现类似的陷阱,当然也有可能发生意外的数据竞争。 简而言之,我认为您更应该喜欢在 *T 上声明方法,除非您有非常充分的理由不该这样做。 我们说 T 但这只是您声明的类型的占位符; 此规则是递归的,取 *T 类型的变量的地址返回的是 **T 类型的结果; 这就是为什么没有人可以在像 int 这样的基础类型上声明方法; Go 中的方法只是将接受者作为第一个形式参数传递的函数的语法糖; 如果方法不改变它的接收者,它是否需要是一个方法吗? 相关文章: What is the zero value, and why is it useful? Ice cream makers and data races Slices from the ground up The empty struct 最后,此篇文章我是第一次尝试翻译英文文章,尽管英文水平不太好,一些单词不认识,但是相信自己翻译一篇文章可以学习英语与理解 Go 设计获取 double 的乐趣。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/6/27
articleCard.readMore

我的阅读

我的阅读清单 2023 状态 书籍 类别 笔记 推荐指数 ⏳ 《富爸爸穷爸爸》 理财 🌟🌟🌟🌟🌟 2022 状态 书籍 类别 笔记 推荐指数 ✅ 《Effective Go 中英双语版》 技术 🌟🌟🌟🌟 ✅ 《一往无前》 公司 🌟🌟🌟🌟🌟 ✅ 《黄金时代》 小说 🌟🌟🌟🌟 ✅ 《乖,摸摸头》 散文 🌟🌟🌟🌟🌟 ✅ 《图解 HTTP》 技术 🌟🌟🌟🌟 ✅ 《分布式缓存:原理、架构及Go语言实现》 技术 🌟🌟🌟🌟 ✅ 《深入理解Java虚拟机JVM高级特性与最佳实践》 技术 🌟🌟🌟🌟🌟 ✅ 《深入设计模式》 技术 🌟🌟🌟🌟 ⏳ 《保重》 散文 🌟🌟🌟🌟🌟 2021 状态 书籍 类别 笔记 推荐指数 ✅ 《MySQL 实战 45 讲》 技术 🌟🌟🌟🌟🌟 ✅ 《Redis 设计与实现》 技术 🌟🌟🌟🌟🌟 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/6/6
articleCard.readMore

emmm 这是一篇碎碎念

距离最后一篇博文 《Go 语言开发设计指北》发布已经过去一个多月的时间了,在这一段的时间里,在看了大量的书籍?,在工作上安排的工作都比较得心应手,时间还算比较充裕,但是懒惰心里没有丝毫退去 ?,这样是不行的,很容易让自己的思维和学习能力下降。 先来谈谈近期阅读的一些文章吧,《高性能 MySQL》这一本书相信是计算机从业人员必读的一本书 ? 了吧,这本书虽然看起来比较厚,但是里面的知识面和富有情趣的讲解还是很不错的,给作者点个赞,现在是第一遍读这本书,本着:“先把书读薄”的原则来读,已经攻读了3章多了,学到了很多实际中业务开发的宝贵经验,但是在实际设计中还是会落入坑中,学到了主键、索引设计技巧,以及具体是怎么去使用的,自己所书写的每一条 SQL 语句是在 MySQL中是怎样运行的,执行效率如何,是否可以优化,以及怎么去衡量自己优化的效率可以达到多少,在这本书中都有所讲解。 第二本就是在极客时间上追更鸟窝大神的《Go语言并发实战》,学习了学多的Go语言并发设计所使用到的并发原语及处理方法,包括 Mutex、RWMutex、WaitGroup、Pool、Once、Context等等操作及内部实现,emmm 目前看老师已经更新完了 自己还没有追更完,惭愧呀,Flag 要树立起来了,哈哈。 第三本是《GC的认识》,在开发过程中,自己在业务代码的设计中无需考虑变量声明后销毁的流程,因为在Go语言中已经实现了对堆栈资源的销毁与清理,但是GC是怎么操作的自己之前都是模糊的了解有三色标记,从根出发标记,很笼统的概念,近期看的这本书,严格意义上一笔记,就讲解了GC的执行过程,怎么去观察GC操作以及怎么去对GC优化等操作,详细的就不展开了,大家感兴趣的可以去看一下。 还有就是一些小的细节点的学习了,还有对自己项目组中的项目及框架了解了一下,这里就不分享啦,实际上是不知道是否存在蟹蜜危机。 毕竟上一篇文章发布的时候就战战兢兢 。 结尾呼应标题,这是一篇水文,主要是想告诉大家 Meng小羽并没有跑路 Debug客栈 还在,另外希望大家有好的分享资料的分享的话可以和我互动或者加入我的群聊,毕竟 1+1 > 2 的,对吧。好了不多说了,跑步去了 ?。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/4/24
articleCard.readMore

Go 语言开发设计指北

Go 语言是一种强类型、编译型的语言,在开发过程中,代码规范是尤为重要的,一个小小的失误可能会带来严重的事故,拥有一个良好的 Go 语言开发习惯是尤为重要的,遵守开发规范便于维护、便于阅读理解和增加系统的健壮性。 以下是我们项目组开发规范加上自己开发遇到的问题及补充,希望对你有所帮助: 注:我们将以下约束分为三个等级,分别是:【强制】、【推荐】、【参考】。 Go 编码相关 【强制】代码风格规范遵循 go 官方标准:CodeReviewComments,请使用官方 golint lint 进行风格静态分析; 【强制】代码格式规范依照 gofmt,请安装相关 IDE 插件,在保存代码或者编译时,自动将源码通过 gofmt 做格式化处理,保证团队代码格式一致(比如空格,递进等) 【强制】业务处理代码中不能开 goroutine,此举会导致 goroutine 数量不可控,容易引起系统雪崩,如果需要启用 goroutine 做异步处理,请在初始化时启用固定数量 goroutine,通过 channel 和业务处理代码交互,初始化 goroutine 的函数,原则上应该从 main 函数入口处明确的调用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func crond() { defer func() { if err := recover(); err != nil { // dump stack & log } }() // do something } func main() { // init system go crond() go crond2() // handlers } 【强制】异步开启 goroutine 的地方(如各种 cronder ),需要在最顶层增加 recover(),捕捉 panic,避免个别 cronder 出错导致整体退出: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func globalCrond() { for _ := ticker.C { projectCrond() itemCrond() userCrond() } } func projectCrond() { defer func() { if err := recover(); err != nil { // 打日志,并预警 } } // do } 【强制】当有并发读写 map 的操作,必须加上读写锁 RWMutex,否则 go runtime 会因为并发读写报 panic,或者使用 sync.Map 替代; 【强制】对于提供给外部使用的 package,返回函数里必须带上 err 返回,并且保证在 err == nil 情况下,返回结果不为 nil,比如: 1 2 resp, err := package1.GetUserInfo(xxxxx) // 在err == nil 情况下,resp不能为nil或者空值 【强制】当操作有多个层级的结构体时,基于防御性编程的原则,需要对每个层级做空指针或者空数据判别,特别是在处理复杂的页面结构时,如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 type Section struct { Item *SectionItem Height int64 Width int64 } type SectionItem struct { Tag string Icon string ImageURL string ImageList []string Action *SectionAction } type SectionAction struct { Type string Path string Extra string } func getSectionActionPath(section *Section) (path string, img string, err error) { if section.Item == nil || section.Item.Action == nil { // 要做好足够防御,避免因为空指针导致的panic err = fmt.Errorf("section item is invalid") return } path = section.Item.Action.Path img = section.Item.ImageURL // 对取数组的内容,也一定加上防御性判断 if len(section.Item.ImageList) > 0 { img = section.Item.ImageList[0] } return } 【推荐】生命期在函数内的资源对象,如果函数逻辑较为复杂,建议使用 defer 进行回收: 1 2 3 4 5 6 7 8 func MakeProject() { conn := pool.Get() defer pool.Put(conn) // 业务逻辑 ... return } 对于生命期在函数内的对象,定义在函数内,将使用栈空间,减少 gc 压力: 1 2 3 4 5 6 7 func MakeProject() (project *Project){ project := &Project{} // 使用堆空间 var tempProject Project // 使用栈空间 return } 【强制】不能在循环里加 defer,特别是 defer 执行回收资源操作时。因为 defer 是函数结束时才能执行,并非循环结束时执行,某些情况下会导致资源(如连接资源)被大量占用而程序异常: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 反例: for { row, err := db.Query("SELECT ...") if err != nil { ... } defer row.Close() // 这个操作会导致循环里积攒许多临时资源无法释放 ... } // 正确的处理,可以在循环结束时直接close资源,如果处理逻辑较复杂,可以打包成函数: for { func () { row, err := db.Query("SELECT ...") if err != nil { ... } defer row.Close() ... }() } 【推荐】对于可预见容量的 slice 或者 map,在 make 初始化时,指定cap大小,可以大大降低内存损耗,如: 1 2 3 4 5 6 7 8 9 10 11 headList := make([]home.Sections, 0, len(srcHomeSection)/2) tailList := make([]home.Sections, 0, len(srcHomeSection)/2) dstHomeSection = make([]*home.Sections, 0, len(srcHomeSection)) …. if appendToHead { headList = append(headList, info) } else { tailList = append(tailList, info) } …. dstHomeSection = append(dstHomeSection, headList…) dstHomeSection = append(dstHomeSection, tailList…) 【推荐】逻辑操作中涉及到频繁拼接字符串的代码,请使用 bytes.Buffer 替代。使用 string 进行拼接会导致每次拼接都新增 string 对象,增加 GC 负担: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 正例: var buf bytes.Buffer for _, name := range userList { buf.WriteString(name) buf.WriteString(",") } return buf.String() // 反例: var result string for _, name := range userList { result += name + "," } return result 【强制】对于固定的正则表达式,可以在全局变量初始化时完成预编译,可以有效加快匹配速度,不需要在每次函数请求中预编译: 1 2 3 4 var wordReg = regexp.MustCompile("[\\w]+") func matchWord(word string) bool { return wordReg.MatchString(word) } 【推荐】JSON 解析时,遇到不确定是什么结构的字段,建议使用 json.RawMessage 而不要用 interface,这样可以根据业务场景,做二次 unmarshal 而且性能比 interface 快很多; 【强制】锁使用的粒度需要根据实际情况进行把控,如果变量只读,则无需加锁;读写,则使用读写锁 sync.RWMutex; 【强制】使用随机数时(math/rand),必须要做随机初始化(rand.Seed),否则产生出的随机数是可预期的,在某些场合下会带来安全问题。一般情况下,使用math/rand可以满足业务需求,如果开发的是安全模块,建议使用crypto/rand,安全性更好; 【推荐】对性能要求很高的服务,或者对程序响应时间要求高的服务,应该避免开启大量 gouroutine; 说明:官方虽然号称 goroutine 是廉价的,可以大量开启 goroutine,但是由于 goroutine 的调度并没有实现优先级控制,使得一些关键性的 goroutine(如网络/磁盘IO,控制全局资源的goroutine)没有及时得到调度而拖慢了整体服务的响应时间,因而在系统设计时,如果对性能要求很高,应避免开启大量goroutine。 打点规范 【强制】打点使用.来做分隔符,打点名称需要包含业务名,模块,函数,函数处理分支等,参考如下: 1 2 // 业务名.服务名.模块.功能.方法 service.gateway.module.action.func 【强制】打点使用场景是监控系统的实时状态,不适合存储任何业务数据; 【强制】在打点个数太多时,展示时速度会变慢。建议单个服务打点的key不超过10000个,key中单个维度不同值不超过 1000个(千万不要用 user_id 来打点); 【推荐】如果展示的时候需要拿成百上千个key的数据通过 Graphite 的聚合函数做聚合,最后得到一个或几个 key。这种情况下可以在打点的时候就把这个要聚合的点聚合好,这样展示的时候只要拿这几个 key,对展示速度是巨大的提升。 日志相关 【强制】日志信息需带上下文,其中 logid 必须带上,同一个请求打的日志都需带上 logid,这样可以根据 logid 查找该次请求相关的信息; 【强制】对debug/notice/info 级别的日志输出,必须使用条件输出或者使用占位符方式,避免使用字符拼接方式: 1 log.Debug("get home page failed %s, id %d", err, id) 【强制】如果是解析 json 出错的日志,需要将报错 err 及原内容一并输出,以方便核查原因; 【推荐】对debug/notice/info级别的日志,在打印日志时,默认不显示调用位置(如/path/to/code.go:335) 说明:go 获取调用栈信息是比较耗时的操作(runtime.Caller),对于性能要求很高的服务,特别是大量调用的地方,应尽量避免开发人员在使用该功能时,需知悉这个调用带来的代价。 Redis 相关 【推荐】统一使用:作为前缀后缀分隔符,这里可以根据 Redis 中间件 key proxy 怎么解析分析 Key 进行自定义,便于基础服务的数据可视化及问题排查; 【强制】避免使用 HMGET/HGETALL/HVALS/HKEYS/SMEMBERS 阻塞命令这类命令在 value 较大时,对 Redis 的 CPU/带宽消耗较高,容易导致响应过慢引发系统雪崩; 【强制】不可把 Redis 当成存储,如有统计相关的需求,可以考虑异步同步到数据库进行统计,Redis 应该回归缓存的本质; 【推荐】避免使用大 key,按经验超过 10k 的 value,可以压缩(gzip/snappy等算法)后存入内存,可以减少内存使用,其次降低网络消耗,提高响应速度: 1 2 3 value, err := c.RedisCache.GetGzip(key) …. c.RedisCache.SetExGzip(content, 60) 【推荐】Redis 的分布式锁,可以使用: 1 2 lock: redis.Do("SET", lockKey, randint, "EX", expire, "NX") unlock: redis.GetAndDel(lockKey, randint) // redis暂不支持,可以用lua脚本 【推荐】尽量避免在逻辑循环代码中调用 Redis,会产生流量放大效应,请求量较大时需采用其他方法优化(比如静态配置文件); 【推荐】key 尽量离散读写,通过uid/imei/xid等跟用户/请求相关的后缀分摊到不同分片,避免分片负载不均衡; 【参考】当缓存量大,请求量较高,可能超出 Redis 承受范围时,可充分利用本地缓存(localcache)+redis缓存的组合方案来缓解压力,削减峰值: 使用这个方法需要具备这几个条件: cache 内容与用户无关,key 状态不多,属于公共信息; 该cache内容时效性较高,但是访问量较大,有峰值流量。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 key := "demoid:3344" value := localcacche.Get(key) if value == "" { value = rediscache.Get(key) if value != "" { // 随机缓存 1~5s,各个机器间错开峰值,只要比 redis缓存短即可 localcache.SetEx(key, value, rand.Int63n(5)+1) } } if value == "" { .... // 从其他系统或者数据库获取数据 appoint.GetValue() // 同时设置到redis及localcache中 rediscache.SetEx(key, content, 60) localcache.SetEx(key, content, rand.Int63n(5)+1) } 【参考】对于请求量高,实时性也高的内容,如果纯粹使用缓存,当缓存失效瞬间,会导致大量请求穿透到后端服务,导致后端服务有雪崩危险: 如何兼顾扛峰值,保护后端系统,同时也能保持实时性呢?在这种场景下,可以采用随机更新法更新数据,方法如下: 正常请求从缓存中读取,缓存失效则从后端服务获取; 在请求中根据随机概率 1%(或者根据实际业务场景设置比率)会跳过读取缓存操作,直接从后端服务获取数据,并更新缓存。 这种做法能保证最低时效性,并且当访问量越大,更新概率越高,使得内容实时性也越高。 如果结合上一条 localcache+rediscache 做一二级缓存,则可以达到扛峰值同时保持实时性。 数据库相关 【强制】操作数据库 sql 必须使用 stmt 格式,使用占位符替代参数,禁止拼接 sql; 【强制】SQL语句查询时,不得使用 SELECT * (即形如 SELECT * FROM tbl WHERE),必须明确的给出要查询的列名,避免表新增字段后报错; 【强制】对于线上业务 SQL,需保证命中索引,索引设计基于业务需求及字段区分度,一般可区分状态不高的字段(如 status 等只有几个状态),不建议加到索引中; 【强制】在成熟的语言中,有实体类,数据访问层(repository / dao)和业务逻辑层( service );在我们的规范中存储实体 struct 放置于 entities 包下; 【强制】对于联合索引,需将区分度较大的字段放前面,区分度小放后面,查找时可以减少被检索数据量; 1 2 3 4 5 6 7 8 -- 字段区分度 item_id > project_id alter table xxx add index idx_item_project ( item_id , project_id ) 【强制】所有数据库表必须有主键 id; 【强制】主键索引名为 pk字段名; 唯一索引名为 uk字段名; 普通索引名则为 idx_字段名; 【强制】防止因字段类型不同造成的隐式转换,导致索引失效,造成全表扫描问题; 【强制】业务上有唯一特性的字段,即使是多字段的组合,也必须建成唯一索引; 【强制】一般事务标准操作流程: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func TestTXExample(t *testing.T) { // 打开事务 tx, err := db.Beginx() if err != nil { log.Fatal("%v", err) return } // defer异常 needRollback := true defer func() { if r := recover(); r != nil { // 处理recover,避免因为panic,资源无法释放 log.Fatal("%v", r) needRollback = true } if needRollback { xlog.Cause("test.example.transaction.rollback").Fatal() tx.Rollback() } }() // 事务的逻辑 err = InsertLog(tx, GenTestData(1)[0]) if err != nil { log.Fatal("%v", err) return } // 提交事务 err = tx.Commit() if err != nil { log.Fatal("%v", err) return } needRollback = false return } 【强制】执行事务操作时,请确保SELECT ... FOR UPDATE条件命中索引,使用行锁,避免一个事务锁全表的情况; 【强制】禁止超过三个表的 join,需要 join 的字段,数据类型必须一致,多表关联查询时,保证被关联的字段有索引; 【强制】数据库 max_open 连接数不可设置过高,会导致代理连接数打满导致不可用状况; 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/3/7
articleCard.readMore

如何提升自己的技术博文水平

2021 年的一月份马上就过去了,在这一个月中,并没有新鲜出炉的博文,恰恰相反的是我这一个月以来,在思考,自己的博客怎么输出高质量有水平的文章,正如一首优美的旋律,怎么听都可以让人回味无穷,每一遍都有自己的收获。 关于提升自己博客文章的水平,这个月思考了很多的方向,也阅读了不少人对于博客的看法和理解,最终对自己博客总结了几点不足之处: 看到及听到一种新技术或者新的事物,总是以为看了几篇的相关的文章介绍及简短的理解文章,就认为自己了解了某一个事物或者技术,之后输出自己的想法,写出一篇总结的文章,理所应当的写不出有深度有营养的文章; 写一篇博文访问量的关注与文章输出的内容对比更倾向于前者,突然感觉到自己很肤浅,本末倒置,好的文章输出最不用关心的就是访问量的问题; 博文不是自己的 OKR,而是自己技术的自留地,入职大厂之后,感觉自己的技术文章输出应该高出一个层次,一些浅显易懂的或者容易学到的点就不需要总结成文章了,眼高手低,没有认清自己的技术与输出的水平; 自己的技术储备不足(看的书及教程少),博客中一些文章还停留在了解的层次,并没有真正的去 Get 到每个的深层次的水平中去; 懒惰的心理,短视频及游戏 ? VS Debug客栈,大部分时间选择前者,但是前者看了再多,玩的再6,也提升不了自己,只能当算法喂养下的白痴。 毫不夸张的说,若是把博客水平比做成人的一生,我的博客水平依旧处在了18岁之前,没有自己的思考,更多的是在教程、分享的模版下输出,虽有输出,但今天的我发现并没有太多的营养。 正如上方的分割线,我希望自己未来的输出在这里是一个分水岭,接下来自己可以学到更多,变得更强,让自己输出的文章有深度,自己的编程技术更加有水平。 回归主题,“如何提升自己的技术博文水平”,其实映射出是自己的技术水平的不足导致的,那如何提升自己的技术水平,自己总结了一下接下来要做的努力: 阅读技术书籍,技术不能停留在会使用的阶段,要知道自己的每一步操作,在计算机内部发生了什么,原理及使用的技术是什么,现在的我谈不上如何去改进某个技术,但是要会灵活的使用现有技术提升自己的编程水平,提高自己代码的稳定性及让自己的代码写出来如诗一般优雅; 在本职工作中,多看项目组及同事的代码,不仅仅是看代码,思考为什么这样设计,这样设计带来的好处是什么,会有哪些不足,如何改进及优化; 在流行的技术及 Go 语言包中,加入到开源的项目中去,多去看大佬们的代码及设计哲学,了解业界技术的更迭及主流用法,可以的话贡献自己的代码; 多去交流技术,不能认为自己代码很 Low,没有了解很多就对研讨会或者分享会望而却步,恰恰相反,自己在这些分享会中会发现自己的水平处在什么阶段及自己如何去提升自己; 多去整理学过了、了解到的技术的笔记,学会提炼及吸收成自己的知识体系: 这是自己搭建的笔记平台:https://notes.debuginn.com 自己的学习笔记都会同步至此平台,更多的是自己的学习笔记及重要的知识点,这就是我的小本本。 最后,感谢自己导师的教诲与提醒,自己要更加的努力,提升自己,2021,定不负大家的厚望,努力成为项目组中的中坚力量,加油!!!撰写出更多有营养的文章,当然,有些理解片面或者不足的文章还请大家批评指出,谢谢 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2021/1/30
articleCard.readMore

2020 年度总结

今年,最大的感受就是时间过的太快了,一切都是那么的来不及 …… 2020 年,疫情、毕业、工作,学生时代的 END,社会人时代的 START …… 2021 年,希望一切都在慢慢变好,新的开始、新的未来!!! 每一次总结都是新的开始的起点,那么,接下来就开始吧~ 网站数据 今年是小站运行的第 4 年,感谢大家的支持与访问,这是我分享的天地、同时也是见证我成长的地方,加油~ 一往无前 !!! 最受欢迎的文章 🔥🔥🔥 Restful API 设计指北 🔥🔥 吊打百度,多吉搜索引擎 🔥 程序猿的 Chrome 浏览器插件推荐 搭建流媒体服务器 PingOS 平台搭建 怎么优雅的选择 MySQL 存储引擎 More 页面 2020 看见的我不止一面,这里记录了我的 MORE https://debuginn.com/about 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/12/31
articleCard.readMore

Go 并发编程之 RWMutex

Mutex 是用来保证只有一个 goroutine 访问共享资源,在大量的并发场景中,特别是读场景中,一个共享资源块只能让 goroutine 串行访问,这就导致了性能的影响,解决方法就是区分读写操作。 这样就可以将串行的读变成并行的读,用来提高读操作的性能。 Go 标准库 RWMutex (读写锁)就是用来解决 readers-writers 问题的。 RWMutex 标准库中的 RWMutex 是一个 reader/writer 互斥锁,RWMutex 在同一时间只能由 n 个 reader 持有,或者只能被单个的 writer 持有。 Lock/Unlock:写操作时调用的方法,若是被 reader 或者 writer 持有, Lock 会一直阻塞,直到可以获取到锁,Ulock 是释放锁; Rlock/RUnlock:读操作时低哦用的方法,如果已经被 writer 持有的话, Rlock 会一直阻塞,直到获取到锁,否者直接返回, RUlock 是 reader 释放锁的方法; RLocker:为读操作返回一个 Locker 接口的对象。 RWMutex 的零值是未加锁的状态,所以在使用 RWMutex 作为变量或者嵌入到 struct 中去,都没有必要进行显式的初始化。 实现原理 针对于 readers-writers 问题是基于对读和写操作的优先级,读写锁的设计分为三类: Read-preferring 读优先设计:并发效果好,但是在大量的并发场景下会导致写饥饿; Write-preferring 写优先设计:针对新请求而言,主要是避免了 writer 饥饿问题,也就是说同一时间有一个 reader 和 writer 等待获取锁,会优先给 writer; 不指定优先级:FIFO,不区分读写优先级,适用于某些特定的场景。 RWMutex 设计是 write-preferring 写优先设计。一个正在阻塞的 Lock 调用会排出新的 reader 请求到锁。 RWMutex 包含一个 Mutex,以及四个辅助字段 writerSem、readerSem、readerCount 和 readerWait: 1 2 3 4 5 6 7 8 9 type RWMutex struct { w Mutex // 互斥锁解决多个 writer 的竞争 writerSem uint32 // writer 信号量 readerSem uint32 // reader 信号量 readerCount int32 // reader 的数量,记录当前 reader 等待的数量 readerWait int32 // writer 等待完成的 reader的 数量 } const rwmutexMaxReaders = 1 << 30 RLock/RUlock 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func (rw *RWMutex) RLock() { // 对 reader 计数 +1,readerCount 会出现负数 // 1、没有 writer 竞争或者持有锁的时候,readerCount 充当计数器存在 // 2、如果有 writer 竞争锁或者持有锁时,那么,readerCount 不仅仅承担着 reader 的计数功能,还能够标识当前是否有 writer 竞争或持有锁 if atomic.AddInt32(&rw.readerCount, 1) < 0 { // rw.readerCount 是负值的时候,意味着此时有 writer 等待请求锁 // 因为writer优先级高,所以把后来的 reader 阻塞休眠 runtime_SemacquireMutex(&rw.readerSem, false, 0) } } func (rw *RWMutex) RUnlock() { // 将 reader 计数 -1 if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 { // 如果为 负数,代表着当前有 writer 在竞争锁,检查是不是所有的 reader 都将锁释放 // 若释放了就让 writer 获取到锁进行写操作 rw.rUnlockSlow(r) // 有等待的writer } } func (rw *RWMutex) rUnlockSlow(r int32) { // rUnlockSlow 将持有锁的 reader 计数 -1 的时候; // 会检查既有的 reader 是不是都已经释放了锁; // 如果都释放了锁,就会唤醒 writer,让 writer 持有锁。 if atomic.AddInt32(&rw.readerWait, -1) == 0 { // 最后一个reader了,writer终于有机会获得锁了 runtime_Semrelease(&rw.writerSem, false, 1) } } Lock / Unlock RWMutex 是一个多 writer 多 reader 的读写锁,所以同时可能有多个 writer 和 reader。那么,为了避免 writer 之间的竞争,RWMutex 就会使用一个 Mutex 来保证 writer 的互斥。 1 2 3 4 5 6 7 8 9 10 func (rw *RWMutex) Lock() { // 首先解决其他 writer 竞争问题 rw.w.Lock() // 反转 readerCount,告诉 reader 有 writer 竞争锁 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders // 如果当前有 reader 持有锁,那么需要等待 if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { runtime_SemacquireMutex(&rw.writerSem, false, 0) } } 一旦一个 writer 获得了内部的互斥锁,就会反转 readerCount 字段,把它从原来的正整数 readerCount(>=0) 修改为负数(readerCount-rwmutexMaxReaders),让这个字段保持两个含义(既保存了 reader 的数量,又表示当前有 writer)。 1 2 3 4 5 6 7 8 9 10 11 func (rw *RWMutex) Unlock() { // 告诉 reader 没有活跃的 writer 了 r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders) // 唤醒阻塞的 reader 们 for i := 0; i < int(r); i++ { runtime_Semrelease(&rw.readerSem, false, 0) } // 释放内部的互斥锁 rw.w.Unlock() } 当一个 writer 释放锁的时候,它会再次反转 readerCount 字段。可以肯定的是,因为当前锁由 writer 持有,所以,readerCount 字段是反转过的,并且减去了 rwmutexMaxReaders 这个常数,变成了负数。 所以,这里的反转方法就是给它增加 rwmutexMaxReaders 这个常数值。既然 writer 要释放锁了,那么就需要唤醒之后新来的 reader,不必再阻塞它们了,让它们开开心心地继续执行就好了。 在 RWMutex 的 Unlock 返回之前,需要把内部的互斥锁释放。释放完毕后,其他的 writer 才可以继续竞争这把锁。 RWMutex 常犯的三种错误 不可复制 重入导致死锁 释放未加锁的 RWMutex 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/12/5
articleCard.readMore

Go 并发编程之 Mutex

我们比较常见的大型项目的设计中都会出现并发访问问题,并发就是为了解决数据的准确性,保证同一个临界区的数据只能被一个线程进行操作,日常中使用到的并发场景也是很多的: 计数器:计数器结果不准确; 秒杀系统:由于同一时间访问量比较大,导致的超卖; 用户账户异常:同一时间支付导致的账户透支; buffer 数据异常:更新 buffer 导致的数据混乱。 上面都是并发带来的数据准确性的问题,决绝方案就是使用互斥锁,也就是今天并发编程中的所要描述的 Mutex 并发原语。 实现机制 互斥锁 Mutex 就是为了避免并发竞争建立的并发控制机制,其中有个“临界区”的概念。 在并发编程过程中,如果程序中一部分资源或者变量会被并发访问或者修改,为了避免并发访问导致数据的不准确,这部分程序需要率先被保护起来,之后操作,操作结束后去除保护,这部分被保护的程序就叫做临界区。 使用互斥锁,限定临界区只能同时由一个线程持有,若是临界区此时被一个线程持有,那么其他线程想进入到这个临界区的时候,就会失败或者等待释放锁,持有此临界区的线程退出,其他线程才有机会获得这个临界区。 Mutex 是 Go 语言中使用最广泛的同步原语,也称为并发原语,解决的是并发读写共享资源,避免出现数据竞争 data race 问题。 基本使用 互斥锁 Mutex 提供了两个方法 Lock 和 Unlock:进入到临界区使用 Lock 方法加锁,退出临界区使用 Unlock 方法释放锁。 1 2 3 4 5 6 7 type Locker interface { Lock() Unlock() } func(m *Mutex)Lock() func(m *Mutex)Unlock() 当一个 goroutine 调用 Lock 方法获取到锁后,其他 goroutine 会阻塞在 Lock 的调用上,直到当前获取到锁的 goroutine 释放锁。 接下来是一个计数器的例子,是由 100 个 goroutine 对计数器进行累加操作,最后输出结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package main import ( "fmt" "sync" ) func main() { var mu sync.Mutex countNum := 0 // 确认辅助变量是否都执行完成 var wg sync.WaitGroup // wg 添加数目要和 创建的协程数量保持一致 wg.Add(100) for i := 0; i < 100; i++ { go func() { defer wg.Done() for j := 0; j < 1000; j++ { mu.Lock() countNum++ mu.Unlock() } }() } wg.Wait() fmt.Printf("countNum: %d", countNum) } 实际使用 很多时候 Mutex 并不是单独使用的,而是嵌套在 Struct 中使用,作为结构体的一部分,如果嵌入的 struct 有多个字段,我们一般会把 Mutex 放在要控制的字段上面,然后使用空格把字段分隔开来。 甚至可以把获取锁、释放锁、计数加一的逻辑封装成一个方法。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package main import ( "fmt" "sync" ) // 线程安全的计数器 type Counter struct { CounterType int Name string mu sync.Mutex count uint64 } // 加一方法 func (c *Counter) Incr() { c.mu.Lock() defer c.mu.Unlock() c.count++ } // 取数值方法 线程也需要受保护 func (c *Counter) Count() uint64 { c.mu.Lock() defer c.mu.Unlock() return c.count } func main() { // 定义一个计数器 var counter Counter var wg sync.WaitGroup wg.Add(100) for i := 0; i < 100; i++ { go func() { defer wg.Done() for j := 0; j < 1000; j++ { counter.Incr() } }() } wg.Wait() fmt.Printf("%d\n", counter.Count()) } 思考问题 Q:你已经知道,如果 Mutex 已经被一个 goroutine 获取了锁,其它等待中的 goroutine 们只能一直等待。那么,等这个锁释放后,等待中的 goroutine 中哪一个会优先获取 Mutex 呢? A:FIFO,先来先服务的策略,Go 的 goroutine 调度中,会维护一个保障 goroutine 运行的队列,当获取到锁的 goroutine 执行完临界区的操作的时候,就会释放锁,在队列中排在第一位置的 goroutine 会拿到锁进行临界区的操作。 实现原理 Mutex 的架构演进目前分为四个阶段: 初版 Mutex:使用一个 flag 变量表示锁?是否被持有; 给新人机会:照顾新来的 goroutine 先获取到锁; 多给些机会:照顾新来的和被唤醒的 goroutine 获取到锁; 解决饥饿:存在竞争关系,有饥饿情况发生,需要解决。 初版 Mutex 1 2 3 4 5 // 互斥锁的结构,包含两个字段 type Mutex struct { key int32 // 锁是否被持有的标识 sema int32 // 信号量专用,用以阻塞/唤醒goroutine } Unlock 方法可以被任意的 goroutine 调用释放锁,即使是没持有这个互斥锁的 goroutine,也可以进行这个操作。这是因为,Mutex 本身并没有包含持有这把锁的 goroutine 的信息,所以,Unlock 也不会对此进行检查。Mutex 的这个设计一直保持至今。 在使用 Mutex 的时候,需要严格遵循 “谁申请,谁释放” 原则。 解决饥饿 由于使用了给新人机会,又肯呢个会出现每次都会被新来的 goroutine 获取到锁,导致等待的 goroutine 一直获取不到锁,造成饥饿问题。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 type Mutex struct { state int32 sema uint32 } const ( mutexLocked = 1 << iota // mutex is locked mutexWoken mutexStarving // 从state字段中分出一个饥饿标记 mutexWaiterShift = iota starvationThresholdNs = 1e6 ) func (m *Mutex) Lock() { // Fast path: 幸运之路,一下就获取到了锁 if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { return } // Slow path:缓慢之路,尝试自旋竞争或饥饿状态下饥饿goroutine竞争 m.lockSlow() } func (m *Mutex) lockSlow() { var waitStartTime int64 starving := false // 此goroutine的饥饿标记 awoke := false // 唤醒标记 iter := 0 // 自旋次数 old := m.state // 当前的锁的状态 for { // 锁是非饥饿状态,锁还没被释放,尝试自旋 if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) { if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 && atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) { awoke = true } runtime_doSpin() iter++ old = m.state // 再次获取锁的状态,之后会检查是否锁被释放了 continue } new := old if old&mutexStarving == 0 { new |= mutexLocked // 非饥饿状态,加锁 } if old&(mutexLocked|mutexStarving) != 0 { new += 1 << mutexWaiterShift // waiter数量加1 } if starving && old&mutexLocked != 0 { new |= mutexStarving // 设置饥饿状态 } if awoke { if new&mutexWoken == 0 { throw("sync: inconsistent mutex state") } new &^= mutexWoken // 新状态清除唤醒标记 } // 成功设置新状态 if atomic.CompareAndSwapInt32(&m.state, old, new) { // 原来锁的状态已释放,并且不是饥饿状态,正常请求到了锁,返回 if old&(mutexLocked|mutexStarving) == 0 { break // locked the mutex with CAS } // 处理饥饿状态 // 如果以前就在队列里面,加入到队列头 queueLifo := waitStartTime != 0 if waitStartTime == 0 { waitStartTime = runtime_nanotime() } // 阻塞等待 runtime_SemacquireMutex(&m.sema, queueLifo, 1) // 唤醒之后检查锁是否应该处于饥饿状态 starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs old = m.state // 如果锁已经处于饥饿状态,直接抢到锁,返回 if old&mutexStarving != 0 { if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 { throw("sync: inconsistent mutex state") } // 有点绕,加锁并且将waiter数减1 delta := int32(mutexLocked - 1<<mutexWaiterShift) if !starving || old>>mutexWaiterShift == 1 { delta -= mutexStarving // 最后一个waiter或者已经不饥饿了,清除饥饿标记 } atomic.AddInt32(&m.state, delta) break } awoke = true iter = 0 } else { old = m.state } } } func (m *Mutex) Unlock() { // Fast path: drop lock bit. new := atomic.AddInt32(&m.state, -mutexLocked) if new != 0 { m.unlockSlow(new) } } func (m *Mutex) unlockSlow(new int32) { if (new+mutexLocked)&mutexLocked == 0 { throw("sync: unlock of unlocked mutex") } if new&mutexStarving == 0 { old := new for { if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 { return } new = (old - 1<<mutexWaiterShift) | mutexWoken if atomic.CompareAndSwapInt32(&m.state, old, new) { runtime_Semrelease(&m.sema, false, 1) return } old = m.state } } else { runtime_Semrelease(&m.sema, true, 1) } } 思考问题 Q: 目前 Mutex 的 state 字段有几个意义,这几个意义分别是由哪些字段表示的? A:state 字段一共有四个子字段,前三个 bit 是 mutexLocked(锁标记)、mutexWoken(唤醒标记)、mutexStarving(饥饿标记),剩余 bit 标示 mutexWaiter(等待数量)。 Q: 等待一个 Mutex 的 goroutine 数最大是多少?是否能满足现实的需求? A:目前的设计来看取决于 state 的类型,目前是 int32,由于3个字节代表了状态,有 536870911,一个 goroutine 初始化的为 2kb,约等于 1024 GB 即 1TB,目前内存体量那么大的服务还是少有的,可以满足现在的使用。 常见错误的四种场景 Lock/Unlock 不是成对出现; Copy 已使用的 Mutex; 重入; 死锁。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/11/15
articleCard.readMore

优雅的使用 Brew 切换 Go 版本

Brew 是 Mac 上包管理工具,和 Linux 上的 apt 、yum、rpm 一样,可以提供非图形化软件的安装,昨天在打造宇宙最强 IDE 的时候,使用brew工具更新了一下软件包,是我的 Go 版本升级到了最新版本,同时之前配置的多版本 Go 抹掉了,现在写一下记录,你如果需要的话可以使用一下。 之前写过一个使用 GVM 版本管理工具的文章,这个是第三方工具管理的,都比较好用,你可以根据自己的需求安装。 方案一 brew switch 1 brew install 1 brew install go 2 brew switch 1 2 ~ brew info go go: stable 1.15.3 (bottled), HEAD 使用 brew info go 命令你可以看到当前目前的 go 可以切换的版本,接下来就安装多个版本并且切换到对应的版本。 1 2 3 4 // 安装指定 go 版本 brew install go@<version> // forexample brew install go@1.12.17 安装好了 之后使用 brew info go 查看是否可以切换了。 1 brew switch go 1.12.17 单纯的使用上面的命令你会发现,go 不能使用了,并且会出现下面的提示: 1 2 3 4 ~ brew switch go 1.12.17 Cleaning /usr/local/Cellar/go/1.12.17 Cleaning /usr/local/Cellar/go/1.15.3 0 links created for /usr/local/Cellar/go/1.12.17 创建了零个连接,就代表着没有成功的将 go 版本指向你所需要的版本下,问题是什么呢?现将 go 版本切回 go 1.15.3,你会发现可以切换并正常使用: 1 2 3 4 5 6 7 ~ brew switch go 1.15.3 Cleaning /usr/local/Cellar/go/1.12.17 Cleaning /usr/local/Cellar/go/1.15.3 3 links created for /usr/local/Cellar/go/1.15.3 ~ go version go version go1.15.3 darwin/amd64 定位这个原因你需要看看为什么没有未给 go 1.12.17 版本创建软连接,首先要找一下 go 默认安装的位置,使用 go env 查看安装目录: 1 /usr/local/Cellar/go/ 使用 brew 工具在 MacOS Catalina 系统安装的位置。 进入到目录之后在 go 目录下只有刚才默认安装的 1.15.3 版本,并没有自己安装的版本,退出父级目录看到了下载的 go@1.12.17 版本,由于软连接连接的是上方的路径,需要将这个目录移动至 go 目录下: 1 2 3 4 5 6 7 8 // 打开默认目录 cd /usr/local/Cellar/go/ // 退出目录 cd .. // 移动目录至 go 目录下 mv go@1.12.17 go/ // 重要!!! 重命名文件夹 mv go@1.12.17 1.12.17 接下来使用切换命令 brew switch go <version> 就可以切换环境了。 方案二 brew link 使用 Homebrew 3.2.9 验证。 1、安装新的版本: 1 brew install go@1.16 // 安装 go 1.16 版本 2、移除原有的 go 版本软链 1 brew unlink go 3、指定新的版本软链 1 brew link go@1.16 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/11/1
articleCard.readMore

Go IP 段范围校验

近期做了一个需求,是检测某个 IP 是否在若干 IP 段内,做固定地点 IP 筛查,满足特定业务需求。 解决方案 PLAN A 点分十进制范围区分 简单来讲,就是将 IPv4 原有的四段,分别对比 IP 地址,查看每一段是否在 IP 段范围内,可以用于段控制在每一个特定段 0 ~ 255 内筛选,例如: 1 192.123.1.0 ~ 192.123.156.255 这样的比较规范的特定段可以实现简单的筛选,但是问题来了,不规则的连续 IP 段怎么排除? 如下: 1 2 IP段:192.168.1.0 ~ 192.172.3.255 IP: 192.160.0.255 这样就会出现问题,可以看到按照简单的分段对比,很明显校验不通过,但是这个 IP 还是存在在 IP 段中,方案只能针对统一分段下规则的IP段才可以区分。 PLAN B 转整型对别 IP 地址可以转换为整数,可以将 IP 范围化整为 整数范围进行排查。 这种方式只需要将授为范围内的地址转换为整数,就可以将 IP 排查在外了。 代码 以下是示例代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package main import ( "fmt" "strconv" "strings" ) func main() { ipVerifyList := "192.168.1.0-192.172.3.255" ip := "192.170.223.1" ipSlice := strings.Split(ipVerifyList, `-`) if len(ipSlice) < 0 { return } if ip2Int(ip) >= ip2Int(ipSlice[0]) && ip2Int(ip) <= ip2Int(ipSlice[1]) { fmt.Println("ip in iplist") return } fmt.Println("ip not in iplist") } func ip2Int(ip string) int64 { if len(ip) == 0 { return 0 } bits := strings.Split(ip, ".") if len(bits) < 4 { return 0 } b0 := string2Int(bits[0]) b1 := string2Int(bits[1]) b2 := string2Int(bits[2]) b3 := string2Int(bits[3]) var sum int64 sum += int64(b0) << 24 sum += int64(b1) << 16 sum += int64(b2) << 8 sum += int64(b3) return sum } func string2Int(in string) (out int) { out, _ = strconv.Atoi(in) return } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/9/8
articleCard.readMore

Go 标准库 限流器 time/rate 设计与实现

限流器是后台服务中十分重要的组件,在实际的业务场景中使用居多,其设计在微服务、网关、和一些后台服务中会经常遇到。限流器的作用是用来限制其请求的速率,保护后台响应服务,以免服务过载导致服务不可用现象出现。 限流器的实现方法有很多种,例如 Token Bucket、滑动窗口法、Leaky Bucket等。 在 Golang 库中官方给我们提供了限流器的实现golang.org/x/time/rate,它是基于令牌桶算法(Token Bucket)设计实现的。 令牌桶算法 令牌桶设计比较简单,可以简单的理解成一个只能存放固定数量雪糕?的一个冰箱,每个请求可以理解成来拿雪糕的人,有且只能每一次请求拿一块?,那雪糕拿完了会怎么样呢?这里会有一个固定放雪糕的工人,并且他往冰箱里放雪糕的频率都是一致的,例如他 1s 中只能往冰箱里放 10 块雪糕,这里就可以看出请求响应的频率了。 令牌桶设计概念: 令牌:每次请求只有拿到 Token 令牌后,才可以继续访问; 桶:具有固定数量的桶,每个桶中最多只能放设计好的固定数量的令牌; 入桶频率:按照固定的频率往桶中放入令牌,放入令牌不能超过桶的容量。 也就是说,基于令牌桶设计算法就限制了请求的速率,达到请求响应可控的目的,特别是针对于高并发场景中突发流量请求的现象,后台就可以轻松应对请求了,因为到后端具体服务的时候突发流量请求已经经过了限流了。 具体设计 限流器定义 1 2 3 4 5 6 7 8 type Limiter struct { mu sync.Mutex // 互斥锁(排他锁) limit Limit // 放入桶的频率 float64 类型 burst int // 桶的大小 tokens float64 // 令牌 token 当前剩余的数量 last time.Time // 最近取走 token 的时间 lastEvent time.Time // 最近限流事件的时间 } limit、burst 和 token 是这个限流器中核心的参数,请求并发的大小在这里实现的。 在令牌发放之后,会存储在 Reservation 预约对象中: 1 2 3 4 5 6 7 type Reservation struct { ok bool // 是否满足条件分配了 token lim *Limiter // 发送令牌的限流器 tokens int // 发送 token 令牌的数量 timeToAct time.Time // 满足令牌发放的时间 limit Limit // 令牌发放速度 } 消费 Token Limiter 提供了三类方法供用户消费 Token,用户可以每次消费一个 Token,也可以一次性消费多个 Token。而每种方法代表了当 Token 不足时,各自不同的对应手段。 Wait、WaitN 1 2 func (lim *Limiter) Wait(ctx context.Context) (err error) func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) 其中,Wait 就是 WaitN(ctx, 1),在下面的方法介绍实现也是一样的。 使用 Wait 方法消费 Token 时,如果此时桶内 Token 数组不足 ( 小于 n ),那么 Wait 方法将会阻塞一段时间,直至 Token 满足条件。如果充足则直接返回。 Allow、AllowN 1 2 func (lim *Limiter) Allow() bool func (lim *Limiter) AllowN(now time.Time, n int) bool AllowN 方法表示,截止到当前某一时刻,目前桶中数目是否至少为 n 个,满足则返回 true,同时从桶中消费 n 个 token。 反之返回不消费 Token,false。 通常对应这样的线上场景,如果请求速率过快,就直接丢到某些请求。 Reserve、ReserveN 官方提供的限流器有阻塞等待式的 Wait,也有直接判断方式的 Allow,还有提供了自己维护预留式的,但核心的实现都是下面的 reserveN 方法。 1 2 func (lim *Limiter) Reserve() *Reservation func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation 当调用完成后,无论 Token 是否充足,都会返回一个Reservation *对象。 你可以调用该对象的 Delay() 方法,该方法返回了需要等待的时间。如果等待时间为 0,则说明不用等待。 必须等到等待时间结束之后,才能进行接下来的工作。 或者,如果不想等待,可以调用 Cancel() 方法,该方法会将 Token 归还。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation { lim.mu.Lock() // 首先判断是否放入频率是否为无穷大 // 如果为无穷大,说明暂时不限流 if lim.limit == Inf { lim.mu.Unlock() return Reservation{ ok: true, lim: lim, tokens: n, timeToAct: now, } } // 拿到截至 now 时间时 // 可以获取的令牌 tokens 数量及上一次拿走令牌的时间 last now, last, tokens := lim.advance(now) // 更新 tokens 数量 tokens -= float64(n) // 如果 tokens 为负数,代表当前没有 token 放入桶中 // 说明需要等待,计算等待的时间 var waitDuration time.Duration if tokens < 0 { waitDuration = lim.limit.durationFromTokens(-tokens) } // 计算是否满足分配条件 // 1、需要分配的大小不超过桶的大小 // 2、等待时间不超过设定的等待时长 ok := n <= lim.burst && waitDuration <= maxFutureReserve // 预处理 reservation r := Reservation{ ok: ok, lim: lim, limit: lim.limit, } // 若当前满足分配条件 // 1、设置分配大小 // 2、满足令牌发放的时间 = 当前时间 + 等待时长 if ok { r.tokens = n r.timeToAct = now.Add(waitDuration) } // 更新 limiter 的值,并返回 if ok { lim.last = now lim.tokens = tokens lim.lastEvent = r.timeToAct } else { lim.last = last } lim.mu.Unlock() return r } 具体使用 rate 包中提供了对限流器的使用,只需要指定 limit(放入桶中的频率)、burst(桶的大小)。 1 2 3 4 5 6 func NewLimiter(r Limit, b int) *Limiter { return &Limiter{ limit: r, // 放入桶的频率 burst: b, // 桶的大小 } } 在这里,使用一个 http api 来简单的验证一下 time/rate 的强大: 1 2 3 4 5 6 7 8 9 10 11 12 13 func main() { r := rate.Every(1 * time.Millisecond) limit := rate.NewLimiter(r, 10) http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { if limit.Allow() { fmt.Printf("请求成功,当前时间:%s\n", time.Now().Format("2006-01-02 15:04:05")) } else { fmt.Printf("请求成功,但是被限流了。。。\n") } }) _ = http.ListenAndServe(":8081", nil) } 在这里,我把桶设置成了每一毫秒投放一次令牌,桶容量大小为 10,起一个 http 的服务,模拟后台 API。 接下来做一个压力测试,看看效果如何: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func GetApi() { api := "http://localhost:8081/" res, err := http.Get(api) if err != nil { panic(err) } defer res.Body.Close() if res.StatusCode == http.StatusOK { fmt.Printf("get api success\n") } } func Benchmark_Main(b *testing.B) { for i := 0; i < b.N; i++ { GetApi() } } 效果如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 ...... 请求成功,当前时间:2020-08-24 14:26:52 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,当前时间:2020-08-24 14:26:52 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 ...... 在这里,可以看到,当使用 AllowN 方法中,只有当令牌 Token 生产出来,才可以消费令牌,继续请求,剩余的则是将其请求抛弃,当然在实际的业务处理中,可以用比较友好的方式反馈给前端。 在这里,先有的几次请求都会成功,是因为服务启动后,令牌桶会初始化,将令牌放入到桶中,但是随着突发流量的请求,令牌按照预定的速率生产令牌,就会出现明显的令牌供不应求的现象。 开源仓库 目前 time/rate 是一个独立的限流器开源解决方案,感兴趣的小伙伴可以给此项目一个 Star,谢谢。 https://github.com/golang/time References 限流器系列(2) – Token Bucket 令牌桶 Golang 限流器的使用和实现 Golang 标准库限流器 time/rate 使用介绍 https://github.com/golang/time/rate.go 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/8/24
articleCard.readMore

我的 MacBook Pro 又满血复活啦

经过近两个星期的检测,维修 ?,我的 MacBook 满血复活了,事情是这样的,两周前我的电脑突然之间就黑屏,有充电反馈,键盘,Bar 和触控板均失灵,拿到公司 IT 部门,给我的意见是去售后 ?,紧接着到了周末去了售后,给我的解决方案是更换硬件,告诉我说要更换主板,也就代表着硬盘数据没有了,允悲,我同时给他说明针对于我两次修复蝶形键盘的经历,售后人员决定给我申请键盘也更换,心中多少有些安慰,于是他给了我一个维修周期,届时来领取就可以了。 于是我就拿着维修单回去了,过了两天,接到了售后的电话,我本以为修好了,并没有,售后给我说做了检测,显示器也有问题,需要给我更换,这,,,不就是更换全部的部件么,直接给我换台多效率 ?,显然苹果并没有那么给我做,现在拿到手中的就是除了下底壳没有更换,其他全部更换的九成新新机,百感交集呀。 不过,最终是修好了,在公司入职的这么多天也学习了很多东西,近期在不断的整理,后续会总结分享的,感谢一个陌生网友的关怀,?,一个高更新的博客要跑路了,虽然技术很菜,分享的技术网上一大堆,但是经验是积累的,相信自己的努力? 最终会成为大牛的,加油,嘿嘿。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/8/13
articleCard.readMore

Go 语言实现 RPC 调用

RPC 在分布式计算,远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC 是一种服务器-客户端( Client/Server )模式,经典实现是一个通过 发送请求-接受回应 进行信息交互的系统。 wiki 维基百科 在这里引用一下维基百科对于 RPC 的解释, 可以针对与 HTTP 协议来比较分析,RPC 更适合于公司中大、中型项目分布式调用场景。 调用流程 客户端调用客户端 stub(client stub)。这个调用是在本地,并将调用参数 push 到栈(stack)中; 客户端 stub(client stub)将这些参数包装,并通过系统调用发送到服务端机器。打包的过程叫 marshalling。(常见方式:XML、JSON、二进制编码); 客户端本地操作系统发送信息至服务器。(可通过自定义TCP协议或HTTP传输); 服务器系统将信息传送至服务端stub(server stub); 服务端stub(server stub)解析信息。该过程叫 unmarshalling; 服务端stub(server stub)调用程序,并通过类似的方式返回给客户端。 RPC 与 HTTP 区别 RPC 调用实现的方式是和 HTTP 有异曲同工之处的,但是对于 RPC 与 HTTP 在 请求 / 响应中还是存在着差别的: HTTP 与 RPC 协议在实现上是不同的,大家都了解到 HTTP 原理就是 客户端请求服务端,服务端去响应并返回结果,但是 RPC 协议设计的时候采用的方式就是服务端给客户端提供 TCP 长连接服务,Client 端去调用 Server 提供的接口,实现特定的功能; RPC 可以同时提供同步调用及异步调用,而 HTTP 提供的方式就是同步调用,客户端会等待并接受服务端的请求处理的结果; RPC 服务设计可以提高代码编写过程中的解耦操作,提高代码的可移植性,每一个 服务可以设计成提供特定功能的小服务,客户端去调取远程的服务,而不用去关心远程是怎么实现的。 RPC 应用领域 大型网站的内部子系统设计; 为系统提供降级功能; 并发设计场景; 当然 RPC 也有缺点,每一个 RPC 服务都需要单独搭建,一旦服务出错或者更为严重的不提供支持,作为客户端的就会出现服务不可用,这对系统稳定性及可持续提供支持要求比较高,当然在设计过程中,这样也加大了对系统调试的难度,也就是说这种设计要求 RPC 服务的稳定性及正确性要求是比较大的。 实现代码 客户端实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( "demo/common" "fmt" "net/rpc" ) func main() { var args = common.Args{A: 32, B: 14} var result = common.Result{} var client, err = rpc.DialHTTP("tcp", "127.0.0.1:9090") if err != nil { fmt.Printf("connect rpc server failed, err:%v", err) } err = client.Call("MathService.Divide", args, &result) if err != nil { fmt.Printf("call math service failed, err:%v", err) } fmt.Printf("call RPC server success, result:%f", result.Value) } 服务端实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package main import ( "demo/common" "fmt" "net/http" "net/rpc" ) func main() { var ms = new(common.MathService) // 注册 RPC 服务 err := rpc.Register(ms) if err != nil { fmt.Printf("rpc server register faild, err:%s", err) } // 将 RPC 服务绑定到 HTTP 服务中去 rpc.HandleHTTP() fmt.Printf("server start ....") err = http.ListenAndServe(":9090", nil) if err != nil { fmt.Printf("listen and server is failed, err:%v\n", err) } fmt.Printf("server stop ....") } 功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package common import "errors" type Args struct { A, B float32 } type Result struct { Value float32 } type MathService struct {} func (s *MathService) Add (args *Args, result *Result) error{ result.Value = args.A + args.B return nil } func (s *MathService) Divide(args *Args, result *Result) error{ if args.B == 0 { return errors.New("arge.B is 0") } result.Value = args.A / args.B return nil } References 简述RPC原理实现 - 博客园 Http和RPC区别 远程过程调用 - 维基百科 直观讲解–RPC调用和HTTP调用的区别 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/8/1
articleCard.readMore

使用 GVM 工具管理 Go 版本

在 Go 项目开发中,团队要保持开发版本一致,怎么能够快速的安装及部署并且切换 Go 环境,在这里推荐一款工具 GVM ( Go Version Manager ),它可以便捷切换与自定义 Go Path 、Go Root 等参数,是一款实打实的多版本安装及管理利器。 GVM,类似于ruby 中的 RVM,java 中的 jenv(国产),可用于方便管理 Go 的版本,它有如下几个主要特性: 管理 Go 的多个版本,包括安装、卸载和指定使用 Go 的某个版本; 查看官方所有可用的 Go 版本,同时可以查看本地已安装和默认使用的 Go 版本; 管理多个 GOPATH,并可编辑 Go 的环境变量; 可将当前目录关联到 GOPATH; 可以查看 GOROOT 下的文件差异。 安装 Installing 1 bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) 或者,如果您使用的是 zsh,只需使用 zsh 更改 bash。 使用 GVM 使用 gvm 可以查看支持的操作: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ➜ ~ gvm Usage: gvm [command] Description: GVM is the Go Version Manager Commands: version - print the gvm version number get - gets the latest code (for debugging) use - select a go version to use (--default to set permanently) diff - view changes to Go root help - display this usage text implode - completely remove gvm install - install go versions uninstall - uninstall go versions cross - install go cross compilers linkthis - link this directory into GOPATH list - list installed go versions listall - list available versions alias - manage go version aliases pkgset - manage go packages sets pkgenv - edit the environment for a package set 安装 Go 版本 例如安装 go1.13 版本: 1 gvm install go1.13 查看 Go 版本 1 2 3 4 5 6 ➜ ~ gvm list gvm gos (installed) go1.12 => system 切换 Go 版本 1 gvm use go1.** 管理 Gopath 环境 GVM 提供了一个比较简单的工具 gvm pkgset 可以创建使用 GOPATH 环境: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ➜ ~ gvm pkgset = gvm pkgset * http://github.com/moovweb/gvm == DESCRIPTION: GVM pkgset is used to manage various Go packages == Usage gvm pkgset Command == Command create - create a new package set delete - delete a package set use - select where gb and goinstall target and link empty - remove all code and compiled binaries from package set list - list installed go packages 卸载 Uninstall 卸载某个安装好的 Go 版本: 1 gvm uninstall go1.13 开源代码 GVM 是一款使用 Shell 脚本实现的便捷工具,作为开源项目,推荐大家给一个 Star 支持。 https://github.com/moovweb/gvm 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/7/12
articleCard.readMore

Go 语言操作 MySQL 之 SQLX 包

SQLX 库 sqlx是 Go 的软件包,它在出色的内置database/sql软件包的基础上提供了一组扩展。 该库兼容 sql 原生包,同时又提供了更为强大的、优雅的查询、插入函数。 该库提供四个处理类型,分别是: sqlx.DB - 类似原生的sql.DB; sqlx.Tx - 类似原生的sql.Tx; sqlx.Stmt - 类似原生的 sql.Stmt, 准备 SQL 语句操作; sqlx.NamedStmt - 对特定参数命名并绑定生成 SQL 语句操作。 提供两个游标类型,分别是: sqlx.Rows - 类似原生的 sql.Rows, 从 Queryx 返回; sqlx.Row - 类似原生的 sql.Row, 从 QueryRowx 返回。 安装 SQLX 库 1 go get github.com/jmoiron/sqlx 使用操作 连接数据库 1 2 3 4 5 6 7 8 9 10 11 12 // 初始化数据库 func initMySQL() (err error) { dsn := "root:password@tcp(127.0.0.1:3306)/database" db, err = sqlx.Open("mysql", dsn) if err != nil { fmt.Printf("connect server failed, err:%v\n", err) return } db.SetMaxOpenConns(200) db.SetMaxIdleConns(10) return } SetMaxOpenConns 和 SetMaxIdleConns 分别为设置最大连接数和最大空闲数。 数据表达及引用 在这里提前声明一个用户结构体 user,将 *sqlx.DB 作为一个全局变量使用,当然也要提前引用 MySQL 的驱动包,如下设计: 1 2 3 4 5 6 7 8 9 10 11 12 13 import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB type user struct { Id int `db:"id"` Age int `db:"age"` Name string `db:"name"` } 查询操作 查询一行数据 查询一行数据使用 sqlx 库中的 Get 函数实现: 1 func (db *DB) Get(dest interface{}, query string, args ...interface{}) error dest 是用户声明变量接收查询结果,query 为查询 SQL 语句,args 为绑定参数的赋值。 1 2 3 4 5 6 7 8 9 10 11 // 查询一行数据 func queryRow() { sqlStr := "SELECT id, name, age FROM user WHERE id = ?" var u user if err := db.Get(&u, sqlStr, 1); err != nil { fmt.Printf("get data failed, err:%v\n", err) return } fmt.Printf("id:%d, name:%s, age:%d\n", u.Id, u.Name, u.Age) } 查询多行数据 而查询多行数据则使用的是Select 函数: 1 func (db *DB) Select(dest interface{}, query string, args ...interface{}) error 使用 Select 函数进行查询的时候,需要先声明一个结构体数组接收映射过来的数据: 1 2 3 4 5 6 7 8 9 10 11 12 13 // 查询多行数据 func queryMultiRow() { sqlStr := "SELECT id, name, age FROM user WHERE id > ?" var users []user if err := db.Select(&users, sqlStr, 0); err != nil { fmt.Printf("get data failed, err:%v\n", err) return } for i := 0; i < len(users); i++ { fmt.Printf("id:%d, name:%s, age:%d\n", users[i].Id, users[i].Name, users[i].Age) } } 插入、更新、删除操作 在 sqlx 库中,使用插入、更新、删除操作是和原生 sql 库实现是一致的,都是采用 Exec 函数来实现的: 插入操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 插入数据 func insertRow() { sqlStr := "INSERT INTO user(name, age) VALUES(?, ?)" result, err := db.Exec(sqlStr, "Meng小羽", 22) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } insertID, err := result.LastInsertId() if err != nil { fmt.Printf("get insert id failed, err:%v\n", err) return } fmt.Printf("insert data success, id:%d\n", insertID) } 更新操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 更新数据 func updateRow() { sqlStr := "UPDATE user SET age = ? WHERE id = ?" result, err := db.Exec(sqlStr, 22, 6) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } affectedRows, err := result.RowsAffected() if err != nil { fmt.Printf("get affected failed, err:%v\n", err) return } fmt.Printf("update data success, affected rows:%d\n", affectedRows) } 删除操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 删除一行 func deleteRow() { sqlStr := "DELETE FROM user WHERE id = ?" result, err := db.Exec(sqlStr, 4) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } affectedRows, err := result.RowsAffected() if err != nil { fmt.Printf("get affected failed, err:%v\n", err) return } fmt.Printf("delete data success, affected rows:%d\n", affectedRows) } 参数绑定 在库中提供最常用的就是NamedQuery和NamedExec函数,一个是执行对查询参数命名并绑定,另一个则是对 CUD 操作的查询参数名的绑定: NamedQuery 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 绑定查询 func selectNamedQuery() { sqlStr := "SELECT id, name, age FROM user WHERE age = :age" rows, err := db.NamedQuery(sqlStr, map[string]interface{}{ "age": 22, }) if err != nil { fmt.Printf("named query failed failed, err:%v\n", err) return } defer rows.Close() for rows.Next() { var u user if err := rows.StructScan(&u); err != nil { fmt.Printf("struct sacn failed, err:%v\n", err) continue } fmt.Printf("%#v\n", u) } } NamedExec 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 使用 named 方法插入数据 func insertNamedExec() { sqlStr := "INSERT INTO user(name, age) VALUES(:name, :age)" result, err := db.NamedExec(sqlStr, map[string]interface{}{ "name": "里斯", "age": 18, }) if err != nil { fmt.Printf("named exec failed, err:%v\n", err) return } insertId, err := result.LastInsertId() if err != nil { fmt.Printf("get last insert id failed, err:%v\n", err) return } fmt.Printf("insert data success, id:%d\n", insertId) } 事务操作 使用Begin函数、Rollback函数及Commit函数实现事务操作: 1 2 3 4 5 6 // 开启事务 func (db *DB) Begin() (*Tx, error) // 回滚事务 func (tx *Tx) Rollback() error // 提交事务 func (tx *Tx) Commit() error 示例代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 // 事务操作 func updateTransaction() (err error) { tx, err := db.Begin() if err != nil { fmt.Printf("transaction begin failed, err:%v\n", err) return err } defer func() { if p := recover(); p != nil { _ = tx.Rollback() panic(p) } else if err != nil { fmt.Printf("transaction rollback") _ = tx.Rollback() } else { err = tx.Commit() fmt.Printf("transaction commit") return } }() sqlStr1 := "UPDATE user SET age = ? WHERE id = ? " reuslt1, err := tx.Exec(sqlStr1, 18, 1) if err != nil { fmt.Printf("sql exec failed, err:%v\n", err) return err } rows1, err := reuslt1.RowsAffected() if err != nil { fmt.Printf("affected rows is 0") return } sqlStr2 := "UPDATE user SET age = ? WHERE id = ? " reuslt2, err := tx.Exec(sqlStr2, 19, 5) if err != nil { fmt.Printf("sql exec failed, err:%v\n", err) return err } rows2, err := reuslt2.RowsAffected() if err != nil { fmt.Printf("affected rows is 0\n") return } if rows1 > 0 && rows2 > 0 { fmt.Printf("update data success\n") } return } 开源项目 最后将此开源项目放在此处,大家要是感兴趣可以给这个开源项目一个 Star,感谢。 https://github.com/jmoiron/sqlx References http://jmoiron.github.io/sqlx/ sqlx库使用指南 - 李文周的博客 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/7/7
articleCard.readMore

Go 语言操作 MySQL 之 预处理

预处理 预处理是 MySQL 为了防止客户端频繁请求的一种技术,是对相同处理语句进行预先加载在 MySQL 中,将操作变量数据用占位符来代替,减少对 MySQL 的频繁请求,使得服务器高效运行。 在这里客户端并不是前台后台之间的 C/S 架构,而是后台程序对数据库服务器进行操作的 C/S 架构,这样就可以简要地理解了后台程序作为 Client 向 MySQL Server 请求并处理结果了。 普通 SQL 执行处理过程: 在客户端准备 SQL 语句; 发送 SQL 语句到 MySQL 服务器; 在 MySQL 服务器执行该 SQL 语句; 服务器将执行结果返回给客户端。 预处理执行处理过程: 将 SQL 拆分为结构部分与数据部分; 在执行 SQL 语句的时候,首先将前面相同的命令和结构部分发送给 MySQL 服务器,让 MySQL 服务器事先进行一次预处理(此时并没有真正的执行 SQL 语句); 为了保证 SQL 语句的结构完整性,在第一次发送 SQL 语句的时候将其中可变的数据部分都用一个数据占位符来表示; 然后把数据部分发送给 MySQL 服务端,MySQL 服务端对 SQL 语句进行占位符替换; MySQL 服务端执行完整的 SQL 语句并将结果返回给客户端。 预处理优点 预处理语句大大减少了分析时间,只做了一次查询(虽然语句多次执行); 绑定参数减少了服务器带宽,只需发送查询的参数,而不是整个语句; 预处理语句针对 SQL 注入是非常有用的,因为参数值发送后使用不同的协议,保证了数据的合法性。 Go 语言实现 在 Go 语言中,使用 db.Prepare() 方法实现预处理: 1 func (db *DB) Prepare(query string) (*Stmt, error) Prepare 执行预处理 SQL 语句,并返回 Stmt 结构体指针,进行数据绑定操作。 查询操作使用 db.Prepare() 方法声明预处理 SQL,使用 stmt.Query() 将数据替换占位符进行查询,更新、插入、删除操作使用 stmt.Exec() 来操作。 预处理查询示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 预处理查询数据 func prepareQuery() { sqlStr := "SELECT id,name,age FROM user WHERE id > ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare sql failed, err:%v\n", err) return } rows, err := stmt.Query(1) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } defer rows.Close() for rows.Next() { var u user err := rows.Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan data failed, err:%v\n", err) return } fmt.Printf("id:%d, name:%s, age:%d\n", u.id, u.name, u.age) } } 预处理更新示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 预处理更新数据 func prepareUpdate() { sqlStr := "UPDATE user SET age = ? WHERE id = ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare sql failed, err:%v\n", err) return } _, err = stmt.Exec(18, 2) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } fmt.Printf("prepare update data success") } 预处理插入示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 预处理更新数据 func prepareUpdate() { sqlStr := "UPDATE user SET age = ? WHERE id = ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare sql failed, err:%v\n", err) return } _, err = stmt.Exec(18, 2) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } fmt.Printf("prepare update data success") } 预处理删除示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 预处理删除数据 func prepareDelete() { sqlStr := "DELETE FROM user WHERE id = ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare sql failed, err:%v\n", err) return } result, err := stmt.Exec(3) n, err := result.RowsAffected() if err != nil { fmt.Printf("delete rows failed, err:%v\n", err) return } if n > 0 { fmt.Printf("delete data success") } else { fmt.Printf("delete data error") } } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/7/2
articleCard.readMore

Go 语言操作 MySQL 之 事务操作

事务 数据库事务( transaction )是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。 MySQL 存储引擎分类有 MyISAM、InnoDB、Memory、Merge等,但是其中最为常用的就是 MyISAM 和 InnoDB 两个引擎,这两个引擎中,支持事务的引擎就是 Innodb(MySQL 默认引擎),在创建数据库中要注意对应引擎。 这里可以看一下针对 MySQL 选择引擎的文章: 怎么优雅的选择 MySQL 存储引擎 事务 ACID 通常事务必须满足4个条件( ACID ):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。 条件 解释 原子性 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 一致性 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 隔离性 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。 持久性 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 Go 操作 MySQL 使用事务 Go语言中使用以下三个方法实现MySQL中的事务操作: 1 2 3 4 5 6 // 开始事务 func (db *DB) Begin() (*Tx, error) // 回滚事务 func (tx *Tx) Rollback() error // 提交事务 func (tx *Tx) Commit() error 示例代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // 事务更新操作 func transActionUpdate() { tx, err := db.Begin() if err != nil { if tx != nil { _ = tx.Rollback() } fmt.Printf("begin trans action failed, err:%v\n", err) return } sqlStr1 := "UPDATE user SET age = ? WHERE id = ?" result1, err := tx.Exec(sqlStr1, 20, 1) if err != nil { _ = tx.Rollback() fmt.Printf("exec failed, err:%v\n", err) return } n1, err := result1.RowsAffected() if err != nil { _ = tx.Rollback() fmt.Printf("exec result1.RowsAffected() failed, err:%v\n", err) return } sqlStr2 := "UPDATE user SET age = ? WHERE id = ?" result2, err := tx.Exec(sqlStr2, 20, 6) if err != nil { _ = tx.Rollback() fmt.Printf("exec failed, err:%v\n", err) return } n2, err := result2.RowsAffected() if err != nil { _ = tx.Rollback() fmt.Printf("exec result1.RowsAffected() failed, err:%v\n", err) return } if n1 == 1 && n2 == 1 { _ = tx.Commit() fmt.Printf("transaction commit success\n") } else { _ = tx.Rollback() fmt.Printf("transaction commit error, rollback\n") return } } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/7/2
articleCard.readMore

Go 语言操作 MySQL 之 CURD 操作

MySQL 是目前开发中最常见的关系型数据库,使用 Go 语言进行操控数据库需要使用 Go 自带database/sql和驱动go-sql-driver/mysql来实现, 创建好 Go 项目,需要引用驱动依赖: 1 go get -u github.com/go-sql-driver/mysql 使用 MySQL 驱动: 1 func Open(driverName, dataSourceName string) (*DB, error) Open 打开一个 dirverName 指定的数据库,dataSourceName 指定数据源,一般至少包括数据库文件名和其它连接必要的信息。 初始化连接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var db *sql.DB //声明一个全局的 db 变量 // 初始化 MySQL 函数 func initMySQL() (err error) { dsn := "root:password@tcp(127.0.0.1:3306)/dbname" db, err = sql.Open("mysql", dsn) if err != nil { return } err = db.Ping() if err != nil { return } return } func main() { // 初始化 MySQL err := initMySQL() if err != nil { panic(err) } defer db.Close() } 初始化连接 MySQL 后需要借助 db.Ping 函数来判断连接是否成功。 SetMaxOpenConns 1 func (db *DB) SetMaxOpenConns(n int) SetMaxOpenConns设置与数据库建立连接的最大数目。 如果 n 大于 0 且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。 如果 n <= 0,不会限制最大开启连接数,默认为0(无限制)。 SetMaxIdleConns 1 func (db *DB) SetMaxIdleConns(n int) SetMaxIdleConns设置连接池中的最大闲置连接数。 如果 n 大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。 如果 n <= 0,不会保留闲置连接。 CURD 进行 CURD 操作,需要对数据库建立连接,同时有供操作的数据(数据库与数据表): 初始化数据 建立数据库 sql_demo 1 2 CREATE DATABASE sql_demo; USE sql_demo; 创建数据表 user 1 2 3 4 5 6 CREATE TABLE `user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `name` VARCHAR(20) DEFAULT '', `age` INT(11) DEFAULT '0', PRIMARY KEY(`id`) )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 查询数据 SELECT 便于接收数据,定义一个 user 结构体接收数据: 1 2 3 4 5 type user struct { id int age int name string } 查询一行数据 db.QueryRow() 执行一次查询,并期望返回最多一行结果(即 Row )。QueryRow 总是返回非 nil 的值,直到返回值的 Scan 方法被调用时,才会返回被延迟的错误。(如:未找到结果) 1 func (db *DB) QueryRow(query string, args ...interface{}) *Row 实例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // 查询一行数据 func queryRowDemo() (u1 *user, err error) { // 声明查询语句 sqlStr := "SELECT id,name,age FROM user WHERE id = ?" // 声明一个 user 类型的变量 var u user // 执行查询并且扫描至 u err = db.QueryRow(sqlStr, 1).Scan(&u.id, &u.age, &u.name) if err != nil { return nil, err } u1 = &u return } func main() { // 初始化 MySQL err := initMySQL() if err != nil { panic(err) } defer db.Close() u1, err := queryRowDemo() if err != nil { fmt.Printf("err:%s", err) } fmt.Printf("id:%d, age:%d, name:%s\n", u1.id, u1.age, u1.name) } 结果如下: 1 id:1, age:111, name:22 多行查询 db.Query()执行一次查询,返回多行结果(即 Rows ),一般用于执行 select 命令。参数 args 表示 query 中的占位参数。 1 func (db *DB) Query(query string, args ...interface{}) (*Rows, error) 实例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 查询多行数据 func queryMultiRowDemo() { sqlStr := "SELECT id,name,age FROM user WHERE id > ?" rows, err := db.Query(sqlStr, 0) if err != nil { fmt.Printf("query data failed,err:%s\n", err) return } // 查询完数据后需要进行关闭数据库链接 defer rows.Close() for rows.Next() { var u user err := rows.Scan(&u.id, &u.age, &u.name) if err != nil { fmt.Printf("scan data failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age) } } 执行结果: 1 2 id:1 name:111 age:22 id:3 name:张三 age:22 使用 rows.Next() 循环读取结果集中的数据。 增加数据 INSERT 增加、删除、更新操作均使用 Exec 方法。 1 func (db *DB) Exec(query string, args ...interface{}) (Result, error) 实例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 增加一行数据 func insertRowDemo() { sqlStr := "INSERT INTO user(name, age) VALUES(?, ?)" result, err := db.Exec(sqlStr, "小羽", 22) if err != nil { fmt.Printf("insert data failed, err:%v\n", err) return } id, err := result.LastInsertId() if err != nil { fmt.Printf("get insert lastInsertId failed, err:%v\n", err) return } fmt.Printf("insert success, id:%d\n", id) } 执行结果: 1 insert success, id:4 更新数据 UPDATE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 更新一组数据 func updateRowDemo() { sqlStr := "UPDATE user SET age = ? WHERE id = ?" result, err := db.Exec(sqlStr, 22, 1) if err != nil { fmt.Printf("update data failed, err:%v\n", err) return } n, err := result.RowsAffected() if err != nil { fmt.Printf("get rowsaffected failed, err:%v\n", err) return } fmt.Printf("update success, affected rows:%d\n", n) } 删除数据 DELETE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 删除一行数据 func deleteRowDemo() { sqlStr := "DELETE FROM user WHERE id = ?" result, err := db.Exec(sqlStr, 2) if err != nil { fmt.Printf("delete data failed, err:%d\n", err) return } n, err := result.RowsAffected() if err != nil { fmt.Printf("get affected failed, err:%v\n", err) return } fmt.Printf("delete success, affected rows:%d\n", n) } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/7/1
articleCard.readMore

Go 语言基础 数组、切片、映射

在 Go 语言中,为便于存储及管理用户数据,其数据结构设计分为数组 Array、切片 Slice、映射 Map 三种结构。 近期又看了 Go 语言基础的内容,看了一下这三种结构实现的原理: 数组 Array 数组是切片和映射的基础数据结构; 数组是长度固定的数据类型并且在内存中也是连续分配的,固索引数组数据速度是非常快的; 声明数组时需要指定数组存储的类型及数量(数组的长度); 数组变量的类型包括数组长度和元素的类型,只有两部分都相同的数组才可相互赋值。 创建及初始化 一旦声明了数组,其本身的数据类型及长度都是不可以进行变更。 1 2 3 4 5 6 7 8 9 // 使用数组字面量声明数组 array := [5]int{1, 2, 3, 4, 5} // 自动推导长度声明数组 array := [...]int{1, 2, 3, 4, 5, 6} // 使用 ... 代替长度,根据初始化元素个数推导 // 声明数组并指定特定元素值 array := [5]int{1:10, 2:20} 指针类型 数组元素的类型可以为任何内置类型,也可以是某种结构类型,也可以是指针类型。 1 2 3 4 5 6 7 // 声明一个元素长度为 3 的指向字符串的指针数组 var array1 [3]*string // 为指针数组指定元素 *array1[0] = "demo0" *array1[1] = "demo1" *array1[2] = "demo2" 多维数组 数组本身是一维数据,多维数组是由多个数组组合而来的。 1 2 3 4 5 6 // 声明一个二维数组 var array = [3][2]int // 声明了一个两个维度为 3 和 2 的元素 // 初始化二维数组 var array = [3][2]int{ {1, 2}, {3, 4}, {5, 6}} 在函数间传递数组:由于在函数间传递变量时,传递的总是变量的值的副本,所以在传递数组变量时将拷贝整个数组!在定义函数时,对于较大的数据类型应该把参数设计为指针类型,这样在调用函数时,只需在栈上分配给每个指针8字节的内存,但这意味着会改变指针指向的值(共享的内存),其实大部分情况下应该使用切片类型,而不是数组。 切片 Slice 切片 slice 是引用类型,它引用了其指针字段所指向的底层数组的一部分或全部; 切片是围绕动态数组的概念构建的; 切片的动态增长是通过 append 来实现的; 缩小则是通过对它再次切片来实现,通过再次切片获得的新切片将和原切片共享底层数组,它们的指针指向同一个底层数组。 创建及初始化 切片类型有3个字段: 指针:指向切片所包含的第一个元素在底层数组中的地址; 长度:切片所包含的底层数组的元素的个数(切片可访问的元素的个数); 容量:切片允许增长到的最大元素个数,即底层数组的长度。 make 和切片字面量 1 2 3 4 5 6 // 使用 make 创建一个切片 slice := make([]int, 3) // 创建一个具有长度和容量的切片 slice := make([]int, 1, 6) // 长度为 1,容量为 6 个元素 nil 和空切片 1 2 3 4 5 6 // nil 字符串切片 var slice []string // 空切片 slice := []int{} // 空的整形切片 由于切片只是引用了底层数组,底层数组的数据并不属于切片本身,所以一个切片只需要 24字节的内存(在 64位机器上):指针字段 8字节、长度字段 8字节、容量字段 8字节。所以在函数之间直接传递切片是高效的,只需分配 24字节的栈内存。 len函数可返还切片的长度、cap函数可返还切片的容量。 映射 Map 映射 map 是用来存储一系列的无序键值对; 映射是无序的集合,其实现使用了散列表; 映射的散列表包含一组桶,每个桶里存储着一部分键值对; 映射内部使用了两个数组: 第一个数组:存储着用于选择桶的散列键的高八位值,该数组用于区分每个键值对要存在哪个桶里; 第二个数组:每个桶里都有一个字节数组,先依次存储了该桶里的所有键,之后存储了该桶的所有值; 创建及初始化 1 2 3 4 5 6 7 8 9 10 11 12 // 创建一个映射 存储学生信息 students := map[string]string{ "name" : "mengxiaoyu", "age" : "22", "sex" : "boy", "hobby": "pingpang", } // 显示映射所有信息 for key, value := range students{ fmt.printf("key:%s, \t value:%s\n", key, value); } 遍历映射的键值对时的顺序是随机,若要有序的获得映射的键值对,则需要先遍历出映射的键存到一个切片中,然后排序该切片,最后遍历该切片,按切片中元素的顺序去映射中取对应的值。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/6/17
articleCard.readMore

Go 语言使用 net 包实现 Socket 网络编程

TCP/IP TCP/IP 传输协议,即传输控制/网络协议,也叫作网络通讯协议。它是在网络的使用中的最基本的通信协议。TCP/IP 传输协议对互联网中各部分进行通信的标准和方法进行了规定。并且,TCP/IP 传输协议是保证网络数据信息及时、完整传输的两个重要的协议。TCP/IP 传输协议是严格来说是一个四层的体系结构,应用层、传输层、网络层和数据链路层都包含其中。 TCP/IP 协议簇常见通信协议 应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等 传输层:TCP,UDP 网络层:IP,ICMP,OSPF,EIGRP,IGMP 数据链路层:SLIP,CSLIP,PPP,MTU Socket 两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用 PID 来唯一标示一个进程,但 PID 只在本地唯一,网络中的两个进程 PID 冲突几率很大,这时候我们需要另辟它径了,我们知道 IP 层的 ip 地址可以唯一标示主机,而 TCP 层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用 ip 地址+协议+端口号唯一标示网络中的一个进程。 能够唯一标示网络中的进程后,它们就可以利用 socket 进行通信了,什么是socket 呢?我们经常把 socket 翻译为套接字,socket 是在应用层和传输层之间的一个抽象层,它把 TCP/IP 层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。 socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。 Socket 是实现“打开–读/写–关闭”这样的模式,以使用 TCP 协议通讯的 socket 为例。如下图所示: TCP 实现 一个 TCP 客户端进行 TCP 通信的流程如下: 建立与服务端的链接 进行数据收发 关闭链接 server 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package main import ( "bufio" "fmt" "net" ) func process(conn net.Conn) { // 处理完关闭连接 defer conn.Close() // 针对当前连接做发送和接受操作 for { reader := bufio.NewReader(conn) var buf [128]byte n, err := reader.Read(buf[:]) if err != nil { fmt.Printf("read from conn failed, err:%v\n", err) break } recv := string(buf[:n]) fmt.Printf("收到的数据:%v\n", recv) // 将接受到的数据返回给客户端 _, err = conn.Write([]byte("ok")) if err != nil { fmt.Printf("write from conn failed, err:%v\n", err) break } } } func main() { // 建立 tcp 服务 listen, err := net.Listen("tcp", "127.0.0.1:9090") if err != nil { fmt.Printf("listen failed, err:%v\n", err) return } for { // 等待客户端建立连接 conn, err := listen.Accept() if err != nil { fmt.Printf("accept failed, err:%v\n", err) continue } // 启动一个单独的 goroutine 去处理连接 go process(conn) } } client 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package main import ( "bufio" "fmt" "net" "os" "strings" ) func main() { // 1、与服务端建立连接 conn, err := net.Dial("tcp", "127.0.0.1:9090") if err != nil { fmt.Printf("conn server failed, err:%v\n", err) return } // 2、使用 conn 连接进行数据的发送和接收 input := bufio.NewReader(os.Stdin) for { s, _ := input.ReadString('\n') s = strings.TrimSpace(s) if strings.ToUpper(s) == "Q" { return } _, err = conn.Write([]byte(s)) if err != nil { fmt.Printf("send failed, err:%v\n", err) return } // 从服务端接收回复消息 var buf [1024]byte n, err := conn.Read(buf[:]) if err != nil { fmt.Printf("read failed:%v\n", err) return } fmt.Printf("收到服务端回复:%v\n", string(buf[:n])) } } UDP 实现 UDP 协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。 server 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package main import ( "fmt" "net" ) func main() { // 建立 udp 服务器 listen, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 9090, }) if err != nil { fmt.Printf("listen failed error:%v\n", err) return } defer listen.Close() // 使用完关闭服务 for { // 接收数据 var data [1024]byte n, addr, err := listen.ReadFromUDP(data[:]) if err != nil { fmt.Printf("read data error:%v\n", err) return } fmt.Printf("addr:%v\t count:%v\t data:%v\n", addr, n, string(data[:n])) // 发送数据 _, err = listen.WriteToUDP(data[:n], addr) if err != nil { fmt.Printf("send data error:%v\n", err) return } } } client 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package main import ( "fmt" "net" ) func main() { // 建立服务 listen, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 9090, }) if err != nil { fmt.Printf("listen udp server error:%v\n", err) } defer listen.Close() // 发送数据 sendData := []byte("Hello server") _, err = listen.Write(sendData) // 发送数据 if err != nil { fmt.Println("发送数据失败,err:", err) return } // 接收数据 data := make([]byte, 4096) n, remoteAddr, err := listen.ReadFromUDP(data) // 接收数据 if err != nil { fmt.Println("接收数据失败,err:", err) return } fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n) } 参考文章 Go语言基础之网络编程 - 李文周的个人博客 简单理解Socket - 谦行 - 博客园 TCP/IP协议 - 百度百科 详解TCP连接的“三次握手”与“四次挥手”(下) 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/5/25
articleCard.readMore

Linux Vim 命令手记

经常使用 Linux 的同学在编辑文本文件的时候一定知道 Vim 这一款神器,它代替 Linux 默认原装的 Vi 编辑器,它的快捷键可以使你在操控文件的时候如庖丁解牛般流畅,博主目前只会简单的命令,感觉不能满足开发需求,今天特地的学习了一下,并且针对于常用的命令做了整理及汇总: 开源项目 首先,Vim 编辑器是一个开源的项目,按照惯例,请给开发者一个 Star 奖励: https://github.com/vim/vim 常用命令示意图 常用命令参考 快捷键 操作说明 Ctrl + f 屏幕向下移动一页,类似 Page Down 按键 Ctrl + b 屏幕向上移动一页,类似 Page Up 按键 0 或 Home 键 移动到这一行最前面的字符处 $ 或 End 键 移动到这一行最后面的字符处 G 移动到这个文件的最后一行 gg 移动到这个文件的第一行,相当于 1G N[Enter] N 为数字。光标向下移动 N 行 /word 向下寻找一个名称为 word 的字符串 ?word 向上寻找一个名称为 word 的字符串 n 搭配查找 word 字符串使用,代表重复前一个查找的操作。例:如果前一个命令执行了 /word 命令去向下查找 word 这个字符串,当按下 n 后,会继续向下查找 word 这个字符串。 N 搭配查找 word 字符串使用,代表重复前一个查找的操作(反向)。 :n1,n2s/word1/word2/g 将此文本中的 word1字符串 替换为 word2 字符串 :1,$s/word1/word2/gc 将此文本中的 word1字符串 替换为 word2 字符串【给用户 confim提示】 x, X 在一行字符中,x为向后删除一个字符,X为向前删除一个字符 dd 删除光标所在那一行 ndd n为数字,删除光标所在向下n行 yy 复制光标所在那一行 nyy n为数字,复制光标所在向下n行 p, P p将已经复制的数据在光标下一行粘贴 P将已经复制的数据在光标上一行粘贴 u 复原前一个操作 Ctrl + r 重做上一个操作 . 重复上一个操作 模式切换 快捷键 操作说明 i, I 进入插入模式(Insert mode): i为目前光标所在处插入,I为在目前行所在的第一个非空格符处插入。 a, A 进入插入模式(Insert mode): a为目前光标的下一个字符处插入,A为在目前行所在的最后一个字符处开始插入。 o,O 进入插入模式(Insert mode): o为在目前光标所在下一行插入一个新行,O为在目前光标所在上一行插入一个新行。 r,R 进入替换模式(Replace mode):r 只会替换光标所在的那一个字符一次,R 会替换光标所在的文字,直到按下 [esc] 键。 [esc] 退出编辑模式 基础操作 快捷键 操作说明 快捷键 操作说明 :w 将编辑的文件写入磁盘文件中去。 :q! 强制退出编辑,且不保存操作。 :q 退出编辑,进入到命令行模式中去。 :wq 保存且退出编辑。 :wq! 强制保存且退出编辑。 Vim 环境修改。 :set nu 显示行号,设置后会在没有行前面前缀对应行号。 :set nonu 与:set nu相反,取消行号显示 键盘标识 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/5/16
articleCard.readMore

怎么优雅的选择 MySQL 存储引擎

对于数据库这一块询问比较多的就是在 MySQL 中怎么去选择一种合适当前业务需求的存储引擎,而 MySQL 中支持的存储引擎又有很多种,那么 MySQL 中分别又有那些,怎么优雅的使用呢? 划分引擎原因 在文件系统中,MySQL 将每个数据库(也可以称之为 schema )保存为数据目录下的一个子目录。创建表时,MySQL 会在数据库子目录下创建一个和表同名的 .frm 文件保存表的定义。例如创建一个名为 DebugTable 的表,MySQL 会在 DebugTable.frm 文件中保存该表的定义。 因为 MySQL 使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体的平台密切相关。在 Windows 系统中,大小写是不敏感的;而在类 Unix 系统中则是敏感的。不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 MySQL 服务层wk统一处理的。 查看支持引擎 想了解 MySQL 中支持的引擎的情况,可以使用如下命令查看: 1 show engines; 结果如下(MySQL版本:Ver 8.0.19): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mysql> show engines; +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ | Engine | Support | Comment | Transactions | XA | Savepoints | +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ | FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL | | MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO | | InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES | | PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO | | MyISAM | YES | MyISAM storage engine | NO | NO | NO | | MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO | | BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO | | CSV | YES | CSV storage engine | NO | NO | NO | | ARCHIVE | YES | Archive storage engine | NO | NO | NO | +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ 9 rows in set (0.00 sec) 存储引擎分类 MySQL 存储引擎分类有 MyISAM、InnoDB、Memory、Merge等,可以看上面表中列出的支持引擎,但是其中最为常用的就是 MyISAM 和 InnoDB 两个引擎,其中针对于以上讲到的存储引擎,如下表进行对比: 对比项目 MyISAM InnoDB Memory Merge 存储限制 256TB 64TB RAM 内存表 / 是否支持事务 否 是 否 否 是否支持全文索引 是 否 否 否 是否支持数索引 是 是 是 否 是否支持哈希索引 否 否 是 否 是否支持数据缓存 否 是 否 否 是否支持外键索引 否 是 否 否 备注 支持事务,行级锁定和外键 指向MyISAM表操作 MyISAM 与 InnoDB 区别 两种类型最主要的差别是InnoDB支持事务处理与外键和行级锁。 InnoDB 可借由事务日志( Transaction Log )来恢复程序崩溃( crash ),或非预期结束所造成的数据错误; 而 MyISAM 遇到错误,必须完整扫描后才能重建索引,或修正未写入硬盘的错误。 InnoDB 的修复时间,一般都是固定的,但 MyISAM 的修复时间,则与数据量的多寡成正比。 相对而言,随着数据量的增加,InnoDB 会有较佳的稳定性。 MyISAM 必须依靠操作系统来管理读取与写入的缓存,而 InnoDB 则是有自己的读写缓存管理机制。( InnoDB 不会将被修改的数据页立即交给操作系统)因此在某些情况下,InnoDB 的数据访问会比 MyISAM 更有效率。 InnoDB 目前并不支持 MyISAM 所提供的压缩与 terse row formats(简洁的行格式) ,所以对硬盘与高速缓存的使用量较大。 当操作完全兼容 ACID(事务)时,虽然 InnoDB 会自动合并数笔连接,但每次有事务产生时,仍至少须写入硬盘一次,因此对于某些硬盘或磁盘阵列,会造成每秒 200 次的事务处理上限。 若希望达到更高的性能且保持事务的完整性,就必使用磁盘缓存与电池备援。 当然 InnoDB 也提供数种对性能冲击较低的模式,但相对的也会降低事务的完整性。而MyISAM则无此问题,但这并非因为它比较先进,这只是因为它不支持事务。 应用场景 MyISAM 管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的 SELECT 查询,那么 MyISAM 是更好的选择。 InnoDB 用于事务处理应用程序,具有众多特性,包括 ACID 事务支持。如果应用中需要执行大量的 INSERT 或 UPDATE 操作,则应该使用 InnoDB,这样可以提高多用户并发操作的性能。 参考文章 Mysql 存储引擎的区别和比较 - zgrgfr - CSDN Mysql的存储引擎之:MERGE 存储引擎 - 翔之天空 - CSDN MySQL存储引擎之 Merge 引擎 MySQL存储引擎 - MyISAM与InnoDB区别 - Rocky - 知乎 MySQL引擎介绍 - 慕课网 - 知乎 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/5/1
articleCard.readMore

搭建流媒体服务器 PingOS 平台搭建

近期由于工作原因需要更换公司原有 RTMP 协议推流,由于 Flash 插件今年年底就淘汰使用,并且一直在寻找一种并发好、延时低、同时便于回放功能的应用,在网上找到了基于Nginx + FFmpeg 推流的解决方案,可以实现 HLS 协议推流,看项目介绍可以实现 HLS+ 协议,这个工具安装比较便捷。 首先介绍一下这个开源的项目,欢迎给他们 star,谢谢。 https://github.com/pingostack/pingos 官网地址:https://pingos.io/ 安装 项目文档:https://pingos.io/docs/zh/quick-start 在官方网站的项目文档中讲解的不是很清晰,特别是针对新手来说是有一定的难度,我这里使用的是 Linux CentOS 7.4 64位环境,需要提前安装 Git 应用,这个就不详细讲解了,接下来讲一下如何安装: 1 下载源码 1 git clone https://github.com/pingostack/pingos.git 2 快速安装 1 2 cd pingos ./release.sh -i 3 启动服务 1 2 cd /usr/local/pingos/ ./sbin/nginx 配置 一般情况下,安装完毕 PingOS 后就可以使用了,通过配置文件可以看到 nginx 占用端口为80,rtmp 端口占用为1935 。 但是在实际情况下,80 端口一般是使用于 HTTP 等服务,所以说尽量将服务端口设置为非 80 端口,由于使用了阿里云,可以关闭防火墙,同时配置安全组策略将 8080 入端口设置为允许状态。 下面是修改好的配置文件,位置为:/usr/local/pingos/conf/nginx.conf: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 user root; daemon on; master_process on; worker_processes 1; #worker_rlimit 4g; #error_log logs/error.log; #error_log logs/error.log notice; error_log logs/error.log info; worker_rlimit_nofile 102400; worker_rlimit_core 2G; working_directory /tmp; #pid logs/nginx.pid; events { # use epoll; worker_connections 1024; multi_listen unix:/tmp/http 8080; multi_listen unix:/tmp/rtmp 1935; } stream_zone buckets=1024 streams=4096; rtmp { log_format log_bandwidth '{"app":"$app","name":"$name","bitrate":$bitrate,"args":"$args","timestamp":$ntp,"ts":"$time_local","type":"$command","remote_addr":"$remote_addr","domain":"$domain"}'; access_log logs/bandwidth.log log_bandwidth trunc=60s; server { listen 1935; serverid 000; out_queue 2048; server_name localhost; application live { rtmp_auto_pull on; rtmp_auto_pull_port unix:/tmp/rtmp; # live_record on; # live_record_path /tmp/record; # recorder r1{ # record all; # record_path /tmp/record; # } # exec_publish bash -c "ffmepg -i rtmp://127.0.0.1/live/$name -c copy /tmp/mp4/$name-$starttime.mp4"; live on; hls on; hls_path /tmp/hls; hls_fragment 4000ms; # hls_max_fragment 6000ms; hls_playlist_length 12000ms; hls2memory on; mpegts_cache_time 20s; hls2_fragment 1300ms; hls2_max_fragment 1600ms; hls2_playlist_length 3900ms; wait_key on; wait_video on; cache_time 3s; low_latency off; fix_timestamp 2s; # h265 codecid, default 12 hevc_codecid 12; } } } http { include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_X-Forwarded-For" "$http_X-Real-IP" "$host"'; access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #reset_server_name www.test1.com www.test2.com; #gzip on; server { listen 8080; location /rtmp_stat { rtmp_stat all; rtmp_stat_stylesheet /stat.xsl; } location /xstat { rtmp_stat all; } location /sys_stat { sys_stat; } location /control { rtmp_control all; } location /live { flv_live 1935; add_header 'Access-Control-Allow-Origin' '*'; add_header Cache-Control no-cache; } location /ts { ts_live 1935 app=live; } location /hls { # Serve HLS fragments types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /tmp; add_header Cache-Control no-cache; add_header 'Access-Control-Allow-Origin' '*'; } location /hls2 { hls2_live 1935 app=live; add_header 'Access-Control-Allow-Origin' '*'; add_header Cache-Control no-cache; } location / { chunked_transfer_encoding on; root html/; } } } 修改完配置之后需要进行 nginx 服务重新载入等操作。 命令 1 2 3 4 5 6 7 8 9 10 11 # 进入到 PingOS 应用目录,下面所有操作皆以此目录下进行 cd /usr/local/pingos/ # 开启 nginx 服务器 ./sbin/nginx # 检查 nginx 配置语法是否正确 ./sbin/nginx -t # 重新加载 nginx 配置 ./sbin/nginx -s reload # 停止 nginx 服务器 ./sbin/nginx -s stop 推流 配置好服务器,可以看一下流媒体服务器推流效果,这里我是用的是 OBS 推流应用,推流端使用的是 RTMP 协议,在播放端使用的是 hls+ 协议。 这里给大家提供两个官方推荐查看推流效果的地址,也是应用提供的 Web 页面: http://ip地址:端口/h5player/flv 无插件播放http-flv直播流 http://ip地址:端口/rtmp_stat 查看当前服务器推流统计数据 播放地址:http://ip地址:端口/hls2/流名.m3u8 参考 PingOS 项目参考 怎么搭建hls低延时直播(lowlatency hls)- 知乎 最后,这是一个系列的文章,后续还有针对 PingOS 流媒体服务还有对应优化,敬请关注 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/4/3
articleCard.readMore

数据结构 浅谈归并排序

在 v2ex 社区看到有人提问怎么把十万个电话号码排出出现次数最多的十个电话号码,我看到这个问题的时候第一时间想到的是将十万个电话号码读出来放到 Redis 中,之后做一个动态计数器,使用 foreach 函数对这个电话号码进行遍历,以电话号码为索引 key,计数器 value 进行自增,最后求出最多的电话号码,这样最后时间复杂度为 O(n),不是一个好的解决方案,之后我看到评论区,有人提出使用归并排序,原理是一样的,不过可以将十万个电话号码平均分成十组,之后每组查找电话号码最多的十个号码,最后将十组最多的号码取出来再次进行相加排序,最后得到的最多的十个号码就是十万个电话号码中出现次数最多的号码。 看到这个问题,我不由得想到了我刚来去某浪面试的时候,面试官问我的问题和这个问题基本一致,不过是数的基数比较大,当时的我解决方案和现在我想的一样,很遗憾,没有结果,不得不说技术还是太菜了。之后我查了一下归并排序是采用的分治法的思想,即将一个问题分为若干个小的子问题进行解决,最后问题的解就是子问题结果的解的合并,接下来就详细的了解一下归并排序吧! 基本思想 归并排序 mergesort,是创建在归并操作上的一种有效的排序算法,效率为O(nlogn)。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。 采用分治思想,将一个问题拆分为若干个问题,之后将若干个问题解决,最后将若干个结果进行合并,即最终结果。 分治合并 在合并结果阶段,可以看到两个子结果的求解数组为[1, 2, 6] 和 [3, 4, 5],将子数组合并排序为 [1, 2, 3, 4, 5, 6]。 算法实现 在这里使用的是 PHP,其实算法思想一致,用啥语言都可以实现,不过一种语言有一种语言的语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <?php class MergeSort { /** * 初始化函数 * MergeSort constructor. * @param array $arr */ public function __construct(array $arr) { // 设置初始数组,左侧下标,及数组总下标大小 $this->mSort($arr, 0, count($arr) - 1); print_r($arr); } /** * 递归拆分数组 * @param array $arr 原始数组 * @param int $left 左侧索引下标 * @param int $right 右侧索引下标 */ public function mSort(array &$arr, int $left, int $right) { if ($left < $right) { $center = floor(($left + $right) / 2); $this->mSort($arr, $left, $center); $this->mSort($arr, $center + 1, $right); $this->mergeArray($arr, $left, $right, $center); } } /** * 合并数组 * @param array $arr * @param int $left * @param int $right * @param int $center */ public function mergeArray(array &$arr, int $left, int $right, int $center) { echo 'sort before:' . $left . ' - ' . $center . ' - ' . $right . ' - ' . implode(',', $arr) . "\n"; $left_i = $left; $right_i = $center + 1; $temp = []; while ($left_i <= $center && $right_i <= $right) { // 当数组A和数组B都没有越界时 if ($arr[$left_i] < $arr[$right_i]) { $temp[] = $arr[$left_i++]; } else { $temp[] = $arr[$right_i++]; } } // 判断 数组A内的元素是否都用完了,没有的话将其全部插入到 temp 数组内: while ($left_i <= $center) { $temp[] = $arr[$left_i++]; } // 判断 数组B内的元素是否都用完了,没有的话将其全部插入到 temp 数组内: while ($right_i <= $right) { $temp[] = $arr[$right_i++]; } // 将 $arr 内排序好的部分,写入到 $arr 内: for ($i = 0, $len = count($temp); $i < $len; $i++) { $arr[$left + $i] = $temp[$i]; } echo 'sort after :' . $left . ' - ' . $center . ' - ' . $right . ' - ' . implode(',', $arr) . "\n"; } } $arr = [2, 1, 6, 3, 5, 4]; new MergeSort($arr); 输出结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 sort before:0 - 0 - 1 - 2,1,6,3,5,4 sort after :0 - 0 - 1 - 1,2,6,3,5,4 sort before:0 - 1 - 2 - 1,2,6,3,5,4 sort after :0 - 1 - 2 - 1,2,6,3,5,4 sort before:3 - 3 - 4 - 1,2,6,3,5,4 sort after :3 - 3 - 4 - 1,2,6,3,5,4 sort before:3 - 4 - 5 - 1,2,6,3,5,4 sort after :3 - 4 - 5 - 1,2,6,3,4,5 sort before:0 - 2 - 5 - 1,2,6,3,4,5 sort after :0 - 2 - 5 - 1,2,3,4,5,6 Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6 ) 结论 归并排序比较占用内存,但却是一种效率高且稳定的算法。归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。 参考文章 图解排序算法(四)之归并排序 - 博客园 归并排序 - 维基百科,自由的百科全书 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/3/30
articleCard.readMore

记一次不太正规的装机实践

早就想着装一台组装机,来实操一下高中学的计算机组装与维修 ?,哈哈,开玩笑,想着组装一台电脑是我早有打算的事情,凭借着在学校实验室的时候帮助老师修理百十来台电脑的经验也可以轻松应对,正好家里人有装电脑的需求,本想着装机是一件比较愉快的事情,实时是激动的心、残废的手,装机翻车了,不过整体流程还可以,最后也正常的运行了起来,听我细细道来吧。 配置 朋友出价,我出配置,4000左右机型,性能还可以。 配件名称 型号 价格 CPU 英特尔(Intel)i5 9400F 酷睿六核 盒装CPU处理器 ¥1199 主板 技嘉(GIGABYTE)B365 M AORUS ELITE ¥669 显卡 七彩虹(Colorful)iGame GeForce GTX 1650 SUPER AD Special OC 4G 1530-1755MHz ¥1279 内存条 金士顿(Kingston) DDR4 2666 8GB 台式机内存条 骇客神条 Fury雷电系列 ¥359 SSD 东芝(TOSHIBA)250G SSD固态硬盘 M.2接口(NVME协议) RC500系列 ¥409 电源 鑫谷(Segotep)额定350W 核动力-巡洋舰 Q5 电源 ¥139 散热器 酷冷至尊(Cooler Master) 暴雪T400i 风冷散热器 ¥89.9 机箱 爱国者(aigo)炫影黑京东专供版 电脑机箱(配3把发光风扇/支持ATX主板/蜂窝玻璃面板/背线) ¥229 总价 ¥4322.90 包装 不得不说,京东快递就是很快,不过,快归快,3000米,抱着这三个大箱子往住的地方跑,最后手抖不停???,看来要健身减肥了。 打开了纸箱,买的配件都显露了出来,在床上摆拍了一下。 很壮观,也很有气势,哈哈,接下来就进行拆解组装了………… 组装 一般组装都讲究顺序,作为从不看产品使用说明的我,上来就装起了主板 ?,看到 CPU 和散热风扇在孤零零的躺着,emmm,拆出来吧,唉,就这样才把 CPU 放上,装上了内存,固态 SSD,和散热器,由于太过锋利,又没有防护措施,唉,挂了彩。 此处省略弱鸡的我装机,省略500子,最后终于安装成功。 给大家一个实践得来的建议,装机顺序,一定要记好: 安装 CPU 及散热风扇; 安装内存条、M.2 接口类型的固态; 安装主板,固定扩展插槽位置对齐; 安装电源; 安装显卡; 从背部走线,对应主板、CPU供电插槽、显卡供电插槽、音视频,USB2.0/3.0 、散热器供电插槽插上对应的线,同学们,看说明书; 开机,测试安装如何; 可以的话就整理线,用扎带固定好,关上机箱,就可以喽。 经过以上安装步骤,终于安装完毕,请看结果: 插上电源,为我爆灯……………… 正常运行,感觉良好,记下来就要装系统喽。 系统 首当其从,当然是 Windows10 啦,嗯,没错,只有 Windows10 是可以配得上我亲手装的机器,所谓郎才配女貌 ??。从公司让同事帮忙借了一套显示器及无线键鼠,之后又安装了大白菜系统,开始安装。 不得不说,现在的配置安装 windows10 真的很快,并且开关机可以达到5秒内完成,牛掰。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/3/21
articleCard.readMore

近期理财学习总结

最近在上了一个在线的理财班(微信群实时上课),因为收费比较便宜,自己抱着学到赚到的原则,看看实际情况进行合理安排自己的进阶课程,因为知道这是一个类似公益性质的课程,之后会推荐他们专业课程,所以自己有一定的衡量,接下来对这个理财做一下小结。 之前并没有意思到理财的重要性,主要是因为理财感觉还比较遥远,后来上课之后了解到,十年至今中国的通货膨胀率远远比现在银行存款利率高,也就是说现在的人民币在手中注定着贬值,贬值的速度远远大于自己资金的积累,最后就会变成一颗韭菜,时间会变成一个巨大的收割机,如果不开始理财,自己会变成新一代的韭菜避免不了被割的风险。 “富人思维”是为期12天理财课程培养的重点,主要是让你养成一个好的攒钱投资的习惯,学会利用“钱生钱”,接下来就给大家介绍一下学习的十四条“富人思维”: 关键富人思维 - 第一条 获得经济独立、财务自由的意义是什么? 经济独立、财富自由并不是独善其身,它的本质是让自己、家人变的更好的能力。 【从个人讲】,可以更好的选择自己喜欢的东西、自己喜欢的生活方式、婚姻方式,最终实现踏踏实实靠自己,得来属于自己的精神自由。把经济这个生存的“命脉”交给他人,其实就是把自己选择的权力让给了他人,也把自己生活的主动权让给了他人。 【从家庭讲】,与爱人共同分担家庭的财务重担,应对老人的医疗花销、孩子的教育花销,让家庭关系因为共同的努力获得财富而更美好,而不是因为钱受到冲击而动摇。 一句话总结:提升理财技能,实现财务独立,获得更多自主选择的权力,获得给家人更好的生活的能力。 关键富人思维 - 第二条 月光或者积蓄不多的人,为什么不能等有钱再学习理财? 月光和积蓄不多本质是错误理财思维造成的结果, 【在花钱上】,很多都是坏支出,一心只想买买买,很多东西在冲动消费之后要么用了几次就放置一边,要么后悔自责又在下次陷入到恶性循环,但对于投资自己成长的好支出,却又思前想后。其关键在于,没有好支出、坏支出的思维意识,被欲望牵着走,没有将有限的资金花在刀刃上。 【在攒钱上】,单纯靠工资攒钱是最低效最差的方式,如果只靠工资收入来攒钱,很可能到退休了还没有攒够理财的本金。真正聪明的小伙伴会在获得第一笔工资收入时就开始选择合适的理财工具来积累自己的本金和非工资收入了。 具有富人思维的人,哪怕是现在月光或者积蓄不多,就会从一开始就通过工资和非工资收入两条路径来增加自己的收入。两条腿走路肯定比只靠工资收入一条腿走的更快更远。 一句话总结:提升财富要靠工资和非工资收入【两条腿】走路,绝对不能独腿前行。 关键富人思维 - 第三条 中产家庭为什么不能沉溺在自己的工资收入中? 中产家庭看似收入比较稳定,其实抗风险能力不强,他们有房贷要还,小孩要养,甚至父母还要大量的开销。 【孩子还未成人的家庭】,孩子没有收入,花销逐年增大,这时候如果夫妻中有一人遇到一段时间不能工作的情况,家庭财务整体情况可能会出现较大落差,进而影响生活质量。 【全职宝妈的家庭】,女性负责照顾孩子,没有工资性收入,只有老公一人的收入是家庭收入的主要来源,如果老公遭遇大裁员等意外情况,家庭很可能立即陷入坐吃山空的财务危机中,或者啃老的尴尬境地。 所以中产家庭更需要尽早尽快建立自己的非工资收入体系,【在没发生意外的时候】,可以为家庭提供一份额外的收入,补贴家用;【在发生意外的时候】,能够抵御财务风险,不至于到毫无收入的被动地步。 学习并掌握获得非工资收入的能力,是为自己负责,也是为了家人负责。 一句话总结:中产的【财务安全】来自工资收入和非工资收入的双管齐下,没有充足的非工资收入的中产家庭,谈不上财务真正安全。 关键富人思维 - 第四条 为什么说复利三要素中最容易掌控的是收益率? 复利三要素,本金、时间、收益率。把钱存在保险箱中,复利会发挥反向作用,把钱一口一口吃掉,长期来看1万元每年贬值500元以上。投资开始的越早,时间要素发挥越大,复利的正向作用越早发挥作用。本金大的人,复利作用也比较大,但是投错了地方,本金再大也会亏完。如果本金和时间都不占优势,那么最有效的就是提高年化收益率,而提高年化收益率的关键在于自己的理财能力。 一句话总结:【时间无法改变,越早开始越好;本金依赖生钱资产;收益率源于理财能力,理财能力才是决定复利终值的关键】。 富人关键思维 - 第五条 为什么说投资自己的大脑、学会理财技能也是中产升级之法? 如同经典书籍《富爸爸》中说的,穷人卖时间换钱,其实本质是“卖命”。他们抱怨自己没有钱,其实本质是他们没有认识到投资自己的大脑是最快的脱贫致富之法。 比如一个专家花费一生写了一部巨著,我们花100元买了他的书回来看,其实某种意义上我们是买到了他一生的生命成果,学到了能获得成千上万的收益,这就叫“站在巨人的肩膀上”,贫穷的矮人站在“巨人的肩膀上”也会达到巨人的视野。 但很多人心疼这点投资大脑的钱,所以一直什么都不懂,一直贫穷下去。反之,穷人也能通过投资自己的头脑,学习他人成功的方法,买他人花了很多时间转化的成果,变成自己的东西,快速创造财富,这是最快的脱贫之法。 一句话总结:心疼投资大脑的钱,一直什么都不懂,不是被通货膨胀割韭菜,就是被投资市场割韭菜,一直穷下去。反之学习他人成功的方法,变成自己的技能,是最快的脱贫、升级之法。 关键富人思维 - 第六条 到底什么时候可以开始投资? 为什么有的人买股票必亏,看了几本书,学了几天课就急急忙忙要去股市里了,我们学车还要几个月,投资作为高度专业化、精细化的一个领域,不经过系统的学习就去“尝试”,这和训练了三天、看了几本武侠小说就上战场的士兵有什么区别呢? 士兵进行系统训练是为了保命,投资者进行系统的学习是为了保钱的“命”,反之,学个半瓶水就去投资市场,美其名为“试试”,这样的“试试”不会学到任何教训、经验,只会收获痛苦。那些经过系统训练的人会把那些无知而自以为是的人收割的一滴血不剩。 【这就是投资的真相】,不系统学习就想赚快钱就是送命。当你打算投资一个目标,有系统的分析方法,不再为了涨跌而心惊胆战的时候,这才是投资可以真正开始的时候。 一句话总结:投资第一原则:不懂不要投,懂了安心投,盲目“尝试”无疑送命,系统训练方可真正保住钱“命”。 关键富人思维 - 第七条 为什么月光、负债的人应该拿出一部分资金学习理财技能? 这里的负债主要指的是让自己的财务状况不断恶化的【坏负债】。 月光和负债只是不懂理财的结果,没有理财技能才是月光和负债的原因。很多人一方面不懂投资的骗局,一方面又眼红想赚一下,最终都入了骗局的“坑”,如果提前投资自己的大脑,难道还会犯这样的错误吗?还有很多人,本来钱就不多,贷款买了一堆耗钱资产,要么不断的刷信用卡、花呗、借呗,窟窿越来越大,要么生活拮据,每天人前风光,人后遭殃。 但对于投资自己,提升理财技能的好负债,他们却斤斤计较,错失了复利的好机会,这样的人生活只会越来越难过。 【成长有顺序,生活致富也有顺序】——先投资大脑,掌握理财的技能,然后再去生钱,月光、负债不学习只会越来越穷?? 一句话总结:月光、负债是财务病,病根是缺少理财技能,忽视病因、不治病根,坏支出、坏负债只会越来越严重。 关键富人思维 - 第八条 股票价格大跌能跌出什么? 我们【投资股票正确的方法】可以分为两大步: 第一步:选出内在价值高的好企业 第二步:在好的价格及时买入 这两步是不能颠倒的。 【当股市的价格出现下跌时】,好企业代表的好股票会出现好的买入价格,但是坏企业会跌出让投机者眼红的“陷阱”。如果在选择企业这一步错了,不管是多便宜的价格都徒劳无功,反而损失惨重。 【理性的投资者】对自己无法预测股票价格有自知之明,因为价格的波动总是难以预测的,因此他们将主要精力放在好企业的选择上,当好价格出现的时候果断出手,而后不管是继续跌还是涨,都是任凭风浪起稳坐钓鱼船。他们一般很少看股票,却获得了很高的收益。 一句话总结:股票大跌既有机会也有陷阱,机会的识别需要眼力,机会的把握需要技能,当眼力和技能配不上这个机会的时候,往往会步入陷阱之中。 关键富人思维 - 第九条 很多人炒股都亏,就说股票风险高,到底该怎么看待投资股票这件事? 【投机炒股的人】,不懂就去投,一心想赚一把就走,那么股市就是一个大赌场,都特别想赢钱,但又特别怕输钱,风险自然是非常大 而且还很容易上当受骗,赚了以为是自己本事,亏了又说运气不好,说到底都是自欺欺人。 涨了开心要命,跌了悲伤绝望,被价格的波动带着一天悲喜两重天,说到底还是没有技能让自己内心踏实? 【真正的投资股票】,是关注股票代表的公司的好坏,是看到股票背后的本质,经过严谨的分析得出的结论。 这样的投资,即使短时间的价格波动也能心理踏实,最终能够获得复利带来的长期收益?? 一句话总结:投资股票的正确姿势是靠分析,能选出好股票风险自然就小。赌徒在股市是把身家性命交给市场,不懂就投,肯定要被懂的人收割的。 关键富人思维 - 第十条 为什么工作者、投资者都需要通晓企业分析技能? 并不是创业者 、大老板才需要了解企业。 【投资中】,股票本质是企业,只有把9大要素都分析清楚,才能分析出好企业,才能给企业估值. 【工作中】,干工作的时候要有企业经营的思维,这样才更容易升职加薪。 即便不为升职,干工作的时候也要有企业经营的思维。因为当你站在更高去看自己的工作内容的时候你才能更好的理解自己的工作内容,这样你可以把工作做成老板真正想要的样子。 而那些只盯着自己的岗位的“井底之蛙”,有的时候挨了骂都不知道为什么,因为他缺乏跳出自己的框框看企业的技能,这就是他自己工作干不好的原因。 所以无论投资、工作,都需要通晓企业分析技能。 总结一句话:工作者用企业分析模型看透工作,投资者用企业分析模型看透股票? 关键富人思维 - 第十一条 为什么要自己掌握理财技能才是最可靠的? 很多之前学员都表示跟着自己的家人、朋友投资,当赚了的时候会特别感激对方,但是亏的了时候又怨恨对方不靠谱。其实他们的家人、朋友很多也真的是好心,但是他们没有掌握投资技能,靠着小道消息,本质是投机,赚了是运气好,长期亏损是必然。 理财投资这么重要的事情,如果交给别人,就像把自己的“财富之命”交给了别人,如果遇上的是一个没有扎实理财技能的人,那就对自己的“财富之命”太不负责了。因为缺乏理财技能的人,是根本拿不住好股票的,他们无法分析企业,就无法正确面对价格的波动,最终还是逃不过亏损的命运。 而听信银行经理的话,选择银行理财,长期看收益连通货膨胀的贬值都跑不赢。 特别是【中产家庭】,与其听信他人的小道消息,【最可靠的】还是自己通过学习成为家庭资产理财师,为自己的家人打理财产,创造更多非工资收入,这才是最安全可靠的方法。 【一句话总结】:最重要的本事需要掌握在自己手中,就像企业要把核心竞争力掌握在自己的手中一样,理财——事关自己和家庭的财富之命,只有自己掌握了,才是对自己的负责。 关键富人思维 - 第十二条 我们最应该为孩子留下什么财富? 我们总想为孩子留下一笔钱、一套房等,这并没有错,但我们给孩子留下的最重要财富却不是金钱。因为如果给孩子留下了金钱,但孩子无法对抗通货膨胀的贬值、缺少科学的理财知识,金山也会吃空,最终落得个流离失所的下场,正如我们前面讲到的山西首富之子一样。 【聪明的父母】会选择在孩子很小的时候就开始正确的财商教育,让孩子从小就养成正确的金钱观,从很小的时候就享受理财技能带来的复利,而【这都取决于父母的理财能力】,如果父母对理财一无所知,或者学个半瓶水,看似明白实则跑偏,那么不仅教不好孩子,还会把孩子带到错误的方向 【不要让孩子像我们父辈和自己一样】,那样又要陷入“无知”—“试错”—“损失”的恶性循环之中,耗费大量的时间成本、试错成本,又错失了大量的机会成本,我们走错的路,就不要让孩子再走一遍了。 【总结一句话】:我们最应该留给孩子的财富是可贵的品格和可靠的理财能力,身教重于言教,用我们的行为带动孩子品格的养成,用我们的理财能力让孩子的财商有一个好基础?? 富人关键思维 - 第十三条 掌握收益率高的工具的科学方法是什么? 【方法一】:很多人刚刚知道有一个高收益的工具,恨不得马上就买一个,然后就自我憧憬能赚多少。或者选择去市场中直接尝试,事实证明,他们自己总结的方法时而有效,时而无效,时而自信找到了暴富出路,时而迷茫否定自己、骂社会,我称之为焦虑的“【烧钱尝试法】”。其实他们不仅损失了自己的血汗钱,同时在错误尝试的时间里,也错过了正确买入好目标的机会。 【方法二】:其实最快的方法,绝对不是马上去盲目尝试,莫刀不误砍材工,应该先去学习,在学习中最快的方法,是什么,是看书吗?如果看书能够实现财务自由,那么很多人早就财务自由了,因为看书最大的弊端就是没有反馈,没有人告诉你学的对还是学的错。沉溺在看书中寻求财富自由的人,不能说不努力,但是因为没有老师指点,很多都成了【无效的努力】,最可怕的不是慢,是选错了方向,走错了路,在错误方向上的努力只是巩固错误。 【方法三】:那么最快的学习方法是什么?答案是【巨人同行法】,跟着已经长期成功的投资者学习,巴菲特的老师是投资之神格雷厄姆,我们前天早读课中8岁的投资神童,她的老师是父亲,也是成功投资家。他们都是一边勤奋学习理论,一边在老师的指导下纠正错误,一直走在一条正确的努力之路上。这就是看似缓慢,但实际最快的投资之路。这是一条少有人懂得的路,也是一条少有人走的路,所以真正赚钱的投资者才不多。你打算选哪条路? 总结一句话:站在巨人的肩膀上,在少有人走的投资学习之路上最快成长。 关键富人思维 - 第十四条 有的投机者说自己也赚钱了,那么选择做投资者到底好在哪? 我们投资理财到底是为了什么呢?赚钱,但进一步想,赚钱是不被钱绑架,能有时间做自己想做的事,能和家人快乐的生活。 【不懂就去投的人】,短期内碰上运气了,也能赚到钱,但是他们是怎么度过的呢,每天把大量时间用来盯着大盘,昼夜想着跌了还是涨了,精神总是高度紧张,甚至睡觉都在想明天股票的情况,因为他们选择了“赌博”。历史证明,绝大多数投机者本来最应该乐观开心的两年,到头来却是在焦虑、闹心中度过的,因为他们没有真正的投资判断依据,这就是投机者的生活。 【懂得投资的人】,通过一整套分析方法选出优质的投资目标,算出买入好价格,买入,持有,再算出卖出合理价格,卖出。剩下的时间该干什么干什么,价格跌了,心里知道这是表面的波动,不被其迷惑,拿得住;价格涨了,知道什么时候该卖出,不冲动,赚踏实稳当钱。更厉害的,长期持有,被分红的复利滋润着。这是学习科学方法的自然成果,这也是不学习像苍蝇撞大运的投机者,与踏实学习走正道的投资者的最大差别。 当然,既不投机,也不投资的人都感受不到这些,他们只能感受到钱越来越不值钱,而自己的生活好像越来越紧吧了。 总结一句话:投资者获得的是金钱自由,时间自由,心理自由,投机者就算撞运赚了点,却一直在焦虑与煎熬中。 以上就是十四条富人思维总结来说就是:“不懂不要投,懂了放心投”。 不过目前没有相关的经验,还是一只实习生,不过还要“利其器”,防止以后踩坑。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/2/28
articleCard.readMore

Redis 知识点汇总

1.Redis数据结构有哪些 ? 1 2 String List Hash Set Sorted Set bitmap Geo HyperLogLog Streams 2.Redis相比memcached有哪些优势? memcached 所有的值均是简单的字符串; redis 作为其替代者,支持更为丰富的数据类型; redis 的速度比 memcached 快很多 redis 可以持久化数据。 3.Redis是单线程,如何解决并发请求访问? redis 利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。 4. Reids6 淘汰策略有哪些? noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。大多数写命令都会导致占用更多的内存,有极少数会例外。 LRU算法 allkeys-lru:所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。 volatile-lru:只限于设置了expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。 随机淘汰 allkeys-random: 所有key通用; 随机删除一部分 key。 volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。 volatile-ttl: 只限于设置了expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。 5.Redis持久化方案有哪些? RDB(Redis DataBase)持久化:是指用数据集快照的方式半持久化模式,记录redis数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复; AOF(Append-only file)持久化:是指所有的命令行记录以redis命令请求协议的格式完全持久化存储,保存为aof文件。 RDB和AOF的优缺点 : RDB持久化: 优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。 缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。 AOF持久化: 与RDB持久化相对应; 优点在于支持秒级持久化、兼容性好; 缺点是文件大、恢复速度慢、对性能影响大。 6.Redis内存划分 数据: 作为数据库,数据是最主要的部分; 这部分占用的内存会统计在used_memory中。 进程本身运行需要的内存: Redis主进程本身运行肯定需要占用内存,如代码、常量池等等; 这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。 这部分内存不是由jemalloc分配,因此不会统计在used_memory中。 缓冲内存: 缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等; 其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能; AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。 在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配, 因此会统计在used_memory中。 内存碎片: 内存碎片是Redis在分配、回收物理内存过程中产生的。 例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory中。 7.Redis 主从复制 复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。 复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。 缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制 。 8.Redis哨兵 在复制的基础上,哨兵实现了自动化的故障恢复。 缺陷:写操作无法负载均衡;存储能力受到单机的限制。 9.redis缓存被击穿处理机制 使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个 get缓存方法。 10.缓存和数据库间数据一致性问题 分布式环境下非常容易出现缓存和数据库间的数据一致性问题,针对这一点的话,如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存。 只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。合适的策略包括合适的缓存更新策略,更新数据库后要及时更新缓存、缓存失败时增加重试机制,例如MQ模式的消息队列 。 11.缓存雪崩问题 像解决缓存穿透一样加锁排队; 建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存和B缓存。 12.Redis分布式 redis支持主从的模式。 原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。 这是一个典型的分布式读写分离模型。利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量 。 13.读写分离模型 通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。 读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。 14.数据分片模型 为解决读写分离模型的缺陷,可以将数据分片模型应用进来。 可以将每个节点看成,都是独立的 master,然后通过业务实现数据分片。 结合上面两种模型,可以将每个 master 设计成由一个 master 和多个slave组成的模型。 15.Redis分布式锁实现 先拿 setnx 来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样? set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的! 16.Redis做异步队列 一般使用list结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当sleep一会再重试。 缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。 能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。 17.Redis中海量数据的正确操作方式 利用SCAN系列命令(SCAN、SSCAN、HSCAN、ZSCAN)完成数据迭代。 18.是否使用过Redis集群,集群的原理是什么? Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。 Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。 19.Redis集群方案什么情况下会导致整个集群不可用? 如果有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。 20.说说Redis哈希槽的概念 Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。 21.Redis集群会有写操作丢失吗?为什么? Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。 22.怎么测试Redis的连通性? 使用ping命令。 23.怎么理解Redis事务? 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。 24.Redis如何做内存优化? 尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。 比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。 25.Redis回收进程如何工作的? 一个客户端运行了新的命令,添加了新的数据。Redis检查内存使用情况,如果大于max memory的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用( 例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。 26.MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据? Redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。 27.假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来? 使用keys指令可以扫出指定模式的key列表。如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?会造成阻塞卡顿。 因为redis是单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以 无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。 28.如果有大量的key需要设置同一时间过期,一般需要注意什么? 如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。 头条面试 1.Redis连接时的 connect 与 pconnect 的区别 connect:脚本结束之后连接就释放了。 pconnect:脚本结束之后连接不释放,连接保持在php-fpm进程中。 2.Redis有哪些结构时间复杂度较高 List 3.Redis hash的实现 哈希对象的编码可以是 ziplist 或者 hashtable 。 ziplist 编码的哈希对象使用压缩列表作为底层实现, 每当有新的键值对要加入到哈希对象时, 程序会先将保存了键的压缩列表节点推入到压缩列表表尾, 然后再将保存了值的压缩列表节点推入到压缩列表表尾, 因此,保存了同一键值对的两个节点总是紧挨在一起, 保存键的节点在前,保存值的节点在后;先添加到哈希对象中的键值对会被放在压缩列表的表头方向, 而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。 4.redis 主从同步是怎样的过程? 见第7题 5.redis 的 zset 怎么实现的? 有序集合的编码可以是 ziplist 或者 skiplist 。 ziplist 编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。 skiplist 编码的有序集合对象使用 zset 结构作为底层实现, 一个 zset 结构同时包含一个字典和一个跳跃表。 小米面试 1.Redis数据结构有哪些 1 String,List,Hash,Set,Sorted Set,bitmap,Geo,HyperLogLog ,Streams 2. Redis 持久化方案有哪些? RDB(Redis DataBase)持久化:是指用数据集快照的方式半持久化模式,记录redis数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复 AOFAppend-only file。 持久化: 是指所有的命令行记录以redis命令请求协议的格式完全持久化存储)保存为aof文件。 360 面试 1.redis的持久化 RDB(Redis DataBase)持久化: 是指用数据集快照的方式半持久化模式,记录redis数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复AOFAppend-only file。 持久化: 是指所有的命令行记录以redis命令请求协议的格式完全持久化存储)保存为aof文件。 2.lru算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <?php /** * LRU是最近最少使用页面置换算法(Least Recently Used) * 也就是首先淘汰最长时间未被使用的页面 */ class LRU_Cache { private $array_lru = array(); private $max_size = 0; function __construct($size) { // 缓存最大存储 $this->max_size = $size; } public function set_value($key, $value) { // 如果存在,则向队尾移动,先删除,后追加 if (array_key_exists($key, $this->array_lru)) { unset($this->array_lru[$key]); } // 长度检查,超长则删除首元素 if (count($this->array_lru) > $this->max_size) { array_shift($this->array_lru); } // 队尾追加元素 $this->array_lru[$key] = $value; } public function get_value($key) { $ret_value = false; if (array_key_exists($key, $this->array_lru)) { $ret_value = $this->array_lru[$key]; // 移动到队尾 unset($this->array_lru[$key]); $this->array_lru[$key] = $ret_value; } return $ret_value; } public function vardump_cache() { var_dump($this->array_lru); } } $cache = new LRU_Cache(5); $cache->set_value("01", "01"); $cache->set_value("02", "02"); $cache->set_value("03", "03"); $cache->set_value("04", "04"); $cache->set_value("05", "05"); $cache->vardump_cache(); $cache->set_value("06", "06"); $cache->vardump_cache(); $cache->set_value("03", "03"); $cache->vardump_cache(); $cache->set_value("07", "07"); $cache->vardump_cache(); $cache->set_value("01", "01"); $cache->vardump_cache(); 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/1/28
articleCard.readMore

Linux >/dev/null 2>&1 命令使用说明

/dev/null 2>&1 命令使用说明" /> 近期在开发项目中遇到了PHP使用shell_exec执行 Shell 命令的问题,具体说是 Shell 使用 FFmpeg 软件进行录制直播流,但是 PHP 等待命令执行时间是有限的,并且会出现等待时间过长导致该执行接口出现未响应问题,寻找了网上方法,发现了一种比较好的方式,就是将要执行的Shell语句后面加上>/dev/null 2>&1这段特殊的命令,简单来说就是将执行的 Shell 操作放到后台进行运行,将快捷反应执行的操作,解决执行 Shell 语句等待问题,接下来就是对这段命令的解析: 命令的结果可以通过%>的形式来定义输出。 /dev/null 代表空设备文件 > 代表重定向到哪里,例如:echo "123" > /home/123.txt; 1 表示 stdout 标准输出,系统默认值是 1,所以>/dev/null等同于1>/dev/null; 2 表示 stderr 标准错误; & 表示等同于的意思,2>&1,表示 2 的输出重定向等同于 1。 针对下面数字的代表含义的解释: 0:表示键盘输入(stdin) 1:表示标准输出(stdout),系统默认是1 2:表示错误输出(stderr) > /dev/null 2>&1 语句含义: > /dev/null : 首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,说白了就是不显示任何信息。 2>&1 :接着,标准错误输出重定向(等同于)标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/1/13
articleCard.readMore

JavaScript 飞雪特效

马上就要到了传统节日“春节”?,网站添加了飞雪❄️特效,从网上找了源代码,先要感谢张戈博客分享?,现计划将网站在今天上线至过年回来下线,看看可以么,你可以复制到自己的网站或者博客体验一波,加上《一剪梅》真是别有一番滋味。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <script type="text/javascript"> (function($){ $.fn.snow = function(options){ var $flake = $('<div id="snowbox" />').css({'position': 'absolute','z-index':'9999', 'top': '-50px'}).html('❄'), documentHeight = $(document).height(), documentWidth = $(document).width(), defaults = { minSize : 6, maxSize : 10, newOn : 1000, flakeColor : "#FFFFFF" /* 此处可以定义雪花颜色 */ }, options = $.extend({}, defaults, options); var interval= setInterval( function(){ var startPositionLeft = Math.random() * documentWidth - 100, startOpacity = 0.5 + Math.random(), sizeFlake = options.minSize + Math.random() * options.maxSize, endPositionTop = documentHeight - 200, endPositionLeft = startPositionLeft - 500 + Math.random() * 500, durationFall = documentHeight * 10 + Math.random() * 5000; $flake.clone().appendTo('body').css({ left: startPositionLeft, opacity: startOpacity, 'font-size': sizeFlake, color: options.flakeColor }).animate({ top: endPositionTop, left: endPositionLeft, opacity: 0.2 },durationFall,'linear',function(){ $(this).remove() }); }, options.newOn); }; })(jQuery); $(function(){ $.fn.snow({ minSize: 5, /* 定义雪花最小尺寸 */ maxSize: 20,/* 定义雪花最大尺寸 */ newOn: 800 /* 定义密集程度,数字越小越密集 */ }); }); </script> 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2020/1/12
articleCard.readMore

2019 年度总结

2019年,这一年来是我经历最多的一年,上半年的我还在学校上着学校学年安排的课程,谈着美好的校园恋爱,下半年就脱离了校园生活,开始独自一人的北漂生活,这一年来也是这个社会变化较大的一年,5G 的商业化的元年,最难就业的一年,等等,这一年现在告诉自己,我坚持了下来,在这个多变的社会生存了下来,顺利找到了实习工作,从校园实验室生活转变为两点一线的上班生活,开始了不一样的生活体验。 这一年来,我的博客访问量也逐渐增多,网站使用了阿里云 ECS + OSS + CDN 全套服务提升性能,虽然写的文章不及阮一峰老师的千万分之一,但是我可以自豪的说,这一年我坚持了下来,持续的积累技术知识及新型的技术的评价让我的博客文章逐渐的多了起来,在项目中的开发知识积累也都写在博客上面,逐步丰富自己的技术栈,使自己成为一个足够优秀的人,来到北京逐渐发现,自己是那千万人中最最平凡的一类人,正如现在实习公司所说:“平凡人做非凡事,成就非凡人生价值”,不断的提升自己的技术能力及分析问题的能力是我现在要做的,另外也是我2020年博客更新的一大方向,2019年,感谢支持Debug客栈成长的小伙伴们,谢谢你们的鼓励与支持,你也可以加入我的QQ群聊,大话人生 @Debug客栈交流群。 2020年,迎来我大学的结束,并且我也将从实习生的身份转变为应届生,开始了真正的工作,去为社会主义添砖加瓦,2020年加油,奥力给!!! 热门文章 接下来将网站本年度每月阅读量最高的文章张贴如下: 一月 蓝桥杯 基础练习 数的读法 二月 【NCRE四级网络工程师】计算机网络多选题 三月 解决百度网盘限速问题 四月 可能是史上最全的无版权免费图片网站推荐文章 五月 Laravel5.8+LayUI踩坑之路 六月 Laravel5.8学习日常之清除视图缓存 七月 给网站文字添加高逼格"抖音"效果 八月 关于5G的SA与NSA架构 九月 PWA 渐进式Web应用程序 十月 Windows常用软件清单 十一月 MacBook Pro 2019款 入手体验 十二月 PhpStorm Mac / Windows常用快捷键 其中在2019年访客最多的文章是:关于5G的SA与NSA架构 网站访客 这里公布的是从2019年8月20日开始统计,使用Google Analytics统计的数据: 用户访问量:8125,事件数:9.5万,新用户数:2.1万。 Google Analytics 在线报告:点击此处访问(需墙) 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/12/31
articleCard.readMore

预约到了泰山异形纪念币

自己有着收藏纪念币的习惯,在11月21号凌晨成功预约到了央行发行的首枚异型纪念币普币“泰山纪念币”,看官网通知这枚纪念币值发行了1.2亿枚,所以说相对来说具有较高的收藏价值,并且此纪念币有塑料桶包装,对于收藏来说更容易一些。 预约 凌晨定好闹钟,收到短信的时候还是挺激动的,北京地区的是建设银行来进行发行,下面是银行给我发的预约成功短信: 尊敬的客户,您已成功预约20枚世界文化和自然遗产-泰山普通纪念币,预约编号0016866157426**********。请于您约定的日期持尾号****的身份证件至中国建设银行股份有限公司北京昌平支行兑换,兑换截止日为2019年12月01日。 [建设银行]2019年12月20日 建行的纪念币预约兑换服务还是比较友好的,在26日即将开始兑换的时候又收到了建行兑换通知: 尊敬的客户,您所预约的世界文化和自然遗产—泰山普通纪念币于2019年11月28日至2019年12月1日(网点营业时间)办理兑换,请您在此期间持第二代居民身份证原件,前往约定的营业网点办理兑换业务。我行部分网点周六、日休息,请您提前咨询约定网点后再前往办理。您可通过我行电子渠道(官网、手机银行、微信银行、网银)的“预约记录查询”功能查看您的预约网点地址及联系电话。如已兑换,请忽略此短信。 [建设银行] 2019年11月26日 兑换 今天昌平区支行进行兑换,总体来说比较顺利,可能是去的比较晚,竟然没有排队的,顺利兑换完毕,接下来给大家看一下泰山币。 发行 从建国到现在纪念币发行了上百枚,具体可以去维基百科去看建国以来我国发行的纪念币,这个网页还是比较全的,可以看一下。 另外我接收到纪念币发行的消息主要是关注了**微信公众号“纪念币爱好者”**获得第一时间预约消息,感兴趣的同学可以关注一波。 感想 纪念币的发行是用来收藏的,而不是一些商家进行炒作使得原本具有一定价值的物品瞬间翻了几番,去闲鱼看了一下竟然到了400一桶,看来我是赚了? 纪念币收藏不应该沦为另类股票,这也许这就是资本经济吧,使得原本价值1元的鸡蛋,最后100元售出的时候人们竞相购买,最后发现不就是1元一个鸡蛋么。 PS:图片均来自锤子 坚果Pro2 拍摄 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/11/30
articleCard.readMore

二维码会使用完么

现在生活中总是会使用到二维码,在支付中、博客的推广图片上、各种各样的商品推广,都有着二维码的身影,二维码已经是我们的日常生活中有着不可替代的便捷信息载体,近几日在网站了解到字节及腾讯根据自己的产品分别推出了抖音码及小程序码的解析流程,心中有一个疑问:“二维码会被使用完么”,在近一周的资料查询及二维码原理分析,我得到的答案是“二维码会被使用完,但我们目前使用不完”,“二维码会被使用完”是因为二维码是采用黑白点阵组成的一段特殊的代码,可以理解为一张特殊的图片,那么这张图片大小比较固定,那么也就代表着二维码会被使用完,而“我们目前使用不完”是因为点阵组成的二维码个数实在是太多了,即使在我们生活中大量应用,每天都有几百亿个二维码产生,还是使用不完。 那么接下来就给大家介绍一下这个二维码是怎么产生及使用的吧。 二维码是什么 二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的、黑白相间的、记录数据符号信息的图形;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。 二维码的结构 二维码符号分为功能图形及编码区格式,其中功能图形主要作用是辨别及定位二维码的位置,便于扫码操作。而编码区格式存储着定义的格式信息、版本信息及最为重要的数据和纠错码字,他们共同构成了我们常见到的二维码。 校正图形:规格确定,校正图形的数量和位置也就确定了; 格式信息:表示该二维码的纠错级别,分为L、M、Q、H; 版本信息:即二维码的规格,QR码符号共有40种规格的矩阵(一般为黑白色),从21x21(版本1),到177x177(版本40),每一版本符号比前一版本 每边增加4个模块。 数据和纠错码字:实际保存的二维码信息,和纠错码字(用于修正二维码损坏带来的错误)。 通过上面的图片可以想象到,二维码的大小就是那么大,无论采用怎样的编码格式绘制二维码,都会出现有限的组合数量。 二维码数量 QR Code的符号版本范围从版本1到版本40。每个版本具有不同的模块配置或模块数量。(模块是指构成QR Code的黑白点。) “模块配置”是指符号中包含的模块数量,从版本1(21×21个模块)到版本40(177×177)模块)。每个更高的版本号每侧包含4个附加模块。 总数量:2^177*177 个。 Reference CSDN 二维码(QR code)基本结构及生成原理 Information capacity and versions of QR Code | QRcode.com | DENSO WAVE 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/11/20
articleCard.readMore

MacBook Pro 2019款 入手体验

购买MacBook Pro已经有了一个多月的时间,在这一段的时间中,首先给MBP给我的第一印象就是“真香”,近乎完美的外表设计搭配自家MacOS,简直就是一款非常有效的生产力工具,2019款增加的Touch Bar就是一个不折不扣的加分项,接下来就听我细细道来吧。 ✅ 目标 高中时期就对与乔帮主的创新创意所深深吸引,将一台笔记本压缩到一个档案袋一样大小,真的是一件了不起的事情,当时就被Mac电脑的精美,近乎完美的工业设计打动,但是当时迫于高考,再加上对于电脑的需求并没有那么高。高考结束后,自己考上了本省的一所普通二本院校,所学专业是计算机相关的,正是因为所学专业是计算机专业,当时一根脑筋的认为必须高性能的游戏本才能在自己大学四年来进行学习与编程,于是购置了一台Dell游匣7559款游戏本。 大学期间,这台笔记本是真的够用,但也真的沉,太沉以至于在大学期间他没有几次走出我的大学实验室。另外就是windows系统是真的很崩溃,我人被它搞崩溃,系统也是跟给力,蓝屏问题一直都是一个不定时的炸弹,真的很苦恼,曾经有一段时间给游匣安装了黑苹果,最后迫于黑苹果网卡及各种驱动问题最后又改装了Windows系统,那时,自己的小目标就是实习期间一定要换一台属于自己的MacBook,接下来就有了现在的入手体验。 ✅ 开箱 苹果的包装一直都是比较讲究的,环保包装,简洁的只有三件(包装三包+MacBook+电源)。 比较灵巧的设计,将两侧“耳朵”竖起,就自动将内包装抬起,挺巧妙的设计。 拿出来之后,打开内侧包装,开始撕膜…… 由于本人撕膜之后过于兴奋,系统设置这一块忘记拍摄了,脑补喜悦的画面吧,接下来给大家介绍一下电脑的性能及系统配置吧。 ✅ 配置 近期升级到了Catalina版本,看着增加了应用使用时间及iPad扩展功能,挺不错的,接下来看看配置吧。 CPU 2.4GHz 四核 Intel Core i5 处理器 (Turbo Boost 最高可达 4.1GHz),配备 128MB eDRAM 内存 8GB 2133 MHz LPDDR3 显卡 Intel lris Plus Graphics 645 1536 MB 显示器 13.3英寸(2560 * 1600) 固态硬盘 256GB 固态硬盘 拓展 两个雷雳 3 (USB-C) 端口,USB 3.1 第二代 (速率最高可达 10Gb/s) 全尺寸背光键盘 64 个 (ANSI) 或 65 个 (ISO) 按键 Wi-Fi 网络 802.11ac Wi-Fi 无线网络 兼容 IEEE 802.11a/b/g/n 蓝牙 蓝牙 5.0 无线技术 摄像头 720p FaceTime 高清摄像头 其他 官网详细介绍 ✅ 系统 全屏幕窗口方式 由于苹果本身尺寸比较小,全屏幕的设计搭配触控板可以轻松实现对窗口的扩展,由于是从windows深度用户,开始不太习惯,不过用了两天就被它这功能所深深的吸引,真的特别便捷实用,给人以最大化工作界面的状态。 这种用户界面将极大简化电脑的使用,减少多个窗口带来的困扰。它将使用户获得与iPhone、iPod touch和iPad用户相同的体验。计算体验并不会因此被削弱;相反,苹果正帮助用户更为有效地处理任务。 百度百科 Mac OS 类Linux系统 这个无疑是我作为开发者实用Mac的神器,在日常工作使用中,对终端的操作,连接远程linux系统,系统本身安装环境都显得那么顺畅,这个是在windows原始Dos系统上不能体会到的,上手后你就了解了。 Touch Bar 不得不说,带把的 带bar 的就是好用,如果说键盘拯救了我们输入系统,那bar就拯救了我们手动的选择。 无广告干扰 Mac系统软件开发本身就约束开发软件对广告的植入,不过VIP、SVIP,库克也无法拯救大家,目前使用了一个多月,几乎没有广告,用着非常省心。 QQ良心版 提到了QQ,简直就是开发者对Mac用户的优惠,红包提醒简直堪比外挂,都说抢红包抢的块,想想那些得到红包的是不是Mac用户? ✅ 总结 Mac本虽然优点特别的多,但是由于本身电脑价格昂贵至今博主还在还白条中,不过苹果对于学生有学生教育优惠政策,原本人民币11499的价格便宜到10700,同时还免费赠送了一台Beats Studio3 Wireless 头戴式耳机,要是不满意这个耳机可以咸鱼出售,之后这台笔记本整体可以10k以内购买下来,即使如此对于学生党来说还是比较昂贵的,这也是我为什么实习更换笔记本的一大原因,在使用了一个多月的时间里,这台电脑优缺点兼备,之所以换电脑还是优点大于缺点的: 优点 轻薄便携,出门或者随身携带重量可以接受; MacOS 使用过的电脑系统中最人性化设计的系统,没有之一; 视网膜屏幕,对使用者十分友好; Touch Bar选择多样,真的很好用; 电池?充满电之后可以重度使用6小时左右; 类Unix设计,命令行上来就用; 无广告干扰,使用很舒适; 薄而坚固的机身,干净的外观设计。 缺点 价格贵,即使使用了教育优惠,还是贵; 第三代蝶式键盘,感觉没有键程,就像怼木头一样,使用过机械键盘的感觉明显; 没有有线网口,只有两个闪电口,Wifi够用; 配件贵,这个时代只要是和苹果沾边的都不便宜,除了? 整体来说,苹果本真是一个革命性质的笔记本,用起来的用户交互真的很人性化,要是想购买,最好是在实习入手,享受教育优惠的同时,自己又可以支付较为昂贵的价格,推荐大家为此买单,但是要理性消费哦。 PS:图片均来自锤子 坚果Pro2 拍摄 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/11/3
articleCard.readMore

一些好玩的代码构图

是不是经常在群中看到大神闲的无聊敲的代码构图,今天,他来了,搜集了网上尽可能多的代码构图,展示给大家,友情提示,推荐使用比较大的屏幕查看,效果会比较好的。 No.1 佛祖保佑,永无Bug 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 _ooOoo_ o8888888o 88" . "88 (| -_- |) O\ = /O ____/`---'\____ .' \\| |// `. / \\||| : |||// \ / _||||| -:- |||||- \ | | \\\ - /// | | | \_| ''\---/'' | | \ .-\__ `-` ___/-. / ___`. .' /--.--\ `. . __ ."" '< `.___\_<|>_/___.' >'"". | | : `- \`.;`\ _ /`;.`/ - ` : | | \ \ `-. \_ __\ /__ _/ .-` / / ======`-.____`-.___\_____/___.-`____.-'====== `=---=' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 佛祖保佑 永无BUG No.2 草泥马 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ┌─┐ ┌─┐ ┌──┘ ┴───────┘ ┴──┐ │ │ │ ─── │ │ ─┬┘ └┬─ │ │ │ │ ─┴─ │ │ │ └───┐ ┌───┘ │ │ │ │ │ │ │ └──────────────┐ │ │ │ ├─┐ │ ┌─┘ │ │ └─┐ ┐ ┌───────┬──┐ ┌──┘ │ ─┤ ─┤ │ ─┤ ─┤ └──┴──┘ └──┴──┘ 神兽保佑 代码无BUG! No.3 知识就是力量 1 2 3 4 5 6 7 8 .-~~~~~~~~~-._ _.-~~~~~~~~~-. __.' ~. .~ `.__ .'// \./ \\`. .'// | \\`. .'// .-~"""""""~~~~-._ | _,-~~~~"""""""~-. \\`. .'//.-" `-. | .-' "-.\\`. .'//______.============-.. \ | / ..-============.______\\`. .'______________________________\|/______________________________`. No.4 史蒂文西蒙斯 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 _.---..._ ./^ ^-._ ./^C===. ^\. /\ .|' \\ _ ^|.^.| ___.--'_ ( ) . ./ /|| /.---^T\ , | / /||| C' ._`| ._ / __,-/ / /-,|| \ \/ ; /O / _ |) )|, i \./^O\./_,-^/^ ,;-^,' \ |`--/ ..-^^ |_-^ `| \^- /|: i. .-- / '|. i ==' /' |\._ _./`._ // |. ^-ooo.._ _.oo../' | ^-.__./X/ . `| |#######b d#### |' ^^^^ / | _\####### #####b ^^^^^^^^--. ...--^--^^^^^^^_.d###### ######b._ Y _.d######### ##########b._ | _.d############# "Piccolo" no. 2 (from Dragonball Z) --- Steven J. Simmons No.5 这样很忍者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 _ \"-._ _.--"~~"--._ \ " ^. ___ / \.-~_.-~ .-----' /\/"\ /~-._ / / __ _/\-.__\L_.-/\ "-. /.-" \ ( ` \_o>"<o_/ \ .--._\ /' \ \: " :/_/ "` / /\ "\ ~ /~" \ I \/]"-._ _.-"[ ___ \|___/ ./ l \___ ___ .--v~ "v` ( `-.__ __.-' ) ~v" ~v--. .-{ | : \_ "~" _/ : | }-. / \ | ~-.,___,.-~ | / \ ] \ | | / [ /\ \| : : |/ /\ / ^._ _K.___,^ ^.___,K_ _.^ \ / / "~/ "\ /" \~" \ \ / / / \ _ : _ / \ \ \ .^--./ / Y___________l___________Y \ \.--^. [ \ / | [/ ] | \ / ] | "v" l________[____/]________j -Row }r" / }------t / \ /`-. / | | Y Y / "-._/ }-----v' | : | 7-. / | |_| | l | / . "-._/ l .[_] : \ : r[]/_. / \_____] "--. "-.____/ "Dragonball Z" ---Row No.6 皮卡丘皮卡丘 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 quu..__ $$$b `---.__ "$$b `--. ___.---uuudP `$$b `.__.------.__ __.---' $$$$" . "$b -' `-.-' $$$" .'| ". d$" _.' | `. / ..." .' | `./ ..::-' _.' | / .:::-' .-' .' : ::''\ _.' | .' .-. .-. `. .' | : /'$$| .@"$\ `. .' _.-' .'|$u$$| |$$,$$| | < _.-' | `:$$:' :$$$$$: `. `. .-' : `"--' | `-. \ :##. == .###. `. `. `\ |##: :###: | > > |#' `..'`..' `###' x: / / \ xXX| / ./ \ xXXX'| / ./ /`-. `. / / : `- ..........., | / .' | ``:::::::' . |< `. | ``` | x| \ `.:``. | .' /' xXX| `:`M`M':. | | ; /:' xXXX'| -'MMMMM:' `. .' : /:' |-'MMMM.-' | | .' /' .'MMM.-' `'`' : ,' |MMM< | `' |tbap\ \ :MM.-' \ | .'' \. `. / / .:::::::.. : / | .:::::::::::`. / | .:::------------\ / / .'' >::' / `',: : .' `:.:' No.7 A computer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ,----------------, ,---------, ,-----------------------, ," ,"| ," ,"| ," ," | +-----------------------+ | ," ," | | .-----------------. | | +---------+ | | | | | | | -==----'| | | | I LOVE DOS! | | | | | | | | Bad command or | | |/----|`---= | | | | C:\>_ | | | ,/|==== ooo | ; | | | | | // |(((( [33]| ," | `-----------------' |," .;'| |(((( | ," +-----------------------+ ;; | | |," /_)______________(_/ //' | +---------+ ___________________________/___ `, / oooooooooooooooo .o. oooo /, \,"----------- / ==ooooooooooooooo==.o. ooo= // ,`\--{)B ," /_==__==========__==_ooo__ooo=_/' /___________," No.8 翼龙 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 __----~~~~~~~~~~~------___ . . ~~//====...... __--~ ~~ -. \_|// |||\\ ~~~~~~::::... /~ ___-==_ _-~o~ \/ ||| \\ _/~~- __---~~~.==~||\=_ -_--~/_-~|- |\\ \\ _/~ _-~~ .=~ | \\-_ '-~7 /- / || \ / .~ .~ | \\ -_ / /- / || \ / / ____ / | \\ ~-_/ /|- _/ .|| \ / |~~ ~~|--~~~~--_ \ ~==-/ | \~--===~~ .\ ' ~-| /| |-~\~~ __--~~ |-~~-_/ | | ~\_ _-~ /\ / \ \__ \/~ \__ _--~ _/ | .-~~____--~-/ ~~==. ((->/~ '.|||' -_| ~~-/ , . _|| -_ ~\ ~~---l__i__i__i--~~_/ _-~-__ ~) \--______________--~~ //.-~~~-~_--~- |-------~~~~~~~~ //.-~~~--\ No.9 不知道像啥 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) ( /+++=)) ((=+++\ /++++++++// \\+++++++++\ /++++++++++//( /\ )\\++++++++++\ /+++++++++++// \\^^// \\+++++++++++\ _/++++++++++++// {{@::@}} \\++++++++++++\_ /+++++++++++++(( {\/} ))+++++++++++++\ /+++++++++++++++\\ <**> //+++++++++++++++\ /+++++++++++++++++\\ / VV \ //+++++++++++++++++\ /+++++++++++++++++++\\/******\//+++++++++++++++++++\ |+/|++++++++++/\++++++(***/\***)++++++/\++++++++++|\+\ |/ |+/\+/\+/\/ \+/\++\**|**|**/++/\+/ \/\+/\+/\+| \| v |/ V V V V \+\|*|**|*|/+/ V v V V \| v v /*|*|**|*|*\... v (**|*|**|*|**). . __\*|*|**|*|*/__. . (vvv(VVV)(VVV)vvv). . ............../ / / ............../ (( No.10 性感的 girl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .::::. .::::::::. ::::::::::: ..:::::::::::' '::::::::::::' .:::::::::: '::::::::::::::.. ..::::::::::::. ``:::::::::::::::: ::::``:::::::::' .:::. ::::' ':::::' .::::::::. .::::' :::: .:::::::'::::. .:::' ::::: .:::::::::' ':::::. .::' :::::.:::::::::' ':::::. .::' ::::::::::::::' ``::::. ...::: ::::::::::::' ``::. ```` ':. ':::::::::' ::::.. '.:::::' ':'````.. No.11 最后一张龙珠 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 MMMMM MMMMMM MMMMMMM MMMMMMMM . MMMMMMMMM HMMMMMMMMMM MMMMMMMMMMMM M MMMMMMMMMMMMM M MMMMMMMMMMMMM M MMMMMMMMMMMMM: oMMMMMMMMMMMMMM .MMMMMMMMMMMMMMo MMMMMMMMMMMMMMM M MMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMM. oMMMMMMMMMMMMMMM.M MMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM oMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM: H MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM . MMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM M MMMMMM .MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM M MMMMMMMMMM MM. MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM M MMMMMMMMMMMM MM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM .MMMMMMMMMMMMMM MM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM .MMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMM.MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM HMMMMMMMMMMMMMMMMMMMMM.MMMMMMMMM.MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMM MMM.oMMMMMMM..MMMMMMMMM:MMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMM MM..MMMMMMM...MMMMMMM. MMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMM ..MMMMMM...MMMMMM ..MMMMMMMMMMMMMMMMMMM MMMMMMM:M.MMM.M.. MMMMM M..MMMMM...MMMMMMMMMMMMMMMMMM MMM MMMM. .M..MM.M...MMMMMM..MMMMM.. MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM . MMMM..M....M.....:MMM .MMMMMM..MMMMMMM...MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMM.M.. ...M......MM.MMMMM.......MHM.M .MMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMM..MM. . MMM.....MMMMMM.M.....M ..MM..M MMMMMMMMMMMMMMMMMMM .MMMMMHMM. ..MMMM. MMM............o..... . .MMMMMMMMMMMMMMM MMM. M... .........................M..:.MMMMMMMMMMMM oMMM............ .................M.M.MMMMMMMMM .....MM........................ . MMMMMM M.....M.....................o.MM.MMMMMMMM. M........................M.. ...MMMMMMMMMMMMMo :....MMM..............MMM..oMMMMMMM M...MMM.............MMMMMMM .............:MMMMMMMM M..... MMM.....M M M............. ................M ooM.................MM MoMMMMMoooM MMoooM......................MoooooooH..oMM MHooooMoM.....................MMooooooM........M oooooooMoooM......... o........MoooooooM............ Mooooooooooo.......M.........Moooooooo:..............M MooMoooooooooM...M........:Mooooooooooo:..............M M..oooooooooooo .........Mooooooooooooooo..............M M...Mooo:oooooooo.M....ooooooooooooooooooo..M...........M ...oooooMoooooooM..Mooooooooooooo:oooooooM.M...........M. M...ooooooMoo:ooooMoooooooooooooHoooooooooH:M. ...........: M..MoooooooMoooooooooooooooooo:ooooooMooooMoM..............M M..ooooooooooMooooooooooooooHoooooooMooHooooM...............M ...ooooooooooooooooooo:MooooooooooooooMoMoooM................ M...oooooooooooooooooooooooooooooooooooooMooMM................M ...MooooooooooooooooooooooooooooooooooooooooMo ................ ...MooooooooooooooooooooooooooooooooooooooooM M................M M...ooooooooooooooooooooooooooooooooooooooooM ................M ...MoooooooooooooooooooooooooooooooooooooooMM .:............... .....MooooooooooooooooooooooooooooooooooooMoo .............M M...... ooooooooooooooooooooooooooooooooooooM M..............M M........MooooMMM MM MM MMMMMMMMMooooooooM M...............M .........HM M: MM :MMMMMM M M............... M..........M M MoM M M................M M.........:M MoH M M M MooooHoooMM. M M...............M M..........Moooo MMooM oooooMooooooooM M..............H M.........MooooM Mooo : ooooooMooooMoooM M........ . .o.M H.. .....ooooo oooo M MooooooooooooooM M... MMMMMMMMMMM MMMMMMMMMMooooM M oooo . ooooooMooooooooM .MMMMMMMMMMMMMMM MMMMMMMMMMooooH : ooooH oooooooooooooooo MMMMMMMMMMMMMMM MMMMMMMMMMoooo ooooM Moooooooooooooooo .MMMMMMMMMMMMMMM MMMMMMMMMMoooo ooooM MooooooooooooooooM MMMMMMMMMMMMMMM MMMMMMMMMMoooM ooooM ooooooooooooooooo MMMMMMMMMMM:M MMMMMMMMMMoooM MooooM oooooooooooMoooooo MH........... . ......Mooo. MooooM oooooooooooooooooo M............M M.M......oooo MooooM Moooooooooooooooooo: .........M..... M.M.....Moooo MooooM ooooooooooooooooooM .M............ .......MooooH MooooM oooooooooMoooooooooo M..o...M..o....M .o....HMooooM MooooH MooooooooMooooooooooM .:M...M.......M M..M.....MoooM :oooo: .MooooooooHooMoooooooooM M M... ..oM.M M...M.:.Mooo. MMMMooooo oooooooooooMoooooooooooooM ....M. M M:M..o.Moooooooooooooo MooooooooooooooMooooooooooooM .Mo MooooooooooooooMooooooooooooMoMoooooooooooooo Mooooooooooooooo:ooooooooooooooooooooooooooooo ooooooooooooooooMooooooooooMoooooooooooooooooo ooooooooooooooooMoooooooooooMooooooooooooooooHo ooMooooooooooooooMoooooooooooooooooooooooooooMoM MooMoooooooooooooo.ooooooooooooooooooooooooooo:oM MoooooooooooooooooooooooooooooooooooooooooooooooM MoooMooooooooooooooMooooooooooooooooooooooooooooo. MoooMooooooooooooooMoooooooooooooooooooooooooMooooM MooooooooooooooooooMoooooooooooooooooooooooooMoooooM MooooMoooooooooooooMoooooooooooooooooooooooooMoHooooM ooooooMooooooooooooooooooooooooooooooooooooooooMoMoooM MooooooooooooooooooooMooooooooooooooooooooooooooMoooooH: MoooooooMooooooooooooMoooooooooooooooooooooooooooooHoooM MooooooooMoooooooooooMoooooooooooooooooooooooooMoooMooooM Moooooooooooooooooooooooooooooooooooooooooooooo.oooMooooo MoooooooooooooooooooooooooooooooooooooooooooooMoooooooooM MooooooooooooooooooooMoooooooooooooooooooooooooooooooooM MooooooooooooooooooooMHooooooooooooooooooooMoooo:ooooo MMooooooooooooooooooMoMHoooooooooooooooooooooooMooooo MMoooooooooooooooMMooo MMooooooooooooooooooooooooooM MMMoooooooooooooMooooo oooooooooooooooooooooMooooo MooMMoooooooooMoooMMoM ooooHooooooooooooooooMooooM MooooMooooooMooooMoooM MoooooMoooooooooooooMooooo ooooooMMooooooooMooooM MoooooooooMooooooooooooooM HooooooMoooooooMooooM HoooooooHooMooooooooooooo oooMoooooooooHoooM MoooooooooMoooooooooM HooooooooooooHM MooooooooMMoooooooM MMMMMMMMMMMMMM Moooooo:MooooHMM MMMMMMM: ... MMMMMMMMMMMMMM M............M MMMMMMMMM .... M.MM.......... M.............M M ..............MM M.............. MMMMM............MMMM ..MMMMMMMM ....M MMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMM...M .MMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM :MMMMMMMMMMMMMMMMMMH MMMMMMMMMMMMMMMMMMM By EBEN Jérôme MMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMM HMMMMMM 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/10/18
articleCard.readMore

HTTP常见状态码

当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。 HTTP状态码的英文为HTTP Status Code。 1开头 1XX 1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态代码。 100 (继续) 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。 101 (切换协议) 请求者已要求服务器切换协议,服务器已确认并准备切换。 2开头 2XX 2xx (成功)表示成功处理了请求的状态代码。 200 (成功) 服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。 201 (已创建) 请求成功并且服务器创建了新的资源。 202 (已接受) 服务器已接受请求,但尚未处理。 203 (非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源。 204 (无内容) 服务器成功处理了请求,但没有返回任何内容。 205 (重置内容) 服务器成功处理了请求,但没有返回任何内容。 206 (部分内容) 服务器成功处理了部分 GET 请求。 3开头 3XX 3xx (重定向) 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。 300 (多种选择) 针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。 301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。 302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。 303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。 304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。 305 (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。 307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。 4开头 4XX 4xx(请求错误) 这些状态代码表示请求可能出错,妨碍了服务器的处理。 400 (错误请求) 服务器不理解请求的语法。 401 (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。 403 (禁止) 服务器拒绝请求。 404 (未找到) 服务器找不到请求的网页。 405 (方法禁用) 禁用请求中指定的方法。 406 (不接受) 无法使用请求的内容特性响应请求的网页。 407 (需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。 408 (请求超时) 服务器等候请求时发生超时。 409 (冲突) 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。 410 (已删除) 如果请求的资源已永久删除,服务器就会返回此响应。 411 (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。 412 (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。 413 (请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。 414 (请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。 415 (不支持的媒体类型) 请求的格式不受请求页面的支持。 416 (请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。 417 (未满足期望值) 服务器未满足"期望"请求标头字段的要求。 5开头 5XX 5xx(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。 500 (服务器内部错误) 服务器遇到错误,无法完成请求。 501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。 504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。 505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/9/26
articleCard.readMore

关于5G的SA与NSA架构

今天又一次看了何同学的5G的视频,从视频上看了关于何同学讲联通试运行的是NSA架构,SA架构还在研发试运行中,我就纳闷了,作为一个计算机行业的学生,怎么不明白这个架构名词,是自己out了么,后来上网找了资料,原来这个是通讯技术的专业名词,今天算是恶补了一下。 2018年12月份的时候,我们其实已经发布了“半个”5G标准。是的没错,那个时候是“非独立组网(NSA)”的5G标准。 现在在等待SA组网标准。 举个栗子 为了大家能够通俗易懂的描述这5G的两个架构,给大家举个栗子: 话说这个张老板,家里养着一头肥朵1, 肥朵1 的小日子过得也很不错! 但是突然有一天 肥朵 的价钱大涨,张老板想再养一只 肥朵,可是由于资金不足,(理想状态是一头 肥朵 需要有一个单独的房间)张老板很是苦恼,于是张老板给想出了两套方案: 方案一:养 肥朵2 的时候,给 肥朵2 在建一个房子; 方案二:把 肥朵1 和 肥朵2 养在一起,只需将房子变大一点就可以了。 方案一按照理想状态的话,有一个比较大的缺点就是花费比较多,这就比较难受了……,但是 肥朵 比较开心~~~~ 方案二是将房子重新升级变大即可作为资金花费最少,同时能够获得最大效益,张老板选择的面比较大,可就难受了 肥朵,唉,万恶的资本家。 想必大家就已经知道制约SA和NSA的因素了吧!就是money~~~ 而 肥朵1 我们就可以看作是 4G基站 ,而把 肥朵2 看作是 5G基站; 按照方案一 肥朵1 的房子 可以看作是 4G核心网络; 按照方案一 肥朵2 的房子 可以看作是 5G核心网络; 按照方案二的话是将 肥朵1 和肥朵2的房子 看作是4G核心网络,而 肥朵1 也是 4G基站,肥朵2 也是5G基站,请看下图 : 简而言之,之所以有SA与NSA架构主要是因为运营商愿意不愿意出钱重新部署。 5G网络架构 目前讲到的都是 5G网络架构 中的两种,其实还有以下多种选项: 这8个选项分为独立组网和非独立组网两组。其中选项1,2,5,6是独立组网,选项3,4,7,8是非独立组网。非独立组网的选项3,4,7还有不同的子选项。 在这些选项中,选项1早已在4G结构中实现,选项6和选项8仅是理论存在的部署场景,不具有实际部署价值,标准中不予考虑。 独立组网SA 选项1:就是单纯的4G核心网+4G核心站,就是纯4G组网架构; 选项2:可以看得出来,就是我们上面例子的方案一,纯5G组网架构; 选项5:只是对原本4G基站做了升级,变为增强型4G基站【eLTE eNB】; 选项6:就是将原本4G基站换为5G基站,然而核心网还是4G。 非独立组网NSA 个人感觉NSA还是要比SA复杂一些,但是这样是比较省钱的一种方式,在节省资金的情况下,不影响用户5G体验,相信这也是当下某某运营商的升级过渡期的优先解决方案。 在非独立组网主要是分为3系、7系、和4系,但是单纯的说你是不会明白的,通信的例外,好吧,你就是不会明白?,请看下面的介绍: NSA3系 可以看到3系的解决方案就是将原有4G网络升级加5G基站,让手机实现“双通道上网”,但是控制面在4G,用户面4G与5G互相向用户终端提供支持,这样的好处就是可以在5G升级过度时期起到临时代替作用,但是这样会加大4G基站的负荷,本来好好的够1头肥朵居住,突然之间加到了10头肥朵,想想也是有些吃不消的。 NSA7系 看到了7系你是不是有了对3系的对比,相比而言,7系有三点改进: 将3系4G基站升级为增强型4G基站; 将原有4G核心网淘汰掉,采用5G核心网; 最后一点改进是7x中,将原有4G基站对接5G核心网络升级为增强型4G基站只传输控制流,将数据流负荷直接转移到了5G基站上。 现在这种方式,特别是7x,对于运营商在初中期过度时期,这种方式又节省成本,同时又能在过渡期给用户提供高质量5G服务,相信现在XX运营商就这么搞的吧。 NSA4系 可以看到,到达4系阶段,把控制权交了出来,5G就距离SA架构只差一步了吧,这种状态应该是目前转型阶段后期的事情了吧!这样的架构方式同时能享受到4G的低费率,5G的高速率,你是不是有所期待!!! 最后感悟 5G时代所谓的NSA与SA架构只不过是4G时代向5G时代过渡的不同方案,所谓的NSA又何尝不是对于SA的追求,5G时代大家的感觉是速度真快,然而在快的同时我们又怎么抓住机遇,获得5G时代的红利呢? 今天做这个文章借鉴了不少前辈的文章及视频知识,并不是想借鉴5G这个热点,自己涉猎的知识面还是狭隘,所学所思才是真正的进步。 PS:举个栗子是我自己P图?,最后讲解方案的用图均引用至51CTO,看着画的不错自己就偷个懒,不自己构图了,感谢这个知识共享的年代,谢谢。 看到这里,你要是感觉此文对你有所帮助,希望你转发给更多的人看到,5G时代的我们,更应该知识共享。。。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/8/17
articleCard.readMore

看完了《影》,黄海海报展走一波

关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/24
articleCard.readMore

软件工程 用例习题

一、选择题 1、 下列关于使用用例的目的,正确的是( B )。 A.确定系统应具备哪些功能 B.为系统的功能提供清晰一致的描述,方便开发人员传递系统的需求 C.为系统验证工作打下基础 D.减少程序员的编码工作量,提高开发效率 2、 识别用例的最好方法是从哪里入手( B ) A.系统边界 B.参与者 C.事件 D.用例 3、 参与者(Actor)与用例之间的关系是( C ) (A)包含关系 (B)泛化关系 (C)关联关系 (D)扩展关系 4、 用户在银行员工的指导下,使用 ATM 机,查阅银行帐务系统的个人帐务数据,并打印其 个人用户账单。在上述过程中,对 ATM 机管理系统而言,哪个不是系统的参与者( B )。 A.用户 B.银行员工 C.打印系统 D.帐务系统 5、 用例从用户角度描述系统的行为。用例之间可以存在一定的关系。假设在“图书馆管理系 统”用例模型中,所有用户使用系统之前必须通过“身份验证”,“身份验证”可以有“密码 验证”和“智能卡验证”两种方式,则“身份验证”与“密码验证”和“智能卡验证”之间 是( B )关系。 A.关联 B.包含 C.扩展 D.泛化 6、 用例用来描述系统在事件做出响应所采取的行动。用例之间是具有相关性的。在一个“订单输入子系统”中,创建新订单和更新订单都需要检查用户账号是否正确。那么,用例“创建新订单”、“更新订单”与用例“检查用户账号”之间是( A )关系。 A.包含(include) B.扩展(extend) C.分类(classification) D.聚集(aggregation) 7、 如果用例 A 与用例 B 相似,但 A 的功能较 B 多,A 的动作序列是通过在 B 的动作序列中 的某些执行点上插入附加的动作序列而构成的,则称( C )。 A.用例 A 扩展用例 B B.用例 A 包含用例 B C.用例 A 泛化用例 B D.用例 A 实现用例 B 8、 如果用例 A 与用例 B 相似,但 A 的动作序列是通过改写 B 的部分或者扩展 B 的动作而获 得的,则称( B ) A.用例 A 实现用例 B B.用例 A 泛化用例 B C.用例 A 扩展用例 B D.用例 A 包括用例 B 9、 如果用例 B 是用例 A 的某项子功能,并且建模者确切地知道在 A 所对应的动作序列中何 时将调用 B,则称( C ) A.用例 A 扩展用例 B B.用例 A 泛化用例 B C.用例 A 包含用例 B D.用例 A 实现用例 B 二、简答题 对于一个电子商务网站而言,以下哪些不是合适的用例,指出并说明理由。 输入支付信息 将商品放入购物车 结账 预订商品 用户登录 邮寄商品 查看商品详情 输入支付信息:太小 邮件商品:系统功能之外 查看商品详情:太小 三、分析题 1、 某电话公司决定开发一个管理所有客户信息的交互式网络系统。系统功能如下: 浏览客户信息:任何使用 Internet 的网络用户都可以浏览电话公司所有的客户信息(包括 姓名、住址、电话号码等)。 登录:电话公司授予每个客户一个账号。拥有授权账号的客户,可以使用系统提供的页 面设置个人密码,并使用该账号和密码向系统注册。 修改个人信息:客户向系统注册后,可以发送电子邮件或者使用系统提供的页面,对个 人信息进行修改。 删除客户信息:只有公司的管理人员才可以删除不再接受公司服务的客户的信息。 请绘制出相应的用例图 2、 档案管理系统功能性需求说明如下: 用户进入系统前,首先要求用户进行登录,验证通过后允许用户进入本系统操作。用户 的密码需要进行加密算法。 用户登录后可以修改自己的注册信息,包括修改用户密码、每页显示行数等信息,不允 许修改用户名、姓名和部门等信息。 系统管理员可以增加系统用户、删除系统用户、修改用户的相关属性、修改用户的权限 表。 档案室人员可以对档案文件信息或者档案案卷信息进行管理,删除时只是做删除标记。 档案借阅管理人员处理外借登记、归还记录和电子借阅申请两部分。外借模块实现档案 文件的借出登记和归还登记功能。一般用户提出借阅电子文档的请求后,被同意阅读后,文 件将被发送给申请人。 本系统需要实现数据的备份和恢复机制。数据备份操作可以按年度、档案种类等条件做 部分备份或完全备份。数据恢复就是将备份的数据恢复到数据库中。具有批量备份和恢复的 功能。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/4
articleCard.readMore

软件工程 活动图、状态图、顺序图实验题

活动图绘图有开始结尾的标志,简称黑点开始、牛眼结束。 1、请应用活动图描述同学们每次参加考试的过程是怎么样的? 开始。 学生进入考场。 监考教师核对检查证件,发放试卷。 学生对号入座。 学生开始答题。 学生交卷。 监考教师收取试卷。 结束。 2、 小张想去吃饭,如果必胜客有空位或最多只等待 15 分钟,则进必胜客吃饭,否则去肯德鸡吃 饭。请画出相应的活动图。 3、开工奠基流程如下: 如果资金到位,则准备施工现场。当领导也到场时,开工奠基开始 请画出相应的活动图 4、对于“远程网络教学系统”,学生登录后可以下载课件。在登录时,系统需要验证用户的登录 信息,如果验证通过系统会显示所有可选服务。如果验证失败,则登录失败。当用户看到系统显示的 所有可选服务后,可以选择下载服务,然后下载需要的课件。下载完成后用户退出系统,系统则会注 销相应的用户信息。画出学生下载课件的活动图。 5、在“远程网络教学系统”中,系统管理员登录后可以处理注册申请或者审核课件。在处理注册 申请后,需要发送邮件通知用户处理结果;在审核完课件后,需要更新页面信息以保证用户能看到最 新的课件,同时系统更新页面。当完成这些工作后,系统管理员退出系统,系统则注销系统管理员账号。画出系统管理员的工作活动图。 6、根据以下叙述,绘制打印社的“打印机”的状态图: 未接到工作命令时,打印机处于闲置状态。接到打印命令后,转入打印状态,完成打印后又回到 闲置状态,等待命令。 若打印时发现没纸,则进入缺纸状态。发出警告等待装纸。装纸完成后又进入闲置状态。 若打印时发现卡纸,则进入卡纸(故障)状态。发出警告等待排除故障。故障排除后又进入闲置 状态。 7、手机开机时,处于空闲状态;当用户开始呼叫某人时,手机进入拨号状态;如果呼叫成功, 进入通话状态;如果呼叫不成功,重新进入空闲状态。在空闲状态被呼叫,进入响铃状态;如果用户 接听,进入通话状态;如果一分钟不接听,重新进入空闲状态。请绘制手机的状态图。 8、用顺序图描述一位学生张三在 ATM 系统上取 100RMB 的“取款”流程,其过程要包括如下 需求。 ➢ 学生张三取款时向 ATM 系统插入银行卡。 ➢ ATM 系统的读卡机读取卡号信息。 ➢ 屏幕显示用户的操作界面。 ➢ 屏幕提示用户输入密码。 ➢ 用户根据提示输入密码。 ➢ 系统检测用户的密码是否有效。 ➢ 屏幕提示选择事务的操作,如查询、存款、取款等。 ➢ 张三选择取款事务。 ➢ 屏幕提示输入取款金额。 ➢ 张三根据提示输入 100RMB。 ➢ 系统准备向张三的账号执行取钱的操作。 ➢ 系统检测张三的余额是否大于等于 100RMB。 ➢ 系统从张三的账户上扣除 100RMB。 ➢ 吐钱机将 100RMB 吐出给学生。 ➢ 系统打印取款凭据。 ➢ 系统退卡。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/4
articleCard.readMore

软件工程 交互预览、组件、部署习题

1、软件部署是( B ) A:部署软件构件 B:部署软件程序 C:部署软件模型 D:部署软件制品 2、下面说法正确的是( C ) A:制品就是制成品 B:制品是软件模块 C:制品是被部署的软件单元 D:制品是软件构件 3、下列说法不正确的是( B )。 A)在用例视图下可以创建类图 B)在逻辑视图下可以创建组件图 C)在逻辑视图下可以创建包 D)在构建试图下可以创建构件 4、在组件图中,( D )用于显示构件之间的关联关系。 A)依赖关系 B)构件 C)包 D)节点 5、 下面哪个符号代表部署图的节点( C ) 6、下列不属于部署图中的设备类型的是:( B )。 A)打印机 B)计算机 C)扫描仪 D)路由器 7、在绘制部署图时,如果要描述处理器之间或处理器与设备之间通过以太网进行连接的关系时,使用下列哪一种构造型( A )。 A)《Ethernet》 B)《parallel》 C)《TCP/IP》 D)《Internet》 8、在UML中,____B_____图显示了一组类、接口、协作以及它们之间的关系。 A、状态图 B、类图 C、用例图 D、部署图 9、节点是存在于运行时并代表一项计算资源的物理元素,没有计算能力的节点称为___B____ A.处理器 B.规范 C.接口 D.设备 10、下图中,表示“节点”的图为_____C_______。 11、_____B_____图可以用来描述系统硬件的物理拓扑结构以及在此结构上运行的软件。 A、用例图 B、类图 C、部署图 D、活动图 12、____D_____是系统中遵从一组接口且提供实现的一个物理部件,通常指开发和运行时类的物理实现。 A、部署图 B、类 C、接口 D、构件 13、下列不属性构件的特征的是______A________。 A、可替换 B、通过接口实现或提供服务 C、可以被一个或多个实现 D、只能是代码的形式 14、对于比如源代码文件及数据文件,并不是直接地参与可执行系统,我们称这类构件为____C_____。 A、实施构件 B、工作产品构件 C、执行构件 D、质量构件 15、___B_______是可复用的,提供明确接口完成特定功能的程序代码块。 A、模块 B、函数 C、用例 D、构件 16、组件图用于对系统的静态实现视图建模,这种视图主要支持系统部件的配置管理,通常可以分为四种方式来完成,下面哪种不是其中之一( B ) (A)对源代码建模 (B)对事物建模 (C)对物理数据库建模 (D)对可适应的系统建模 17、( D )由节点和节点之间的联系组成,描述了处理器、设备和软件构件运行时的体系结构。 A.组件图 B.状态图 C.顺序图 D.部署图 18、( C )的基本元素有节点、构件、对象、连接、依赖等。 A.组件图 B.状态图 C.部署图 D.顺序图 19、下面关于部署图的说法中,错误的是( A ) A 部署图描述系统运行时的软件和硬件的物理结构,用于对系统的物理方面建模。(组件图) B 处理器和设备的区别在于是否具有计算能力。 C 部署图描述的是系统物理模型的布置,实际节点间的连接必须用一段导线、电缆或其它的方式连接。 D 一个系统可以有多个部署图。 20、部署图中的结点具有以下哪些方面的内容( ABC ) (A)计算能力 (B)基本内存 (C)位置 (D)接口 21、当需要说明系统的静态实现视图时,应该选择( A )。 A.组件图 B.协作图 C.状态图 D.部署图 22、当需要说明体系结构的静态实施视图时,应该选择( D ) A.协作图 B.对象图 C.活动图 D.部署图 23、( D )是系统中遵从一组接口且提供实现的一个物理部件,通常指开发和运行时类的物理实现 A.部署图 B.类 C.接口 D.组件 24、组件图用于对系统的静态实现视图建模,这种视图主要支持系统构件的配置管理,通常可以分为四种方式来完成,下面哪种不是其中之一( B )。 A.对源代码建模 B.对可执行组件建模 C.对数据库建模 D.对事物建模 25、( A )是用来反映代码的物理结构。 A、组件图 B、用例图 C、类图 D、状态机 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/4
articleCard.readMore

软件工程 类图实验题

1.请按下述要求绘制类图。 一个年级里有 3 到 5 个班级。 一个班级有 1 到 40 名学生。 1 个班级有 1 名担任班主任。 2.请按下述要求绘制出书橱的类图。 可以把书放到书橱里。 书橱的门有木制的门或玻璃制的门。 3.请按下述要求绘制出网上商店的类图。 为了一次可以购买多件商品,为每个顾客准备一个购物车。 购物车里可以装入 10 件商品。 顾客分会员及非会员两类。 4.看图回答下面问题 类 Student 和类 Course 之间是什么关系?并用文字性语言描述该类图表达意思。 答:关联关系 该图描述的是:学生和课程之间的选择关系,一个学生可以选择多门课程,一门课程可以被多个学生所选择。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/4
articleCard.readMore

软件工程 类图习题

依赖(Dependency): 虚线箭头表示 关联(Association):实线箭头表示 聚合(Aggregation):带空心菱形头表示(整体和局部关系) 组合(Composition):带实心菱形头的实线表示 泛化(Generalization): 带空心箭头的实线线表示( 继承关系) 实现(Realization):空心箭头和虚线表示 一、选择题 1、在认识过程中,下面哪个不是对象的要素( D ) A:认识的指向物 B:认识者 C:认识指向物在认识者主观中的反映 D:认识的背景 2、下面哪一个对对象的说法不正确( B ) A:客观实体 B:事物的对立面 C:认识的指向物 D:软件的一个基本单位 3、下面属性命名不正确的是( A ) A *BirthDay:Date B #studentBirthDay:Date=1999-10-21 C -price:float=12.01{R/W} D +studentName:String=“张敏” 4、指出下面不合适的类名( B ) A:材料 B:事物 C:订单 D:会员 5、在类图中,下面哪个符号表示继承关系( C ) 6、在类图中,“#”表示的可见性是( B ) (A)Public (B)Protected (C)Private (D)Package 7、在类图中,下面哪个符号表示实现关系( B ) 8、在类图中,哪种关系表达总体与局部的关系( D ) (A)泛化 (B)实现 (C)依赖 (D)聚合 9、UML中类的有三种,下面哪个不是其中之一( D ) A.实体类 B.边界类 C.控制类 D.主类 10、在UML中,类之间的关系有一种为关联关系,其中多重性用来描述类之间的对应关系,下面哪个不是其中之一( D ) A. 0..1 B. 0..* C. 1..* D. *..* 11、通常对象有很多属性,但对于外部对象来说某些属性应该不能被直接访问,下面哪个不是UML中的类成员访问限定性( C ) A.公有的(public) B.受保护的(protected) C.友员(friendly) D.私有的(private) 12、在一个课程注册系统中,定义了类CourseSchedule和类Course,并在类CourseSchedule中定义了方法add(c:Course)和方法remove(c:Course),则类CourseSchedule和类Course之间的关系是:(B) A、泛化关系 B、组成关系 C、依赖关系 D、包含关系 13、类A的一个操作调用类B的一个操作,且这两个类之间不存在其他关系,那么类A和类B之间是( C )关系。 A 实现 B、关联 C、 依赖 D、 泛化 14、在UML中下列图形代表什么关系?(A) A、组合关系 B、 依赖关系 C、聚合关系 D、泛化关系 15、在UML中下列图形代表什么关系?( D ) A、组成关系 B、 依赖关系 C、聚集关系 D、泛化关系 16、汽车(Car)由轮子、发动机、油箱、座椅、方向盘等组成。那么car类和其他类(Wheel、Engin、Tank、Chair、SteeringWheel)之间的关系是:( A ) A、泛化关系 B、实现关系 C、包含关系 D、组合关系 17、在下面的图例中,哪个用来描述注释( B ) 18、消息传递是对象间通信的手段,一个对象通过向另一个对象发送消息来请求其服务,一个消息通常包括:( A ) A、发送消息的对象的标识、调用的发送方的操作名和必要的参数 B、发送消息的类名和接收消息的类名 C、接收消息的对象的标识、调用的接收方的操作名和必要的参数 D、接收消息的类名 19、在一个网络游戏系统中,定义了类Cowboy和类Castle,并在类Cowboy中定义了方法open(c:Castle)和方法Close(c:Castle),则类Cowboy和类Castle之间的关系是:……( C ) A、依赖(dependency)关系 B、组成(composition)关系 C、泛化(generalization)关系 D、包含(include)关系 20、根据下面的代码,判断下面那些叙述是正确的?( B ) 1 2 3 4 5 6 public class HouseKeeper{ private TimeCard timecard; public void clockIn(){ timecard.punch(); } } A、类HouseKeeper和类TimeCard之间存在关联(Association)关系; B、类HouseKeeper和类TimeCard之间存在泛化(Generalization)关系; C、类HouseKeeper和类TimeCard之间存在实现(Realization)关系; D、类HouseKeeper和类TimeCard之间存在依赖(dependency)关系 21、UML关系包括关联、聚合、泛化、实现、依赖等5种类型,对应的编号分别为A、B、C、D和E,请将合适的关系填写在下列描述的( )中。 ① 用例及参与者之间是( E )关系。 ②类A的一个操作调用类B的一个操作,且这两个类之间不存在其他关系,那么类A和类B之间是( C )关系。 ③在学校中,一个学生可以选修多门课程,一门课程可以由多个学生选修,那么学生和课程之间是( A )关系。 ④森林和树木之间是( B )关系。 22、已知类A需要类B提供的服务,下列所描述的四种情况中,哪种情况不好把类A和类B之间的关系定义成依赖关系 ( C ) A、类A中存在两个操作都需要访问类B的同一个对象 B、类A的某个操作内部创建了类B的对象,而其他操作均与类B无关 C、类A的某个操作其参数是类B的对象,而其他操作均与类B无关 D、类B是一个全局变量 23、“一个研究生在软件学院做助教(teaching assistant),同时还在校园餐厅打工做收银员(cashier)。也就是说,这个研究生有3种角色:学生、助教、收银员,但在同一时刻只能有一种角色。” 根据上面的陈述,下面哪个设计是最合理的?( C ) 24、类X与类Y有许多相同的属性和行为,但是它的行为与类Y稍微有所不同,这时可以认为类X是类Y的一种特例;则类X和类Y之间是( A )关系。 A 、泛化关系 B、 关联关系 C、 依赖关系 D、 实现关系 25、关于类和对象的关系,下列说法中哪个是错误的 ( C ) A、每个对象都是某个类的实例 B、每个类某一时刻必定存在对象实体 C、类是静态的描述 D、对象是动态的实例 二、填空题 1、 下图中类的名字是: _ Login_类中的成员属性是: _sName、sPass _ 类中的行为(方法)是:checkUser。 2、类描述具有相同性质的一组对象的(集合),类用(new)来表示。 3、在设计阶段,可以把类分为(控制类)、边界类和(实体类)等类型。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/3
articleCard.readMore

软件质量测试知识点

1、什么是软件、软件的特征有哪些? 软件是计算机程序、规程以及可能的相关文档和运行计算机系统需要的数据。即计算机程序、规程、文档和软件系统运行所必需的数据。 软件的特征: 软件是由开发产生,不是用传统方法制造的; 软件不会像硬件那样有磨损; 软件不能通过已有构件组装,只能自己定义。 2、软件分为哪几类? 系统软件、应用软件、WEB软件、工程和科学软件、嵌入式软件、产品线软件、人工智能软件等。 3、什么是软件质量、软件质量从哪几个方面看? 软件质量 是指软件系统或系统中的软件部分的质量,既满足用户需求,也包括功能需求和性能需求的程度。 从哪几个方面来看软件质量? 软件结构方面:软件应具有良好的结构; 功能和性能方面:其软件应能够按照既定的工作要求工作,并且与明确规定的功能、性能需求一直; 开发标准与文档方面:软件开发应用必须和明确沉稳的开发标准一致,遵循软件开发准则,做到软件文档资料齐全。 4、软件测试与软件调试的区别是什么? 软件测试:在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程; 软件调试:将编制的程序投入实际运行前,用手工或编译程序等方法进行测试,修正语法错误和逻辑错误的过程; 测试是为了发现软件中存在的错误,调试是为了证明软件开发的正确性; 测试以已知条件开始,使用预先定义的程序,且有预知的结果,不可预见的仅是程序是否通过测试. 调试一般是以不可预知的内部条件开始,除统计性调试外,结果是不可预见的; 测试是有计划性的,需要进行测试设计,调试是不受时间约束的; 测试经历发现错误、改正错误、重新测试的过程,调试是一个推理的过程。 5、软件测试的方法有哪些? 静态方法和动态方法:静态方法分为代码检查、静态结构分析、代码质量度量,动态方法分为功能确认与接口测试、覆盖率分析及性能分析; 黑盒测试方法:主要有等价类划分、边值分析、因果图、错误推测、穷举输入测试方法; 白盒测试方法:逻辑覆盖、域测试、路径测试、程序插桩、程序编译; 灰盒测试,介于黑盒白盒测试之间; 软件开发阶段的测试方法:需求测试、单元测试、集成测试、性能测试、容量测试、配置测试、安装测试、安全性测试。 6、什么是黑盒测试、白盒测试和灰盒测试? 黑盒测试也称功能测试或数据驱动测试,是已知软件所需功能,通过测试来检测每个功能是否都能正常使用。 白盒测试也称结构测试或逻辑驱动测试,知道软件内部的工作过程,可通过测试来检测软件内部的动作是否按照规格说明书所规定正常运行,并且按照软件内部的结构测试程序来检测程序中的每条道路是否都能按照预定要求进行正常工作,而不考虑功能是否正确。 灰盒测试介于黑盒与白盒之间,关注对于输入的正确性,同时也关注内部表现。 7、什么是软件质量控制模型TSQC? TSQC过程是一个调节和控制那些影响软件质量的参数的过程。隐形软件质量的参数如下: 产品:所有可交付物; 过程:所有活动的集合; 资源:活动的物质基础(人力、技术、设备、时间、资金等)。 TSQC过程是PDCA的几个活动的循环。 计划Plan:确定参数要求; 实施Do:根据要求开展活动; 检查Check:通过评审、度量、测试确认满足要求; 改进Action:纠正参数要求再开发。 8、软件质量控制的实施过程有哪些? 预开发阶段:主要活动包括买主与客户研究建立需求、发布招标请求、选择资源、与开发者签订合同; 开发阶段:涵盖从产品开发到移交产品别获得客户的满意度结束的全过程; 维护阶段:主要是对产品的及时更新。 9、软件质量保证模型有哪些?分别是啥? McCall模型:分别面向软件产品的运行、修正、转移; Boehm模型:着手于软件总体功效,对于软件系统而言,除了有用性以外,开发过程必定是一个时间、金钱、能量的小号过程; FURPS模型:功能性、可用性、可靠性、性能及支持度评估; ISO 9126:功能性、可靠性、可用性、效率、可维护性及可移植性。 10、什么是冗余技术?又分为哪几种技术? 冗余技术又称储备技术,有时也称容灾备份技术,它是利用系统的并联模型来提高系统可靠性的一种手段; 冗余技术分为工作冗余和后备冗余。 11、软件影响因素有哪些? 需求分析定义错误; 设计错误; 编码错误; 测试错误; 文档错误。 12、软件的差错、故障和失效是什么? 异常:偏离期望的状态(或预期值)的任何情形都称为异常; 缺陷:不符合使用要求,或与技术规格说明不一致的任何状态都称为缺陷; 差错: 计算的,观测的或测量的值与真实的规定的或理论上正确的值或者条件之间的差别; 一个不正确的步骤、过程、数据定义; 一个不正确的结果; 一次产生不正确的结果的人的活动; 故障:一个计算机程序中出现的不正确的步骤、过程、数据定义常称为故障; 失效:一个程序运行的外部结果与软件产品的要求出现不一致时称为失效。 13、什么是软件可靠性模型?都有哪些模型? 为预计或估算软件的可靠性所建立的可靠性框图和数学模型 。 Musa模型,包括基本模型和对数模型; Shooman模型; aoel-oknmoto模型; 测试成功模型; 威布尔模型。 14、为什么需要软件评审? 提高项目的生产率; 改善软件的质量; 在评审过程中使开发团队的其他成员更熟悉产品和开发过程; 通过评审标志软件开发的一个阶段的完成; 生产出更容易维护的软件。 15、软件评审有哪些内容? 管理评审; 技术评审; 文档评审; 过程评审。 16、评审的方法及技术有哪些? 评审的方法:特别检查、轮查、走查、团队评审、监视; 评审的技术:缺陷检查表、规则表、评审工具的使用、从不同角度理解产品、场景分析技术。 17、如何准备评审会议? 何时召开评审会议; 选择那些评审材料; 打包分发评审材料; 合理安排评审活动进程。 18、召开评审会议有几个步骤? 评审预备; 评审开始; 评审决议; 评审结果; 几个原则。 19、提高程序质量的技术有哪些? 内存分配方式:从静态存储区域分配;在栈上创建;从堆上分配 面向对象的设计规则 1)开-闭原则 2)里氏代换原则 3)依赖倒转原则 4)合成/聚合复用原则 5)迪米特原则 6)接口隔离原则 7)基本的设计模式 20、软件测试的原则有哪些? 在整个开发过程中要尽早地和不断的进行软件测试; 在开始测试时不应默认程序中不存在错误; 在设计测试用例时要给出测试的预期结果; 测试工作应避免由系统开发人员或开发机构本身来承担; 对合理和不合理的输入数据都要进行测试; 重点测试错误群集的程序区段; 除检查程序功能是否有多余; 用穷举例测试是不可能的; 长期完整的保留所有的测试用例和测试文件,直到该软件产品被废弃为止。 21、软件测试过程有哪些? 单元测试、集成测试、系统测试、验收测试、在所有测试过程中始终贯穿着回归测试。 22、软件测试的种类有哪些? 单元测试、集成测试、功能测试、压力/负载测试、验收测试。 23、白盒测试方法工具有哪些? 1)静态测试方法 工具:logiscope软件,P.RQA软件,c++Test; 2)动态测试方法:代码检查法,静态结构分析法,静态质量度量法,逻辑覆盖法,基本路径测试法,域测试,符号测试,路径覆盖,程序变异; 工具:DevPartner软件、Purity系列、xUnit系列框架(单元测试工具)。 24、黑盒测试工具有哪些? 1)功能测试工具:WinRunner,AutoIT,Twist 2)性能测试工具:LoadRunner 25、黑盒测试方法有哪些? 等价类划分 边界值分析法 因果图法 功能图法 26、划分等价类的原则是什么? 在输入条件规定了取值范围或值的个数和情况下可以确立一个有效等价类和两个无效等价类; 在输入条件规定了输入值的集合或者规定了“必须如何”的条件的情况下可以确立一个有效等价类和一个无效等价类; 在输入条件是一个布尔量的情况下可以确定一个有效等价类和一个无效等价类; 在规定了输入数据的一组值(假设n个)并且程序要对每一个输入值分别处理的情况下可以确立n个有效等价类和一个无效等价类; 在规定了输入数据必须遵守的规则的情况下可以确立一个有效等价类(符合规则)和若干个无效等价类(从不同角度违反规则); 在确知已划分的等价类中各元素在程序处理中的方式不同的情况下应将该等价类进一步划分为更小的等价类。 27、软件测试 经典测试用例三角形问题 软件质量测试 等价类划分 三角形问题 28、因果图测试用例问题 软件质量测试 因果图测试用例 自动售货机 29、白盒测试程序的结构有哪些? 顺序结构、分支结构、循环结构 。 30、白盒测试方法的覆盖标准有哪些? 逻辑覆盖,循环覆盖,基本路径测试。 31、逻辑覆盖包括那些? 分支结构的测试和循环结构的测试、 分支结构的测试又包括语句覆盖 、分支覆盖 、条件覆盖、分支-条件覆盖、条件组合覆盖、路径覆盖。 32、集成测试策略有:非渐增式集成和渐增式集成。 33、什么是渐进增式集成?什么是非渐增式继承? 光分别测试每个模块,再把所有模块按设计要求放在一起,结合所要的程序,这种方法称为非渐增式集成; 把一个要测试的模块与已经测试好的那些模块结合起来进行测试,测试完以后再把下一个应该测试的模块结合起来进行测试,这种每次增加一个模块的方法称为渐增式集成。 34.渐增式集成分为:自顶向下增式集成测试,自底向上增式集成测试 35.面向对象的集成测试的步骤 先选定检测的类,仔细给出类的状态和相应的行为,类或成员函数间传递的消息,输入或输出的界定等 确定覆盖标准 利用结构关系图,确定待测类的所有关联 根据程序中类的对象构造测试用例,确认使用什么输入法激发类的状态,使用类的服务和期望产生什么行为等 36.常用的技术测试有哪些? 1)抽样测试 2)正交阵列测试 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/3
articleCard.readMore

软件质量测试 因果图测试用例 自动售货机

问题描述 有一个饮料自动售货机(处理单价为5角钱)的控制处理软件,它的软件规格说明如下: 若投入5角钱的硬币,按下“橙汁”或“啤酒”的按钮,则相应的饮料就送出来。 若投入1元钱的硬币,同样也是按“橙汁”或“啤酒”的按钮,则自动售货机在送出相应饮料的同时退回5角钱的硬币。 分析情况 怎么分析这种具有一定实际意义的情况呢? 按照因果图的说法,我们先分析一下,把原因与结果先找出来: 原因是输入条件,在自动售货机里,硬币的投入、按钮的按下,都是输入,这样的话就有以下几个原因: (1)投入5角硬币 (2)投入1元硬币 (3)按下“橙汁”按钮 (4)按下“啤酒”按钮 结果有哪些呢? (1)送出“橙汁”饮料 (2)送出“啤酒”饮料 (3)找5角硬币 按照因果关系,把因果图的雏形画出来: 因果测试图判定 再加上因果图的约束关系,那么图形就成为以下: 根据最终的因果图生成判定表: 最后把测试用例写出来: 转载自文章: 测试用例设计技术-因果图之二 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/3
articleCard.readMore

软件质量测试 等价类划分 三角形问题

问题描述 一个程序读入3个整数,把这三个数值看作一个三角形的3条边的长度值。这个程序要打印出信息,说明这个三角形是不等边的、是等腰的、还是等边的。 我们可以设三角形的3条边分别为A,B,C。如果它们能够构成三角形的3条边,必须满足: A>0,B>0,C>0,且A+B>C,B+C>A,A+C>B。 如果是等腰的,还要判断A=B,或B=C,或A=C。 如果是等边的,则需判断是否A=B,且B=C,且A=C。 等价类划分 代码实现 1 2 3 4 5 6 7 8 9 10 float a, b, c; printf("请输入三角形三边"); scanf("%f,%f,%f",&a,&b,&c); if (a==b||b==c||a==c) printf("等腰三角形"); if (a==b&&b==c) printf("等边三角形"); if (a*a+b*b==c*c||a*a+c*c==b*b||b*b+c*c==a*a) printf("直角三角形"); else printf("普通三角形"); 转载自文章: 软件测试-三角形问题 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/3
articleCard.readMore

软件工程 包图习题

1、( A )是用于把元素组织成组的通用机制 A)包 B、类 C)接口 D)组件 2、下面哪个符号代表包图( A ) 3、包内的元素可以被包内的元素、继承的子包元素所访问是指包的____B_____。 A.公有访问 B. 保护访问 C. 私有访问 D.通用访问 4、包内的元素可以被包外的元素所访问是指包的______A______。 A.公有访问 B. 保护访问 C. 私有访问 D.通用访问 5、包内的元素只能被属于同一个模型包的内含元素访问是指包的_____C______。 A.公有访问 B. 保护访问 C. 私有访问 D.通用访问 6、客户包依赖于提供者包是指包的____B____依赖关系。 A. use B. import C. access D. trace 《use》使用关系:是一种默认的依赖关系,说明客户包(发出者)中的元素以某种方式使用提供者包(箭头指向的包)的公共元素,也就是说客户包依赖于提供者包。 7、客户包中的元素也能够访问提供者包的所有公共元素是指包的____B____依赖关系。 A. use B. import C. access D. trace 《import》引用关系:最普遍的包依赖类型,说明提供者包(箭头指向的包)的命名空间(包本身代表命名空间)将被添加到客户包(发出者)的命名空间中,客户包中的元素也能够访问提供者包的所有公共元素 。 8、 客户包中的元素能够访问提供包中的所有公共元素,但客户包必须使用路径名,是指包的__C____关系。 A. use B. import C. access D. trace 《access》访问关系:只想使用提供者包中的元素,而不想将其命名空间合并则应使用该关系。 9、表示一个包到另一个包的历史发展,是指包的____D_____关系。 A. use B. import C. access D. trace 《trace》追溯关系:想表示一个包到另一个包的历史发展,则需要使用《trace》关系来表示 。 10、包元素可以拥有的元素包括(多选)A、C、D_。 A. 类 B. 构件 C. 用例 D. 包 包中拥有的元素可以是模型的各种元素,例如类、接口、组件、用力、也可以是其他包。 11、包的常见构造型包括(多选)A、B、C__。 A. 《subsystem》 B. 《facade》 C. 《framework》 D. 《node》 12、UML系统需求分析阶段产生的包图描述了系统的( B )。 A.状态 B.系统体系层次结构 C.静态模型 D.功能要求 13、( A )是一组用于描述类或组件的一个服务的操作 A、包 B、节点 C、接口 D、组件 14、如下选项所示,哪一种设计所包含的包之间的依赖关系是最不好的?(A) 15、下面哪一项不是包图中的关系( D ) (A)«use» (B)«access» (C)«trace»(D)«stub» 16、建立模型时包的嵌套不宜过深,包的嵌套一般以(A)为宜。 A.23层 B.34层 C.12层 D.35层 17、下列对于创建包的说法不正确的是(A) A.在序列图和协作图中可以创建包 B.在类图中可以创建包 C.如果将包从模型中永久删除,包及包中的内容都将被删除 D.在创建包的依赖关系时,尽量避免循环依赖 18、关于包的描述,哪个不正确( D ) A.和其他建模元素一样,每个包必须有一个区别于其他包的名字; B.包中可以包含其他元素,比如类、接口、组件、用例等等; C.包的可见性分为:public、protected、private; D.导出(export)使的一个包中的元素可以单向访问另一个包中的元素; 19、UML的( B )模型图由类图、对象图、包图、构件图和配置图组成。 A.用例 B.静态 C.动态 D.系统 20、( A )是用于把元素组织成组的通用机制 A.包 B.类 C.接口 D.组件 21、( C )是一组用于描述类或组件的一个服务的操作 A.包 B.节点 C.接口 D.组件 22、在UML中,以下( B )是可以应用于包的构造型 A、框架{《Framework》} B、虚包{《Facade》} C、子系统{《Subsystem》} D、系统{《system》} 23、UML系统需求分析阶段产生的包图描述了系统的( C )。 A.状态 B.系统体系层次结构 C.静态模型 D.功能要求 24、在UML中,( B )可以对模型元素进行有效组织,如类,用例,构件,从而构成具有一定意义的单元。 A、连接 B、包 C、构件 D、节点 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/2
articleCard.readMore

软件工程 状态图、顺序图习题

1、UML图不包括 ( D ) A)用例图 B)类图 C)状态机图 D)流程图 2、下面中( C )图表示结束状态。 3、UML中,对象行为是通过交互来实现的,是对象间为完成某一目的而进行的一系列消息交换。消息序列可用两种类来表示,分别是( D )。 A)状态机图和顺序图 B)活动图和通信图 C)状态机图和活动图 D)顺序图和通信图 4、在UML提供的图中,( D ) 用于按时间顺序描述对象间交互。 A)网络图 B)状态机图 C)通信图 D)顺序图 5、在状态图中不能表示下面哪些概念?( D ) A)动作(Action) B)事件(event) C)转移 D)类 6、生命线是UML视图中哪个图形的组成部分( D ) A)类图 B)状态机图 C)活动图 D)顺序图 7、顺序图由类角色,生命线,激活期和( B )组成。 A)关系 B)消息 C)用例 D)实体 8、下面哪种图最合适用来描述场景:( B )。 A)包图 B)交互图(顺序图、通信图) C)类图 D)用例图 9、UML中,对象行为是通过交互来实现的,是对象间为完成某一目的而进行的一系列消息交换。消息序列可用两种类来表示,分别是( D )。 A.状态图和顺序图 B.活动图和通信图 C.状态图和活动图 D.顺序图和通信图 10、顺序图由类角色,生命线,激活期和( B )组成 A、关系 B、消息 C、用例 D、实体 11、顺序图是强调消息随时间顺序变化的交互图,下面哪个不是用来描述顺序图的组成部分( A ) A.信号 B.生命线 C.激活期 D.类角色 12、关于通信图的描述,下列哪个不正确____B____ A.通信图作为一种交互图,强调的是参加交互的对象的组织; B.通信图是顺序图的一种特例 C.通信图中有消息流的顺序号 D.通信图和顺序图不能互换 13、请在下面选项目中选出两种可以互相转换的图(多选)A、B_。 A 顺序图 B 通信图 C活动图 D状态图 14、下面哪个不是UML中的静态视图(A) (A)状态图 (B)用例图 (C)对象图 (D)类图 15、顺序图的模型元素有( A )、消息、链接等,这些模型元素表示某个用例中的若干个对象和对象之间所传递的消息,来对系统的行为建模。 A.对象 B.箭头线 C.活动 D.状态 16、顺序图描述( D )对象之间消息的传递顺序。 A.某个 B.单个 C.一个类产生的 D.一组 17、顺序图和合作图建立了UML面向对象开发过程中的对象动态( A )模型。 A.交互 B.状态 C.体系结构 D.软件复用 18、状态图可以表现( B )在生存期的行为、所经历的状态序列、引起状态转移的事件以及因状态转移而引起的动作。 A.一组对象 B.一个对象 C.多个执行者 D.几个子系统 19、状态图描述一个对象在不同( A )的驱动下发生的状态迁移。 A.事件 B.对象 C.执行者 D.数据 20、 下面的状态图描述了一辆汽车的状态,指出哪种说法是错误的( D ) A “运动”状态是一个组成状态,由多个简单状态组成。 B “前进”状态和“低速”状态有可能同时出现。 C 汽车在“前进”、“后退”、“高速”及“低速”任何一个子状态下,都有可能转到“停止”状态。 D “前进”状态和“低速”状态是两个顺序子状态。 21、下面哪个UML视图是描述一个对象的生命周期的( B ) (A)类图 (B)状态图 (C)协作图 (D)顺序图 22、下面哪个视图属于UML语言的交互图( B ) (A)行为图 (B)状态图 (C)实现图 (D)顺序图 23、顺序图主要可以为设计人员提供(A)信息。 A. 消息发送的顺序 B. 某个方法的执行流程 C. 类之间关联关系的多重性 D. 某个对象在不同状态之间的转移 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/7/1
articleCard.readMore

Python 数据挖掘实例 决策树分析

安装Anaconda Python集成环境 下载环境 官网: https://www.anaconda.com/ 下载: https://www.anaconda.com/distribution/ 安装环境 下载过程中使用默认,但有一个页面需要确认,如下图。 第一个勾是是否把 Anaconda 加入环境变量,这涉及到能否直接在 cmd中使用 conda、jupyter、 ipython 等命令,推荐打勾。 第二个是是否设置 Anaconda 所带的 Python 3.6 为系统默认的 Python 版本,可以打勾。 安装完成后,在开始菜单中显示“Anaconda2”如下图所示。 安装第三方程序包 Graphviz 目的是在决策树算法中八进制最终的树结构。 1、打开 Anaconda Prompt ,输入 conda install python-graphviz,回车即可完成安装,如下图所示,本图所示已经安装 了 graphviz 包,若之前没有安装,这时会花点时间安装,安装不用干预。 安装完成后先输入 python,然后再输入 import graphviz,测试是否成功安装,如上图所示。 需要设置环境变量,才能使用新安装的 graphviz。 Anaconda及依赖包环境变量设置 首先查看 anaconda 安装在哪个目录下,可以打开 Spyder 的属性,看一看目标是什么目 录。例如本机的 anaconda 安装路径为 C:\Users\lenovo\Anaconda2。 下面设置环境变量 在用户变量“path”里添加 C:\Users\debuginn\Anaconda2\Library\bin\graphviz 在系统变量的“path”里添加 C:\Users\debuginn\Anaconda2\Library\bin\graphviz\dot.exe 如果现在有正在打开的 anaconda 程序,例如正在 Spyder,那么关闭 Spyder,再启动,这 样刚才设置的环境变量生效。 决策树分析 格式化原始数据 将下图的表 demo 输入到 Excel 中,保存为.csv 文件(.csv 为逗号分隔值文件格式)。 注意将表 demo 中的汉字值转换成数据字值,例如“是否是公司职员”列中的“是”为“1”, “否”为“0”。转换后的表中数据如下图所示。 编写数据分析代码 编写程序对上面的数据进行决策树分类,采用信息熵(entropy)作为度量标准。参考代码如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from sklearn.tree import DecisionTreeClassifier,export_graphviz import graphviz import csv dataset = [] reader = csv.reader(open("demo.csv")) for line in reader: if reader.line_num == 1: continue dataset.append(line) X = [x[0:4] for x in dataset] y = [x[4] for x in dataset] clf = DecisionTreeClassifier(criterion='entropy').fit(X, y) dot_data = export_graphviz(clf, out_file=None) graph = graphviz.Source(dot_data) graph.render("table"); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 digraph Tree { node [shape=box] ; 0 [label="X[0] <= 0.5\nentropy = 0.94\nsamples = 14\nvalue = [9, 5]"] ; 1 [label="X[1] <= 1.5\nentropy = 0.985\nsamples = 7\nvalue = [3, 4]"] ; 0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ; 2 [label="entropy = 0.0\nsamples = 3\nvalue = [0, 3]"] ; 1 -> 2 ; 3 [label="X[1] <= 2.5\nentropy = 0.811\nsamples = 4\nvalue = [3, 1]"] ; 1 -> 3 ; 4 [label="entropy = 0.0\nsamples = 2\nvalue = [2, 0]"] ; 3 -> 4 ; 5 [label="X[3] <= 0.5\nentropy = 1.0\nsamples = 2\nvalue = [1, 1]"] ; 3 -> 5 ; 6 [label="entropy = 0.0\nsamples = 1\nvalue = [1, 0]"] ; 5 -> 6 ; 7 [label="entropy = 0.0\nsamples = 1\nvalue = [0, 1]"] ; 5 -> 7 ; 8 [label="X[1] <= 2.5\nentropy = 0.592\nsamples = 7\nvalue = [6, 1]"] ; 0 -> 8 [labeldistance=2.5, labelangle=-45, headlabel="False"] ; 9 [label="entropy = 0.0\nsamples = 4\nvalue = [4, 0]"] ; 8 -> 9 ; 10 [label="X[3] <= 0.5\nentropy = 0.918\nsamples = 3\nvalue = [2, 1]"] ; 8 -> 10 ; 11 [label="entropy = 0.0\nsamples = 2\nvalue = [2, 0]"] ; 10 -> 11 ; 12 [label="entropy = 0.0\nsamples = 1\nvalue = [0, 1]"] ; 10 -> 12 ; } 数据分析结果 程序运行结果在与该程序在同一目录下的 table.pdf 文件中,将每一个叶子结点转换成 IF-THEN 规则。 IF-THEN分类规则 1 2 3 4 5 6 7 (1)IF"不是公司员工" AND "年龄大于等于40", THEN "不买保险"。 (2)IF"不是公司员工" AND "年龄小于40", THEN "买保险"。 (3)IF"不是公司员工" AND "年龄大于50" AND "信用为良", THEN "不买保险"。 (4)IF"不是公司员工" AND "年龄大于40" AND "信用为优", THEN "买保险"。 (5)IF"是公司员工" AND "年龄小于50", THEN "不买保险"。 (6)IF"是公司员工" AND "年龄小于50" AND "信用为优", THEN "买保险"。 (7)IF"是公司员工" AND "年龄小于50" AND "信用为良", THEN "不买保险"。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/6/17
articleCard.readMore

网络笔记之端口及常见端口号

端口定义 通信端口,又称为连接端口、端口、协议端口在计算机网络中是一种经由软件创建的服务,在一个计算机操作系统中扮演通信的端点。每个通信端口都会与主机的IP地址及通信协议关联。通信端口以16比特数字来表示,这被称为通信端口编号。 位于传输层的通信协议通常需要指定端口号,例如在TCP/IP协议族之下的TCP与UDP协议。 引用来源:维基百科 传输层协议,如传输控制协议(TCP)与用户数据包协议(UDP),在分组表头中,定义了来源端口号与目的端口号。 一个通信端口号使用16位无符号整数(unsigned integer)来表示,其范围介于0与65535之间。 在TCP协议中,端口号0是被保留的,不可使用。 1–1023 系统保留,只能由root用户使用。 1024—4999 由客户端程序自由分配。 5000—65535 由服务器端程序自由分配在UDP协议中,来源端口号是可以选择要不要填上,如果设为0,则代表没有来源端口号。 常见端口对照表 端口号码 / 层 名称 注释 1 tcpmux TCP 端口服务多路复用 5 rje 远程作业入口 7 echo Echo 服务 9 discard 用于连接测试的空服务 11 systat 用于列举连接了的端口的系统状态 13 daytime 给请求主机发送日期和时间 17 qotd 给连接了的主机发送每日格言 18 msp 消息发送协议 19 chargen 字符生成服务;发送无止境的字符流 20 ftp-data FTP 数据端口 21 ftp 文件传输协议(FTP)端口;有时被文件服务协议(FSP)使用 22 ssh 安全 Shell(SSH)服务 23 telnet Telnet 服务 25 smtp 简单邮件传输协议(SMTP) 37 time 时间协议 39 rlp 资源定位协议 42 nameserver 互联网名称服务 43 nicname WHOIS 目录服务 49 tacacs 用于基于 TCP/IP 验证和访问的终端访问控制器访问控制系统 50 re-mail-ck 远程邮件检查协议 53 domain 域名服务(如 BIND) 63 whois++ WHOIS++,被扩展了的 WHOIS 服务 67 bootps 引导协议(BOOTP)服务;还被动态主机配置协议(DHCP)服务使用 68 bootpc Bootstrap(BOOTP)客户;还被动态主机配置协议(DHCP)客户使用 69 tftp 小文件传输协议(TFTP) 70 gopher Gopher 互联网文档搜寻和检索 71 netrjs-1 远程作业服务 72 netrjs-2 远程作业服务 73 netrjs-3 远程作业服务 73 netrjs-4 远程作业服务 79 finger 用于用户联系信息的 Finger 服务 80 http 用于万维网(WWW)服务的超文本传输协议(HTTP) 88 kerberos Kerberos 网络验证系统 95 supdup Telnet 协议扩展 101 hostname SRI-NIC 机器上的主机名服务 102 iso-tsap ISO 开发环境(ISODE)网络应用 105 csnet-ns 邮箱名称服务器;也被 CSO 名称服务器使用 107 rtelnet 远程 Telnet 109 pop2 邮局协议版本2 110 pop3 邮局协议版本3 111 sunrpc 用于远程命令执行的远程过程调用(RPC)协议,被网络文件系统(NFS)使用 113 auth 验证和身份识别协议 115 sftp 安全文件传输协议(SFTP)服务 117 uucp-path Unix 到 Unix 复制协议(UUCP)路径服务 119 nntp 用于 USENET 讨论系统的网络新闻传输协议(NNTP) 123 ntp 网络时间协议(NTP) 137 netbios-ns 在红帽企业 Linux 中被 Samba 使用的 NETBIOS 名称服务 138 netbios-dgm 在红帽企业 Linux 中被 Samba 使用的 NETBIOS 数据报服务 139 netbios-ssn 在红帽企业 Linux 中被 Samba 使用的NET BIOS 会话服务 143 imap 互联网消息存取协议(IMAP) 161 snmp 简单网络管理协议(SNMP) 162 snmptrap SNMP 的陷阱 163 cmip-man 通用管理信息协议(CMIP) 164 cmip-agent 通用管理信息协议(CMIP) 174 mailq MAILQ 177 xdmcp X 显示管理器控制协议 178 nextstep NeXTStep 窗口服务器 179 bgp 边界网络协议 191 prospero Cliffod Neuman 的 Prospero 服务 194 irc 互联网中继聊天(IRC) 199 smux SNMP UNIX 多路复用 201 at-rtmp AppleTalk 选路 202 at-nbp AppleTalk 名称绑定 204 at-echo AppleTalk echo 服务 206 at-zis AppleTalk 区块信息 209 qmtp 快速邮件传输协议(QMTP) 210 z39.50 NISO Z39.50 数据库 213 ipx 互联网络分组交换协议(IPX),被 Novell Netware 环境常用的数据报协议 220 imap3 互联网消息存取协议版本3 245 link LINK 347 fatserv Fatmen 服务器 363 rsvp_tunnel RSVP 隧道 369 rpc2portmap Coda 文件系统端口映射器 370 codaauth2 Coda 文件系统验证服务 372 ulistproc UNIX Listserv 389 ldap 轻型目录存取协议(LDAP) 427 svrloc 服务位置协议(SLP) 434 mobileip-agent 可移互联网协议(IP)代理 435 mobilip-mn 可移互联网协议(IP)管理器 443 https 安全超文本传输协议(HTTP) 444 snpp 小型网络分页协议 445 microsoft-ds 通过 TCP/IP 的服务器消息块(SMB) 464 kpasswd Kerberos 口令和钥匙改换服务 468 photuris Photuris 会话钥匙管理协议 487 saft 简单不对称文件传输(SAFT)协议 488 gss-http 用于 HTTP 的通用安全服务(GSS) 496 pim-rp-disc 用于协议独立的多址传播(PIM)服务的会合点发现(RP-DISC) 500 isakmp 互联网安全关联和钥匙管理协议(ISAKMP) 535 iiop 互联网内部对象请求代理协议(IIOP) 538 gdomap GNUstep 分布式对象映射器(GDOMAP) 546 dhcpv6-client 动态主机配置协议(DHCP)版本6客户 547 dhcpv6-server 动态主机配置协议(DHCP)版本6服务 554 rtsp 实时流播协议(RTSP) 563 nntps 通过安全套接字层的网络新闻传输协议(NNTPS) 565 whoami whoami 587 submission 邮件消息提交代理(MSA) 610 npmp-local 网络外设管理协议(NPMP)本地 / 分布式排队系统(DQS) 611 npmp-gui 网络外设管理协议(NPMP)GUI / 分布式排队系统(DQS) 612 hmmp-ind HMMP 指示 / DQS 631 ipp 互联网打印协议(IPP) 636 ldaps 通过安全套接字层的轻型目录访问协议(LDAPS) 674 acap 应用程序配置存取协议(ACAP) 694 ha-cluster 用于带有高可用性的群集的心跳服务 749 kerberos-adm Kerberos 版本5(v5)的“kadmin”数据库管理 750 kerberos-iv Kerberos 版本4(v4)服务 765 webster 网络词典 767 phonebook 网络电话簿 873 rsync rsync 文件传输服务 992 telnets 通过安全套接字层的 Telnet(TelnetS) 993 imaps 通过安全套接字层的互联网消息存取协议(IMAPS) 994 ircs 通过安全套接字层的互联网中继聊天(IRCS) 995 pop3s 通过安全套接字层的邮局协议版本3(POPS3) 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/6/15
articleCard.readMore

再见Windows7,中学时期的一代神器

今天不经意间开启了windows7的虚拟机,用了半天,给出了下面的弹窗,一看,原来是微软最后做windows7的更新停止工作,想想用Windows7这个系统,记忆中从小学就开始使用了,用着它参加了我人生中的大考,山东省2016年春季高考,也算是圆了自己的一个大学梦,考上了本科院校,PS(自己学校实在是emm,一年出不了多少本科生),在这写着一篇文章纪念一下自己使用了近十年的系统。 社会的进步,同时又看出美帝国主义的险恶,不过好消息是华为要推出自己的操作系统,鸿蒙OS,比较期待,现如今我们只有软件、硬件强大起来,打破美帝的垄断,才能在互联网有得自己的一席之地,加油,中国、加油,新时代的程序猿、加油,华为,加油、中华民族,加油!!! 最后给大家展示一下Windows7这个系统的告白网页吧,以后Windows家族何去何从,大家拭目以待,致敬Win7。 不得不说,最后还要推一波广告,哈哈,Windows10现在已经是我的主要使用的OS了!! 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/6/5
articleCard.readMore

微信小程序入门笔记

最近两个星期在学习小程序,主要是应对“全国大学生计算机应用能力与信息素养比赛”,虽然最后没有取得一个比较好的成绩(PS:国家安慰奖),但是收获了不少,边学习边敲代码也是不错的,嘿嘿,下面就是我对这个学习小程序的代码笔记及我设计的小程序的演示图片,嘿嘿,在此处建立一个里程碑吧! 小程序学习笔记 设计小程序图例 PPT 文稿 总结 这个框架才用了ColorUI设计,界面个人感觉清新爽目,嘿嘿,,,,,遗憾的是没有做后台,相比其他学校终结了一下,缺少以下几点: 绝大部分作品与本学校教务处对接,已经运行上线; 后台设计及算法优化比较先进; 采用多种技术,只是采用小程序做显示及基本功能的实现,主要寄托于后台; 获得国一的作品用了Python进行爬虫及数据分析,膜拜大佬。。。 算了,以后就不比赛了,准备北漂工作了,加油!!! 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/6/4
articleCard.readMore

关于站长

你好,我是 Meng小羽,一名热爱技术的全栈开发者。除了编程,我还热衷于摄影、音乐和吉他。我对新技术充满好奇,始终保持探索精神。 这是我独立维护的个人博客,已持续运营 7 年。近期完成了全站静态化改造,让我能够更专注于内容创作。 工作经历 我目前任职于小米,担任软件开发工程师,主要负责小米商城服务端的业务开发和技术研发。2020 年通过小米 “YOU计划” 应届生专项计划加入公司,至今已有 5 年工作经验。“永远相信美好的事情即将发生” 这句公司 slogan 一直激励着我不断成长。 以下是我主要使用的技术栈: 摄影作品 摄影是我工作之余的重要爱好。我每年都会精选最佳作品上传至个人摄影站: https://photo.debuginn.com 开源项目 我开发并开源了一些实用工具和项目,欢迎访问我的项目页面查看并给予支持: 项目展示 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/5/28
articleCard.readMore

蓝桥杯 2019第十届蓝桥杯B组C++ 灵能传输

题目背景 在游戏《星际争霸II》中,高阶圣堂武士作为星灵的重要AOE 单位,在 游戏的中后期发挥着重要的作用,其技能”灵能风暴“可以消耗大量的灵能对 一片区域内的敌军造成毁灭性的伤害。经常用于对抗人类的生化部队和虫族的 刺蛇飞龙等低血量单位。 问题描述 你控制着n 名高阶圣堂武士,方便起见标为1; 2; ...... ; n。每名高阶圣堂武士 需要一定的灵能来战斗,每个人有一个灵能值ai 表示其拥有的灵能的多少(ai 非负表示这名高阶圣堂武士比在最佳状态下多余了ai 点灵能,ai 为负则表示这 名高阶圣堂武士还需要-ai 点灵能才能到达最佳战斗状态)。现在系统赋予了 你的高阶圣堂武士一个能力,传递灵能,每次你可以选择一个i 属于2 [2; n - 1],若 ai >=0 则其两旁的高阶圣堂武士,也就是 i - 1、i + 1 这两名高阶圣堂武士会从 i 这名高圣堂武士这里各抽取ai 点灵能;若ai < 0 则其两旁的高阶圣堂武士, 也就是i - 1;i + 1 这两名高阶圣堂武士会给i 这名高阶圣堂武士?ai 点灵能。形式化来讲就 灵能是非常高效的作战工具,同时也非常危险且不稳定,一位高阶圣堂 武士拥有的灵能过多或者过少都不好,定义一组高阶圣堂武士的不稳定度为 请你通过不限次数的传递灵能操作使得你控制的这一组高阶圣堂武 士的不稳定度最小。 输入格式 本题包含多组询问。输入的第一行包含一个正整数T 表示询问组数。 接下来依次输入每一组询问。 每组询问的第一行包含一个正整数n,表示高阶圣堂武士的数量。 接下来一行包含n 个数a1; a2; ……..; an。 输出格式 输出T 行。每行一个整数依次表示每组询问的答案。 样例输入 1 2 3 4 5 6 7 3 3 5 -2 3 4 0 0 0 0 3 1 2 3 样例输出 1 2 3 3 0 3 样例说明 对于第一组询问: 对2 号高阶圣堂武士进行传输操作后a1 = 3 对于第二组询问: 这一组高阶圣堂武士拥有的灵能都正好可以让他们达到最佳战斗状态。 样例输入 1 2 3 4 5 6 7 3 4 -1 -2 -3 7 4 2 3 4 -8 5 -1 -1 6 -1 -1 样例输出 1 2 3 5 7 4 样例输入 1 2 3 4 5 6 7 3 5 6 -4 2 -7 3 10 -99 -53 43 80 -83 72 99 78 -63 -9 100 373837389 225627048 -847064399 487662607 579717002 903937892 -89313283 134706789 259978604 399131737 298183518 62083619 -444218530 403702220 358088455 -973959249 -637339048 -736509394 -552801709 -98262597 -532577703 -393599463 762744971 -683270041 716127816 -991756495 734780346 27919355 -421469435 258728334 844409214 -270792553 -490888330 133696186 843888283 -35439761 -73481392 -118968548 269164182 978558860 522378250 -979427259 -330256906 235192566 -652699569 -708569352 -778693386 241745676 583226906 121065292 -503683097 599394257 405122877 437067802 238539735 -957745973 -843677563 -690555937 908484805 940157941 524765035 730436972 -17856720 -530595388 -727773574 617781285 491720304 -779040285 -298295760 -699402143 230749576 404009775 126806094 -140842651 198136484 681875881 997449600 898972467 -239590302 -62193410 866009412 -401154712 -276085482 593177187 -236793216 487533624 75511548 -446699920 -869912037 -330666015 268937148 -430325605 -635949275 361887555 -855294881 87004526 782523543 -69083645 -965396597 -880697065 样例输出 1 2 3 5 88 381470940 数据规模与约定 对于所有评测用例: 评测时将使用25 个评测用例测试你的程序,每个评测用例的限制如下: 注意:本题输入量较大请使用快速的读入方式。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/4/1
articleCard.readMore

蓝桥杯 2019第十届蓝桥杯B组C++ 后缀表达式

问题描述 给定N 个加号、M 个减号以及N + M + 1 个整数A1; A2; ……; AN+M+1,小 明想知道在所有由这N 个加号、M 个减号以及N + M +1 个整数凑出的合法的 后缀表达式中,结果最大的是哪一个?请你输出这个最大的结果。 例如使用1 2 3 + -,则“2 3 + 1 -” 这个后缀表达式结果是4,是最大的。 输入格式 第一行包含两个整数N 和M。 第二行包含N + M + 1 个整数A1; A2; …… ; AN+M+1。 输出格式 输出一个整数,代表答案。 样例输入 1 2 1 1 1 2 3 样例输出 1 4 评测用例规模与约定 对于所有评测用例,0 <= N; M >= 100000,109 >= Ai <= 109。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/4/1
articleCard.readMore

蓝桥杯 2019第十届蓝桥杯B组C++ 等差数列

问题描述 数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一 部分的数列,只记得其中N 个整数。 现在给出这N 个整数,小明想知道包含这N 个整数的最短的等差数列有几项? 输入格式 输入的第一行包含一个整数N。 第二行包含N 个整数A1; A2; …… ; AN。(注意A1 ~AN 并不一定是按等差数 列中的顺序给出) 输出格式 输出一个整数表示答案。 样例输入 1 2 5 2 6 4 10 20 样例输出 1 10 样例说明 包含2、6、4、10、20 的最短的等差数列是2、4、6、8、10、12、14、16、 18、20。 评测用例规模与约定 对于所有评测用例,2 <= N <= 100000,0 <= Ai <= 109。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> #include<string> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define N 100005 int num[N]={0},d[N]={0}; int gcd(int a,int b) { return (b>0)?gcd(b,a%b):a; } int main() { int n; scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d",&num[i]); sort(num,num+n); bool zero=false; for(int i=0;i<n-1;i++) { d[i]=num[i+1]-num[i]; if(d[i]==0) { zero=true; break; } } if(zero)//常数数列 printf("%d\n",n); else { int mind=gcd(d[0],d[1]); for(int i=2;i<n-1;i++) mind=gcd(mind,d[i]); printf("%d\n",(num[n-1]-num[0])/mind+1); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/4/1
articleCard.readMore

蓝桥杯 2019第十届蓝桥杯B组C++ 完全二叉树的权值

问题描述 给定一棵包含N 个节点的完全二叉树,树上每个节点都有一个权值,按从 上到下、从左到右的顺序依次是A1, A2,…… AN,如下图所示: 现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点 权值之和最大?如果有多个深度的权值和同为最大,请你输出其中最小的深度。 注:根的深度是1。 输入格式 第一行包含一个整数N。 第二行包含N 个整数A1, A2, …… AN 。 输出格式 输出一个整数代表答案。 样例输入 1 2 7 1 6 5 4 3 2 1 样例输出 1 2 评测用例规模与约定 对于所有评测用例,1 <= N <= 100000,100000 <= Ai <=100000。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> #include<string> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define INF 0x3f3f3f3f #define N 100005 int num[N]={0}; int main() { int n; scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d",&num[i]); int ans=1,k=0,max=-INF; for(int i=1;i<=ceil(log(n+1)/log(2));i++) { int sum=0; for(int j=0;j<pow(2,i-1);j++) sum+=num[k++]; if(sum>max) { max=sum; ans=i; } } printf("%d\n",ans); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/4/1
articleCard.readMore

蓝桥杯 2019第十届蓝桥杯B组C++ 特别数的和

问题描述 小明对数位中含有2、0、1、9 的数字很感兴趣(不包括前导0),在1 到 40 中这样的数包括1、2、9、10 至32、39 和40,共28 个,他们的和是574。 请问,在1 到n 中,所有这样的数的和是多少? 输入格式 输入一行包含两个整数n。 输出格式 输出一行,包含一个整数,表示满足条件的数的和。 样例输入 40 样例输出 574 评测用例规模与约定 对于20% 的评测用例,1<= n <= 10。 对于50% 的评测用例,1<= n <=100。 对于80% 的评测用例,1<= n <=1000。 对于所有评测用例, 1<=n <=10000。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> #include<string> #include<vector> #include<queue> #include<map> #include<set> using namespace std; bool check(int n) { while(n) { int t=n%10; if(t==2||t==0||t==1||t==9) return true; n/=10; } return false; } int main() { int n,ans=0; cin>>n; for(int i=1;i<=n;i++) { if(check(i)) ans+=i; } cout<<ans<<endl; return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/4/1
articleCard.readMore

蓝桥杯-2019第十届蓝桥杯B组C++ 迷宫

下图给出了一个迷宫的平面图,其中标记为1 的为障碍,标记为0 的为可 以通行的地方。 1 2 3 4 010000 000100 001001 110000 迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这 个它的上、下、左、右四个方向之一。 对于上面的迷宫,从入口开始,可以按DRRURRDDDR 的顺序通过迷宫, 一共10 步。其中D、U、L、R 分别表示向下、向上、向左、向右走。 对于下面这个更复杂的迷宫(30 行50 列),请找出一种通过迷宫的方式, 其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。 请注意在字典序中D<L<R<U。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 01010101001011001001010110010110100100001000101010 00001000100000101010010000100000001001100110100101 01111011010010001000001101001011100011000000010000 01000000001010100011010000101000001010101011001011 00011111000000101000010010100010100000101100000000 11001000110101000010101100011010011010101011110111 00011011010101001001001010000001000101001110000000 10100000101000100110101010111110011000010000111010 00111000001010100001100010000001000101001100001001 11000110100001110010001001010101010101010001101000 00010000100100000101001010101110100010101010000101 11100100101001001000010000010101010100100100010100 00000010000000101011001111010001100000101010100011 10101010011100001000011000010110011110110100001000 10101010100001101010100101000010100000111011101001 10000000101100010000101100101101001011100000000100 10101001000000010100100001000100000100011110101001 00101001010101101001010100011010101101110000110101 11001010000100001100000010100101000001000111000010 00001000110000110101101000000100101001001000011101 10100101000101000000001110110010110101101010100001 00101000010000110101010000100010001001000100010101 10100001000110010001000010101001010101011111010010 00000100101000000110010100101001000001000000000010 11010000001001110111001001000011101001011011101000 00000110100010001000100000001000011101000000110011 10101000101000100010001111100010101001010000001000 10000010100101001010110000000100101010001011101000 00111100001000010000000110111000000001000000001011 10000001100111010111010001000110111010101101111000 答案 1 DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRURRUURRDDDDRDRRRRRRDRRURRDDDRRRRUURUUUUUUULULLUUUURRRRUULLLUUUULLUUULUURRURRURURRRDDRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> #include<string> #include<vector> #include<queue> #include<set> using namespace std; #define N 30 #define M 50 char map[N][M]; int dir[4][2]={{1,0},{0,-1},{0,1},{-1,0}};//D<L<R<U char ch[4]={'D','L','R','U'}; int vis[N][M]={0}; struct point { int x,y; string road; point(int a,int b) { x=a; y=b; } }; void bfs() { queue<point> q; point p(0,0); p.road=""; q.push(p); vis[0][0]=1; while(!q.empty()) { point t=q.front(); q.pop(); if(t.x==N-1&&t.y==M-1) { cout<<t.road<<endl; break; } for(int i=0;i<4;i++) { int dx=t.x+dir[i][0]; int dy=t.y+dir[i][1]; if(dx>=0&&dx<N&&dy>=0&&dy<M) { if(map[dx][dy]=='0'&&!vis[dx][dy]) { point tt(dx,dy); tt.road=t.road+ch[i];//记录路径 q.push(tt); vis[dx][dy]=1; } } } } } int main() { for(int i=0;i<N;i++) { for(int j=0;j<M;j++) scanf("%c",&map[i][j]); getchar();//读掉回车 } bfs(); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/4/1
articleCard.readMore

蓝桥杯 2019第十届蓝桥杯B组C++ 数的分解

问题描述 把 2019 分解成 3 个各不相同的正整数之和,并且要求每个正整数都不包含数字 2 和 4,一共有多少种不同的分解方法? 注意交换 3 个整数的顺序被视为同一种方法,例如 1000+1001+18 和 1001+1000+18 被视为同一种。 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一 个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。 答案 40785 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> #include<string> #include<vector> #include<queue> #include<map> #include<set> using namespace std; bool check(int n) { while(n) { if(n%10==2||n%10==4) return false; n/=10; } return true; } int main() { int ans=0; for(int i=1;i<2019;i++) { if(!check(i)) continue; for(int j=i+1;j<2019;j++) { if(!check(j)) continue; for(int k=j+1;k<2019;k++) { if(!check(k)) continue; if(i+j+k==2019) ans++; } } } cout<<ans<<endl; return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/4/1
articleCard.readMore

蓝桥杯 2019第十届蓝桥杯B组C++ 数列求值

问题描述 给定数列 1, 1, 1, 3, 5, 9, 17, …,从第 4 项开始,每项都是前 3 项的和。求第 20190324 项的最后 4 位数字。 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一 个 4 位整数(提示:答案的千位不为 0),在提交答案时只填写这个整数,填写多余的内容将无法得分。 答案 4659 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> #include<string> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define MOD 10000 int num[20190324]={1,1,1}; int main() { for(int i=3;i<20190324;i++) { num[i]=(num[i-3]+num[i-2])%MOD; num[i]=(num[i-1]+num[i])%MOD; } cout<<num[20190323]<<endl; return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/4/1
articleCard.readMore

蓝桥杯 2019第十届蓝桥杯B组C++ 年号字串

小明用字母A 对应数字1,B 对应2,以此类推,用Z 对应26。 对于27 以上的数字,小明用两位或更长位的字符串来对应,例如AA 对应27,AB 对 应28,AZ 对应52,LQ 对应329。 请问2019 对应的字符串是什么? 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> using namespace std; void solve(int n) { if (!n) { return ; } solve(n / 26); cout << (char)(n % 26 + 64); } int main() { solve(2019); return 0; } 答案:BYQ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/27
articleCard.readMore

数据仓库与数据挖掘 使用SQL语句实现AdventureWorksDW数据仓库的多维数据分析

准备工作 AdventureWork各种版本下载链接: 此操作数据库版本为:2014版本。 切片操作 进行切片操作切片。选择地点维、产品维和时间维查看2012年3月份的销售额 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 SELECT DimProduct.EnglishProductName AS 产品名称, DimSalesTerritory.SalesTerritoryRegion AS 产品地区, MONTH(FactInternetSales.OrderDate) AS 月份, SUM(FactInternetSales.SalesAmount) AS 销售额 FROM DimProduct, DimSalesTerritory, FactInternetSales WHERE DimProduct.ProductKey = FactInternetSales.ProductKey AND DimSalesTerritory.SalesTerritoryKey = FactInternetSales.SalesTerritoryKey AND MONTH(FactInternetSales.OrderDate) = 3 AND YEAR(FactInternetSales.OrderDate) = 2012 GROUP BY DimProduct.EnglishProductName, DimSalesTerritory.SalesTerritoryRegion, MONTH(FactInternetSales.OrderDate); 切块操作 切块操作切块。选择地点维、产品维和时间维查看2011年3月份和4月份的销售额 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 SELECT DimProduct.EnglishProductName AS 产品名称, DimSalesTerritory.SalesTerritoryRegion AS 产品地区, MONTH(FactInternetSales.OrderDate) AS 月份, SUM(FactInternetSales.SalesAmount) AS 销售额 FROM DimProduct, DimSalesTerritory, FactInternetSales WHERE DimProduct.ProductKey = FactInternetSales.ProductKey AND DimSalesTerritory.SalesTerritoryKey = FactInternetSales.SalesTerritoryKey AND MONTH(FactInternetSales.OrderDate)BETWEEN 5 and 7 AND YEAR(FactInternetSales.OrderDate) = 2012 GROUP BY DimProduct.EnglishProductName, DimSalesTerritory.SalesTerritoryRegion, MONTH(FactInternetSales.OrderDate); 旋转操作 旋转操作旋转。选择地点维、产品维和时间维,以地区维为主视图查看销售额 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 SELECT DimSalesTerritory.SalesTerritoryRegion AS 产品地区, DimProduct.EnglishProductName AS 产品名称, YEAR(FactInternetSales.OrderDate) AS 年份, MONTH(FactInternetSales.OrderDate) AS 月份, SUM(FactInternetSales.SalesAmount) AS 销售额 FROM -- 产品表 DimProduct, -- 销售地区表 DimSalesTerritory, -- 销售量 FactInternetSales WHERE DimProduct.ProductKey = FactInternetSales.ProductKey AND DimSalesTerritory.SalesTerritoryKey = FactInternetSales.SalesTerritoryKey AND YEAR(FactInternetSales.OrderDate) = 2011 GROUP BY DimProduct.EnglishProductName, DimSalesTerritory.SalesTerritoryRegion, YEAR(FactInternetSales.OrderDate), MONTH(FactInternetSales.OrderDate); 旋转+切块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 SELECT DimSalesTerritory.SalesTerritoryRegion AS 产品地区, DimProduct.EnglishProductName AS 产品名称, YEAR(FactInternetSales.OrderDate) AS 年份, MONTH(FactInternetSales.OrderDate) AS 月份, SUM(FactInternetSales.SalesAmount) AS 销售额 FROM -- 产品表 DimProduct, -- 销售地区表 DimSalesTerritory, -- 销售量 FactInternetSales WHERE DimProduct.ProductKey = FactInternetSales.ProductKey AND DimSalesTerritory.SalesTerritoryKey = FactInternetSales.SalesTerritoryKey AND YEAR(FactInternetSales.OrderDate) BETWEEN 2011 AND 2014 GROUP BY DimProduct.EnglishProductName, DimSalesTerritory.SalesTerritoryRegion, YEAR(FactInternetSales.OrderDate), MONTH(FactInternetSales.OrderDate); 上钻操作 上钻。选择地点维、产品维和时间维查看不同年份的销售额 1 2 3 4 5 6 7 8 9 10 11 12 13 SELECT DimProduct.EnglishProductName AS 产品名称, DimSalesTerritory.SalesTerritoryRegion AS 产品地区, MONTH(FactInternetSales.OrderDate) AS 月份, SUM(FactInternetSales.SalesAmount) AS 销售额 FROM DimProduct, DimSalesTerritory, FactInternetSales WHERE DimProduct.ProductKey = FactInternetSales.ProductKey AND DimSalesTerritory.SalesTerritoryKey = FactInternetSales.SalesTerritoryKey GROUP BY DimProduct.EnglishProductName, DimSalesTerritory.SalesTerritoryRegion, MONTH(FactInternetSales.OrderDate); 下钻操作 下钻。选择地点维、产品维和时间维查看不同日期的销售额 1 2 3 4 5 6 7 8 9 10 11 12 SELECT DimProduct.EnglishProductName AS 产品名称, DimSalesTerritory.SalesTerritoryRegion AS 产品地区, MONTH(FactInternetSales.OrderDate) AS 月份, SUM(FactInternetSales.SalesAmount) AS 销售额 FROM DimProduct, DimSalesTerritory, FactInternetSales WHERE DimProduct.ProductKey = FactInternetSales.ProductKey GROUP BY DimProduct.EnglishProductName, DimSalesTerritory.SalesTerritoryRegion, MONTH(FactInternetSales.OrderDate); 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/27
articleCard.readMore

软件工程 活动图习题

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 UML定义了5类,10种模型图: 1.用例图:从用户角度描述系统功能,并指各功能的操作者。 2.静态图:包括类图,包图,对象图。 类图:描述系统中类的静态结构 包图:是包和类组成的,表示包与包之间的关系,包图描述系统的分层结构 对象图:是类图的实例 3.行为图:描述系统动态模型和对象组成的交换关系。包括状态图和活动图 活动图:描述了业务实现用例的工作流程 状态图:是描述状态到状态控制流,常用于动态特性建模 4.交互图:描述对象之间的交互关系 顺序图:对象之间的动态合作关系,强调对象发送消息的顺序,同时显示对象之间的交互 合作图:描述对象之间的协助关系 5.实现图: 配置图:定义系统中软硬件的物理体系结构 1、下面哪个不是活动图中的基本元素( D ) A)状态、分支 B)转移、汇合 C)泳道、转移 D)用例、状态 活动、起始状态与终止状态、状态转移、判断、分叉与汇和、泳道 活动图的基本要素 2、 在下面的图例中,哪个用来描述活动(activity)(A) 3、下面哪个不是UML中的静态视图_____B______。 A.用例图 B.活动图 C.对象图 D.类图 4、下列关于活动图的说法错误的是______C_______ A一张活动图从本质上说是一个流程图,显示从活动到活动的控制流 B 活动图用于对业务过程中顺序和并发的工作流程进行建模。 C活动图中的基本要素包括活动节点、分支、分叉和汇合、泳道、对象流。 D活动图是UML中用于对系统的静态建模图 5、______A______技术是将一个活动图中的活动状态进行分组,每一组表示一个特定的类、人或部门,他们负责完成组内的活动。 A、泳道 B、分叉汇合 C、分支 D、转移 6、活动图的建模关键是表示出____B______,其它的建模元素都是围绕这一宗旨所进行的补充 A、控制流 B、数据流 C、状态 D、对象之间的关系 7、活动图利用_____C_____和_________来用来建模并发活动 A、分叉 监护条件 B、分支 监护条件 C、分叉 汇合 D、分支 汇合 8、 C 是UML中对系统动态方面建模的两种主要形式 A、活动图 类图 B、交互图 类图 C、活动图 交互图 D、状态图 用例图 9、活动图的___B____元素代表活动连接输入、输出值的连接点 A、转换 B、引脚 C、起始节点 D、泳道 引脚是一个对象节点,代表活动连接输入、输出值的连接点 UML之活动图 10、一个活动图中开始状态能有__A___个,结束状态能有________个。 A、1个 多个 B、1个 1个 C、多个 多个 D、多个 1个 11、UML的( C )模型图由活动图、顺序图、状态图和合作图组成。 A.用例 B.静态 C.动态 D.系统 12、要对一个企业的工作流程建模,下面4种图中的( B )是最重要的。 A 交互图 B 活动图 C 状态图 D 类图 13、使用UML对系统进行动态建模,不能使用以下哪种图( A ) A 类图 B 顺序图 C 状态图 D 活动图 1 静态图:包括类图,包图,对象图。 14、如果要对一个学校课程表管理系统的主要角色学生,老师的工作流程建模,需要使用的图是(C) A.序列图 B.状态图 C.活动图 D.协作图 15、下列对活动图的描述不正确的是(B) A.活动图是对象之间传送消息的时间顺序的可视化表示,目的在于描述系统中各个对象按照时间顺序的交互的过程 B.活动图是一种用于描述系统行为的模型视图,它可用来描述动作和动作导致对象状态改变的结果 C.活动图是模型中的完整单元,表示一个程序或工作流 ,常用于计算流程和工作流程建模 D.活动图可以算是状态图的一种变种并且活动图的符号与状态图的符号非常相似 16、活动图中结束状态使用(C )表示 A.菱形 B.直线箭头 C.黑色实心圆 D.空心圆 17、下列说法不正确的是(B) A.对象流中的对象表示的不仅仅是对象自身,还表示了对象作为过程的一个状态存在 B.活动状态是原子性的,用来表示一个具有子结构的纯粹计算的执行 C.一个组合活动在表面上看是一个状态,但其本质确是一组子活动的概括 D.分支将转换路径分成多个部分,每一部分都有单独的监护条件和不同的结果 18、下面属于活动图组成要素的有(A) A.泳道 B.动作状态 C.转换 D.活动状态 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/21
articleCard.readMore

蓝桥杯 2017年省赛C++B组题6 最大公共子串

最大公共子串长度问题就是: 求两个串的所有子串中能够匹配上的最大长度是多少。 比如:“abcdkkk” 和 “baabcdadabc”, 可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。 下面的程序是采用矩阵法进行求解的,这对串的规模不大的情况还是比较有效的解法。 请分析该解法的思路,并补全划线部分缺失的代码。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> #include <string.h> #define N 256 int f(const char* s1, const char* s2) { int a[N][N]; int len1 = strlen(s1); int len2 = strlen(s2); int i,j; memset(a,0,sizeof(int)*N*N); int max = 0; for(i=1; i<=len1; i++){ for(j=1; j<=len2; j++){ if(s1[i-1]==s2[j-1]) { a[i][j] = __________________________; //填空 if(a[i][j] > max) max = a[i][j]; } } } return max; } int main() { printf("%d\n", f("abcdkkk", "baabcdadabc")); return 0; } 注意:只提交缺少的代码,不要提交已有的代码和符号。也不要提交说明性文字。 代码答案 1 a[i-1][j-1] + 1 Congruent prime sequence 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/20
articleCard.readMore

蓝桥杯 2017年省赛C++B组题5 取数位

求1个整数的第k位数字有很多种方法。 以下的方法就是一种。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 求x用10进制表示时的数位长度 int len(int x){ if(x<10) return 1; return len(x/10)+1; } // 取x的第k位数字 int f(int x, int k){ if(len(x)-k==0) return x%10; return _____________________; //填空 } int main() { int x = 23574; printf("%d\n", f(x,3)); return 0; } 对于题目中的测试数据,应该打印5。 请仔细分析源码,并补充划线部分所缺少的代码。 注意:只提交缺失的代码,不要填写任何已有内容或说明性的文字。 解题算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "iostream" #include "algorithm" // 求x用10进制表示时的数位长度 int len(int x){ if(x<10) return 1; return len(x/10)+1; } // 取x的第k位数字 int f(int x, int k){ if(len(x)-k==0) return x%10; return f(x/10, k); //填空 } int main() { int x = 23574; printf("%d\n", f(x,3)); return 0; } 解题答案 f(x/10, k) 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/19
articleCard.readMore

蓝桥杯 2017年省赛C++B组题3 承压计算

X星球的高科技实验室中整齐地堆放着某批珍贵金属原料。 每块金属原料的外形、尺寸完全一致,但重量不同。 金属材料被严格地堆放成金字塔形。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 7 5 8 7 8 8 9 2 7 2 8 1 4 9 1 8 1 8 8 4 1 7 9 6 1 4 5 4 5 6 5 5 6 9 5 6 5 5 4 7 9 3 5 5 1 7 5 7 9 7 4 7 3 3 1 4 6 4 5 5 8 8 3 2 4 3 1 1 3 3 1 6 6 5 5 4 4 2 9 9 9 2 1 9 1 9 2 9 5 7 9 4 3 3 7 7 9 3 6 1 3 8 8 3 7 3 6 8 1 5 3 9 5 8 3 8 1 8 3 3 8 3 2 3 3 5 5 8 5 4 2 8 6 7 6 9 8 1 8 1 8 4 6 2 2 1 7 9 4 2 3 3 4 2 8 4 2 2 9 9 2 8 3 4 9 6 3 9 4 6 9 7 9 7 4 9 7 6 6 2 8 9 4 1 8 1 7 2 1 6 9 2 8 6 4 2 7 9 5 4 1 2 5 1 7 3 9 8 3 3 5 2 1 6 7 9 3 2 8 9 5 5 6 6 6 2 1 8 7 9 9 6 7 1 8 8 7 5 3 6 5 4 7 3 4 6 7 8 1 3 2 7 4 2 2 6 3 5 3 4 9 2 4 5 7 6 6 3 2 7 2 4 8 5 5 4 7 4 4 5 8 3 3 8 1 8 6 3 2 1 6 2 6 4 6 3 8 2 9 6 1 2 4 1 3 3 5 3 4 9 6 3 8 6 5 9 1 5 3 2 6 8 8 5 3 2 2 7 9 3 3 2 8 6 9 8 4 4 9 5 8 2 6 3 4 8 4 9 3 8 8 7 7 7 9 7 5 2 7 9 2 5 1 9 2 6 5 3 9 3 5 7 3 5 4 2 8 9 7 7 6 6 8 7 5 5 8 2 4 7 7 4 7 2 6 9 2 1 8 2 9 8 5 7 3 6 5 9 4 5 5 7 5 5 6 3 5 3 9 5 8 9 5 4 1 2 6 1 4 3 5 3 2 4 1 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 其中的数字代表金属块的重量(计量单位较大)。 最下一层的X代表30台极高精度的电子秤。 假设每块原料的重量都十分精确地平均落在下方的两个金属块上, 最后,所有的金属块的重量都严格精确地平分落在最底层的电子秤上。 电子秤的计量单位很小,所以显示的数字很大。 工作人员发现,其中读数最小的电子秤的示数为:2086458231 请你推算出:读数最大的电子秤的示数为多少? 注意:需要提交的是一个整数,不要填写任何多余的内容。 格式化金字塔 将金字塔转化为二维数组形式,见下图: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 {7}, {5,8}, {7,8,8}, {9,2,7,2}, {8,1,4,9,1}, {8,1,8,8,4,1}, {7,9,6,1,4,5,4}, {5,6,5,5,6,9,5,6}, {5,5,4,7,9,3,5,5,1}, {7,5,7,9,7,4,7,3,3,1}, {4,6,4,5,5,8,8,3,2,4,3}, {1,1,3,3,1,6,6,5,5,4,4,2}, {9,9,9,2,1,9,1,9,2,9,5,7,9}, {4,3,3,7,7,9,3,6,1,3,8,8,3,7}, {3,6,8,1,5,3,9,5,8,3,8,1,8,3,3}, {8,3,2,3,3,5,5,8,5,4,2,8,6,7,6,9}, {8,1,8,1,8,4,6,2,2,1,7,9,4,2,3,3,4}, {2,8,4,2,2,9,9,2,8,3,4,9,6,3,9,4,6,9}, {7,9,7,4,9,7,6,6,2,8,9,4,1,8,1,7,2,1,6}, {9,2,8,6,4,2,7,9,5,4,1,2,5,1,7,3,9,8,3,3}, {5,2,1,6,7,9,3,2,8,9,5,5,6,6,6,2,1,8,7,9,9}, {6,7,1,8,8,7,5,3,6,5,4,7,3,4,6,7,8,1,3,2,7,4}, {2,2,6,3,5,3,4,9,2,4,5,7,6,6,3,2,7,2,4,8,5,5,4}, {7,4,4,5,8,3,3,8,1,8,6,3,2,1,6,2,6,4,6,3,8,2,9,6}, {1,2,4,1,3,3,5,3,4,9,6,3,8,6,5,9,1,5,3,2,6,8,8,5,3}, {2,2,7,9,3,3,2,8,6,9,8,4,4,9,5,8,2,6,3,4,8,4,9,3,8,8}, {7,7,7,9,7,5,2,7,9,2,5,1,9,2,6,5,3,9,3,5,7,3,5,4,2,8,9}, {7,7,6,6,8,7,5,5,8,2,4,7,7,4,7,2,6,9,2,1,8,2,9,8,5,7,3,6}, {5,9,4,5,5,7,5,5,6,3,5,3,9,5,8,9,5,4,1,2,6,1,4,3,5,3,2,4,1} 解题算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include "iostream" #include "algorithm" double a[30][30]{ {7}, {5,8}, {7,8,8}, {9,2,7,2}, {8,1,4,9,1}, {8,1,8,8,4,1}, {7,9,6,1,4,5,4}, {5,6,5,5,6,9,5,6}, {5,5,4,7,9,3,5,5,1}, {7,5,7,9,7,4,7,3,3,1}, {4,6,4,5,5,8,8,3,2,4,3}, {1,1,3,3,1,6,6,5,5,4,4,2}, {9,9,9,2,1,9,1,9,2,9,5,7,9}, {4,3,3,7,7,9,3,6,1,3,8,8,3,7}, {3,6,8,1,5,3,9,5,8,3,8,1,8,3,3}, {8,3,2,3,3,5,5,8,5,4,2,8,6,7,6,9}, {8,1,8,1,8,4,6,2,2,1,7,9,4,2,3,3,4}, {2,8,4,2,2,9,9,2,8,3,4,9,6,3,9,4,6,9}, {7,9,7,4,9,7,6,6,2,8,9,4,1,8,1,7,2,1,6}, {9,2,8,6,4,2,7,9,5,4,1,2,5,1,7,3,9,8,3,3}, {5,2,1,6,7,9,3,2,8,9,5,5,6,6,6,2,1,8,7,9,9}, {6,7,1,8,8,7,5,3,6,5,4,7,3,4,6,7,8,1,3,2,7,4}, {2,2,6,3,5,3,4,9,2,4,5,7,6,6,3,2,7,2,4,8,5,5,4}, {7,4,4,5,8,3,3,8,1,8,6,3,2,1,6,2,6,4,6,3,8,2,9,6}, {1,2,4,1,3,3,5,3,4,9,6,3,8,6,5,9,1,5,3,2,6,8,8,5,3}, {2,2,7,9,3,3,2,8,6,9,8,4,4,9,5,8,2,6,3,4,8,4,9,3,8,8}, {7,7,7,9,7,5,2,7,9,2,5,1,9,2,6,5,3,9,3,5,7,3,5,4,2,8,9}, {7,7,6,6,8,7,5,5,8,2,4,7,7,4,7,2,6,9,2,1,8,2,9,8,5,7,3,6}, {5,9,4,5,5,7,5,5,6,3,5,3,9,5,8,9,5,4,1,2,6,1,4,3,5,3,2,4,1} }; int main(){ int i, j; double max=0, min=9999999; double result; for(i=1; i<=29; i++){ for(j=0; j<=i; j++){ //如果为首尾两个数值话,直接自身/2运算 if(j==0){ a[i][j] += a[i-1][0]/2.0; }else{ //正常两个数值进行向下除法加法运算 a[i][j] += a[i-1][j-1]/2.0 + a[i-1][j]/2.0; } } } for(i=0; i<=29; i++){ if(a[29][i]<min){ min = a[29][i]; } if(a[29][i]>max){ max = a[29][i]; } } printf("%lf\n",2086458231/min*max); return 0; } 题解答案 72665192664 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/19
articleCard.readMore

蓝桥杯 2017年省赛C++B组题2 等差素数列

2,3,5,7,11,13,….是素数序列。 类似:7,37,67,97,127,157 这样完全由素数组成的等差数列,叫等差素数数列。 上边的数列公差为30,长度为6。 2004年,格林与华人陶哲轩合作证明了:存在任意长度的素数等差数列。 这是数论领域一项惊人的成果! 有这一理论为基础,请你借助手中的计算机,满怀信心地搜索: 长度为10的等差素数列,其公差最小值是多少? 注意:需要提交的是一个整数,不要填写任何多余的内容和说明文字。 解题算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include "iostream" #include "algorithm" using namespace std; typedef long long ll; bool isprime(int n){ //如果数值小于等于1并且大于二且为偶数 if(n<=1 || (n>2 && n%2==0)){ return false; } //查找最小公倍数对应的偶数序列,是否满足条件 for(ll i=3; i*i<=n; i+=2){ if(n%i==0){ return false; } } return true; } int main(){ for(int d = 2; d<1000; d++){ for(ll n = 2; n<1000; ++n){ if( isprime(n) && isprime(n + d) && isprime(n + 2*d) && isprime(n + 3*d) && isprime(n + 4*d) && isprime(n + 5*d) && isprime(n + 6*d) && isprime(n + 7*d) && isprime(n + 8*d) && isprime(n + 9*d) ){ cout << d <<endl; break; } } } return 0; } 题解答案 210 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/19
articleCard.readMore

蓝桥杯 2017年省赛C++B组题1 购物单

问题描述 小明刚刚找到工作,老板人很好,只是老板夫人很爱购物。老板忙的时候经常让小明帮忙到商场代为购物。小明很厌烦,但又不好推辞。 这不,XX大促销又来了!老板夫人开出了长长的购物单,都是有打折优惠的。 小明也有个怪癖,不到万不得已,从不刷卡,直接现金搞定。 现在小明很心烦,请你帮他计算一下,需要从取款机上取多少现金,才能搞定这次购物。 取款机只能提供100元面额的纸币。小明想尽可能少取些现金,够用就行了。 你的任务是计算出,小明最少需要取多少现金。 以下是让人头疼的购物单,为了保护隐私,物品名称被隐藏了。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 ----------------- **** 180.90 88折 **** 10.25 65折 **** 56.14 9折 **** 104.65 9折 **** 100.30 88折 **** 297.15 半价 **** 26.75 65折 **** 130.62 半价 **** 240.28 58折 **** 270.62 8折 **** 115.87 88折 **** 247.34 95折 **** 73.21 9折 **** 101.00 半价 **** 79.54 半价 **** 278.44 7折 **** 199.26 半价 **** 12.97 9折 **** 166.30 78折 **** 125.50 58折 **** 84.98 9折 **** 113.35 68折 **** 166.57 半价 **** 42.56 9折 **** 81.90 95折 **** 131.78 8折 **** 255.89 78折 **** 109.17 9折 **** 146.69 68折 **** 139.33 65折 **** 141.16 78折 **** 154.74 8折 **** 59.42 8折 **** 85.44 68折 **** 293.70 88折 **** 261.79 65折 **** 11.30 88折 **** 268.27 58折 **** 128.29 88折 **** 251.03 8折 **** 208.39 75折 **** 128.88 75折 **** 62.06 9折 **** 225.87 75折 **** 12.89 75折 **** 34.28 75折 **** 62.16 58折 **** 129.12 半价 **** 218.37 半价 **** 289.69 8折 -------------------- 需要说明的是,88折指的是按标价的88%计算,而8折是按80%计算,余者类推。 特别地,半价是按50%计算。 请提交小明要从取款机上提取的金额,单位是元。 答案是一个整数,类似4300的样子,结尾必然是00,不要填写任何多余的内容。 解题思路 其实这题就是送分的,不过就是送分了也是很容易丢分的,一不小心就少算了一个,分就没了,所以还是需要谨慎的。这个题目其实有好多的解法。 解题思路一 笨办法,就是将数值一个一个的输入并且用for循环进行sum求和运算,最后输出结果,不过不推荐,比较麻烦。 解题思路二 相信在考试的机器中都存在Office这个神奇的软件,那么恭喜你,有了Excel这个软件,哈哈,将数值复制进去,拆分单元格,之后sum函数求和,最后得到你的结果,试试吧! 解题答案 5200 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/18
articleCard.readMore

软件工程 怎样建立甘特图

甘特图(Gantt chart )又叫横道图、条状图(Bar chart)。它是以图示的方式通过活动列表和时间刻度形象地表示出任何特定项目的活动顺序与持续时间。它是在第一次世界大战时期发明的,以亨利·L·甘特先生的名字命名,他制定了一个完整地用条形图表进度的标志系统。由于甘特图形象简单,在简单、短期的项目中,甘特图都得到了最广泛的运用。 首先,建立基本的图表框架和时间刻度日期。您还可以选择如何设置任务栏、里程碑和其他图表元素的格式。 稍后,您可以更改日期,添加或删除任务和里程碑,以及创建任务间的依赖关系。您还可以应用配色方案,以及添加标题和图例。 在 Visio 的“文件”菜单上,依次指向“新建”和“日程”,然后单击“甘特图”。 单击“日期”选项卡,然后选择所需的选项。 注释:“主要单位”是您要在图表中使用的最长时间单位(如年或月),“次要单位”是最短时间单位(如日或小时)。 在“格式”选项卡中单击要在任务栏、里程碑和摘要栏上使用的形状和标签,然后单击“确定”。 注释:如果您不确定要选择何种格式,接受默认选项即可。您可以在以后更改该格式。 完成图表框架 建立甘特图后,将显示一个通用的图表框架。 该框架就像一幅空白画布,您可以在其上添加日程的详细信息: 在“任务名称”列中,单击某个单元格,键入特定的任务名称来代替通用文字。随着项目进展,您可以添加更多任务。 最初,“开始时间”和“完成时间”列中的日期反映了您为项目指定的开始日期。要更改该日期,请单击单元格,然后键入新日期。 “工期”列将随您键入的新开始日期和完成日期自动更新。您还可以键入开始日期和完成日期之一以及工期来指示任务的时间长度。 在“时间刻度”(标有“2000”的其下显示有月份的区域)中,“主要单位”显示在顶部,“次要单位”显示在底部。 时间刻度始于您指定的开始日期,止于您指定的完成日期。当您添加任务的开始日期和结束日期或工期时,任务栏将出现在时间刻度下面的区域中,且该区域将展开。 提示 要记录与每一任务有关的其他数据,您可以添加更多的列。例如,您可以添加“资源”列,以便您的甘特图反映出每个任务的负责人。 给甘特图添加数据 您可以将反映项目日程详细信息的数据填入框架。还可以添加并优化以下日程元素: 任务 甘特图中的每个任务在图表框架中占用一行。当您在“任务名称”列的单元格中键入任务名称时,任务工期将表示为时间刻度下方区域中的任务栏。 目的 采取的操作 更改任务名称 单击包含该任务的“任务名称”列中的单元格,然后键入新名称。 设置或更改任务工期 在包含要更改日期或工期的甘特图框架中,单击单元格,然后键入新信息。 注释 根据以下规则键入工期:1h 表示 1 小时 1d 表示 1 天 1w 表示 1 周 1m 表示 1 个月 在甘特图底部添加新任务 通过单击围绕图表的实线,选择甘特图框架。要创建新的任务行,请拖动位于框架底部中央的绿色选择手柄。 在两个现有任务之间添加新任务 右键单击要在其上方显示新任务行的行中的任意单元格,然后单击快捷菜单中的“新建任务”。 给任务添加完成百分比指示器 右键单击要显示完成百分比列的位置左侧的列顶部的阴影部分,然后单击快捷菜单中的“插入列”。 在“列类型”下,单击“完成百分比”,然后单击“确定”。 随着任务的进展,在新列中键入任务的完成百分比。完成百分比指示器便会出现在任务栏中。 删除任务 右键单击表示要删除的任务的行中的任意单元格,然后单击快捷菜单中的“删除任务”。 更改任务栏的显示方式 右键单击任务栏,然后单击快捷菜单中的“任务选项”。在列表中单击所需选项,然后单击“确定”。 里程碑 当您要在一个总括任务下合并若干附属任务时,可以使用摘要任务。 目的 采取的操作 创建带有附属任务的摘要任务 给甘特图添加摘要任务和附属任务或里程碑。 要选择附属任务,请单击包含该任务名称的单元格。要选择多个任务,请在单击时按住 Shift。 右键单击其中一个选定的任务,然后单击快捷菜单中的“降级”。 设置摘要任务的工期 在表示第一个附属任务的行中,单击“开始时间”列中的单元格,然后键入该任务的开始日期。 对于同一个附属任务,单击“完成时间”列中的单元格,然后键入该任务的结束日期。 对每个附属任务重复第 1 步和第 2 步。 注释 在为所有附属任务添加任务工期信息后,摘要任务的工期会自动填入。 降低任务级别(降级) 右键单击要降级的任务的名称,然后单击快捷菜单中的“降级”。 提升任务级别(升级) 右键单击要提升的任务的名称,然后单击快捷菜单中的“升级”。 更改摘要任务栏的显示方式 右键单击要更改的摘要任务的任务栏,然后单击快捷菜单中的“任务选项”。 在“摘要栏”下,选择摘要栏开头和结尾要使用的符号,然后单击“确定”。 依赖关系(链接任务) 当您在甘特图中创建依赖另一个任务的任务时,一个箭头将把两个任务栏连接起来。如果更改另一个任务所依赖的任务的日期或工期,则依赖任务的日期也会随之更改。 目的 采取的操作 设置任务之间的依赖关系 通过单击包含任务名称的单元格,选择要在其间建立依赖关系的任务和里程碑。要选择多个任务,请在选择时按住 Shift。 右键单击所选任务之一,然后单击快捷菜单中的“链接任务”。 中断任务之间的依赖关系 通过单击包含任务名称的单元格,选择带有要断开依赖关系的任务。要选择多个任务,请在选择时按住 Shift。 右键单击其中一个选定的任务,然后单击快捷菜单中的“取消链接任务”。 更改依赖关系箭头的样式 打开甘特图,右键单击绘图页,然后单击快捷菜单中的“S 型连接线”。 数据列 项目日程是根据特定于任务的数据创建的。任务开始日期和工期这两个因素综合在一起决定项目的完成日期。在 Visio 甘特图中,任务数据存储在数据列中。如果要在甘特图中记录并显示其他任务数据,可以添加新列。例如,您可能要添加任务注释列,您可以在其中说明复杂的任务或独特的任务;添加资源列以列出负责完成每个任务的人员;或添加完成百分比列以跟踪每个任务已完成的百分比。 默认情况下,新的甘特图在创建时将包含“任务名称”列、“开始时间”列、“完成时间”列和“工期”列。您可以重新安排现有列、添加新列或删除不再需要的列。 目的 采取的操作 重命名现有列 单击要重命名列的标题,然后键入新名称。 添加预先设计的新数据列 右键单击要显示新列的位置左侧的列标题,然后单击快捷菜单中的“插入列”。 在“列类型”列表中,单击与要添加的数据类型相对应的列名称,然后单击“确定”。 添加您自己设计的新数据列 右键单击要显示新列的位置左侧的列标题,然后单击快捷菜单中的“插入列”。 在“列类型”列表中,单击与要使用的数据格式(例如,“用户定义的小数”、“用户定义的文本”或“用户定义的时间”)相对应的一个用户定义的列,然后单击“确定”。为列键入新的名称。 注释 如果添加多个文本列,请每次选择不同的用户定义文本选项。例如,为第一列单击“用户定义的文本 1”,为第二列选择“用户定义的文本 2”,依此类推。 删除(隐藏)数据列 右键单击要删除(隐藏)的列的标题,然后单击快捷菜单中的“隐藏列”。 注释 删除或隐藏图表中的列时,该列中的数据将保存到文件中。如果以后要再次显示该列,请右键单击列标题,然后单击快捷菜单中的“插入列”。在列表中选择要再次显示的列,然后单击“确定”。 移动数据列 单击移动的列的标题。 将列拖到新的位置。 请执行下列操作之一: 要将一列移到另一列的左侧,请将要移动列的中点放置在另一列中点的左侧。 要将一列移到另一列右侧,请将要移动列的中点放置在另一列中点的右侧。 要将一列移到时间刻度区域的右侧,请将要移动列的中点放置在时间刻度区域中点的右侧。 注释 如果时间线刻度区域很长,您可能必须缩小视图,以便可以将该列移过该区域的中点。要缩小视图,请在“视图”菜单上指向“缩放比例”,然后单击所需的缩放级别。 时间刻度 时间刻度是主要时间单位和次要时间单位的刻度,它将从项目的开始日期延伸到结束日期。您可以定义时间刻度的时间单位、开始日期和结束日期以及非工作日。 您可以滚动至时间刻度上特定的日期或任务,还可以更改时间刻度区域的宽度并显示更多的日期。 目的 采取的操作 更改开始日期和/或结束日期 在甘特图中,右键单击时间刻度中的任何部分,然后单击快捷菜单中的“日期选项”。 在“时间刻度范围”下,选择新的开始日期/时间或结束日期/时间,然后单击“确定”。 更改时间单位 在甘特图中,右键单击时间刻度中的任何部分,然后单击快捷菜单中的“日期选项”。 在“时间单位”下,选择所需的“主要单位”和“次要单位”,然后单击“确定”。 设置非工作日 在甘特图中,右键单击时间刻度中的任何部分,然后单击快捷菜单中的“配置工作时间”。 为“工作日”和“工作时间”选择所需选项,然后单击“确定”。 滚动至特定的任务或里程碑 通过单击包含任务名称的单元格,选择要滚动至的任务或里程碑。 在“甘特图”工具栏上,单击“滚动至任务”按钮。 注释 如果看不到“甘特图”工具栏,请在“视图”菜单上指向“工具栏”,然后单击“甘特图”。 滚动至特定日期 在甘特图中,右键单击时间刻度中的任何位置,然后单击快捷菜单中的以下选项之一:“滚动至完成日期”- 滚动至时间刻度的结束位置。 “向左滚动一个单位”- 向左滚动一个次要单位。 “向右滚动一个单位”- 向右滚动一个次要单位。 “滚动至开始日期”- 滚动至时间刻度的开始位置。 更改时间刻度区域的宽度 在时间刻度区域顶部的灰色区域中单击一次,然后再次单击,选择时间刻度列。 向任一个方向拖动列右侧的绿色选择手柄,直到区域的宽度满足您的要求。 显示更多时间单位 单击甘特图框架周围的实线以选择该框架。 向右拖动位于框架中心偏右侧的绿色选择手柄。 注释 当您展开时间刻度以显示更多时间单位时,还可以更改与项目相关的结束日期。 打印大型甘特图 除非是为小项目创建日程,否则,您的甘特图很可能超出一页标准打印纸的边界。下表说明了您可能遇到的一些打印问题,以及为了获得所预期的效果在打印前可以采取的相应措施。 问题 解决方案 采取的操作 只打印了部分甘特图。 请确保整个图表适合绘图页的大小。 在“文件”菜单上,单击“页面设置”。 单击“页面大小”选项卡,单击“调整大小以适应绘图内容”,然后单击“确定”。 打印纸和绘图页的方向不同。 更改打印纸方向。 在“文件”菜单上,单击“页面设置”。 单击“打印设置”选项卡,单击所需的方向,然后单击“确定”。 您不知道甘特图打印时会占几页。 在打印绘图前预览其打印效果。 在“文件”菜单上,单击“打印预览”。 不知道分页符将出现在什么位置。 启用分页符,查看图表将平铺跨越多少张打印纸。 在“视图”菜单上,单击“分页符”。图表上的灰线表示进行分页的位置。 打印纸断开的位置不理想。 更改边距设置,以控制各页间的重叠。边距越大,页间的重叠越大。 在“文件”菜单上,单击“页面设置”。 在“打印设置”选项卡上,单击“设置”。 键入所需的边距设置,然后单击两次“确定”。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/13
articleCard.readMore

蓝桥杯 算法训练 最大最小公倍数

问题描述 已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少。 输入格式 输入一个正整数N。 输出格式 输出一个整数,表示你找到的最小公倍数。 样例输入 9 样例输出 504 数据规模与约定 1 <= N <= 106。 算法分析 如果 n <= 2, 那么最小公倍数为 n 如果 n 是奇数,那么最小公倍数的最大值为末尾的三个数相乘 如果是偶数的话,如果同时出现两个偶数肯定会不能构成最大值了,因为会被除以2分两种情况: 如果 n 是偶数且不是三的倍数, 比如8,那么跳过n-2这个数而选择 8 7 5 能保证不会最小公倍数被除以2所以最小公倍数的最大值为n * (n – 1) * (n – 3) 如果 n 是偶数且为三的倍数,比如6,如果还像上面那样选择的话,6和3相差3会被约去一个3,又不能构成最大值了。那么最小公倍数的最大值为(n – 1) * (n – 2) * (n – 3) C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include "iostream" #include "algorithm" using namespace std; int main(){ long long n, ans; cin >> n; if(n <= 2){ ans = n; }else if(n%2 == 1){ ans = n * (n-1) * (n-2); }else if(n%3 == 0){ ans = (n-1) * (n-2) * (n-3); }else{ ans = n * (n-1) * (n-3); } cout << ans; return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/5
articleCard.readMore

蓝桥杯 算法训练 区间k大数查询

问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个。 输入格式 第一行包含一个数n,表示序列长度。 第二行包含n个正整数,表示给定的序列。 第三个包含一个正整数m,表示询问个数。 接下来m行,每行三个数l,r,K,表示询问序列从左往右第l个数到第r个数中,从大往小第K大的数是哪个。序列元素从1开始标号。输出格式总共输出m行,每行一个数,表示询问的答案。 样例输入 1 2 3 4 5 5 1 2 3 4 5 2 1 5 2 2 3 2 样例输出 1 2 4 2 数据规模与约定 对于30%的数据,n,m<=100; 对于100%的数据,n,m<=1000; 保证k<=(r-l+1),序列中的数<=106。 C++算法解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include "iostream" #include "algorithm" using namespace std; int a[1001], b[1001]; bool cmp(int a, int b){ return a>b; } int main(){ int n, m; int l, r, k; int i, j; while(cin>>n){ for(i=0; i<n; i++){ cin >> a[i]; } cin >> m; while(m--){ cin >>l>>r>>k; for(j=l-1, i=0; j<r; ++j,++i){ b[i] = a[j]; } sort(b, b+i, cmp); cout<<b[k-1]<<endl; } } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/3/4
articleCard.readMore

怎样提高博客的页面访问量

看到别人翻译的一篇文章,是讨论如何提高博客访问量的,提到的有几个方法蛮有新意,不过不赞成原文的“在聚合中只输出摘要”的方法。 十四个方法提高博客的页面访问量 作者:Darren Rowse,翻译:Lucifer 如何增加Blog的访问量已经是老生长谈了,其实对于blogger来说另一个统计数也至关重要:页面浏览量。 很多的网站流量统计服务会同时提供这两项数据- “访问量”计数的是访问者的数目,而页面浏览量算的则是这些访问者所浏览的你blog上的网页的数目。 对不同blog来说,每个访问者的页面浏览量会有所不同,但希望这个数字可以大一总不是件坏事: 回头率:读者浏览的页面越多,那么他再次造访的可能性也就更大。 收入:放在blog上的广告多是印象型的,也就是说被浏览的次数多了,被点击的可能性才越大。 写 blog 的目的不同,所以想不想把增加页面浏览量放在第一位悉听尊便。对于那些希望这项统计值上升的人,这里有14条建议: 1 内链到自己的文章 这可能是最简单的增加页面流量的办法,那就是在自己文章里加入链接,指向自己的其它文章。自卖自夸可能有点好笑,不过如果是链接到一些之前写过的相关或相同主题的文章的话,相信还是读者们所喜闻乐见的。很多的blogger在自己的blog上会多次写到同一主题,把这些文章都链到一起无疑会显出你对这个主题的探讨深度。 2 高亮显示相关的文章 不想在文章内部加入指向之前文章的链接的话,专门独立出一个“相关文章”(Related post)的部分就不失为一个好的替代方法了(你可以在侧栏里看到我的相关文章(related entries),而原文作者,ProbBlogger的主人的相关文章则放在了每篇文章之后,并以黄色为背景以突出显示)。Wordpress有专门的插件可以自动实现这一功能(名为related posts)。当然你要是想手动实现也是可以的,只要在文章结尾处加进一些和本文相关的可供读者参考的文章链接就可以了。 3 加入一个邮件订阅或文章更新提提示服务 原文作者就尝到了通过邮件订阅来提高页面浏览量的甜头,凡是通过邮件订阅的读者在收到ProBlogger的最新消息的同时都会留意到一个叫作“hot posts”的部分,在这个部分中整理收入了一周当中的最受欢迎的5篇文章。因为不同读者所关注的文章不同,所以很可能一些人只看过其中的某几篇文章,加入“hot posts”之后,这部分读者就会对没看过的那些文章产生兴趣。这样一来就在增加“访问量”的同时也增加了“页面浏览量”。其它的一些通过邮件提示更新的订阅服务应该也很有效(比如Zookoda和Feedburner就都有这个功能)。 4 在醒目位置高亮显示重要文章 ProBlogger在页面的上部有三个高亮显示的菜单栏,里面放置了一些介绍或提示性质的文章,比如什么是blog,blog设计的窍门之类。而通过作者的观察很多读者都会认真地把这里的文章看过一遍。这样一来无疑就提高了页面量。 5 “几大……” 这种“最…”或是“几几大…”的链接到多个页面的文章似乎总是会勾起读者的兴趣。比如“二十大最受欢迎文章”往往就成为初次造访的读者必看的文章,而这又指引着他们挨着个地去看,不失为一个好办法。 6 写一个系列 写一系列的文章来提高页面量有着两层意义。首先在写这个系列的这段时间里,读者会不时地被吸引回来,因为他们想看看你下一篇文章写了些什么; 其次,在你完成了这个系列之后,如果能很好的把这些文章都链在一起(参考1和2),这样一来读者就会从头到尾读完整个系列(当然是由多个页面组成的)。原文作者就写过一个初学者blog指南的系列,而每个从头到尾看完这个系列的读者都要看上30到40篇文章(汗)。 7 在首页上输出摘要 在首页上只输出摘要或是只输出文章的一部分,然后通过一个“阅读完整文章”的链接链到单独的页面,这样一来想看完整文章的就得再去单独的页面。不得不说这样一来会很烦人,所以很多人都不这么做。不过对于篇幅长的文章来说,这么做不仅使首页看起来更严谨,也在无形中增加了页面量。 8 企划或专题 和之前的系列文章相类似,比如很多主题(theme)设计者的blog就是通过建立某个主题的企划,从而吸引关注这个主题的人不时地来看看工作的进展情况。类似的比如开展讨论或者竞赛也可以达到相同的效果。不过这些牛人做这个的首要目的并不是区区页面量,页面量不过是副产品罢了。 9 在聚合中只输出摘要 这是为我所不耻的。就连原文作者也不这么做。尽管这样一来你的页面访问可能会有所上升,但我觉得无论是对于读者还是作者来说都是得不偿失。在这样一个“你有压力,我有压力”的社会,每个人的时间都很宝贵,用聚合的目的就是节省时间和资源。所以我看到只输出摘要的blog就一个反应,把它从我的bloglines里删除。强烈建议所有的blogger都在聚合里都输出完整的文章! 10 诱使聚合读者访问页面 相比起输出摘要的做法,我觉得这个更可取。不是通过强迫,而是通过一些技巧,比如投票,吸引读者参与评论,或是内部链接的方式来把读者带到你的页面上来。 11 互动 读者参与的越多,回访的机率也就越大,同时页面量也就越多。参与了评论或是投票的读者很多都会回访,来看看其他人的回应。而回应本身就带来了两次页面访问量。就留言评论来说,看一遍文章就是一次页面访问,而留个言就是又一次。同样的,最好不要把这个当成吸引留言的首要目的,交流才是根本啊。互动主要发生在留言部分,当然投票以及其它的工具也应该有效果。 12 吸引读者的评论 有几个方法可以有效地达到这个目的:比如通过插件实现在侧栏里显示最新的评论,为评论提供一个专门的聚合,或是提供邮件订阅评论的选项。 13 搜索 通过加入搜索功能从而方便读者检索你之前写的文章也可以提高页面量。有很多方法可以实现这一功能。大多数的主题都内建了一个搜索引擎,Google的AdSense也提供了这样的一个服务,读者可以选择搜索本站或是整个网络,而且如果他们在搜索结果页面点击了广告,那么还可以给你带来一点小小的外块。 14 给你的读者留作业 原文作者举了他的一个提供摄影技巧的blog为例,因为是为读者提供一些摄影的窍门,所以在文章的结尾布置一些“作业”以便让读者可以亲自去尝试就显得十分自然了。这样一来读者就会经常回访,一方面这种窍门或是教学多是一步步的,所以他们会经常打开你的页面,反复按照你的指导一步步地去做;另一方面,很多人都会想要向你展示一下他们的作业成果。 在文章的结尾,我也学着作者的样子留个作业吧: 你的blog上应用了以上的哪一项方法? 看过这篇文章之后你有没有想试试哪一个的想法? 试试其中的一个(或者几个)方法,然后告诉回来告诉我们效果怎么样。 原文地址:How to Increase a Blog’s Page Views 译文地址:十四个方法提高博客的页面访问量 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/28
articleCard.readMore

【NCRE四级网络工程师】操作系统单选题

在不同类型的操作系统中,批处理操作系统的缺点是: 缺少交互性 页式存储管理方案中,若一个进程的虚拟地址空间为2GB,页面大小为4KB,当用4字节标识物理页号时,页表需要占用多少个页面? 一个进程的虚拟地址空间为2GB,页面大小为4KB,则共有2*1024*1024/4个页表项; 每个页面用4个字节标识物理块号,则需要210241024/4*4大小的页表,由于每个页表为4KB,即2*1024*1024/4*4/(4*1024)=512个页面。 假设某文件由100个逻辑记录组成,每个逻辑记录长度为80个字符。磁盘空间被划分为若干块,块大小为1024字符。在没有采用成组操作时,磁盘空间的利用率为多少? 在没有成组操作时,每一个记录占用一个块,那么磁盘空间的利用率为80/1024=8%. 假设某文件由100个逻辑记录组成,每个逻辑记录文件长度为80个字符。磁盘空间被划分为若干块,块大小为1024个字符。若才作用成组操作,块因子为12,那么磁盘空间的利用率为多少? 若采用成组操作时,每12个记录占用一个块,那么磁盘空间的利用率为80*12/1024=94%。 下列关于死锁与安全状态的描述中,那个事正确的? 死锁状态一定是不安全状态 在可变分区存储管理方案中,为加快内存分配,当采用最佳适应算法时空闲区的组织应该是:按空闲区大小递增顺序排列。 假设某文件系统的物理结构采用类UNIX的二级索引结构。主索引表有12项,前10项给出文件前10块的磁盘地址,第11项给出一级索引表的地址,第12项给出二级索引表的地址。一级和二级索引表的大小均为一个磁盘块,可存放100个磁盘地址。在找到主索引表之后,要访问文件的第1000块,还需要启动多少次磁盘? 1~10块采用的是直接索引,需要启动磁盘1次; 11~110块采用的是一级索引,需要启动磁盘2次; 111~10110块采用的是二级索引,需要启动磁盘3次。 第1000块访问时,找到主索引后,需要启动磁盘2次。 在文件系统中,文件的逻辑块与存储介质上物理块存放顺序一致的物理结构是:顺序结构。 打开文件时,系统主要完成以下工作: ①根据文件路径名查目录,找到FCB主部; ②根据打开方式,共享说明和用户身份检查访问合法性; ③根据文件号查系统打开文件表,看文件是否已被打开; ④在用户打开文件表中取一空表项,填写打开方式等,并指向系统打开文件表对应表项。系统返回用户文件描述符fd,用于以后读写文件。 假设某文件系统的物理结构采用类UNIX的二级索引结构。主索引表有12项,前10项给出文件前10块的磁盘地址,第11项给出一级索引表的地址,第12项给出二级索引表的地址。一级和二级索引表的大小均为一个磁盘块,可存放100个磁盘地址。针对以上描述的文件系统,一个文件最大为多少块 直接索引磁盘块有10个, 采用一级索引的磁盘块有100个, 采用二级索引的磁盘块有100*100个,合计为10000个。 假设某文件系统的物理结构采用类UNIX的二级索引结构。主索引表有12项,前10项给出文件前10块的磁盘地址,第11项给出一级索引表的地址,第12项给出二级索引表的地址。一级和二级索引表的大小均为一个磁盘块,可存放100个磁盘地址。在找到主索引表之后,要访问文件的第1000块,还需要启动多少次磁盘? 110块采用的是直接索引,需要启动磁盘1次;11110块采用的是一级索引,需要启动磁盘2次;111~10110块采用的是二级索引,需要启动磁盘3次。第1000块访问时,找到主索引后,需要启动磁盘2次。 进程从运行态转换为阻塞态的原因是( A )。 A) 需要的数据没有准备好 B) 需要的设备不存在 C) 分配给该进程的时间片用完 D) 运算过程中栈溢出 一个运行着的进程打开了一个新的文件,则指向该文件数据结构的关键指针存放在( D )。 A) 文件目录中 B) 文件句柄中 C) 进程头文件中 D) 进程控制块中 进程控制块中的进程资源清单,列出所拥有的除CPU外的资源记录,如拥有的I/O设备,打开的文件列表等。 在内存分区管理中,下列哪一种技术可以将零碎的空闲区集中为一个大的空闲区( C )。 A) 覆盖技术 B) 交换技术 C) 内存紧缩 D) 动态重定位 解决碎片问题的办法是在适当时刻进行碎片整理,通过移动内存中的程序,把所有空闲碎片合并成一个连续的大空闲区并且放在内存的一端,而把所有程序放在另一端,这技术称为“移动技术”或“紧缩技术”。 在内存分配方案中,下列哪一种方法使内存的利用率较高且管理简单( B )。 A) 段式分配 B) 页式分配 C) 可变分区分配 D) 固定分区分配 页式分配的优点有: ① 由于它不要求作业或进程的程序段和数据在内存中连续存放,从而有效地解决了碎片问题。 ② 动态页式管理提供了内存和外存统一管理的虚存实现方式,使用户可以利用的存储空间大大增加。这既提高了主存的利用率,又有利于组织多道程序执行。 在一个虚拟存储系统中,决定虚拟存储空间最大容量的要素是( A )。 A) 计算机系统地址位宽 B) 计算机系统数据字长 C) 内存和磁盘容量之和 D) 交换空间容量 实现虚拟存储器需要系统有容量足够大的外存、系统有一定容量的外存,最主要的是,硬件提供实现虚-实地址映射的机制。在一个虚拟存储系统中,决定虚拟存储空间最大容量的要素是计算机系统地址位宽。 在虚拟页式存储管理系统中,若采用请求调页方式,当用户需要装入一个新的页面时,其调入的页面来自(磁盘文件区)。 UNIX操作系统中,对文件系统中空闲区的管理通常采用 成组链接法。 对于FAT32文件系统,它采用的是哪一种文件物理结构 链接结构。 关于操作系统的结构,下列特性中,哪一个不是微内核结构的特点(清晰的单向依赖和单向调用性)。 程序局部性原理分为空间局部性和时间局部性,空间局部性是指(程序代码的顺序性)。 程序的并发执行产生了一些和程序顺序执行时不同的特性(并发程序与计算过程无法一一对应)。 在Pthread线程包关于条件变量的使用中,pthread_mutex_init()表示的是(创建一个互斥量)。 init是初始化变量,和git操作初始化一致,小技巧 程序的并发执行产生了一些和程序顺序执行时不同的特性,下列哪一个特性是正确的(并发程序在执行期间具有相互制约关系)。 为了保证计算机中临界资源的正确使用,进程在对临界资源访问前,必须首先调用下列哪一区的代码( 进入区 )。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/26
articleCard.readMore

【NCRE四级网络工程师】操作系统多选题

保存在进程控制块中的是 进程标识符 进程当前状态 代码段指针 PCB的内容可以分为调度信息和现场信息两大部分。调度信息供进程调度时使用。调度信息供进程调度时使用,描述了进程当前所处的状态,包括进程名、进程号、存储信息、优先级、当前状态、资源清单、家族关系、消息队列指针、当前打开文件等。 下列关于地址映射的叙述中,正确的是: 地址映射过程通常是有硬件完成的; 地址映射是将虚拟地址转换为物理地址; 页表项的一些内容是由硬件决定的; 根据页表项的有效位确定所需访问的页面时都已经在内存。 下列对于块表的叙述中,正确的是: 块表的另一个名称是TLB 当切换进程时,要刷新快表; 快表存放在高速缓存中; 对块表的查找是按内容并行进行的。 下列各项中,那些事文件控制块中必须保存的信息? 文件名 文件大小 文件创建时间 磁盘块起始地址 文件控制块FCB包括:文件名、用户名、文件号、文件地址、文件长度、文件类型、文件属性、共享技术、文件的建立日期、保存期限、最后修改日期、最后访问日期、口令、文件文件物理结构等等。 设计文件系统时应尽量减少访问磁盘的次数,以提高文件系统的性能。下列各项措施中,哪些可以提高文件系统的性能? 块高速缓存 磁盘驱动调度 目录项分解法 设备与CPU之间的数据传送和控制方式有多种,他们是: 程序直接控制方式 中断控制方式 DMA方式 通道控制方式 当前测到系统发生死锁之后,解除死锁的方法是? 剥夺某些进程所占有的资源; 撤销某些进程 从新启动系统 测试与设置指令(Test&Set)是解决互斥访问临界区的硬件方法。下列关于该指令功能的叙述中,哪些是正确的 A) 测试W的值,若W=1,则返回重新测试 B) 测试W的值,若W=0,置位W=1,进入临界区 C) 退出临界区时,复位W=0 TS指令实现互斥的算法是:测试锁变量的值,如为1,则重复执行本命令,不断重复测试变量的值;如为0,则立即将锁变量测值置为1,进入临界区;测试并设置指令是一条完整的指令,而在一条指令的执行中间是不会被中断的,保证了锁的测试和关闭的连续性;退出临界区时,将锁变量测试值设为0。 下列关于虚拟存储器的叙述中,哪些是正确的? 在虚拟存储系统中,进程的部分程序装入后便可运行 虚拟存储技术允许用户使用比物理内存更大的存储空间 实现虚存必须有硬件支持 段页式存储管理为用户提供了一个二维地址空间,满足程序和信息的逻辑分段的要求。其基本思想是用页式方法来分配和管理内存空间,即把内存划分为若干大小相等的页面。内存是以页为基本单位分配给每个用户程序的,逻辑上相邻的页面在物理内存中不一定相邻。内存空间最小的单位是页而不是段。页式存储管理的特征是等分内存,有效的克服了碎片,提高了存储器的利用率。 下列文件的物理结构中,哪些结构适合文件的随机存取 连续结构 索引结构 多级索引结构 在程序控制I/O方式中,若输出设备向处理机返回“准备就绪”信号,则表示()。 输出缓冲区已空 可以向输出缓冲区写数据 在设备分配中,预防死锁的策略包括()。 A) 建立SPOOLing系统 B) 一次分配所有资源 C) 有序分配资源 D) 剥夺其他进程的资源 在设计系统时确定资源分配算法,限制进程对资源的申请,从而保证不发生死锁。具体的做法是破坏产生死锁的四个必要条件之一: ①破坏“互斥条件”:可以通过采用假脱机(SPOOLing)技术,允许若干个进程同时输出; ②破坏“不可剥夺”条件:如果资源没有被等待进程占有,那么该进程必须等待,在其等待过程中,其资源也有可能被剥夺; ③破坏“请求和保持”条件:可以采用静态分配资源策略,将满足进程条件的资源一次性分配给进程,也可以采用动态资源分配,即需要资源时才提出申请,系统在进行分配; ④破坏“循环等待”条件:进程申请资源时,必须严格按照资源编号的顺序进行,否则系统不予分配。 下列关于进程的叙述中,哪些是正确的( BC )。 A) 一个进程的状态变化必定会引起另一个进程的状态变化 B) 信号量的初值一定大于等于零 C) 进程是资源分配的基本单位,线程是处理机调度的基本单位 D) 进程被挂起后,它的状态一定为阻塞态 E) 操作系统中引入P、V操作主要是为了解决死锁问题 在下列存储管理方案中,能支持多道程序设计的是( )。 A) 可变分区存储管理 B) 页式存储管理 C) 单一分区存储管理 D) 固定分区存储管理 E) 段页式存储管理 单一分区存储器管理,只充许一道程序独占内存空间,因此不能支持多道程序设计技术。 在计算机系统中,形成死锁的必要条件是( ABCD )。 A) 资源互斥使用 B) 部分分配资源 C) 已分配资源不可剥夺 D) 资源申请形成环路 E) 系统资源不足 当前Android操作系统应用广泛,它具有下列哪些特性( BC )。 A) 批处理 B) 移动应用 C) 支持网络 D) 分布式 E) 兼容性 下列关于进程控制块的叙述中,哪些是正确的( ABC )。 A) 进程控制块的英文缩写是PCB B) 每个进程都拥有自己的进程控制块 C) 进程控制块必须常驻内存 D) 进程控制块必须指明其兄弟进程的进程号 E) 进程创建完毕后,系统将其进程控制块插入等待队列 下列关于信号量使用的叙述中,哪些是正确的( ABD )。 A) 信号量初始化后,只能实施P、V原语操作 B) 在互斥信号量与同步信号量都使用的进程中,应先执行同步信号量的P操作 C) 在互斥信号量与同步信号量都使用的进程中,应先执行同步信号量的V操作 D) 信号量的初值不能小于0 E) 互斥信号量的变化范围只能是正整数 下列页面置换算法中,哪些算法需要用到访问位(引用位)( CDE )。 A) 先进先出算法FIFO B) 最佳置换算法OPT C) 最近最久未使用算法LRU D) 时钟算法CLOCK E) 最近未使用算法NRU 从简单页式存储管理方案发展到虚拟页式存储管理方案,页表项中通常需要增加的信息有:有效位,修改位,访问位。 SPOOLing系统的主要组成部分是( ABC )。 A) 输入井和输出井 B) 输入缓冲区和输出缓冲区 C) 输入进程和输出进程 D) 输入控制器和输出控制器 E) 输入分配器和互斥分配器 下列关于死锁的叙述中,哪些是正确的( ABC )。 A) 死锁产生的原因是进程推进顺序不当 B) 环路是死锁产生的必要条件 C) 采用银行家算法能有效地实现死锁避免 D) 当系统中只有一个进程时也可能会产生死锁 E) 系统出现死锁是因为进程调度不当 进程(线程)调度的主要功能有( ABCD )。 A) 根据一定的调度算法选择被调度的进程(线程) B) 将CPU分配给选中的进程(线程) C) 将换下CPU的进程(线程)的现场信息保存到进程控制块中 D) 将选中的进程(线程)的现场信息送入到相应寄存器中 E) 将阻塞的进程(线程)唤醒并置为就绪状态 下列哪一种存储管理方案以一个进程为单位分配一组连续的内存单元( AB )。 A) 固定分区 B) 可变分区 C) 页式 D) 段式 E) 段页式 在虚拟页式存储方案中,当判断一个页面是否已调入内存时需要用到页表表项的哪些位( AB )。 A) 驻留位 B) 中断位 C) 修改位 D) 访问位 E) 保护位 下列哪些文件是按照文件的组织形式划分的文件类型( BDE )。 A) 系统文件 B) 普通文件 C) 临时文件 D) 目录文件 E) 特殊文件 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/26
articleCard.readMore

【NCRE四级网络工程师】计算机网络单选题

如果交换机的总带宽为14.4Gbps,它具有12个百兆的全双工端口,则其千兆的全双工端口数量最多为? 全双工交换机的带宽计算方法是:端口数端口速率2。 12个百兆的全双工端口占用的带宽为122100=2400 Mbps,则剩余带宽 为14400-2400=12000Mbps。用于千兆的全双工端口,则12000/(2*1000)=6。 Ethernet网卡使用的物理地址的长度为(48位)。 每个物理网络都有自己的MTU,MTU主要规定:一个帧最多能够携带的数据量 在客户机/服务器模型中,服务器响应客户机的请求有两种实现方案,它们是并发服务器方案和(重复服务器)。 在DNS系统中,如果解析器收到一条“非授权的”服务器响应,那么解析器可以认为(该响应提供的信息可能不准确)。 在POP3协议中,查询报文总数和长度可以使用的命令为(STAT)。 关于即时通信系统的描述中,正确的是(RFC2778规定了其通讯模型)。 即时通信IM是一种基于Internet的通信服务,由以色列Mirabils公司最早提出,它提供近实时的信息交换和用户状态跟踪。文件 RFC2778,描述了即时通信系统的功能,正式为即时通信系统勾勒出了模型框架。IM系统一般采用两种通信模式,一种是客户机/服务器模式,另一种采用用户/用户模式,IM软件的文本消息大多使用客户机/服务器模式,而文件传送等大数据量业务使用的是用户/用户模式。在聊天通信中,聊天信息通过加密的方式传输。 关于即时通信协议的描述中,正确的是(XMPP基于JABBER)。 目前,很多即时通信系统都采用服务提供商自己设计开发的IM协议,如微软MSN采用自己的MSNP协议,AOL采用OSCAR协议,QQ采用自己的私有 协议。目前IM通用的协议主要由两个代表:基于SIP协议框架的SIMPLE协议簇及基于Jabber协议框架的XMPP协议簇。SIP协议称为会话发起协议,它是一种在IP网络上实现实时通信的应用层的控制(信令)协议。 即时通信系统通常需要支持两种基本的服务,它们是:呈现服务和即时消息服务 关于P2P文件共享系统的描述中,错误的是(A)。 A) BitTorrent不使用Tracker服务器 B) Maze系统含有搜索引擎 C) 早期的Napster是一个音乐分享系统 D) eDonkey2000采用哈希信息进行文件定位 BitTorrent协议要求文件的发布者制作一个.torrent文件,被称为“种子”文件,种子文件中包含了Tracker服务器的相关信息和发布者共享的 文件的信息。 搜索引擎主要由4个关键部分组成,它们是搜索器、检索器、用户接口和(索引器)。 利用公钥加密和数字签名技术建立的安全服务基础设施称为(PKI)。 关于对称加密的描述中,正确的是(C)。 A) 加密密钥与解密密钥不同 B) 加密算法与密钥可以公开 C) DES属于对称加密方法 D) DSA属于对称加密方法 对称加密技术使用相同的密钥对信息进行加密和解密。由于通信双方加密与解密使用同一个密钥,所以密钥在加密方和解密方之间的传递和分发必须通过安全通道进行。常用的对称加密算法有DES(数字加密算法)、IDEA算法、RC2算法、RC4算法与Skipjack算法等。 关于MD5的描述中,错误的是(C)。 A) 是一种单向散列函数 B) 可用于判断数据完整性 C) 属于对称加密方法 D) 不能从散列值计算出原始数据 散列函数MD5属于一种认证函数,不属于对称加密方法。 关于P2P文件共享的描述中,正确的是(理论基础是六度分割)。 **P2P文件共享的基础是“六度分割”理论。**一般认为P2P文件共享起源于Napster,采用集中式结构,利用 点对点下载过程下载软件,随后另一种P2P文件共享网络Gnutella出现,采用分布式网络共享。BitTorrent即比特洪流,种子文件的扩展名为.torrent。 在网络管理服务中,定义管理对象结构的是(管理信息库(MIB))。 管理信息库(MIB)是TCP/IP网络管理协议标准框架的内容之一,MIB定义了受管设备必须保存的数据项、允许对每个数据 项进行的操作及其含义,即管理系统可访问的受管设备的控制和状态信息等数据变量都保存在MIB中。所以在网络管理服务中,定义管理对象结构的是MIB。 关于CMIP协议的描述中,正确的是()。 A) 由IETF制定 B) 针对TCP/IP环境 C) 是网络管理的事实标准 D) 采用委托监控机制 国际标准化组织(ISO)最先在1979年对网络管理通信进行标准化工作,主要针对OSI(开放系统互联)模型而设计。ISO的成果是CMIS和CMIP。 CMIP提供管理信息传输服务的应用层协议,而CMIS支持管理进程和管理代理之间的通信要求,二者规定了OSI系统的网络管理标准。在网络管理过程中,CMIP不是通过轮询而是通过事件报告进行工作的。 瓦特斯利用电子邮件验证“小世界假设”理论时,邮件平均被转发多少次即可到达接收者手中(6)。 IP数据报是IP协议单元使用的数据单元,它的格式可以分为报头区和数据区两大部分,其中数据区包括高层需要传输的数据,而报头区是为了正确传输高层数据而增加的控制信息。 在域名系统中,解析器收到一个“非权威性”的映射时,解析器可以认为(响应服务器不是该域名的授权管理者)。 如果一个IP数据报的报头长度为256b,那么该数据报报头长度字段的值为( 8 )。 头部的IHL域指明了该头部有多长(以32位字的长度为单位),所以256/32=8。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/26
articleCard.readMore

【NCRE四级网络工程师】计算机网络多选题

在以下几种帧中,属于IEEE 802.11管理帧的是(BC)。 A) 信标帧 B) 探测帧 C) 认证帧 D) RTS帧 E) ACK帧 属于IEEE 802.11管理帧的是探测帧和认证帧。 关于千兆以太网物理层标准的描述中,错误的是(ABE)。 A) 1000Base-T使用屏蔽双绞线 B) 1000Base-CX使用非屏蔽双绞线 C) 1000Base-LX使用单模光纤 D) 1000Base-SX使用多模光纤 E) 1000Base-W使用无线传输介质 1000Base-T使用5类非屏蔽双绞线作为传输介质,双绞线长度可达100m。1000Base-CX使用的是屏蔽双绞线,双绞线长度可达25m。 1000Base-LX使用波长为1300nm的单模光纤,光纤长度可达3000m。1000Base-SX使用波长为250nm的多模光纤,光纤长度可达300~500m。 千兆类型以太网中没有1000Base-W类型。 关于Internet接入方式的描述中,正确的是(ACD)。 A) HFC采用共享信道传输方式 B) HFC发展与电话网普及密不可分 C) ADSL需要进行调制解调 D) ADSL 的上、下行速率可以不同 E) HFC的上、下行速率必须相同 Internet的接入方式主要有4种:通过电话线网接入、利用ADSL(非对称数字用户线路)接入、使用HFC(混合光纤同轴电缆网)接入、通过数据通信线路接入。 HFC是在有线电视网的基础上发展起来的。与ADSL类似,HFC也采用非对称的数据传输速率。一般的上行速率在10Mbps左右,下行速率在10~40Mbps左右。HFC采用共享式的传输方式,所有通过Cable Modem的发送和接收使用同一个上行和下行信道。 在ADSL用户端,用户需要使用一个ADSL终端(传统的调制解调器类似)来连接电话线路。通常ADSL可以提供最高1Mbps的上行速率和最高8Mbps的下行速率。 为了解决慢收敛问题,RIP协议可以采用的策略为(BDE)。 A) 加密传输 B) 水平分割 C) 用户认证 D) 毒性逆转 E) 触发刷新 为了解决慢收敛问题,RIP协议采用限制路径最大“距离”对策、水平分隔对策、保持对策、带触发刷新的毒性逆转对策。 在VoIP系统中,网关的主要功能包括()。 A) 号码查询 B) 信号调制 C) 路由寻址 D) 呼叫控制 E) 身份验证 IP电话网关位于公用交换电话网与IP网的接口处,它是电话用户使用IP电话的接入设备。它的主要功能为号码查询、建立通信连接、信号调制、信号压缩和解压、路由寻址。 网络故障管理的功能主要包括(ABD)。 A) 维护错误日志 B) 执行诊断检测 C) 生成用户账单 D) 跟踪错误 E) 发布安全事件报告 网络故障管理包括检测故障、隔离故障和纠正故障3个方面,应包括典型的功能有维护并检测错误日志、接收错误检测报告并作出响应、跟踪与辨认错误、执行诊断测试、纠正错误。 关于IPSec的描述中,正确的是(ABDE)。 A) 在网络层提供安全服务 B) 主要协议包括AH和ESP C) SPI使用64位连接标识符 D) AH头位于原IP数据报数据和IP头之间 E) SA定义的逻辑连接是单向的 关于无线局域网的描述中,正确的是()。 A) 以无线电波作为传输介质 B) 协议标准是IEEE 802.11 C) 可采用直接序列扩频技术 D) 可作为有线局域网的补充 无线局域网络利用微波、激光和红外线等无线电波作为传输介质,它是有线局域网的补充。按采用的传输技术可以分为3类:红外线局域网、扩频局域网(调频扩频或直接序列扩频)、窄带微波局域网,采用IEEE 802.11标准,支持基于漫游访问(Nomadic Access)和无线访问接入点(Wireless Access Point,AP)访问模式。 在以下P2P网络中,哪些采用了分布式结构化拓扑()。 Pastry Tapestry Chord CAN P2P中采用了分布式结构化拓扑有Pastry、Tapestry、Chord和CAN,Napster采用集中式结构。 关于RSA算法的描述中,正确的是()。 A) 安全性建立在大素数分解的基础上 B) 常用于数字签名中 C) Rivest是发明人之一 E) 加密强度取决于密钥长度 RSA公钥加密算法是1977年由罗纳德•李维斯特(Ron Rivest)、阿迪•萨莫尔(Adi Shamir)和伦纳德•阿德曼(Leonard Adleman)一起提出的一种公钥密码,也是一种分组密码,也是一种既能用于数据加密和数字签名的算法,RSA的安全性依赖于大数分解,但是否等同于大数分解一直未能得到理论上的证明。RSA密钥长度随着保密级别提高,增加很快。 在Internet中,IP路由器应具备的主要功能包括( ABD )。 A) 转发所收到的IP数据报 B) 为投递的IP数据报选择最佳路径 C) 分析IP数据报所携带的TCP内容 D) 维护路由表信息 E) 解析用户的域名 路由器是Internet种最重要的设备,它是网络与网络之间连接的桥梁。它主要的功能是:维护路由表信息(路由表决定着IP数据报发往何处),转发所收到的IP数据报,为投递的IP数据报选择最佳路径。 在IP数据报分片后,分片报头中的哪些字段与原数据报中的字段一定相同( AC )。 A) 标识 B) 标志 C) 目的地址 D) 片偏移 E) 头部校验和 提高域名系统解析效率的技术包括( ADE )。 A) 解析从本地域名服务器开始 B) 减小资源记录的TTL值 C) 拒绝使用”非权威性的”映射报告 D) 本地主机上采用高速缓冲技术 E) 域名服务器中使用高速缓冲技术 关于Ethernet帧结构的描述中,错误的是( ABCD )。 A) 前导码字段的长度是1字节 B) 源地址字段使用的是IP地址 C) 数据字段的最小长度为64字节 D) 类型字段指出应用层协议类型 E) 帧校验字段采用的是CRC校验 关于VLAN技术的描述中,正确的是( BCDE )。 A) 可利用集线器组建VLAN B) 可基于广播组定义VLAN C) 可基于IP地址划分VLAN D) 可基于MAC地址划分VLAN E) 可基于交换机端口划分VLAN 关于浏览器安全性的描述中,正确的是( BD )。 A) 为了避免非安全软件的危害,可在浏览器中加载自己的证书 B) 为了验证站点的真实性,可要求站点将它的证书传送过来 C) 为了避免他人假冒自己,可将Internet分成几个区域 D) 为了避免传送过程中第三方偷看,可使用SSL技术 E) 为了防止传送过程中第三方篡改,可使用base64编码技术 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/26
articleCard.readMore

数据仓库与数据挖掘 - 大数据在SEO网站优化领域的具体应用案例

当今互联网行业中,大大小小几百万公司成立,相对而来的就是成百上千的官方网站的陆续建立,但是怎样使自己公司的官网在百度、谷歌、必应等大牌搜索引擎中被收录,同时提高公司官网访问数量及知名度? 提高官网服务质量是第一位的,但是还需要对网站做SEO优化,但是怎样做SEO优化? 大公司的SEO优化都是形成体系的,提高网站的被搜索权重,同时增加特色关键字,增加网站访问量、加大网站被搜索引擎的索引等等方式。 作为一个计算机系的学生,对CSDN网站的需求量在浏览器中算是有着比较大的权重,接下来我借用CSDN网站的大数据网站分析,通过站长之家平台数据进行阐述个人对于大数据在SEO优化领域的具体应用。 上图是通过站长平台获取的对程序员网站CSDN的搜索排名,可以从图中得知CSDN占中文网站排名158、技术编程排名3、北京市排名85、百度权重6、Google权重7、反链数:4278,可以看出,此网站网络排名是较同等网站排名还是比较靠前的,同时由上图可以看到整站日均IP访问量达到100万响应之多,其数据承载量及SEO优化量还是比较大的。 上图可以看到CSDN中网站百度流量统计达到了1万2900,同时网站关键词库有着6196的热门关键词,其中索引量达到了234万7195,可见网站索引量都是比较大额的。 接下来我们看一下网站的收录及反链情况,见下图: 收录:百度占155万、谷歌占62万、360占140万、搜狗占341万; 反链:百度占171万、谷歌占3万左右、360占416万。 上图可以看到CSDN网站的热门标题及优化建议,CSDN全程“CSDN-专业IT技术社区”,这一标题占据着网站整体SEO关键词搜索,其中站长之家提供了对搜索的优化建议:一般不超过80个字符,这样来说对搜索引擎来说是比较友好的,便于搜索引擎的搜录及索引。 通过站长之家云平台可以看到,CSDN的百度权重走势、Alexa排名趋势、百度收录量变化趋势、整体来看,CSDN网站数据流量是稳步上升,众所周知,CSDN的文章来源至中国绝大部分程序员的技术经验及感受等文章,并呈现稳步增长趋势。 CSDN网站的关键词由上图可以看到主要是技术类的关键字,例如“HTTP、a-b、或与非、程序、下载”等关键词,这些词索引量搜录量都在100000000之中。 根据CSDN网站得出SEO优化的途径,主要优化来源有以下几点: 主动使网站让百度、Google、必应、360搜索引擎索引; 用户数量提升、访问数量提升,基数做大,访问量才会稳定; 关键词尽量不要和大牌网站关键词冲突,发掘新颖关键词; 升级网站为HTTPS协议,使网站安全且不会被挂马; 必要时购买百度、360权重,提升搜索时展示位置; 购买信用产品,提升网站信用值,吸引浏览者访问; 适当做网站分享,引流,提升访问量; 搜索体验及用户体验优化。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/25
articleCard.readMore

蓝桥杯 基础练习 阶乘计算

问题描述 输入一个正整数n,输出n!的值。 其中n!=1*2*3*…*n。 算法描述 n!可能很大,而计算机能表示的整数范围有限,需要使用高精度计算的方法。使用一个数组A来表示一个大整数a,A[0]表示a的个位,A[1]表示a的十位,依次类推。 将a乘以一个整数k变为将数组A的每一个元素都乘以k,请注意处理相应的进位。 首先将a设为1,然后乘2,乘3,当乘到n时,即得到了n!的值。 输入格式 输入包含一个正整数n,n<=1000。 输出格式 输出n!的准确值。 样例输入 10 样例输出 3628800 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <stdio.h> #include <string.h> #define MAX 10000 #define mod 10000 #define baselen 4 #define in(a) scanf("%d",&a) #define out1(a) printf("%d",a) #define out2(a) printf("%04d",a) typedef int type; struct bint{ type dig[MAX], len; bint(){len = 0, dig[0] = 0;} }; void by(bint a, type b, bint& c){ type i, carry; for( i = carry = 0; i <= a.len || carry; i++){ if( i <= a.len ) carry += b*a.dig[i]; c.dig[i] = carry%mod; carry /= mod; } i--; while( i && !c.dig[i] )i--; c.len = i; } bool input(bint& a){ type i, j, w, k, p; char data[MAX*baselen+1]; if(scanf("%s",data)==EOF)return false; w = strlen(data) - 1, a.len = 0; for(p=0;p<=w&&data[p]=='0';p++); while(1){ i = j = 0, k = 1; while(i<baselen&&w>=p){ j = j+ (data[w--] - '0')*k; k *= 10, i++; } a.dig[a.len++] = j; if(w<p)break; } a.len--; return true; } void output(bint& a){ type i; i = a.len - 1; out1(a.dig[a.len]); while(i>=0)out2(a.dig[i--]); } void give(type a, bint& b){ b.dig[0] = a%mod; a /= mod; if(a>0)b.dig[1] = a, b.len = 1; else b.len = 0; } int main() { bint a;int b,i;scanf("%d",&b);give(1,a); for(i=2;i<=b;i++)by(a,i,a); output(a);printf("\n"); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/7
articleCard.readMore

蓝桥杯 基础练习 高精度加法

问题描述 输入两个整数a和b,输出这两个整数的和。a和b都不超过100位。 算法描述 由于a和b都比较大,所以不能直接使用语言中的标准数据类型来存储。对于这种问题,一般使用数组来处理。 定义一个数组A,A[0]用于存储a的个位,A[1]用于存储a的十位,依此类推。同样可以用一个数组B来存储b。 计算c=a+b的时候,首先将A[0]与B[0]相加,如果有进位产生,则把进位(即和的十位数)存入r,把和的个位数存入C[0],即C[0]等于(A[0]+B[0])%10。然后计算A[1]与B[1]相加,这时还应将低位进上来的值r也加起来,即C[1]应该是A[1]、B[1]和r三个数的和.如果又有进位产生,则仍可将新的进位存入到r中,和的个位存到C[1]中。依此类推,即可求出C的所有位。 最后将C输出即可。 输入格式 输入包括两行,第一行为一个非负整数a,第二行为一个非负整数b。两个整数都不超过100位,两数的最高位都不是0。 输出格式 输出一行,表示a+b的值。 样例输入 1 2 20100122201001221234567890 2010012220100122 样例输出 1 20100122203011233454668012 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <cstdio> #include <iostream> #include <cstring> using namespace std; int a[401], alen, b[401], blen, c[400], clen; char st[400]; int main() { int i, j, n, len; scanf( "%s", st ); alen = strlen( st ); for ( i = 1; i <= alen; i++ ) a[i] = st[alen - i] - 48; scanf( "%s", st ); blen = strlen( st ); for ( i = 1; i <= blen; i++ ) b[i] = st[blen - i] - 48; if ( alen > blen ) clen = alen; else clen = blen; for ( i = 1; i <= clen; i++ ) c[i] = a[i] + b[i]; for ( i = 1; i <= clen; i++ ) { if ( c[i] >= 10 ) { c[i + 1] = c[i + 1] + c[i] / 10; c[i] = c[i] % 10; } } if ( c[clen + 1] > 0 ) clen++; for ( i = clen; i >= 1; i-- ) { printf( "%d", c[i] ); } printf( "\n" ); return(0); } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/5
articleCard.readMore

蓝桥杯 基础练习 Huffuman树

问题描述 Huffman树在编码中有着广泛的应用。在这里,我们只关心Huffman树的构造过程。 给出一列数{pi}={p0,p1, …,pn-1},用这列数构造Huffman树的过程如下: 找到{pi}中最小的两个数,设为pa和pb,将pa和pb从{pi}中删除掉,然后将它们的和加入到{pi}中。这个过程的费用记为pa+pb。 重复步骤1,直到{pi}中只剩下一个数。 在上面的操作过程中,把所有的费用相加,就得到了构造Huffman树的总费用。 本题任务:对于给定的一个数列,现在请你求出用该数列构造Huffman树的总费用。 例如,对于数列{pi}={5, 3, 8, 2, 9},Huffman树的构造过程如下: 找到{5, 3, 8, 2, 9}中最小的两个数,分别是2和3,从{pi}中删除它们并将和5加入,得到{5, 8, 9, 5},费用为5。 找到{5, 8, 9, 5}中最小的两个数,分别是5和5,从{pi}中删除它们并将和10加入,得到{8, 9, 10},费用为10。 找到{8, 9, 10}中最小的两个数,分别是8和9,从{pi}中删除它们并将和17加入,得到{10, 17},费用为17。 找到{10, 17}中最小的两个数,分别是10和17,从{pi}中删除它们并将和27加入,得到{27},费用为27。 现在,数列中只剩下一个数27,构造过程结束,总费用为5+10+17+27=59。 输入格式 输入的第一行包含一个正整数n(n<=100)。 接下来是n个正整数,表示p0,p1, …,pn-1,每个数不超过1000。 输出格式 输出用这些数构造Huffman树的总费用。 样例输入 1 2 5 5 3 8 2 9 样例输出 59 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include<iostream> #include<queue> using namespace std; priority_queue<int, vector<int>, greater<int> > pq; //构造从小到大的优先队列 int main() { int n; cin >> n; while (!pq.empty()) pq.pop(); int x, s; for (int i = 0; i < n; i++) { cin >> x; pq.push(x); } int sum = 0; while (pq.size() > 1) { s = pq.top(); pq.pop(); s += pq.top(); pq.pop(); sum += s; pq.push(s); } cout << sum << endl; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/4
articleCard.readMore

蓝桥杯 基础练习 2n皇后问题

问题描述 给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。 输入格式 输入的第一行为一个整数n,表示棋盘的大小。 接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。 输出格式 输出一个整数,表示总共有多少种放法。 样例输入 1 2 3 4 5 4 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 样例输出 2 样例输入 1 2 3 4 5 4 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 样例输出 0 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include<cstdio> using namespace std; int n; int sum; bool g[9][9]; bool wh[9]; bool wd[17]; bool wu[17]; bool bh[9]; bool bd[17]; bool bu[17]; void white(int h){ if(h==n){ sum++; }else{ for(int i=0;i<n;i++){ if(!g[h][i])continue; if(wh[i])continue; if(wd[i+h])continue; if(wu[(i-h)+n])continue; wh[i]=wd[i+h]=wu[(i-h)+n]=1; white(h+1); wh[i]=wd[i+h]=wu[(i-h)+n]=0; } } } void black(int h){ if(h==n){ white(0); }else{ for(int i=0;i<n;i++){ if(!g[h][i])continue; if(bh[i])continue; if(bd[i+h])continue; if(bu[(i-h)+n])continue; g[h][i]=0; bh[i]=bd[i+h]=bu[(i-h)+n]=1; black(h+1); g[h][i]=1; bh[i]=bd[i+h]=bu[(i-h)+n]=0; } } } int main(){ int i; int x; sum=0; scanf("%d",&n); for(i=0;i<n;i++){ wh[i]=bh[i]=0; wd[i]=bd[i]=0; wu[i]=bu[i]=0; for(int j=0;j<n;j++){ scanf("%d",&x); g[i][j]=(bool)x; } } for(;i<2*n;i++){ wd[i]=bd[i]=0; wu[i]=bu[i]=0; } black(0); printf("%d\n",sum); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/3
articleCard.readMore

蓝桥杯 基础练习 报时助手

问题描述 给定当前的时间,请用英文的读法将它读出来。 时间用时h和分m表示,在英文的读法中,读一个时间的方法是: 如果m为0,则将时读出来,然后加上“o’clock”,如3:00读作“three o’clock”。 如果m不为0,则将时读出来,然后将分读出来,如5:30读作“five thirty”。 时和分的读法使用的是英文数字的读法,其中0~20读作: 0:zero, 1: one, 2:two, 3:three, 4:four, 5:five, 6:six, 7:seven, 8:eight, 9:nine, 10:ten, 11:eleven, 12:twelve, 13:thirteen, 14:fourteen, 15:fifteen, 16:sixteen, 17:seventeen, 18:eighteen, 19:nineteen, 20:twenty。 30读作thirty,40读作forty,50读作fifty。 对于大于20小于60的数字,首先读整十的数,然后再加上个位数。如31首先读30再加1的读法,读作“thirty one”。 按上面的规则21:54读作“twenty one fifty four”,9:07读作“nine seven”,0:15读作“zero fifteen”。 输入格式 输入包含两个非负整数h和m,表示时间的时和分。非零的数字前没有前导0。h小于24,m小于60。 输出格式 输出时间时刻的英文。 样例输入 0 15 样例输出 zero fifteen C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <iostream> #include <string> #include <map> using namespace std; int main(int argc, char** argv) { map<int,string> maptime; maptime[0]="zero"; maptime[1]="one"; maptime[2]="two"; maptime[3]="three"; maptime[4]="four"; maptime[5]="five"; maptime[6]="six"; maptime[7]="seven"; maptime[8]="eight"; maptime[9]="nine"; maptime[10]="ten"; maptime[11]="eleven"; maptime[12]="twelve"; maptime[13]="thirteen"; maptime[14]="fourteen"; maptime[15]="fifteen"; maptime[16]="sixteen"; maptime[17]="seventeen"; maptime[18]="eighteen"; maptime[19]="nineteen"; maptime[20]="twenty"; maptime[30]="thirty"; maptime[40]="forty"; maptime[50]="fifty"; int h,m; cin>>h>>m; if(m==0) { if(h<=20) { cout<<maptime[h]<<" o'clock"; } else { cout<<maptime[20]<<" "<<maptime[h-20]<<" o'clock"; } } else { if(h<=20) { cout<<maptime[h]<<" "; } else { cout<<maptime[20]<<" "<<maptime[h-20]<<" "; } if(m<=20) { cout<<maptime[m]<<" "; } else { int k=m%10; cout<<maptime[m-k]<<" "<<maptime[k]<<" "; } } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/2
articleCard.readMore

蓝桥杯 基础练习 回形取数

问题描述 回形取数就是沿矩阵的边取数,若当前方向上无数可取或已经取过,则左转90度。一开始位于矩阵左上角,方向向下。 输入格式 输入第一行是两个不超过200的正整数m, n,表示矩阵的行和列。接下来m行每行n个整数,表示这个矩阵。 输出格式 输出只有一行,共mn个数,为输入矩阵回形取数得到的结果。数之间用一个空格分隔,行末不要有多余的空格。 样例输入 1 2 3 4 3 3 1 2 3 4 5 6 7 8 9 样例输出 1 2 3 1 4 7 8 9 6 3 2 5 样例输入 1 2 3 4 3 2 1 2 3 4 5 6 样例输出 1 3 5 6 4 2 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include<stdio.h> int main() { int m,n; scanf("%d %d",&m,&n); int s[200][200]; int a[200][200]; int i,j; for(i=0;i<m;i++) for(j=0;j<n;j++) { scanf("%d",&s[i][j]); a[i][j]=-1; } int k=0,b=m-1,c=n-1; int h=0; for(i=j=h;a[i][j]==-1&&k<=m*n;) { if(k<m*n) printf("%d ",s[i][j]); else printf("%d",s[i][j]); k++; a[i][j]=0; if(i<b&&a[i+1][j]==-1&&j==n-1-c) { i++; continue; } if(i==b&&a[i][j+1]==-1) { j++; continue; } if(j==c&&a[i-1][j]==-1) { i--; continue; } if(i==m-1-b&&a[i][j-1]==-1) { j--; continue; } i=j=(++h); b--;c--; } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/2/1
articleCard.readMore

蓝桥杯 基础练习 龟兔赛跑预测

问题描述 话说这个世界上有各种各样的兔子和乌龟,但是研究发现,所有的兔子和乌龟都有一个共同的特点——喜欢赛跑。于是世界上各个角落都不断在发生着乌龟和兔子的比赛,小华对此很感兴趣,于是决定研究不同兔子和乌龟的赛跑。他发现,兔子虽然跑比乌龟快,但它们有众所周知的毛病——骄傲且懒惰,于是在与乌龟的比赛中,一旦任一秒结束后兔子发现自己领先t米或以上,它们就会停下来休息s秒。对于不同的兔子,t,s的数值是不同的,但是所有的乌龟却是一致——它们不到终点决不停止。   然而有些比赛相当漫长,全程观看会耗费大量时间,而小华发现只要在每场比赛开始后记录下兔子和乌龟的数据——兔子的速度v1(表示每秒兔子能跑v1米),乌龟的速度v2,以及兔子对应的t,s值,以及赛道的长度l——就能预测出比赛的结果。但是小华很懒,不想通过手工计算推测出比赛的结果,于是他找到了你——清华大学计算机系的高才生——请求帮助,请你写一个程序,对于输入的一场比赛的数据v1,v2,t,s,l,预测该场比赛的结果。 输入格式 输入只有一行,包含用空格隔开的五个正整数v1,v2,t,s,l,其中(v1,v2<=100;t<=300;s<=10;l<=10000且为v1,v2的公倍数) 输出格式 输出包含两行,第一行输出比赛结果——一个大写字母“T”或“R”或“D”,分别表示乌龟获胜,兔子获胜,或者两者同时到达终点。 第二行输出一个正整数,表示获胜者(或者双方同时)到达终点所耗费的时间(秒数)。 样例输入 10 5 5 2 20 样例输出 1 2 D 4 样例输入 10 5 5 1 20 样例输出 1 2 R 3 样例输入 10 5 5 3 20 样例输出 1 2 T 4 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include<cstdio> #include<iostream> using namespace std; int main() { int v1,v2,t,s,L,L1=0,L2=0,ans=0,i,j; bool bk=true; scanf("%d%d%d%d%d",&v1,&v2,&t,&s,&L); while(1) { if(L1-L2>=t&&L1<L&&L2<L) { for(i=1;i<=s;i++) { if(L1<L&&L2<L) { L2+=v2; ans++; } } } else { L2+=v2; L1+=v1; ans++; } if(L1>=L) break; if(L2>=L) break; } if(L1>=L&&L2>=L){printf("D\n%d\n",ans);return 0;} if(L1>=L) {printf("R\n%d\n",ans); return 0;} if(L2>=L) {printf("T\n%d\n",ans); return 0;} return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/31
articleCard.readMore

蓝桥杯 基础练习 Sine之舞

问题描述 最近FJ为他的奶牛们开设了数学分析课,FJ知道若要学好这门课,必须有一个好的三角函数基本功。所以他准备和奶牛们做一个“Sine之舞”的游戏,寓教于乐,提高奶牛们的计算能力。 不妨设 1 2 An=sin(1–sin(2+sin(3–sin(4+…sin(n))…) Sn=(…(A1+n)A2+n-1)A3+…+2)An+1 FJ想让奶牛们计算Sn的值,请你帮助FJ打印出Sn的完整表达式,以方便奶牛们做题。 输入格式 仅有一个数:N<201。 输出格式 请输出相应的表达式Sn,以一个换行符结束。输出中不得含有多余的空格或换行、回车符。 样例输入 3 样例输出 ((sin(1)+3)sin(1–sin(2))+2)sin(1–sin(2+sin(3)))+1 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include<stdio.h> void An_Output(int n, int t) { if(n == t) { printf("sin(%d)", t); return ; } char c; c = t % 2 == 1 ? '+' : '-'; printf("sin(%d%c", t, c); An_Output(n, ++t); printf(")"); } void Sn_Output(int n, int t) { // Sn=(...(A1+n)A2+n-1)A3+...+2)An+1 if(n == t) { return ; } printf("("); Sn_Output(n, t+1); if(t != n - 1) { printf(")"); } An_Output(n - t, 1); printf("+%d", t+1); } int main() { int n; scanf("%d", &n); Sn_Output(n, 1); if(n!=1) printf(")"); An_Output(n, 1); printf("+1\n"); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/30
articleCard.readMore

蓝桥杯 基础练习 FJ的字符串

问题描述 FJ在沙盘上写了这样一些字符串: A1 = “A” A2 = “ABA”    A3 = “ABACABA”    A4 = “ABACABADABACABA”    … …    你能找出其中的规律并写所有的数列AN吗? 输入格式 仅有一个数:N ≤ 26。 输出格式 请输出相应的字符串AN,以一个换行符结束。输出中不得含有多余的空格或换行、回车符。 样例输入 3 样例输出 ABACABA C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include<iostream> #include<cstdio> using namespace std; void dfs(int k,int p) { if (k==1) { printf("%c",p+'A'); return; } dfs(k/2,p-1);dfs(1,p);dfs(k/2,p-1); } int main() { int n; scanf("%d",&n); int sum=1; n--; for (int i=1;i<=n;i++) sum=sum*2+1; dfs(sum,n); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/30
articleCard.readMore

蓝桥杯 基础练习 芯片测试

问题描述 有n(2≤n≤20)块芯片,有好有坏,已知好芯片比坏芯片多。 每个芯片都能用来测试其他芯片。用好芯片测试其他芯片时,能正确给出被测试芯片是好还是坏。而用坏芯片测试其他芯片时,会随机给出好或是坏的测试结果(即此结果与被测试芯片实际的好坏无关)。 给出所有芯片的测试结果,问哪些芯片是好芯片。 输入格式 输入数据第一行为一个整数n,表示芯片个数。 第二行到第n+1行为n*n的一张表,每行n个数据。表中的每个数据为0或1,在这n行中的第i行第j列(1≤i, j≤n)的数据表示用第i块芯片测试第j块芯片时得到的测试结果,1表示好,0表示坏,i=j时一律为1(并不表示该芯片对本身的测试结果。芯片不能对本身进行测试)。 输出格式 按从小到大的顺序输出所有好芯片的编号 样例输入 1 2 3 4 3 1 0 1 0 1 0 1 0 1 样例输出 1 3 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include<iostream> #include<cstdio> #include<cstring> using namespace std; bool a[25][25]; bool v[25]; int n; bool dfs(int k) { if (k==n) { int sum=0; for (int i=1;i<=n;i++) if (v[i]) sum++; if (sum>n-sum) for (int i=1;i<=n;i++) if (v[i]) printf("%d ",i); return true; } if (v[k]==true) { int len=0,s[25]; for (int i=1;i<=n;i++) if (!a[k][i] && v[i]) { s[++len]=i; v[i]=false; } if (dfs(k+1)) return true; for (int i=1;i<=len;i++) v[s[i]]=true; } if (dfs(k+1)) return true; } int main() { scanf("%d",&n); memset(v,true,sizeof(v)); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { int c; scanf("%d",&c); if (c) a[i][j]=1; else a[i][j]=0; } dfs(1); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/30
articleCard.readMore

蓝桥杯 基础练习 数的读法

问题描述 Tom教授正在给研究生讲授一门关于基因的课程,有一件事情让他颇为头疼:一条染色体上有成千上万个碱基对,它们从0开始编号,到几百万,几千万,甚至上亿。    比如说,在对学生讲解第1234567009号位置上的碱基时,光看着数字是很难准确的念出来的。    所以,他迫切地需要一个系统,然后当他输入12 3456 7009时,会给出相应的念法:    十二亿三千四百五十六万七千零九    用汉语拼音表示为    shi er yi san qian si bai wu shi liu wan qi qian ling jiu    这样他只需要照着念就可以了。    你的任务是帮他设计这样一个系统:给定一个阿拉伯数字串,你帮他按照中文读写的规范转为汉语拼音字串,相邻的两个音节用一个空格符格开。    注意必须严格按照规范,比如说“10010”读作“yi wan ling yi shi”而不是“yi wan ling shi”,“100000”读作“shi wan”而不是“yi shi wan”,“2000”读作“er qian”而不是“liang qian”。 输入格式 有一个数字串,数值大小不超过2,000,000,000。 输出格式 是一个由小写英文字母,逗号和空格组成的字符串,表示该数的英文读法。 样例输入 1234567009 样例输出 shi er yi san qian si bai wu shi liu wan qi qian ling jiu C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> using namespace std; char df[][10]={"ling","yi","er","san","si","wu","liu","qi","ba","jiu"}; char s[15]; int main() { scanf("%s",s); int lens=strlen(s); bool bk=false; for (int i=0;i<lens;i++) { int p,lendf; p=s[i]-'0'; if (p!=0) { bk=false; lendf=strlen(df[p]); if (s[i-1]-'0'==0) printf("ling "); if ((lens-i)%4==2 && p==1 /*&& s[i-1]-'0'==0 && s[i-2]-'0'==0*/ && i==0) { printf("shi "); continue; } for (int j=0;j<lendf;j++) printf("%c",df[p][j]); printf(" "); if ((lens-i)%4==2) printf("shi "); if ((lens-i)%4==3) printf("bai "); if ((lens-i)%4==0) printf("qian "); } if ((lens-i)%4==1) { if ((lens-i)/4==2) { bk=true; printf("yi "); } if (bk==false && (lens-i)/4==1) printf("wan "); } } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/29
articleCard.readMore

蓝桥杯 基础练习 矩形面积交

问题描述   平面上有两个矩形,它们的边平行于直角坐标系的X轴或Y轴。对于每个矩形,我们给出它的一对相对顶点的坐标,请你编程算出两个矩形的交的面积。 输入格式   输入仅包含两行,每行描述一个矩形。 在每行中,给出矩形的一对相对顶点的坐标,每个点的坐标都用两个绝对值不超过10^7的实数表示。 输出格式   输出仅包含一个实数,为交的面积,保留到小数后两位。 样例输入 1 2 1 1 3 3 2 2 4 4 样例输出 1.00 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <algorithm> #include <cmath> #include <cstdio> using namespace std; int main() { double x1,x2,y1,y2; double q1,q2,w1,w2; while(cin>>x1>>y1>>x2>>y2>>q1>>w1>>q2>>w2) { double xx=max(min(x1,x2),min(q1,q2)); double yy=max(min(y1,y2),min(w1,w2)); double xxup=min(max(x1,x2),max(q1,q2)); double yyup=min(max(y1,y2),max(w1,w2)); if(xxup>xx) printf("%.2f\n",fabs((xx)-(xxup))*fabs((yy)-(yyup))); else printf("0.00\n"); } } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/23
articleCard.readMore

蓝桥杯 基础练习 矩阵乘法

问题描述   给定一个N阶矩阵A,输出A的M次幂(M是非负整数) 1 2 3 4 5 6 7 例如:   A =   1 2   3 4   A的2次幂   7 10   15 22 输入格式   第一行是一个正整数N、M(1<=N<=30, 0<=M<=5),表示矩阵A的阶数和要求的幂数   接下来N行,每行N个绝对值不超过10的非负整数,描述矩阵A的值 输出格式   输出共N行,每行N个整数,表示A的M次幂所对应的矩阵。相邻的数之间用一个空格隔开 样例输入 1 2 3 2 2 1 2 3 4 样例输出 1 2 7 10 15 22 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include<cstdio> #include<iostream> #include<cstring> using namespace std; int a[101][101]; int c[101][101]; int ans[101][101]; int main() { int i,j,k,l,m,n; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) for(j=1;j<=n;j++) scanf("%d",&a[i][j]); memset(ans,0,sizeof(ans)); for(i=1;i<=n;i++) ans[i][i]=1; for(k=1;k<=m;k++) { memset(c,0,sizeof(c)); for(i=1;i<=n;i++)for(j=1;j<=n;j++)for(l=1;l<=n;l++)c[i][j]+=ans[i][l]*a[l][j]; for(i=1;i<=n;i++)for(j=1;j<=n;j++)ans[i][j]=c[i][j]; } for(i=1;i<=n;i++) { for(j=1;j<n;j++)printf("%d ",ans[i][j]); printf("%d\n",ans[i][n]); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/22
articleCard.readMore

蓝桥杯 基础练习 完美的代价

问题描述   回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串,它不一定是回文的,请你计算最少的交换次数使得该串变成一个完美的回文串。    交换的定义是:交换两个相邻的字符    例如mamad    第一次交换 ad : mamda 第二次交换 md : madma 第三次交换 ma : madam (回文!完美!) 输入格式   第一行是一个整数N,表示接下来的字符串的长度(N <= 8000) 第二行是一个字符串,长度为N.只包含小写字母 输出格式   如果可能,输出最少的交换次数。 否则输出Impossible 样例输入 5 mamad 样例输出 3 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include<cstdio> #include<iostream> using namespace std; int changes(char s[],char x,int n) { int i,change=0,j,k; for(i=0;i<n/2;i++) { if(s[i]==x) { for(j=i;j<n-i-1;j++) if(s[n-i-1]==s[j]) break; change+=j-i; for(k=j;k>i;k--) s[k]=s[k-1]; s[i]=s[n-i-1]; } else { for(j=n-i-1;j>=i;j--) if(s[i]==s[j]) break; change+=n-i-1-j; for(k=j;k<n-i-1;k++) s[k]=s[k+1]; s[n-i-1]=s[i]; } } return change; } int main() { int n,i,k=0,b[26]={0},j; char y,s[8001]={0}; scanf("%d\n",&n); for(i=0;i<n;i++) { scanf("%c",&s[i]); b[s[i]-'a']++; } char x; for(j=0;j<26;j++) { if(b[j]%2!=0) { k++; x=j+'a'; } } if(k>=2) printf("Impossible\n"); else { printf("%d\n",changes(s,x,n)); return 0; } } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/22
articleCard.readMore

蓝桥杯 基础练习 分解质因数

问题描述  求出区间[a,b]中所有整数的质因数分解。 输入格式 输入两个整数a,b。 输出格式  每行输出一个数的分解,形如k=a1*a2*a3…(a1<=a2<=a3…,k也是从小到大的)(具体可看样例) 样例输入 3 10 样例输出 1 2 3 4 5 6 7 8 3=3 4=2*2 5=5 6=2*3 7=7 8=2*2*2 9=3*3 10=2*5 提示 先筛出所有素数,然后再分解。 数据规模和约定   2<=a<=b<=10000 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include<stdio.h> #include<iostream> #include<string.h> #include<string> #include <ctype.h> #include <math.h> using namespace std; int factor(int n) { int i, j = (int)sqrt(n); if (n % 2 == 0) return 2; for (i = 3; i <= j; i++) if (n % i == 0) return i; return n; } int main() { int i, j, k, m, n; scanf("%d%d", &m, &n); for (i = m; i <= n; i++) { j = factor(i); k = i / j; printf("%d=%d", i, j); while (k > 1) { j = factor(k); k /= j; printf("*%d", j); } printf("\n"); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/20
articleCard.readMore

蓝桥杯 基础练习 字符串对比

问题描述   给定两个仅由大写字母或小写字母组成的字符串(长度介于1到10之间),它们之间的关系是以下4中情况之一: 两个字符串长度不等。比如 Beijing 和 Hebei 两个字符串不仅长度相等,而且相应位置上的字符完全一致(区分大小写),比如 Beijing 和 Beijing 两个字符串长度相等,相应位置上的字符仅在不区分大小写的前提下才能达到完全一致(也就是说,它并不满足情况2)。比如 beijing 和 BEIjing 两个字符串长度相等,但是即使是不区分大小写也不能使这两个字符串一致。比如 Beijing 和 Nanjing 编程判断输入的两个字符串之间的关系属于这四类中的哪一类,给出所属的类的编号。 输入格式   包括两行,每行都是一个字符串 输出格式 仅有一个数字,表明这两个字符串的关系编号 样例输入 BEIjing beiJing 样例输出 3 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; char A[15],B[15]; int main() { scanf("%s",A); scanf("%s",B); int a=strlen(A); int b=strlen(B); int count=0; if(a!=b) //长度不等 { printf("1\n"); return 0; } //长度相等 else { for(int i=0;i<a;i++) { if((A[i]!=B[i])) { if(abs(A[i]-B[i])!=32) { printf("4\n"); return 0; } else { ++count; continue; } } } if((count==0)) printf("2\n"); else printf("3\n"); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/19
articleCard.readMore

蓝桥杯 基础练习 时间转换

问题描述   给定一个以秒为单位的时间t,要求用“::”的格式来表示这个时间。表示时间,表示分钟,而表示秒,它们都是整数且没有前导的“0”。 例如,若t=0,则应输出是“0:0:0”;若t=3661,则输出“1:1:1”。 输入格式   输入只有一行,是一个整数t(0<=t<=86399)。 输出格式   输出只有一行,是以“::”的格式所表示的时间,不包括引号。 样例输入0 样例输出0:0:0 样例输入5436 样例输出1:30:36 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std; int main() { int n,H,M,S,t; cin>>n; H=n/3600; t=n%3600; M=t/60; S=t%60; cout<<H<<":"<<M<<":"<<S; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/18
articleCard.readMore

蓝桥杯 基础练习 数列排序

问题描述   给定一个长度为n的数列,将这个数列按从小到大的顺序排列。1<=n<=200 输入格式   第一行为一个整数n。 第二行包含n个整数,为待排序的数,每个整数的绝对值小于10000。 输出格式   输出一行,按从小到大的顺序输出排序后的数列。 样例输入 58 3 6 4 9 样例输出 3 4 6 8 9 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include<iostream> #include<algorithm> using namespace std; int cmp(int a,int b) { return a<b; } int main() { int n; while(cin>>n) { int a[205]; for(int i=0;i<n;i++) { cin>>a[i]; } sort(a,a+n,cmp); cout<<a[0]; for(int i=1;i<n;i++) { cout<<' '<<a[i]; } cout<<endl; } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/17
articleCard.readMore

蓝桥杯 基础练习 十六进制转八进制

问题描述 给定n个十六进制正整数,输出它们对应的八进制数。 输入格式 输入的第一行为一个正整数n (1<=n<=10)。   接下来n行,每行一个由09、大写字母AF组成的字符串,表示要转换的十六进制正整数,每个十六进制数长度不超过100000。 输出格式 输出n行,每行为输入对应的八进制正整数。 【注意】   输入的十六进制数不会有前导0,比如012A。   输出的八进制数也不能有前导0。 样例输入   2   39   123ABC 样例输出   71   4435274 【提示】   先将十六进制数转换成某进制数,再由某进制数转换成八进制。 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> #include <stdio.h> #include <string.h> #include <STDLIB.H> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int GetI(char c) { return c>>4&1?c&15:(c&15)+9; } int main(int argc, char *argv[]) { char arr[200001] = {'\0'}; char brr[400001] = {'\0'}; int n = 0; int i = 0; scanf("%d",&n); for(i = 0;i < n;i++) { scanf("%s",arr); int m[3] = {1,16,256}; int len = strlen(arr); int j = len-1; int a,b,c; a = b = c = 0; int k = 0,l = 0; int count = 0; while(j>-1) { a += (arr[j]>>4&1?arr[j]&15:(arr[j]&15)+9)*m[k]; //个位 if(k==2||j==0) { while(a) { brr[l++] = ((a&7)|48); a = a>>3; count++; } while(j!=0&&count<4) { brr[l++] = '0'; count++; } count = 0; } k = (k+1)%3; j--; } strrev(brr); printf("%s\n",brr); memset(arr,'\0',(sizeof(char)*200001)); memset(brr,'\0',(sizeof(char)*400001)); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/16
articleCard.readMore

蓝桥杯 基础练习 十六进制转十进制

问题描述 从键盘输入一个不超过8位的正的十六进制数字符串,将它转换为正的十进制数后输出。 注:十六进制数中的10~15分别用大写的英文字母A、B、C、D、E、F表示。 样例输入 FFFF 样例输出 65535 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include<iostream> #include<string> using namespace std; int main() { string s; while(cin>>s) { int leth=s.length(); long long sum=0; for(int i=0;i<leth;i++) { if(s[i]>='A'&&s[i]<='F') { sum=sum*16+s[i]-'A'+10; // cout<<sum<<endl; } else { sum=sum*16+s[i]-'0'; //cout<<sum<<endl; } } cout<<sum<<endl; } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/15
articleCard.readMore

蓝桥杯 基础练习 十进制转十六进制

问题描述   十六进制数是在程序设计时经常要使用到的一种整数的表示方式。它有0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F共16个符号,分别表示十进制数的0至15。十六进制的计数方法是满16进1,所以十进制数16在十六进制中是10,而十进制的17在十六进制中是11,以此类推,十进制的30在十六进制中是1E。   给出一个非负整数,将它表示成十六进制的形式。 输入格式   输入包含一个非负整数a,表示要转换的数。0<=a<=2147483647 输出格式   输出这个整数的16进制表示 样例输入30 样例输出1E C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include<iostream> #include<cstdio> using namespace std; int main() { int n; char s[100000]; while(cin>>n) { int k=0; if(n==0) { cout<<0; } else { while(n!=0) { if(n%16>=10) { s[k++]='A'+n%16-10; } else { s[k++]='0'+n%16; } n=n/16; } for(int i=k-1;i>=0;i--) { cout<<s[i]; } } cout<<endl; } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/14
articleCard.readMore

蓝桥杯 基础练习 特殊回文数

问题描述   123321是一个非常特殊的数,它从左边读和从右边读是一样的。 输入一个正整数n, 编程求所有这样的五位和六位十进制数,满足各位数字之和等于n 。 输入格式   输入一行,包含一个正整数n。 输出格式   按从小到大的顺序输出满足条件的整数,每个整数占一行。 样例输入 52 样例输出 899998 989989 998899 数据规模和约定   1<=n<=54。 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std; int main() { int n,a,b,c,t; cin>>n; for(a=1;a<10;a++) for(b=0;b<10;b++) for(c=0;c<10;c++) { t=a*10001+b*1010+c*100; if(2*a+2*b+c==n) cout<<t<<endl; } for(a=1;a<10;a++) for(b=0;b<10;b++) for(c=0;c<10;c++) { t=a*100001+b*10010+c*1100; if(2*a+2*b+2*c==n) cout<<t<<endl; } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/13
articleCard.readMore

蓝桥杯 基础练习 回文数

问题描述   1221是一个非常特殊的数,它从左边读和从右边读是一样的,编程求所有这样的四位十进制数。 输出格式   按从小到大的顺序输出满足条件的四位十进制数。 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include<stdio.h> int main() { for(int i1=1;i1<10;i1++) { for(int i2=0;i2<10;i2++) { for(int i3=0;i3<10;i3++) { for(int i4=0;i4<10;i4++) { if(i1==i4 && i2==i3) printf("%d%d%d%d\n",i1,i2,i3,i4); } } } } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/12
articleCard.readMore

蓝桥杯 基础练习 特殊的数字

问题描述   153是一个非常特殊的数,它等于它的每位数字的立方和,即153=111+555+333。编程求所有满足这种条件的三位十进制数。 输出格式   按从小到大的顺序输出满足条件的三位十进制数,每个数占一行。 C++算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include<iostream> using namespace std; int main() { int i,j,k; for(i=1;i<=9;i++) { for(j=0;j<=9;j++) { for(k=0;k<=9;k++) { if(i*100+j*10+k==i*i*i+j*j*j+k*k*k) { cout<<i*100+j*10+k<<endl; } } } } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/11
articleCard.readMore

蓝桥杯 基础练习 杨辉三角形

问题描述 杨辉三角形又称Pascal三角形,它的第i+1行是(a+b)i的展开式的系数。 它的一个重要性质是:三角形中的每个数字等于它两肩上的数字相加。 下面给出了杨辉三角形的前4行: 1 2 3 4 1 1 1 1 2 1 1 3 3 1 给出n,输出它的前n行。 输入格式 输入包含一个数n。 输出格式 输出杨辉三角形的前n行。每一行从这一行的第一个数开始依次输出,中间使用一个空格分隔。请不要在前面输出多余的空格。 样例输入 4 样例输出 1 2 3 4 1 1 1 1 2 1 1 3 3 1 数据规模与约定 1 <= n <= 34。 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std; const int MAXN = 40; int n; int a[MAXN][MAXN]; int main() { cin >> n; a[0][0] = 1; for (int i = 0; i < n; ++i) { a[i][0] = a[i][i] = 1; for (int j = 1; j < i; ++j) a[i][j] = a[i-1][j-1] + a[i-1][j]; } for (int i = 0; i < n; ++i) { for (int j = 0; j <= i; ++j) cout << a[i][j] << " "; cout << endl; } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/10
articleCard.readMore

蓝桥杯 基础练习 查找整数

问题描述 给出一个包含n个整数的数列,问整数a在数列中的第一次出现是第几个。 输入格式 第一行包含一个整数n。 第二行包含n个非负整数,为给定的数列,数列中的每个数都不大于10000。 第三行包含一个整数a,为待查找的数。 输出格式 如果a在数列中出现了,输出它第一次出现的位置(位置从1开始编号),否则输出-1。 样例输入 6 1 9 4 8 3 9 9 样例输出 2 数据规模与约定 1 <= n <= 1000。 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std; const int MAXN = 10001; int n, a, ans; int s[MAXN]; int main() { cin >> n; for (int i = 0; i < n; ++i) cin >> s[i]; cin >> a; ans = -1; for (int i = 0; i < n; ++i) { if (s[i] == a) { ans = i + 1; break; } } cout << ans << endl; return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/9
articleCard.readMore

蓝桥杯 基础练习 数列特征

问题描述 给出n个数,找出这n个数的最大值,最小值,和。 输入格式 第一行为整数n,表示数的个数。 第二行有n个数,为给定的n个数,每个数的绝对值都小于10000。 输出格式 输出三行,每行一个整数。第一行表示这些数中的最大值,第二行表示这些数中的最小值,第三行表示这些数的和。 样例输入 5 1 3 -2 4 5 样例输出 5 -2 11 数据规模与约定 1 <= n <= 10000。 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include<cstdio> #include<iostream> #include<algorithm> #include<cstring> using namespace std; int main(){ int n; while(cin>>n){ int a[10005]; int sum=0; for(int i=0;i<n;i++){ scanf("%d",&a[i]); sum+=a[i]; } sort(a,a+n); cout<<a[n-1]<<endl<<a[0]<<endl<<sum<<endl; } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/8
articleCard.readMore

蓝桥杯 基础练习 字母图形

问题描述 利用字母可以组成一些美丽的图形,下面给出了一个例子: 1 2 3 4 5 ABCDEFG BABCDEF CBABCDE DCBABCD EDCBABC 这是一个5行7列的图形,请找出这个图形的规律,并输出一个n行m列的图形。 输入格式 输入一行,包含两个整数n和m,分别表示你要输出的图形的行数的列数。 输出格式 输出n行,每个m个字符,为你的图形。 样例输入 5 7 样例输出 1 2 3 4 5 ABCDEFG BABCDEF CBABCDE DCBABCD EDCBABC 数据规模与约定 1 <= n, m <= 26。 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <cmath> using namespace std; int main() { int n, m; cin >> n >> m; for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) cout << char('A'+abs(i-j)); cout << endl; } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/7
articleCard.readMore

蓝桥杯 基础练习 01字串

问题描述 对于长度为5位的一个01串,每一位都可能是0或1,一共有32种可能。它们的前几个是: 1 2 3 4 5 00000 00001 00010 00011 00100 请按从小到大的顺序输出这32种01串。 输入格式 本试题没有输入。 输出格式 输出32行,按从小到大的顺序每行一个长度为5的01串。 样例输出 1 2 3 4 5 00000 00001 00010 00011 <以下部分省略> C++代码 1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std; int main() { for (int i = 0; i <= 1; ++i) for (int j = 0; j <= 1; ++j) for (int k = 0; k <= 1; ++k) for (int l = 0; l <= 1; ++l) for (int m = 0; m <= 1; ++m) cout << i << j << k << l << m << endl; return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/6
articleCard.readMore

蓝桥杯 基础练习 闰年判断

问题描述 给定一个年份,判断这一年是不是闰年。 当以下情况之一满足时,这一年是闰年: 年份是4的倍数而不是100的倍数; 年份是400的倍数。 其他的年份都不是闰年。 输入格式 输入包含一个整数y,表示当前的年份。 输出格式 输出一行,如果给定的年份是闰年,则输出yes,否则输出no。 说明:当试题指定你输出一个字符串作为结果(比如本题的yes或者no,你需要严格按照试题中给定的大小写,写错大小写将不得分。 样例输入2013 样例输出no 样例输入2016 样例输出yes 数据规模与约定 1990 <= y <= 2050。 C++源代码 1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std; int main() { int y; cin >> y; if (y%4==0 && y%100!=0 || y%400==0) cout << "yes" << endl; else cout << "no" << endl; return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/5
articleCard.readMore

蓝桥杯 入门训练 Fibonacci数列

问题描述 Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1。 当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少。 输入格式 输入包含一个整数n。 输出格式 输出一行,包含一个整数,表示Fn除以10007的余数。 说明:在本题中,答案是要求Fn除以10007的余数,因此我们只要能算出这个余数即可,而不需要先计算出Fn的准确值,再将计算的结果除以10007取余数,直接计算余数往往比先算出原数再取余简单。 样例输入10 样例输出55 样例输入22 样例输出7704 数据规模与约定 1 <= n <= 1,000,000 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdlib.h> #include <stdio.h> #define MOD 10007 #define MAXN 1000001 int n, i, F[MAXN]; int main() { scanf("%d", &n); F[1] = 1; F[2] = 1; for (i = 3; i <= n; ++i) F[i] = (F[i-1] + F[i-2]) % MOD; printf("%d\n", F[n]); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/4
articleCard.readMore

蓝桥杯 入门训练 圆的面积

问题描述 给定圆的半径r,求圆的面积。 输入格式 输入包含一个整数r,表示圆的半径。 输出格式 输出一行,包含一个实数,四舍五入保留小数点后7位,表示圆的面积。 说明:在本题中,输入是一个整数,但是输出是一个实数。 对于实数输出的问题,请一定看清楚实数输出的要求,比如本题中要求保留小数点后7位,则你的程序必须严格的输出7位小数,输出过多或者过少的小数位数都是不行的,都会被认为错误。 实数输出的问题如果没有特别说明,舍入都是按四舍五入进行。 样例输入 4 样例输出 50.2654825 数据规模与约定 1 <= r <= 10000 提示 本题对精度要求较高,请注意π的值应该取较精确的值。你可以使用常量来表示π,比如PI=3.14159265358979323,也可以使用数学公式来求π,比如PI=atan(1.0)*4。 C++源代码 1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> #include <math.h> int main() { int r; double s, PI; scanf("%d", &r); PI = atan(1.0) * 4; s = PI * r * r; printf("%.7lf", s); return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/3
articleCard.readMore

蓝桥杯 入门训练 序列求和

问题描述 求1+2+3+…+n的值。 输入格式 输入包括一个整数n。 输出格式 输出一行,包括一个整数,表示1+2+3+…+n的值。 样例输入4 样例输出10 样例输入100 说明:有一些试题会给出多组样例输入输出以帮助你更好的做题。 一般在提交之前所有这些样例都需要测试通过才行,但这不代表这几组样例数据都正确了你的程序就是完全正确的,潜在的错误可能仍然导致你的得分较低。 样例输出5050 数据规模与约定 1 <= n <= 1,000,000,000。 说明:请注意这里的数据规模。 本题直接的想法是直接使用一个循环来累加,然而,当数据规模很大时,这种“暴力”的方法往往会导致超时。此时你需要想想其他方法。你可以试一试,如果使用1000000000作为你的程序的输入,你的程序是不是能在规定的上面规定的时限内运行出来。 本题另一个要值得注意的地方是答案的大小不在你的语言默认的整型(int)范围内,如果使用整型来保存结果,会导致结果错误。 如果你使用C++或C语言而且准备使用printf输出结果,则你的格式字符串应该写成%I64d以输出long long类型的整数。 C++源代码 1 2 3 4 5 6 7 8 9 #include <iostream> using namespace std; int main() { long long n; cin >> n; cout << (1+n) * n / 2; return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2019/1/2
articleCard.readMore

2018 年度总结

转眼之间,自己已经成了大三狗了,记得上一年,自己还是一个刚有目标,刚有理想的孩子,现在已经转身一变,变成一块老腊肉了,?,再过一年的时间,自己将走向社会,去赚取自己人生过程中的真正的第一桶金。 14号参加了学院的新生学习交流会,作为一个老学长给学弟学妹们讲解自己的人生规划,自己的大学之路怎么走过,感慨万千,大学四年时光匆匆而逝,面对新一届的新生,真的是老了,哈哈,其中,和我作伴演讲的邻班同学讲了一个深刻的问题,是“何为大学”,大学–单纯的是年纪大么?我不这么认为,大学之道,在于学,在于精,在于创新,只有不断地学习,充实自己的大学时光,才是大学之道。 又是一年给大家分享的时光,希望今年能够给你们传播一些比较好的经验、理念、公众号、还有一系列好玩的东西…… 和上年一样,希望给大家带来一些比较实用的工具及网页,还有公众号,等等福利。 emmm 没有喽 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/12/28
articleCard.readMore

新域名 debuginn.cn 上线了

,太贵了,后来听说.cn域名是咱国家的域名,而且根服务器在咱国家,而且第二年续费及第一年的购买只需要28元钱,还是比较实惠的,原本本网站的域名是roguefeathers.link现在改为debuginn.cn. 取此域名有几个含义: Debug客栈:Debug这一个词,就是一个在程序员界的“调试”一词,取此词语正好是用于我的网站在不断更新,加上我就是一名编程菜鸟,取此义也是对编程的一种热爱; debuginn 正好是 Debug客栈 的英文解释; 接受了同僚之间的建议,域名还是取一个简单易记的。 另外,本人在此声明一下,以后此网站唯一域名:debuginn.cn,谢谢大家的支持,嘿嘿。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/10/29
articleCard.readMore

Ajax 对 XML 信息的接收和处理

Ajax负责请求xml和接收xml信息,dom负责处理xml信息 dom: php中,dom是php与xml(html)之间的沟通桥梁; javascript中,dom是javascript与html(xml)之间沟通的桥梁。 xml需要从服务器端返回到客户端被javascript处理; ajax:负责请求xml回来; DOM(javascript):负责处理xml信息。 Ajax+JavaScript实现对xml的接收处理,可以方便我们后期实现一个静态网站(html+css+javascript)实现对各个接口数据的处理。 document对象和普通元素对象都可以调用getElementsByTagName()方法。 函数执行操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>利用ajax+javaScript实现对xml的接受和处理</title> <script type="text/javascript"> function f1(){ //ajax请求xml信息回来 //javascript的dom技术处理xml //document xmldocument var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 ){ //[object XMLDocument] 其实是xml根结点的父节点对象 //alert(xhr.responseXML); var xmldom = xhr.responseXML; //console.log(xmldom.firstChild);<weather> var citys = xmldom.getElementsByTagName('city'); //citys[1] //第二个city的元素节点对象 /*for(var k in citysp[1]){//k代表元素节点的成员名称 //有输出其中一个成员方法:getElementsByTagName //结论:document对象 和 普通元素都可以调用getElementdByTagName函数 console.log(k); }*/ var str =""; for(var i=0; i<citys.length; i++){ var nm = citys[i].getElementsByTagName('name')[0].firstChild.nodeValue; var temp = citys[i].getElementsByTagName('temp')[0].firstChild.nodeValue; var wind = citys[i].getElementsByTagName('wind')[0].firstChild.nodeValue; str += "城市:" +nm+ "--温度:"+temp+"--风向:"+wind+"<br/>"; } document.getElementById('result').innerHTML =str; } } xhr.open('get','./test01.xml'); xhr.send(null); } </script> </head> <body> <h2>利用ajax+javaScript实现对xml的接受和处理</h2> <input type="button" value="触发" onclick="f1()"> <div id="result"></div> </body> </html> XML文件数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="utf-8" ?> <weather> <city> <name>北京</name> <temp>23-31度</temp> <wind>北风</wind> </city> <city> <name>上海</name> <temp>23-31度</temp> <wind>东风</wind> </city> <city> <name>广州</name> <temp>28-31度</temp> <wind>南风</wind> </city> <city> <name>深圳</name> <temp>29-31度</temp> <wind>东南风</wind> </city> </weather> 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/10/22
articleCard.readMore

HTTP 中 Get 与 POST 请求的区别

给服务器传递数据量 get方式的大小是受限于浏览器的,大部分浏览器是2k的限制; 每一个浏览器的限制是不一样的 Chrome的限制是8K http://网址/index.php?name=tom 上述请求get方式传递了9个字节的信息; 1024字节 = 1k post原则没有限制,php.ini最其限制为8M 安全方面 POST传输数据相对来说比较安全。 传输数据的形式不一样 Get方式在url地址后面以请求字符串的形式传递参数 http://网址/index.php?name=tom&age=23&addr=DZU 蓝色部分就是请求字符串,就是一些“名-值”对,中间使用 & 符号链接 post方式是把from表单的数据请求出来以XML方式传递给服务器 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/7/20
articleCard.readMore

JavaScript 跨域问题

JS跨域 跨域,指的是浏览器不能执行其他网站的脚本。 它是由浏览器的同源策略造成的,是浏览器施加的安全限制。 JavaScript处于安全方面的考虑,不允许跨域调用其他页面的对象。 http://debuginn.com/a.html调用http://debuginn.com/b.php (非跨域) http://debuginn.com/a.html调用http://baidu.link/b.php (跨域) http://debuginn.com/a.html调用http://a.debuginn.com/b.php (跨域) http://debuginn.com/a.html调用http://debuginn.com:81/b.php (跨域) http://debuginn.com/a.html调用https://debuginn.com/b.php (跨域) 跨域解决方法一 — 代理 跨域解决方法二 — JSONP JSONP用于解决主流浏览器的跨域数据访问的问题。 JSONP技术仅仅支持GET请求,不支持POST请求。 跨域解决方法三 — XHR2 在HTML5中提供的XMLHttpREquest Level2已经实现了跨域访问以及其他的一些新功能 IE10以下版本均不支持 在服务器端做一些小的改造即可: header(‘Access-Control-Allow-Origin:*’); header(‘Access-Control-Allow-Methods:POST,GET’); 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/7/18
articleCard.readMore

Ajax 异步的JavaScript与XML技术

Ajax 技术简介 AJAX即“Asynchronous JavaScript and XML”(异步的JavaScript与XML技术),指的是一套综合了多项技术的浏览器端网页开发技术。Ajax的概念由杰西·詹姆士·贾瑞特所提出。传统的Web应用允许用户端填写表单(form),当提交表单时就向网页服务器发送一个请求。服务器接收并处理传来的表单,然后送回一个新的网页,但这个做法浪费了许多带宽,因为在前后两个页面中的大部分HTML码往往是相同的。由于每次应用的沟通都需要向服务器发送请求,应用的回应时间依赖于服务器的回应时间。这导致了用户界面的回应比本机应用慢得多。与此不同,AJAX应用可以仅向服务器发送并取回必须的数据,并在客户端采用JavaScript处理来自服务器的回应。因为在服务器和浏览器之间交换的数据大量减少,服务器回应更快了。同时,很多的处理工作可以在发出请求的客户端机器上完成,因此Web服务器的负荷也减少了。 JSON技术 JavaScript 对象表示法JSON 用jQuery实现Ajax jQuery.ajax([settings]) type:类型,“POST”或“GET”,默认为“GET” url:发送请求的地址 data:是一个对象,联通请求的发送到服务器中的数据; dataType:预期服务器返回的数据类型。如果不确定,jQuery将自动根据HTTP包MIME信息来只能判断,一般采用json格式,将其设置为“JSON”; success:是一个方法请求成功后的回调函数,传入返回后的数据,以及包含成功代码的字符串; error:是一个方法,请求失败时调用此函数,传入XMLHttpRequest对象。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/7/17
articleCard.readMore

JavaScript 对象表示法JSON

JSON基本概念 JSON:JavaScript对象表示法(JavaScript Object Notation) JSON是存储和交换文本信息的语法,类似XML。采用键值对的方式来组织,易于人们阅读和编写,同时也有益于机器解析与生成。 JSON是独立于语言的,不管是什么语言,都可以及逆行解析json,按照json规则来就行。 JSON和XML对比 JSON的长度相对于XML来说比较短小 JSON读写速度比较快 JSON可以使用JavaScript内建的方法直接进行解析,转换成JavaScript对象,十分的方便 语法规则 书写格式是:名称/值对 名称/值对组合中的名称写在前面(在双引号中),值对写在后面(同样在双引号中),中间用冒号隔开,比如“name”:”张三” 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/7/16
articleCard.readMore

网站已经运行180天了

不知不觉中,网站已经运行了小半年了,其中有60天左右是在GitHub上部署的,当初是用本地的Node.JS进行网站渲染静态网页进行上传的,但是随着时间的流逝,自己的文章逐渐多了起来,渲染时间越来越长,看到现在网站加载的越来越慢,自己真是于心不忍,看到网上有这个WordPress好的平台,二话不说,就是干,哈哈,于是在大年29完成搭建,到现在一看我的时间戳,时间过的真快,希望网站越来越好,继续发展下去吧! 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/4/4
articleCard.readMore

操作系统 文件管理 文件的结构

文件的逻辑结构 设计文件逻辑结构的原则 易于操作 查找快捷 修改方便 空间紧凑 文件的逻辑结构 文件的逻辑结构就是用户所看到的文件的组织形式。 文件划分为三类逻辑结构:无结构的字符流式文件、定长记录文件和不定长记录文件构成的记录树。 定长记录文件和不定长文件可以统称为记录式文件。 流式文件 流式文件是有序字符的集合,其长度为该文件所包含的字符个数,所以又称为字符流文件。 源程序、目标代码等文件属于流式文件。UNIX内系统采用流式文件结构。 记录式文件 定长记录文件:各个记录长度相等。在检索时,可以根据记录号i及记录长度L就可以确定该记录的逻辑地址。 不定长记录文件:各个记录的长度不等,在查找时,逐条查找,直到找到所需要的记录。 文件的物理结构 顺序结构 顺序结构原理 顺序结构又称为连续结构,这是一种最简单的文件物理结构,他把逻辑上连续的文件信息依次存放在连续编号的物理快中。 在顺序结构中,一个文件的目录项中只要指出该文件占据的总块数和起始块号即可。 顺序结构的优缺点 优点:只要是知道了文件在文件存储设备上的起始块号和文件长度,就能很快地进行存取。 缺点:文件不能动态增长。 链接结构 链接结构原理 为每个文件构造所使用的磁盘块的链表。使用这种链接结构的文件,将逻辑上连续的文件分散存放在若干个不连续的物理块中。 间接索引是在索引表所指的物理快中不存放在文件信息,而是装有存在这些信息的物理快地址。 在索引结构文件中要存取文件时,需要至少访问存储设备两次以上,其中,一次是访问索引表,另一次是根据索引表访问在存储设备上的文件信息。 索引表的链接模式:一个索引表通常就是一个物理盘快。对大文件就用多个索引连接在一起。 多级索引:将一个大文件的所有索引表(二级索引)的地址存放在另一个索引表(一级索引)中。 索引结构的示例–I节点 基本思想:给每个文件赋予一张称为I节点的小表,在这张小表中列出了文件属性及文件中个块在磁盘上的地址。 文件数据盘快,称为直接盘快。 该索引指向文件数据盘快,称为一重间接盘快。 二级索引表,称为二重间接盘快。 三级索引表,称为三重间接盘快。 文件的存储介质 存储介质的特点 外存储设备同内存相比较,一般有容量大、断电后仍可保存信息、速度快慢、成本较低等特点。 外存储设备通常由驱动部分和存储介质两部分组成。存储介质又常称为卷。 驱动器的作用是是计算机能够实现读写(及保存、控制、测试)存储介质上的内容。 存储设备有很多种类。如磁盘、磁带、磁鼓、纸带、光盘和内存等。一个计算机系统中可同时连接说中存储设备。 磁盘空间由盘面、柱面、磁道和扇区组成。 外存设备存取的过程大致由:读状态-》置数据-》置地址-》置控制-》读状态。 用户对外存储设备的要求 用户对外存设备的要求是:方便、效率、安全。 在读写外存储设备时不涉及硬件细节,用户直接使用逻辑地址和逻辑操作。 外存储设备存取速度尽可能快,容量大切空间利用率高。 外存储设备上存放的信息安全可靠,防止来自硬件的故障和他人的侵权。 可以方便的共享,存储空间可以动态扩大、缩小,携带,拆卸便捷,可随时了解存储设备及使用情况。 以尽可能小的代价完成上述要求。 文件在存储设备中的存取 顺序存储设备 磁带就是典型的顺序存储介质。 优点:存储容量大; 缺点:存取速度比较慢。 随机存取设备 磁盘是典型的随机存储设备。 磁盘一般由若干个磁盘片组成,每个磁盘片对应两个读写磁头,分别对磁盘片的上下两面进行读写。各个磁头与磁头臂之间相连。系统在对磁盘初始化时,将盘片上划分出一些同心圆,作为存储信息的介质,称为磁道。对每个磁道又分为若干段,称为扇区。每个扇区就构成了一个物理快,整个磁盘上所有扇区(物理块)统一编号,从零开始,所有磁盘片的相同磁道称为柱面。 磁盘上每个物理快的位置可用柱面号、磁头号、扇区号。 已知物理号,则磁盘地址: 柱面号 = [ 物理块号/(磁头数 X 扇区数) ] 磁头号 = [(物理块号 mod (磁头数 X 扇区数)) / 扇区数] 扇区号 = (物理块号 mod (磁头数 X 扇区数)) mod 扇区数 已知磁盘地址: 物理块号 = 柱面号 X(磁头数 X 扇区数)+ 磁头号 X 扇区数 + 扇区号。 磁头臂只能沿半径方向移动。在访问磁盘时,首先要把磁头臂移动到相应柱面的磁道上,称为寻道。然后等待盘片旋转,使指定的扇区转到磁头之下,实现了对磁道和扇区的定位。最后控制磁头对扇区中的数据进行读写。 一次访问磁盘的时间由寻道时间、旋转定位时间和数据传输时间所组成,寻道时间是机械动作的时间,因而需要花费的时间最长。 文件的存储方式 在用户面前,文件的呈现方式是文件的物理结构,在存储介质面前,文件呈现的是文件的物理结构,这与文件所使用的存储介质的特性有关。 哪一种文件的存取方式,取决于用户使用文件的方式,也与文件所使用的存储介质有关。数据库文件,就适合采用随机存储的方法。而如果存储介质采用的是磁带,就只能采用顺序存储。 顺序存储 顺序存储就是从前往后的依次访问文件的各个信息项。 若当前读取的记录为R,则下一次读取的记录被自动的确定为Ri+1. 随机存储 随机存取又称为直接存取,即允许用户按任意的次序直接存取文件中的任意一个记录,或者根据存储命令把读写指针移到文件的指定记录处读写。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/1/26
articleCard.readMore

操作系统 文件管理 概述

计算机的主要功能之一就是对数据进行数值或非数值计算。系统软件必须提供数据存储、数据处理、数据管理的基本功能。数据管理是通过文件管理的方式来完成的,而目录又是建立在分区或卷的基础之上的。操作系统中文件和目录相关的子系统称之为文件系统。 计算机程序都要存储信息、检索信息。 能够存储大量的信息。 长期保存信息。 可以共享信息。 管理的内容:文件的结构、命名、存取、使用、保护和实现方法。 透明存取:指的是不必要了解文件存放的物理机制和查找方法,只需要给定一个代表某段程序或数据的文件名称,文件系统就会自动的完成对与给定文件名称相对应的文件的有关操作。 文件和文件系统 文件的定义 文件:一组带标识的、逻辑上有完整意义的信息项的序列。 标识:文件名。 信息项:文件内容的基本单位。一组有序序列。 读写指针:用来记录文件当前的读取位置,它向下一个将要读取的信息项。 写指针:用来记录文件当前的写入位置,下一个将要写入的信息项被写到该处。 文件的长度:是单字节或多字节,这些字节可以是字符,也可以组成记录。 UFS:可达255个字符。 FAT12(MS-DOS所使用的文件系统)命名规则规定文件名为8个字符。 NTFS:达到255个字符。 EXT2:chap5, htm, Chap5等文件名称。 文件系统 文件系统:操作系统中统一管理信息资源的一种软件。 从用户的角度来看,文件系统负责为用户建立文件、读写文件、修改文件、复制文件和撤销文件。文件系统还负责完成对文件的按名存取和对文件进行存取控制。 文件分类 按文件的用途分类 系统文件 操作系统和各种系统应用程序和数据所组成的文件。 不允许对该类文件进行读写或修改。 库函数文件 标准子程序及常用应用程序组成的文件。允许用户对其进行读取、执行,但不允许对其进行修改。C语言子程序库。 用户文件 用户文件是用户委托文件系统保护的文件。可以由源程序、目标程序、用户数据文件、用户数据库等组成。 按文件的组织形式分类 普通文件 指文件的组织格式为文件系统中所规定的最一般格式的文件。普通文件即包括用户文件、库函数文件和用户实用程序文件等。 目录文件 有文件的目录构成的特殊文件。含有文件目录信息的一种特定文件。主要用来检索文件的目录信息。 特殊文件 把特殊文件的操作转成为对应设备的操作。 一些常见的文件分类方式 按文件的保护方式可划分为:只读文件、读写文件、可执行文件、无保护文件等 按信息的流向分类可划分为:输入文件、输出文件和输入输出文件 按文件的存放时限可划分为:临时文件、永久文件和档案文件 按文件所使用的介质类型可划分为:磁盘文件、磁带文件、卡片文件和打印文件 按文件的组织结构分类:顺序文件、链接文件和索引文件。 UNIX类操作系统中文件的分类 普通文件 目录文件 特殊文件 文件系统的功能 统一管理文件的存储空间,实施存储空间的分配和回收。 实现文件从名字空间到外存地址空间的映射。 实现文件信息的共享,并提出文件的保护和保密措施。 向用户提供一个方便使用的接口。 系统维护及向用户提供有关信息。 保持文件系统的执行效率。 提供与I/O的统一接口。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/1/6
articleCard.readMore

操作系统 内存管理 虚拟存储技术与虚拟页式存储管理方案的实现

虚拟存储技术 基本思想:利用大容量的外存来扩充内存,产生一个比有限的实际内存空间大得多的、逻辑的虚拟内存空间,简称虚存。 操作系统把程序当前使用的部分保留在内存,而把其他部分保存在磁盘上,并在需要时在内存与磁盘之间动态交换。支持多道程序设计技术。 实现虚拟存储器需要以下的硬件支持: 系统有容量足够大的内存。 系统有一定容量的内存。 最主要的是:硬件提供实现虚-实地址映射的机制。 工作原理:当进程开始运行时,现将一部分程序转入内存,另一部分暂时留在外存但要执行的指令不在内存时,系统自动选择部分内存空间将其中原有的内容交换到磁盘扇区,并释放这些内存空间供其他进程使用。 交换技术是以进程为单位进行的,进程所需内存大于当前西戎内存,那么该进程就不能在系统中运行。 虚拟内存一般是以页或段为单位,所以如果一个进程所需内存大于当前系统内存,那么该进程仍然可以在系统中正常运行,因为该进程的一部分可以被还出到外存上。 虚拟页式存储管理 基本思想 在进程开始运行之前,不是装如全部页面。而是转入一个或零个页面,之后根据进程运行的需要,动态转入其他页面,当内存空间已满,而又需要装入新的页面时,则根据某种算法置换出某个页面,易变装入新的页面。 在使用虚拟页式存储管理时需要在页表中增加以下表项: 页号—页面的编号。 有效位—又称驻留位、存在位或中断位,表示该页是在内存还是在外存。 页框号—页面在内存中时所对应的内存块号。 访问位—又称引用位或参考位,表示该页在内存期间是否被访问过。 修改位—表示该页在内存中是否被修改过。 保护位—是否能读写执行。 禁止缓存位—采用内存映射I/O的机器中需要的位。 缺页中断 页面调度策略 虚拟存储器系统通常定义三种策略来规定如如何(或何时)进行页面调度:调入策略、置页策略和置换策略。 调入策略 什么时候将一个页由外存调入内存中。 请求调页:只调入发生缺页时所需的页面。这种调入策略实现简单,但容易产生较多的缺页中断,造成对外存I/O次数过多,时间开销过大,容易产生抖动现象。 预调页:在发生缺页需要调入某页时,一次调入该页以及相邻的几个页。提高了调页I/O效率,减少I/O次数。 置页策略 当线程产生缺页中断,内存管理器还必须确定将调入的虚拟页放在物理内存的何处。用于确定最佳位置的一组规则。 置换策略 如果缺页中断发生时物理内存已经满,“置换策略”被用于确定那个虚拟页面必须从内存中移出,为新的页面腾出空位。 固定分配局部置换:可基于进程的类型,为每一进程分配固定的页数的空间,在整个运行期间都不会再改变。采用该策略时,如果进程在运行中出现缺页,则只能从该进程的N个页面中选出一个换出,然后再调入一页,以保证分配给该进程的内存空间不变。 可变分配全局置换:先为系统中的每一进程分配一定数量的物理快,操作协同本身也保持一个空闲物理快队列,当某进程发生缺页是,由系统的空闲物理快队列中取出一物理快分配给该进程。但当空闲物理快队列中的物理快用完时,操作系统才从内存中选这一块调出。该块可能是系统中任意一个进程的页。 可变分配局部变量:基于进程的类型,为每个进程分配一定的数目的内存空间。但当某进程发生缺页时,只允许从该进程的页面中选出一页换出,这样就不影响其他进程的运行。 页面置换算法 如果刚被调出的页面又要立即要用,因而又要把它装入,而装入不久又被选中调出,调出不久又被装入,如此反复,使调度非常频繁,这种现象称之为“抖动”或称“颠簸”。 先进先出页面置换算法FIFO 选择最先装入内存的一页调出,或者说是把驻留在内存中时间最长的一页调出。 把转入内存的哪些页面的页号按进入的先后次序排好队,每次总是调用队首的页,当装入一个新页面后,把新页面的页号排入队尾。 把操作系统维护一个所有当前在内存中的页面的链表,最老的页面在表头,最新的页面在表尾。当发生缺页时,置换表头的页面并把新调入的页面加到表尾。 最近最少使用页面置换算法LRU 在缺页发生时,首先置换掉最长时间未被使用过的页面。 总是选择距离现在最长时间内没有被访问过的页面先调出。实现这种算法的一种方法是在页表中为每个页增加一个“计时”标志,记录该页面自上次被访问以来的所经历的时间,每个访问一次都应从“0”开始计时。 计时值最大的那一页调出(即最近一段时间里最长时间没有被使用过的页),此开销比较大。 最近最不常用页面置换算法LFU 根据在一段时间里页面被使用的次数选择可以调出的页,如果一个页面被访问的次数比较多,则是最常使用的页面,就不应该把它调出。 每一页设置一个计数器,每当访问一页时就把该页对应的计数器加1,另外,操作系统还要确定一个周期T,在周期T的一段时间内,若没有发生缺页中断,则把所有的计数器清“0”,开始一个新的周期从新计数。若在周期T的时间内发生了缺页中断,则选择计数值最小的那页调出。 实现要花很大的开销,并且要确定一个合适的周期T也有一定的难度。 理想页面置换算法OPT 该算法置换以后不再需要的或者在最长时间以后才会用到的页面。 作为衡量其他页面置换算法优势的一个标准。 最近未使用页面置换算法NRU 当访问页面(读或写)时设置R位,当写入页面(即修改页面)时设置M位。 用R位和M位可以构造一个简单的页面置换算法:当启动一个进程时,它的所有页面的两个位都是由操作系统设置为0,定期将R位(比如在每次时钟中断时)清零,以区别最近没有被访问的页面和被访问的页面。 NRU算法随机地从类编号最小的非空类中挑选一个页面淘汰。在最近一个时钟滴答中(典型的时间为20ms)置换一个没有被访问的已经修改的页面比要置换一个被频繁使用的“干净”页面好。 优点:易于理解和能够有效地被实现。但性能并不是最好的。 第二次机会页面置换的算法 FIFO算法可能会把经常使用的页面置换出去。检查进入内存时间最久的页面的R位,如果是0,那么这个页面即老有没有被使用过,可以立刻置换掉;如果是1,就将R位清0,并把该页放到链表的尾部,修改其进入时间,然后继续搜索。 基本思想:寻找一个从来没有访问过的页面,如果所有的页面都被访问过了,该页面就退化为FIFO算法。 时钟页面置换算法Clock 把所有的页面都保存在一个类似时钟面的环形链表中,一个表针指向最老的页面。当发生缺页中断时,算法首先检查表针指向的页面,如果他的R位为0,就置换这个页面,并把新的页面插入到这个位置,然后把表针前移一个位置,如果是R位是1,就清楚R位并把表指针前移一个位置,重复这个过程直到找到一个R位为0的页面为止。 缺页中断率 假设一个程序共有n页,系统分配给它的内存块是m块(m、n均为正整数,且1<=m<=n)。该程序最多有m页可以同时装入内存。如果程序执行中访问页面的总次数为A,其中有F次访问的页面尚未装入内存,故产生了F次缺页中断。 f = F / A 把f称为“缺页中断率”。 缺页中断率与缺页中断的次数有关。 分配给程序的内存块数 分配给程序的内存块数多,这同时装入内存的页面数就越多,故减少了缺页中断的次数,也就降低了缺页中断率,反之,缺页中断率就高。 页面的大小 页面的大小取决于内存分块的大小,快大页面也大,每个页面大了则程序的页面数就少。装入程序时是按页面存放在内存中的,因此,装入一页的信息量就越大,就减少了缺页中断的次数,降低了缺页中断率。反之,若页面小则缺页中断率就越高。 程序编制方法 缺页中断率与程序的局部化程度密切相关。 页面置换算法 页面置换算法对缺页中断率的影响很大,调度不好就会出现“抖动”,理想的调度算法(OPT)能使缺页中断率最低。 虚拟存储管理的性能问题 在虚拟内存中,页面可能在内存与外存之间频繁调度,有可能出现抖动或颠簸。 颠簸是由于缺页率高引起的。 一般进程在一段时间内集中访问一些页面,称为“活动页面”,如果分配给一个进程的内存物理页面数太少,使得该进程所需要的“活动页面”不能全部装入内存,则进程在运行过程中会频繁的发生缺页中断,从而产生颠簸。 段式与段页式存储管理方案 段式与段页式存储管理方案 设计思想 系统将内存空间动态划分为为若干个长度不同的区域,每个区域乘坐一个物理段。每个物理段在内存中有一个起始地址,乘坐段首址。将物理段中的所有单元从0开始依次编址,称为段内地址。 用户程序则按逻辑上有完整意义的段来划分,称为逻辑段(简称段),将用户程序的所有逻辑段从0开始编号,称为段号。将一个逻辑段中的所有单元从0开始编址,称为段内地址。用户程序的逻辑地址由段号和段内两部分组成。 内存分配时,系统以段为单位进行内存分配,为每个逻辑段分配一个连续的内存区(物理段)。逻辑上连续的段在内存中不一定连续存放。 段表包括逻辑段号、物理段起始地址(段首址)和物理长度三项内容。 按逻辑段的顺序排列,放在内存中。 地址转换 与页式存储管理相同,为了实现段式管理,系统提供一对寄存器:段表起始地址和段表长度寄存器。 段表起始地址寄存器用于保存正在运行程序的段表在内存的首地址。当进程被调度程序选中并投入使用时,系统将其段表首地址从进程控制块中取出送入该寄存器。 段表长度寄存器用于保护正在运行进程的段表的长度。当进程被选中时,系统将他从进程控制块中取出送入该寄存器。 与可变分区管理方案的比较 相同:有相同结构的内存分配表,包括已分配区表和空闲区表。 不同:段式存储管理是为程序的每一个分段分配一个连续的内存空间。 段页式存储管理方案 为用户提供了一个二维地址空间,满足程序和信息的逻辑分段的要求。段式管理反映了程序的逻辑结构,有利于段的动态增长以及共享和内存保护,大大方便了用户。 特征:等分内存,有效的克服了碎片,提高了存储器的利用率。 基本思想:用页式存储方法来分配和管理内存空间,即把内存划分为若干大小的相等的页面;用段式方法对用户程序按照其内在的逻辑关系划分成若干个大小相等的页面。内存是以页为基本单位的分配给每个用户程序的,逻辑上相邻的页面在物理内存中不一定相邻。 需要增加段式管理和页式管理的成分:必须为每个程序建立一张段表;由于一个段又被分为了若干也,系统有为每个段建立一张表页。段表中记录了该段对应页表的起始地址和长度,而页表则给出该段逻辑页面与内存块号之间的对应关系。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/1/2
articleCard.readMore

操作系统 内存管理 页式存储管理方案

把一个逻辑地址连续的程序分散存放到几个不连续的内存区域中,并且保证程序的正确执行,即可充分利用内存空间,又可减少移动所花费的开销。 基本思想 该技术已广泛用于微机系统中,支持页式存储管理的硬件部件通常称为“存储管理部件”。 存储管理部件首先把内存分为大小相等的许多区把每个区称为“块”,块是进行主存空间分配的物理单位。要求程序中的逻辑地址也进行分页,页的大小与块的大小一致。 假定地址用m个二进制表示,其中页内地址部分占用n个二进制位,那么,每一块的长度就是2的n次方,也就是每一页有2的n次方个字节。页号部分占用了m-n位,所以,最大的程序可允许有2的(m-n)次方个页面。逻辑地址从“0”,页内地址也为“0”,当编制到2的n次方-1时,第0页的页内地址的各位均为“1”,即占满了一个页面。下一个地址是2的n次方,这时页号部分就为“1”,而页内地址部分又恢复到了“0”,表示进入了第1页。再继续顺序编址,此时页内地址0~(2のn次方-1)是属于第1页。一组顺序的逻辑地址结构将其自然地分页。 存储空间的分配与回收 那些块已经分配。 那些块尚未进行分配。 当前剩余的空闲块数。 假设内存的可分配区域被分为256块,则可用字长为32位的8个字作为“位示图”,位于图中的每一位与一个内存块对应,每一一个的值可以是0或1,0表示对应的内存块为空闲,1表示已经占用。 找出一些为0位,把这些位置成1,并从空闲块数中减去本次分配的块数,然后按照找的的位计算出相对应的块号。 块号 = 字号 X 字长 +位号 根据归还的块号计算出该块在位示图中对应的位置,将占用标志修改成0,再把回收的块数相加到空闲块数中。 地址转换与块表 为每一个被装入内存的进程提供一张页表,该页表所在内存的起始地址和长度作为现场信息存放在该进程的PCB中。 页式存储管理的地址转换 当进程被调度程序选中投入运行时,系统将其页表手地址从进程控制块中取出送入该寄存器,页表长度寄存器用于保存正在运行进程的页表的长度。 页表指出该程序逻辑地址中的页号与所占用的内存块号之间的对立关系。页表长度由程序拥有的页面数而定,故每个程序的页表长度可能是不同的。 若页表中有次页号,则可得到对应的内存块号, 物理地址 = 内存块号 x 块长 + 页内地址 页表 多级页表 假设用户地址空间为2GB,页面大小为4KB,则一个进程最多有2的19次方页。 存放页表的页面为页表页。 在大多数操作系统中采用二级页表,有页表页和页目录一起构成进程页表。 第一级表示页目录,保存页表页的地址,第二级表示页表页,保存物理页面号(即内存块号)。 散列页表 当地址空间大于32位时,一种常见的方法是使用以页号为散列值的散列页表。 虚拟页号 所映射的页框号。 指向链表中下一个元素的指针。 反置页表 每个进程都有与之相关的页表。 每个物理页框对应一个表现,每个表项包含与该页框相对应的虚拟页面地址以及拥有该页面进程的信息。 块表 页面存储管理中的页表是存放在内存中的。当要按给定的逻辑地址进行读写时,必须访问内存两次。 第一次按页号读出页表中对应的块号。 第二次按计算出来的绝对地址进行读写。 两次访问内存显然延长了指令的执行周期,降低了执行速度。 在地址映射机制中增加一组高速寄存器保存页表,这需要大量的硬件开销,在经济上不可行。 在地址映射机制中增加一个小容量的联想寄存器(相联寄存器),他又Cache组成。 利用高速缓冲存储器存放当前访问次数最少活动页面的页号,这个高速缓冲器被称为“快表”,也称为转换检测缓冲器。TLB 快表中登记了页表中的一部分页号与内存块号的对应关系。 快表只存放当前进程中最活跃的少数几页,随着进程的推进,快表的内容动态更新。 更新原理:查找快表和查找内存页表,而直接利用快表中的逻辑页号。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2018/1/1
articleCard.readMore

数据结构 平衡二叉树AVL树

平衡二叉树介绍 平衡二叉树,又称AVL树,实际上就是遵循一下两个特点的二叉树: 每一子树中的左子树和右子树的深度都不超过1; 二叉树的每一个子树都要求是平衡二叉树。 只要是每个子树都满足左子树还有右子树的深度都不超过1即可。 平衡因子 每一个结点都有各自的平衡因子,表示的就是左子树深度同右子树深度的差。 平衡二叉树中的各结点平衡因子取值只可能是:0、1、-1. 其中 (a) 的两棵二叉树中由于各个结点的平衡因子数的绝对值都不超过 1,所以 (a) 中两棵二叉树都是平衡二叉树;而 (b) 的两棵二叉树中有结点的平衡因子数的绝对值超过 1,所以都不是平衡二叉树。 二叉排序树转化为平衡二叉树 为了排除动态查找表中不同的数据排列方式对算法性能的影响,需要考虑在不会破坏二叉排序树本身结构的前提下,将二叉排序树转化为平衡二叉树。 例如,使用上一节的算法在对查找表{13,24,37,90,53}构建二叉排序树时,当插入 13 和 24 时,二叉排序树此时还是平衡二叉树: 当继续插入 37 时,生成的二叉排序树如图 (a),平衡二叉树的结构被破坏,此时只需要对二叉排序树做“旋转”操作(如图(b)),即整棵树以结点 24 为根结点,二叉排序树的结构没有破坏,同时将该树转化为了平衡二叉树: 当二叉排序树的平衡性被打破时,就如同扁担的两头出现了一头重一头轻的现象,如图(a)所示,此时只需要改变扁担的支撑点(树的树根),就能使其重新归为平衡。实际上(b) 是对(a) 的二叉树做了一个向左逆时针旋转的操作。 继续插入 90 和 53 后,二叉排序树如图 (a)所示,导致二叉树中结点 24 和 37 的平衡因子的绝对值大于 1 ,整棵树的平衡被打破。此时,需要做两步操作: 如图 (b) 所示,将结点 53 和 90 整体向右顺时针旋转,使本该以 90 为根结点的子树改为以结点 53 为根结点; 如图 (c) 所示,将以结点 37 为根结点的子树向左逆时针旋转,使本该以 37 为根结点的子树,改为以结点 53 为根结点; 做完以上操作,即完成了由不平衡的二叉排序树转变为平衡二叉树。 特殊的树转为平衡树的情况 当平衡二叉树由于新增数据元素导致整棵树的平衡遭到破坏时,就需要根据实际情况做出适当的调整,假设距离插入结点最近的“不平衡因子”为 a。则调整的规律可归纳为以下 4 种情况: 单向右旋平衡处理:若由于结点 a 的左子树为根结点的左子树上插入结点,导致结点 a 的平衡因子由 1 增至 2,致使以 a 为根结点的子树失去平衡,则只需进行一次向右的顺时针旋转,如下图这种情况: 单向左旋平衡处理 :如果由于结点 a 的右子树为根结点的右子树上插入结点,导致结点 a 的平衡因子由 -1变为 -2,则以 a 为根结点的子树需要进行一次向左的逆时针旋转,如下图这种情况: 双向旋转(先左后右)平衡处理 :如果由于结点 a 的左子树为根结点的右子树上插入结点,导致结点 a 平衡因子由 1 增至 2,致使以 a 为根结点的子树失去平衡,则需要进行两次旋转操作,如下图这种情况: 注意:图中插入结点也可以为结点 C 的右孩子,则(b)中插入结点的位置还是结点 C 右孩子,(c)中插入结点的位置为结点 A 的左孩子。 双向旋转(先右后左)平衡处理:如果由于结点 a 的右子树为根结点的左子树上插入结点,导致结点 a 平衡因子由 -1 变为 -2,致使以 a 为根结点的子树失去平衡,则需要进行两次旋转(先右旋后左旋)操作,如下图这种情况: 注意:图中插入结点也可以为结点 C 的右孩子,则(b)中插入结点的位置改为结点 B 的左孩子,(c)中插入结点的位置为结点 B 的左孩子。 注 :在对查找表{13,24,37,90,53}构建平衡二叉树时,由于符合第 4 条的规律,所以进行先右旋后左旋的处理,最终由不平衡的二叉排序树转变为平衡二叉树。 时间复杂度 使用平衡二叉树进行查找操作的时间复杂度为O(logn)。 构建平衡二叉树代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 #include <stdio.h> #include <stdlib.h> //分别定义平衡因子数 #define LH +1 #define EH 0 #define RH -1 typedef int ElemType; typedef enum {false,true} bool; //定义二叉排序树 typedef struct BSTNode{ ElemType data; int bf;//balance flag struct BSTNode *lchild,*rchild; }*BSTree,BSTNode; //对以 p 为根结点的二叉树做右旋处理,令 p 指针指向新的树根结点 void R_Rotate(BSTree* p) { //借助文章中的图所示加以理解,其中结点 A 为 p 指针指向的根结点 BSTree lc = (*p)->lchild; (*p)->lchild = lc->rchild; lc->rchild = *p; *p = lc; } ////对以 p 为根结点的二叉树做左旋处理,令 p 指针指向新的树根结点 void L_Rotate(BSTree* p) { //借助文章中的图 6 所示加以理解,其中结点 A 为 p 指针指向的根结点 BSTree rc = (*p)->rchild; (*p)->rchild = rc->lchild; rc->lchild = *p; *p = rc; } //对以指针 T 所指向结点为根结点的二叉树作左子树的平衡处理,令指针 T 指向新的根结点 void LeftBalance(BSTree* T) { BSTree lc,rd; lc = (*T)->lchild; //查看以 T 的左子树为根结点的子树,失去平衡的原因,如果 bf 值为 1 ,则说明添加在左子树为根结点的左子树中,需要对其进行右旋处理;反之,如果 bf 值为 -1,说明添加在以左子树为根结点的右子树中,需要进行双向先左旋后右旋的处理 switch (lc->bf) { case LH: (*T)->bf = lc->bf = EH; R_Rotate(T); break; case RH: rd = lc->rchild; switch(rd->bf) { case LH: (*T)->bf = RH; lc->bf = EH; break; case EH: (*T)->bf = lc->bf = EH; break; case RH: (*T)->bf = EH; lc->bf = LH; break; } rd->bf = EH; L_Rotate(&(*T)->lchild); R_Rotate(T); break; } } //右子树的平衡处理同左子树的平衡处理完全类似 void RightBalance(BSTree* T) { BSTree lc,rd; lc= (*T)->rchild; switch (lc->bf) { case RH: (*T)->bf = lc->bf = EH; L_Rotate(T); break; case LH: rd = lc->lchild; switch(rd->bf) { case LH: (*T)->bf = EH; lc->bf = RH; break; case EH: (*T)->bf = lc->bf = EH; break; case RH: (*T)->bf = EH; lc->bf = LH; break; } rd->bf = EH; R_Rotate(&(*T)->rchild); L_Rotate(T); break; } } int InsertAVL(BSTree* T,ElemType e,bool* taller) { //如果本身为空树,则直接添加 e 为根结点 if ((*T)==NULL) { (*T)=(BSTree)malloc(sizeof(BSTNode)); (*T)->bf = EH; (*T)->data = e; (*T)->lchild = NULL; (*T)->rchild = NULL; *taller=true; } //如果二叉排序树中已经存在 e ,则不做任何处理 else if (e == (*T)->data) { *taller = false; return 0; } //如果 e 小于结点 T 的数据域,则插入到 T 的左子树中 else if (e < (*T)->data) { //如果插入过程,不会影响树本身的平衡,则直接结束 if(!InsertAVL(&(*T)->lchild,e,taller)) return 0; //判断插入过程是否会导致整棵树的深度 +1 if(*taller) { //判断根结点 T 的平衡因子是多少,由于是在其左子树添加新结点的过程中导致失去平衡,所以当 T 结点的平衡因子本身为 1 时,需要进行左子树的平衡处理,否则更新树中各结点的平衡因子数 switch ((*T)->bf) { case LH: LeftBalance(T); *taller = false; break; case EH: (*T)->bf = LH; *taller = true; break; case RH: (*T)->bf = EH; *taller = false; break; } } } //同样,当 e>T->data 时,需要插入到以 T 为根结点的树的右子树中,同样需要做和以上同样的操作 else { if(!InsertAVL(&(*T)->rchild,e,taller)) return 0; if (*taller) { switch ((*T)->bf) { case LH: (*T)->bf = EH; *taller = false; break; case EH: (*T)->bf = RH; *taller = true; break; case RH: RightBalance(T); *taller = false; break; } } } return 1; } //判断现有平衡二叉树中是否已经具有数据域为 e 的结点 bool FindNode(BSTree root,ElemType e,BSTree* pos) { BSTree pt = root; (*pos) = NULL; while(pt) { if (pt->data == e) { //找到节点,pos指向该节点并返回true (*pos) = pt; return true; } else if (pt->data>e) { pt = pt->lchild; } else pt = pt->rchild; } return false; } //中序遍历平衡二叉树 void InorderTra(BSTree root) { if(root->lchild) InorderTra(root->lchild); printf("%d ",root->data); if(root->rchild) InorderTra(root->rchild); } int main() { int i,nArr[] = {1,23,45,34,98,9,4,35,23}; BSTree root=NULL,pos; bool taller; //用 nArr查找表构建平衡二叉树(不断插入数据的过程) for (i=0;i<9;i++) { InsertAVL(&root,nArr[i],&taller); } //中序遍历输出 InorderTra(root); //判断平衡二叉树中是否含有数据域为 103 的数据 if(FindNode(root,103,&pos)) printf("\n%d\n",pos->data); else printf("\nNot find this Node\n"); return 0; } 本博文从 平衡二叉树(AVL树)及C语言实现 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/28
articleCard.readMore

操作系统 内存管理 覆盖与交换技术

覆盖技术 覆盖技术是指一个程序的若干程序段和几个程序的某些部分共享一个存储空间。覆盖技术的实现是把程序分为若干个功能上相对独立的程序,按照其自身的逻辑结构使那些不会同时执行的程序段共享同一块内存区域。未执行的程序段先保存在磁盘上,当有关程序段的前一部分执行结束后,把后续程序段调入内存,覆盖前面的程序段。 覆盖技术是用户程序自己附加的控制。要把一个程序划分成不同的程序段,并规定好他们的执行和覆盖顺序。操作系统则根据程序员提供的覆盖结构,完成程序段之间的覆盖。 该程序正文段所需要的内存空间是A(20KB)+B(50KB)+F(30KB)+C(30KB)+D(20KB)+E(40KB)=190KB,但是在采用了覆盖技术后只需要A(20KB)+B(50KB)+E(40KB)=110KB占用空间。 覆盖技术主要用于系统程序的内存管理上,MS-DOS系统分为两个部分。 操作系统中经常要用到的基本部分,它们常驻在内存且占用固定区域。 不太经常使用的部分,它们存放在磁盘上,当调用它们时才被调入内存覆盖区。 交换技术 交换技术:在分时系统中,用户的进程比内存能容纳的数量要多,这就需要在磁盘上保存那些内存放不下的进程。在需要运行这些进程时,再将它们装入内存。 进程从内存移到磁盘并再移动回内存称为交换。交换技术是进程在内存与外存之间的动态调度,是由操作系统控制的。 后备存储区(又称盘交换区)。 目的:尽可能达到”足够快的交换进程,以使当CPU调度程序想重新调度CPU时,总有进程在内存中处于就绪(准备执行)状态“的理想状态,从而提高内存利用率。 交换技术的原理: (1)换出进程的选择:系统需要将内存中的进程换出时,应该选择那个进程? 根据时间片轮转法或基于优先数的调度算法来选择要换出的进程。 (2)交换时间的确定 在内存空间不够或有不够的危险时,还出内存中的部分进程到外存,以释放所需要的内存。 (3)交换空间的分配 在一些系统中,当进程在内存中时,不再外塔分配磁盘空间。当它被换出时,必须为它分配磁盘交换空间。 在另一些系统中,进程一但创建,就分配给它磁盘上的交换空间。无论何时程序被换出,他都被换到已经为它分配的空间,而不是每次换到不同的空间。 (4)换入进程换回内存时位置的确定 绝对地址:在原来的位置上; 相对地址:可再进行地址重定位。 交换技术的缺点: 由于交换时需要花费大量的CPU时间,这将影响对用户的响应时间,因此,减少交换的信息量是交换技术的关键问题。 合理的做法: 在外存中保留每个程序的交换副本,换出时仅将执行时修改过的部分复制到外存。 覆盖技术和交换技术的发展导致了虚拟存储技术的出现。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/27
articleCard.readMore

操作系统 内存管理 内存存储管理方案

基本思想:是把内存划分成若干个连续的区域,称为分区,每个分区装入一个运行程序。 固定分区 基本思想 固定分区是指系统先把内存划分为若干个大小固定的分区,一旦分配好,在系统运行期间便不再重新划分。程序运行时必须提供对内存资源的最大申请量。 内存分配表与分区的分配、回收 用于固定分区管理的内存分配表是一张分区说明表,按顺序每个分区说明表中对应一个表目。表目内容包括分区序号、分区大小、分区起始地址以及使用状态(空闲或占用)。一个程序在运行时,想要根据其对内存的需求量,按一定的分配策略在分区说明表中查找空闲分区。若找到合乎需要的分区,就将该分区分配给程序,并将该分区置为占用状态。当程序完成时释放这块分区内存,由系统回收,并在分区说明表中间回收的分区重新置为空闲状态。 固定分区方案灵活性差,可接纳程序的大小受到了分区大小的严格限制。 可变分区 基本思想 可变分区是指系统不预先划分固定分区,而是在装入程序时划分内存分区,使为程序分配的分区的大小正好等于该程序的需求量,且分区的个数是可变的。可变分区有较大的灵活性,较之固定分区能更好的内存利用率。 系统初次启动后,在内存中出操作系统区之外,其余空间为一个完整的大空闲区,当有程序要求装入内存运行时,系统从该空闲区中划分出一块与程序大小相同的区域进行分配。当系统运行一段时间后,随一系列的内存分配与回收,原来的一整块大空闲区形成了若干占用区和空闲区相间的布局,若有上下相邻的两块空闲区,系统应将他们合并成为一块连续的大空闲区。 移动技术 内存经过一段时间的分配回收之后们会存在很多晓得空闲块。它们每一块都不足以满足程序进一步分配内存的要求,但其总和却可以满足程序的分配要求,这些空闲块称之为碎片。 解决碎片的办法:在适当时刻进行碎片整理,通过移动内存中的程序,把所有空闲碎片合成一个连续的大空闲区且放在内存的一端,而把所有程序占用区放在内存的另一端,称为“移动技术”或“紧凑技术”或“紧缩技术”。 提高内存的利用率,便于作业动态扩充内存。采用移动技术需要注意以下问题: 移动技术会增加系统的开销。增大了系统运行时间。 移动是由条件的,不是任何在内存中的作业都能随时移动。 采用移动技术是应该尽可能减少需要移动的作业数和信息量。 可变分区的实现 采用可变分区方式管理时,要有硬件的地址转换机构作为支持。硬件设置两个专用的控制寄存器:基址寄存器和限长寄存器。 基址寄存器用来存放程序所占用分区的起始地址。 限长寄存器用来存放程序所占分区的长度。 但程序被装到所分配的分区后,把分区的起始地址和长度作为现场信息存入该作业进程的进程控制块中。 为了实现可变分区的管理,必须设置某种数据结构用以记录内存分配的情况,确定某种分配策略并且实施内存的分配与回收。 内存分配表由两张表格组成: 已分配区表:记录已装入的程序在内存中占用分区的起始地址和长度,用标志位指出占用分区的程序名。 空闲区表:记录内存中可供分配的空闲区的起始地址和长度,用标志位指出该分区是未分配的空闲区。 空闲分区的分配策略 最先适应算法 最先适应算法,又称顺序分配算法,当接到内存申请是,顺序查找分区说明表,找到第一个满足申请长度的空闲区,将其分割并分配,可以快速做出分配决定。 最优适应算法 当接到内存申请时,查找分区说明表,找到第一个能满足申请长度的最小空闲区,将其分割并分配。 优点:最节约空间,因为它尽量不分割大的空闲区。 缺点:可能会形成很多很小的空闲区域,称为碎片。 最坏适应算法 当接到内存申请时,查找分区说明表,找到能满足申请要求的最大的空闲区。 基本思想:在大空闲区中装入信息后,分割剩下的空闲区相对也很大,还能用于装入其他程序。 优点:是可以避免形成碎片。 缺点:分割了大的空闲区后,如果在遇到较大的程序申请内存时,无法满足要求的可能性越大。 下次适应算法 当接到内存申请时,查找分区说明表,从上一次分配的位置开始扫描内存,选择下一个大小足够的可用块。 分区的回收 当用户程序执行接受后,系统要回收已经使用完毕的分区,将其记录在空闲区表中。在回收空间时,应首先检查是否有与回收区相邻的空闲区,即检查相邻的空闲区表中标志为“未分配”的栏目,以确定是否有相邻空闲区,若有,则应合并成一个空闲区登记。 假定作业归还的分区起始地址为S,长度为L。 (1)回收区的上邻分区是空闲的,需要将两个空闲区合并成一个更大的空闲区,然后修改空闲区表。 如果空闲区表中第i个登记栏中的“起始地址+长度”正好等于S,则说明回收区有一个上邻空闲区。 长度 = 原长度 + L 2)回收分区的下邻分区是空闲的,需要将两个空闲区合并成一个更大的空闲区,然后修改空闲区表。 如果S+L正好等于空闲区表中某个登记的栏目(假定为第i栏)所示分区的起始地址表明回收区有一个下邻空闲区。 起始地址 = S 长度 = 原长度 + L 第i栏指示的空闲区是回收区与下邻空闲区合并之后的一个大空闲区。 (3)回收区的上邻分区和下邻分区都是空闲的,需要将三个空闲区合并成一个更大的空闲区,然后修改空闲区表。 S = 第i栏起始地址 + 长度 S + L = 第k栏起始地址 表明回收区既有上邻空闲区,又有下邻空闲区。必须把这三个区合并为一个空闲区。 第i栏起始地址不变。 第i蓝长度为“i栏中原长度+k栏中长度+L”。 第k栏目的标志应修改为“空”状态。 (4)回收分区的上邻分区和下邻分区都不是空闲的,则直接将空闲分区记录在空闲区表中。 应找一个标志为“空”的登记栏,把回收区的起始地址和长度登记入表,且把该栏目中的标志位修改成“未分配”,表示该登记栏中指示了一个空闲区。 分区的保护 (1)系统设置界限寄存器,界限寄存器是可以上下界寄存器或基址、限长寄存器。 (2)保护键发:即为每个分区分配一个保护键,相当于一把锁。同时为每个进程分配一个相应的保护键,相当于一把钥匙,存放在程序状态字中。美方访问内存时,都要检查钥匙和锁是否匹配,若不匹配,将发出保护性中断。 分区管理方案的优缺点 优点:分区管理是实现多道程序设计中一种简单易行的存储管理技术。通过分区管理,内存真正成了共享资源,有效地利用了处理机和I/O设备,从而提高了系统的吞吐量和缩短了周转时间。在内存利用率方面,可变分区的内存利用率比固定分区高。 缺点:内存使用不充分,并且存在较为严重的碎片问题,虽然可以解决碎片问题,但需要移动大量信息,浪费了处理机时间。收到物理存储器实际存储容量的限制。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/26
articleCard.readMore

操作系统 并发与同步

进程(线程)间相互作用 相关进程与无关进程 相关进程:在逻辑上具有某种联系的进程。 无关进程:在逻辑上没有任何联系的进程。 如果一个进程的执行不影响其他进程的执行,且与其他进程的进展情况无关,即它们是各自独立的,则说这些并发进程的相互之间是无关的。无关的并发进程一定没有共享的变量。 如果一个进程的执行依赖其他进程的进展情况,或者说,一个进程的执行可能影响其他进程的执行结果,则说这些并发进程是相关的。 与时间有关的错误 京城执行的速度是不能由进程自身控制的。对于相关进程来说,可能有若干并发进程同时使用共享资源,即一个进程一次使用未结束,另一个进程也开始使用,形成交替使用共享资源。 进程同步:值多个进程中发生的事件存在某种时序关系,必须协同动作,相互配合,以共同完成一个任务。 进程互斥:指由于共享资源所要求的排他性,进程间要相互竞争以使用这些互斥资源。 进程互斥 解决进程互斥的两种方法: 由竞争各方平等协商。 引入进程管理者,有管理者来协调竞争各方对互斥资源的使用。 临界资源:计算机系统中的需要互斥使用的硬件或软件资源,如外设、共享代码块、共享数据结构等。对各进程在对临界资源进程进行访问时,特别是进行写入或修改操作时,必须互斥的运行。 计算机系统中资源共享的程度分为三个层次:互斥、死锁和饥饿。 互斥:保证资源的互斥使用是指多个进程不能同时使用同一个资源,这是正确使用资源的最基本要求。 死锁:避免死锁是指多个进程互不相让,避免出现都得不到足够资源的情况,从而保证系统功能的正常运行。 饥饿:避免饥饿是指避免某些进程一直得不到资源或者得到资源的概率很小,从而保障系统内资源使用的公平性。 为了保证临界资源的正确使用,可把临界资源的访问过程分为四个部分: 进入区:为了进入临界区使用临界值资源,在进入区要检查可否进入临界区;如果可以进入临界区,通常设置相应的”正在访问临界区“标志,以阻止其他进程同时进入临界区。 临界区:进程中访问临界资源的一段代码。 退出区:将”正在访问临界区“标识清除。 剩余区:代码中的其他部分。 为了合理使用计算机系系统中的资源,在操作系统中采用的进程同步机制应遵循以下几条: 空闲则入:任何同步机制都必须保证任何时间嗯最多只有一个进程位于临界区。当有程序位于临界区时,任何其他进程均不能进入临界区。 忙着等待:当以有进程处于其他临界区时,后到达的进程只能在进入区等待。 有限等待:为了避免死锁等现象的出现,等待进入临界区的进程不能无期限的”死等“。 让权等待:因在进入区等待而不能进入临界区的进程,应释放处理机,转换到阻塞状态以使得其他进程有机会得到处理机的使用权。 进程互斥的软件方法 算法1:单标志算法 假设有两个进程Pi和Pj,设立一个公用整理变量turn,描述允许进入临界区的进程标识。每个进程都在进入区循环检查变量turn是否允许本进程进入。即turn为i时,进程Pi可进入,否则循环检查该变量,直到turn为本进程标识,在退出区修改允许进入进程标识,即进程Pi退出时,Pj的标识为j。 可以保证任何时刻最多只有一个进程在临界区。 缺点:强制轮流进入临界区,没有考虑进程的实际需要,容易造成资源利用不充分。 算法2:双标志、先检查算法 修改临界区标志的设置,设立一个标志数组flag[],描述各进程是否在临界区,初始值均为FALSE. 在进入区的操作为:先检查,后修改。即在进入区像检查另一个进程是否在临界区,不在时修改本进程在临界区的标志,表示本进程在临界区,在退出区修改本进程在临界区的标志,表示本进程不在临界区。 算法2的优点是克服了算法1的缺点,两个进程不用交替进入,可连续使用。但由于使用多个标志,算法有产生新的问题,即进程Pi和Pj可能同时进入临界区,从而违反了最左只有多个进程在临界区的要求。 算法3:双标志、后检查算法 一是保证检查和修改操作间不会出现间隔。 一是修改标志含义。 算法3可防止两个进程同时进入临界区,但它的缺点是Pi和Pj可能都进入不了临界区。在修改本进程标志flag之后和检查对方flag之间有一段时间间隔,这个间隔导致两个进程都想进入临界区,从而在检查对方标志时不通过。 算法4:先修改、后检查、后修改者等待算法 结合了算法3和1,标志flag[i]表示进程i想进入临界区,标志turn表示同时修改标志时要在进入区等待的进程标识。 在进入区先修改后检查,通过修改统一标志turn来描述标志修改的先后;检查对方标志flag,如果对方不想进入临界区则自己进入;否则在检查标志turn,由于标志turn中保存的是较晚的一次赋值,则交往修改标志的进程等待,较早的修改标志的进程进入临界区。 实现了同步机制要求的四条准则中的前两条:空闲则入、忙着等待。 进程互斥的硬件方法 主要思路:使用一种指令完成读和写的两个操作,因而保证读操作与写操作不被打断,依据采用的指令的不同,硬件方法分成TS指令和Swap指令。 TS(Test-and-Set)指令 TS指令的功能是读出指定标识后把该标志设置为TRUE。 每个临界资源设置一个公共布尔变量lock,表示资源两种状态:TURE表示正被占用,FALSE表示空闲,初始值为FALSE。 有进程在临界区时,重复检查,直到其他进程退出时检查通过,所有要访问临界资源的进程的进入区和退出区代码是相同的。 Swap指令 利用Swap指令实现的进程互斥算法是,每个临界资源设置一个公共布尔变量lock,初值为FALSE,每个进程设置一个私有布尔变量key,用于与lock间的信息交换。在进入区利用Swap指令交换lock和key的内容,然后检查key的状态,有进程在临界区时,重复交换和检查过程到其他进程推出啊是检查通过。 优点: 适用范围广:适用于任意数目的进程,在单处理器和多处理器黄健中完全相同。 简单:硬件方法的标志设置简单,含义明确,容易验证其正确性。 支持多个临界区:在一个进程内有多个临界区是,只需为每个临界区设立一个变量。 缺点: 进程在等待进入临界区时,要耗费处理机时间,不能实现”让权等待“。 由于进入临界区的进程是从等待进程中随机选择的,有的进程可能一直选不上,从而导致”饥饿“。 信号量 信号量机制所使用的P、V原语就来自荷兰语test和increment。每个信号量s除一个整数值s.count(计数)外,还有一个进程等待队列s.queue,其中存放的是阻塞在该信号量的各个进程的标识。 信号量只能通过初始化和标准的原语来访问。 P、V原语的执行,不受进程调度和执行的打断,从而很好地解决了原语操作的整体性。信号量的初始化可指定一个非负整数数值,表示空闲资源总数;若为负值,其绝对值表示当前等待临界区的进程数。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 P原语所执行的操作可用下面函数wait(s)来描述。 wait(s){ --s.count; //表示申请一个资源 if(s.count<0){ //表示没有空闲资源 调用进程进入等待队列s.queue; 阻塞调用进程; } } V原语所执行的操作可用下面函数signal(s)描述。 signal(s){ ++s.count; //表示释放一个资源 if(s.count <= 0){ //表示有进程处于阻塞状态 从等待队列s.queue中取出头一个进程P; 进程P进入就绪队列; } } 在使用信号量进行共享资源访问控制时,必须成对使用P和V原语。遗漏P原语则不能保证互斥访问,遗漏V原语则不能在使用临界资源之后将其释放给其他等待的进程。P、V原语的使用不能次序错误、重复或遗漏。 利用操作系统提供的信号量机制可实现进程间的同步,即所谓的前驱关系。 前趋关系是指并发执行的进程P1和P2中,分别有代码C1和C2,要求C1在C2开始前完成执行。可为每个前趋关系设置一个互斥信号量S12,其初值为0.这样,只有在P1执行到V(S12)后,P2才会结束P(S12)的执行。 经典的进程同步问题 Dijkstra将同步问题抽象成一种“生产者-消费者关系”。 简单生产者-消费者问题 设有一个生产者进程P,一个消费者进程Q,他们通过一个缓冲区联系起来。缓冲区只能容纳一个产品,生产者不断的生产产品;而消费者则不断从缓冲区中取出产品,并消费掉。 生产者-消费者同步问题的解决方案如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 生产者进程P: while(true){ P(empty); 生产一个产品; 送产品到缓冲区; V(full); }; 消费者进程Q: while(true){ P(full); 从缓冲区去产品; V(empty); 消费产品; }; 产品生产出来之后立即往缓冲区中存放产品,因为刚开始时缓冲区是空的,一定可以存放一个产品。 多个生产者-消费者问题 设有多个生产者进程P1,P2,……, Pn,若干个消费者进程Q1,Q2,Q3,……,Qm,他们通过一个唤醒缓冲池联系起来,该环形缓冲池由K个大小相等的缓冲区组成,每个缓冲区能容纳一个产品,生产者每次往空缓冲区送一个产品;消费者每次从缓冲区取出一个产品。生产者进程不断地生产产品并把他们放给缓冲池内,消费者进程不断的从缓冲池内取出产品并消费之。 当整个缓冲池全满时,出现供大于求的现象。当整个缓冲池全空时,出现供不应求的现象。 环形缓冲池是临界资源,因为生产者和消费者都需要使用它。 同步问题:P进程不能往“满”的缓冲区中放产品,设置信号量empty,初值为k,用于指示缓冲池中空缓冲区数目。Q进程不能从“空”的缓冲区中取产品,设置信号量full,初值为0,用于指示缓冲池中满缓冲区数目。 互斥问题:设置信号量mutex,初值为1,用于实现临界区(环形缓冲区)的互斥。 算法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 P1,P2,......,Pn; i:=0; while(true){ 生产产品; P(empty); P(mutex); 往Buffer[i]中放产品; i:=(i+1) mod k; V(mutex); V(full); }; Q1,Q2,......,Qm; j:=0; while(true){ P(full); P(mutex); 从Buffer[i]中存放产品; j:=(j+1) mod k; V(mutex); V(empty); 消费产品; } 读者-写者问题 假定有某个共享文件F,系统允许若干个进程对文件F进行读或写,这里要把读文件的进程称为读者,要把写文件的进程称为写者. 多个进程可以同时读文件F; 任一个进程在对文件F进行写时,按规定每次只允许一个进程执行写操作; 当有进程正在读文件时不允许任何进程去写文件。 当有多个读者与写者都需要读写文件时,按规定每次只允许一个进程执行写操作,且在有进程执行写的时候不允许进程读文件。 同步与互斥的综合应用 例1 路口单双号交通管制 Check:指示可否在车辆号码识别区中进入一辆汽车,由于只能进入一辆,其初值为1. Ddd:指示汽车号码是否为奇数,其初值为0,表是不是奇数。 Lven:指示汽车号码是否为偶数,其初值为0,表示不是偶数。 例2 物流系统中的物品分拣问题 问题 从沿长江一线进入枢纽的集装箱,要从这里直接吊装到上海至旧金山的定期集装箱班轮上。 而从沪杭高速公路上进入枢纽的集装箱,要从这里还转到专门在京沪高速公路上行驶的集装箱运输箱上。 该中转枢纽的场地每次只能接受一个方向来的同一批次的集装箱。 分析 长江一线进入的集装箱卸货是一个生产者,从沪杭高速公路上进入的集装箱卸货是第二个生产者。 这两个生产者都要使用中转枢纽的场地,由于该场地每次只能接受一个方向来的同一批次的集装箱,所以长江一线生产者和沪杭高速公路生产者必须互斥。 Site:指示能否在中转的枢纽的场地上卸下集装箱。 Arrive_Y:指示场地上的集装箱是否来自长江。 Arrive_H:指示场地上的集装箱是否来自沪杭。 说明 由于Site初值为1,P(Site)起到互斥作用,无论谁先卸下了集装箱,另一个物流方向上不能在卸货,只能等待. 进程“旧金山班轮装货”和“北京运输车装货”在装完集装箱之后,都调用V(Site),发出可以接受新集装箱的消息。 Site信号量既作为互斥的信号量,又起着同步信号量的作用。 管程 管程的提出 采用P、V同步机制来编写并发程序,对于共享变量及信号量的操作将被分散于各个进程中。 缺点: 对于一组共享变量及信号量的操作是否正确,则必须通读整个系统或者并发程序。 程序不利于修改和维护,局部性很差,所以任意一组变量或一段代码的修改都可能影响全局。 正确性难以保证,保证一个复杂系统没有逻辑错误是很难的。 管程的概念及组成 一个管程是一个由过程、变量及数据结构等组成的集合,他们组成一个特殊的模块或软件包。进程可在任何需要的时候调用管程中的过程,但他们不能在管程之外声明的过程中直接访问管程内的数据结构。 一个管程由四个部分组成:管程名称、共享数据的说明,对数据进行操作的一组过程和对共享数据赋初值的语句。 管程能保障共享资源的互斥执行,即一次只能有一个进程可以在管程内活动。 三个特性: 模块化: 一个基本程序单位,可以单独编译。 抽象数据类型: 管程是一种特殊的数据类型,其中不仅有数据,而且还有对数据进行操作的代码。 信息隐蔽: 管程是半透明的,管程中的外部过程(函数)实现了某些功能,至于这些功能怎么样实现的,其外部则是不可见的。 管程中的共享变量在管程外部是不可见的,外部只能通过调用管程中所说明的外部过程(函数)来间接的访问管程中的共享变量,为了保证管程共享变量的数据完整性,规定管程互斥进入;管程通常是用来管理资源的,因而在管程中应当设有进程等待队以及相应的等待及唤醒操作。 任意时刻管程中只能有一个活跃进程,这个特性使管程能有效地完成互斥。当一个进程调用管程过程时,该过程中的前几条指令将检查在管程中是否有其他的活跃进程,如果有,调用进程将被挂起,直到另一个进程离开管程将其唤醒,如果没有活跃进程在使用管程,则该调用进程可以进入。 管程中的条件变量 解决方法是引入条件变量以及相关的两个操作:wait和signal,当一个管程过程发现它无法继续运行时(例如:生产者发现缓冲区满),他会在某个条件变量(如full)上执行wait操作,该操作导致调用进程自身阻塞,并且还将另一个以前等在管程之外的进程调入管程,另一个进程,比如消费者,可以唤醒正在睡眠的伙伴进程,这可以通过对其伙伴正在等待的一个条件变量执行signal完成。 wait操作必须在signal之前,这条规则使得实现简单了许多,实际上这不是一个问题,因为需要时,用变量很容易跟踪每个进程的状态。 当一个进入管程的进程执行等待操作时,它应当释放管程的互斥权每当一个进入管程的进程执行唤醒操作(如P唤醒Q)时,管程中便存在两个同时处于活动状态的进程。 处理方法: P等待Q继续,直到Q退出或等待(Hoare提出)。 Q等待P继续,直到P等待或退出。 规定唤醒为管程中最后一个可执行的操作。 当一个进程试图进入一个已被占用的管程时它应当在管程的入口处等待,因而在管程的入口处应当由一个进程等待队列。在管程内部,由于执行唤醒操作,可能会出现多个进程等待队列,因而还需要有一个进程等待队列,这个等待队列被称为紧急等待队列,它的优先级应当高于入口等待队列的优先级signal(c);如果c链为空,则相当于空操作,执行此操作的进程继续;否者幻想第一个等待者,执行此操作的进程的PCB入紧急等待队列的尾部。 用管程解决生产者-消费则问题 Pthread中的互斥与同步 Pthread提供了可用于线程同步与互斥的机制,他们是互斥量和条件变量,两者结合起来使用已达到管程的效果。 互斥量及相关函数 解决线程互斥问题的基本思想是使用一个可以加锁和解锁的互斥量来保护临界区。一个进程如果想要进入临界区,他首先尝试锁住相关的互斥量。如果互斥量没有加锁,那么这个线程可以立即进入,并且该互斥量被自动锁定以防止其他进程进入。如果互斥量已经被加锁,则调用线程被阻塞,直到该互斥量被解锁。如果多个线程在等待同一个互斥量,当它被解锁时,这些等待的线程中只有一个得到互斥量并将其锁定。 条件变量及相关函数 除互斥量之外,Pthread提供了一种同步机制:条件变量,它允许线程由于一些为满足的条件而被阻塞。 让一个线程锁住一个互斥量,如果该线程不能获得它期望的结果时,则等待一个条件变量;最后另一个线程会向它发出信号,使它可以继续执行。 通信进程 P、V操作是一类低级通信原语,不能承担进程间大量信息的交换任务。 解决进程之间的大量信息通信问题有三个方案:共享内存、消息机制以及通过共享文件进行通信,即管道通信。他们不仅要保证相互制约的进程之间的正确关系,还要同时实现进程之间的信息交换。 共享内存 在相互通信的进程之间设有一个公共内存区,一组进程向该公共内存中写,另一组进程中的读写互斥问题。操作系统一般只提供要共享的内存空间,而处理进程间在公共内存中的互斥关系则是程序开发人员的责任。 消息机制 消息机制是用于进程间同行的高级通信原语之一。进程在运行过程中可能需要与其他的进程进行信息交流,于是进程通过某种手段发出自己的信息或接收其他进程发来的消息。这种方式类似于人们通过邮政局收发邮件来实现交换信息的目的。 信息缓冲通信 基本思想:根据“生产者-消费者”原理,利用内存中共用消息缓冲区实现进程之间的信息交换。 内存中开辟了若干信息缓冲区,用于存放消息。 一个进程可以给若干个进程发送消息,反之,一个进程可以接受不同进程发来的消息,显然,进程中关于消息队列的操作是临界区,当发送进程正往接收进程的消息队列中添加一条消息时,接收进程不能同时从该消息队列中取出信息;反之也一样。 消息缓冲区通信机制包括以下几个内容: 消息缓冲区:这是一个由消息长度、消息正文、发送者、消息队列指针组成的数据结构。 消息队列首指针:m_q,一般存在PCB中。 互斥信号量m_mutex,初始值为1. 同步信号量m_syn,初始值为0. 发送消息原语send(receiver, a)。 接收信息原语receive(a). 信箱通信 以发送信件以及接收回答新建为进程间通信的基本方式。 当一个进程希望与另一个进程通信时,就创建一个链接两个进程的信箱,发送进程把信件投入信箱,而接收进程可以在任何时刻取走信件。 一个新鲜的结构可有“信箱说明”和“信箱体”两部分组成。 有如下的数据结构: 可存信件数 是在设立信箱时预先确定的,表明信箱的容量大小。 已有信件数 指出信箱中已有信件的数量。 可存信件的指针 指示当前可存入一封信的位置。该指针的初始值为指向可存第一封信的位置。 为了实现信箱通信,必须提供相应的原语,如创建信箱原语、撤销信箱原语、发送信箱原语和接收信箱原语等。 表示的是一个发送者和一个接收者单向通信的例子,在进程A发送信件之间,信箱中至少应该有空位置,可以存放信件,同样,在进程B接收信件之前,信箱中应该有信件,否则进程应该等待。 好处:发送方和接收方不必直接建立联系,没有处理时间上的限制。发送方可以在任何时间发信,接收方可以在任何时间收信。 由于发送方和接收方都是独立工作的,如果发的快而接受的慢,则信箱会溢出。相反,如果发的慢而收的快,则信箱会变空。 规则: 若发送信件时信箱已经满了,则发送进程应被置为“等信箱”状态,直到信箱有空时才被释放。 若取信件时信箱中无信,则接收进程应被置成“等信件”状态,直到有信件时才被释放。 管道通信 管道通信首先出现在UNIX操作系统中。 管道:就是连接在两个进程之间的一个打开的共享文件,专用于进程之间进行数据通信。发送进程可以源源不断的从管道一端写入数据流,每次写入的信息长度是可变的,接受进程在需要时可以从管道的另一端读出数据,读出单位长度也是可变的。管道通信的基础是文件系统。 在对管道文件进行读写操作的过程中,发送进程和接收进程都要实施正确的同步和互斥,以确保通信的正确性,管道通信机制中的同步与互斥都由操作系统自动进行,对用户是透明的。 具有传送数据量大的优点,但是通信速度比较慢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/16
articleCard.readMore

操作系统 内存管理 基本概念

计算机系统中的存储器可以分为两类:内存储器(简称内存)和外存储器(简称外存)。处理器可以直接访问内存,但不能直接访问内存。CPU要通过启动相应的输入/输出设备后才能使内存和外存交换信息。 内存管理是操作系统中重要功能之一。 基本概念 存储体系 存储设备的速度仍然明显慢于同一级别的中央处理器的速度。任何一种存储设备都无法在速度与容量两个方面同时满足用户的需求。 少量的、非常快速、昂贵、内存易变的高速缓冲器Cache,通常是有KB的数量级。 若干兆字节、中等速度、中等价格、内容易变的内存RAM,通常是千MB的数量级。 低速、廉价、内容不一边的外存(磁盘),通常是百至千GB的数量级。 存储管理的任务 任何程序和数据以及各种控制用的数据结构都必须占用一定的存储空间,因此,存储空间直接影响系统性能。 内存空间:由存储单元(子节或字)组成的一维连续的地址空间,内存空间用来存放当前正在运行程序的代码及数据,是程序中指令本身地址所指的亦即程序计数器所指的存储空间。 系统区:用以存放操作系统常驻内存的部分,用户不能占用这部分空间。 用户区:分配给用户使用,用于装入并存放用户程序和数据,信息随时都会发生变化, 存储管理的实质就是管理供用户使用的那部分空间。 内存管理问题的主要包括:内存管理方法、内存的分配和释放算法、虚拟存储器的管理、控制内存和外存之间的数据流动方法、地址交换技术和内存数据保护与共享技术等。 单道、单用户:在一个区域内存放系统软件,如操作系统本身,而另外一个区域放置用户程序。 多道、多用户系统:为了提高系统的的利用率,需要将内存划分更多的区域,以便支持多道程序。 充分利用内存,为多道程序并发执行提供内存基础。 尽可能方便用户使用: 操作西戎自动装入用户程序。 用户程序中不必考虑硬件细节。 系统能够解决程序空间比内存实际内存空间大的问题。 程序的长度在执行时可以动态伸缩。 内存存取速度快。 存储保护与安全。 共享与通讯。 及时了解有关资源的使用状况。 实现的性能和代价合理。 在操作系统中存储管理的主要任务。 内存的回收与分配 一个有效的存储分配机制,应对用户提出的需求予以快速响应,为之分配相应的存储空间。在用户程序不再需要它的同时及时回收,以供其他用户使用。 功能: 记住每个存储区的状态。 实施分配。 回收。 为了实现上述功能,必须引入分配表格,统称为内存分配表,其组织方式包括: 位示图表示法:用一位(Bit)表示一个空闲页面(0表示空闲,1表示占用)。 空闲页面表:包括首页面号和空闲页面的个数,连续若干个页面作为一组登记在表中。 空闲块表: 空闲块首址和空闲块长度,没有记录的区域即为进程所占用。 内存分配的两种方式: 静态分配:程序要求的内存空间是在目标模块连续装入内存时确定并分配的,并且在程序运行过程中不允许再申请或者在内存中“搬家“,即分配工作是在程序运行前一次性完成。 动态分配:程序要求的基本内存空间是在目标模块转入时确定并分配的,但是在程序运行过程中,允许申请附加的内存空间或在内存中”搬家“,即分配工作是在程序运行前即运行过程中逐步完成的。 动态存储分配具有较大的灵活性: 它不需要一个程序的全部信息进入内存后才可以运行,而是在程序运行中需要时系统自动将其调入内存。 程序当前暂不使用的信息可以不进入内存,这对提高内存的利用率有好处。 存储共享 存储共享是指两个或多个进程共用内存中的相同区域,这样不仅能使多道程序动态的共享内存,提高内存利用率,而且还能共享内存中某个区域的信息。 内容包括:代码共享和数据共享,特别是代码共享要求代码必须是纯代码。 一是通过代码共享节省内存空间,提高内存利用率。 一是通过数据共享实现进程通信。 存储保护 为多个程序共享内存提供保障,是在内存中的各个程序只能访问其自己的区域,避免各程序间相互干扰。 存储保护通常是需要有硬件的支持,并由软件配合实现。 保护系统程序区不被用户有意或者无意的侵犯。 不允许用户程序读写不属于自己地址空间的数据,如系统区地址空间、其他用户程序的地址空间。 (1)地址越界保护 每个进程都具有其相对独立的进程空间,如果进程在运行是所产生的地址超出其地址空间,则发生地址越界。地址越界可能侵犯其他进程的空间,影响其他进程的正常运行;也可能侵犯操作系统空间,导致系统混乱。对程序产生的地址必须加以检查,发生越界时产生中断,由操作系统进行相应处理。 (2)权限保护 对于多个进程贡献的公共区域,每个进程都有自己的访问权限。 对属于自己的区域的信息,可读可写。 对公共区域中允许共享的信息或获得授权可使用的信息,可读而不可修改。 对未获授权使用的信息。不可读、不可写。 当发生地址越界或者非法操作的时候,由硬件产生中断,进入操作系统处理。 ”扩充“内存容量 在硬件支持下,软件、硬件相互协作,将内存和外存结合起来统一使用。 借助虚拟存储技术或其他交换技术在逻辑上扩充内存容量,亦即为用户提供比内存物理空间大的多的地址空间,使得用户感受它的程序是在一个大的存储器中运行。 地址转换 存储器以字节(1B=8个二进制位)为编址单位,每个字节都有一个地址与其对应。假定存储器的容量为n个字节,其地址编号为0,1,2,3,…,n-1 。这些地址称为内存的“绝对地址”,与绝对地址对应的内存空间称为“物理地址空间”。 用户程序中使用的地址称为“逻辑地址”,与逻辑地址对应的存储空间称之为“逻辑地址空间”。 地址重定位 当用户程序进入计算机系统请求执行时,存储管理要为他分配合适的内存空间,这个分配到的内存空间可能是从某个单元开始的一组连续的地址空间。该地址空间的起始地址是不固定的,而且逻辑地址与分到的内存地址空间的绝对地址经常不一致。每个逻辑地址在内存中也没有一个固定的绝对地址与之对应。 为了保证程序的正常执行,必须根据分配给程序的内存区域对程序中指令和数据的存放地址进行重定位,即要把逻辑地址转换成为绝对地址。 把逻辑地址转换成绝对地址的工作称为“地址重定位”或“地址转换”,又称“地址映射”。重定位的方式有“静态重定位”和“动态重定位”两种。 静态重定位 由于地址转换工作是在程序开始执行前集中完成的,所以在程序执行过程中就无须再进行地址转换工作。称为“静态重定位”。 动态重定位 在装入程序时,不进行地址转换,而是直接把程序装到分配的内存区域中,在程序执行过程中,每当执行一条指令时都由硬件的地址转换机构将指令中的逻辑地址转换成绝对地址。称为“动态重定位”。 动态重定位由软件和硬件互相配合来实现。硬件要有一个地址转换机构,该机构可由一个基址寄存器和一个地址转换线路组成。 存储管理为程序分配内存区域后,装入程序把程序直接装到所分配的区域中,并把内存区域的起始地址存入相应程序进程的进程控制块中。当程序进程被调度占用处理器时,随同现场信息的恢复,程序所占的内存区域的起始地址也被存放到“基址寄存器”中。程序执行时,处理器每执行都会把指令中的逻辑地址与基址寄存器中的值相加得到绝对地址,然后按绝对地址访问内存。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/16
articleCard.readMore

操作系统 进程线程模型 线程模型

线程模型 线程:能够独立运行的基本单位,试图用它来提高系统内程序并发执行的程度。 线程的引入 基本属性:进程是一个可拥有资源的独立单位,又是一个可以独立调度和分派的基本单位。 创建进程:必须为其分配所有资源(除CPU外),包括内存空间、I/O设备以及建立相应的数据结构PCB。 撤销进程:必须先对这些资源进行回收操作,然后在撤销PCB。 进程切换:由于要保留当前进程的CPU环境和设置新选中进程的CPU环境,为此需要花费不少的CPU时间。 进程是一个资源拥有者,因而在进程的创建、撤销和切换中,系统必须为之付出较大的时空开销。 创建背景:如果将作为调度和分派的经本单位不同时作为独立分配资源的单位,以使轻快运行;而对拥有资源的基本单位,又不频繁地对之进行切换。 线程的基本概念 线程是进程中的一个实体,是CPU调度和分派的基本单位。 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。 线程也同样有就绪、等待和运行三种基本状态。 线程的属性 每一个线程有一个唯一的标识符和一张线程描述表,记录了线程执行的寄存器和栈等现场状态。 不同的线程可以执行相同的程序,同一个服务程序被不同用户调用时操作系统为它创建不同的线程。 同一个进程中的各个线程共享进程的内存地址空间。 线程是处理器的独立调度单位,多个线程是可以并发执行的,在单个CPU的计算机系统中,各个线程可交替的占用CPU;在多个CPU计算机系统中,各个线程可同时占用不同的CPU,若各个CPU同时为一个进程内的各种线程服务是可以缩短进程的处理时间。 一个线程被创建后便开始了它的生命周期,直至终止,县城在生命周期内会经历等待、就绪和运行等各种状态变化。 引入线程的好处 创建一个新的进程花费的时间少,不需另行分配资源,创建线程的速度比创建进程的速度快,且系统的开销也少。 两个线程的切换花费时间少。 由于同一个进程内的线程共享内存和文件,线程之间相互通信无须调用内核。 线程能独立运行,能充分利用和发挥处理器与外围设备并行工作能力。 线程与进程的比较 线程具有许多传统进程所具有的特征,故又称为轻量级进程或者是进程元,把床听的进程称为重量级进程。 调度:在传统的操作系统中,拥有资源的基本单位和独立调度、分派的基本单位都是进程。而在引入县城的操作系统中,则把线程作为调度和分派的基本单位。同一进程中,线程切换不会引起进程切换;而在由一个进程中的线程切换到另一个进程中的线程时,将会引起进程切换。 并发性:在引入线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行。很有效的使用系统资源和提高系统的吞吐量。 拥有资源:线程的自己不拥有系统资源,但它可以访问其隶属进程的资源,可供同一进程的其他所有线程共享。 系统开销:由于在创建或撤销进程时,系统都要为之分配或回收资源。因此,操作系统所付出的开销将显著地大于在创建或撤销线程时的开销。 线程实现机制 用户级线程 用户级线程不依赖于内核。用户级线程只存在于用户态中,对它的创建、撤销和切换不会通过系统调用来实现,因而这种线程与内核无关。内核也并不知道有用户级线程的存在,从内核角度考虑,就是按正常的方式管理即单线程进程。 支持用户级进程的典型操作系统就是Linux。 在用户空间管理线程时,每个进程都需要有其专用的线程表。用来跟踪该进程中的线程。该线程表由运行时系统管理。但一个线程转换到就绪状态或阻塞状态时,在该进程表中存放着从新启动该线程所需要的信息,于内核在进程表中存放进程的信息完全一样。 内核级线程 内核级线程依赖于内核,无论是在用户进程中的线程,还是系统进程中的线程,他们的创建、撤销和切换都是有内核实现的。在内核中保留一个线程控制块,系统根据该控制块而感知该线程的存在并对县城进行控制。 支持内核级线程的典型操作系统是Windows。 内核的线程表保存了每个线程的仅存表、状态和其他信息。所以能够阻塞线程的表用都以系统调用的形式实现。当一个线程阻塞时,内核可以选择运行的同一个进程中的另一个线程(若有一个就绪进程)或者运行另一个进程中的线程。而在用户及线程中,运行时系统时钟运行自己进程中的线程,直到内核剥夺它的CPU(或者没有可运行的线程存在了)为止。 用户级线程和内核级线程比较 线程的调度与切换速度:核心级线程的调度与切换与进程的调度和切换十分相似。在线程调度时的调度方式,同样也是采用抢占方式和非抢占方式两种。在线程的调度算法上,也同样可采用时间片轮转法、优先权算法等。用户级线程的切换通常是发生在一个应用进程的诸线程之间,这时,不仅无需同福哦终端进入操作系统的内核,而且切换的规则页远比进程调度和切换的规则简单。用户级线程的切换速度特别快。 系统调用:在传统的用户进程调用一个西戎调用时,要由用户状态转入核心状态,用户进程将被封锁。当那个完成系统调用而返回时,才将该进程唤醒,继续执行,而在用户级线程调用一个系统调用时,由于内核并不知道有该用户级进程的存在,因而把西戎调用看作是整个进程的行为,于是使该进程等待,而调度另一个进程执行。当一个进程调用一个系统调用时,内核把系统调用只看作是该线程的行为,以问封锁该进程中的其他线程执行。 线程执行时间:对于只设置了用户级线程的系统,调度是以进程为单位进行的。 混合实现方式 支持混合方式线程的典型操作系统是Solaris。 Pthread线程包 IEEE标准1003.1c定义了线程标准,Pthread是基于该标准实现的线程包。 多道程序设计模型 作用:提高CPU的利用率。 程序的顺序执行 程序是一个在时间上按严格次序前后相继的操作序列,这些操作是机器指令或高级语言编写的语句。 特点: 顺序性:程序所规定的动作在机器上严格地按顺序执行。 封闭性:资源的状态(除了初始状态外)只有程序本身的动作才能改变。 确定性:程序执行结果与它的执行速度无关,也称为程序执行结果与时间无关性。即CPU在执行程序时,任意两个动作之间的停顿对程序的计算结果都不会产生影响。 可再现性:如果程序执行在不同的时间执行,只要输入的初始条件相同,则无论何时重复执行该程序都会得到相同的结果。 多道程序系统中程序执行环境的变化 多道程序设计技术的引用 为了提高计算机系统中各种资源的利用效率,缩短作业的周转时间。多种硬件资源能并行工作。 单CPU:并发程序按给定的时间片交替的在处理机上执行,其执行的时间是重叠的。 多CPU:这些并发程序在各自的处理机上运行。 例: 假设有两个程序A和B都要执行,A程序的执行顺序为在CPU中执行10s、在设备DEV1上执行5s、又在CPU上执行了5s、在设备DEV2上执行了10s、最后在CPU上执行了10s; B程序的执行顺序为:在设备DEV2上执行10s、在CPU上执行10s、在设备DEV1上执行5s、又在CPU上执行了5s、最后在设备DEV2上执行10s。 在顺序环境下,A执行完之后B执行,或则B执行完之后A执行。假设A先执行,程序A、B全部执行完之后需要80s的时间,其中有40s是程序使用CPU,15S使用设备DEV1,25s使用设备DEV2. CPU利用率=40/80=50% DEV1利用率=15/80=18.75% DEV2利用率=25/80=31.25% 在并发环境下,程序A、B可以同时执行,当程序A在CPU上执行时,程序B可以在设备DEV1上执行,程序A、B全部执行完成之后需要45s时间。 CPU利用率=40/45=89% DEV1利用率=15/45=33% DEV2利用率=25/45=56% 多道程序设计环境的特点 多道程序设计就是允许多个程序同时进入内存并运行。 系统吞吐量衡量系统效率的尺度。吞吐量是指单位时间内系统所处理的作业(程序)的道数(数量)。 如果系统的资源利用率高,则单位时间内所完成的有效工作多,吞吐量大。 如果系统的资源利用率低,则单位时间内所完成的有效工作少,吞吐量小。 **作用:**提高了设备资源利用率,提高了内存资源利用率,提高了处理机资源利用率,最终,最终提高了系统吞吐量。 多道程序设计环境的特点 独立性:每道程序都是在逻辑上独立的。 随机性:程序和数据的输入与执行开始时间都是随机的。 资源共享性:资源共享将导致对进程执行速度的制约。 程序的并发执行 程序的并发执行是指两个或两个以上的程序在计算机系统中同处与已开始执行的且尚未结束的状态。能够参与并发执行的程序称为并发程序。 特性 并发程序在执行期间具有相互制约关系:“执行-暂停-执行”。 程序与计算不再一一对应:允许多个用户作业调用一个共享程序段。 并发程序执行结果不再可现:宏观上是同时进行的,在单CPU系统中,他们仍是顺序执行。 进程模型 进程 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配与调度的一个独立单位。 系统进程执行操作系统程序,完成操作系统的某些功能。 用户进程运行用户程序,直接为用户进行服务。 系统进程的优先级通常高于一般的用户进程的优先级。 进程与程序的联系与区别 联系: 程序是构成进程的组成部分之一,一个进程的运行是执行它所对应的程序。 进程是有程序,参数和进程控制块(PCB)部分组成。 区别: 程序是静态的,而进程是动态的。 程序可以是永久的,而进程存在只是暂时的。 一个进程可以执行一个或几个程序、一个程序可以构成多个进程。 进程具有创建其他进程的功能,被创建的进程称为子程序,创建者称为父程序。 进程的特性 并发行:一个进程的第一个动作可以在另一个进程的最后一个动作结束之前开始。 动态性:进程动态产生、动态消亡,在进程生命周期内,其状态动态变化。 独立性:一个进程是一个相对完整的资源分配单位。 交往性:一个进程在运行过程中可能会与其他进程发生直接或间接的相互作用。 异步性:每个进程按照各自独自的、不可预知的速度向前推进。 进程的状态及其状态转换 三状态进程模型 运行中的进程可以处于以下三种状态之一:运行、就绪、等待。 运行状态Running:运行状态是指进程已经获得CPU,并且在CPU上执行的状态。 在一个单CPU系统中,最多只有一个进程处于运行状态。 就绪状态Ready:指一个进程已经具备运行条件,但由于没有获得CPU而不能运行所处的状态。一旦CPU分配给他,该程序就可以运行,处于就绪状态的进程可以是多个。 等待状态Waiting:也称阻塞状态或封锁状态,是指程序因等待某种事件发生而暂时不能运行的状态。 就绪–》运行 未能获取处理机,故仍然不能运行。 运行–》就绪 由于规定的运行时间片用完而使系统发出超时中断请求,超时中断处理程序把该进程的状态修改为就绪状态。 运行–》等待 处于运行状态的进程是否能继续运行,除了受时间限制外,还收其他种种因素的影响。 等待–》就绪 等待的进程在其被柱塞的原因获得解除后,因为处理及满足不了进程的需要,于是将状态有等待变成就绪,仅当进程调度程序把处理机再次分配给他时,才可恢复现场继续运行。 五状态进程模型 运行状态 Running:进程占用处理机资源 出于此状态的进程的数目不小等于处理机的数目,再没有其他进程时可以执行是,通常会自动执行系统的空闲进程。 就绪状态 Ready:进程已获得除处理机外所需的资源,等待分配处理机资源,只要分配处理机就可以执行。 阻塞状态 Blocked:由于进程等待的I/O操作或进程同步等条件而暂停运行时处于阻塞状态。即使把处理机分配给该进程,也是无法继续执行的。 创建状态 New:进程正在创建的过程中,还不能运行。包括分配和创建进程控制块表项、建立资源表格并分配资源,机在程序并建立地址空间。 结束状态 Exit:进程已结束运行,回收除进程控制块之外的其他资源,并让其他进程从进程控制块中收集有关的信息。 操作系统中多个进程的并发执行是通过进程交替进入运行状态来实现的。 创建进程:创建一个新的进程,来运行一个程序。 提交admit:完成一个新进程的创建过程,新进程进入就绪状态。 调度运行dispatch:从就绪进程表中选择一个进程进入运行状态。 释放release:由于进程完成或失败而终止进程运行,进入结束状态。 运行到结束的转换可分为正常退出和异常退出,其中异常退出是指进程执行超时、内存不够。 超时timeout:由于运行的时间片或高优先级进程就绪状态等因素导致进程停止运行。 事件等待event wait:进程要求的事件未出现而进入阻塞。 可能的原因包括申请进程系统服务或资源、通信、I/O等操作。 事件出现event occurs:进程等待的事件出现,如操作完成,申请成功。 七状态进程模型 五状态进程模型没有区分进程地址空间位于内存还是外存,虚拟存储管理技术后,需要进一步区分进程的地址空间状态。 好处: 有空闲内存空间用于提交新进程。 提供足够的内存。 有利于调试:被挂起的调试程序,可方便对其他地址空间进行读写。 下面是列出在挂起的进程模型中的四种意义变化或新的状态。 就绪状态:立即进入运行状态。 阻塞状态:在内存并等待某事件的出现。 阻塞挂起状态:进程在外存并等待某事件的出现。 就绪挂起状态:进程在外存,但只要进入内存。 挂起:把一个进程从内存转到外存。 阻塞到阻塞挂起。 就绪到就绪挂起:有高优先阻塞加入时。 运行到就绪挂起。 激活:把一个进程从内存转到外存。 就绪挂起到就绪:就绪挂起进程优先级高于就绪进程。 阻塞挂起到阻塞:当一个进程释放足够的内存时,系统会把一个高优先级阻塞挂起进程激活。 事件出现:进程等待的事件出现。 阻塞到就绪:内存进程的事件出现。 阻塞挂起到就绪挂起:针对外存进程的事件出现。 提交:完成一个新进程的创建过程,新进程进入就绪状态或就绪挂起状态。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/10
articleCard.readMore

操作系统 进程线程模型 进程线程调度

调度是分层次的,在操作系统中,一般将调度分为高级调度、中级调度和低级调度。 高级调度也称作业调度,其主要任务是按一定的原则,对磁盘中的处于后备状态的作业进行选择并创建为进程。 中级调度的主要任务是按照给定的原则和策略,将处在磁盘对换区中切具备运行条件的就绪进程调入内存,或将处于内存就绪状态或内存阻塞状态的进程交换到对换区。 低级调度即进程(线程)调度,是决定就绪队列中哪个进程将获得处理机,并使即将处理及分配给该进程的操作。 进程(线程)调度即处理机调度。任务是控制、协调进程(线程)对CPU的竞争,按照一定的调度算法,使某一就绪进程获得CPU的控制权,转换成运行状态。 概述 进程(线程)调度的主要功能 记录系统中所有进程(线程)的执行状况,根据一定的调度算法,从就绪队列中选出一个进程(线程)来,准备把CPU分配给它,把CPU分配给进程(线程),即把选中进程(线程)的进程(线程)控制块内有关的现场信息,让它占用CPU运行。 进程(线程)调度的时机 正在执行的进程(线程)运行完毕。 调用阻塞原语将自己阻塞起来进入等待状态。 调用阻塞原语操作,并且因为资源不足而被阻塞;或调用唤醒原语操作激活了等待资源的进程(线程)。 时间片用完。 就绪队列中的某个就绪队列中一旦有优先级高于当前运行进程(线程)优先级时,引发进程(线程)调度。 抢占方式:就绪队列中一旦由优先级高于当前运行进程(线程)优先级的进程(线程)存在时,便立即进行调度,转让CPU。 不可抢占方式:一旦把CPU分配给一个进程(线程),它就一直占用CPU,直到该进程(线程)自己因调用原语操作或等待I/O而进入阻塞状态或时间片用完时才让出CPU,重新执行进程(线程)调度。 调度算法设计原则 进程行为 几乎所有的进程的(磁盘)I/O请求或计算都是交替完成的。某些I/O活动可以看作是计算,在CPU向视频RAM复制数据以更新屏幕时,因为使用了CPU,所以这是计算,而不是I/O,当一个进程等待外部设备完成工作而被阻塞的行为属于I/O。 计算密集型:进程花费了绝大多数时间在计算上。 I/O密集型:进程在等待I/O上花费了绝大多数时间。 系统分类 通常分为三类环境:批处理、交互式和实时系统。 批处理系统:减少了进程的切换从而改善了性能。 交互式:避免一个进程霸占CPU拒绝为其他进程服务,抢占是必需的。服务器也归于此类,因为通常他们要服务多个突发的(远程)用户。 实时限制:只运行那些用来推进现有应用的程序,而交互式系统是通用的,它可以运行任意的非协作甚至是有恶意程序。 调度算法的设计目标 运行大量批处理作业的大型计算中心的管理者们为了掌握其系统的工作状态,通常是检查各个指标:吞吐量、周转时间以及CPU利用率。 吞吐量:是系统每小时完成的作业数量。 周转时间:从一个批处理作业提交时间开始直到该作业完成时刻为止的统计平均时间。 CPU利用率:用于对批处理系统的度量,系统每小时可完成多少作业(吞吐量),以及完成作业需要多长时间(周转时间)。 进程(线程)调度算法 进程(线程)调度算法解决以何中次序对各就绪进程(线程)进程处理机的分配以及按何种时间比例让进程(线程)占用处理机。 先来先服务FCFS算法 进程按照他们请求CPU的顺序使用CPU。 最短作业优先SJF算法 当输入队列中有若干同等重要的作业被启动时,调度程序应使用最短作业优先算法。 有4个作业A、B、C、D,运行时间分别是8、4、4、4分钟。 先到先服务:若按图A,B,C,D 的次序运行,则A的周转时间为8分钟,B为12分钟,C为16分钟,D为20分钟,平均为14分钟。 最短作业优先:运行顺序为BCDA,则周转时间分别为4、8、12、20分钟,平均时间为11分钟。 最短剩余时间优先SRTN算法 调度程序总是选择其剩余运行时间最短的那个进程运行。 最高响应比优先HRRF算法 响应比Rp=(等待时间+预计运行时间)/预计运行时间=周期时间/预计运行时间。 轮转法RR算法 基本思想:将CPU的处理时间划分为一个个的时间片,就绪队列中的诸程序轮流运行一个时间片。当时间片结束时,就强迫运行的进程让出CPU,该进程机内就绪队列,等待下一次调度。同时,进程调度又去选择就绪队列中的一个进程,分配给它一个时间片,以投入运行。 影响时间片值设置的几个主要因素: 系统响应时间:当进程数目一定时,时间片Q值的大小占比于系统对响应时间的要求,例如进程数目为N,要求响应时间为T,则Q=T/N,Q值随T值的大或小而大或小。 就绪进程的数目:当系统响应时间T一定时,时间片Q值的大小反比于就绪进程数。 计算机的处理能力:计算机的处理能力直接决定了每道程序的处理时间,显然,处理速度越高,时间片值就可以越小。 结论:时间片设置的太短会导致过多的进程切换,降低了CPU效率;而设的太长有可能引起对短的交互请求的响应时间变长,将时间片设置为20~50ms通常是一个比较合理的折中。 最高优先级HPF算法 最高优先级调度每次将处理及分配给具有最高优先级的就绪进程(线程)。进程(线程)的优先级由进程(线程)优先数决定的。 进程(线程)优先数的设置可以是静态的也可以是动态的。 静态优先数是在进程(线程)创建时根据进程(线程)初始特性或用户要求而确定的,在进程(线程)运行期间不能再改变。 动态优先数是指在进程(线程)创建时先确定一个初始优先数,以后在进程(线程)运行中随着进程(线程)特性的改变(如等待时间增长),不断修改优先数。优先数小的进程(线程)优先级高。 如果不对优先级进行调整,则低优先级进程很有可能产生饥饿现象。 多级反馈队列算法 以最高优先级算法作为主要的调度模式,但对于具有相同优先数的进程(线程)按先进先出调度算法处理。 多级队列反馈法就是综合了先进先出调度算法、时间片轮转法和可抢占式最高优先级算法的一种进程(线程)调度算法。 被调度队列的设置:系统按优先级别设置若干个就绪队列,不同优先级别的队列有不同的时间片,对级别较高的队列分配较小的时间片Si(i=1,2…..,n)。 在同一个队列之间的调度原则:除了第n级队列是按照RR算法调度之外,其他各级队列均按照先进先出调度算法调度。 在不同队列之间的调度原则:西戎总是先调度级别比较高的队列,仅当级别较高的队列为空是才去调度次一级队列中的就绪队列。当等待进程(线程)被唤醒时,它进入与其优先级相同的就绪队列,若该进程(线程)优先级高于正在执行的的进程(线程),便抢占CPU。 最短进程优先 如何从当前可运行进程中找出最短的那一个进程。 根据进程过去的行为进行推测,并执行估计运行时间最短的哪一个。 老化:通过当前测量值和向前估计值进程加权平均而得到下一个估计值的技术。 实时系统中的调度算法 实时系统是一种时间起着主导作用的系统,即系统的正确性不及取决于计算的逻辑结果,而且还依赖于产生结果的时间。 实时系统应用的例子包括实验控制、过程控制设备、机器人、空中交通管制、电信、军事指挥与控制系统。 硬实时任务值必须满足最后期限的限制,否则会给系统带来不可接受的破坏或者致命的错误。 软实时任务也有一个与之关联的最后期限,并希望能满足这个期限的要求,但并不是强制的,即使超过了最后期限,调度和完成这个任务仍是有意义的。 速率单调调度算法:适用于可抢先的周期性进程的经典静态实时调度算法是速率单调调度RMS。 每个周期性进程必须在其周期内完成。 没有进程依赖于任何其他进程。 每一进程在一次有突发中需要相同的CPu时间量。 任何非周期性进程都没有最终时限。 进程抢先即刻发生而没有系统开销。 最早最终时限优先调度:EDF算法是一个动态算法,它不像速率单调算法那样要求进程是周期性的,他也不详RMS那样要求CPU突发有相同的运行时间。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/9
articleCard.readMore

操作系统 进程线程模型 进程控制块及进程控制

进程控制块PCB 在操作系统中,为进程定义了一个专门的数据结构,称为进程控制块PCB。 PCB内容 PCB内容可以分为调度信息和现场信息两大部分。 调度信息供进程使用时使用,描述了进程当前所处的状况,他包括进程名、存储信息、进程号、优先级、当前状态、资源清单、“家族”关系、消息队列指针、进程队列指针和当前打开的文件等。 现场信息刻画了进程的运行情况,由于每个进程都有自己专用的工作存储区,其他进程运行时不会改变它的内容。 进程的组成 PCB组织 线性方式:将所有的PCB部分状态组织在一个连续表(称为PCB表)中。 优点:简单,且不需要额外的开销,适用于进程数且不多的系统。 缺点:需要扫描整个PCB表。 索引方式:对于具有相同状态的进程,分别设置各自的PCB索引表,表目为PCB在PCB表(线性表)中的地址。就构成了就绪索引表和等待索引表。 链接方式:对于具有相同状态的进程PCB,通过PCB中的链接字构成一个队列。按“先进先出”的原则出对,若队列指针为0,表示该队列为空。 进程的队列 就绪队列:进程入队和出队的次序与处理机调度算法有关。 等待队列:每一个等待事件一个队列。 运行队列:在单CPU系统中整个系统有一个运行队列。 进程控制 作用:就是对进程在这个生命周期中各种状态之间的转换进行有效的控制。 原语:通常由若干的指令组成,用来实现某个指定的操作。通过一段不可分割的或不可中断的程序实现其功能。原语的执行过程必须是连续的,一旦开始执行就不能间断,直到执行结束。原语是操作系统的可行,在管态下执行,并且常驻内存。 进程控制原语 用于进程控制的原语一般有:创建进程、撤销进程、挂起进程、激活进程、阻塞进程、唤醒进程以及改变进程优先级等。 创建原语:一个进程可以使用创建原语创建一个新的进程,前者称为父进程,后者称为子进程,子进程又可以创建新的子进程,构成新的子进程,构成新的父子关系。建立进程控制快PCB:先申请一个空闲的PCB区域,将有关信息填入PCB,置该进程为就绪状态,最后将它插入到就绪状态队列中去。 撤销原语:找到要被撤销的进程PCB,将它从所在队列中消去。 阻塞原语:把进程运行状态转换为阻塞状态。首先应中断CPU执行,把CPU的当前状态保存到PCB的现场信息中,把它插入到该事件的等待队列中去。 唤醒原语:京城因为等待时间的发生而处于等待状态,当等待事件完成后,就用唤醒原语将其转换为就绪状态。具体操作过程:在等待队列中找到该进程,置该进程的当前状态为就绪状态,然后将它从等待队列中撤去并插入到就绪队列中排队,等待调度执行。 UNIX类操作系统的进程控制操作 父进程调用fork()函数。 为子进程分配一个空闲的proc结构(进程描述符)。 赋予子进程唯一标识pid。 以一次一页的方式复制父进程用户地址空间。 获得子进程继承的共享资源的指针。 子进程就绪,加入调度队列。 对子进程返回标识符0;向父进程返回子进程的pid。 父进程和新建子进程的区别在于它们有着不同的pid。 fork()函数的执行的特点就像是只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/4
articleCard.readMore

Ajax 对缓存的处理

缓存 浏览器的一次请求需要从服务器获得许多css、img、js等相关的文件,如果每次请求都把相关资源文件加载一次,对带宽、服务器资源、用户等待时间都有严重的损耗,浏览器有做优化处理,就是把css、img、js等文件在第一次请求成功后就在本地保留一个缓存备份,后续的每次请辞u就在本身获得相关的缓存资源文件就可以了,可以明显地加快用户的访问速度。 css、img、js等文件可以缓存,但是动态程序文件例如PHP文件不能进行缓存,即使缓存我们也不要其缓存效果。浏览器对动态程序文件缓存的处理解决: 给请求的地址设置随机数【推荐】; 给动态程序设置header头信息,禁止浏览器对其缓存。 给请求的地址设置随机数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Ajax对缓存的处理</title> <script type="text/javascript"> function f1(){ var xhr = new XMLHttpRequest(); xhr.open('get', './server.php?'+Math.random()); xhr.send(null); } </script> </head> <body> <h2>Ajax对缓存的处理</h2> <input type="button" value="触发" onclick="f1()"> <div id="result"></div> </body> </html> 给动态程序设置header头信息 1 2 3 4 5 6 //设置header头禁止浏览器缓存当前页面 header("Cache-Control:no-cache"); header("Pragma:no-cache"); header("Expires:-1"); $fp = fopen("test.txt","a"); fwrite($fp, "php "); fclose($fp); 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/3
articleCard.readMore

计算机网络 网络技术基础

计算机网络技术的形成与发展 主要知识点记录 “三网融合” :计算机网络、电信网与电视网之间的融合。 UNIX操作系统 集中式、分时、多用户的系统架构 制订了基于Unix的易移植操作系统环境(POSIX)标准 Linux操作系统 常见的发行版本:Rad Hat、Mandrake、Slackware、SUSE、TurbpLinux、Debian、Caldera、Ubuntu,国内的有蓝点、红旗等。 计算机网络的基本概念 计算机网络的定义 计算机网络 用相互共享资源的方式互联起来的自治计算机系统的集合。 特征: 主要目的是资源的共享 互联的计算机是分布在不同地理位置的多台独立的“自治计算机” 联网计算机之间的通讯需要遵循共同的网络协议。 常见计算机名词区分: 术语 英文名称 含义 计算机网络 computer network 用相互共享资源的方式互联起来的自治计算机系统的集合 网络互联 internet 将多个计算机网络互联成大型网络系统的技术 互联网 Internet 指目前广泛应用、覆盖了全世界的大型网络系统 内部的专用网络系统 Intranet 将分布在不同地理位置的部门局域网连接起来的企业内部专用网络。 计算机网络的分类 按照覆盖的地理范围划分为:广域网、城域网、局域网、个人区域网。 广域网技术 WAN又称为远程网,覆盖的地理位置从几十千米到几千千米。 城域网技术 IEEE802协会对其的定义与特征表述: 以光纤为传输介质,能够提供45-150Mbps的高传输速率,支持数据、语音和视频综合业务的数据传输,可以覆盖50~100km的城市范围,实现高速的数据传输。 局域网技术 局域网用于将有限范围的各种计算机,终端与外部设备互联成网。 特征如下: 局域网覆盖有限的地理范围 提供高传输速率(10Mbps~100Gbps)、低误码率的高质量数据传输环境 一般为一个单位所有,易于建立、维护与扩展 决定局域网的三个因素: 拓扑 传输介质 介质访问控制方法 从介质访问控制方法的角度,分为以下两方面: 共享介质式局域网 交换式局域网 个人局域网技术 自身附近范围的个人操作空间。 无线个人局域网络 用无线通信技术实现联网设备之间的通信。 计算机网络的拓扑结构 网络拓朴学知识补充: 拓扑学时间实体抽象成于其大小、形状无关的“点”,将连接实体的路线抽象成“线”。 计算机网络拓扑是通过网中节点与通信线路之间的几何关系表示的网络结构。 计算机网络拓扑是指通信子网的网络拓朴。 基本的网络拓朴有五种:星形、环形、总线型、树形与网形。 星形拓扑 通过点-点通信线路与中心节点连接。 任何两个节点之间的通信都要通过中心节点。 结构简单,易于实现,便于管理。 中心节点是全网性能与可靠性的预测,中心节点瘫痪会造成全网瘫痪。 环形拓扑 通过点-点通信线路连接成闭合的环路。 数据沿一个方向传送。 结构简单、传输延时稳定。 每个节点与连接节点之间的通信线路都会成为网络可靠性的瓶颈。 方便接点的加入和撤出、控制节点数据传输顺序。 总线型拓扑 所有的节点连接到一条作为公共传输介质的纵向,以广播的方式发送和接受数据。 但一个节点利用总线发送数据时,其他节点只能接受数据。 两个或两个以上的节点同时发送数据时,就会出现冲突,照成传输失败。 结构简单,但是得解决多节点访问总线的介质访问控制问题。 树形拓扑 按层次进行连接,信息交换主要在上下节点之间进行,相邻及同层节点之间通常不进行数据交换,或则交换量较小。 树形拓扑可以看成是星形拓扑的一种扩展,树形拓扑网络适用于汇集信息。 网状拓扑 节点之间的连接是任意的,没有规律。 系统可靠性高,广域网一般都采用网状拓扑。 必须采用路由算法、流量控制与拥塞控制方法。 描述计算机网络传输性能的参数 数据传输率 数据传输率是每秒钟传输构成数据的二进制比特数。 单位:比特/秒(bit/second)简称:bps 数据传输率公式: S=1/T(bps) T:发送1比特所需的时间。 常见速率换算公式: 奈奎斯特准则与香农定律 奈奎斯特准则 定义:具有理想低通矩形特性的信道在无噪声情况下的最高速率与带宽关系的公式。 奈奎斯特准则指出:如果间隔为π/w(w=2πf),通过理想通信信道传输窄脉冲信号,则前后码元之间不会产生相互串扰。 对于二进制数据信号的最大数据传输速率Rmax与通信信道带宽B(B=f,单位Hz)的关系可以转化为: $$ R_{\max }=2f\left( bps\right) $$ 此准则描述了有限带宽、五噪声信道的最大数据传输速率与信道带宽之间的关系。 香农定律 定义:在有随机热噪声信道上传输数据信号时,数据传输速率Rmax与信道带宽B,信号与噪声功率比S/N关系。 R max = 13 log 2 1 + S / N 此定律描述了有限带宽、有随机热噪声的最大传输速率与信道带宽、信号噪声功率比之间的关系。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/2
articleCard.readMore

数据结构 非线性结构

树 树的定义 专业定义: 有且只有一个根的节点 有若干的互不相交的子树,这些子树本身也是一棵树 通俗的定义: 树是由节点和边组成 每个节点只有一个父节点,但可以有多个子结点 但有一个节点例外,该节点没有父节点,此节点为根节点。 专业术语: 节点 父节点 子节点 子孙 祖先 堂兄弟 深度:从根节点到最底层节点的层数。(根节点是第一层) 叶子节点:没有子节点的节点 非终端节点:实际就是非叶子节点 度:子结点的个数 树的分类 一般树 任意一个节点的子节点的个数都不受限制 二叉树 任意一个节点的子节点的个数最多为两个,且子节点的位置不可变更 分类: 一般二叉树 满二叉树 在不增加树的层数的前提下,无法再多添加一个节点的二叉树。 完全二叉树 如果只是删除了满二叉树最底层最右边的连续若干个节点。 森林 n 个互不相交的树的集合 树的存储 二叉树的存储 连续存储[完全二叉树] 优点: 查找某个节点的父节点和子结点速度(也包括有没有子结点)很快. 缺点: 耗用的内存空间比较大. 链式存储 一般树的存储 双亲表示法 求父节点方便。 孩子表示法 求子节点方便。 双亲孩子表示法 求父节点和子结点都很方便。 二叉树表示法 把一个普通的树转换成二叉树来存储。 具体转换方法: 设法保证任意一个节点的左指针指向它的第一个孩子,右指针指向它的堂兄弟。 只要能满足此条件,就可以把一个普通的树转换成为二叉树。 一个普通树转换成的二叉树一定没有右子树。 森林的存储 先把森林转化成二叉树,再存储二叉树 树的操作 遍历 先序遍历 先访问根节点 再先序访问左子树 再先序访问右子树 先序遍历顺序:ABDCEFG 先序遍历顺序:ABCDEFLQMNS 中序遍历 中序遍历左子树 再访问根节点 再中序遍历右子树 中序遍历顺序:CDFELBAMSNQA 后序遍历 中序遍历左子树 中序遍历右子树 遍历根节点 后序遍顺序:FLEDCBSNMQA 已知两种遍历序列求原始二叉树 通过先序和中序 或者 中序和后序 可以还原出原始的二叉树 但是通过先序和后序是无法还原出原始的二叉树的 换种说法: 只有通过先序和中序,或通过中序与后序 我们才能可以唯一的确定一个二叉树 示例1(已知先序和中序求后序): 先序:ABCDEFGH 中序:BDCEAFHG 画出图如下: 求后序:DECBHGFA 示例2(已知中序和后序求先序): 中序:BDCEAFHG 后序:BECBHGFA 画出图如下: 求先序:ABCDEFGH 树的应用 树是数据库中数据组织的一种重要形式。 操作系统子父进程的关系本身就是一棵树。 面向对象中的类的继承关系 赫夫曼树 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/12/1
articleCard.readMore

数据结构 静态树表查找算法

算法思想 在使用查找表中有n个关键字,表中的每个关键字被查找的概率都是1/n。在等概率的情况下,使用折半查找算法最优。 然而在某些情况下,查找表中的个关键字被查找的概率都是不同的。例如在UI设计师设计图片的时候,不同的设计师和不同的项目经理需求不同,有些项目经理喜欢暖色调,那么暖色调就会应用的多一些,有的项目经理比较喜欢冷色调,之后你的设计采用冷色调的概率也是比较大的。 在查找表中的关键字不同的情况下,对应于折半查找算法,按照上面的情况并不是最优的查找算法。 静态最优查找二叉树 若在考虑查找成功的情况下,描述查找过程的判定树其带权路径之和(用PH表示)最小时,查找性能最优。 算法思想例子 在查找表中各关键字查找概率不相同的情况下,对于使用折半查找算法,按照之前的方式进行,其查找的效率并不一定是最优的。例如,某查找表中有 5 个关键字,各关键字被查找到的概率分别为:0.1,0.2,0.1,0.4,0.2(全部关键字被查找概率和为 1 ),则根据之前介绍的折半查找算法,建立相应的判定树为(树中各关键字用概率表示): 折半查找查找成功的平均查找长度计算方式: ASL = 判定树中的各节点的查找概率 * 所在层次 相对的平均查找长度为: ASL=0.41 + 0.22 + 0.22 + 0.13 + 0.1*3=1.8 带权路径之和的计算公式:PH = 所有结点所在的层次数 * 每个结点对应的概率值。 但是由于构造最优查找树花费的时间代价较高,而且有一种构造方式创建的判定树的查找性能同最优查找树仅差 1% – 2%,称这种极度接近于最优查找树的二叉树为次优查找树。 次优查找树的构建方法 构建二叉树方式 首先取出查找表中每个关键字及其对应的权值,采用如下公式计算出每个关键字对应的一个值: 其中 wj 表示每个关键字的权值(被查找到的概率),h 表示关键字的个数。 表中有多少关键字,就会有多少个 △Pi ,取其中最小的做为次优查找树的根结点,然后将表中关键字从第 i 个关键字的位置分成两部分,分别作为该根结点的左子树和右子树。同理,左子树和右子树也这么处理,直到最后构成次优查找树完成。 算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 typedef int KeyType;//定义关键字类型 typedef struct{ KeyType key; }ElemType;//定义元素类型 typedef struct BiTNode{ ElemType data; struct BiTNode *lchild, *rchild; }BiTNode, *BiTree; //定义变量 int i; int min; int dw; //创建次优查找树,R数组为查找表,sw数组为存储的各关键字的概率(权值),low和high表示的sw数组中的权值的范围 void SecondOptimal(BiTree T, ElemType R[], float sw[], int low, int high){ //由有序表R[low...high]及其累计权值表sw(其中sw[0]==0)递归构造次优查找树 i = low; min = abs(sw[high] - sw[low]); dw = sw[high] + sw[low - 1]; //选择最小的△Pi值 for (int j = low+1; j <=high; j++){ if (abs(dw-sw[j]-sw[j-1])<min){ i = j; min = abs(dw - sw[j] - sw[j - 1]); } } T = (BiTree)malloc(sizeof(BiTNode)); T->data = R[i];//生成结点(第一次生成根) if (i == low) T->lchild = NULL;//左子树空 else SecondOptimal(T->lchild, R, sw, low, i - 1);//构造左子树 if (i == high) T->rchild = NULL;//右子树空 else SecondOptimal(T->rchild, R, sw, i + 1, high);//构造右子树 } 时间复杂度 由于使用次优查找树和最优查找树的性能差距很小,构造次优查找树的算法的时间复杂度为 O(nlogn),因此可以使用次优查找树表示概率不等的查找表对应的静态查找表(又称为静态树表)。 完整实例演示 例如,一含有 9 个关键字的查找表及其相应权值如下表所示: 则构建次优查找树的过程如下: 首先求出查找表中所有的 △P 的值,找出整棵查找表的根结点: 例如,关键字 F 的 △P 的计算方式为:从 G 到 I 的权值和 – 从 A 到 E 的权值和 = 4+3+5-1-1-2-5-3 = 0。 通过上图左侧表格得知,根结点为 F,以 F 为分界线,左侧子表为 F 结点的左子树,右侧子表为 F 结点的右子树(如上图右侧所示),继续查找左右子树的根结点: 通过重新分别计算左右两查找子表的 △P 的值,得知左子树的根结点为 D,右子树的根结点为 H (如上图右侧所示),以两结点为分界线,继续判断两根结点的左右子树: 通过计算,构建的次优查找树如上图右侧二叉树所示。 后边还有一步,判断关键字 A 和 C 在树中的位置,最后一步两个关键字的权值为 0 ,分别作为结点 B 的左孩子和右孩子,这里不再用图表示。 注意:在建立次优查找树的过程中,由于只根据的各关键字的 P 的值进行构建,没有考虑单个关键字的相应权值的大小,有时会出现根结点的权值比孩子结点的权值还小,此时就需要适当调整两者的位置。 总结 在解决静态树表查找时,使用次优查找树的表示概率不等的查找表对应的静态查找表(又称静态树表)。 Reference 静态树表查找算法及C语言实现 严长生 数据结构 – 算法9.3-9.4 静态树表-构造次优查找树 最优二叉查找树详解(算法导论学习笔记) 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/30
articleCard.readMore

数据结构 键树查找法

定义 键树查找法 又称数字查找树(根节点子树>=2个),键树节点存储的不是某个关键字,而是组成关键字的单个符号。 如果关键字本身是字符串,则键树中的一个结点只包含有一个字符;如果关键字本身是数字,则键树中的一个结点只包含一个数位。每个关键字都是从键树的根结点到叶子结点中经过的所有结点中存储的组合。 例如,当使用键树表示查找表 1 { CAI,CAO,CHEN,LI,LAN,ZHAO } 时,为了查找方便,首先对该查找表中关键字按照首字符进行分类(相同的在一起): 1 { { CAI,CAO,CHEN}, {LI,LAN} , { ZHAO}} 然后继续分割,按照第二个字符、第三个字符、…,最终得到的查找表为: 1 { {CAI,CAO},{ CHEN},{ LI,LAN},{ ZHAO}} 然后使用键树结构表示该查找表,如下图所示: 注意 :键树中叶子结点的特殊符号 $ 为结束符,表示字符串的结束。使用键树表示查找表时,为了方便后期的查找和插入操作,约定键树是有序树(兄弟结点之间自左至右有序),同时约定结束符 ‘$’ 小于任何字符。 键树的存储结构 键树的存储结构有两种,分别是: 双链树 :通过使用树的孩子兄弟表示法来表示键树。 字典树 :以树的多重链表表示键树。 双链树 当使用孩子兄弟表示法表示键树时,树的节点构成以下3部分: symobl域:存储关键字的一个字符。 first域:存储指向孩子节点的指针。 next域:存储指向兄弟节点的指针。 注意:对于叶子结点来说,由于其没有孩子结点,在构建叶子结点时,将 first 指针换成 infoptr 指针,用于指向该关键字。当叶子结点(结束符 ‘$’ 所在的结点)中使用 infoptr 域指向该自身的关键字时,此时的键树被称为双链树。 上图中键树用孩子兄弟表示法表示为双链树时,如下图所示: 提示:每个关键字的叶子结点 $ 的 infoptr 指针指向的是各自的关键字,通过该指针就可以找到各自的关键字的首地址。 双链树查找功能的具体实现 在使用孩子兄弟表示法表示的键树中做查找操作,从树的根结点出发,依次同被查找的关键字进行比对,如果比对成功,进行下一字符的比对;反之,如果比对失败,则跳转至该结点的兄弟结点中去继续比对,直至比对成功或者为找到该关键字。 实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> typedef enum{LEFT,BRANCH}NodeKind;//定义结点的类型,是叶子结点还是其他类型的结点 typedef struct { char a[20];//存储关键字的数组 int num;//关键字长度 }KeysType; //定时结点结构 typedef struct DLTNode{ char symbol;//结点中存储的数据 struct DLTNode *next;//指向兄弟结点的指针 NodeKind *kind;//结点类型 union{//其中两种指针类型每个结点二选一 struct DLTNode* first;//孩子结点 struct DLTNode* infoptr;//叶子结点特有的指针 }; }*DLTree; //查找函数,如果查找成功,返回该关键字的首地址,反则返回NULL。T 为用孩子兄弟表示法表示的键树,K为被查找的关键字。 DLTree SearchChar(DLTree T, KeysType k){ int i = 0; DLTree p = T->first;//首先令指针 P 指向根结点下的含有数据的孩子结点 //如果 p 指针存在,且关键字中比对的位数小于总位数时,就继续比对 while (p && i < k.num){ //如果比对成功,开始下一位的比对 if (k.a[i] == p->symbol){ i++; p = p->first; } //如果该位比对失败,则找该结点的兄弟结点继续比对 else{ p = p->next; } } //比对完成后,如果比对成功,最终 p 指针会指向该关键字的叶子结点 $,通过其自有的 infoptr 指针找到该关键字。 if ( i == k.num){ return p->infoptr; } else{ return NULL; } } Trie树(字典树) 若以树的多重链表表示键树,则树中如同双链树一样,会含有两种结点: 叶子结点:叶子结点中含有关键字域和指向该关键字的指针域; 除叶子结点之外的结点(分支结点):含有 d 个指针域和一个整数域(记录该结点中指针域的个数); d 表示每个结点中存储的关键字的所有可能情况,如果存储的关键字为数字,则 d= 11(0—9,以及 $),同理,如果存储的关键字为字母,则 d=27(26个字母加上结束符 $)。 开始的键树,采用字典树表示如下图所示: 注意:在 Trie 树中,如果从某个结点一直到叶子结点都只有一个孩子,这些结点可以用一个叶子结点来代替,例如 ZHAO 就可以直接作为叶子结点。 字典树查找功能的具体实现 使用 Trie 树进行查找时,从根结点出发,沿和对应关键字中的值相对应的指针逐层向下走,一直到叶子结点,如果全部对应相等,则查找成功;反之,则查找失败。 实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 typedef enum{LEFT,BRANCH}NodeKind;//定义结点类型 typedef struct {//定义存储关键字的数组 char a[20]; int num; }KeysType; //定义结点结构 typedef struct TrieNode{ NodeKind kind;//结点类型 union{ struct { KeysType k; struct TrieNode *infoptr; }lf;//叶子结点 struct{ struct TrieNode *ptr[27]; int num; }bh;//分支结点 }; }*TrieTree; //求字符 a 在字母表中的位置 int ord(char a){ int b = a - 'A'+1; return b; } //查找函数 TrieTree SearchTrie(TrieTree T, KeysType K){ int i=0; TrieTree p = T; while (i < K.num){ if (p && p->kind==BRANCH && p->bh.ptr[ord(K.a[i])]){ i++; p = p->bh.ptr[ord(K.a[i])]; } else{ break; } } if (p){ return p->lf.infoptr; } return p; } 使用 Trie 树进行查找的过程实际上是走了一条从根结点到叶子结点的路径,所以使用 Trie 进行的查找效率取决于该树的深度 总结 双链树和字典树是键树的两种表示方法,各有各的特点,具体使用哪种方式表示键树,需要根据实际情况而定。例如,若键树中结点的孩子结点较多,则使用字典树较双链树更为合适。 本博文从 键树查找法(双链树和字典树)及C语言实现 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/29
articleCard.readMore

数据结构 哈夫曼树

与哈夫曼树相关名词 路径:在一棵树中,一个节点到另一个节点之间的通路。 路径长度:在一条路径中,每经过一个节点,路径都要加1. 节点的权:给每一个节点赋予一个新的数值。 节点的带全路径长度:从根节点到该节点之间的路径长度与该节点的乘积。 树的带权路径长度为树中所有叶子结点的带权路径长度之和。通常记作 “WPL” 。 此树的带全路径长度为: WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3 哈夫曼树 当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”,有时也叫“赫夫曼树”或者“哈夫曼树”。 遵循一个原则,那就是:权重越大的结点离树根越近。 构建哈夫曼树 对于给定的有各自权值的 n 个结点,构建哈夫曼树有一个行之有效的办法: 在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和; 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推; 重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。 (A)给定了四个结点a,b,c,d,权值分别为7,5,2,4; 第一步如(B)所示,找出现有权值中最小的两个,2 和 4 ,相应的结点 c 和 d 构建一个新的二叉树,树根的权值为 2 + 4 = 6,同时将原有权值中的 2 和 4 删掉,将新的权值 6 加入; 进入(C),重复之前的步骤。直到(D)中,所有的结点构建成了一个全新的二叉树,这就是哈夫曼树。 哈夫曼树的节点结构 构建哈夫曼树时,首先需要确定树中结点的构成。由于哈夫曼树的构建是从叶子结点开始,不断地构建新的父结点,直至树根,所以结点中应包含指向父结点的指针。但是在使用哈夫曼树时是从树根开始,根据需求遍历树中的结点,因此每个结点需要有指向其左孩子和右孩子的指针。 代码结构: 1 2 3 4 5 //哈夫曼树结点结构 typedef struct { int weight;//结点权重 int parent, left, right;//父结点、左孩子、右孩子在数组中的位置下标 }HTNode, *HuffmanTree; 哈夫曼树中的查找算法 构建哈夫曼树时,需要每次根据各个结点的权重值,筛选出其中值最小的两个结点,然后构建二叉树。 查找权重值最小的两个结点的思想是:从树组起始位置开始,首先找到两个无父结点的结点(说明还未使用其构建成树),然后和后续无父结点的结点依次做比较,有两种情况需要考虑: 如果比两个结点中较小的那个还小,就保留这个结点,删除原来较大的结点; 如果介于两个结点权重值之间,替换原来较大的结点; 代码结构: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 //HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置 void Select(HuffmanTree HT, int end, int *s1, int *s2) { int min1, min2; //遍历数组初始下标为 1 int i = 1; //找到还没构建树的结点 while(HT[i].parent != 0 && i <= end){ i++; } min1 = HT[i].weight; *s1 = i; i++; while(HT[i].parent != 0 && i <= end){ i++; } //对找到的两个结点比较大小,min2为大的,min1为小的 if(HT[i].weight < min1){ min2 = min1; *s2 = *s1; min1 = HT[i].weight; *s1 = i; }else{ min2 = HT[i].weight; *s2 = i; } //两个结点和后续的所有未构建成树的结点做比较 for(int j=i+1; j <= end; j++) { //如果有父结点,直接跳过,进行下一个 if(HT[j].parent != 0){ continue; } //如果比最小的还小,将min2=min1,min1赋值新的结点的下标 if(HT[j].weight < min1){ min2 = min1; min1 = HT[j].weight; *s2 = *s1; *s1 = j; } //如果介于两者之间,min2赋值为新的结点的位置下标 else if(HT[j].weight >= min1 && HT[j].weight < min2){ min2 = HT[j].weight; *s2 = j; } } } 注意:s1和s2传入的是实参的地址,所以函数运行完成后,实参中存放的自然就是哈夫曼树中权重值最小的两个结点在数组中的位置。 哈夫曼编码 哈夫曼编码就是在哈夫曼树的基础上构建的,这种编码方式最大的优点就是用最少的字符包含最多的信息内容。 根据发送信息的内容,通过统计文本中相同字符的个数作为每个字符的权值,建立哈夫曼树。对于树中的每一个子树,统一规定其左孩子标记为 0 ,右孩子标记为 1 。这样,用到哪个字符时,从哈夫曼树的根结点开始,依次写出经过结点的标记,最终得到的就是该结点的哈夫曼编码。 文本中字符出现的次数越多,在哈夫曼树中的体现就是越接近树根。编码的长度越短。 如图所示,字符 a 用到的次数最多,其次是字符 b 。字符 a 在哈夫曼编码是 0 ,字符 b 编码为 10 ,字符 c 的编码为 110 ,字符 d 的编码为 111 。 使用程序求哈夫曼编码有两种方法: 从叶子结点一直找到根结点,逆向记录途中经过的标记。例如,图中字符 c 的哈夫曼编码从结点 c 开始一直找到根结点,结果为:0 1 1 ,所以字符 c 的哈夫曼编码为:1 1 0(逆序输出)。 实现代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 //HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数 void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){ *HC = (HuffmanCode) malloc((n+1) * sizeof(char *)); char *cd = (char *)malloc(n*sizeof(char)); //存放结点哈夫曼编码的字符串数组 cd[n-1] = '\0';//字符串结束符 for(int i=1; i<=n; i++){ //从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放 int start = n-1; //当前结点在数组中的位置 int c = i; //当前结点的父结点在数组中的位置 int j = HT[i].parent; // 一直寻找到根结点 while(j != 0){ // 如果该结点是父结点的左孩子则对应路径编码为0,否则为右孩子编码为1 if(HT[j].left == c) cd[--start] = '0'; else cd[--start] = '1'; //以父结点为孩子结点,继续朝树根的方向遍历 c = j; j = HT[j].parent; } //跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码 (*HC)[i] = (char *)malloc((n-start)*sizeof(char)); strcpy((*HC)[i], &cd[start]); } //使用malloc申请的cd动态数组需要手动释放 free(cd); } 从根结点出发,一直到叶子结点,记录途中经过的标记。例如,求图中字符 c 的哈夫曼编码,就从根结点开始,依次为:1 1 0。 实现代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 //HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数 void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){ *HC = (HuffmanCode) malloc((n+1) * sizeof(char *)); int m=2*n-1; int p=m; int cdlen=0; char *cd = (char *)malloc(n*sizeof(char)); //将各个结点的权重用于记录访问结点的次数,首先初始化为0 for (int i=1; i<=m; i++) { HT[i].weight=0; } //一开始 p 初始化为 m,也就是从树根开始。一直到p为0 while (p) { //如果当前结点一次没有访问,进入这个if语句 if (HT[p].weight==0) { HT[p].weight=1;//重置访问次数为1 //如果有左孩子,则访问左孩子,并且存储走过的标记为0 if (HT[p].left!=0) { p=HT[p].left; cd[cdlen++]='0'; } //当前结点没有左孩子,也没有右孩子,说明为叶子结点,直接记录哈夫曼编码 else if(HT[p].right==0){ (*HC)[p]=(char*)malloc((cdlen+1)*sizeof(char)); cd[cdlen]='\0'; strcpy((*HC)[p], cd); } } //如果weight为1,说明访问过一次,即是从其左孩子返回的 else if(HT[p].weight==1){ HT[p].weight=2;//设置访问次数为2 //如果有右孩子,遍历右孩子,记录标记值 1 if (HT[p].right!=0) { p=HT[p].right; cd[cdlen++]='1'; } } //如果访问次数为 2,说明左右孩子都遍历完了,返回父结点 else{ HT[p].weight=0; p=HT[p].parent; --cdlen; } } } 遍历哈夫曼树程序设计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include <stdlib.h> #include <stdio.h> #include <string.h> //哈夫曼树结点结构 typedef struct { int weight;//结点权重 int parent, left, right;//父结点、左孩子、右孩子在数组中的位置下标 }HTNode, *HuffmanTree; //动态二维数组,存储哈夫曼编码 typedef char ** HuffmanCode; //HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置 void Select(HuffmanTree HT, int end, int *s1, int *s2) { int min1, min2; //遍历数组初始下标为 1 int i = 1; //找到还没构建树的结点 while(HT[i].parent != 0 && i <= end){ i++; } min1 = HT[i].weight; *s1 = i; i++; while(HT[i].parent != 0 && i <= end){ i++; } //对找到的两个结点比较大小,min2为大的,min1为小的 if(HT[i].weight < min1){ min2 = min1; *s2 = *s1; min1 = HT[i].weight; *s1 = i; }else{ min2 = HT[i].weight; *s2 = i; } //两个结点和后续的所有未构建成树的结点做比较 for(int j=i+1; j <= end; j++) { //如果有父结点,直接跳过,进行下一个 if(HT[j].parent != 0){ continue; } //如果比最小的还小,将min2=min1,min1赋值新的结点的下标 if(HT[j].weight < min1){ min2 = min1; min1 = HT[j].weight; *s2 = *s1; *s1 = j; } //如果介于两者之间,min2赋值为新的结点的位置下标 else if(HT[j].weight >= min1 && HT[j].weight < min2){ min2 = HT[j].weight; *s2 = j; } } } //HT为地址传递的存储哈夫曼树的数组,w为存储结点权重值的数组,n为结点个数 void CreateHuffmanTree(HuffmanTree *HT, int *w, int n) { if(n<=1) return; // 如果只有一个编码就相当于0 int m = 2*n-1; // 哈夫曼树总节点数,n就是叶子结点 *HT = (HuffmanTree) malloc((m+1) * sizeof(HTNode)); // 0号位置不用 HuffmanTree p = *HT; // 初始化哈夫曼树中的所有结点 for(int i = 1; i <= n; i++) { (p+i)->weight = *(w+i-1); (p+i)->parent = 0; (p+i)->left = 0; (p+i)->right = 0; } //从树组的下标 n+1 开始初始化哈夫曼树中除叶子结点外的结点 for(int i = n+1; i <= m; i++) { (p+i)->weight = 0; (p+i)->parent = 0; (p+i)->left = 0; (p+i)->right = 0; } //构建哈夫曼树 for(int i = n+1; i <= m; i++) { int s1, s2; Select(*HT, i-1, &s1, &s2); (*HT)[s1].parent = (*HT)[s2].parent = i; (*HT)[i].left = s1; (*HT)[i].right = s2; (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight; } } //HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数 void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){ *HC = (HuffmanCode) malloc((n+1) * sizeof(char *)); char *cd = (char *)malloc(n*sizeof(char)); //存放结点哈夫曼编码的字符串数组 cd[n-1] = '\0';//字符串结束符 for(int i=1; i<=n; i++){ //从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放 int start = n-1; //当前结点在数组中的位置 int c = i; //当前结点的父结点在数组中的位置 int j = HT[i].parent; // 一直寻找到根结点 while(j != 0){ // 如果该结点是父结点的左孩子则对应路径编码为0,否则为右孩子编码为1 if(HT[j].left == c) cd[--start] = '0'; else cd[--start] = '1'; //以父结点为孩子结点,继续朝树根的方向遍历 c = j; j = HT[j].parent; } //跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码 (*HC)[i] = (char *)malloc((n-start)*sizeof(char)); strcpy((*HC)[i], &cd[start]); } //使用malloc申请的cd动态数组需要手动释放 free(cd); } //打印哈夫曼编码的函数 void PrintHuffmanCode(HuffmanCode htable,int *w,int n) { printf("Huffman code : \n"); for(int i = 1; i <= n; i++) printf("%d code = %s\n",w[i-1], htable[i]); } int main(void) { int w[5] = {2, 8, 7, 6, 5}; int n = 5; HuffmanTree htree; HuffmanCode htable; CreateHuffmanTree(&htree, w, n); HuffmanCoding(htree, &htable, n); PrintHuffmanCode(htable, n); return 0; } 本节中介绍了两种遍历哈夫曼树获得哈夫曼编码的方法,同时也给出了各自完整的实现代码的函数,在完整代码中使用的是第一种逆序遍历哈夫曼树的方法。 本博文从 哈夫曼树(赫夫曼树、最优树)及C语言实现 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/28
articleCard.readMore

数据结构 分块查找法

算法定义 分块查找,也叫索引顺序查找,算法实现除了需要查找表本身之外,还需要根据查找表建立一个索引表。 建立的索引表要求按照关键字进行升序排序,查找表要么整体有序,要么分块有序。 分块有序:指的是第二个子表中所有关键字都要大于第一个子表中的最大关键字,第三个子表的所有关键字都要大于第二个子表中的最大关键字,依次类推。 块(子表)中各关键字的具体顺序,根据各自可能会被查找到的概率而定。如果各关键字被查找到的概率是相等的,那么可以随机存放;否则可按照被查找概率进行降序排序,以提高算法运行效率。 算法原理 所有前期准备工作完成后,开始在此基础上进行分块查找。分块查找的过程分为两步进行: 确定要查找的关键字可能存在的具体块(子表); 在具体的块中进行顺序查找。 方法描述 将n个数据元素”按块有序”划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须”按块有序”;即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,……。 算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <stdlib.h> struct index { //定义块的结构 int key; int start; } newIndex[3]; //定义结构体数组 int search(int key, int a[]){ int i, startValue; i = 0; while (i<3 && key>newIndex[i].key) { //确定在哪个块中,遍历每个块,确定key在哪个块中 i++; } if (i>=3) { //大于分得的块数,则返回0 return -1; } startValue = newIndex[i].start; //startValue等于块范围的起始值 while (startValue <= startValue+5 && a[startValue]!=key) { startValue++; } if (startValue>startValue+5) { //如果大于块范围的结束值,则说明没有要查找的数 return -1; } return startValue; } int cmp(const void *a,const void* b){ return (*(struct index*)a).key>(*(struct index*)b).key?1:-1; } int main(){ int i, j=-1, k, key; int a[] = {33,42,44,38,24,48, 22,12,13,8,9,20, 60,58,74,49,86,53}; //确认模块的起始值和最大值 for (i=0; i<3; i++) { newIndex[i].start = j+1; //确定每个块范围的起始值 j += 6; for (int k=newIndex[i].start; k<=j; k++) { if (newIndex[i].key<a[k]) { newIndex[i].key=a[k]; } } } //对结构体按照 key 值进行排序 qsort(newIndex,3, sizeof(newIndex[0]), cmp); //输入要查询的数,并调用函数进行查找 printf("请输入您想要查找的数:\n"); scanf("%d", &key); k = search(key, a); //输出查找的结果 if (k>0) { printf("查找成功!您要找的数在数组中的位置是:%d\n",k+1); }else{ printf("查找失败!您要找的数不在数组中。\n"); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/27
articleCard.readMore

数据结构 插入排序算法

插入排序算法介绍 插入排序算法是所有排序方法中最简单的一种算法,其主要的实现思想是将数据按照一定的顺序一个一个的插入到有序的表中,最终得到的序列就是已经排序好的数据。 直接插入排序是插入排序算法中的一种,采用的方法是:在添加新的记录时,使用顺序查找的方式找到其要插入的位置,然后将新记录插入。 例如采用直接插入排序算法将无序表 1 {3,1,7,5,2,4,9,6} 进行升序排序的过程为: 首先考虑记录 3 ,由于插入排序刚开始,有序表中没有任何记录,所以 3 可以直接添加到有序表中; 向有序表中插入记录 1 时,同有序表中记录 3 进行比较,1<3,所以插入到记录 3 的左侧; 向有序表插入记录 7 时,同有序表中记录 3 进行比较,3<7,所以插入到记录 3 的右侧; 向有序表中插入记录 5 时,同有序表中记录 7 进行比较,5<7,同时 5>3,所以插入到 3 和 7 中间; 向有序表插入记录 2 时,同有序表中记录 7进行比较,2<7,再同 5,3,1分别进行比较,最终确定 2 位于 1 和 3 中间; 照此规律,依次将无序表中的记录 4,9 和 6插入到有序表中。 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> //自定义的输出函数 void print(int a[], int n ,int i){ printf("%d:",i); for(int j=0; j<8; j++){ printf("%d",a[j]); } printf("\n"); } //直接插入排序函数 void InsertSort(int a[], int n) { for(int i= 1; i<n; i++){ if(a[i] < a[i-1]){//若第 i 个元素大于 i-1 元素则直接插入;反之,需要找到适当的插入位置后在插入。 int j= i-1; int x = a[i]; while(j>-1 && x < a[j]){ //采用顺序查找方式找到插入的位置,在查找的同时,将数组中的元素进行后移操作,给插入元素腾出空间 a[j+1] = a[j]; j--; } a[j+1] = x; //插入到正确位置 } print(a,n,i);//打印每次排序后的结果 } } int main(){ int a[8] = {3,1,7,5,2,4,9,6}; InsertSort(a,8); return 0; } 运行结果为: 1 2 3 4 5 6 7 1:13752496 2:13752496 3:13572496 4:12357496 5:12345796 6:12345796 7:12345679 时间复杂度 直接插入排序算法本身比较简洁,容易实现,该算法的时间复杂度为O(n2)。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/27
articleCard.readMore

数据结构 二叉排序树

定义 二叉排序树要么是空二叉树,要么具有如下特点: 二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点的值; 二叉排序树中,如果其根结点有右子树,那么右子树上所有结点的值都大小根结点的值; 二叉排序树的左右子树也要求都是二叉排序树; 例如,下图就是一个二叉排序树: 二叉排序树关键字的操作 使用二叉排序树查找关键字 二叉排序树中查找某关键字时,查找过程类似于次优二叉树,在二叉排序树不为空树的前提下,首先将被查找值同树的根结点进行比较,会有 3 种不同的结果: 如果相等,查找成功; 如果比较结果为根结点的关键字值较大,则说明该关键字可能存在其左子树中; 如果比较结果为根结点的关键字值较小,则说明该关键字可能存在其右子树中; 实现函数为:(运用递归的方法) 1 2 3 4 5 6 7 8 9 10 11 12 BiTree SearchBST(BiTree T,KeyType key){ //如果递归过程中 T 为空,则查找结果,返回NULL;或者查找成功,返回指向该关键字的指针 if (!T || key==T->data) { return T; }else if(key<T->data){ //递归遍历其左孩子 return SearchBST(T->lchild, key); }else{ //递归遍历其右孩子 return SearchBST(T->rchild, key); } } 二叉排序树本身是动态查找表的一种表示形式,有时会在查找过程中插入或者删除表中元素,当因为查找失败而需要插入数据元素时,该数据元素的插入位置一定位于二叉排序树的叶子结点,并且一定是查找失败时访问的最后一个结点的左孩子或者右孩子。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 c++ //插入函数 BOOL InsertBST(BiTree T,ElemType e){ BiTree p=NULL; //如果查找不成功,需做插入操作 if (!SearchBST(T, e,NULL,&p)) { //初始化插入结点 BiTree s=(BiTree)malloc(sizeof(BiTree)); s->data=e; s->lchild=s->rchild=NULL; //如果 p 为NULL,说明该二叉排序树为空树,此时插入的结点为整棵树的根结点 if (!p) { T=s; } //如果 p 不为 NULL,则 p 指向的为查找失败的最后一个叶子结点,只需要通过比较 p 和 e 的值确定 s 到底是 p 的左孩子还是右孩子 else if(e<p->data){ p->lchild=s; }else{ p->rchild=s; } return true; } //如果查找成功,不需要做插入操作,插入失败 return false; } 例如,假设原二叉排序树为空树,在对动态查找表 {3,5,7,2,1} 做查找以及插入操作时,可以构建出一个含有表中所有关键字的二叉排序树,过程如图 所示: 通过不断的查找和插入操作,最终构建的二叉排序树如图 (5) 所示。当使用中序遍历算法遍历二叉排序树时,得到的序列为:1 2 3 5 7 ,为有序序列。 一个无序序列可以通过构建一棵二叉排序树,从而变成一个有序序列。 删除关键字 在查找过程中,如果在使用二叉排序树表示的动态查找表中删除某个数据元素时,需要在成功删除该结点的同时,依旧使这棵树为二叉排序树。 假设要删除的为结点 p,则对于二叉排序树来说,需要根据结点 p 所在不同的位置作不同的操作,有以下 3 种可能: 结点 p 为叶子结点,此时只需要删除该结点,并修改其双亲结点的指针即可; 结点 p 只有左子树或者只有右子树,此时只需要将其左子树或者右子树直接变为结点 p 双亲结点的左子树即可; 结点 p 左右子树都有,此时有两种处理方式: 令结点 p 的左子树为其双亲结点的左子树;结点 p 的右子树为其自身直接前驱结点的右子树,如图所示; 用结点 p 的直接前驱(或直接后继)来代替结点 p,同时在二叉排序树中对其直接前驱(或直接后继)做删除操作。如图为使用直接前驱代替结点 p: 在对左图进行中序遍历时,得到的结点 p 的直接前驱结点为结点 s,所以直接用结点 s 覆盖结点 p,由于结点 s 还有左孩子,根据第 2 条规则,直接将其变为双亲结点的右孩子。 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 #include<stdio.h> #include<stdlib.h> #define TRUE 1 #define FALSE 0 #define ElemType int #define KeyType int /* 二叉排序树的节点结构定义 */ typedef struct BiTNode { int data; struct BiTNode *lchild, *rchild; } BiTNode, *BiTree; //二叉排序树查找算法 int SearchBST(BiTree T,KeyType key,BiTree f,BiTree *p){ //如果 T 指针为空,说明查找失败,令 p 指针指向查找过程中最后一个叶子结点,并返回查找失败的信息 if (!T){ *p=f; return FALSE; } //如果相等,令 p 指针指向该关键字,并返回查找成功信息 else if(key==T->data){ *p=T; return TRUE; } //如果 key 值比 T 根结点的值小,则查找其左子树;反之,查找其右子树 else if(key<T->data){ return SearchBST(T->lchild,key,T,p); }else{ return SearchBST(T->rchild,key,T,p); } } int InsertBST(BiTree *T,ElemType e){ BiTree p=NULL; //如果查找不成功,需做插入操作 if (!SearchBST((*T), e,NULL,&p)) { //初始化插入结点 BiTree s=(BiTree)malloc(sizeof(BiTree)); s->data=e; s->lchild=s->rchild=NULL; //如果 p 为NULL,说明该二叉排序树为空树,此时插入的结点为整棵树的根结点 if (!p) { *T=s; } //如果 p 不为 NULL,则 p 指向的为查找失败的最后一个叶子结点,只需要通过比较 p 和 e 的值确定 s 到底是 p 的左孩子还是右孩子 else if(e < p->data){ p->lchild=s; }else{ p->rchild=s; } return TRUE; } //如果查找成功,不需要做插入操作,插入失败 return FALSE; } //删除函数 int Delete(BiTree *p) { BiTree q, s; //情况 1,结点 p 本身为叶子结点,直接删除即可 if(!(*p)->lchild && !(*p)->rchild){ *p = NULL; } else if(!(*p)->lchild){ //左子树为空,只需用结点 p 的右子树根结点代替结点 p 即可; q = *p; *p = (*p)->rchild; free(q); } else if(!(*p)->rchild){//右子树为空,只需用结点 p 的左子树根结点代替结点 p 即可; q = *p; *p = (*p)->lchild;//这里不是指针 *p 指向左子树,而是将左子树存储的结点的地址赋值给指针变量 p free(q); } else{//左右子树均不为空,采用第 2 种方式 q = *p; s = (*p)->lchild; //遍历,找到结点 p 的直接前驱 while(s->rchild) { q = s; s = s->rchild; } //直接改变结点 p 的值 (*p)->data = s->data; //判断结点 p 的左子树 s 是否有右子树,分为两种情况讨论 if( q != *p ){ q->rchild = s->lchild;//若有,则在删除直接前驱结点的同时,令前驱的左孩子结点改为 q 指向结点的孩子结点 }else{ q->lchild = s->lchild;//否则,直接将左子树上移即可 } free(s); } return TRUE; } int DeleteBST(BiTree *T, int key) { if( !(*T)){//不存在关键字等于key的数据元素 return FALSE; } else { if( key == (*T)->data ){ Delete(T); return TRUE; } else if( key < (*T)->data){ //使用递归的方式 return DeleteBST(&(*T)->lchild, key); } else{ return DeleteBST(&(*T)->rchild, key); } } } void order(BiTree t)//中序输出 { if(t == NULL){ return ; } order(t->lchild); printf("%d ", t->data); order(t->rchild); } int main() { int i; int a[5] = {3,4,2,5,9}; BiTree T = NULL; for( i = 0; i < 5; i++ ){ InsertBST(&T, a[i]); } printf("中序遍历二叉排序树:\n"); order(T); printf("\n"); printf("删除3后,中序遍历二叉排序树:\n"); DeleteBST(&T,3); order(T); } 运行结果: 中序遍历二叉排序树: 2 3 4 5 9 删除3后,中序遍历二叉排序树: 2 4 5 9 总结 使用二叉排序树在查找表中做查找操作的时间复杂度同建立的二叉树本身的结构有关。即使查找表中各数据元素完全相同,但是不同的排列顺序,构建出的二叉排序树大不相同。 例如:查找表 {45,24,53,12,37,93} 和表 {12,24,37,45,53,93} 各自构建的二叉排序树图下图所示: 使用二叉排序树实现动态查找操作的过程,实际上就是从二叉排序树的根结点到查找元素结点的过程,所以时间复杂度同被查找元素所在的树的深度(层次数)有关。 本博文从 二叉排序树(二叉查找树)及C语言实现 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/26
articleCard.readMore

数据结构 B加树

B+树定义 一颗 m 阶的 B+树和 m 阶的 B-树的差异在于: 有 n 棵子树的结点中含有 n 个关键字; 在上一节中,在 B-树中的每个结点关键字个数 n 的取值范围为⌈m/2⌉ -1≤n≤m-1,而在 B+树中每个结点中关键字个数 n 的取值范围为:⌈m/2⌉≤n≤m。 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。 所有的非终端结点(非叶子结点)可以看成是索引部分,结点中仅含有其子树(根结点)中的最大(或最小)关键字。 例如,下图中所示的就是一棵深度为 4 的 3 阶 B+树: 如上图所示,B+树中含有两个头指针,一个指向整棵树的根结点,另一个指向关键字最小的叶子结点。同时所有的叶子结点依据其关键字的大小自小而大顺序链接,所有的叶子结点构成了一个 sqt 指针为头指针的链表。 所有,B+树可以进行两种查找运算:一种是利用 sqt 链表做顺序查找,另一种是从树的根结点开始,进行类似于二分查找的查找方式。 在 B+树中,所有非终端结点都相当于是终端结点的索引,而所有的关键字都存放在终端结点中,所有在从根结点出发做查找操作时,如果非终端结点上的关键字恰好等于给定值,此时并不算查找完成,而是要继续向下直到叶子结点。 B+树的查找操作,无论查找成功与否,每次查找操作都是走了一条从根结点到叶子结点的路径。 B+树中插入关键字 在B+树中插入关键字时,需要注意以下几点: 插入的操作全部都在叶子结点上进行,且不能破坏关键字自小而大的顺序; 由于 B+树中各结点中存储的关键字的个数有明确的范围,做插入操作可能会出现结点中关键字个数超过阶数的情况,此时需要将该结点进行“分裂”; B+树中做插入关键字的操作,有以下 3 种情况: 若被插入关键字所在的结点,其含有关键字数目小于阶数 M,则直接插入结束; 例如,在上图中插入关键字13,其结果如下图所示: 若被插入关键字所在的结点,其含有关键字数目等于阶数 M,则需要将该结点分裂为两个结点,一个结点包含⌊M/2⌋,另一个结点包含⌈M/2⌉。同时,将⌈M/2⌉的关键字上移至其双亲结点。假设其双亲结点中包含的关键字个数小于 M,则插入操作完成。 例如,在开始的图的基础上插入关键字 95,其插入后的 B+树如下图所示: 在第 2 情况中,如果上移操作导致其双亲结点中关键字个数大于 M,则应继续分裂其双亲结点。 例如,在开始的图的B+树中插入关键字 40,则插入后的 B+树如下图所示: 注意:如果插入的关键字比当前结点中的最大值还大,破坏了B+树中从根结点到当前结点的所有索引值,此时需要及时修正后,再做其他操作。例如,在图 1 的 B+树种插入关键字 100,由于其值比 97 还大,插入之后,从根结点到该结点经过的所有结点中的所有值都要由 97 改为 100。改完之后再做分裂操作。 B+树中删除关键字 在 B+树中删除关键字时,有以下几种情况: 找到存储有该关键字所在的结点时,由于该结点中关键字个数大于⌈M/2⌉,做删除操作不会破坏 B+树,则可以直接删除; 当删除某结点中最大或者最小的关键字,就会涉及到更改其双亲结点一直到根结点中所有索引值的更改; 当删除该关键字,导致当前结点中关键字个数小于⌈M/2⌉,若其兄弟结点中含有多余的关键字,可以从兄弟结点中借关键字完成删除操作; 第 3 种情况中,如果其兄弟结点没有多余的关键字,则需要同其兄弟结点进行合并; 当进行合并时,可能会产生因合并使其双亲结点破坏 B+树的结构,需要依照以上规律处理其双亲结点。 总之,在 B+树中做删除关键字的操作,采取如下的步骤: 删除该关键字,如果不破坏 B+树本身的性质,直接完成操作; 如果删除操作导致其该结点中最大(或最小)值改变,则应相应改动其父结点中的索引值; 在删除关键字后,如果导致其结点中关键字个数不足,有两种方法:一种是向兄弟结点去借,另外一种是同兄弟结点合并。(注意这两种方式有时需要更改其父结点中的索引值。) 本博文从 B+树 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/23
articleCard.readMore

数据结构 B-树

B-树定义 B-树,有时又写为B_树(其中的“-”或者“-_只是连字符,并不读作“B减树”),一颗 m 阶的 B-树,或者本身是空树,否则必须满足以下特性: 树中每个结点至多有 m 棵子树; 若根结点不是叶子结点,则至少有两棵子树; 除根之外的所有非终端结点至少有棵子树; 所有的非终端结点中包含下列信息数据:(n,A0,K1,A1,K2,A2,…,Kn,An); n 表示结点中包含的关键字的个数,取值范围是:⌈m/2⌉-1≤ n ≤m-1。Ki (i 从 1 到 n)为关键字,且 Ki < Ki+1 ;Ai 代表指向子树根结点的指针,且指针 Ai-1 所指的子树中所有结点的关键字都小于 Ki,An 所指子树中所有的结点的关键字都大于 Kn。 如图所示,当前结点中有 4 个关键字,之间的关系为:K1<K2<k3<K4。同时对于 A0 指针指向的子树中的所有关键字来说,其值都要比 K1 小;而 A1 指向的子树中的所有的关键字的值,都比 K1 大,但是都要比 K2 小。 所有的叶子结点都出现在同一层次,实际上这些结点都不存在,指向这些结点的指针都为 NULL; 例如图所示就是一棵 4 阶的 B-树,这棵树的深度为 4 : 在使用 B-树进行查找操作时,例如在如上图所示的 B-树中查找关键字 47 的过程为: 从整棵树的根结点开始,由于根结点只有一个关键字 35,且 35 < 47 ,所以如果 47 存在于这棵树中,肯定位于 A1 指针指向的右子树中; 然后顺着指针找到存有关键字 43 和 78 的结点,由于 43 < 47 < 78,所以如果 47 存在,肯定位于 A1 所指的子树中; 然后找到存有 47、53 和 64 三个关键字的结点,最终找到 47 ,查找操作结束; 以上图中的 B-树为例,若查找到深度为 3 的结点还没结束,则会进入叶子结点,但是由于叶子结点本身不存储任何信息,全部为 NULL,所以查找失败。 B-树插入关键字 B-树也是从空树开始,通过不断地插入新的数据元素构建的。B-树在插入新的数据元素时并不是每次都向树中插入新的结点。 因为对于 m 阶的 B-树来说,在定义中规定所有的非终端结点(终端结点即叶子结点,其关键字个数为 0)中包含关键字的个数的范围是[⌈m/2⌉-1,m-1],所以在插入新的数据元素时,首先向最底层的某个非终端结点中添加,如果该结点中的关键字个数没有超过 m-1,则直接插入成功,否则还需要继续对该结点进行处理。 假设现在下图的基础上插入 4 个关键字 30、26、85 和 7: 插入关键字 30 :从根结点开始,由于 30 < 45,所以要插入到以 b 结点为根结点的子树中,再由于 24 < 30,插入到以 d 结点为根结点的子树中,由于 d 结点中的关键字个数小于 m-1=2,所以可以将关键字 30 直接插入到 d 结点中。结果如下图所示: 插入关键字 26: 从根结点开始,经过逐个比较,最终判定 26 还是插入到 d 结点中,但是由于 d 结点中关键字的个数超过了 2,所以需要做如下操作: 关键字 37 及其左右两个指针存储到新的结点中,假设为 d’ 结点; 关键字 30 存储到其双亲结点 b 中,同时设置关键字 30 右侧的指针指向 d’; 经过以上操作后,插入 26 后的B-树为: 插入关键字 85: 从根结点开始,经过逐个比较,最终判定插入到 g 结点中,同样需要对 g 做分裂操作: 关键字 85 及其左右两个指针存储到新的结点中,假设为 g’ 结点; 关键字 70 存储到其双亲结点 e 中,同时设置 70 的右侧指针指向 g’ ; 经过以上操作后,插入 85 后的结果图为: 上图中,由于关键字 70 调整到其双亲结点中,使得其 e 结点中的关键字个数超过了 2,所以还需进一步调整: 将 90 及其左右指针存储到一个新的结点中,假设为 e’ 结点; 关键字 70 存储到其双亲结点 a 中,同时其右侧指针指向 e’ ; 最终插入关键字 85 后的 B-树为: 通过上边的例子,可以总结出一下结论:在构建 B-树的过程中,假设 p 结点中已经有 m-1 个关键字,当再插入一个关键字之后,此结点分裂为两个结点,如下图所示: 提示:如上图所示,结点分裂为两个结点的同时,还分裂出来一个关键字 K⌈m/2⌉,存储到其双亲结点中。 B-树删除关键字 在 B-树种删除关键字时,首先前提是找到该关键字所在结点,在做删除操作的时候分为两种情况,一种情况是删除结点为 B-树的非终端结点(不处在最后一层);另一种情况是删除结点为 B-树最后一层的非终端结点。 例如上边插入关键字的原始图来说,关键字 24、45、53、90属于不处在最后一层的非终端结点,关键字 3、12、37等同属于最后一层的非终端结点。 如果该结点为非终端结点且不处在最后一层,假设用 Ki 表示,则只需要找到指针 Ai 所指子树中最小的一个关键字代替 Ki,同时将该最小的关键字删除即可。 例如上边插入关键字的原始图中,如果要删除关键字 45 ,只需要使用关键字 50 代替 45 ,同时删除 f 结点中的 50 即可。 如果该结点为最后一层的非终端结点,有下列 3 种可能: 被删关键字所在结点中的关键字数目不小于⌈m/2⌉,则只需从该结点删除该关键字 Ki 以及相应的指针 Ai 。 例如,在上边插入关键字的原始图中,删除关键字 12 ,只需要删除该关键字 12以及右侧指向 NULL 指针即可。 被删关键字所在结点中的关键字数目等于⌈m/2⌉-1,而与该结点相邻的右兄弟结点(或者左兄弟)结点中的关键字数目大于⌈m/2⌉-1,只需将该兄弟结点中的最小(或者最大)的关键字上移到双亲结点中,然后将双亲结点中小于(或者大于)且紧靠该上移关键字的关键字移动到被删关键字所在的结点中。 例如在上边插入关键字的原始图中删除关键字 50,其右兄弟结点 g 中的关键字大于2,所以需要将结点 g 中最小的关键字 61 上移到其双亲结点 e 中(由此 e 中结点有:53,61,90),然后将小于 61 且紧靠 61 的关键字 53 下移到结点 f 中,最终删除后的 B-树如图所示。 上图删除结点50后的B-树 被删除关键字所在的结点如果和其相邻的兄弟结点中的关键字数目都正好等于⌈m/2⌉-1,假设其有右兄弟结点,且其右兄弟结点是由双亲结点中的指针 Ai所指,则需要在删除该关键字的同时,将剩余的关键字和指针连同双亲结点中的 Ki 一起合并到右兄弟结点中。 例如,在上图中 B-树中删除关键字 53,由于其有右兄弟,且右兄弟结点中只有 1 个关键字。在删除关键字 53 后,结点 f 中只剩指向叶子结点的空指针,连同双亲结点中的 61(因为 61 右侧指针指向的兄弟结点 g)一同合并到结点 g 中,最终删除 53 后的 B-树为: 上图删除结点53后的B-树。 在合并的同时,由于从双亲结点中删除一个关键字,若导致双亲结点中关键字数目小于⌈m/2⌉-1,则继续按照该规律进行合并。例如在上图中 B-树的情况下删除关键字 12 时,结点 c 中只有一个关键字,然后做删除关键字 37 的操作。此时在删除关键字 37 的同时,结点 d 中的剩余信息(空指针)同双亲结点中的关键字 24 一同合并到结点 c 中,效果图为: 上图删除结点 37后的效果图。 由于结点 b 中一个关键字也没有,所以破坏了B-树的结构,继续整合。在删除结点 b 的同时,由于 b 中仅剩指向结点 c 的指针,所以连同其双亲结点中的 45 一同合并到其兄弟结点 e 中,最终的B-树为: 上图删除37后的B-树。 总结 由于 B-树具有分支多层数少的特点,使得它更多的是应用在数据库系统中。 本博文从 B-树 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/22
articleCard.readMore

操作系统 运行机制

中央处理器CPU 单机系统: 一个计算机系统只有一个处理器。 多处理器系统: 一个计算机系统有多个处理器。 CPU的构成与基本工作方式 处理器一般由运算器、控制器、寄存器以及高速缓存构成。 运算器 实现任何指令中的算术和逻辑运算,是计算机的核心; 控制器 负责控制程序运行的流程,包括取指令、维护CPU状态、CPU与内存的交互等; 寄存器 是指令在CPU内部做处理的过程中能够张村数据、地址以及指令信息的存储设备; 高速缓存 处于CPU和物理内存之间,一般由控制器中的内存管理单元MMU管理; 处理器中的寄存器 用户可见存储器:对于高级语言来说,编译器通过一定的算法分配并使用这些寄存器,以最大限度地减少程序运行过程中的访问存储器的次数,这对程序运行速度的影响很大。 控制和状态存储器:用于控制处理器的操作。 数据寄存器:又称为通用寄存器,主要用于各种算术逻辑指令和访存指令,对具有浮点能力和多媒体能力处理能力的处理器来说,浮点处理过程的数据寄存器和整数处理时的数据寄存器一般是分离的。 地址寄存器:用于存储数据及指令的物理地址、线性地址、有效地址。 条形码寄存器:保存CPU操作结果的各种标记位。 作用:控制处理器的操作。 控制和状态寄存器包括了程序计数器、指令寄存器和程序状态字。 程序计数器(PC):记录了将要取出的指令的地址。 指令寄存器(IR):包含了最近取出的指令。 程序状态字(PSW):记录了处理器的运行模式信息。 指令执行的基本过程 处理指令的最简单的方式包括两种步骤:处理器先从存储器中每次读取一条指令,然后执行这条指令。 这些指令大致分为以下五类: 访问存储器指令:负责处理器和存储器之间的数据传送。 I/O指令:负责处理器与I/O模块之间的数据传输及命令发送。 算术逻辑指令:又称为数据处理指令,用以执行有关数据的算术与逻辑操作。 控制转移指令:可以指令一个新的指令的执行起点。 处理器控制指令:用于修改处理器状态,改变处理器工作方式。 例: 假设程序计数器PC正指向2000h地址处的指令,指令机器描述如下: 地址 指令 2000h MOVE [3340h], R1 2004h ADD R1, 1 2008h MOVE R1, [3340h] …… …… …… …… 指令MOVE被送入指令寄存器IR中,同时将自增一个指令的长度,(4个字节),取指之后PC为2004h。 这是一条访问内存的指令,树3340h所指定的双字地址单元中的数据取到通用寄存器R1中来。 CPU又从PC(地址为2004h)处取出指令ADD到IR中,PC变为2008h。 CPU根据指令将R1寄存器和立即数1相加。 访存指令MOVE被取到IR中,PC变为2004h。 特权指令与非特权指令 单用户单任务下使用计算机指令系统中的全部命令。 多用户多任务中,分为:特权模式和非特权模式。 特权指令 :是指指令系统中那些只能用操作系统使用的指令,这些特权指令是不允许一般的用户所使用的。 用户只能使用非特权指令,因为只有操作系统才能使用所有的指令。 处理器的状态 管态与目态 管态 :指操作系统管理程序运行的状态,具有较高的特权级别,又称特权态、系统态。 目态: 指用户程序运行时的状态,具有较低的特权级别,又称普通态、用户态。 例: 英特尔X86系列处理器特权级别 R0:运行操作系统的核心代码 R1:运行关键设备驱动程序和I/O处理例程 R2:运行其他受保护的贡献代码 R3:运行各种用户程序 R0到R3特权能力依次降低,R0相当于双状态系统的管态,R3相当于目态,而R1和R2则介于两者之间。 CPU 状态的转换 目态到管态的转换,权限提升 管态到目态的转换,可以通过设置PSW指令(修改程序状态字),实现从操作系统向用户程序的转换。 限制用户程序执行特权指令 程序状态字 PSW 程序状态字PSW:用程序计数器PC这个专门的寄存器来指示下一条要执行的指令。 PSW包括以下状态代码: CPU的工作状态码 条件码 中断屏蔽码 某些常见标志位: CF:进位标志位 ZF:结构为零标志位 SF:符号标志位 OF:溢出标志位 TF:陷阱标志位 IF:中断使能标志位 VIF:虚拟中断标志位 VIP:虚拟中断带决标志位 IOPL:IO特权级别 存储体系 一个作业必须把它的程序和数据存放在主存储器中才能运行。 存储器的层次结构 设计主要考虑三方面:容量、速度和成本。 容量是存储系统的基础。 速度存储系统的速度则要能匹配处理器的速度。 存储器的成本和其他部件相比应该在一个合适的范围内。 容量、速度和成本的匹配 存储速度越快,平均每比特价格越高,容量越小,平均每比特的价格越低,同时容量也增大。 存储访问局部性原理 存储保护 界地址寄存器(界限寄存器) 在CPU中设置一队界限寄存器来存放用户作业在主存中的下限和上限地址,分别称为下限寄存器和上限寄存器。指出程序在内存的存放位置。 越界中断又称存储保护中断,每当CPU要访问主存时,硬件自动被访问的主存地址与界限存储器的内容进行比较,以判断是否越界。如果未越界,则按此地址访问主存,否则将产生程序中断。 存储键 每个存储块都有一个与其相关的由二进位组成的存储保护键。 每当一个用户作业被允许进入主存时,操作系统分给他一个唯一的、不与其他作业相同的存储键号;并将分配该作业的各存储快的存储键,也设置成同样的键号。操作系统同时将该作业的存储键号存放到程序状态字PSW的存储键域中,这样,每当CPU访问主存时,都将对主存块的存储键与PSW中的钥匙进行比较。如果相比配,则允许访问;否则,拒绝并报警。 中断和异常机制 中断与异常 中断 指CPU对系统中或者系统外发生的异步事件的响应。 异步事件 是指无一定时间关系的随机发生的事件。 中断是所有要打断处理器的正常工作次序,并要求其去处理某一事件的一种手段。 中断事件:又称中断源,引起中断的事件。 中断请求:中断源向处理器发出的请求信号。 中断处理程序:处理中断事件的程序。 中断断点:处理器暂时当前程序转而处理中断的过程。 中断响应:处理器暂停当前程序转而处理中断的过程。 中断返回:中断处理结束后恢复原来程序的执行。 中断字一个计算机系统提供的中断源的有序集合。 中断向量表:中断处理程序入口地址映射表。 中断向量:表中的每一项,主要是由程序状态字PSW和指令计数器PC的值组成。 中断是由外部事件引发的。 异常则是由正在执行的指令引发的。 中断与异常的分类 中断 时钟中断:是由处理器内部的计时器产生。 输入输出(I/O)中断:正常完成或则发生的错误。 控制台中断:如系统操作员通过控制台发出的命令。 硬件故障中断:由掉电、存储器校验错等硬件故障引起的。 异常 程序性中断:由指令执行结果产生。 访问指令异常:要求操作系统提供系统服务。 中断系统 中断系统:是由硬件及软件相互配合、相互渗透而使得计算机系统得以充分发挥能力的计算机模式。 中斷系統的硬件中断装置和软件中断处理程序。硬件终端装置负责捕获中断源发出的中断请求,并以一定的方式响应中断源,然后将处理器的控制权移交给特定的中断处理程序.中断处理程序就是针对中断事件的性质而执行的一系列操作。 中断请求的接受 中断请求的接受是通过在计算机硬件上的终端逻辑线路和中断寄存器实现的。 触发器的值为1时,表示该触发器接收到了中断信号,为0时表示无中断信号。 中断响应 响应机制:处理器控制不见中的设置有中断信号扫描结构,它在每条指令执行周期内的最后时刻扫描出中断寄存器,查看是否有中断信号的到来。 有中断到来=》处理器结构硬件终端装置发来的中断向量代号。 无中断到来=》处理器就继续执行下一条指令。 中断请求响应的工作过程: 处理器接受中断信号 保护现场个,将中断断点的程序状态字PSW和程序计数器PC值存入系统堆栈。 分析中断向量,取得中断向量程序的入口程序。 将处理器的PC值置为中断处理程序的入口地址。 调解中断处理程序。 中断处理 接受和响应中断。 保护中断现场。 分析中断向量。 调用中断处理程序。 中断处理结束恢复现场。 原有程序继续执行。 几种典型的中断的处理 I/O中断:一般是由I/O设备的控制器或者通道发出。 时钟中断 维护软件时钟 处理器调度 控制系统定时任务 实时处理 硬件故障中断:一般是由硬件的问题引起的。例如复位硬件或者更换设备。 程序性中断:程序指令出错、指令越权或者指令寻址越界而引发的系统保护。 系统服务请求(访管中断):应用程序设计接口API。 中断优先级与终端屏蔽 多级中断与中断优先级 硬件上,多级中断系统表现为有多根中断请求线从不同设备连接到中断逻辑线路上。 各类中断信号依据其紧急程度和重要性划分级别。 解决如果有重要程度相当的多个中断信号同时到达时,如何选择首个被处理的中断信号的问题。 中断屏蔽 在整个中断系统中,可以允许或则禁止中断系统对某些类别中断的响应。 在程序状态字PSW中设计有中断屏蔽位,主机是否允许响应或禁止某些中断,则由PSW中的中断屏蔽位决定,这些屏蔽位标识了那些被屏蔽中断类或者中断。 例:在一个计算机系统中,CD-ROM到硬盘的数据传输的优先级低于硬盘内部的数据传输操作。 内存奇偶检验错,以及掉电等使得机器无法继续操作的一类故障。一旦发生这类不可屏蔽的中断,不管程序状态字的屏蔽位是否建立,处理器都要立即相应这类中断,并进行处理。 系统调用 系统调用 系统调用就是用户在程序中调用操作系统所提供的一系列子功能。 有特殊的机器指令实现的,由汇编语言直接访问。 系统调用与一般过程调用的区别 运行在不同的系统状态:调用程序运行在用户态,而被调用程序则运行在系统态。 状态的转换:通过软中断机制先由用户态转换为核心态,在操作系统核心分析之后,转向相应的系统调用处理子程序。 返回问题:让优先级最高的进程优先执行。 嵌套调用:在一个被调用的过程执行期间,还可在利用系统调用命令在去调用另一个系统调用。 系统调用的分类 进程控制类系统调用:对进程的控制,如创建和终止进程的系统调用。 文件操作类系统调用:对文件进行操作的系统调用,如创建、打开、关闭、读写文件等操作。 进程通信类系统调用:被用在进程之间传递信息和信号。 设备管理类系统调用:系统调用被用来请求和释放有关设备,以及启动设备操作。 信息维护类系统调用:有关信息维护的系统调用。 系统调用命令是作为扩充机器指令,增强系统的功能,方便用户使用而提供的。 “广义指令”:系统调用命令的过程。软件实现的 系统调用的处理过程 在系统中为控制系统调用服务的机构称为陷入(TRAP)或异常处理机构。 由于系统调用引起的处理机中断的指令称为陷入或异常指令(或称访管指令)。 一种是由陷入指令自带的参数。 另一种是通过有关通用寄存器来传递参数。 处理机在用户程序中执行称为用户态,而把处理机在系统程序中执行称为系统态(或管态)。 I/O技术 I/O结构 通道 通道是独立于中央处理器的,专门负责数据I/O传输工作的处理单元。 I/O处理机通道对外部设备实行统一的管理,代替CPU对I/O设备操作进行控制。 工作原理: 按程序规定的顺序执行一条条指令,按指令中给定的参数启动指定的设备。 控制权转移到通道 信息传送,由通道控制,而中央处理器则继续执行程序。 产生一个“输入输出操作结束”的I/O中断事件。 DMA 技术 直接存储器访问DMA技术通过系统总线中的一个独立的控制单元,自动的控制成块的数据在内存和I/O单元之间的传送, DMA控制单元命令包含了I/O设备的编址、开始读或写的主存编址、需要传送的数据长度、是否请求一次读和写等信息。 缓冲技术 缓冲技术实在外部设备于其他硬件之间的一种数据暂存技术,他利用存储器件在外部设备中设置了数据的一个存储区域,称为缓冲区。 两种用途: 在外部设备与外部设备之间的通信上。 在外部设备和处理器之间。 最根本原因:CPU处理数据的速度与设备传输数据速度不相匹配,需要缓冲区缓解其间的速度矛盾。 时钟 时钟的作用 在多道程序运行的环境中,防止时机的浪费。 在分时系统中,用时钟间隔来实现各个作业按时间片轮转运行。 在实时系统中,按要求的时间间隔输出争取的时间信号给相关的实时控制设备。 定时唤醒哪些要求按照事先给定的时间执行的各个外部事件。 记录用户使用各种设备的时间和记录某外部事件发生的时间间隔。 记录用户和系统所需要的绝对时间,即年月日。 时钟一般分为硬件时钟和软件时钟。 硬件时钟工作原理:在电路中的晶体震荡器,每个一定的时间间隔产生固定的脉冲频率,时钟电路中的时钟寄存器依据时钟电路所产生的脉冲数,对时钟寄存器进行加1操作。 软件时钟工作原理:主要是利用内存单元模拟时钟寄存器,并采用一段程序来计算相应的脉冲数,对内存时钟寄存器进行加1或减1操作。 时钟的用途可以分为绝对时钟和相对时钟。 绝对时钟是在计算机系统中不受外界干扰、独立运行的一种时钟。 相对时钟又称“间隔时钟”,它只计算从某一个时间初值开始的一段时间间隔。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/20
articleCard.readMore

数据结构 折半查找法

算法原理 先确定待查记录所在的范围(区间),然后逐步缩小范围指导找到或找不到该记录为止。 算法性能 时间复杂度: log 2 n + 1 平均查找长度: log 2 n + 1 – 1 注意事项 折半查找法必须为有序数列。 可以是逆序的,但是必须得提前定义遍历对比对象。 算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 include "stdio.h" //折半查找函数 int binarySearch(int a[], int n, int key){ //定义数组的第一个数 int low = 0; //定义数组的最后一个数 int high = n-1; //定义中间的数值 int mid; //存放中间的数值的变量 int midVal; //当左边的值小于等于右边的值的时候 while(low <= high){ mid = (low + high)/2; midVal = a[mid]; //如果中间值小于用户查找到的数值,最低的数字到中间数值+1位置上 if(midVal < key){ low = mid + 1; }else if(midVal > key){ //如果中间值大于用户查找到的数值,最高的数字到中间数值-1位置上 high = mid - 1; } else return mid; } return -1; } int main(){ int i, val, ret; int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //打印数组 for(i=0; i<10; i++) printf("%d \t", a[i]); printf("\n请你输入你要进行查找的元素\n"); scanf("%d", &val); ret = binarySearch(a, 10, val); if(ret == -1){ printf("查找失败!\n"); } else{ printf("查找成功!\n"); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/18
articleCard.readMore

操作系统 概论

操作系统的概念 计算机系统 计算机系统包括硬件子系统及软件子系统。 各种程序和数据组成了计算机的软件系统。 操作系统:在计算机系统中,集中了资源管理功能和控制程序执行功能的一种软件。 操作系统的定义 “有效”是指根据用户的不同的要求,在管理计算机资源时考虑到系统运行的效率及资源的利用率。 “合理”是指操作系统要“公平对待”不同的用户程序,保证系统不发生“死锁”及“饥饿”现象。 操作系统的特征 并发性 是指在计算机系统中同时存在若干个运行的程序。计算机的并发行体现在下面两个方面: 用户程序与用户程序之间并发执行 用户程序与操作系统之间并发执行 共享性 随机性 研究操作系统的观点 软件的观点 操作系统就是一种大型的软件系统,它是多种功能程序的集合。 外在特性 操作系统是一种软件,它的外部表现形式,即它的操作命令定义集和它的界面,完全确定了炒作系统这个软件的使用方式。 内在特性 操作系统既然是臃肿软件,他就具有一般软件的结构特点。 资源管理的观点操作系统就是负责记录谁在使用什么样的资源。 操作系统要提供一些机制去协调程序间的竞争与同步,提供机制对资源进行合理使用,对其保护,一机采取虚拟技术来“扩充”资源等。 进程的观点 操作系统就死看作是由多个可以独立运行的程序和一个对这些程序进行协调的核心所组成的。 虚拟机的观点 服务提供者的观点 操作系统的功能 进程管理 对中央处理器进行管理。 进程管理分为一下几个方面: 进程控制进程控制的主要任务就是创建进程、撤销结束的进程以及控制进程进行时候的各种状态的转换。 进程同步 互斥 :是指多个进程对临界资源访问时采用互斥的形式。 同步 :是在相互协作共同完成任务进程之间,用同步机制协调他们之间的执行顺序。 进程间通讯进程通讯主要发生在相互协作的进程之间。由操作系统提供给的进程间的通讯机制是协作的进程之间相互交换数据和消息的手段。 调度 调度又称处理器调度,通常包括进程调度、线程调度及作业调度。 进程调度 任务就是从进程(线程)的就绪队列中按照一定的算法挑选出一个,吧处理器资源分配给他,并准备好特定的执行上下文让他执行起来。 作业调度 依照作业说明书为他们分配一定的资源,把他们装进内存并未每个作业建立相应的进程。 存储管理存储管理的任务就是管理计算机内存的资源。 文件管理 文件管理的任务就是有效的支持文件的存储、检索及修改等操作,解决文件的共享、保密及保护问题,以使用户方便、安全的访问文件。 设备管理 用户接口 用户计算机系统之间的接口。 操作系统的发展 手工操作 通过在插板上的硬连接线来控制计算机的基本功能。 监控程序(早期批处理) 多道批处理 多道 是指允许多个程序同时存在于内存之中,由CPU以切换的方式为之服务,使得多个程序可以同时执行。 分时系统 分时系统 是指多个用户通过终端设备与计算机交互作用来运行自己的作业,并且共享一个计算机系统而互不干扰。 UNIX通用操作系统 C语言编写。 个人计算机操作系统 Android操作系统 操作系统分类 批处理操作系统 批处理操作系统特点就是成批处理。 作业吞吐率:在单位时间内计算机系统处理作业的个数。 设计思想 在监控程序启动之前,操作员有选择的把若干作业合并成一批作业,将这些作业安装在输入设备之上,然后自动监控程序,监控程序将自动控制这批作业执行。 一般指令与特权指令 运行模式通常分为用户模式和特权模式。 目态: 为用户服务的用户模式。 管态: 为系统专用的特权模式。 系统调用的过程 系统调用时,通常是中断或者异常处理,将处理器模式转变成特权模式。 由监控程序执行被请求的功能代码。 处理结束之后,监控程序恢复系统调用之前的现场;把运行模式从特权模式恢复成为用户方式;最后将控制权转移到原来的用户程序。 SPOOLing技术(假脱技术) 基本思想: 用磁盘设备作为主机的直接输入/输出设备,主机直接从磁盘上选取作业运行,作业的执行结果也存在磁盘上;相应的,通道则负责将用户作业从卡片机上动态写入磁盘,而这一操作与主机并行。 分时系统 基本工作方式 在分时系统中,一台计算机主机连接了若干个终端,每个终端可有一个用户使用。 设计思想 分时系统将CPU的时间划分成若干个小片段,称为时间片。操作系统以时间片为单位,轮流为每个终端用户服务。 特点 多路性: 只有多个用户在使用同一台计算机。 交互性: 指用户根据系统响应的结果提出下一个请求。 独占性: 指每个用户感觉不到计算机在为其他人服务。 及时性: 指系统能够对用户提出的请求及时给予响应。 分时操作系统追求的目标 :及时响应用户输入的交互命令。 分时与批处理的处理原则 :分时优先,批处理在后。 实时操作系统 实时操作系统(RTOS)是指计算机能在规定的时间内及时响应外部事件的请求,同时完成对该事件的处理,并能够控制所有实时设备和实时任务协调一致の工作的操作系统。 硬实时系统 对关键外部事件的响应和处理时间有着极为严格的要求,系统必须满足这种严格的时间要求,否则会产生严重的不良后果。 软实时系统 对事件的响应和处理时间有一定的时间范围要求,不能满足相关的要求会影响系统的服务质量,但是通常不会引发灾难性后果。 实时时钟管理 主要设计目标:对实时任务能够进行实时处理。 依据时间要求 定时任务: 依据用户定时启动并按照严格的时间间隔重复运行。 延时任务: 非周期运行,允许被延后执行,但往往有一个严格的时间线界限。 依据功能划分 主动式任务: 依据时间间隔主动运行,多用于实时监控。 从动式任务: 运行以来于外部时间的发生,但外部事件出现时,这种实时任务应尽可能地进行处理,并且保证不丢失现象。 过载防护 实时任务的启动时间和数量具有很大的随机性,突发的大量实时任务极有可能超出系统的处理能力,从而发生过载。 高可靠性 嵌入式操作系统EOS 嵌入式操作系统就是运行在嵌入式环境芯片中,对整个芯片以及它所操作的、控制的各种部件装置等资源进行统一协调、调度、指挥和控制的系统软件。 优点 具有高可靠性、实时性、占用资源少、智能化能源管理、易于连接、低成本等优点。 个人计算机操作系统PCOS 个人计算机操作系统是一种单用户多任务的操作系统。 网络操作系统NOS 网络操作系统:为计算机网络配置的操作系统。 分布式操作系统DOS 将大量计算机通过网络连接在一起,可以获得极高的运算能力及广泛的数据共享。 特征: 是一个统一的操作系统。 实现资源的深度共享。 透明性。 自治性。 集群 Cluster是分布式系统的一种,一个集群通常由一群处理器密集构成,集群操作系统专门服务于这样的集群。用低成本的微型计算机和以太网设备等产品,构造出性能相当于超级计算机运行性能的集群。 智能卡操作系统COS 四个基本功能:资源管理、通信管理、安全管理和应用管理。 操作系统结构 操作系统结构就是指操作系统各部分程序存在方式及相互关系。 模块结构: 以程序模块方式存在。 进程结构: 以进程的方式存在。 整体式结构 模块 将总功能分解成若干个子功能,实现每个子功能的程序。 优点:结构紧密,接口简单直接,系统效率较高。 模块组合法 (又称无需模块法,模块接口法),系统中的模块不是根据程序和数据本身的特性而是根据他们完成的功能来划分的,数据基本上作为全称量使用。 层次结构 层次结构就是把操作系统的所有功能模块,按照功能流程图的调用次序,分别将这些模块排列成若干层,各层之间的模块只能是单项依赖或则单先调用 。 全序的层次关系: 每一层中的同层模块之间不存在相互调用的关系。 优点: 整体问题局部化 各模块之间的组织架构和依赖关系清晰明了。 分层原则: 可适应性,方便于系统一直,可放在仅靠硬件的最底层。BIOS但硬件系统环境改变时只需要修改这一层模块就可以。 多种操作方式, 共同要使用的基本部分放在内层,而改变的部分放在外层。 微内核结构(C/S结构) 采用C/S结构的操作系统适宜于应用在网络环境下分布式处理的计算环境。 特点: 运行在核心态的内核:线程调度、虚拟内存、信息传递、设备驱动以及内核的原语操作集中中断处理等。 运行在用户态的并以C/S方式运行的进程层:除内核部分外,操作系统所有的其他部分被分成若干个相对独立的进程,每一个进程实现一组服务,称为服务进程。 这些服务进程可以提供各种系统功能、文件系统服务以及网络服务等。 好处: 可靠: 每一个分支是独立的,不会引起其他组成部分的损坏或崩溃。 灵活: 是自包含的,且接口规范,可维护性好。 分布式处理:具有分布式处理的能力。 缺点: 效率较低。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/18
articleCard.readMore

Sublime Text 崇高的文本编辑器

安装 Sublime text3 软件 官方网址:https://www.sublimetext.com/3 选择Windows - also available as a portable version一项,点击下载安装。 安装 packagecontrol 插件 官方网址:https://packagecontrol.io/ 选择 Installation 项 选择 SUBLIME TEXT3 代码进行复制 打开 sublime text3 软件,选择 View->Show Console 选项(或者按 Ctrl+~组合键),调出命令行,将代码粘贴至命令行,回车,进行安装 packagecontrol 插件; 安装好之后在菜单栏Preferences栏目中会有packagecontrol选项,即安装成功。 安装汉化插件 ChineseLocalization 安装 sublime 汉化插件 ChineseLocalization 在弹出的框中,由于网速原因,请耐心等待…… 输入插件名称ChineseLocalization,回车[enter]进行安装 各类插件安装推荐 其他插件均和 ChineseLocalization 插件安装过程一样,在此不再重复操作,只推荐几款插件。 必备的插件安装 (初学者推荐安装) Emmet Emmet 的前身是大名鼎鼎的 Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生。它使用仿 CSS 选择器的语法来生成代码,大大提高了 HTML/CSS 代码编写的速度。 PySide PySide 是跨平台的应用程序框架 Qt 的 Python 绑定版本。 AutoFileName 一款在 Sublime Text 中可以自动补全文件路径及名称的插件。 DocBlockr DocBlockr 是一款 Sublime Text 2 & 3 都可以使用的代码快注释插件。支持的语言有:JavaScript (including ES6), PHP, ActionScript, Haxe,CoffeeScript, TypeScript, Java, Groovy, Objective C, C, C++ and Rust. BracketHighlighter BracketHighlighter 是一款 Sublime 下匹配标签高亮的小插件,可以把匹配到的如 {}、()、”、””等对应的符号或者标签高亮显示。 Browser Refresh 通过一个快捷键可以实现保存文件,切换到浏览器并自动刷新浏览器来查看更改结果。 ConvertToUTF8 解决文档保存编码问题。 ColorPicker 一个多平台的颜色选择器插件。默认情况下,十六进制颜色代码使用大写字母插入。 进阶的程序猿推荐插件安装 a file icon 美化插件。可以更清楚了解每个文件的类型,一目了然。 git 版本控制仓库,推荐学习使用。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2017/11/12
articleCard.readMore

Search

关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2001/1/1
articleCard.readMore

文章归档

关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2001/1/1
articleCard.readMore

友情链接

博客组织 BlogsClub | BlogFinder | 十年之约 | 个站商店 | 博客录 | 博友圈 | 笔墨迹 友情链接 关注微信公众号,第一时间获取最新内容,让我们一起变得更强! Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 摄影展集· 我的主页

2001/1/1
articleCard.readMore