Java基础小记

线程与进程

1、一个线程只能属于一个进程,一个进程可以有多个线程。

2、资源分配给线程,进程不拥有资源,而是共享进程的资源。

3、处理机分给线程,在处理机运行的是线程。

Java 包装类

Integer、Boolean、Byte、Character、Short、Long 都有缓存机制,因此两个变量使用 == 做比较时,如果在缓存中,是不会比较地址的。

多态

多态是指不同的子类对同一种行为有不同的表现形式。

多态包括编译时多态(重载)和运行时多态(重写)。重载:方法名相同,参数列表不同(参数个数、类型、顺序),返回值、访问修饰符也可以不同。

运行时多态的实现包括:子类继承父类、实现接口。

String 的不可变

image-20240222173405251

修改 s 变量时,不在原来的地址上修改,而是指向新的引用地址。

image-20240222173659994

value 用 final 修饰,指的是 value 变量的引用不能更改,不能指向其他地址。

但数组本身是可以修改值的,这并不会对其地址有影响。

  • 设计成不可变,可以使用字符串常量池。

String s1 = new String(“abc”); 这句话创建了几个字符串对象?

1 个或 2 个。一、如果字符串常量池中没有字符串对象 “abc” 的引用,则它会在堆上创建两个字符串对象,其中一个字符串对象的引用会被保存在字符串常量池中。

image-20240222170700469

2、如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。

对于 String = “abc”,如果常量池中有 “abc” 字符串对象,就直接指向它;否则会在常量池中创建一个 “abc” 字符串对象。

实现深拷贝

三种方式:重写 clone() 方法、通过序列化实现利用反射方式实现

可以不重写 hashcode 吗?

不可以,如果两个对象通过 equals 判断是相等的,但哈希码不同,当用作哈希表的键时,可能不能正常工作。例如,当向 HashSet 中添加两个逻辑上相等的对象时,本应该只有一个被存储,但因为哈希码不同,被存储了两次。这就违反了 Set 集合中唯一性的约束。

String 类型的变量和常量用 “+” 运算符发生了什么?

String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
  • 对于 String str3 = “str” + “ing”;编译器会优化成String str3 = “string”;

  • 对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

String str4 = new StringBuilder().append(str1).append(str2).toString();

String str = “abc” + new String(“def”); 创建了几个对象?(假设常量池中没有缓存)

这会被优化为

String s = new String("def");
new StringBuilder().append("abc").append(s).toString();

创建了 5 个对象 new StringBuilder() -> “abc” -> “def” -> new String() -> toString()

final 可以加在哪里,有什么作用

类:这个类不能被继承,final 类中的所有成员方法都隐式地指定为 final,但是字段不自动成为 final

方法:这个方法不能被子类覆盖或重写。

变量:该变量的值一旦被初始化之后就不能被改变。对于基本数据类型,它的值不能被改变。对于引用类型,它的引用不能改变,但对引用的对象本身是可以修改的。

JMM 内存模型

每个线程都有一个私有的本地内存,线程可以把变量保存 本地内存 (比如机器的寄存器)中,而不是直接在主存中进行读写。如果线程间需要通信,必须通过主内存来进行。image-20240313145809511

Cookies 和 Session 的区别

1、当用户第一次登录成功后,服务器会创建一个 Session,并为这个 Session 创建唯一的 SessionID。Session 保存在服务器端,保存了用户的状态信息。

2、服务器将这个唯一的 SessionID 存储到一个 Cookies 中,并通过 HTTP 响应将该 Cookies 发生给用户的 Web 浏览器。

3、用户的浏览器会保存这个 Cookies,后续请求都会携带此 Cookies 发送回服务器。

4、服务器会读取 Cookies 中的 SessionID,通过它找到 Session 数据,里面包含了用户的状态信息,如登录凭证、购物车内存等。

5、当用户退出登录或 Session 过期后,服务器会销毁 Session 数据。

JUC

sleep() 和 wait()

sleep() 是 Thread 类的静态方法

