前言 vue2.x 版本中的虚拟DOM 和 Diff 算法借鉴了 snabbdom 库,所以这篇文章以 snabbdom 库代码为例,来分析 虚拟 DOM 和 Diff 算法。本文仅仅是自己对 虚拟 dom 和diff 算法的一些分析和理解,如果有写的不对的地方,还请指正。本篇文章内部代码均是抽离出来的简化版本。想看源码的同学,请自行在 github 上搜索 snabbdom
虚拟 DOM 是什么 ? 虚拟 dom 其实就是由 js 对象,一个包含一些特定属性的对象,特定属性(比如 属性 sel 代表选择器,属性 data 是一个对象,里边包含 key 属性 calss 类名,id 等,children属性是数组,里边包含子节点,每个子节点也是一个包含一些特定属性的对象,text属性是文本节点,elm 表示真实 dom 节点)。这就是 虚拟 DOM 。如下图
在 sanbbdom 中,虚拟 dom 是通过 调用 h() 函数, h() 函数内部调用 vnode() 函数生成。
vnode() 函数 // 函数功能非常简单,就是把传入的 5 个参数组合成对象返回 /** * sel: 选择器,标签名字 * data: 存放选择器属性, key props 等 * children: 子节点 * text: 文本节点 * elm: 真实dom节点 */ export default function (sel, data, children, text, elm) { let key = data.
网上搜索,大多是这样的教程:
但是更新后的google已经没有这样的设置方式了
所以应该这样操作:
1.登陆Chrome
2.登陆这个网址:https://www.google.com/preferences
3. 勾选下图所示的设置:
不过,这样只是针对搜索页中的链接,能够跳转到新的页面
对于文章中的链接就不行了
因此推荐这个方法·:ctrl+shift+鼠标左键点击相关链接
前段时间看了中华石杉老师讲解的消息队列,感受很深刻,之前也了解MQ,在工作中也会用到,但是没有进行过系统的整理和反思,当看到一些问题时,一时间真不知道怎么回答。所以在此处进行记录一下,自己也对消息队列有个深刻的认识。
面试题
为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ、Pulsar不同MQ中间件有什么区别及适用场景?如何保证消息队列的高可用?有几百万消息持续积压几小时,你会如何处理?如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?如果让你写一个消息队列,该如何进行架构设计?······ 1 为什么使用消息队列 其实是问消息队列都有哪些使用场景,然后你说一下你项目里具体是什么业务场景,有什么技术挑战,如果不用MQ就会很麻烦,,在这个场景里为什么用,解决了什么问题,有什么好处。
MQ,消息队列,消息可以理解为一个业务现场,而队列则是保存这个业务现场的容器,而B服务对消息的处理,则是一个对业务现场的异步处理。所以,消息队列的本质,就是将某个业务现场暂存下来,异步处理。异步,解耦,消峰,MQ的三大主要应用场景。
异步。异步就是MQ的第一个能力。可以将一些非核心流程,如日志,短信,邮件等,通过MQ的方式异步去处理。这样做的好处是缩短主流程的响应时间,提升用户体验。
解耦。假设现在,日志不光要插入到数据库里,还要在硬盘中增加文件类型的日志,同时,一些关键日志还要通过邮件的方式发送给指定的人。那么,如果按照原来的逻辑,A可能就需要在原来的代码上做扩展,除了B服务,还要加上日志文件的存储和日志邮件的发送。但是,如果你使用了MQ,那么,A服务是不需要做更改的,它还是将消息放到MQ中即可,其它的服务,无论是原来的B服务还是新增的日志文件存储服务或日志邮件发送服务,都直接从MQ中获取消息并处理即可。这就是解耦,它的好处是提高系统灵活性,扩展性。
消峰。这个其实也很好理解,因为MQ的本质就是业务的排队。所以,面对突然到来的高并发,MQ也可以不用慌忙,先排好队,不要着急,一个一个来。消峰的好处就是避免高并发压垮系统的关键组件,如某个核心服务或数据库等。
2 使用消息队列有什么优缺点? 优点就是上面说的,在特殊场景下可以解决特定的问题,有其对应的好处:异步,解耦,削峰;
缺点:
系统可用性降低:系统引入的外部依赖越多,越容易挂掉,本来就是A系统调用BCD三个系统的接口就好了,这4个系统好好的,你偏加入个MQ进来,万一MQ挂了,业务流程走不下去了,整套系统崩溃了;系统复杂性提高:硬生生加个MQ进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?等等一系列问题;一致性问题:A系统处理完了直接返回成功了,人都以为你这个请求成功了。但是在异步处理过程中,BD两个系统写库成功,但是C系统写库失败,怎么办,数据不一致了; 所以消息队列,实际上是一种非常复杂的架构,引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉。最后,系统复杂度提升了一个数量级,可能比原来的复杂10倍。但是关键时刻,用还是要用的。
3 Kafka、ActiveMQ、RabbitMQ、RocketMQ、Pulsar不同MQ中间件有什么区别及适用场景? 此处见下表总结
4 如何保证消息队列的高可用 RabbitMQ的高可用
RabbitMQ是比较有代表性的,因为是基于主从做高可用的,我们就以他为例讲解第一种MQ的高可用性怎么实现。rabbitMQ有三种模式:单机模式,普通集群模式,镜像集群模式;
(1)单机模式
demo级别,一般自己本地启动玩玩,生产环境没人用;
(2)普通集群模式
意思就是在多台机器上启动多个rabbitMQ实例,每个机器启动一个。但是你创建的queue,只会放在一个rabbitmq实例上,但是每个实例都同步queue的元数据。等你消费的时候,实际上如果连接到了另外一个实例,那么那个实例就会从queue所在实例上拉取数据过来。
这种方式确实很麻烦,也不怎么好,没做到所谓的分布式,就是普通集群。因为这导致你要么消费者每次随机连接一个实例,然后拉取数据,要么固定连接那个queue所在实例消费数据。前者有数据拉取的开销,后者导致单实例性能瓶颈。
而且如果那个放queue的实例宕机了,会导致接下来其他实例就无法从那个实例拉取。如果你开启了消息持久化,消息不一定会丢,但是得等这个实例回复了,然后才能从这个queue拉取数据。
所以这个事就比较尴尬了,这就没有什么所谓的高可用性可言了,这个方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个queue的读写操作。
(3)镜像集群模式
这种模式,才是所谓的rabbitMQ的高可用模式,跟普通集群模式不一样的是,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息进行同步到多个实例的queue里。
这样的话,好处在于,你任何一个机器宕机了,别的机器都可以用。坏处在于,第一,性能开销太大,消息同步所有机器,导致网络带宽压力和消耗很重;第二,没有扩展性,如果某个queue负载很重,你加机器,新增的机器也包含了这个queue的所有数据,并没有办法线性扩展你的queue。
如何开启镜像集群模式呢?rabbitmq有很好的管理控制台,在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点的,也可以要求就同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上。
Kafka的高可用
Kafka,一个最基本的架构认识:多个broker组成,每个broker是一个节点;你创建一个topic,这个topic就可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition只放一部分数据。
这就是天然的分布式消息队列,就是说一个topic的数据,是分散放在多个机器上的,每个机器就放一部分数据。实际上rabbitmq之类的并不是分布式消息队列,就是传统的消息队列,只不过提供了一些集群、HA的机制而已,因为无论怎么玩儿,rabbitmq一个queue的数据都是放在了一个节点里的,镜像集群下,也是每个节点都放这个queue的完整数据,
kafka 0.8以前,是没有HA机制的,就是任何一个broker宕机了,那个broker上的partition就废了,没法写也没法读,没有什么高可用性可言。
0.8以后,提供了HA机制,就是replica副本机制。每个partition的数据都会同步到其他机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都跟这个leader打交道,然后其他replica就是follower。写的时候,leader会负责把数据同步到所有follower上去,读的时候就直接读leader上数据即可。只能读leader?很简单,要是你可以随机读写每个follower,那么就要关心数据一致性的问题,系统复杂度太高,容易出问题。kafka会均匀的将一个partition的所有replica分布在不同机器上,这样才可以提高容错性。
现在就有高可用性了,因为如果某个broker宕机了,那个broker上面的partition在其他机器上都有副本的,如果这上面有某个partition的leader,那么此时会重新选举一个新的leader出来,大家继续读写那个新的leader即可。
写数据的时候,生产者就写leader,然后leader将数据落地写本地磁盘,接着其他follower自己主动从leader来pull数据,一旦所有follower同步好数据了,就会发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。(当然这只是其中一种模式,还可以适当调整这个行为)
消费的时候,只会从leader去读,但是只有当一个消息已经被所有follower都同步成功返回ack的时候,这个消息才会被消费者读到。
5 有几百万消息持续积压几小时,你会如何处理? 几千万条数据在MQ里积压了七八个小时,从下午4点多,积压到了晚上很晚,10点多,11点多。这个是我们真实遇到过的一个场景,确实是线上故障了,这个时候要不然就是修复consumer的问题,让他恢复消费速度,然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。一个消费者一秒是1000条,一秒3个消费者是3000条,一分钟是18万条,1000多万条
所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概1小时的时间才能恢复过来
一般这个时候,只能操作临时紧急扩容了,具体操作步骤和思路如下:
1)先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉
2)新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量
3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息
6 如何解决消息队列的延时以及过期失效问题? 假设你用的是rabbitmq,rabbitmq是可以设置过期时间的,就是TTL,如果消息在queue中积压超过一定的时间就会被rabbitmq给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。
这个情况下,就不是说要增加consumer消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。
这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。
假设1万个订单积压在mq里面,没有处理,其中1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到mq里去再补一次
7 消息队列满了以后该怎么处理? 如果走的方式是消息积压在mq里,那么如果你很长时间都没处理掉,此时导致mq都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。
8 如果让你写一个消息队列,该如何进行架构设计? 其实聊到这个问题,一般面试官要考察两块:
(1)你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个mq的架构原理;
(2)看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来;
说实话,我一般面类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,大多数人就是平时埋头用,从来不去思考背后的一些东西。类似的问题,我经常问的还有,如果让你来设计一个spring框架你会怎么做?如果让你来设计一个dubbo框架你会怎么做?如果让你来设计一个mybatis框架你会怎么做?
新年新气象!今天开始【凌晨GIS】第一篇原创文章,希望大家在新的一年里牛气冲天,直上云霄!
这期小编跟大家分享的是如何制作一张人口重心迁移地图。
首先百度一下定义,人口重心又称人口中心,为地区人口分布的综合统计与表示方法之一。人口重心是研究人口分布变动的重要手段,用于揭示人口(自然与机械)变动的地区不平衡性,进而显示区域经济发展的差异性。
以美国1990年和2000年的人口数据为例,分别计算美国主体区域质心和两期的人口重心,最后得到美国1990年至2000年的人口重心迁移专题地图。
其实原理很简单,大致思路就是每个点坐标(行政区质心)×点人口数据,然后全加起来除以总人口,按照公式分别算出x,y坐标这样就得到总的人口重心了。
接下来看看具体怎么操作。。。
首先,看下我们有的数据是美国主体区域的边界,属性表包含1990和2000年的人口数。
具体操作步骤:
1.计算质心坐标
分别添加x,y字段,右键选择计算几何分别计算出各州市的质心坐标。
2.人口加权
再添加4个字段,分别命名为x_1990,y_1990,x_2000,y_2000,右键x_1990字段选择字段计算器,输入公式[x]*[POP1990],由此得到经过人口加权后的坐标,其3三个字段同理。
3.计算平均坐标
将属性表导出为EXCEL(为了计算过程更直观,也可直接在属性表里统计),在转换工具中找到表转Excel工具,将表格导出。分别计算各年人口总数及加权坐标总和,再将各年份的坐标总和除以对应年份的总人口数(就是加权平均的意思),就得到1990年和2000年的人口重心坐标啦。 4.人口重心坐标可视化
将人口重心坐标整理成下面的表格,在arcmap中添加表格后右键选择显示xy数据,人口重心就显示出来了,通过属性设置中的符号系统的类别设置对其进行区分显示。
5.求取主体区域质心坐标
大致思路是先提取主体区域整体边界,再利用计算几何求取质心。在工具箱找到数据管理工具-制图综合-融合工具,将主体区域的边界融合为一个边界。最后找到要素转点工具直接提取出质心点。 6.制图表达
前几步我们已经将人口重心和主体区域质心求出,接下来就是通过符号化设置进行制图表达,这个在以前的文章已经讲过了,这里就不一一赘述了。只有一点,如果想得到这种黑色背景的地图呢,直接右键数据框设置一下背景颜色,然后将要素的颜色设置为亮一点就可以了。
最后插入图例、比例尺、指北针,效果图就出来啦。。。
好啦,这期的分享就到这里了,如果小伙伴们觉得内容讲的还算详细,希望您能给小编一个大大的肯定,这也是小编坚持下去的动力,谢谢啦,下期再见!
家中闲置一台08年的笔记本(没有无线无卡),自己加装了一个2G的内存条,食之无味弃之可惜,思量再三准备重装Ubuntu18.04的系统当做小型服务器使用。因此记录下安装步骤以及分区方法。
目录
1. 准备Ubuntu18.04系统
2. 制作U盘启动
2.1 下载U盘启动制作工具
2.2 选择Ubuntu镜像
2.3 制作U盘启动
3. 系统安装
3.1 U盘启动
3.2 系统安装设置
3.2.1. 选择语言
3.2.2. 选择键盘布局
3.2.3. 选择更新和其他软件
3.2.4. 选项安装类型
3.2.6 选择时区
3.2.7 设置用户名和密码
3.2.7 安装完成
1. 准备Ubuntu18.04系统 网上获取Ubuntu系统的方法有很多,我这里是在Ubuntu镜像网站下载的Ubuntu18.04的最后一个发布版18.04.5
2. 制作U盘启动 2.1 下载U盘启动制作工具 我使用的是UltralSO,下载并安装即可。
2.2 选择Ubuntu镜像 安装完成之后打开软件,选择 “继续试用”即可,如下图
选择下载好的Ubuntu镜像,依次选择工具栏 “文件”-->"打开" ,选择下载Ubuntu打开即可,如下图
2.3 制作U盘启动 选择好镜像之后插上U盘,选择菜单 “启动” --> "写入硬盘" 选择要制作U盘镜像的U盘,如下图
选择好之后,点击 “写入” 等待完成即可。
3. 系统安装 3.1 U盘启动 首先笔记本插上前面制作好的U盘,重启按 “F8” 或者其他的键键入boot模式 选择U盘启动(不同的笔记本根据型号百度搜索如何设置即可),如下图,
3.2 系统安装设置 3.2.1. 选择语言 首先选择语言 “中文” ,然后选择“安装Ubuntu”
1.声明 当前内容主要用于记录在CentOS7中安装和启动当前的Neo4j(这个图形数据库)
小心版本问题:
Neo4j版本为4.0或以上需要JDK11(本人下了一个最新版,发现需要JDK11,结果使用下面3.5的版本)Neo4j版本为3.5的需要JDK1.8
登录用户名和密码:
2.下载安装 1.下载:3.5.26版本的Neo4j,进去点击here即可
2.上传到Linux的CentOS7服务器上面
3.解压:tar xvf neo4j-community-3.5.26-unix.tar.gz(解压后就行了)
3.启动并开放给外界使用 1.修改conf文件夹下的neo4j.conf:vi conf/neo4j.conf
修改下面两个地方修改为本机Linux地址:
2.启动neo4j:./bin/neo4j start
查看启动状态:./bin/neo4j status
此时表示启动成功!
注意开放Linux中的端口:7474这个端口
4.使用Windos访问 在浏览器中输入:192.168.1.100:7474/,然后输入用户名和密码
第一次需要修改密码(密码不能为neo4j),修改完毕后显示
5.关闭Neo4j 执行命令:./bin/neo4j stop
此时关闭成功!
List转换成String字符串思路分析: 1.List转化成String[],再通过String[]数组遍历拼接成String字符串; 2.List转化成String存到StringBuffer中,再通过StringButter中的append()方法拼接成String字符串; 注意:StringBuffer字符串变量、StringBuilder字符串变量在方式二和方式三种可以把StringBuffer换成StringBuilder,但需要了解二者之间的区别,网址:https://blog.csdn.net/u013131716/article/details/99628104
package com.test01.listtostring;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class ListToStringTest {
/*
* List转换成String字符串
* 方式一:
* 分析:List转化成String[],再通过String[]数组遍历拼接成String字符串
*
* List内容:[laoyi, laoer, laosan]
* 结果:laoyi,laoer,laosan
* */
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
@Test
public void listToString01(){
List list = new ArrayList<>();
list.add("laoyi");
list.add("laoer");
list.add("laosan");
System.out.println("List数组:"+list);
if(list == null && list.size() == 0){
System.out.println("list中的内容为空!");
}else{
String[] strArrStrings = new String[list.size()];
String resultString = "";
for(int i=0;i<=list.
在本教程中,我们将向您介绍在运行systemd的linux系统上如何使用systemctl命令工具有效的控制系统和服务.
Systemctl 介绍 Systemctl是一个systemd工具,主要负责控制systemd系统和服务管理器。
Systemd是一个系统管理守护进程、工具和库的集合,用于取代System V初始进程。Systemd的功能是用于集中管理和配置类UNIX系统。
在Linux生态系统中,Systemd被部署到了大多数的标准Linux发行版中,只有为数不多的几个发行版尚未部署。Systemd通常是所有其它守护进程的父进程,但并非总是如此。
开始Systemd和Systemctl 基础工具之旅 01、首先检查系统上是否安装了systemd以及当前安装的Systemd的版本是什么?
# systemd --versionsystemd 215+PAM +AUDIT +SELINUX +IMA +SYSVINIT +LIBCRYPTSETUP +GCRYPT +ACL +XZ -SECCOMP -APPARMOR 从上面的例子可以清楚地看出,我们已经安装了systemd 215版本。
02.检查systemd和systemctl的二进制文件和库的安装位置。
# whereis systemd systemd: /usr/lib/systemd /etc/systemd /usr/share/systemd /usr/share/man/man1/systemd.1.gz# whereis systemctlsystemctl: /usr/bin/systemctl /usr/share/man/man1/systemctl.1.gz 03.检查systemd是否正在运行。
# ps -eaf | grep [s]ystemdroot 1 0 0 16:27 ? 00:00:00 /usr/lib/systemd/systemd --switched-root --system --deserialize 23root 444 1 0 16:27 ? 00:00:00 /usr/lib/systemd/systemd-journaldroot 469 1 0 16:27 ? 00:00:00 /usr/lib/systemd/systemd-udevdroot 555 1 0 16:27 ?
chap6习题练习
Chp6 面向对象三大特性
Key Point
● 封装/数据隐藏
● 继承的基本语法
● 访问修饰符
● 对象创建过程
● super 关键字
● 方法覆盖
● 多态的基本语法和使用
● instanceof
● 多态用在参数和返回值上
练习
1. (继承、this 和super 关键字)有以下代码
class Super{
public Super(){
System.out.println("Super()");
}
public Super(String str) {
System.out.println("Super(String)");
}
}
class Sub extends Super{
public Sub(){
System.out.println("Sub()");
}
public Sub(int i){
this();
System.out.println("Sub(int)");
}
public Sub(String str){
super(str);
System.out.println("Sub(String)");
}
}
public class TestSuperSub{
public static void main(String args[]){
1前言
在创建项目中,IDEA提供了很多项目模板,比如Spring MVC模板,可以直接创建一个基于Maven的Spring MVC的demo,各种配置都已经设定好了,直接编译部署就可以使用。
最开始自己创建maven web项目时,要么创建一个springmvc项目进行修改,要么创建了一个maven项目(不是web项目),自己添加webapp目录添加配置web.xml文件,以及添加web moudle,配置属性等等。
另外之前总结的几篇Intellij使用文章,里面多多少少都还有点问题,请以本篇为准。
在Intellij 13.1以后的版本创建项目时的界面发生了调整,不过具体步骤都还是差不多的。本文使用的版本为13.1.2,
2创建Maven Web项目
new project——maven——勾选 create from archetype 选中webapp
下一步 (图略)填写项目的 groupId artifactId version 后Next 出现以下界面
Next后添加项目名称 Finish
第一次使用时,可能需要下载archetype webapp的插件 有时候网络不好 可能需要很长时间 不过以后就好了
刚创建完之后,需要稍等一下,加载相应配置文件,之后如下图所示
此时的项目已经是Maven的 webapp项目了
这个时候IDE还有缓存,需要在右侧Maven project中 刷新一下,加载pom中的配置
(以后在pom中添加jar的依赖后,都要点击maven project的刷新按钮 刷下缓存)
这个时候Maven webapp项目就彻底创建好了 不需要再在project structure进行配置了。
如下两图,都是默认给配置好的,不需要进行修改:
3关于Maven Web项目的配置说明
Project Structure中Artifaces的配置
如上图所示,在Aritifaces中有两个war,一个是war 一个是war exploded,在配置服务器时需要选择war exploded
千万要注意,这两个war都不应该是自己添加的,而是根据maven中的配置自己生成的
之前没有这两个war的时候,我都是自己创建,往往后果就是项目在编译后缺少jar包,依赖的jar没有自动拷贝
其实Intellij中的mavan项目配置,都是依赖于pom中的配置,如果pom中的配置正确,就不需要修改项目的IDE配置
首先pom要有war,如果pom中配置了这个,Aritifaces就会有上面的两个war
其次pom要有
test 这个关系到上图中的output
directory中的配置,finalName作为target下的项目目录,以及会创建对应名字的war包(比如test.war)
配置src/main/java
如上图,src/main目录下缺少java目录,我们可以右键创建目录java,然后再modules中配置为sources目录
这样src/main/java图标就变成了蓝色,作为java源代码区了。
防抖和节流的总结: 1. 防抖(debounce) 防抖是限制频率,多次出发一次执行。
2. 节流(throttle) 节流是限制次数,限制规定时间内执行次数。
这篇文章以 underscore.js 工具库中的 防抖和节流函数作为演示,并探究其源码。
underscore.js 中文文档
本篇文章只对其原理进行探究,使用方式不做太多概述,如果不知道怎么使用的同学,可自行百度。
**下边是 html 演示代码, 里边引入debounce.js throttle.js 两个文件 **
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>防抖 节流</title> <style> #container { width: 100%; height: 400px; background: #999; text-align: center; line-height: 400px; color: #fff; } </style> </head> <body> <div id="container"></div> <div id="btn">取消</div> <!-- 这里引入 underscore.js CDN 地址,可以参考其使用方式和完成的效果是否一致 --> <script src="https://cdn.bootcdn.net/ajax/libs/underscore.js/1.9.1/underscore.js"></script> <!-- 防抖实现 --> <script src="
展开全部
最快k*O(n)~O(n),将前K个数放入一个k的数组中,按序放,对K+1到N的数,依次如e68a843231313335323631343130323136353331333433663038果比K数组总的小,将K中最大的取出,放入最小的数,一直维持K个,最后就是最小的K个。
import java.util.Arrays;
/**先给数值里的值进行从小到大排序,取从0到k-1索引的数组值
* @创建一个数组,包含11个元素,求5个最小的数
* @author Administrator
public class ArrayTest {
public static void main(String[] args) {
//最小元素的个数
int k=5;
//创建一个数组,
int[] arr = {1,3,45,33,67,12,88,11,46,32,89};
//操作数组的工具Arrays,sort()方法自动排序,由小到大
Arrays.sort(arr);
for(int i=0;i
System.out.println(arr[i]);
}
扩展资料:
此处数组是程序中声明的变量数组。它们不同于控件数组,控件数组是在设计时通过设置控件的 Index 属性规定的。变量数组总是连续的;与控件数组不同的是,不能从一个数组的中部加载或卸载数组元素。
一个数组中的所有元素具有相同的数据类型(在C、C++、Java、pascal中都这样。但也并非所有涉及数组的地方都这样,比如在Visual Foxpro中的数组就并没这样的要求)。当然,当数据类型为 Variant 时,各个元素能够包含不同种类的数据(对象、字符串、数值等等)。可以声明任何基本数据类型的数组,包括用户自定义类型和对象变量。
参考资料来源:百度百科-数组
Linux 内核 vs Windows 内核 良许Linux 今天
以下文章来源于小林coding ,作者小林coding
小林coding
时而图解技术,时而拍拍猫片,时而说说杂事
Windows 和 Linux 可以说是我们比较常见的两款操作系统的。
Windows 基本占领了电脑时代的市场,商业上取得了很大成功,但是它并不开源,所以要想接触源码得加入 Windows 的开发团队中。
对于服务器使用的操作系统基本上都是 Linux,而且内核源码也是开源的,任何人都可以下载,并增加自己的改动或功能,Linux 最大的魅力在于,全世界有非常多的技术大佬为它贡献代码。
这两个操作系统各有千秋,不分伯仲。
操作系统核心的东西就是内核,这次我们就来看看,Linux 内核和 Windows 内核有什么区别?
内核 什么是内核呢?
计算机是由各种外部硬件设备组成的,比如内存、cpu、硬盘等,如果每个应用都要和这些硬件设备对接通信协议,那这样太累了。
所以,这个中间人就由内核来负责,让内核作为应用连接硬件设备的桥梁,应用程序只需关心与内核交互,不用关心硬件的细节。
内核
内核有哪些能力呢?
现代操作系统,内核一般会提供 4 个基本能力:
管理进程、线程,决定哪个进程、线程使用 CPU,也就是进程调度的能力;
管理内存,决定内存的分配和回收,也就是内存管理的能力;
管理硬件设备,为进程与硬件设备之间提供通信能力,也就是硬件通信能力;
提供系统调用,如果应用程序要运行更高权限运行的服务,那么就需要有系统调用,它是用户程序与操作系统之间的接口。
内核是怎么工作的?
内核具有很高的权限,可以控制 cpu、内存、硬盘等硬件,而应用程序具有的权限很小,因此大多数操作系统,把内存分成了两个区域:
内核空间,这个内存空间只有内核程序可以访问;
用户空间,这个内存空间专门给应用程序使用;
用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。
因此,当程序使用用户空间时,我们常说该程序在用户态执行,而当程序使内核空间时,程序则在内核态执行。
应用程序如果需要进入内核空间,就需要通过「系统调用」,下面来看看系统调用的过程:
内核程序执行在内核态,用户程序执行在用户态。当应用程序使用系统调用时,会产生一个中断。发生中断后, CPU 会中断当前在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把 CPU 执行权限交回给用户程序,回到用户态继续工作。
Linux 的设计 Linux 的开山始祖是来自一位名叫 Linus Torvalds 的芬兰小伙子,他在 1991 年用 C 语言写出了第一版的 Linux 操作系统,那年他 22 岁。
Spring DAO之JDBC
Spring提供的DAO(数据访问对象)支持主要的目的是便于以标准的方式使用不同的数据访问技术, 如JDBC,Hibernate或者JDO等。它不仅可以让你方便地在这些持久化技术间切换, 而且让你在编码的时候不用考虑处理各种技术中特定的异常。
为了便于以一种一致的方式使用各种数据访问技术,如JDBC、JDO和Hibernate, Spring提供了一套抽象DAO类供你扩展。这些抽象类提供了一些方法,通过它们你可以 获得与你当前使用的数据访问技术相关的数据源和其他配置信息。
Dao支持类:
JdbcDaoSupport - JDBC数据访问对象的基类。 需要一个DataSource,同时为子类提供 JdbcTemplate。
HibernateDaoSupport - Hibernate数据访问对象的基类。 需要一个SessionFactory,同时为子类提供 HibernateTemplate。也可以选择直接通过 提供一个HibernateTemplate来初始化, 这样就可以重用后者的设置,例如SessionFactory, flush模式,异常翻译器(exception translator)等等。
JdoDaoSupport - JDO数据访问对象的基类。 需要设置一个PersistenceManagerFactory, 同时为子类提供JdoTemplate。
JpaDaoSupport - JPA数据访问对象的基类。 需要一个EntityManagerFactory,同时 为子类提供JpaTemplate。
本节主要讨论Sping对JdbcDaoSupport的支持。
下面是个例子:
drop table if exists user;
/*==============================================================*/
/* Table: user */
/*==============================================================*/
create table user
(
id bigint AUTO_INCREMENT not null,
name varchar(24),
age int,
primary key (id)
);
public class User {
private Integer id;
当我们在删除最后一页的最后一条数据时,我们想要的效果是回到上一页,查询处上一页的数据显示出来,当前页码变成上一页,而真正删除的时候显示出来是个空的,而且页码也还在当前页
这时我们就需要去判断当前删除的是否为最后一页的最后一条数据,如果是,我们删除后要将当前页码-1再查询,这样页码回到了上一页,查询出来的也是上一页的数据了,话不多说下面爷直接上代码:
// template模板 <el-table-column label="操作"> <template slot-scope="scope"> <el-button type="danger" @click="del(scope.row.id)"> 删除 </el-button> </template> </el-table-column> // 删除方法: async del(id) { // 调用接口根据id删除 await deleteLexiconClassification({ id }); // 判断当前删除的是否为最后一页的最后一个记录 // 遍历词库列表 this.repositoryCategoryList.forEach((item, index) => { // 如果当前id是要删除的id if (item.id === id) { // 判断规则:(当前页码-1)*页容量+要被删除项的索引+1===总条数 && 判断被删项索引是否为0 && 当前不是第一页 if ( (this.currentPage - 1) * this.pageSize + index + 1 === this.total && index === 0 && this.currentPage > 1 ) { // 如果是就将页码回到上一页 this.
UndefinedError: ‘int object’ has no attribute ‘endswith’ 这里是因为给图表设置width,height里面要给字符串,不能是int
编写简单的WDF驱动程序 在创建新的KMDF或UMDF程序时,必须选择一个不多于32个字符的驱动程序名称。此长度限制在wdfglobals.h中定义。如果驱动程序的名称超出最大尺度,则驱动程序无法加载。每个基于框架的驱动程序都包含一个DriverEntry例程和一组事件回调函数,框架在发生特定于对象的事件时将调用该函数。 基于框架的简单驱动程序可能由以下内容组成: DriverEntry例程,在加载驱动程序并调用WdfDriverCreate时调用。
一个EvtDriverDeviceAdd事件回调函数,当即插即用(PnP)管理器使用与驱动程序支持的硬件ID匹配的硬件标识符(ID)报告设备检测时,框架将调用该函数。
可以通过提供一个INF文件来指定驱动程序支持的硬件ID,操作系统会在第一次将设备连接到计算机时使用该文件来安装驱动程序。
驱动程序的EvtDriverDeviceAdd回调函数调用WdfDeviceCreate为检测到的设备创建框架设备对象。
当i/o管理器向驱动程序发送i/o请求时,框架调用请求处理程序(如EvtIoDefault回调函数)。
当i/o管理器将i/o请求发送到驱动程序时,框架会将请求放入i/o队列,然后通过调用请求处理程序通知你的驱动程序。
驱动程序必须为每个设备创建至少一个i/o队列,以便驱动程序可以接收设备的i/o请求。若要创建i/o队列,驱动程序将调用WdfQueueCreate,这将创建一个框架队列对象并注册设备的请求处理程序。
WDF驱动程序例程的DriverEntry DriverEntry是加载驱动程序后调用的第一个驱动程序提供的例程。它负责初始化驱动程序。
语法
NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ); 参数
DriverObject:指向驱动程序_对象结构的指针,该结构表示驱动程序的WDM驱动程序对象。
RegistryPath:指向UNICODE_字符串结构的指针,该字符串结构指定注册表中驱动程序的Parameters项的路径。
返回值
如果例程成功,则必须返回状态“_成功”,否则,它必须返回在ntstatus中定义的错误状态值之一。
备注
与所有WDM驱动程序一样,基于框架的驱动程序必须具有DriverEntry例程,该例程是在加载驱动程序后调用的。基于框架的驱动程序的DriverEntry例程必须:
激活WPP软件跟踪
DriverEntry应包含一个WPP_INIT_TRACING宏来激活软件跟踪。调用WdfDriverCreate
对WdfDriverCreate的调用使驱动程序可以使用Windows驱动程序框架接口。在调用WdfDriverCreate之前,驱动程序无法调用其他框架例程。分配任何特定于设备的系统资源和可能需要的全局变量
通常驱动程序将系统资源与单个设备相关联。因此,基于框架的驱动程序在EvtDriverDeviceAdd回调中分配大多数资源,在检测到各个设备时将调用该回调。因为UMDF驱动程序的多个实例可能由单独的Wudfhost实例托管,所以全局变量可能无法在UMDF驱动程序的所有实例中使用。从注册表获取驱动程序特定的参数
某些驱动程序从注册表中获取参数,这些驱动程序可以调用WdfDriverOpenParametersRegistryKey来打开包含这些参数的注册表项。提供DriverEntry返回值 串行(KMDF)示例驱动程序的DriverEntry例程 NTSTATUS DriverEntry( IN PDRIVEROBJECT DriverObject, IN PUNICODE RegistryPath ) { WDF_DRIVER_CONFIG config; WDFDRIVER hDriver; NTSTATUS status; WDF_OBJECT_ATTRIBUTES attributes; SERIAL_FIRMWARE_DATA driverDefaults; //初始化WPP 跟踪 WPP_INIT_TRACING( DriverObject, RegistryPath ); SerialDbgPrintEx( TRACING_LEVEL_INFORMATION, DBG_INIT, "Serial Sample (WDF Version) - Built %s %s\n"
依据百度后果,揣测问题可能是,当应用ip创立ZooKeeper对象时,如果host中没有ip到主机名的映射,ZooKeeper创立过程中会调用ZooInetAddress.getHostName()这个办法从网络中获取主机名,这里消耗工夫太长所致。通过调试定位到SaslServerPrincipal类的
static String getServerPrincipal(WrapperInetSocketAddress addr, ZKClientConfig clientConfig) {
String configuredServerPrincipal = clientConfig.getProperty(ZKClientConfig.ZOOKEEPER_SERVER_PRINCIPAL);
if (configuredServerPrincipal != null) {
// If server principal is already configured then return it
return configuredServerPrincipal;
}
String principalUserName = clientConfig.getProperty(
ZKClientConfig.ZK_SASL_CLIENT_USERNAME,
ZKClientConfig.ZK_SASL_CLIENT_USERNAME_DEFAULT);
String hostName = addr.getHostName();
......
if (canonicalize) {
WrapperInetAddress ia = addr.getAddress();
......
String canonicalHostName = ia.getCanonicalHostName();
//avoid using literal IP address when security check fails
if (!canonicalHostName.equals(ia.getHostAddress())) {
hostName = canonicalHostName;
}
一、什么是惯性导航系统? INS(Inertial Navigation System)惯性导航系统GPS(Global Positioning System)全球卫星导航系统INS和GPS的区别:INS告诉你有没有移动、GPS告诉你在哪里 打开 GPS 接收器,假设一切正常,在很短的时间后,它将生成位置测量值。接收器生成的位置测量值非常具体,不存在 GPS 的不精确性。它指出“您在这个纬度和这个经度”- 换句话说,它用已知的坐标系提供给我们一个绝对位置。惯性导航系统不能这样工作。就它们而言,它们生成的测量值是相对于他们最后已知的位置。因此,即使在惯性导航系统已经打开几分钟后,它也不能说“您在这个纬度和这个经度”,但它可能会说,“您没有从开始位置移动“。
4.为什么使用INS
惯性导航系统可以计算出它所处的位置与它的起点–因此,如果你告诉INS的起点,它就可以根据自己的测量结果轻松地计算出现在的位置。这就是宇宙飞船、潜艇、飞机和导弹如何使用INS成功导航的原因–因为它们知道自己的起点。
二、INS到底是如何工作的 INS的组成 惯性导航系统由两个不同的部分组成;第一个是IMU(惯性测量单元),有时也称为IRU(惯性参考单元)。这是提供加速度和角速度测量的加速度计和陀螺仪的总称。第二部分是导航计算机。导航计算机从IMU获取测量数据,并利用这些数据计算INS的相对位置、方向和速度。
导航计算机种类 目前使用的导航计算机基本上有两种,一种是稳定平台,一种是绑带式导航仪。
INS是如何工作的呢?
三、参考框架 需要注意IMU放的位置,根据不同的位置需要重新定义坐标的方向。
四、加速度计 加速计是大多数惯性导航系统中使用的传感器类型之一。从它们的名字就可以猜到,它们测量的是加速度,而不是速度;可以根据加速度计算速度、位移。加速度计静止时垂向的加速度计的读数为-9.81 m/s²,这是因为加速度计实际测量的是相对于自由落体的加速度 一个自由落体的加速度计,天空中落下时在加速,显示出零加速度。
因此,总结一下,加速度计在测量直线运动方面很出色,但在旋转方面就不擅长了,这就是陀螺进来。 五、陀螺仪 惯性导航系统大多数惯性导航系统中使用的加速计是测量直线运动的好帮手,但它们不擅长测量旋转运动,这就是陀螺仪的作用。陀螺仪根本不关心直线运动,只关心旋转。
六、死亡计算 利用三个加速度计和三个陀螺仪的测量数据,该系统的运行情况如下。OxTS 惯性导航系统追踪它在三维空间中的位置。它使用一种叫做**“死亡计算”**的过程来实现这一目的。 您可以看到,INS 最初是静止的,并且与图像成直角对齐,其 x 轴指向上方。然后,图像显示了其他三个位置以及它们之间的传感器记录的信息。当然,实际上,INS 每秒更新其位置数十或数百次,但是在本例中,仅在发生关键变化时才显示位置更新,以便于理解。因此,在时间为零时,INS 是静止的(并且不知道它在哪里)。然后,X 轴加速度计上出现 5 m/s² 的加速度 1 秒,其速度为 5 m/s(或 18 km/h)。随后立即完全停止运行 - 在 0.5 秒内检测到 -10 m/s² 的加速度。由于其他传感器上没有记录其他测量值,因此捷联式导航仪可以轻松地确定它沿 x 轴方向移动了 3.75 米。同样,此时 INS 不知道它在哪里,因为我们没有提供任何位置信息。只要INS停在位置更新1处,z轴陀螺仪就会检测到一个90°/s的值,持续0.5秒;因此它知道它刚刚向顺时针方向转了45°。同样,当该运动完成后,INS再次在X轴加速度计上看到加速度。这次是1米/秒²,持续10秒,然后是-5米/秒²,持续2秒。使用与之前相同的技术,INS 可以计算出它现在已经以 45°角从位置更新 1 时的位置再往前移动了 60 米。这就是前面我们说到INS的位置更新是相对于最后一个已知位置而言的。
最后的移动与之前的移动是不同的。在位置更新2的时候,你可以看到INS已经旋转了,所以它的方向和最初的方向是一样的。然而,当它向位置3移动时,我们可以看到INS现在正与其测量轴(IMU框架)成一定角度移动–它正以135°的方位向后和向右移动。由于此运动,同时在 x 轴和 y 轴上录得加速度。也没有引起 INS 停止的负加速度 - 因此,尽管加速度计上的测量值在 1 秒后降为零,但导航计算机知道该装置仍然具有速度。在这种情况下,它以 7.
这篇文章主要探究 call、apply、bind 的实现原理,对于使用方法不做太多概述。如果有不太了解的同学可以查看JavaScript MDN 官方文档 call apply bind 详解
前言 我们都知道 call、apply、bind 三个方法是用来改变 this 指向的,但是也只局限于会用状态。具体的实现原理,却没有思考过。所以有必要研究一下。在明白起原理的情况下,才能用的更好。
具体实现 call 众所周知这三个方法都是提供给 函数使用的,函数通过这三个方法调用把 this 传入进去就可以改变其 this 指向。通过这个特性我们可以推断出: 这三个方法都是写在 Function 对象的原型上的。也就是 Function.prototype 上
下边我们先在这个原型上写一个自己的 call 方法就叫 newCall:
function person () { console.log(this.name) } let obj = { name: '渣渣辉' } Function.prototype.newCall = function (obj) { console.log(this) // 这里的this指向的是 person 函数 console.log(obj) // {name: '渣渣辉'} } person.newCall(obj) 运行上边的代码,我们会发现 newCall 原型方法里的 this 指向的是 person 函数, 因为 newCall 这个方法是定义在 Function 对象原型上的,并且是 person 函数调用了 newCall 方法,这就印证了那句话,谁调用了这个方法 this 就指向谁。然后只要在 newCall 方法中想办法执行 this 指向的这个 person 函数就行。