HashCode 和 equals 方法
参考:
https://blog.csdn.net/SEU_Calvin/article/details/52094115
https://blog.csdn.net/jing_bufferfly/article/details/50868266
https://blog.csdn.net/anmoyyh/article/details/76019777
https://blog.csdn.net/zzg1229059735/article/details/51498310
1. 是什么?
HashCode是用于查找使用的,而equals是用于比较两个对象是否相等的。
HashCode:按某种规则生成的 int 类型的数值,(用于确定对象存储地址)。
equals :比较两个对象内容是否相等,用于保证元素不重复。
2. 为什么需要重写
- 重写 hashCode:
相等的对象具有相等的hashCode这一原则。而hashCode 默认实现是根据对对象内存地址换算出的值。
减少了 equals 比较的次数,提高了效率。
若 HashCode 相同再去调用 equals,如果不同,那没就不必在进行 equals 的比较了. - 重写 equals:
默认比较的地址值,但是我们一般需要比较内容是否相等。
二者关系:
- 如果两个对象 equals,那么它们的 hashCode 值一定相同
- 如果两个对象的 hashCode 相同,它们并不一定 equals
存过程:
- 先调用元素的 hashCode 方法,定位它存放的位置
- 如果这个位置无元素,就放入该位置
- 如果这个位置已经有元素了,则调用它的 equals 方法与新元素进行比较:
- 如果相同的话就不存了。
- 如果不同(Hash key相同冲突),那么在这个 Hash key 的位置产生一个链表,将所有产生相同 HashCode 的对象放到这个单链表上去,串在一起。
这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
取过程:
- hashcode不重复:
通过 hashCode 直接找到存放的位置了 - hashcode重复:
先通过 hashCode 来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
问题:重写了equals(),为什么还要重写hashCode()呢?
违反了 hashCode 通用约定(相等的两个对象必须具有相等的散列码),导致该类无法结合所有基于散列的集合一起正常工作(HashMap、HashSet、HashTable)。
你要在一个桶里找东西,你必须先要找到这个桶,不通过重写hashcode()来找到桶,光重写equals()有什么用啊 。
3. 如何重写?重写的原则
重写 equals 原则:
使用 == 操作符判断参数是否是这个对象的引用。
使用 instance of 操作符判断”参数是否是正确的类型“。
把参数转换成正确的类型。
-
对于参数中的各个字段,判断其是否和对象中的字段相匹配;
@Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof User)) { return false; } User user = (User) o; return user.name.equals(name) && user.age == age && user.passport.equals(passport); }
重写 hashCode原则:
-
为不相等的对象产生不相等的 hashCode。
@Override public int hashCode() { int result = 17; result = 31 * result + name.hashCode(); result = 31 * result + age; result = 31 * result + passport.hashCode(); return result; }
HashSet (HashMap)怎么判断集合元素重复?
HashSet不能添加重复的元素,当调用add(Object)方法时候:
- 先比较 hashCode 是否相同,hashCode 不同则表示对象不同,直接添加
- hashCode 相同则继续比较 equals 方法:
- 返回 true: 说明元素重复,就不添加
- 返回 false:说明元素不重复,就添加
数组和链表的区别?
数组:长度固定,元素在内存中连续存储。
- 优点:查找效率比较高;
- 缺点:长度固定不灵活,插入、删除数据效率低。
链表:长度可变,是动态申请内存空间 ,元素通过指针关联
- 优点:动态申请或者删除内存空间,对于数据增加和删除比数组灵活
- 缺点:查询慢
加密算法相关
MD5:
MD5 -- message-digest algorithm 5 (信息-摘要算法),特点是不管文件多大,经过MD5后都能生成唯一的MD5值;项目中用于对密码进行加密保存,容易被反查,所以一般会加盐值。
BASE64:
对数据内容进行编码来适合传输,是一种编码算法。常见于邮件、http加密
HMAC:
HMAC(Hash Message Authentication Code,散列消息鉴别码,基于密钥的Hash算法的认证协议。实现原理:用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输。接收方利用与发送方共享的密钥进行鉴别认证等。
非对称加密 RSA:
公钥加密、私钥解密;
优点:非对称算法,加密程度高。
缺点:进行的都是大数计算,速度慢
应用场景:对安全性要求较高的数据加密和数字签名,如 支付宝、银行
对称加密
对称加密算法,又称秘钥加密:用一个秘钥来管理信息的加密解密。
优点:算法公开、计算量小、加密速度快、加密效率高。
缺点:秘钥泄露就会被破解。
应用场景:
- 将敏感信息保存到本地的时候加密,取出的时候还原。
- 或上传一些敏感数据到服务器时候,服务端使用同样的算法就可以解密。
常用算法:
DES :安全度在现代已经不够高
3DES:算法强度提高了很多,但是其执行效率低下
AES:算法加密强度大,执行效率高,使用简单,实际开发中建议选择AES 算法。
多线程相关
1. 进程和线程的区别
- 进程是 CPU 资源分配的最小单位,线程是 CPU 执行的最小单位。
- 进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。
- 一个进程内可拥有多个线程,进程可开启进程,也可开启线程。
- 一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。
2. run() 和 start() 方法区别
- run() :仅仅是封装被线程执行的代码,直接调用时普通方法
- start()::首先启动了线程,然后由 JVM 去调用该线程的 run() 方法,调用两次抛出异常
3. 线程调度模型
- 分时调度模型:
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。 - 抢占式调度模型:
优先让优先级高的线程使用 CPU ,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java 使用的是抢占式调度模型
4. 三个线程保证顺序执行
Thread.join() 方法 :join() 方法放到 start() 后面
-
newSingleThreadExecutor
ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(t1); executor.submit(t2); executor.submit(t3); executor.shutdown();
同步锁+生产者消费者模型
信号量
5. Java 开启线程的方式
- 继承 Thread 类
- 实现 Runable 接口
- 避免 Java 单继承带来的局限性。
- 把线程同程序的代码、数据分离,较好的体现了面向对象的设计思想,适合多个相同的程序代码去处理同一资源的情况。
- Callable:结合线程池
6. 死锁问题及其代码
死锁:指两个或两个以上的线程在执行过程中,因争夺资源产生的一种互相等待现象。