1. HashMap的put流程
1.1 计算key的hash值
1.2 基于hash值计算存储位置
1.3 将数据写入到数组,链表,红黑树
1.4 计数器++

HashMap中如果传入的key为null,hash值固定为0
首先int是32个bit位,key.hashCode() 生成出来的是一个int类型数据
然后异或自身高位右移16位(相同为0 不同为1)
高低16位异或运算时为了让hashCode的高16位也能参与运算,从而减少哈希冲突

(数组长度-1)与key的hash值 做与运算,确定索引下标

为啥HashMap的长度必须是2^n?
因为如果长度不是2^n,会导致在计算索引位置时,hash冲突变多,导致hashMap查询效率变慢
写入到数组,链表,红黑树
数组:当判断插入的索引位置上,没数据,直接扔数组上
key一致:先判断hash是否y一致,如果一致,在判断==或者equals是否为true,为true,覆盖value值
红黑树:如果p的类型是TreeNode那么走红黑树逻辑,将数据插入到红黑树中
链表:循环找到链表最后一个位置,挂上
计数器:
直接对size属性++即可,因为是单线程(如果是覆盖value,不会++)
2. 项目中设计模式的应用
比如我们做的短信平台项目,里面涉及到了多个校验操作,我们希望这些校验操作之间可以做到解耦,并且根据客户情况不同,做到一定的定制化校验
比如客户A:1,3,2,5,6,4
比如客户B:1,2,3,4 顺序校验
针对每个客户保存他要做的校验规则,存储客户的某个字段里
可以将校验规则抽取一个接口,所有的校验规则都去实现这个接口,编写具体的逻辑
再结合 MyBatis中的Cache接口,Servlet中的Filter SprintMVC的Interceptor
3. ThreadLocal你知道吗
最核心:传参(只要是一个线程下 都可以使用该参数)
你可以将上面聊的策略+责任链模式结合在y一起,去聊一下项目中那个业务涉及到了。例如校验规则存放到threadLocal
---
ThreadLocal存储数据的方式
ThreadLocal本身不存储数据,它只是一个key,真正存储的是线程对象Thread当中的一个Map(threadLocalMap),这个Map的底层是一个Entry数组,每一个Entry都可以存储key和value
其中的key,就是threadLocal
我们可以声明多个ThreadLocal对象,但是存储数据的,就是线程中的那个Entry数组(0位置存储的为ThreadLocal1,1位置存储的为ThreadLocal2)
---
ThreadLocal内存泄露问题
ThreadLocal有两个内存泄露问题
1. key的内存泄露,这个问题已经被解决了,因为ThreadLocal内部对key的引用是弱引用
2. value的内存泄露,在线程池操作ThreadLocal时,因为线程一直没有被回收,value数据一直在,前面如果ThreadLocal回收掉了,但是value还在,导致value占用内存,但是还查询不到。
还有一个安全问题,上次逻辑存储的数据,在下次逻辑里又查询出来,导致数据存在安全问题。 所以 value的内存泄露问题,需要我们使用完毕后,主动remove,避免下次操作,出现问题
4.ThreadLocal如何实现主子线程之间数据同步
InheritableThreadLocal:一定是父线程主动创建的子线程才可以。(线程池不能创建)
如果是子线程给父线程传递数据,那就是采用共享变量,或者作为返回值
5.接口抽象类区别
1. 属性:接口属性都是public static final 修饰常量,抽象类没有限制
2. 方法:接口方法维度和抽象类区别不大,抽象类可以声明抽象方法,非抽象方法,静态方法,interface也可以使用抽象方法,default 修饰可以写方法体,static修饰可以提供静态方法。但是inteface的访问修饰符基本是public
接口不能写静态代码块,抽象类可以。
3. 抽象类单继承,限制大,接口多线程
6.清洁术RESTful API的设计原则 并说明如何在Java中实现这些原则
RESTful API本质是一个架构风格,他不是标准,可以遵循一部分,也可以全部遵循。
1. 服务端不保存会话状态信息。要求每次发送请求,在请求的报文中携带好必要的信息,比如JWT,会话信息的token放到请求头里。说白了,会话信息存储到客户端。
2. 前后端分离,后端不做请求转发,重定向操作,后端每一个接口都是一个资源
3. 对请求的路径也有要求,不会在请求路径上搞?key=value传参,而是基于路径本身传参或者基于请求体中的JSON传参
4. 其次利用不同的请求方式代表不同的操作,比如/user,GET请求代表查询,POST请求代表添加,PUT请求代表修改,Del请求代表删除
5. 响应数据层面:利用不同的状态码表示不同的事情,201-created 代表添加成功
Java中实现非常简单,就上JWT,搞token,还能去中心化
SpringMVC提供了@pathVariable路径传参 @requestBody接受请求体参数
SpringMVC提供了GetMapping PostMapping PutMapping DelMapping
HttpStatus,或者自定义 code=0/200
7.Java后端架构设计的原则是什么?
单一职责:一个类,专门干一个事
里氏替换:子类可以拓展父类功能,但是别影响其他功能的正确性
开闭原则:功能可以增加,但是不能修改
依赖倒置:面向接口编程
如果没有太好的方案 说以上的方案
更推荐的方式i:
1.可维护性(低耦合):比如在涉及缓存和搜索功能时,会单独提供一套缓存服务和搜索服务,甚至支付也一样,单独提供一个支付的服务,这个服务只对外暴露基本的功能接口。如果后期涉及到一些政策原因,其他原因,导致你需要去替换中间件,比如将redis替换为其他国产化缓存组件。因为前面的设计,我不需要去改动我的业务服务里的任何内容,只需要在缓存服务中,将之前Redis的API,替换为国产化的缓存API即可,其他的不需要动
2.可扩展性:需要主动追加一些额外的功能或者方向时,可以更方便的动态增减功能,其他功能不会收到影响
3.安全性:安全是每个产品必须考虑的点,所以很多加密算法都要考虑好,比如你们传输数据时,可以上非对称加密,堆成加密,(AES,RSA,SHA),还有一些敏感数据过滤,XSS攻击,CSRF攻击,而且后期HTTPS是必上的
4.性能:比如CDN,DNS优化,缓存设计,分库分表,合理的中间件,多线程
5.兜底(容错)能力:比如熔断,降级,做好兜底,限流,MQ削峰,部署的时候要规避单点故障问题,每个服务至少两台节点,还可以考虑一些异地多活,其次还有数据的冗余备份。
6.监控:普罗米修斯
7.弹性伸缩:GraalVM
8. 描述Java中的异常处理机制,包括checked和unchecked异常的区别,以及如何自定义异常类
Java中处理异常的一些机制:
1.try-catch
2.throw
3.throws
4.SpringMVC异常处理器
异常的结构:顶级父类,Throwable,下面两个子类Error和Exception,其中Exception里分为运行时异常和检查时异常
检查时异常(编译时异常):在编译器就存在,比如IO操作,文件不存在,提前向上抛出IOException.
运行时异常:程序运行后,在执行代码后,可能会出现的异常,比如空指针异常
自定义异常:一定集成RuntimeException,这样才能更好的适配Spring事务,如果不是RuntimeException,spring声明事务会失效
9. 描述Java中基本数据类型的种类及其特点,如何选择合适的数据类型来优化内存
byte int short long double float char boolean
如果单纯从减少内存占用来聊,肯定是根据具体业务选择占用字节数小的类型更好
比如:年龄byte,再不行short
而且特别是数据库字段,肯定是在业务合理情况下,使用较小的类型,因为这样
一个页可以存储更多的行,更多的数据就可以存放在Mysql的Buffer Pool中提升性能
其次,还是要贴近业务,比如做主键索引类型,还是要选择long类型,一般选择分布式ID的雪花方法生成ID,64位界限更长
比如存储金额,一般不会采用float或者double,因为Java中计算浮点有误差,一般针对这种类型,选择BigDecimal或者采用long类型,比如金额以厘作为单位
但是其实不是所有操作都是占用内存小,效率就高,比如CPU,在不同厂商CPU里,针对32位的int或者是64位的long类型处理,性能更高。因为你占用内存小,在CPU的缓存行中会存储大量的数据,每次操作其中一个,都可能导致缓存行失效,从而需要去主内存同步数据,这样反而会导致CPU的处理性能降低

1482

被折叠的 条评论
为什么被折叠?



