在实现 Vue3 的响应式原理前,我们先来回顾一下 Vue2 的响应式存在什么缺陷,主要有以下三个缺陷:默认会劫持的数据进行递归;不支持数组,数组长度改变是无效的;不能劫持不存在的属性。Vue3 使用了 Proxy 去实现数据的代理,在实现 Vue3 的响应式原理的同时,我们需要思考 Proxy 会不会存在上面的缺陷,它的缺点又是什么呢?
实际上 Java 中的动态代理实现已经非常精简了,所以在 Kotlin 在动态代理实现并没有特别不一样的,它和 Java 的实现没有不同。所以这里就不再重复实现,只是换了 Kotlin 语言实现没有什么不一样的。
大家好,今天我们来学习一门实用而且成就感强的专题–Dreamweaver,就像它的标志图标一样,我们今后可以称它 DW。关于 DW 的知识多且细,市面上的介绍寥寥无几,而且缺少系统性。这个专题我们重点针对 DW 在网页开发的基础实战功能对DW的应用进行讲解。本文我们先介绍一下 Dreamweaver 是什么?Dreamweaver 主要优势有哪些?DW 各版本简介及推荐?DW 主干学习路线?
整体来说,我们传统的实现用户登录功能的业务逻辑还是正确的, 不管我们再怎么对用户登录功能进行优化,这个传统的用户登录功能业务流程始终不会发生改变,发生改变的只是我们的代码实现层。那么,传统实现方案中存在的弊端,也就存在于我们的功能代码实现层上。弊端一 用户登录状态的处理对于上述代码来说,我们处理用户登录状态所采用的手段,是通过将用户登录成功后的数据存放在服务端中的 session 中,后续如果需要使用到用户登录状态,我们可以直接对 session 进行判断,并返回相应的判断结果。但是,这种模式如果遇到了前后端分离的项目,我们就无法再使用 session 了,因为前端框架不能直接操控我们后端的 session ,所以,我们就要把 session 替换掉,替换成前后端均可操控的手段来实现。弊端二 高并发环境下容易出现的问题如果我们的项目在使用过程中出现了用户激增的情况,即会有越来越多的人来访问我们的用户登录功能,如果我们只是采用上述的实现方式来处理,不会这种情况进行考虑,那么我们的用户登录功能就会在非常短的一段时间过后瘫痪,我们的用户登录功能不会再返回任何响应数据。这就是高并发环境下传统的用户登录功能最容易出现问题的地方,而这一地方则体现在用户登录前和用登录后的数据不一致问题上。即,在高并发环境下,我们的用户 A 进行了登录,与此同时,我们的用户 B 也进行了登录,这两个用户进行登录的时刻恰好重叠了,由于我们并没有对这种场景进行处理,所以,在这两个用户登录之后,就有可能出现返回给用户 A 的用户数据,其实是用户 B 的。除了数据不一致问题,还有一种问题也比较容易出现,那就是当我们的用户数激增时,同一时刻进行登录的用户也随之激增,我们的用户登录功能接口无法在一瞬间响应这么多的用户登录请求,导致了后续登录的用户只能等待,只能等待当先用户登录完成之后,才能进行登录。这个等待过程是不确定的,可能很长,也可能很短,这就对用户的体验造成了非常严重的不良影响。上述就是两个在传统用户登录功能中,出现的显而易见的问题,我们会对这两个功能进行优化,并且会使用 RabbitMQ 对第二个弊端进行优化。
一方面我们可以利用现有的 CSS 中文库:chinese-layout 来快速实现吕形布局:中文布局 CSS 库这种方式不仅利于我们的学习,更加有助于我们的记忆。然后也会带领大家如何在不依赖于任何外界条件的情况下纯手工实现吕形布局:固定定位法外边距的作用非固定定位作为扩展阅读,我们还提供了交互更加充满趣味性的渐隐渐现效果:固定定位 + 渐隐渐现不过由于渐隐渐现效果必须用 JS 才能获取到当前页面的滚动距离,所以这章只作为扩展阅读,防止没有 JS 基础的小伙伴看了懵圈。
一方面我们可以利用现有的CSS中文库:chinese-layout 来快速实现上下栏布局:中文布局 CSS 库这种方式不仅利于我们的学习,更加有助于我们的记忆。然后也会带领大家如何在不依赖于任何外界条件的情况下纯手工实现上下栏布局:固定定位法外边距的作用非固定定位作为扩展阅读,我们还提供了交互更加充满趣味性的渐隐渐现效果:固定定位 + 渐隐渐现不过由于渐隐渐现效果必须用 JS 才能获取到当前页面的滚动距离,所以最后这一小节只作为扩展阅读,防止没有 JS 基础的小伙伴看了懵圈。
一方面我们可以利用现有的CSS中文库:chinese-layout 来快速实现九宫格布局:中文布局 CSS 库这种方式不仅利于我们的学习,更加有助于我们的记忆。然后也会带领大家如何在不依赖于任何外界条件的情况下纯手工实现九宫格布局:网格布局表格布局绝对定位弹性布局左浮动法但除了上面的那些类似于朋友圈或微博的九宫格以外,我们还有另一种九宫格的形式:加入边框边框九宫格接下来我们还会对其进行改进:改进版作为扩展阅读,我们还提供了一道用九宫格来实现的经典面试题:经典面试题不过由于经典面试题考察的范围很广、不止考察了面试者的 CSS 功底,同样也考察了一些 JS 功底。所以最后这一小节只作为扩展阅读,防止没有 JS 基础的小伙伴看了懵圈。
密码框可谓表单填写中一个不可缺少的输入框。它和普通文本输入框最大的区别就是用它输入的键盘文字在屏幕上会隐藏显示,正如它的名字一样,密码,隐秘的编码。是不能让人看到的。因此,密码框的使用在 web 网页中是十分常见并且普遍的。首先,我们先打开 DW,创建一个空白的 HTML 文档,然后新建一个表单。建立好空白表单后,我们再点击右侧面板的密码,可以看到图中出现了一个 password 和一个带有输入功能的输入框,我们在输入框中尝试输入文字,可以看到输入的文字都被加密成为了*这种星形符号。以上就是表单中密码框的设置方法。有木有很简单?其实 Dreamweaver CC 2018 中的很多操作都可以抽象为一个操作模式,重点在于理解各种元素在网页设计和交互中的作用。然后才是学习工具的使用。而 Adobe Dreamweaver CC 2018 只是给我们网页设计便利化搭建了一个充分的平台,要想利用好,还得理论和实践相结合的去操作。
在 Zookeeper 的通信及会话一节中,我们学习了 Zookeeper 的请求协议和响应协议,并查看了部分源码。我们可以发现,无论是请求协议还是响应协议的具体类,都实现了接口 Record,Record 接口其实就是 Jute 定义的序列化接口。package org.apache.jute;import java.io.IOException;import org.apache.yetus.audience.InterfaceAudience.Public;@Publicpublic interface Record { // 序列化 void serialize(OutputArchive var1, String var2) throws IOException; // 反序列化 void deserialize(InputArchive var1, String var2) throws IOException;}我们这里使用请求头 RequestHeader 作为例子来查看序列化和反序列化的实现:// 请求头,实现了 Record public class RequestHeader implements Record { private int xid; private int type; // 使用 OutputArchive 进行序列化,tag:序列化标识符 public void serialize(OutputArchive a_, String tag) throws IOException { a_.startRecord(this, tag); a_.writeInt(this.xid, "xid"); a_.writeInt(this.type, "type"); a_.endRecord(this, tag); } // 使用 InputArchive 进行反序列化,tag 序列化标识符 public void deserialize(InputArchive a_, String tag) throws IOException { a_.startRecord(tag); this.xid = a_.readInt("xid"); this.type = a_.readInt("type"); a_.endRecord(tag); }}在 serialize 序列化方法中,根据成员变量的数据类型来选择 OutputArchive 的方法来进行序列化操作。在 deserialize 反序列化方法中,根据成员变量的数据类型来选择 InputArchive 的方法来进行反序列化操作。在这里我们可以发现,Record 只是定义了序列化方法和反序列化方法,真正执行序列化操作是 OutputArchive 和 InputArchive 这两个接口的实现类。接下来我们讲解 OutputArchive 序列化接口的实现类 BinaryOutputArchive。
以上效果是一个基于 console 控制台的聊天效果,根据上节的思路设计,我们也知道主要核心是服务端保存一份关系映射,通过接受人 ID 找到对应的通道进行消息发送。但是,我们实现具体的功能,需要大体上做一个核心步骤的拆解,具体如下所示:第一步: 编码和解码的实现。两个核心功能点,第一是序列化和反序列化,第二通讯协议实现。客户端和服务端之间的数据通讯,我们是基于实体对象去交互,这样数据格式更加的方便,因此,然而对于实体对象对象的序列化和反序列化推荐使用 Fastjson 框架去实现,而不是前面章节所使用的对象流。同时为了更加规范的管理不同业务的实体,我们需要定义一个实体基类,所有的业务实体都继承它(下面会详细讲解)。第二步: 登录和消息发送两个业务点的实现。登录主要是为了让用户 ID 和通道进行绑定,在登录成功之后给 Channel 通过 attr () 方法绑定用户 ID,主要目的有两个:在发送消息时,可以通过 Channel 获取用户 ID,发送给服务端,让另外一个客户端接收消息时,知道是谁发送过来的消息;在 Channel 断开的时候,服务端可以监听到 Channel,并且获取 Channel 的属性,从而删除对应的映射关系。业务处理,用户登录和消息发送是两个不同的业务点,一般来说需要定义多个 Handler 来分别处理,但是这里为了减少 Handler 的数量,统一一个 Handler 处理。第三步: 映射关系的实现。前面也解析过了,服务端需要保存一份映射关系,只需要使用一个 Map 进行存储即可,Map<Integer,Channel>,key 是用户 ID,value 是 Channel,也就是连接通道。
步骤 1:通过 zmail 实现最新邮件接收import zmail#获取最新邮件并打印邮件信息server = zmail.server('xxxxx@qq.com', 'mdgxgiwpnkspbxgx')mail = server.get_latest()zmail.show(mail)print(mail["id"])print(mail["from"])print(mail["to"])print(mail["subject"])print(mail["context_text"])print(mail["context_html"])代码解释:首先通过 zmail 实现最新一封邮件的接收,这里的代码同前面小节中接收邮件代码一致。步骤 2:新旧邮件比对想要提示用户有最新的邮件,就要把最后一封邮件的信息进行记录,在指定时间后,拿着两封邮件的信息进行比对,这里我们使用的邮件 ID(注意 ID 并不唯一,如要确保一定的准确,可以用 ID 结合邮件标题等多方面信息)记录邮件信息我们选择在这里放在了一个 txt 文件中,每次先读取再写入old_mailid = open('id.txt', 'r').readline()# 写入新邮件IDwith open('id.txt', mode='w+', encoding='utf-8') as f: f.write(str(mail_id))步骤 3:实现 window 弹窗提示像这种操作早有前人已经替我们完成了,这种库有很多,本小节采用的是 pymsgbox, 使用前注意通过 pip install pymsgbox 进行安装先了解下 pymsgbox 弹窗的多种形式pymsgbox.alert(text='', title='', button='OK')pymsgbox.confirm(text='', title='', buttons=['OK', 'Cancel'])pymsgbox.prompt(text='', title='' , default='')pymsgbox.password(text='', title='', default='', mask='*')效果依次如下图所示。显然我们这里用不到太复杂的弹窗,只要一个 alert 就阔以了,代码如下所示:#判断邮件是否是最新if old_mailid != str(mail_id): pymsgbox.alert("你有一封新邮件!") # 弹窗提示有新邮件步骤 4:配置 Window 计划任务前面都完成之后,执行 python 文件已经可以提示最新邮件了,现在遇到的问题就是如果自动化起来,这里通过配置 Window 计划任务的方式来实现。首先创建一个新任务,起个名字 autoemail,配置触发器,每 1 分钟执行一次,在操作这里,要执行的内容是什么呢,显然 Window 并不能直接执行 python,这里创建一个 .bat 文件,在文件中执行 Python 文件,在计划任务这里指定. bat 文件即可现在自己手动给邮箱发一封邮件之后,等待 1 分钟后,就可以看到 pymsgbox 的弹窗提示了,现在就可以去邮箱里面去查收邮件了,或者也可以直接把邮件内容一起显示出来,这个可以根据需求来修改代码就可以了。
我们再看一个生活中的例子。房屋买卖中经常会出现代理的情况。当卖家不在房屋所在地时,可能会委托自己的亲人或者朋友进行交易。而买方会和代理人直接进行交易。交易中间的问题代理人会回答,手续代理人会办理。如下图:这个代理人不太老实,私自加了20万,想赚差价。所以不要以为代理人真的只是代理,在这个过程中他可以加入自己的逻辑处理。而客户和被代理人并不知道。我们看看采用代理模式如何实现这个场景。首先真正卖掉房子的还是房主,只不过和买房人直接进行买卖的是代理人。那么房主和代理人有一个公共的行为都是卖房。那么我们可以抽象出一个接口定义卖房的行为。房主和代理人都需要实现这个接口。真正的卖房逻辑在房主的实现中,代理人的卖房实现只是调用房主的实现而已。要达到这个目的,代理人需要持有房主的引用。而买方进行买卖的时候,仅和代理人打交道。不用知道房主是谁,也不用让房主到现场过户。甚至连房主身在何处都不知道。上面其实就是这个例子的程序设计。代码如下:房屋交易接口代码:public interface RealEstate { void sell();}房主代码:public class Seller implements RealEstate { @Override public void sell() { System.out.println("卖了房子"); }}代理人代码:public class SellerProxy implements RealEstate{ private Seller seller; @Override public void sell() { if(seller==null){ seller = new Seller(); } seller.sell(); System.out.println("退税办理完毕"); }}类图:代理人在这里有什么用处呢?没有代理人,直接和房主买就好了啊?试想下,假如现在有了新的买房政策,交易完成后可以退税,那么在不修改房主代码的前提下,我们只需要修改此代理人的代码即可。如果在其他地方卖房没有此政策,只需要定义另外一个地区的代理人即可,这里实现了开闭原则。其实代理模式还有很多好处和适用的场景。我们下面详细来看。
实现单例模式,其实我们需要实现如下需求:提供获取实例的方法。此方法会控制全局仅有一个实例,而不会重复创建实例;全局唯一的实例要有地方能存放起来;不能随意通过new关键字创建实例。这样才能控制调用方只能用受控的方法来创建对象。针对以上三点需求我们需要做如下事情:编写一个获取实例的公有方法,已经创建过实例就直接返回实例,否则进行实例化;实例化好的对象存哪里呢?存在类当中是最好的。这样不用引入新的类,而且也符合就近原则;禁止通过new关键字初始化,只需要把无参构造方法私有化。此外不要添加任何有参数的构造方法。我们按照上面的思路实现第一版单例模式,代码如下:public class SingletonOne { private static SingletonOne singletonOne; private SingletonOne() { } public static SingletonOne getInstance() { if (singletonOne == null) { singletonOne = new SingletonOne(); } return singletonOne; }}代码中使用静态变量,也称之为类变量保存SingletonOne的实例。无参构造方法私有化,并且不提供其他构造方法。getInstance() 对外提供获取实例的方法。方法内部也符合我们的需求,已经实例化,直接返回实例,如果还是null,去创建这个实例。这种方式称之为懒汉式,是因为类的实例化延迟到第一次getInstance的时候。看起来上面的代码实现了我们提到的三点需求,无懈可击。没错,一般的场景采用上面的代码足以应付。但是在并发的时候,上面的代码是有问题的。并发时,两个线程对于 singletonOne == null 的判断可能都满足,那么接下来每个线程各自都创建了一个实例。这和单例模式的目标是相违背的。我们需要改造一下。
了解了 Go 语言中接口的定义之后,我们来学习一下如何实现Go语言的接口。在 Go 语言的接口中有两个非常重要的特性:只要有类实现(模拟)了接口中包含的所有方法,我们就称这个类型实现了这个接口;只要是实现了这个接口的类型,用这个类型定义的变量就可以给这个接口声明的变量赋值。所以从以上两个特性也可以推出 interface{} 类型的变量,可以接收 任何变量的赋值。这两个特性也被称为鸭子类型,即"走起来像鸭子,叫起来像鸭子(类型模拟了接口中包含的所有方法),那么它就是一只鸭子"。代码示例:package mainimport ( "fmt")var stuInterface interface { PrintAge()}type Student struct { Name string Age int}func (s Student) PrintAge() { fmt.Println(s.Age)}func main() { stuInterface = Student{ Name: "Codey", Age: 18, } stuInterface.PrintAge()}第 21~24 行:定义了一个自定义类型,并且这个类型实现了接口变量类型中的方法;第 21 行:实例化一个自定义类型的变量,并赋值给这个接口变量;第 25 行:使用接口中的方法。会发现打印了学生的年龄。执行结果:
面试题:实现一个 Promise.all() 方法。前面我们说到了 thenable 对象,也就是判断一个值是不是 Promise 对象,就是判断它是函数或对象,并具有 then 方法。const isPromise = (val) => { if (typeof val === "function" || (typeof val == "object" && val !== null)) { if (typeof val.then === "function") { return true; } } return false;};Promise.all() 会接收一个数组,数组的每一项都是一个 Promise 实例,并且它的返回结果也是一个 Promise,所以我们需要在内部 new 一个 Promise 对象,并返回。在执行器中我们的目标是:当有实例中有错误或抛出异常时,就要执行执行器中的 reject;没有错误时,只有所有的实例都成功时才会执行执行器中的 resolve。基于这两点,有如下步骤:内部创建一个计数器,用于记住已经处理的实例,当计数的值和传入实例的数组长度相等时,执行执行器中的 resolve;创建一个用于存放实例返回结果的数组;处理实例的结果有两种:一种返回的是普通值、一种返回的是 Promise 对象,然后分别处理;返回普通值结果时直接存放到数组中即可;返回的是一个 Promise 对象时,就需要调用这个实例上的 then 方法得到结果后在存放到结果数组中去。根据上面的五个步骤基本就可以把 Promise.all() 实现出来了,具体代码如下:Promise.all = function(arr) { return new Promise((resolve, reject) => { let num = 0; // 用于计数 const newArr = []; // 存放最终的结果 function processValue(index, value) { // 处理Promise实例传入的结果 newArr[index] = value; if (++num == arr.length) { // 当计数器的值和处理的 Promise 实例的长度相当时统一返回保护所以结果的数组 resolve(newArr); } } for (let i = 0; i < arr.length; i++) { const currentValue = arr[i]; // Promise 实例 if (isPromise(currentValue)) { currentValue.then((res) => { processValue(i, res); }, reject) } else { processValue(i, currentValue); } } });}上面的代码已经实现了 Promise.all() 方法,可以使用下面的例子进行测试。const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("任务1成功..."); }, 1000);});const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("任务2成功..."); }, 500);});Promise.all([p1, p2]).then((res) => { console.log(res)})
我们先来看下官网对 Dreamweaver 是如何介绍的:Adobe Dreamweaver 为网页设计软件提供了一套直观的可视界面,供您创建和编辑 HTML 网站和移动应用程序。使用专为跨平台兼容性设计的自适应网格版面创建适应性版面。在发布前使用多屏幕预览审阅设计。使用更新的“实时视图”和“多屏预览”面板高效创建和测试跨平台、跨浏览器的 HTML5 内容。利用增强的 jQuery 和 PhoneGap™ 支持构建更出色的移动应用程序。—Adobe 公司中国区官网Tips:这里的 HTML5 主要指第五代 HTML 标准,可以理解为更高级的 HTML 语言。看过了 DW 官网的介绍,简单说就是 Dreamweaver 能够适应各种不同的电脑系统。在 DW 使用过程中,你看到的就是你制作好的网页,不用编写 HTML 代码,不用做繁杂的代码检查,只需要发布前使用预览功能检查一下是不是自己心中理想的效果效果就可以。DW 还能帮助我们做出现在非常时髦的移动程序 — 各种APP。DreamWearver 是 Dream (梦想)和 weaver (编织工)两个单词的组合,直译为梦想编织人。为什么叫梦想编织人?因为有了 Dreamweaver,我们不必掌握 HTML,CSS,Javascript (一些构成网页的语言)等程序开发技能就可以设计出界面美观大方,交互合理的网页。Dreamweaver 贯穿了网页设计,发布,管理的方方面面,描述为网页开发的一站式服务一点儿也不过分。
一方面我们可以利用现有的CSS中文库:chinese-layout 来快速实现双列布局:中文布局 CSS 库这种方式不仅利于我们的学习,更加有助于我们的记忆。然后也会带领大家如何在不依赖于任何外界条件的情况下纯手工实现双列布局:多列属性法左浮动法绝对定位弹性布局网格布局增光添彩
由于篇幅,本节没有实现一个完整的计算器,在这里仅仅讨论实现计算器程序的关键要点。windows 自带的计算器的界面如下所示:计算器向用户展示各种按钮,包括:数字按键,0、1、2、3、4、5、6、7、9运算符按键,+、-、*、\、=用户在点击某个按键时,程序得到通知:按键被点击了,但是这样的信息还不够,为了实现运算逻辑,还需要知道具体是哪一个按键被点击了。为了区分是哪一个按键被点击了,可以为不同的按键设定不同的按键处理函数,如下所示:import tkinterdef on_button0_click(): print('Button 0 is clicked')def on_button1_click(): print('Button 1 is clicked')def on_button2_click(): print('Button 2 is clicked')root = tkinter.Tk()button0 = tkinter.Button(root, text = 'Button 0', command = on_button0_click)button0.pack()button1 = tkinter.Button(root, text = 'Button 1', command = on_button0_click)button1.pack()button2 = tkinter.Button(root, text = 'Button 2', command = on_button0_click)button2.pack()root.mainloop()为了节省篇幅,这里仅仅处理了 3 个按键。显然,这样的方式是很不合理的,在一个完整的计算器程序中,存在 20 多个按键,如果对每个按键都编写一个事件处理函数,就需要编写 20 多个事件处理函数。在下面的小节中,通过使用闭包解决这个问题。
一方面我们可以利用现有的CSS中文库:chinese-layout 来快速实现居中布局:中文布局 CSS 库这种方式不仅利于我们的学习,更加有助于我们的记忆。然后也会带领大家如何在不依赖于任何外界条件的情况下纯手工实现居中布局:绝对定位法绝对定位 + 负边距绝对定位 + 平移网格布局弹性布局表格布局
我们知道 Java 不支持多继承,而接口可以实现多继承的效果,但实现接口就必须实现里面所有的方法,有时候我们的需求只是实现其中某个方法,内部类就可以解决这些问题。下面示例中的 SubClass,通过两个成员内部类分别继承 SuperClass1 和 SuperClass2,并重写了方法,实现了多继承:// SuperClass1.javapublic class SuperClass1 { public void method1() { System.out.println("The SuperClass1.method1"); }}// SuperClass2.javapublic class SuperClass2 { public void method2() { System.out.println("The SuperClass2.method2"); }}// SubClass.javapublic class SubClass { // 定义内部类1 class InnerClass1 extends SuperClass1 { // 重写父类1方法 @Override public void method1() { super.method1(); } } // 定义内部类2 class InnerClass2 extends SuperClass2 { // 重写父类2方法 @Override public void method2() { super.method2(); } } public static void main(String[] args) { // 实例化内部类1 InnerClass1 innerClass1 = new SubClass().new InnerClass1(); // 实例化内部类2 InnerClass2 innerClass2 = new SubClass().new InnerClass2(); // 分别调用内部类1、内部类2的方法 innerClass1.method1(); innerClass2.method2(); }}编译执行 SubClass.java,屏幕将会打印:$ javac SubClass.java$ java SubClassThe SuperClass1.method1The SuperClass1.method2
和用户登录功能相似,我们传统的实现用户注册功能的业务逻辑还是正确的, 不管我们再怎么对用户注册功能进行优化,这个传统的用户注册功能业务流程始终不会发生改变,发生改变的只是我们的代码实现层。那么,传统实现方案中存在的弊端,也就存在于我们的功能代码实现层上。传统用户注册功能中存在的弊端,整体来说,最核心的也就只有一种弊端了。那就是,无论我们的用户注册功能所在的场景是不是在高并发环境中,都会直接对我们的数据库的访问压力造成较高的冲击, 如果在高并发环境中,我们的数据库可能会随着用户注册请求数量的激增,而直接崩溃,这是非常致命的一点。我们先来说非高并发环境下,用户注册数据在逻辑检测通过之后,会直接访问我们的数据库,并将用户注册数据插入到我们的数据库中,正常环境下我们的数据库还是能扛得住的,但是,即使能扛得住,数据库的压力在此时也是较高的。那么,在高并发环境下,由于我们没有在用户数据和数据库之间做处理,这就导致,当大量请求都在同一时刻到来时,我们的数据库的压力会直线上升,并最终导致数据库崩溃,这会直接造成我们的用户注册功能直接卡死,无法完成用户注册功能。针对这种情况,我们又该如何优化呢?下面就让我们来看一下,如何使用 RabbitMQ 来优化这一弊端。
接口定义了一些行为协议,而实现接口的类要遵循这些协议。implements 关键字用于实现接口,一个类可以实现一个或多个接口,当要实现多个接口时,implements 关键字后面是该类要实现的以逗号分割的接口名列表。其语法为:public class MyClass implements MyInterface1, MyInterface2 { ...}下面是实现了 Person 接口的 Student 类的示例代码:public class Student implements Person { @Override public void walk() { // 打印接口中的常量 System.out.println(Person.NAME); System.out.println("学生可以走路"); } @Override public void run() { System.out.println("学生可以跑步"); }}上述代码中,Student 类实现了 Person 接口。值得注意的是,可以使用接口名。常量名的方式调用接口中所声明的常量:String name = Person.NAME;
在了解了秒杀抢购的业务场景流程之后,接下来我们就需要实现这一业务场景了,那么,这种业务场景我们应该怎么用 RabbitMQ 和 Redis 去实现呢?在使用 RabbitMQ 打造扛得住的高并发环境系列小节内容的第二小节中,我们使用 RabbitMQ 消息通信中间件和 Redis 缓存中间件,对 RabbitMQ 自身的消息队列进行了改造,改造成了一种 Redis 承载的高可用的消息队列,在本节,我们就会用到这一高可用的消息队列。在实现上述实际高并发业务场景时,由于篇幅原因,我们并不会从用户登录开始,逐步地去实现每一个过程,我们只实现在秒杀抢购业务场景中,最核心的部分,也就是,当我们在秒杀抢购商品区域,点击立即购买这个秒杀按钮时,我们后台所需要应对高并发处理的内容。让我们来看看具体应该怎么设计实现吧。
编写文件 main.py,读取用户的命令并执行:import sysimport catimport lsimport cpimport rmimport help在第 1 行导入 Python 内置的 sys 模块在第 2 行到第 6 行导入用户自定义的 5 个模块cat 模块中定义了类 CatCommand,实现 cat 命令的功能ls 模块中定义了类 LsCommand,实现 ls 命令的功能cp 模块中定义了类 CpCommand,实现 cp 命令的功能rm 模块中定义了类 RmCommand,实现 rm 命令的功能help 模块中定义了类 HelpCommand,实现 help 命令的功能def readAndExecute(): print('> ', end = '') line = input() args = line.split() if len(args) == 0: return在第 1 行,定义函数 readAndExecute(), 读取用户输入的命令并执行在第 2 行,打印提示符 >在第 3 行,读取用户输入的命令在第 4 行,将用户输入的命令分割为多个单词假设用户输入命令是 cp test.txt test.bak经过 split() 后,args = [‘cp’, ‘test.txt’, ‘test.bak’] arg0 = args[0] if arg0 == 'exit': sys.exit() elif arg0 == 'cat': command = cat.CatCommand(args) elif arg0 == 'ls': command = ls.LsCommand(args) elif arg0 == 'cp': command = cp.CpCommand(args) elif arg0 == 'rm': command = rm.RmCommand(args) else: command = help.HelpCommand(args) command.execute()第 0 个参数是命令的名称,根据命令的名称构造相应的命令对象如果命令是 exit,调用 sys 模块的 exit 方法退出程序如果命令是 cat,使用 cat 模块中定义的类 CatCommand 实例化一个命令对象如果命令是 ls,使用 ls 模块中定义的类 LsCommand 实例化一个命令对象如果命令是 cp,使用 cp 模块中定义的类 CpCommand 实例化一个命令对象如果命令是 rm,使用 rm 模块中定义的类 RmCommand 实例化一个命令对象如果不是以上命令,使用 help 模块中定义的类 HelpCommand 实例化一个命令对象在最后,调用命令对象 command 的 execute 方法执行命令while True: try: readAndExecute() except IOError as error: print(error)在第 1 行,定义循环,循环反复执行函数 readAndExecute()执行函数 readAndExecute 时,可能会出现 IOError在 try 语句中执行 readAndExecute使用 except 语句捕获执行过程中的 IOError,并打印 error注意,当 readAndExecute 出现异常时,仅仅终止 readAndExecute,而不是终止程序
在说明冒泡排序的整个过程之后,接下来,我们看看如何用 Java 代码实现冒泡排序算法。import java.util.Arrays;public class BubbleSort { public static void main(String[] args) { //初始化需要排序的数组 int array[] = {9,2,11,7,12,5}; //对需要排序的数组进行排序 for (int i=1; i<array.length; i++){ //针对待排序序列中除了已经排序好的元素之外,重复排序工作 for(int j=0;j<array.length-i;j++){ //当相邻两个元素需要交换时,交换相邻的两个元素 if(array[j]>array[j+1]){ int temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } //打印出排序好的序列 System.out.println(Arrays.toString(array)); }}运行结果如下:[2, 5, 7, 9, 11, 12]代码中的第 8 行初始化一个需要排序的数组,后面按照从小到大的排序规则,实现了数组的排序。第 11 行是外层循环,不断地重复排序工作。第 14 行是内层循环,不断地实现每一次 “冒泡” ,将最大的一个元素找出。第 17 至第 21 行实现当相邻两个元素需要交换时,交换相邻的两个元素的功能。第 25 行打印出排序好的数组。
我们将以最常用的FileInputStream实现类为例进行学习。其他实现类大同小异,如有需要可翻阅官方文档。FileInputStream就是从文件流中读取数据,我们在imooc目录下新建一个文本文档Hello.txt,并输入如下内容:读取Hello.txt文件中数据的实例代码如下:import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class FileInputStreamDemo1 { public static void main(String[] args) throws IOException { // 实例化文件流 FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Colorful\\Desktop\\imooc\\Hello.txt"); for (;;) { int n = fileInputStream.read(); if (n == -1) { // read() 方法返回-1 则跳出循环 break; } // 将n强制转换为 char 类型 System.out.print((char) n); } // 关闭文件流 fileInputStream.close(); }}运行结果:Hello imooc!如果我们打开了一个文件并进行操作,不要忘记使用close()方法来及时关闭。这样可以让系统释放资源。
接下来我们就以电子地图为例,讲解如何用策略模式实现。不过先别着急,上一节我们学习了工厂模式,看起来电子地图也可以用工厂模式来实现。所以我们先来看看用工厂模式如何实现。下面的例子为了方便展示,接口入参只有出行方式,省略了出发地和目的地。计算结果是预估时长。
在说明选择排序的整个过程之后,接下来,我们看看如何用 Java 代码实现选择排序算法。import java.util.Arrays;public class SelectSort { public static void main(String[] args) { //初始化需要排序的数组 int array[] = {9, 2, 11, 7, 12, 5}; //依次进行选择排序,每次找出最小的元素,放入待排序的序列中 for(int i=0;i<array.length;i++){ //记录最小元素min和最小元素的数组下标索引minIndex int min = array[i]; int minIndex = i; //在未排序的序列中找出最小的元素和对应数组中的位置 for(int j=i+1;j<array.length;j++){ if(array[j] < min){ min = array[j]; minIndex = j; } } //交换位置 int temp = array[i]; array[i] = array[minIndex]; array[minIndex] = temp; } //打印出排序好的序列 System.out.println(Arrays.toString(array)); }}运行结果如下:[2, 5, 7, 9, 11, 12]代码中的第 7 行初始化一个需要排序的数组,后面按照从小到大的排序规则,实现了数组的排序。第 10 行是外层 for 循环,不断地重复选择排序工作。第 17 行是内层循环,不断地实现每一次 “选择 “,在未排序的序列中找出最小的元素和对应数组中的位置。第 24 至第 27 行实现了将未排序好的序列中的最小元素与需要排序的位置的元素进行交换的功能。第 31 行打印出排序好的数组。
前面我们使用过 Session 的 get()方法,大家还记得是怎么用的吗?stu = (Student) session.get(Student.class, new Integer(1));其实这个方法还可以传递第三个参数,好吧,先看一下方法的原型:public Object get(Class clazz, Serializable id, LockOptions lockOptions);LockOptions 类本质是对 LockMode 枚举类型的高级封装,提供了几种锁的使用:无锁的机制,Transaction 结束时,切换到此模式;hibernate 内部使用。public static final LockOptions NONE = new LockOptions(LockMode.NONE);查询的时候,Hibernate 自动获取锁;hibernate 内部使用。public static final LockOptions READ = new LockOptions(LockMode.READ);利用数据库的 for update 子句加锁(Select * from 表 for update),通过此选项实现悲观锁。public static final LockOptions UPGRADE = new LockOptions(LockMode.UPGRADE);悲观锁在实际生产环境中使用频率并不高,限制了并发的发生率,降低了程序的响应速度。编写一个简单的测试实例:第一个事务,查询加锁,使用 Thread.sleep()模拟事务操作时长;模拟时间不要太长,如果长时间不释放锁,其它等待事务会抛出等待超时异常。stu = (Student) session.get(Student.class, new Integer(1), LockOptions.UPGRADE);Thread.sleep(30000);transaction.commit();System.out.println("-----------第一个事务结束-----------");执行此实例,查看控制台输出信息,查询语句上添加了 for update,在模拟时长内事务没有结束。Hibernate: select student0_.stuId as stuId1_1_0_, student0_.classRoomId as classRoo5_1_0_, student0_.stuName as stuName2_1_0_, student0_.stuPassword as stuPassw3_1_0_, student0_.stuSex as stuSex4_1_0_ from Student student0_ where student0_.stuId=? for update第二个事务,进行查询、更新操作,此事务并不能马上更新成功,只有等待第一个事务结束后才能成功。 stu = (Student) session.get(Student.class, new Integer(1)); System.out.println("-------------更新-------------"); stu.setStuName("Hibernate 01"); transaction.commit(); System.out.println("--------------更新成功-----------");悲观锁的实现很简单,也很好理解,无非就是我用时你不能用的问题。
这里需要用到一点点的 JS 来获取页面滚动的距离,不会 JS 的同学先不要紧张,看不懂的话可以先记住怎么用:1220运行结果:这里就不用再给主盒子加上边距啦,因为在最上面的时候上栏完全透明,不会覆盖住内容的。通过我们对移动端的各种网站的观察发现,通常来说下面那栏是不会渐隐渐现的,所以这里下栏一直是固定的。如果同学们也想给下栏加入一个炫酷的效果,那就赶快来打开编辑器来改造一下这段代码吧!