wait() 是 Object 类的方法,直到其他线程调用相同对象的 notify() 方法。wait() 方法通常与 synchronized 关键字一起使用,表示释放锁,从而允许其他线程获取锁。

线程池

线程池的7个参数和工作原理

int corePoolSize,    // 核心线程数,创建后不会被回收
int maximumPoolSize,   // 最大线程数,当核心线程数已满、最大线程数未满,就创建一个新线程
long keepAliveTime,  // 空闲线程存活时间,当可被回收的线程大于keepAliveTime被回收
TimeUnit unit, 
BlockingQueue<Runnable> workQueue, // 工作队列,当提交的任务超过核心线程数,再提交的任务就放到工作队列
ThreadFactory threadFactory,  // 线程工厂,可以设定线程名、线程编号等
RejectedExecutionHandler handler // 拒绝策略

怎么关闭线程池?

shutdown():会在当前运行的线程执行完任务后关闭它们。

shutdownNow():会试图停止所有正在执行的任务,并返回还没有开始执行的任务。并不保证每个任务都能成功停止,因为这个方法本质是对线程池每个线程调用了 interrupt() 方法来尝试取消线程的执行,但中断一个线程是非强制性的,也就是它只是给线程发送了一个取消的信号,而线程能否响应,怎样响应是取决于线程本身的。

Java 线程通信的主要方式

volatile关键字

  • 所有volatile修饰的变量一旦被某个线程更改,必须立即刷新到主内存
  • 所有volatile修饰的变量在使用之前必须重新读取主内存的值

等待/通知机制:等待通知机制是基于wait和notify方法来实现的,在一个线程内调用该线程锁对象的wait方法,线程将进入等待队列进行等待直到被通知或者被唤醒。

集合

HashMap 和 HashTable 的区别

  • 安全性:HashMap 是线程不安全的,HashTable 是线程安全的
  • HashMap 可以使用null作为key,Hashtable则不允许null作为key
  • 扩容:HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。HashMap扩容时是当前容量翻倍即:capacity *2,Hashtable扩容时是容量翻倍+1即:capacity* (2+1)

HashMap 1.7 和 1.8

1.7 底层是数组加链表,1.8底层是数组+链表+红黑树。

当 put 一个 Node 时,会计算 key 的 hashcode, 再通过将 hashcode 与 (n - 1) 进行与运算,有两种情况:

1、数组索引的元素是空的,这种情况很简单,直接将元素放进去就好了。

2、已经有元素占据了索引位置,这种情况下我们需要判断一下该位置的元素和当前元素是否相等,使用equals来比较。如果两者相等则直接覆盖如果不等则在原元素下面使用链表的结构存储该元素

image-20240320110614657

当链表的元素个数达到8并且数组长度超过64的时候使用链表存储就转变成了使用红黑树存储。

HashMap中有两个重要的参数:初始容量大小和加载因子。初始容量大小为 16,默认加载因子为 0.75。

为什么加载因子设置为 0.75?

往哈希表中放数据类似骰硬币,01分布。做个假设:扔 k 次骰子,没有一次是相同的概率。

二项分布 Cn^0

为什么 HashMap 不安全?

  • 多线程下扩容会死循环
  • 多线程下 put 会导致元素丢失
  • put 和 get 并发时会导致 get 到 null

CopyonWriteArraylist:读写、读读都不互斥,只有写写互斥。多个线程可以同时读。

// 插入元素到 CopyOnWriteArrayList 的尾部
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获取原来的数组
        Object[] elements = getArray();
        // 原来数组的长度
        int len = elements.length;
        // 创建一个长度+1的新数组,并将原来数组的元素复制给新数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 元素放在新数组末尾
        newElements[len] = e;
        // array指向新数组
        setArray(newElements);
        return true;
    } finally {
        // 解锁
        lock.unlock();
    }
}

也就是对数组进行增加元素时,会复制出一份新的数组,并将元素加到新的数组。

对数组进行修改时,会分段复制,index 前的和 index 后的分别复制到新数组。

Spring 框架

介绍下 Spring IOC 和 Spring Aop

ioc 是控制反转,ioc 可以看作一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系。

