26届JAVA 学习日记——Day13

2024.11.20周三
今天猛猛刷了下八股,恶补了Java和计算机的一些基础知识,计划是明天做Day9的苍穹外卖项目的实战。
在这里插入图片描述

八股

Java基础

为什么重写 equals 还要重写 hashcode ?

在Java中,hashCode() 方法是 Object 类的一部分,每个类都继承自 Object 类,因此每个对象都有一个 hashCode() 方法。该方法返回对象的哈希码值,它是该对象的一个整数值,通常用于哈希表(如 HashMapHashSet 等)中快速查找对象

当然,如果我们不把自定义对象当成HashMap的键来使用,那么自定义对象不重写equalshashCode也是可以的,该规定主要使HashMap正常工作。

假设我们有一个简单的类 Person,它有两个属性:name age。我们想要根据这两个属性来判断两个Person对象是否相等。

如果我们使用错误的 hashCode 实现,那么即使两个Person对象的 nameage 相同,它们也会被视为不同的对象,因为它们的哈希码是基于内存地址计算的。

示例代码

import java.util.Objects;

public class Person {
	//成员变量
    private String name;
    private int age;
	//构造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
	
	//equals方法重写
	@Override
	public boolean equals(Object o){
		//若当前对象与比较对象的 内存地址 相同,返回true
		if (this == o) return true;	
		//若比较对象为空或当前对象与比较对象的 类 不同,返回false
		//此处this.getClass()和getClass()效果相同
		if (o == null || getClass() != o.getClass()) return false;
		//将比较对象强转成当前类的对象存储用于比较
		Person person = (Person) o;
		//int类通过==比较值是否相同
		//String类通过调用java.util.Objects类的静态方法比较
		//若String类直接使用 == 比较可能会抛出 NullPointerException
		return age == person.age && Objects.equals(name, person.name);
	}
	
	//默认的hashCode实现
	public int hashCode(){
		//调用父类(Object类)的hashCode()方法
		//返回的是对象的内存地址的哈希码
		return super.hashCode();
	}

	//hashcode方法重写
	@Override
	public int hashCode() {
		/**Objects.hash()是一个静态方法,能接受多个参数
		并返回这些参数的哈希码的组合*/
		return Objects.hash(name, age);
	}
}
== 和 equals 比较的区别?

对于字符串变量来说,使用 == equals比较字符串时,其比较方法不同。 ==比较两个变量本身的值,即两个对象在内存的首地址equals比较字符串包含内容是否相同。

对于非字符串变量来说,如果没有对equals()进行重写的话,==equals方法的作用是相同的,都是用来比较对象在堆内存中的首地址,即比较两个引用变量是否指向同一个对象。

  • ==:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较
  • euqals():比较的是两个字符串的内容,属于内容比较
为什么有时会出现 4.0 - 3.6 = 0.40000001这种现象?

不是所有小数都可以用「完整」的二进制来表示的,比如十进制 0.1 在转换成二进制小数的时候,是一串无限循环的二进制数,计算机是无法表达无限循环的二进制数的,毕竟计算机的资源是有限。

因此,计算机只能用「近似值」来表示该二进制,那么意味着计算机存放的小数可能不是一个真实值。

现在基本都是用 IEEE 754 规范的「单精度浮点类型」(float)或「双精度浮点类型」(double)来存储小数的,根据精度的不同,近似值也会不同。
在这里插入图片描述

  • 符号位:表示数字是正数还是负数,为 0 表示正数,为 1 表示负数;
  • 指数位:指定了小数点在数据中的位置,指数可以是负数,也可以是正数,指数位的长度越长则数值的表达范围就越大;
  • 尾数位:小数点右侧的数字,也就是小数部分,比如二进制 1.0011 x 2^(-2),尾数部分就是 0011,而且尾数的长度决定了这个数的精度,因此如果要表示精度更高的小数,则就要提高尾数位的长度;

在这里插入图片描述

final关键字的作用

