十分钟掌握 Java框架应用 面试考点

1.Spring 并发任务

spring通过 TaskExecutor来实现多线程并发编程。使用ThreadPoolExecutor可实现基于线程池的TaskExecutor,使用@EnableAsync开启对异步任务的支持,并通过在实际执行bean方法中使用@Async注解来声明一个异步任务

2.Spring 计划任务

通过配置注解@EnableScheduling来开启对计划任务的支持,然后再要执行的任务上加注解@Scheduled, 通过@Scheduled支持多种类型计划任务,包含cron,fixDelay、fixRate(固定时间执行

3.Spring Bean 生命周期

1.instantiate bean对象实例化

2.populate properties 封装属性 (Ioc 中用构造函数注入会导致相互依赖的问题, 用属性注入则不会)

3.如果Bean实现BeanNameAware 执行 setBeanName

4.如果Bean实现BeanFactoryAware 或者 ApplicationContextAware 则调用响应的setBeanFactory 和 setApplicationContext

5.如果存在类实现 BeanPostProcessor(后处理Bean) ,执行postProcessBeforeInitialization,BeanPostProcessor接口提供钩子函数,用来动态扩展修改Bean。(程序自动调用后处理Bean, 如@Before 注解的方法)

 

6.如果Bean实现InitializingBean 执行 afterPropertiesSet

7.调用<bean init-method=”init”> 指定初始化方法 init

8.如果存在类实现 BeanPostProcessor(处理Bean) ,执行postProcessAfterInitialization (如@PostConstruct 注解的方法)

9.执行业务处理

10.如果Bean实现 DisposableBean 执行 destroy

11.调用<bean destroy-method=”customerDestroy”> 指定销毁方法 customerDestroy

4.Spring 依赖注入 Ioc 作用域

singleton 单列

prototype 非单例, 每次调用创建新的实例

request 每个 http 请求产生一个实例, 该作用域仅在基于web的Spring ApplicationContext情形下有效。

session 每个 http session 产生一个实例, 该作用域仅在基于web的Spring ApplicationContext情形下有效。

global session

在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于 web的Spring ApplicationContext情形下有效。

5. Spring AOP 实现原理

与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的

6. Redis 的好处

1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
2.支持丰富数据类型,支持string,list,set,sorted set,hash
3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
4.丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

5.Redis是单进程单线程的, 原子性地操作对并发友好

7. Redis 的事物特征

1.在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。
2.和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。
3.我们可以通过MULTI命令开启一个事务,在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。
4.在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。
5.当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。

8. Redis 分布式锁

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放. 或者可以同时把setnx和expire合成一条指令来用.

9. Redis keys/scan

keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了

10. Redis 异步队列

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。或者blpop,在没有消息的时候,它会阻塞住直到消息到来

使用pub/sub主题订阅者模式,可以实现1:N的消息队列, 但是在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等

延时队列, 使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理

11. Redis 持久化

bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态

aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

12. Redis pipeline

可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。

13. Redis 集群

Redis Sentinel着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

14. GET / POST 请求区别

  1. 不同浏览器对携带数据大小限制不同, Get 比 Post 小很多, 大多数浏览器为2k
  2. GET产生一个TCP数据包,浏览器会把http headerdata一并发送出去,服务器响应200(返回数据);
    POST
    产生两个TCP数据包,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)
    不过有些浏览器, POST 也只发一次, 比如火狐

  3. GET在浏览器回退时是无害的,POST会再次提交请求。
    GET
    产生的URL地址可以被Bookmark,而POST不可以。
    GET
    请求会被浏览器主动cache,而POST不会,除非手动设置。
    GET
    请求只能进行url编码,而POST支持多种编码方式。
    GET
    请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
    GET
    只接受ASCII字符的参数的数据类型,而POST没有限制

15. 冒泡排序 / 快速排序

// 冒泡
public static void main(String[] args) {
        int[] array = new int[]{1, 2, 3, 49, 7, 6, 5, 4, 3, 0};
        for (int i = 0; i < array.length - 1; i++) {//外层循环控制排序趟数
            for (int j = 0; j < array.length - 1 - i; j++) {//内层循环控制每一趟排序多少次
                if (array[j] > array[j + 1]) {
                    array[j] = array[j] ^ array[j + 1];
                    array[j + 1] = array[j] ^ array[j + 1];
                    array[j] = array[j] ^ array[j + 1];
                }
            }
        }
        System.out.println(Arrays.toString(array));
    }