aop 是面向切面编程,当类中的方法有大量冗余的且与业务无关的代码,我们可以将它们通过注解方式抽离出来,让开发者只关心业务逻辑。

Spring AOP 的实现原理是基于动态代理字节码操作实现的。在编译时,Spring 会使用 AspectJ 编译器将切面代码编译成字节码文件。在运行时,Spring 会使用 Java 动态代理或 CGLIB 代理生成代理类。代理类会在目标对象方法执行前后插入切面代码,从而实现 AOP。

Spring AOP 可以使用两种代理方式:JDK动态代理和 CGLIB 代理。如果目标对象实现了至少一个接口,则使用JDK动态代理;否则,使用 CGLIB 代理。

Bean 的生命周期

image-20240326162005596

  • 实例化、属性赋值、初始化、销毁。

  • 初始化的具体操作:Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及InitializingBean 和 init-method 的初始化操作。

  • 销毁的具体操作,有注册相关销毁回调接口,最后通过DisposableBean 和 destory-method 进行销毁。

Bean 会被垃圾回收吗

Bean没有被其他Bean引用,且容器也没有任何引用指向它时,会变成一个不可达对象,就会被垃圾回收。但在 Spring 中的 Bean 是单例的,一直存活在应用程序的整个生命周期中,所以除非应用程序结束或 Spring 容器被销毁,否则是不会被 GC 的。

@SpringBootApplication 注解

@Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类

@EnableAutoConfiguration:启用 SpringBoot 的自动配置机制

@ComponentScan:扫描被@Component (@Repository,@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。

自动装配

image-20240326105137278

Spring Boot 通过 @EnableAutoConfiguration 开启自动装配,通过 SpringFactoriesLoader 最终加载 META-INF/spring.factories 中的自动配置类实现自动装配,自动配置类其实就是通过 @Conditional 按需加载的配置类,想要其生效必须引入 spring-boot-starter-xxx 包实现起步依赖。

Spring 常见的一些模块

Spring Core:供 ioc 容器

Spring AOP

Spring DAO:JDBC的抽象层

Spring ORM:实体关系映射

Spring Web

Spring Web MVC

Spring Test:单元测试

动态获取spring容器里面的bean

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student st = applicationContext.getBean(Student.class);

@Autowired 和 @Resource

一个按类型,一个按名称进行装配。

依赖注入

是一种消除类之间依赖关系的设计模式,动态的向某个对象提供它所需要的其他对象。底层是多态机制与反射机制

https://zhuanlan.zhihu.com/p/67032669

https://blog.csdn.net/bestone0213/article/details/47424255

SpringMVC

image-20240325135256785

springmvc 流程:

1、客户端发起请求,Servlet会根据根据请求的路径进行解析,DispatchServlet 拦截请求。

2、DispatchServlet 调用 HandlerMapping,去查找对应的 handler (也就是 controller)。

3、DispatchServlet 调用 HanderAdapter 执行 handler。

4、handler 执行完毕后返回 ModelAndView,视图解析器 ViewResolver 会查找实际的 view,DispatchServlet 会把 model 渲染到 view 上。

5、把 view 返回给浏览器。

过滤器 和 拦截器

  • 调用时机:Filter 是在进入容器后,在 servlet 之前进行预处理,结束是在 servlet 处理完后。Interceptor 是在进入 servlet 后,在进入 controller 之前预处理的,在controller中的方法执行完后返回 ModelAndView 后请求结束。
  • Filter 一般用于字符编码和 cors 跨域问题,Interceptor 一般作用于业务逻辑的处理,如用户权限等。

MyBatis

#{} 和 ${} 的区别

#{} 方式是先用占位符代替参数将SQL语句先进行预编译,再将参数中的内容替换进来。由于SQL语句已经被预编译过,其SQL意图将无法通过非法的参数内容实现更改,其参数中的内容,无法变为SQL命令的一部分。

MySQL

join 和 left join

JOIN:返回两个表中都有匹配的行

left JOIN:返回左表的所有行,如果在右表没有为 null。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1216271933@qq.com

×

喜欢就点赞,疼爱就打赏