简单总结同时联系打字
可以用于对象的表示: hashCode(),toString();
用于支持多线程: wait(),notify(),notifyAll();
用于支持垃圾回收: finalize()
equals()方法
Clone()方法 另存一个当前存在的对象
参考链接:
https://blog.csdn.net/ningmengbaby/article/details/80284264
注意重写equals()方法时需要重写hashCode()方法;
避免改变Key自定义对象中的属性值,以免改变对象的hashcode值导致无法从HashMap中读取值。
hashCode将数据依特定算法直接指定到一个地址上,主要是用于查找的快捷性;
如果两个对象相同,那么它们的hashCode值一定相同;
如果两个对象的hashCode相同,它们并不一定相同 对象相同指的是用eqauls方法比较;
参考链接:
https://blog.csdn.net/anmoyyh/article/details/76019777
参考链接:
https://www.cnblogs.com/ltb6w/p/7954839.html
非对称加密:
RSA原理:RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
算法:
(1)选择两个不同的大素数p和q;
(2)计算乘积n=pq和Φ(n)=(p-1)(q-1);
(3)选择大于1小于Φ(n)的随机整数e,使得gcd(e,Φ(n))=1;注:gcd即最大公约数。
(4)计算d使得d*e=1mod Φ(n);注:即d*e mod Φ(n) =1。
(5)对每一个密钥k=(n,p,q,d,e),定义加密变换为Ek(x)=xe mod n,解密变换为Dk(x)=yd mod n,这里x,y∈Zn;
(6)p,q销毁,以{e,n}为公开密钥,{d,n}为私有密钥。
MD5 -- message-digest algorithm 5 (信息-摘要算法)缩写,广泛用于加密和解密技术,常用于文件校验。不管文件多大,经过MD5后都能生成唯一的MD5值。
参考链接:
https://blog.csdn.net/weixin_37887248/article/details/82805508
https://blog.csdn.net/ssy_1992/article/details/79094556
https://blog.csdn.net/kai_1215/article/details/48176025
1、对于8种基本数据类型的变量,变量直接存储的是“值”,使用==来进行比较时,比较的就是 “值” 本身;
2、对于非基本数据类型,引用类型的变量存储的并不是 “值”本身,而是于其关联的对象在内存中的地址;
3、equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象;
Date、String等类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等;
参考链接:
https://www.cnblogs.com/dolphin0520/p/3592500.html
异常或错误继承体系
Throwable: 它是所有错误与异常的超类(祖宗类) |- Error 错误 |- Exception 编译期异常,进行编译JAVA程序时出现的问题 |- RuntimeException 运行期异常, JAVA程序运行过程中出现的问题Throwable类是所有错误跟异常类的超类。
Exception异常类及其子类都是继承自Throwable类,用来表示java中可能出现的异常,并且合理的处理这些异常。
RuntimeException类是运行异常类,继承自Exception类,它以及它的子类只能在运行过程中存在,当出现时,只能修改源代码,否则此类异常无法处理。
Error类是与Exception的平级的类,用来表示Java中存在的严重错误,只能通过修改代码来解决问题。
异常与错误的区别:
异常是指程序在编译或者运行时出现的某种异常问题,我们可以对异常进行某种处理,如果不处理异常的话,程序将会停止运行。
错误是指程序在运行时出现的严重问题,无法处理,程序将会停止运行,Error通常都是系统级别的问题,都是虚拟机jvm所在系统发生的,只能通过修改源代码解决问题。
参考链接:
https://www.cnblogs.com/0328dongbin/p/9186676.html
Thread类和Runnable接口
任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。
参考链接:
https://www.cnblogs.com/qingquanzi/p/8146638.html
线程池参数设定的策略
finally里一般拿来做一些善后清理工作;
try块里出现错误的话,会立即跳出try块,找到匹配的错误,执行catch块里的语句,此时,可能在try块里打开的文件没关闭,连接的网络没断开,对这些浪费的内存就不能及时释放回收;
如果有finally块的话,不管有没有出错,都会执行finally块里的内容,即使try里包含continue,break,return,try块结束后,finally块也会执行。
tail 命令: 查看 catalina.out 后50行日志
grep命令:grep可以对日志的关键行提取,搜索关键字附近的日志
vi命令:编辑查找
1、进入vim编辑模式:vim filename
2、输入“/关键字”,按enter键查找
3、查找下一个,按“n”即可
退出:按ESC键后,接着再输入:号时,vi会在屏幕的最下方等待我们输入命令
wq! 保存退出;
q! 不保存退出;
参考链接:
https://blog.csdn.net/gjp014/article/details/84905522
事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。
数据库向用户提供保存当前程序状态的方法,叫事务提交(commit);当事务执行过程中,使数据库忽略当前的状态并回到前面保存的状态的方法叫事务回滚(rollback)
参考链接:
https://www.cnblogs.com/mxmbk/p/5341258.html
第一步:发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求HandlerMapping查找 Handler (可以根据xml配置、注解进行查找)
第三步:处理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略
第四步:前端控制器调用处理器适配器去执行Handler
第五步:处理器适配器HandlerAdapter将会根据适配的结果去执行Handler
第六步:Handler执行完成给适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)
第八步:前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可
第九步:视图解析器向前端控制器返回View
第十步:前端控制器进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)
第十一步:前端控制器向用户响应结果
参考链接:
https://www.baidu.com/link?url=5qs0IfSj9PzOfWZLlYEjFyfEbKYgAh-o5tXsvqddKlT0eHj7uv7yGHzke0feHCJNT2eEmWMdZlw5B_ZXsuJQZq&wd=&eqid=ccd4d2f800019559000000035cecdb8c
AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性
AOP的用处
1、水平分离 : MVC 架构
2、垂直分离 : 模块划分(订单, 库存等)
3、切面分离 : 分离功能性需求与非功能性需求
使用AOP的好处
1.集中处理某一关注点/横切逻辑
2.可以很方便地添加/删除关注点
3.增加代码的可读性,可维护性
参考链接:
https://www.cnblogs.com/zhaozihan/p/5953063.html
https://blog.csdn.net/qq_36462955/article/details/83752248#AOP_2
session创建的时机
某服务端程序(如servlet)调用HttpServletRequest.getSession(true)这样的语句时被创建,而不是在客户端请求到达的时候就被创建;
分布式环境下如果不采用事务机制会出现什么情况
sessionId一致 , session对象不一致
1、客户端首次请求 , 分配到服务器A , A首先检查客户端请求中是否包含sessionId , 首次则没有 . 然后A创建一个session对象保存会话信息 , 并生成唯一的session标识sessionId返回给客户端;
2、然后每次客户端请求都会携带该标识sessionId;
3、客户端第二次请求 , 可能分配到服务器B , B检测到请求中包含sessionId , 则先会根据sessionId检索响应的session对象 , 检索不到 , 会新建一个session对象 , sessionId不变 .
解决分布式session中的问题 , 主流有6种解决方案 :
session广播session增量广播session客户端粘性sessionsession持久化session集中缓存参考链接:
https://blog.csdn.net/xingbaozhen1210/article/details/84067495
消息丢失
消息丢失的三种可能的情况:
1、消息在传入的过程中丢失(生产者弄丢了数据)
可以选择用 RabbitMQ 提供的事务功能,就是生产者发送数据之前开启 RabbitMQ 事务channel.txSelect,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit;
但是问题是,RabbitMQ 事务机制(同步)一搞,基本上吞吐量会下来,因为太耗性能。
所以一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启 confirm 模式,在生产者那里设置开启 confirm 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 ack 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 nack 接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
2、消息中间件接收到消息,暂存在内存中,消费者还没接收,出现故障,内存中的数据搞丢
开启 RabbitMQ 的持久化,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,可能导致少量数据丢失,但是这个概率较小。
设置持久化有两个步骤:
创建 queue 的时候将其设置为持久化 这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。第二个是发送消息的时候将消息的 deliveryMode 设置为 2 就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。持久化可以跟生产者那边的 confirm 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 ack 了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到 ack,你也是可以自己重发的
3、消费者接收到消息,没来得及处理,消费者端出问题,消息丢失,但是消息中间件以为消息已经被处理
用 RabbitMQ 提供的 ack 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 ack,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack 一把。这样的话,如果你还没处理完,不就没有 ack了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。
消息去重
消费者每次执行查询前,首先在DB上查询任务的执行状态,若处于「取消/失败/成功」则表示已经由其它消费者消费过,那么直接返回ACK状态码给MQ,将消息从MQ中移除
一:缓存——热数据 热点数据(经常会被查询,但是不经常被修改或者删除的数据);
二:计数器 统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且保证毫秒级性能;
三:队列 相当于消息系统,由于redis把数据添加到队列是返回添加元素在队列的第几位,所以可以做判断用户是第几个访问这种业务;
四:位操作(大数据处理) 用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等;
redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示用户id(必须是数字),于是,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统,
五:分布式锁与单线程机制
验证前端的重复请求(可以自由扩展类似情况),可以通过redis进行过滤:每次请求将request Ip、参数、接口等hash作为key存储redis(幂等性请求),设置多长时间有效期,然后下次请求过来的时候先在redis中检索有没有这个key,进而验证是不是一定时间内过来的重复提交
秒杀系统,基于redis是单线程特征,防止出现数据库“爆破”
全局增量ID生成,类似“秒杀”
六:最新列表
例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种语句,尝试redis的 LPUSH命令构建List,一个个顺序都塞进去就可以啦。万一内存清掉,查询不到存储key的话,用mysql查询并且初始化一个List到redis中就可以了;
七:排行榜 ZADD(有续集,sorted set)
参考链接:
https://www.cnblogs.com/NiceCui/p/7794659.html
批量插入
foreach
1. 垂直切分 垂直切分的依据原则是:将业务紧密,表间关联密切的表划分在一起,例如同一模块的表。结合已经准备好的数据库ER图或领域模型图,仿照活动图中的泳道概念,一个泳道代表一个shard,把所有表格划分到不同的泳道中。
2. 水平切分 垂直切分后,需要对shard内表格的数据量和增速进一步分析,以确定是否需要进行水平切分。
2.1若划分到一起的表格数据增长缓慢,在产品上线后可遇见的足够长的时期内均可以由单一数据库承载,则不需要进行水平切分,所有表格驻留同一shard,所有表间关联关系会得到最大限度的保留,同时保证了书写SQL的自由度,不易受join、group by、order by等子句限制。2.2 若划分到一起的表格数据量巨大,增速迅猛,需要进一步进行水平分割。进一步的水平分割就这样进行:2.2.1.结合业务逻辑和表间关系,将当前shard划分成多个更小的shard,通常情况下,这些更小的shard每一个都只包含一个主表(将以该表ID进行散列的表)和多个与其关联或间接关联的次表。这种一个shard一张主表多张次表的状况是水平切分的必然结果。这样切分下来,shard数量就会迅速增多。如果每一个shard代表一个独立的数据库,那么管理和维护数据库将会非常麻烦,而且这些小shard往往只有两三张表,为此而建立一个新库,利用率并不高,因此,在水平切分完成后可再进行一次“反向的Merge”,即:将业务上相近,并且具有相近数据增长速率(主表数据量在同一数量级上)的两个或多个shard放到同一个数据库上,在逻辑上它们依然是独立的shard,有各自的主表,并依据各自主表的ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。这样,每个数据库结点上的表格数量就相对平均了。2.2.2. 所有表格均划分到合适的shard之后,所有跨越shard的表间关联都必须打断,在书写sql时,跨shard的join、group by、order by都将被禁止,需要在应用程序层面协调解决这些问题。
参考链接:
https://www.cnblogs.com/zhaoding/p/6249498.html
技术总结博客:
https://github.com/doocs/advanced-java