面经分享--小米Java一面

目录

1.Kafka和RocketMQ的区别

2.反射的作用

3.类加载的具体过程,双亲委派模型的机制

4.TCP的四次挥手

5.多线程的优势

6.死锁产生的原因,怎么解决

7.Java并发的工作原理

8.常用的git命令

9.算法题

[1.leetcode 3.无重复字符的最长子串(中等)](#1.leetcode 3.无重复字符的最长子串(中等))

[2.leetcode 88. 合并两个有序数组(简单)](#2.leetcode 88. 合并两个有序数组(简单))


1.Kafka和RocketMQ的区别

虽然自己平时用RabbitMQ用得多一点,但是在选择RabbitMQ的时候也对这两个进行了一定的了解

1.适用的对象不同:RokcetMQ是阿里自己开发的,产品质量确实经过了阿里淘宝天猫的双11的检验,并发量也能达到十万级,Kafka适用于更大型的项目,支持的并发量更高,能达到几十万级,支持集群部署。

2.实现的引言和支持的语言不同:RocketMQ目前主要支持Java语言,C++都有所限制,其他的语言是不支持的,而Kafka支持的语言较多,但kafka有一个非常致命的缺点,就是可能会发生一小部分消息的丢失。

选择:如果能够允许一小部分的内容丢失,并且追求极高的吞吐量和高性能直接选Kafka,如果使用的是Java语言,并且也想用MQ的一些功能如:延迟队列,消息事务等就选RockctMQ

2.反射的作用

反射其实就是一个程序在运行的时候,对于一个类,无论这个类是什么类,不可继承类啊,父类子类啊,抽象类这些都能够获得其类的属性和方法,也不管这些属性和方法是由什么修饰符来修饰的,都能够获取。

反射有一些特性:

1.运行时类信息的访问。

2.动态对象的创建:通过反射API动态创建对象实例,即使在编译的时候不知道具体的类名,通过class类的newInstance和Constructor对象的newInstance方法实现的。

3.动态方法调用:通过Method类的invoke方法。

4.访问和修改字段值:通过Filed类的get和set方法实现。

优点就是可以动态地去获取对象实例,缺点是效率比较低(编译器无法优化,调用了很多其他方法),并且反射破坏了封装和类的私有方法,不安全。

3.类加载的具体过程,双亲委派模型的机制

类加载过程其实用大白话理解就是运行的时候,所写的类在JVM中是如何进行加载的

1.加载阶段:将字节码数据从不同的数据源读取到JVM中,并生成.class对象

2.连接:将原始的类信息转化为JVM运行的过程,这又分为3个部分

(1)验证:JVM得去验证所加载的类是安全的,不会出现一些非法的行为,要符合JVM虚拟机规范

(2)准备:创建类或接口的静态变量,并初始化静态变量的初始值,分配所需要的内存

(3)解析:将常量池的符号引用替换成直接引用(将符号表示的类,方法转换成内存地址等)

3.初始化阶段:真正去执行类初始化的代码逻辑,包括静态字段赋值等。

父亲委派模型:当要去加载一个类的时候,会交由其上一级加载器去判断有没有加载过,从下至上是自定义类加载器,应用程序类加载器,扩展类加载器,启动类加载器,目的是避免重复加载Java类型

4.TCP的四次挥手

进行数据传输的双方,因为某一方认为数据已经处理完毕,就向对方发起FIN报文,代表我这边已经不会再发送数据过去了,进入FIN_WAIT1状态,对方收到这个FIN报文之后,返回一个ACK,表示已经收到这个报文了,进入CLOSE_WAIT状态,接收到这个FIN报文之后,TCP协议栈会开启一个文件结束符EOF在接收缓冲区中,处理完缓冲区中的数据之后,也就是读到了EOF,这时对方就会发送一个FIN报文,代表对方也不会再发送数据了,进入LAST_ACK状态,此时我这边收到之后,返回一个确认ack,进入CLOSE状态,对方受到之后也进入CLOSE状态,到这里本次连接就结束了

5.多线程的优势

1.开启多个线程能帮我们处理某个任务的时候更快,例如处理400条数据,如果一个线程要1s中,那就需要400s,现在我有10个线程,同时去执行这样的操作,就只需要40s。

为什么不用进程是因为线程的创建和销毁的开销比进程小得多,linux中启动一个进程,操作系统要给他分配栈,堆,内存地址等。由于线程是共享内存数据,所以创建线程轻量很多。其次,进程之间的通信比较复杂,而线程很容易

6.死锁产生的原因,怎么解决

1.互斥条件:一个资源被一个线程占有之后,不能再被其他线程占有。

2.保持且请求:一个线程在占有一个资源的同时,去申请占有其他的资源

3.不可剥夺:一个线程不能强行从其他线程中剥夺部分资源

4.循环条件:A需要B的资源,B需要A的资源

解决方式只能从2,4入手,即资源有序分配法,A获取资源的顺序是1,2,3,4,那设置B获取资源的方式也是A,B,C,D

7.Java并发的工作原理

并发就是同一时间内执行多个线程,完成多个任务。Thread类和Runnable接口是实现并发的关键。线程的生命状态:新建,就绪,运行,阻塞,停止。

保证线程安全:1,原子性:通过加锁的方式保证同一时刻只有一个线程对资源进行修改,Java通过synchronized或者Atomic这样的关键字保证

2.可见性:通过synchronized和volatile这样的关键字保证,volatile的原理是读时,从主内存中读取数据,写时,先写工作内存之后刷新到主内存中

3.有序性:happens-before保证有序性

提供了很多锁机制:

1.内置锁:synchronized,JVM提供的。syncronized加锁时有无锁、偏向锁、轻量级锁和重量级锁几个级别。

2.ReentrantLock:JUC包下的一个锁,比synchronized高级一定,有一定的特殊用处(公平性,定时等)

3.读写锁

4.自旋锁:通过CAS进行实现

5.乐观锁和悲观锁:不先加锁(通过版本号或时间戳实现),直接加锁

8.常用的git命令

git pull从远程仓库拉代码 git add --将工作区代码的修改添加到暂存区 git commit -m将暂存区代码提交到本地仓库并添加相应的注释 git push 提交代码

想要撤回git commit 就使用git reset --soft HEAD 撤回add操作:git reset --mixed HEAD

撤回push:git push origin xxx --force 回退版本

9.算法题

1.leetcode 3.无重复字符的最长子串(中等)

java 复制代码
public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int[] hash = new int[128];
        int left=0,right=0;
        // 用于返回结果
        int ret = 0;
        while(right<n){
            char ch = s.charAt(right);
            hash[ch]++;
            while(hash[ch]>1){
                char c = s.charAt(left);
                hash[c]--;
                left++;
            }
            ret = Math.max(ret,right-left+1);
            right++;
        }
        return ret;
    }

2.leetcode 88. 合并两个有序数组(简单)

java 复制代码
public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m-1;
        int j = n-1;
        int k = m+n-1;
        while(i>=0 && j>=0){
            // 大的放后面
            if(nums1[i]>nums2[j]){
                nums1[k--] = nums1[i--];
            }else{
                nums1[k--] = nums2[j--];
            }
        }
        // 处理剩余的内容
        while(j>=0){
            nums1[k--] = nums2[j--];
        }
    }