final关键字是一个用于修饰类、方法和变量的关键字。

  • 修饰类: 当final关键字用于修饰一个类时,表示这个类不能被继承。这意味着该类是最终的,其设计和实现不能被任何子类修改或扩展。
  • 修饰方法: 当final关键字用于修饰一个方法时,表示这个方法不能被子类覆盖重写)。这意味着该方法的行为在所有使用它的地方都是一致的,子类不能改变这个行为。
  • 修饰变量:
    • 成员变量(实例变量):当final关键字用于修饰类的成员变量时,表示这个变量的值一旦被初始化之后就不能再更改。也就是说,它是一个常量。如果成员变量是基本数据类型,其值不能更改;如果成员变量是引用类型,其引用不能指向另一个对象,但是对象本身的内容可以更改。
    • 静态变量(类变量):final修饰后即是常量。
    • 局部变量:当final关键字用于修饰局部变量时,表示这个变量的值在初始化之后不能更改。局部final变量必须在声明时或构造器中初始化。

抽象类能加final修饰吗?
不能,Java中的抽象类是用来被继承的,而final修饰符用于禁止类被继承或方法被重写,因此抽象类和final修饰符是互斥的,不能同时使用。

介绍Java的集合类

Java集合框架是一组接口和类,它们提供了一种用来存储和操作对象组的统一架构。这个框架主要位于java.util包中,它包含了许多接口和类,用于表示不同类型的集合,例如列表、集合、队列和映射等。

集合类的特点:

  • 泛型:Java集合框架广泛使用泛型,允许集合类在定义时指定它们可以包含的对象类型。
  • 迭代器:所有集合类都实现了Iterator接口,提供了一种统一的方式来遍历集合中的元素。
  • 线程安全性:Java集合框架中的一些类是线程安全的(如VectorHashtable),而另一些则不是(如ArrayListHashMap)。

Java集合框架中一些主要的接口和类:

Collection接口:

  • 是集合层次中的根接口,定义了集合的基本操作,如添加、删除、清空、遍历等。
  • 它有两个子接口:ListSet

List接口:

  • 继承自Collection接口,表示一个有序集合,可以包含重复的元素。
  • 常用实现类有ArrayListLinkedListVector

Set接口:

  • 继承自Collection接口,表示一个不包含重复元素的集合。
  • 常用实现类有HashSetLinkedHashSetTreeSet

Deque接口:

  • 继承自Queue接口,代表双端队列,允许元素从两端被添加或移除。
  • 常用实现类有ArrayDequeLinkedList

Map接口:

  • 表示键值对的集合,其中键是唯一的。
  • 常用实现类有HashMapLinkedHashMapTreeMapHashtable
    在这里插入图片描述

常用的Java集合实现类:

  • ArrayList: 动态数组,实现了List接口,支持动态增长。
  • LinkedList: 双向链表,也实现了List接口,支持快速的插入和删除操作。
  • HashMap: 基于哈希表的Map实现,存储键值对,通过键快速查找值。
  • HashSet: 基于HashMap实现的Set集合,用于存储唯一元素。
  • TreeMap: 基于红黑树实现的有序Map集合,可以按照键的顺序进行排序。
  • LinkedHashMap: 基于哈希表和双向链表实现的Map集合,保持插入顺序或访问顺序。
  • PriorityQueue: 优先队列,可以按照比较器或元素的自然顺序进行排序。
ArrayList和LinkedList的区别

ArrayListLinkedList都是Java中常见的集合类,它们都实现了List接口。

  • 底层数据结构不同:ArrayList使用数组实现,通过索引进行快速访问元素;LinkedList使用链表实现,通过节点之间的指针进行元素的访问和操作。
  • 插入和删除的操作的效率不同:ArrayList在尾部的插入和删除操作效率较高,但在中间或开头的插入和删除操作效率较低,需要移动元素。LinkedList在任意位置的插入和删除操作效率都比较高,因为只需要调整节点之间的指针,但是LinkedList是不支持随机访问的,所以除了头节点外插入和删除的时间复杂度都是O(n)。
  • 随机访问的效率不同:ArrayList支持通过索引进行快速随机访问,时间复杂度为O(1);LinkedList需要从头到尾开始遍历链表,时间复杂度为O(n)。
  • 空间占用:ArrayList在创建时需要分配一段连续的内存空间,因此会占用较大的空间。LinkedList每个节点只需要存储元素和指针,因此相对较小。
  • 使用场景:ArrayList适用于频繁随机访问和尾部的插入删除操作,而LinkedList适用于频繁的中间插入删除操作和不需要的随机访问的场景。
  • 线程安全:两个集合都不是线程安全的。