// 快速
    public static void main(String[] args) {
        int[] array = new int[]{1, 2, 3, 49, 7, 6, 5, 4, 3, 0};
        quickSort(array, 0, array.length - 1);
        System.out.println(Arrays.toString(array));
    }

    private static void quickSort(int array[], int left, int right) {
        int leftIndex = left;
        int rightIndex = right;
        int temp = array[left];
        if (left >= right)
            return;
        while (leftIndex != rightIndex) {
            while (leftIndex < rightIndex && array[rightIndex] >= temp)
                rightIndex--;
            if (rightIndex > leftIndex)
                array[leftIndex] = array[rightIndex];//a[i]已经赋值给temp,所以直接将a[j]赋值给a[i],赋值完之后a[j],有空位
            while (leftIndex < rightIndex && array[leftIndex] <= temp)
                leftIndex++;
            if (leftIndex < rightIndex)
                array[rightIndex] = array[leftIndex];
        }
        array[leftIndex] = temp;
        quickSort(array, left, leftIndex - 1);/*递归左边*/
        quickSort(array, leftIndex + 1, right);/*递归右边*/
    }

16. String, Integer 这样的封装适合做 HashMap 的 key 吗?

String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变.

当然你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了

17. TCP/IP 三次握手和四次挥手

三次握手流程

  • 客户端发个请求“开门呐,我要进来”给服务器
  • 服务器发个“进来吧,我去给你开门”给客户端
  • 客户端有很客气的发个“谢谢,我要进来了”给服务器

四次挥手流程

  • 客户端发个“时间不早了,我要走了”给服务器,等服务器起身送他
  • 服务器听到了,发个“我知道了,那我送你出门吧”给客户端,等客户端走
  • 服务器把门关上后,发个“我关门了”给客户端,然后等客户端走(尼玛~矫情啊)
  • 客户端发个“我知道了,我走了”,之后自己就走了

18. 线程和进程

进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。

线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。

一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。

线程没有地址空间,线程包含在进程的地址空间中。线程上下文只包含一个堆栈、一个寄存器、一个优先权,线程文本包含在他的进程 的文本片段中,进程拥有的所有资源都属于线程。所有的线程共享进程的内存和资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段, 寄存器的内容,栈段又叫运行时段,用来存放所有局部变量和临时变量。

19. 经典死锁问题

情况一:

Object1 获取已 lock1, 并尝试获取 lock2

Object 获取已获取 lock2, 并尝试获取 lock1

情况二:

代码问题, 比如发生异常没捕获到并释放锁, 捕获方式错误, 如抛出 throwable, 捕获的确实 exception

20. 二叉树遍历 (前序遍历)

// 递归
public static void main(String[] args) {
        BinaryTreeNode binaryTreeNode = new BinaryTreeNode();
        printlnBinaryTreeNode(binaryTreeNode);
    }

    private static void printlnBinaryTreeNode(BinaryTreeNode root) {
        if (null != root) {
            System.out.println(root.getData());
            printlnBinaryTreeNode(root.getLeft());
            printlnBinaryTreeNode(root.getRight());
        }
    }
// 非递归
public static void main(String[] args) {
        BinaryTreeNode binaryTreeNode = new BinaryTreeNode();
        printlnBinaryTreeNode(binaryTreeNode);
    }

    public static void printlnBinaryTreeNode(BinaryTreeNode root) {
        Stack<BinaryTreeNode> stack = new Stack<BinaryTreeNode>();
        while (true) {
            while (root != null) {
                System.out.println(root.getData());
                stack.push(root);
                root = root.getLeft();
            }
            if (stack.isEmpty()) break;
            root = stack.pop();
            root = root.getRight();
        }
    }

21. 一根棍子, 折成3段形成三角形概率

设长L的棍子任意折成3段的长度分别是x,y和z=L-(x+y),<x,y,z∈(0,L)>

x +y<L

三段能构成三角形,则

x+y>z, 即 x +y>(L-x-y), x +y>L/2

y+z>x, 即 y +(L-x-y)>x, x<L/2

z+x>y, 即 (L-x-y)+x>y, y<L/2

所求概率等于x+y=L/2、x=L/2、y=L/2三条直线所包围图形的面积除以直线(x+y)=L与x轴、y轴所包围图形的面积(图略)。

故长L的棍子任意折成3段,此3段能构成一个三角形的概率是是

(L/2*L/2*1/2)÷(L*L*1/2)=L^2/8÷(L^2/2)=1/4

22. 6个球,其中1个重量不一样, 天平找法

像猜价格一样, 中间砍, 但是价格不定, 所以需要有一个参照物, 就得将6个球分为3组, 先对比2组, 好的情况是一样的, 再从第三组中拿一个和这2组中任意一个球比即可.

23. 逆序单项链表

以链表A->B->C->D为例,逆序此链表。
  0.初始状态                                                        1.2.3 循环部分
  p = head->next;                                              while(q!=null){
  q = head->next->next;                                      t = q->next;
  t = null;                                                            q->next = p;
                              p = q;
                                                                              q = t;
                           }
head->next->next = null;//设置链表尾
 head-next = p;//修改链表头

24. 判断是否链表存在环

2个指针, 1个走1步, 1个走2步, 如果遇到相等则有换, 否则走2步的走完则没有环

或者用一个结构体, 比如 hashset 来存放遍历过的节点, 比较是否走过

25. Linux 文件之间传送文件常用命令

SCP  /  rsync / gzip

3人评论了“十分钟掌握 Java框架应用 面试考点”

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

Scroll to Top