怎么判断一个点是否在一个三角形内
思路一:
面积法:
如果一个点在三角形内,其与三角形的三个点构成的三个子三角形的面积等于大三角形的面积。否则,大于大三角形的面积。
所以,这个问题就转化成如何在知道三角形的三个点的情况下,求这个三角形的面积的问题了。
因为所有点的坐标已知,我们有几种方式计算面积:
1)首先可以计算出每条边的长度及周长,我们就可以利用海伦公式计算面积,然后进行比较。
2)向量法:先求出这个三角形的对应的平行四边形的面积。然后这个面积的1/2就是三角形的面积了。
先随意选择两个点,如B、C通过其坐标相减得向量(B,C)。记得谁减另一个就是指向谁。然后求出其中一个点和剩下一个点的向量。这两个向量的叉乘的便是平行四边形的面积。除以2就是三角形的面积。(注意这里是叉乘 (cross product),而非点乘(dot product))。
(补充)向量之间的积分为两种:叉乘和点乘。叉乘求面积,点乘求投影。这是两者的意义。而且,叉乘理论得到的是一个向量,而点乘得到的是一个标量。
class POINT{
int x;
int y;
public POINT(int x,int y){
this.x = x;
this.y = y;
}
}
private static final double ABS_DOUBLE_0 = 0.0001;
public static boolean isInTriangle(POINT A, POINT B, POINT C, POINT P) {
double ABC = triAngleArea(A, B, C);
double ABp = triAngleArea(A, B, P);
double ACp = triAngleArea(A, C, P);
double BCp = triAngleArea(B, C, P);
double sumOther = ABp + ACp + BCp;
if (-ABS_DOUBLE_0 < (ABC - sumOther) && (ABC - sumOther) < ABS_DOUBLE_0) { // 若面积之和等于原三角形面积,证明点在三角形内
return true;
} else {
return false;
}
}
private static double triAngleArea(POINT A, POINT B, POINT C) { // 由三个点计算这三个点组成三角形面积
POINT ab,bc;
ab = new POINT(B.x - A.x,B.y - A.y);//
bc = new POINT(C.x - B.x,C.y - B.y);
return Math.abs((ab.x * bc.y - ab.y * bc.x) / 2.0);
}
做题:单调栈问题
进程之间切换用到了哪些数据结构
进程调度的时机
在上篇中说到,进程调度(低级调度),就是按照某种算法从就绪队列中选择一个进程为其分配处理机。我们现在来说说什么时候需要使用到进程调度与切换。 进程调度与切换的时机分为两种情况,一种是当前运行的进程主动放弃处理机,还有一种是当前运行的进程被动放弃处理机。接下来看看它们分别对应什么事件。
- 当前运行的进程主动放弃处理机
- 进程正常终止。
- 运行过程中发生异常而终止。(如 内中断)
- 进程主动请求阻塞。(如 请求使用打印机)
- 当前运行的进程被动放弃处理机
- 分配给该进程的时间片用完。
- 有更紧急的事情要处理。(如 外中断)
- 有更高优先级的进程进入就绪队列。
注意:有的系统只允许进程主动放弃处理机,而在有的系统,进程可以既主动放弃处理机,又可以被剥夺处理机(当有更紧急的任务需要处理时)。
struct runqueue {
spinlock_t lock;//自旋锁,用于锁定运行队列
unsigned long nr_running;//可运行的的任务数目
unsigned long cpu_load[3];//基于运行队列中进程的平均数量的cpu负载因子
unsigned long long nr_switches;//上下文切换数目
unsigned long nr_uninterruptible;//处于不可中断或者睡眠状态的任务数目
unsigned long expired_timestamp;//队列最后被换出的时间
unsigned long long timestamp_last_tick;//最后一个调度程序的节拍
task_t *curr, *idle;//当前运行的任务和空闲的任务
struct mm_struct *prev_mm;//最后运行任务的mm_struct结构体。
prio_array_t *active, *expired, arrays[2];//1.活动优先级队列2.超时优先级队列,3.实际优先级数组
int best_expired_prio;//最适宜换出的优先级队列
atomic_t nr_iowait;//等待I/O操作的任务数目
struct sched_domain *sd;//多处理器相关
int active_balance;//活动的需要负载平衡进程个数
int push_cpu;//哪个CPU需要进行负载平衡?
task_t *migration_thread;//换出任务
struct list_head migration_queue;//换出队列
/* latency stats */
struct sched_info rq_sched_info;
/* sys_sched_yield() stats */
unsigned long yld_exp_empty;
unsigned long yld_act_empty;
unsigned long yld_both_empty;
unsigned long yld_cnt;
/* schedule() stats */
unsigned long sched_switch;
unsigned long sched_cnt;
unsigned long sched_goidle;
/* try_to_wake_up() stats */
unsigned long ttwu_cnt;
unsigned long ttwu_local;
};
TIME_WAIT状态
虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
Linux中如何找到包含指定字符串的文件
2. 目录下的所有文件中查找字符串:
grep -ri "字符串" .
字符串就是你要查找的内容,结果会显示此路径下,所有包含“字符串”的文件相对路径和所在行。
3. 目录下所有文件中进行查找,只显示文件名:
find . | xargs grep -ri "字符串" -l
如上所述,只显示路径和文件名。
负载均衡的组件(算法)
轮询
默认方式
weight
权重方式
ip_hash
依据ip分配方式
least_conn
最少连接方式
fair(第三方)
响应时间方式
url_hash(第三方)
依据URL分配方式
TCP粘包问题、应用层协议有哪些
TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的。这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的。TCP的发送方无法保证对等方每次接收到的是一个完整的数据包。主机A向主机B发送两个数据包,主机B的接收情况可能是
从数据发送的过程中,经过那些步骤来看:
应用层首先要将自己的数据通过套接字发送,首先要调用一个write方法:(将应用进程缓冲区中的数据拷贝到套接口发送缓冲区SO_SNDBUF,有一个大小的限制),如果应用进程缓冲区的一条消息的字节的大小超过了发送缓冲区的大小,就有可能产生粘包问题,因为消息已经被分割了,有可能一部分已经被发送出去了,对方已经接受了,但是另外一部分可能刚放入套接口发送缓冲区里准备进一步发送,就直接导致接受的后一部分,直接导致了粘包问题的出现。
2,就是说:TCP是基于字节流的,只维护发送出去多少,确认了多少,并没有维护消息与消息之间的边界,因而极有可能导致粘包问题,(应该在应用层维护一个消息边界,加\n)
TCP所发送的的字节流中存在一个MSS(最大报文端长度),如果所发送的消息的字节过长,那么会对所发送的消息进行分割,那么也会直接导致粘包。
3,链路层所发送的数据有一个最大传输单元(MTU)的限制(以太网的MTU是1500bytes),如果我们所传输的信息超过了限制,那么会在IP层进行分组,或者分片,这也可能导致消息的粘包问题的产生。
② 粘包的问题的解决思路 粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪。我们通过使用某种方案给出边界,例如:
- 发送定长包。如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。
- 包尾加上\r\n标记。FTP协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。(可能出现错误)
- 包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对等方先接收包体长度,依据包体长度来接收包体。
- 使用更加复杂的应用层协议。
包的大小
以太网数据包(packet)的大小是固定的,最初是1518字节,后来增加到1522字节。其中, 1500 字节是负载(payload),22字节是头信息(head)。这个1500就是网络设备设置的MTU大小了,因此正常来说MTU只能比1500小。IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要20字节,所以 IP 数据包的负载最多为1480字节。
如果做秒杀,你会怎么做
为什么用分布式事务
Java SPI机制
Spring SPI机制
如何是你,你会怎么设计SPI
怎么学习Dubbo和RocketMQ的
从源码上讲以下Spring bean的初始化前后所有过程(我没讲循环依赖,可惜了,这可是Spring的精髓之一呀)
Spring IOC容器---对象循环依赖。
(1)循环依赖-->循环引用。--->即2个或以上bean 互相持有对方,最终形成闭环。 eg:A依赖B,B依赖C,C又依赖A。【注意:这里不是函数的循环调用【是个死循环,除非有终结条件】,是对象相互依赖关系】
Spring中循环依赖的场景?where?
①:构造器的循环依赖。【这个Spring解决不了】
StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,我们都把这三个Bean交给Spring管理,并用有参构造实例化
public class StudentA {
private StudentB studentB ;
public void setStudentB(StudentB studentB) {
this.studentB = studentB;
}
public StudentA() {
}
public StudentA(StudentB studentB) {
this.studentB = studentB;
}
}
public class StudentB {
private StudentC studentC ;
public void setStudentC(StudentC studentC) {
this.studentC = studentC;
}
public StudentB() {
}
public StudentB(StudentC studentC) {
this.studentC = studentC;
}
}
public class StudentC {
private StudentA studentA ;
public void setStudentA(StudentA studentA) {
this.studentA = studentA;
}
public StudentC() {
}
public StudentC(StudentA studentA) {
this.studentA = studentA;
}
}
②【setter循环依赖】field属性的循环依赖【setter方式 单例,默认方式-->通过递归方法找出当前Bean所依赖的Bean,然后提前缓存【会放入Cach中】起来。通过提前暴露 -->暴露一个exposedObject用于返回提前暴露的Bean。】
setter方式注入:
图中前两步骤得知:Spring是先将Bean对象实例化【依赖无参构造函数】--->再设置对象属性的
这就不会报错了:
原因:Spring先用构造器实例化Bean对象----->将实例化结束的对象放到一个Map中,并且Spring提供获取这个未设置属性的实例化对象的引用方法。结合我们的实例来看,,当Spring实例化了StudentA、StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题喽。
3. 如何检测是否有循环依赖?how to find?
可以 Bean在创建的时候给其打个标记,如果递归调用回来发现正在创建中的话--->即可说明循环依赖。
4.怎么解决的?
Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或zh属性是可以延后设置的(但是构造器必须是在获取引用之前)。
Spring的单例对象的初始化主要分为三步:
①:createBeanInstance:实例化,其实也就是 调用对象的构造方法实例化对象
②:populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
③:initializeBean:调用spring xml中的init() 方法。
从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
调整配置文件,将构造函数注入方式改为 属性注入方式 即可。
AOP源码
Spring事务原理,以及失效
所谓spring事务的传播属性,就是定义在存在多个事务同时存在的时候,spring应该如何处理这些事务的行为。这些属性在TransactionDefinition中定义,具体常量的解释见下表:
常量名称常量解释PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring 默认的事务的传播。PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()。
PROPAGATION_REQUIRED(spring 默认)
如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。
假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。
PROPAGATION_REQUIRES_NEW(两个新的事务,导致都不会有相互影响)
比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。
那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。
他与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。
PROPAGATION_SUPPORTS(跟着最外面的事务一起处理)
假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法
PROPAGATION_NESTED(嵌套事务 外面的包括了里面的事务的处理)
现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:的事务性完全依赖于最外层的事务。
a、捕获异常,执行异常分支逻辑
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
}
这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。
b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback。
有没有看过iterator的源码
但是在使用的时候需要注意的是,如果在遍历的过程中增加元素、删除元素等改变了List、HashMap之类的List的结构时,会产生ConcurrentModificationException(并发修改)异常。
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Copyright (C), 2018-2020
* FileName: Iterator_JDK
* Author: xjl
* Date: 2020/8/16 14:25
* Description: 迭代器的源码
*/
public class Iterator_JDK {
public static void main(String[] args) {
Map map = new HashMap();
map.put("1", "111");
map.put("2", "222");
map.put("3", "333");
Iterator iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
//这里会引发ConcurrentModificationException异常
Map.Entry entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
if (value.equals("111")) {
map.remove(key);
//map.put("444");
}
}
}
}
从引发异常的代码行跟踪进去,进入到HashMap的HashIterator类,该类实现了Iterator接口。而next()方法最终会执行到nextEntry()方法。看一下nextEntry()方法的实现:
final Entry nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
HashMapEntry e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
HashMapEntry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
可以看到抛出异常的原因是modCount != expectedModCount。modCount是HashMap中的一个变量,当HashMap结构改变时(比如put进去了一个新的元素,删除了一个新的元素等),这个modCount记录的是改变的次数,而expectedModCount是HashIterator类对象的一个变量,在HashIterator构造函数中赋值,如下所示:
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
HashMapEntry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}
面的expectedModCount = modCount即为赋值语句。
返回上面举的例子,当我们在遍历HashMap时删除了一个元素,即map.remove(key); 最终执行removeEntryForKey(key)方法,在该方法中执行了modCount++,也即modCount的值改变了。当在HashIterator中继续往下执行到nextEntry()方法时,由于modCount的值不等于expectedModCount,那么就抛出了ConcurrentModificationException异常。
3、为什么不相等就抛出异常
final Entry nextEntry() {
...省略
HashMapEntry e = next;
...省略
if ((next = e.next) == null) {
HashMapEntry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
例如,我们在遍历的时候删除了3号的元素,这个时候index指向了下一个元素,即index=3。当我们继续执行nextEntry时,由于hasMap改变了,也即table改变了,那么下次访问到就是5号的元素,也就是说4号元素完全没有被我们访问到,所以这是有问题的。所以Java规定了如果HashMap的结构发生了变化,那么就抛出并发修改异常。
4、怎么在遍历时增加或者删除元素?
上面分析了既然在遍历时不允许删除HashMap的元素,那么我们有什么样的方法删除或者添加吗?因为我们在工作时肯定会遇到这样的问题的。
对于删除,我们可以看到Iterator有一个remove()的方法。而HashIterator的remove()方法如下:
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
可以看到在这里通过调用HashMap的removeEntryForKey删除了当前的元素,并且同步地将expectedModCount修改为modCount,所以下次执行nextEntry()方法时就不会报并发修改异常了。
上面的情况只是在单线程不会出问题,但是如果在多线程下,即使使用了remove()方法,也会有可能出现ConcurrentModificationException错误。所以在多线程下为了保证现场安全,我们需要对要操作的HashMap进行一个加锁操作,这样就可以防止在遍历的过程中有其他现场去修改HashMap的结构,从而导致出现ConcurrentModificationException错误。
那么如果我们想要添加元素呢?好像Iterator只实现了remove()这样一个方法,对于其他操作并没有为我们实现,那么我们就需要自己来实现了:
LinkedList tempList = new LinkedList();
tempList.addAll(map.entrySet());
ListIterator itor = tempList.listIterator();
Map.Entry entry = null;
while (itor.hasNext()) {
entry = (Map.Entry) itor.next();
Object key = entry.getKey();
if (key.toString().equals("3")) {
map.put("33", "33");
}
}
我们可以使用一个LinkedList来装载HashMap的entrySet,然后在遍历时修改或者添加map的元素,由于该LinkedList的Iterator和HashMap的Iterator是不同的对象,所以不用担心会引发并发修改异常。
来源:https://www.jianshu.com/p/f9f8dd129b41
在海量数据中查找重复元素
什么是位图法?
举个简单例子,在java中一个int类型的数有32位,而这32只表示一个数太过浪费,于是就考虑让这32位可以表示32个数,每一位表示该数是否存在,例如:
这里用16位的二进制就能表示十六个数字,1表示存在,0表示不存在,上图就表示存在(16,12,6,4,1)这五个数。
在海量数据中查找重复出现的元素或者去除重复出现的元素也是常考的问题。针对此类问题,一般可以通过位图法实现,例如,已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。
2020腾讯的面试的问题:1、如何保证外API接口的安全性。?
- 使用加签名方式,防止数据篡改
- 信息加密与密钥管理
- 搭建OAuth2.0认证授权
- 使用令牌方式
- 搭建网关实现黑名单和白名单
一令牌方式搭建搭建API开放平台
方案设计:
1、第三方机构申请一个appId,通过appId去获取accessToken,每次请求获取accessToken都要把老的accessToken删掉
2、第三方机构请求数据需要加上accessToken参数,每次业务处理中心执行业务前,先去dba持久层查看accessToken是否存在(可以把accessToken放到redis中,这样有个过期时间的效果),存在就说明这个机构是合法,无需要登录就可以请求业务数据。不存在说明这个机构是非法的,不返回业务数据。
3、好处:无状态设计,每次请求保证都是在我们持久层保存的机构的请求,如果有人盗用我们accessToken,可以重新申请一个新的taken.
二基于OAuth2.0协议方式
第三方授权,原理和1的令牌方式一样
1、假设我是服务提供者A,我有开发接口,外部机构B请求A的接口必须申请自己的appid(B机构id)
2、当B要调用A接口查某用户信息的时候,需要对应用户授权,告诉A,我愿同意把我的信息告诉B,A生产一个授权token给B。
3、B使用token获取某个用户的信息。
联合微信登录总体处理流程
1 :用户同意授权,获取code
2 :通过code换取网页授权access_token
3 :通过access_token获取用户openId
4 :通过openId获取用户信息
三信息加密与密钥管理
1:使用的是加密的算法来实现的安全的访问
四使用加签名方式,防止数据篡改
1、客户端:请求的数据分为2部分(业务参数,签名参数),签名参数=md5(业务参数)
2、服务端: 验证md5(业务参数)是否与签名参数相同
2、API的访问的限流怎么设计?
3、限流算法的种类的具体的实现?
4、MQ的原理和问题?
5、Mybatis的一级缓存和二级缓存的失效
6、登入系统的设计为什么采用 Spring-security而不是OAuth2介绍 ?
Spring-security是对系统的自校验的一种登入功能,但是OAuth2是一种的第三方的登入系统的方式
对用户
对用户而言,第三方账号登录带来最大的好处就是方便。一方面可以省去一个注册流程,另一方面也不用费心去记各种账号密码,可以用微博等账号一号走遍天下。另外通过微博、QQ等平台可以更容易地对好友进行分享并与好友实现互动。这些好处让许多比较“懒”的用户会更倾向于用第三方账号体系登录。
而第三方账号登录带来的一个巨大问题就是隐私安全问题。虽然现在第三方账号登录普遍使用OAuth技术,可以让你无需将微博等第三方平台的用户名和密码提供给应用,在一定程度上保证了用户名与密码的安全。但通过授权该应用却可以访问用户的很多资源,比如评论、好友、生日等信息,而且许多在授权时都是默认选项,用户非常容易因此就泄露了自己的信息。许多应用在授权时还会默认在微博等第三方平台同步状态,这等于将你用了什么应用,在应用里做了什么都公诸于众,让用户感觉毫无隐私可言(比如用了某款婚恋应用你却不想让人知道)。所以对个人信息敏感的用户可能就不会选择用第三方账号登陆体系。
对开发者
对开发者而言,第三方账号登陆的好处主要是有三点,一点是提升了用户的注册转化率,降低了进入的门槛。另一点就是可以利用微博、人人等平台的资源,提高自己的知名度。还有就是可以省去自主登陆体系的开发工作。提升用户注册转化率是因为给了用户更方便的路径,减少了因嫌注册麻烦而流失的用户;连接了微博等第三方平台,可以通过信息在该平台的同步提升自己产品的曝光率,同时可以通过授权获得用户的粉丝、好友,并进行针对性的营销。
而对开发者不好的地方就是如果没有较多的授权,无法掌握用户的全部信息。从某种角度而言用户还是在微博、人人等平台下,自己没有累计下用户的沉淀,虽说开放是现在的主旋律,但总还是受制于各大平台,万一哪天开放的策略有变或平台以用户相要挟,开发者也没有什么太好的反击办法。
我认为这个还是要根据你应用或网站的定位、功能来决定。如果你的应用重分享、重评论、重社交,并不定位于做行业的标杆,是比较轻量级的应用,那么你可以选择放弃自主账号体系,让用户无需纠结于是用第三方账号还是重新注册一个账号,让注册流程更加简洁,用户目标更加明确(如啪啪、Instagram、你画我猜等)。 而如果你的应用是重内容、轻社交,独立性较强,有可能形成一个社区或闭环,可以经过时间培养起一批有自己风格的用户,有自己的用户文化,那么还是应该保留有自己的登录体系,一来可能适应更多用户的需求,二来也容易让用户产生一种归属感。如果有信心的话甚至可以放弃第三方登录,让用户对你的产品有着更深的需求与认知,这样慢慢发展也许你也会成为另一个第三方平台,不过这毕竟是少数。另外移动端跟PC端注册的成本也不同,在移动端输入更少的应用也许让用户会更舒服一些,这也是要考虑到的。
我咨询了周围的一些朋友,看看他们平时是用第三方登录体系还是应用的自主登录体系,下面是几个发现:1. 女性朋友为了方便更喜欢用第三方等账号登陆,而男性朋友则对隐私有着更多一些的关注,有些更倾向于自主账号登陆; 2. 如果应用正规、名气大、口碑良好,则对隐私泄露的担忧会下降; 3. 如果发现应用会自动在第三方平台更新用户动态,则对该应用好感度明显下降; 4. 如果第三方登录会带来明显好处(如一些社交类游戏就是为了与好友一起玩),则更倾向于使用第三方登录。我咨询的朋友不多,而且用户模型也较为相似,所以不具有很强的代表性,不过从中也能学到一些东西,如在授权第三方时默认授权不要过多,要尊重用户,要让自己的产品更加专业与正规,给用户留下正面的印象等。
总结一下就是自主登录体系与第三方登录体系均有利弊,在要结合自己产品的形态、定位、风格以及愿景来进行选择,无论何种选择,永远要做到尊重用户
7、加密算法的原理是什么:
- saltRounds: 正数,代表hash杂凑次数,数值越高越安全,默认10次。
- myPassword: 明文密码字符串。
- salt: 盐,一个128bits随机字符串,22字符
- myHash: 经过明文密码password和盐salt进行hash,个人的理解是默认10次下 ,循环加盐hash10次,得到myHash。
每次明文字符串myPassword过来,就通过10次循环加盐salt加密后得到myHash, 然后拼接BCrypt版本号+salt盐+myHash等到最终的bcrypt密码 ,存入数据库中。这样同一个密码,每次登录都可以根据自省业务需要生成不同的myHash, myHash中包含了版本和salt,存入数据库。
在下次校验时,从myHash中取出salt,salt跟password进行hash;得到的结果跟保存在DB中的hash进行比对。 如Spring Security crypto 项目中实现的BCrypt 密码验证BCrypt.checkpw(candidatePassword, dbPassword)。
8、为什么HashMap使用红黑树而不使用AVL树?
在CurrentHashMap中是加锁了的,实际上是读写锁,如果写冲突就会等待,如果插入时间过长必然等待时间更长,而红黑树相对AVL树他的插入更快!
红黑树和AVL树都是最常用的平衡二叉搜索树,它们的查找、删除、修改都是Olong(n);
AVL树和红黑树有几点比较和区别:
(1)AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。 (2)红黑树更适合于插入修改密集型任务。 (3)AVL树的旋转比红黑树的旋转更加难以平衡和调试。
总结:
1)AVL以及红黑树是高度平衡的树数据结构。它们非常相似,真正的区别在于在任何添加/删除操作时完成的旋转操作次数。 2)两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。 3)在AVL树中,从根到任何叶子的最短路径和最长路径之间的差异最多为1。在红黑树中,差异可以是2倍。 4)两个都给O(log n)查找,但平衡AVL树可能需要O(log n)旋转,而红黑树将需要最多两次旋转使其达到平衡(尽管可能需要检查O(log n)节点以确定旋转的位置)。旋转本身是O(1)操作,因为你只是移动指针。
2020腾讯二面1 redis中的为什么使用的Hash而不是用string?
hash类型是一个string类型的field和value的映射表,每个 hash 可以存储 232 - 1 键值对(40多亿)hash类型主要存储对象数据
2. 存储对象
hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。在介绍string类型的应用场景时有所介绍,string + json也是存储对象的一种方式,那么存储对象时,到底用string + json还是用hash呢?
string + jsonhash效率很高高容量低低灵活性低高序列化简单复杂当对象的某个属性需要频繁修改时,不适合用string+json,因为它不够灵活,每次修改都需要重新将整个对象序列化并赋值,如果使用hash类型,则可以针对某个属性单独修改,没有序列化,也不需要修改整个对象。比如,商品的价格、销量、关注数、评价数等可能经常发生变化的属性,就适合存储在hash类型里。
当然,不常变化的属性存储在hash类型里也没有问题,比如商品名称、商品描述、上市日期等。但是,当对象的某个属性不是基本类型或字符串时,使用hash类zx/12型就必须手动进行复杂序列化,比如,商品的标签是一个标签对象的列表,商品可领取的优惠券是一个优惠券对象的列表(如下图所示)等,即使以coupons(优惠券)作为field,value想存储优惠券对象列表也还是要使用json来序列化,这样的话序列化工作就太繁琐了,不如直接用string + json的方式存储商品信息来的简单。
2 线程池的原理?
3 Dubbo在超大访问的时候怎么处理的?
4 Springboot和Spring的区别?Springboot的启动的原理?