操作系统

什么是死锁?死锁产生的条件?

在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。

那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。

举个例子,小林拿了小美房间的钥匙,而小林在自己的房间里,小美拿了小林房间的钥匙,而小美也在自己的房间里。如果小林要从自己的房间里出去,必须拿到小美手中的钥匙,但是小美要出去,又必须拿到小林手中的钥匙,这就形成了死锁。

死锁只有同时满足以下四个条件才会发生:

  • 互斥条件;
  • 持有并等待条件;
  • 不可剥夺条件;
  • 环路等待条件;

互斥条件
互斥条件是指多个线程不能同时使用同一个资源。
在这里插入图片描述
上图中,如果线程A已经持有的资源,不能再同时被线程B持有,如果线程B请求获取线程A已经占用的资源,那线程B只能等待,直到线程A释放该资源。

持有并等待条件
持有并等待条件是指,当线程A已经持有了资源1,又想申请资源2,而资源2已经被线程C持有了,所以线程A就会处于等待状态,但是线程A在等待资源2的同时并不会释放自己已经持有的资源1
在这里插入图片描述

不可剥夺条件
不可剥夺条件是指,当线程已经持有了资源,在自己使用完之前不能被其他线程获取,线程B如果也想使用此资源,则只能在线程A使用完并释放完才能获取。
在这里插入图片描述

环路等待条件
环路等待条件指的是,在死锁发生的时候,两个线程获取资源的顺序构成了环形链
比如,线程A已经持有资源2,而想请求资源1,线程B已经获取了资源1,而想请求资源2,这就形成资源请求等待的环形图。
在这里插入图片描述

线程有哪几种状态?

java.lang.Thread.State枚举类中定义了六种线程的状态,可以调用线程Thread中的getState()方法获取当前线程的状态。

线程状态解释
NEW尚未启动的线程状态,即线程创建,还未调用start方法
RUNNABLE就绪状态(调用start,等待调度)+正在运行
BLOCKED等待监视器锁时,陷入阻塞状态
WAITING等待状态的线程正在等待另一线程执行特定的操作(如notify)
TIMED_WAITING具有指定等待时间(比如等待?秒钟)的等待状态
TERMINATED线程完成执行,终止状态
有哪些进程调度算法?

进程调度算法也称 CPU 调度算法,毕竟进程是由 CPU 调度的。

当 CPU 空闲时,操作系统就选择内存中的某个「就绪状态」的进程,并给其分配 CPU。

什么时候会发生 CPU 调度呢?通常有以下情况:

「非抢占式调度」
非抢占式的意思就是,当进程正在运行时,它就会一直运行,直到该进程完成或发生某个事件而被阻塞时,才会把 CPU 让给其他进程。

  • 当进程从运行状态转到等待状态;
  • 当进程从运行状态转到终止状态;

「抢占式调度」
抢占式调度,顾名思义就是进程正在运行的时,可以被打断,使其把 CPU 让给其他进程。抢占原则一般有三种:时间片原则、优先权原则、短作业优先原则。

  • 当进程从等待状态转到就绪状态;
  • 当进程从运行状态转到终止状态;
先来先服务调度算法

非抢占式的先来先服务(First Come First Severd, FCFS)算法。
在这里插入图片描述
顾名思义,先来后到,每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行

FCFS 对长作业有利(短作业等待的时间长),适用于 CPU 繁忙型作业的系统,而不适用于 I/O 繁忙型作业的系统。

最短作业优先调度算法

最短作业优先(Shortest Job First, SJF)调度算法同样也是顾名思义,它会优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。
在这里插入图片描述
显然对长作业不利,很容易造成一种极端现象:比如一个长作业在就绪队列等待运行,而这个就绪队列有非常多的短作业,那么就会使得长作业不断的往后推,周转时间变长,致使长作业长期不会被运行。

高响应比优先调度算法

高响应比优先 (Highest Response Ratio Next, HRRN)调度算法主要是权衡了短作业和长作业。

