广联达5月java面经和笔经整理
记录一下我准备广联达面试整理的面经
我也准备了半天,结果面试官问我的真的比较简单,太友好了,希望能有二面。
二面面经地址:https://www.nowcoder.com/discuss/438545?source_id=profile_create&channel=666
内推地址:https://www.nowcoder.com/discuss/449191?channel=2000&source_id=home_feed
按知识点整理的面经
JAVA基础
4.多态,能说多少说多少。
多态概念:多态就是同一个接口,使用不同的实例而执行不同操作 不同类的对象对同一消息作出不同的响应就叫做多态 多态的分类 1、编译时多态,即方法的重载 2、运行时多态,即方法的重写 在 Java 中有两种形式可以实现多态:继承(多个子类对父类同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
String 和StringBuilder 和StringBuffer 的区别:
8、string和stringbuilder和stringbuffer那些(final类型,常量池啥的)
String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[],所以 String 对象是不可变的。 而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的 线程安全性 String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder与StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的 性能 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 对于三者使用的总结: 操作少量的数据: 适用 String 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
你对Java的理解(面向对象,jvm,语言特性,GC,类库)
简单易学; 面向对象(封装,继承,多态); 平台无关性( Java 虚拟机:在不同系统有特定实现,实现平台无关性); 有垃圾回收这种自动的内存管理机制,不需要手动释放内存。 不提供指针来访问内存 支持多线程(而 Java 语言却提供了多线程支持); 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便); 编译与解释并存(.class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。JIT是运行时编译,对热点代码进行编译)
说出5个常用的类
static关键字的作用,修饰在变量上,修饰在方法上有什么作用
static 关键字主要有以下四种使用场景:
1.修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量存放在 Java 内存区域的方法区。调用格式:类名.静态变量名 类名.静态方法名()
2.静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
final关键字主要用在三个地方:变量、方法、类。
1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
3. 使用final方法的原因有把方法锁定,以防任何继承类修改它的含义。
6、接口和抽象类(背了面经)
1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。
2. 接口中除了 static、final 变量,不能有其他变量,而抽象类中则不一定。
3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口。
4. 接口方法默认修饰符是 public,抽象方法可以有 public、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰!)。
5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。
override和overload
override重写: 重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。另外,如果父类方法访问修饰符为 private 则子类就不能重写该方法。也就是说方法提供的行为改变,而方法的外貌并没有改变。 overload:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
fianally在return之前还是之后
return语句已经执行了再去执行finally语句,不过并没有直接返回,而是等finally语句执行完了再返回结果
集合
集合
集合类Collection都有哪些,使用场景
HashMap:
多线程
多线程
5.Java的线程的生命周期
6.很详细,那说说Java的线程创建方式和生命周期
6.什么时候线程永远没法使用cpu?
外部中断,wait,sleep,yield
7.如何避免死锁发生?
5.很详细,那说说死锁,背完嗓子都说不了话了
Java锁机制有哪些
1、公平锁/非公平锁 2、可重入锁 3、独享锁/共享锁 4、互斥锁/读写锁 5、乐观锁/悲观锁 6、分段锁 7、偏向锁/轻量级锁/重量级锁 8、自旋锁(java.util.concurrent包下的几乎都是利用锁)
9.分布式锁实现高可用和可重入
基于表主键唯一做分布式锁(不可重入) 数据库mvcc实现乐观锁 基于redis实现分布式锁(非公平,非阻塞) 基于zookeeper实现分布式锁
10.锁的一些分类和使用,引入红锁
11.谈了红锁场景、如何判断资源已修改,失效啥的(说的不好,脑子有点乱)😌
Jvm
7.JVM内存模型还有java跨平台
3.Java内存区域划分
7、JVM分区、局部变量在哪、类加载过程(局部变量我说在堆,因为对象都在堆里,面试官说不对)局部变量在栈里面
//https://blog.csdn.net/ln152315/article/details/79223441?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase 类加载过程:加载->验证->准备->解析->初始化 加载指的是把class字节码文件通过类加载器装载入内存中 验证主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误 准备主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值 解析:将常量池里面的符号引用替换为直接引用 举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。 初始化:对类变量就是static修饰的变量或语句执行类构造器初始化 Java中对象的存储位置 String aa = new String(); new创建的对象存储在堆内存中; aa这个局部变量存储在栈内存中; 2.Java中常量的存储位置 常量存放在常量池中,而常量池在堆内存中 3.Java中局部变量的存储位置 局部变量存放在栈内存中
4.Java在哪些地方会出现内存溢出,如何避免?如果内存溢出了如何优化?
1、堆溢出 既然堆是存放实例对象的,那我们无限创建实例对象。这样堆区迟早会满。 堆内存当tentired区空间不够时,JVM会在tentired区进行full GC; full GC后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”: 解决方案:提前设置堆区的内存 2.虚拟机栈和本地方法栈溢出,递归太深,然后虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常 3.方法区和运行时常量池溢出 方法区大小设置大一点 1、尽早释放无用对象的引用 2、使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域 3、尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收 4、避免在循环中创建对象 5、开启大型文件或从数据库一次拿了太多的数据很容易造成内存溢出,所以在这些地方要大概计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。 解决堆溢出可以增大java虚拟机中的Xms(初始堆)和Xmx(最大堆)参数大小 解决栈溢出可以调高-Xss(线程的堆栈大小)大小
8.GC机制和jvm数据区的线程安全问题
垃圾回收
数据结构
2.数据结构的理解,说了线性表、链表、栈、队列等
3.排序算法和原理,说了冒泡、交换、快速、堆(忘了原理)和归并
排序算法
冒泡排序
口述快排:
排序算法
冒泡排序
每次冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作。它是原地排序,是稳定的排序算法,时间复杂度o(n^2),空间复杂度o(1).
插入排序
首先,我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。 插入排序也包含两种操作,一种是元素的比较, 一种是元素的移动。当我们需要将一个数据a插入到已排序区间时,需要拿a与已排序区间的元素依次比较大小,找到合适的插入位置。找到插入点之后,我们还需要将插入点之后的元素顺序往后移动一位,这样才能腾出位置给元素a插入。 原地算法(空间复杂度o(1)),是稳定排序算法(后出现元素插入到前面出现元素的后面),时间复杂o(n^2)
选择排序
它的实现思路有点类似插入排序,也分已排序区间和未排序区间。
但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。不稳定的,每次找未排序元素最小的与前面元素交换位置破坏了稳定性。空间复杂度o(1),时间复杂o(n^2)
// 插入排序,a表示数组,n表示数组大小 public void insertionSort(int[] a, int n) { if (n <= 1) return; for (int i = 1; i < n; ++i) { int value = a[i]; int j = i - 1; // 查找插入的位置 for (; j >= 0; --j) { if (a[j] > value) { a[j+1] = a[j]; // 数据移动 } else { break; } } a[j+1] = value; // 插入数据 } }
归并排序
归并排序的核心思想还是蛮简单的。如果要排序一个数组, 我们先把数组从中间分成前后两
部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有
序了。它是稳定的排序算法,时间复杂度是O(nlogn),实现方式是递归的方式实现,合并操作需要临时空间,最大为长度大小,空间复杂度O(n)
递推公式: merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r)) 终止条件: p >= r 不用再继续分解
快速排序
快排的思想是这样的:如果要排序数组中下标从p到r之间的一组数据,我们选择p到r
之间的任意一个数据作为pivot (分区点)一般直接选最后一个元素作为分区点。
我们遍历p到r之间的数据,将小于pivot的放到左边,将大于pivot的放到右边,将
pivot放到中间。经过这一步骤之后, 数组p到r之间的数据就被分成了三个部分,前面p
到q-1之间都是小于pivot的,中间是pivot,后面的q+1到r之间是大于pivot的。然后用递归排序下标从p到q-1之间的数据和下标从
q+1到r之间的数据,直到区间缩小为1,就说明所有的数据都有序了。
递推公式: quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1… r) 终止条件: p >= r
2.vector, list
3.上面俩容器的查询复杂度及原因。(vector地址连续,lis地址空间不连续,我说list的时候扯到跳表了。。)
vector可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作。拥有一段连续的内存空间,因此它能非常好的支持随即存取,即[]操作符 list就是数据结构中的双向链表,内存空间可以是不连续的。每个节点包括三个信息:元素本身,指向前一个元素的节点(prev)和指向下一个元素的节点(next)。因此list可以高效率的对数据元素任意位置进行访问和插入删除等操作。 查询指定元素 vector o(1) list o(n) 删除指定位置元素 vector o(n) list o(n) 删除给定指针指向的节点 单链表 o(n)(要找到前驱节点) list o(n)
9.二叉树的bfs和dfs的区别,分别用什么数据结构储存
//BFS: 用队列存储节点 // 给定跟节点 求出BFS遍历二叉树的锅。由于队列是先进先出的顺序,因此可以先将左子树入队,然后再将右子树入队 public List Bfs_tree(TreeNode root){ Queue myq = new LinkedList(); List res = new ArrayList(); if(root==null) return null; myq.add(root); while(!myq.isEmpty()){ int len = myq.size(); for(int i=0;i<len;i++){ if(myq.peek().left!=null) myq.add(myq.peek().left); if(myq.peek().right!=null) myq.add(myq.peek().right); res.add(myq.poll()); } } return res; } //DFS: 用栈存取节点 //Dfs遍历二叉树 先压栈右子树 然后左子树 public List Dfs_tree(TreeNode root){ Stack sta = new Stack(); List res = new ArrayList(); if(root==null) return null; // res.add(root); sta.add(root); while(!sta.isEmpty()){ TreeNode temp = sta.pop(); res.add(temp); if(temp.right!=null) sta.push(temp.right); if(temp.left!=null) sta.push(temp.left); } return res; }
计算机网络
4.说说计算机网络,每层干嘛,有什么协议,说了30分钟,从物理层到TCP到防火墙、入侵检测,HTTP和HTTPs嗓子背干了
ip协议的作用主要是在相互连接的网络之间传递IP数据报。可将IP数据报从源设备(例如用户的计算机)传送到目的设备(例如某部门的www服务器)
3、说一下输入网址以后经历的事儿,越详细越好(答了DNS递归查找,三次握手,为啥要有三次,arp和rapr协议)
https://segmentfault.com/a/1190000006879700
1. DNS解析 为了获取域名对应IP
DNS查找过程:浏览器缓存,路由器缓存、DNS缓存。然后首先在本地域名服务器中查询IP地址,如果没有找到的情况下,本地域名服务器会向根域名服务器发送一个请求,如果根域名服务器也不存在该域名时,本地域名会向com顶级域名服务器发送一个请求,依次类推下去。直到最后本地域名服务器得到google的IP地址并把它缓存到本地。
2. TCP连接
三次握手 1.SYN 2.SYN/ACK 3.ACK
3. 发送HTTP请求
发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议中发送到服务器指定端口(HTTP协议80/8080, HTTPS协议443)。HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文。
请求行:可以设置GET或者POST方法
请求报头:传给服务器的附加信息,常见的有Content-Type(设置请求数据格式如json)、Cookie、Keep-alive(http1.1保持tcp连接)等
请求正文:客户端要传给服务器的数据
4. 服务器处理请求并返回HTTP报文
接收到TCP报文后Web服务器像Tomcat对数据进行处理封装成HTTP Request对象。然后返回HTTP响应报文。
HTTP响应报文也是由三部分组成: 状态码, 响应报头和响应报文。
状态码如常见的访问失败时出现的404。
响应报头如Server、Connection
响应报文:服务器返回给浏览器的文本信息如HTML、CSS、JS、图片等信息。
5. 浏览器解析渲染页面
浏览器在收到HTML,CSS,JS文件后,首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上
6. 连接结束
设计模式(专栏中41,42,44,46,48,50,)
常见的设计模式有(单例 工厂 建造者 代理 观察者 适配器 装饰)记得准备一下)非面经
设计模式
Java的I/O使用到了什么设计模式(有装饰者模式,FilterInputStream聚合了InputStream)
装饰者模式的好处,为什么
装饰者模式:
装饰器模式它主要的作用是给原始类添加增强功能,主要解决用继承方式实现会导致继承关系过于复杂的问题,通过组合来替代继承。装饰器模式可以对原始类嵌套使用多个装饰器,这种情况,装饰器类需要跟原始类继承相同的抽象类或者接口,如BufferedInputSream(支持缓存读取)和DataInputStream(支持基本数据类型读取)继承于FilterInputStream,FilterInputStream为装饰器父类。
Java IO中就用到了装饰者模式,BufferedInputSream(支持缓存读取)和DataInputStream(支持基本数据类型读取)继承于FilterInputStream,FilterInputStream为装饰器父类。FilterInputStream继承于InputStream,对InputStream中方法进行重新实现。如果BufferedInputSream和DataInputStream直接继承InputStream需要对它的有默认实现的方法进行重新实现,为了避免代码重复就抽象出了FilterInputStream
代理类附加和原始类无关的功能,装饰器类附加原始类相关的增强功能。
12、说一下用过的设计模式
设计模式(手写饿汉懒汉单例模式)
单例模式:
是一种创建型设计模式。他的定义为:一个类只能创建一个实例,并提供一个访问它的全局访问点。
//饿汉式,类加载时就实例化一个对象,能快速创一个单例对象,而且线程安全(只有类加载时初始化),缺点是不支持延迟加载,不管要不要都会直接创一个对象 public class Id { private static final Id instance = new Id(); private Id() {} public static Id getInstance() { return instance; } } //懒汉式单例,只有在调用getInstance时才会实例化一个单例对象,优点是支持延迟加载,缺点是调用方***频繁加锁释放锁,并发度比较低 public class Id { public static Id instance; private Id(){} public static synchronized Id getInstance(){ if(instance==null){ //step 1. instance = new Id(); //step 2 } return instance; } } //双重检测,支持延迟加载和高并发。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中 public class Id { private static volatile Id instance; private Id() {} public static Id getInstance() { if (instance == null) { synchronized(Id.class) { // 此处为类级别的锁 if (instance == null) { instance = new IdG(); } } } return instance; } }
作用:1.处理资源访问冲突2.系统的配置信息类,系统中只用存一次的数据
单例模式一般体现在类声明中,单例的类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
反射和序列化破坏单例
观察者模式
它定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新
MySQL
5、mysql隔离级别(答了RC、RR、序列化,讲了一下innodb下RR级别咋解决幻读问题)
RR解决幻读 间隙锁:对数据进行条件,范围检索时,对其范围内也许并存在的值进行加锁 针对当前读,RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象
8.问你最后一个left join、right join和自然连接的区别
Inner join 内连接,在两张表进行连接查询时,只保留两张表中完全匹配的结果集 left join 在两张表进行连接查询时,会返回左表所有的行,即使在右表中没有匹配的记录。 right join 在两张表进行连接查询时,会返回右表所有的行,即使在左表中没有匹配的记录。 自然连接,针对相同表的 mysql会根据多个表内的相同字段作为连接条件 条件: 1)两张连接的表中列名称和类型完全一致的列作为条件 2)多个列名相同时,列的值也要同时相同才会连接出查询数据
13、mysql索引,再谈了谈高并发下B+树的优越性
mysql的查询,怎么判断有没有使用到索引(explain)
select前面加上explain
索引作用,失效的场景
索引用来在大量数据中快速定位到我们想要查找的数据 1.like 以%开头,索引无效 2.or语句前后没有同时使用索引 3.单独引用复合索引里非第一位置的索引列 4.数据类型出现隐式转化。如varchar不加单引号的话可能会自动转换为int型 5.当全表扫描速度比索引速度快时,mysql会使用全表扫描,此时索引失效
Having作用
来过滤由GROUP BY语句返回的记录集(对分组后结果过滤,可以使用聚合函数如(max、min)) SELECT id, COUNT(course) as numcourse, AVG(score) as avgscore FROM student GROUP BY id HAVING AVG(score)>=80;
Redis
Redis缓存失效的场景
4、redis过期策略(不会)
Redis:它是缓存数据库,数据存在内存中,读写速度非常快(高性能),高并发 。支持事务、持久化。
常用数据结构有:String、Hash、List、Set、Sorted Set
过期策略:
即对存储在 redis 数据库中的值可以设置一个过期时间
1.定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除
2.惰性删除:定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉
如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? redis 内存淘汰机制
缓存失效场景:
缓存雪崩:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方法:事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
缓存穿透:说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
解决方法:使用布隆过滤器,通过它我们可以非常方便地判断一个给定数据是否存在与海量数据中。把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,我会先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程
Spring
Spring IoC是什么,优点
AOP的理解(展示一下自己写的AOP代码)
7.说说IOC和AOP吧,静态代理和动态代理
5、Spring机制bean对象怎么应用于业务
4、开始问springMVC注解使用
6、控制器层注解方式有哪些
会用Springboot吗?
spring
@Controller
返回一个页面.
@RestController
返回 JSON 或 XML 形式数据:返回对象,对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)
IOC
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
控制 :指的是对象创建(实例化、管理)的权力
反转 :控制权交给外部环境(Spring 框架、IoC 容器)
IOC是实现方式优点:
1. 对象之间的耦合度或者说依赖程度降低;
2. 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。
可以通过XML方式或者注解来配置bean
Spring设计容器方式?
使用 ApplicationContext
,它是 BeanFactory
的子类,更好的补充并实现了 BeanFactory
的。
BeanFactory
简单粗暴,可以理解为 HashMap:
- Key - bean name
- Value - bean object
ClassPathXmlApplicationContext
- 从 class path 中加载配置文件,更常用一些;
然后ApplicationContext的对象可以调用getBean方法获取对象
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
代理分为静态代理和动态代理,静态代理,顾名思义,就是你自己写代理对象,动态代理,则是在运行期,生成一个代理对象。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy[`proksi],去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理。
AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
切 :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑
面 :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念
@pointcut切点注解 可以设置拦截方式
消息队列
消息队列的使用场景 https://blog.csdn.net/fygu18/article/details/80863596
1.异步处理
如用户注册后,需要发注册邮件和注册短信。传统的做法有两种:串行的方式和并行方式
注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20QPS。比串行提高了3倍,比并行提高了两倍!
2.应用解耦
场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。
传统模式的缺点:
假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合。
引入消息队列后
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作
假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。
3.流量削锋
应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。
可以控制活动的人数,可以缓解短时间内高流量压垮应用。
4.日志处理
是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题
5.消息通讯
消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。
比如说交易系统下订单后可以通过Kafka去通知其他的系统如广告系统、推荐系统等
Kafka采用发布订阅模型就是设计模式中的观察者模型
发布订阅模型(Pub-Sub) 使用主题(Topic) 作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。
算法题
解释一下4.22笔试的第一题
题目:
1.求链表的中间结点
public node getMidNode(node head){ node fast=head,slow=head; while(fast&&fast.next){ fast=fast.next.next; slow=slow.next; } return slow; }
链表探环 - 我一开始说用HashSet判断面试官居然没听懂,还问我判断的是值还是引用……我还解释了一下contains方法的原理是用hashcode + equals方法,面试官一阵沉默,我只好继续说了快慢指针法
set s=new HashSet(); contains add public class Solution { public boolean hasCycle(ListNode head) { //解法1 Set s=new HashSet(); while(head!=null){ if(s.contains(head)){ return true; } s.add(head); head=head.next; } return false; //解法2 ListNode fast=head; ListNode slow=head; while(fast!=null){ if(fast.next==null)return false; slow=slow.next; fast=fast.next.next; if(slow==fast){ return true; } } return false; } }
链表反转
class Solution { public ListNode reverseList(ListNode head) { if(head==null)return null; ListNode a=head; ListNode b=head.next; while(b!=null){ ListNode c=b.next; b.next=a; a=b; b=c; } head.next=null; return a; } }
二叉有序树合并
合并二叉树lc617
//思路:我们可以对这两棵树同时进行前序遍历,并将对应的节点进行合并。在遍历时,如果两棵树的当前节点均不为空,我们就将它们的值进行相加,并对它们的左孩子和右孩子进行递归合并;如果其中有一棵树为空,那么我们返回另一颗树作为结果;如果两棵树均为空,此时返回任意一棵树均可(因为都是空)。 //https://leetcode-cn.com/problems/merge-two-binary-trees/solution/he-bing-er-cha-shu-by-leetcode/ class Solution { public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { if(t1==null)return t2; if(t2==null)return t1; t1.val+=t2.val; t1.left=mergeTrees(t1.left,t2.left); t1.right=mergeTrees(t1.right,t2.right); return t1; } }
笔经
5.13笔试
四道编程题,纯白板 20 20 30 30
第一道是
5个人分金币,好像不是贪心
每个人要把金币分成五堆,如果不能分成五堆,自己可以添加金币,最后每个人都添加了一个金币,然后还剩1000~2000金币,求每个人分到了多少金币
第二题是
给定一颗二叉树和数值。
求二叉树中的路径和等于给定值的所有路径。
第三题跟蓝桥杯3月校内赛第8题类似
【问题描述】
有一块空地,将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。
选了其中的一些小块空地,灌溉,其他小块仍然保持是空地。
每个月,已经灌溉了的地会向自己的上、下、左、右四小块空地扩展,这四小块空地都将变为1。
问k月后有多少地没被灌溉
【输入格式】
输入的第一行包含两个整数 n, m。
接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 1,表示被灌溉。
接下来包含一个整数 k。
#include #include #include #define loop(i,x,y) for(int i=x;i<=y;i++) using namespace std; struct block{ int x; int y; int month; }; const int dx[]={1,0,-1,0}; const int dy[]={0,1,0,-1}; int map[1001][1001]; int main() { ios::sync_with_stdio(false);//加快读取 cin.tie(0); cout.tie(0); int n,m,k,count=0; char s; queue q;//队列中存放可扩展的已灌溉小块 memset(map,0,sizeof(map));//初始化数组 cin>>n>>m; loop(i,0,n-1){ loop(j,0,m-1){ cin>>s; if(s=='1'){ q.push({i,j,0});//进队 map[i][j]=1; //标记进队 } } } cin>>k; while(!q.empty()){ block b=q.front();//取队首,向四周灌溉 int month=b.month; if(month<k){ loop(i,0,3){//循环做出四个新坐标 int n_x=b.x+dx[i]; int n_y=b.y+dy[i]; if(n_x>=0&&n_x=0&&n_y<m&&map[n_x][n_y]==0){//新坐标在范围内且未被访问 map[n_x][n_y]=1; q.push({n_x,n_y,month+1}); } } } q.pop();//出队首,队首向四周灌溉完毕 } loop(i,0,n-1){ loop(j,0,m-1){ if(map[i][j]==0) count++; } } cout<<count; return 0; }
其中第四道是leetcode原题 312,不过没时间了就没写了,看题解是可以用动态规划做。
class Solution { public int maxCoins(int[] nums) { // reframe the problem int n = nums.length + 2; int[] new_nums = new int[n]; for(int i = 0; i < nums.length; i++){ new_nums[i+1] = nums[i]; } new_nums[0] = new_nums[n - 1] = 1; // cache the results of dp int[][] memo = new int[n][n]; // find the maximum number of coins obtained from adding all balloons from (0, len(nums) - 1) return dp(memo, new_nums, 0, n - 1); } public int dp(int[][] memo, int[] nums, int left, int right) { // no more balloons can be added if (left + 1 == right) return 0; // we've already seen this, return from cache if (memo[left][right] > 0) return memo[left][right]; // add each balloon on the interval and return the maximum score int ans = 0; for (int i = left + 1; i < right; ++i) ans = Math.max(ans, nums[left] * nums[i] * nums[right] + dp(memo, nums, left, i) + dp(memo, nums, i, right)); // add to the cache memo[left][right] = ans; return ans; } }
4.22的笔试题目
题目:
1.求链表的中间结点
public node getMidNode(node head){ node fast=head,slow=head; while(fast&&fast.next){ fast=fast.next.next; slow=slow.next; } return slow; }
输入:n = 3 输出:[ "((()))", "(()())", "(())()", "()(())", "()()()" ]
3.股票问题
输入近几天的股票值,比如:{23,24,25,21,12,22,31,23}
输出至少再过几天股票会增值,如果之后股票一直没有增值,则输出0
例如,对第一天(股票值为23),再过一天就会增值,输出1。
对倒数第二天(股票值为31),再不会有股票增值的情况,输出0.
因此,对于例题的输出应为{1,1,4,2,1,1,0,0}
4.宾馆房间问题
一个宾馆8个房间,输入房间的初始状态,房间的占用状态用1,0表示。
1代表房间被占用,0代表房间空置。
对于以后的每一天房间的占用规则如下:
如果该房间的相邻房间前一天都被占用或空置,则该房间今天被占用。
否则该房间今天空置。
输入这8个房间的初始状态:
{0,1,0,1,1,0,0,1}
输出7天后房间的占用情况:
{0,0,1,1,0,0,0,0}
注意:对于最左和最后两个房间,他们只有一个邻居,这里去让他们空置。
解释:这七天内房间的占用情况:
第0天:[0, 1, 0, 1, 1, 0, 0, 1]
第1天:[0, 1, 1, 0, 0, 0, 0, 0]
第2天:[0, 0, 0, 0, 1, 1, 1, 0]
第3天:[0, 1, 1, 0, 0, 1, 0, 0]
第4天:[0, 0, 0, 0, 0, 1, 0, 0]
第5天:[0, 1, 1, 1, 0, 1, 0, 0]
第6天:[0, 0, 1, 0, 1, 1, 0, 0]
第7天:[0, 0, 1, 1, 0, 0, 0, 0]
面经
3、说一下输入网址以后经历的事儿,越详细越好(答了DNS递归查找,三次握手,为啥要有三次,arp和rapr协议)
4、redis过期策略(不会)
5、mysql隔离级别(答了RC、RR、序列化,讲了一下innodb下RR级别咋解决幻读问题)
6、接口和抽象类(背了面经)
7、JVM分区、局部变量在哪、类加载过程(局部变量我说在堆,因为对象都在堆里,面试官说不对)局部变量在栈里面
8、string和stringbuilder和stringbuffer那些(final类型,常量池啥的)
5.19
口述快排:
二叉有序树合并:()
String 和StringBuilder 和StringBuffer 的区别:
HashMap:
5.19
1.自我介绍、学校的项目、专利
2.数据结构的理解,说了线性表、链表、栈、队列等
3.排序算法和原理,说了冒泡、交换、快速、堆(忘了原理)和归并
4.说说计算机网络,每层干嘛,有什么协议,说了30分钟,从物理层到TCP到防火墙、入侵检测,HTTP和HTTPs嗓子背干了
5.很详细,那说说死锁,背完嗓子都说不了话了
6.很详细,那说说Java的线程创建方式和生命周期
7.说说IOC和AOP吧,静态代理和动态代理
8.问你最后一个left join、right join和自然连接的区别
5.22
自我介绍
你对Java的理解(面向对象,jvm,语言特性,GC,类库)
说出5个常用的类
垃圾回收
static关键字的作用,修饰在变量上,修饰在方法上有什么作用
会用Springboot吗?
设计模式(手写饿汉懒汉单例模式)
AOP的理解(展示一下自己写的AOP代码)
Redis缓存失效的场景
索引作用,失效的场景
冒泡排序
override和overload
fianally在return之前还是之后(return语句已经执行了再去执行finally语句,不过并没有直接返回,而是等finally语句执行完了再返回结果)
5.21一面
(时长30min)
1、自我介绍:基本信息学历,说明了做java后端等 2、登录改密项目:看了简历后,问了session对象的存储和项目功能啥😓 3、飞机躲避炸弹游戏:问了该游戏的多线程应用场景 3、谈了自己的SSM maven聚合项目作品 4、开始问springMVC注解使用 5、Spring机制bean对象怎么应用于业务 6、控制器层注解方式有哪些 7、JVM内存模型还有java跨平台 8、GC机制和jvm数据区的线程安全问题 9、分布式锁实现高可用和可重入 10、锁的一些分类和使用,引入红锁 **11**、谈了红锁场景、如何判断资源已修改,失效啥的(说的不好,脑子有点乱)😌 12、说一下用过的设计模式 13、mysql索引,再谈了谈高并发下B+树的优越性
5.22java一面
全程25min
1. 自我介绍
2. 链表探环 - 我一开始说用HashSet判断面试官居然没听懂,还问我判断的是值还是引用……我还解释了一下contains方法的原理是用hashcode + equals方法,面试官一阵沉默,我只好继续说了快慢指针法
3. Java内存区域划分
4. Java在哪些地方会出现内存溢出,如何避免?如果内存溢出了如何优化?
5. Java的线程的生命周期
6. 什么时候线程永远没法使用cpu?
7. 如何避免死锁发生?
8. 链表反转
9. 二叉树的bfs和dfs的区别,分别用什么数据结构储存
广联达
问的挺全。从集合,设计模式,框架,io,算法,排序算法,线程。都问了但挺基础,不难。
5.22一面
面试三十分钟,都是些基础问题,面试官时间很紧,有些点都不让细说就换下一个问题,也是醉了。。。
问题大概如下:
- 解释一下笔试的第一题
- 集合类Collection都有哪些,使用场景
- Java锁机制有哪些
- Java的I/O使用到了什么设计模式(有装饰者模式,FilterInputStream聚合了InputStream)
- 装饰者模式的好处,为什么
- Spring IoC是什么,优点
- mysql的查询,怎么判断有没有使用到索引(explain)
- having作用
- 消息队列的使用场景
5.22
1.服务端如何处理多客户端
2.vector, list
3.上面俩容器的查询复杂度及原因。(vector地址连续,lis地址空间不连续,我说list的时候扯到跳表了。。)
4.多态,能说多少说多少。
根据面经按知识点整理的内容,然后最后是4.23和5.13的笔试题目
#面经##广联达##Java工程师##实习#