每次进行进程调度时,先计算「响应比优先级」,然后把「响应比优先级」最高的进程投入运行,「响应比优先级」的计算公式:
在这里插入图片描述

  • 「等待时间」相同时,依据糖水公式,「要求的服务时间」(水)越短,「响应比」(浓度)就越高,因此短作业的进程容易被选中运行。
  • 「要求的服务时间」(水)相同时,「等待时间」(糖分)越长,「响应比」(浓度)就越高,这就兼顾到了长作业进程,因为进程的响应比可以随时间等待的增加而提高,当其等待时间足够长时,其响应比便可以升到很高,从而获得运行的机会;
时间片轮转调度算法

最古老、最简单、最公平且使用最广的算法就是时间片轮转(Round Robin, RR)调度算法。
在这里插入图片描述
每个进程被分配一个时间段,称为时间片(Quantum),即允许该进程在该时间段中运行。

  • 如果时间片用完,进程还在运行,那么将会把此进程从 CPU 释放出来,并把 CPU 分配另外一个进程;
  • 如果该进程在时间片结束前阻塞或结束,则 CPU 立即进行切换;

另外,时间片的长度就是一个很关键的点:

  • 如果时间片设得太短会导致过多的进程上下文切换,降低了 CPU 效率;
  • 如果设得太长又可能引起对短作业进程的响应时间变长。

通常时间片设为 20ms~50ms 通常是一个比较合理的折中值。

最高优先级调度算法(抢占式/非抢占式)

对于多用户计算机系统就有不同的看法了,它们希望调度是有优先级的,即希望调度程序能从就绪队列中选择最高优先级的进程进行运行,这称为最高优先级(Highest Priority First,HPF)调度算法

进程的优先级可以分为,静态优先级或动态优先级:

  • 静态优先级:创建进程时候,已经确定过优先级,之后整个运行时间优先级都不会变化。
  • 动态优先级:根据进程的动态变化调整优先级,比如,如果进程运行时间增加,则降低其优先度;如果进程等待时间(就绪队列的等待时间)增加,则升高其优先级,也就是随着时间的推移增加等待进程的优先级

依然有缺点,可能会导致低优先级的进程永远不会运行。

多级反馈队列调度算法

多级反馈队列(Multilevel Feedback Queue)调度算法是「时间片轮转算法」和「最高优先级算法」的综合和发展。

  • 「多级」表示有多个队列,每个队列优先级从高到低,同时优先级越高时间片越短。
  • 「反馈」表示如果有新的进程加入优先级高的队列时,立刻停止当前正在运行的进程,转而去运行优先级高的队列。

在这里插入图片描述
工作原理:

  • 设置多个队列,赋予每个队列不同的优先级,每个队列优先级从高到低,同时优先级越高时间片越短
  • 新的进程会被放入到第一级队列的末尾,按先来先服务的原则排队等待调度,如果在第一级队列规定的时间片没有运行完成,则转入第二级队列的末尾,以此类推,直至完成。
  • 当较高优先级的队列为空,才调度较低优先级的队列。如果进程运行时,有新进程进入较高优先级的队列,则停止当前运行的进程并将其移入到原队列末尾,接着让较高优先级的进程运行。

对于短作业可能可以在第一级队列很快被处理完。对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待的时间变长了,但是运行时间也会更长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间

算法

今日暂无该内容学习。

项目

苍穹外卖项目回顾

全局异常处理器的实现
com.sky.handler.GlobalExceptionHandler

/**
 * 业务异常
 */
 // BaseException 属于 Exception class
public class BaseException extends RuntimeException {

    public BaseException() {
    }

    public BaseException(String msg) {
        super(msg);
    }
}
/**
 * 全局异常处理器,处理项目中抛出的业务异常
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 捕获业务异常
     * @param ex
     * @return
     */
    @ExceptionHandler 
    public Result exceptionHandler(BaseException ex){
        log.error("异常信息:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }

    /**
     * 处理SQL异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        //违反了约束条件的SQL错误处理(如此处username设置成唯一约束,此时重复添加就会出现该异常)
        String message = ex.getMessage();
        if (message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username = split[2];
            String msg = username + MessageConstant.ALREADY_EXISTS;
            return Result.error(msg);
        }else{
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }
}

总结:在方法中添加@ExceptionHandler注解,将Exception class的类作为参数传入方法中,再针对这些异常做异常处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值