校招笔记(一)_Java_Java入门
我的校招记录:校招笔记(零)_写在前面 ,以下是校招笔记总目录。
备注 | ||
---|---|---|
算法能力(“刷题”) | 这部分就是耗时间多练习,Leetcode-Top100 是很好的选择。 | 补充练习:codeTop |
计算机基础(上)(“八股”) | 校招笔记(一)__Java_Java入门 | C++后端后续更新 |
校招笔记(一)__Java_面对对象 | ||
校招笔记(一)__Java_集合 | ||
校招笔记(一)__Java_多线程 | ||
校招笔记(一)__Java_锁 | ||
校招笔记(一)__Java_JVM | ||
计算机基础(下)(“八股”) | 校招笔记(二)__计算机基础_Linux&Git | |
校招笔记(三)__计算机基础_计算机网络 | ||
校招笔记(四)__计算机基础_操作系统 | ||
校招笔记(五)__计算机基础_MySQL | ||
校招笔记(六)__计算机基础_Redis | ||
校招笔记(七)__计算机基础_数据结构 | ||
校招笔记(八)__计算机基础_场景&智力题 | ||
校招笔记(九)__计算机基础_相关补充 | ||
项目&实习 | 主要是怎么准备项目,后续更新 |
1.1 JAVA入门
1.1.1 JAVA基本
1.介绍一下JVM&JRE&JDK? JAVA语言有什么特点?
- JVM&JRE&JDK
- JVM: 即java虚拟机,针对不同操作系统,JVM把Java代码翻译成对应操作系统可以识别的内容,实现跨平台 ;
- JRE : JVM + 核心类库 = JRE , 即Java运行时环境。只有JVM不能运行,它还需要核心类库,才能保证Java运行 ;
- JDK: JRE + java开发工具(编译器等) = JDK ,Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具。
- Java语言特点
-
简单易学;
-
面向对象(封装,继承,多态);
-
平台无关性( Java 虚拟机实现平台无关性);
-
可靠性;
-
安全性;
-
支持多线程( C++ 语⾔没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语⾔却提供了多线程支持);
-
2.什么是Java虚拟机?为什么Java被称为平台无关的编程语言?
-
java虚拟机,是执行字节码文件(.class)的虚拟机进程;
在 Java 中,JVM 可以理解的代码就叫做 字节码 (即扩展名为 .class 的⽂件),它不面向任 何特定的处理器,只面向虚拟机。
-
java源程序(.java)被编译器编译成字节码文件(.class)。然后字节码文件,将由java虚拟机,解释成机器码(不同平台的机器码不同)。
3. 请你谈谈Java中是如何支持正则表达式操作的?(补充实例)
Java中的String类提供了支持正则表达式操作的方法,包括:
matches()、replaceAll()、replaceFirst()、split()
此外,Java中可以用Pattern类表示正则表达式对象,它提供了丰富的API进行各种正则表达式操作,如:
1 | import java.util.regex.Matcher; |
实例示范(PCG问过)
-
特殊字符
-
普通字符
-
实例示范
-
匹配邮箱
-
匹配电话号码
1
2
3
4//匹配电话号码
String phone = "18637866964";
String reg = "^1[3,5,7,8,9]\\d{9}$";
System.out.println(phone.matches(reg)); -
匹配第一个出现的数字
下面好像是不对的。
1
2
3String phone = "avss1sdp22";
String reg = "\d?";
System.out.println(phone.matches(reg));
-
4.(补充例子)请你简单描述一下正则表达式及其用途。
在编写处理字符串的程序时,经常会有查找 符合某些复杂规则的字符串 的需要。
- 计算机处理的信息更多的时候不是数值而是字符串,正则表达式就是在进行字符串匹配和处理的时候最为强大的工具;
- 绝大多数语言都提供了对正则表达式的支持。
5.&和&&区分?
- 共同点:都要求运算符左右两端的布尔值 都是true 整个表达式的值才是true
- 区别:&&之称为短路运算,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。 好处:
- e.g. :右边判别式有如果有空指针
NullPointerException
异常判断风险,可以避免。
- e.g. :右边判别式有如果有空指针
6.值传递和引用传递区分?
-
值传递是该变量的一个副本, 改变副本不影响原变量;
-
引用传递是对象地址的副本,引用对象进行操作会同时改变原对象。
7.十进制与二进制?
-
请你讲讲一个十进制的数在内存中是怎么存的?
补码形式。
-
为什么会出现4.0-3.6=0.40000001这种现象?
2进制的小数无法精确的表达10进制小数,计算机在 计算10进制小数的过程中要先转换为2进制进行计算 ,这个过程中出现了误差。
8.(重要)equals与==的区别
很清晰严谨的一篇文章:https://www.cnblogs.com/skywang12345/p/3324958.html
-
==
- 基本类型:比较的是值是否相同;
- 引用类型:比较的是引用(对象地址)是否相同;
-
equals
要看类是否覆盖equals()方法,将它分为两种情况:
-
若某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,实际上是比较两个对象(地址)是不是同一个对象。这时,等价于通过“==”去比较这两个对象;
-
我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象的内容(而不是地址)是否相等。
String 中的 equals 方法是被重写过的:
- 因为 object 的 equals 方法是⽐教的对象的内存地址
- 而 String 的 equals 方法(1)先比较对象地址是否相等 ,相同则ture,否则(2)再比较值是否相等
-
7.请解释hashCode()和equals()方法有什么联系?
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode方法?”
-
hashCode()介绍
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该 对象在哈希表中的索引位置。
我们仅在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。 其它情况下hashCode() 则根本没有任何作用,所以,不用理会hashCode()。
- 在这种情况下对象相等,hashcode值也会不相等。
-
为什么要有hashcode()
hashCode() 的作用就是获取哈希码,也称为散列码;它实际上是返回⼀个 int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。 hashCode()在散列表中才有用,在其它情况下没用。在散列表中 hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
先判断hashcode,而不是直接遍历O(n)复杂度用equals()判断,减少判断时间。
如果hashcode一样,会调用equals()去比较。
- HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals() 方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加⼊操作成功。如果不同的话,就会重新散列到其他位置。
7.1 为什么重写了equals()一定要重写hashcode()方法?
在Hashmap / Hashset中,通过计算hash = hash(key.hashcode) 然后进行取余操作,快速定位到数组中。
因为map中是不允许重复key的,所以对内部get()/add()方法:对于散列到数组同一位置的对象来说,如果hash相等 && equals()判断相等 ,是要进行覆盖的。
1 | //调用 equals 方法判断key是否相等,若相等,该key对应的键值对已经存在,用新的value取代旧的value |
如果我们只重写了equals方法:用来判断两个对象是否相等。但是依旧可能出现:两个相同对象equals相等,但hashcode不等,被散列到不同桶上,map中依旧出现了重复键值对!
所以,需要重写hashcode方法,保证相同对象一定是散列到同一个位置(具有同样的hash值)。
7.2 两个对象值相同(x.equals(y) == true),但却可有不同的hash code,该说法是否正确,为什么?
不一定正确,如果在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。
其它情况下可能会出现题目描述的情况。
8. 自动拆箱和装箱?
8.1 介绍一下int&Integer?
Java为了编程的方便还是引入了基本数据类型,但是 为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class)
-
int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换:
1
2
3
4
5
6
7
8
9
10
11// e.g.
public static void main(String[] args)
{
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
// false 两个引用没有引用同一对象
System.out.println(a == b);
// true a自动拆箱成int类型再和c比较
System.out.println(a == c);
} -
Java 为每个原始类型提供了包装类型:
- (8种基本类型)原始类型: boolean,char,byte,short,int,long,float,double
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
8.2 拆箱、装箱存在的意义?
为什么要有装箱、拆箱,它们的作用是什么?
java 是 面对对象编程,而基本数据类型不是对象,所有才有封装类 引用基本数据类型进行操作。比如,下面打印出int型数据:
1 | int i = 1; |
其实,查看源码,实际经过以下几个过程:
- 将 i 自动装箱成封装类 Integer
- 然后调用 Integer中 toString() 方法,打印出字符串输出到控制台。
自动装箱和拆箱?
- 自动装箱:就是自动将基本数据类型转换为包装器类型
- 自动拆箱:就是自动将包装器类型转换为基本数据类型
8.3 char和byte的区别 , 能否强制转换?
区别:
- Char是无符号型的,可以表示一个整数,不能表示负数,大小范围 是0—65535;而byte是有符号型的,可以表示-128—127 的数
- char可以表中英文字符,byte不可以
强制转换:
可以,但是会出现精度丢失。
9. String & StringBuffer& StringBuilder 区别?为什么String不可变?
-
请解释String & StringBuffer区别?
-
共同点:它们可以储存和操作字符串,即包含多个字符的字符数据;
-
可否修改:String类提供了数值不可改变的字符,StringBuffer可以修改字符串,需要 字符数据要改变 时用。
典型地,你可以使用StringBuffers来动态构造字符数据。
-
-
请解释 StringBuilder& StringBuffer 区别?
-
共同点:
AbstractStringBuilder
是 StringBuilder 与 StringBuffer 的公共⽗类。都可以修改字符串,操作字符串方法丰富。1
2
3
4
5
6
7
8
9
10
11
12
13abstract class AbstractStringBuilder implements Appendable, CharSequence
{
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
AbstractStringBuilder(int capacity) {
value = new char[capacity];
} -
线程安全:单线程且操作大量字符串用StringBuilder,速度快,但线程不安全,可修改;在多线程且操作大量字符串用StringBuffer,线程安全,可修改。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
-
-
为什么String 不可变?
String 类中使用 final 关键字修饰字符数组来保存字符串, 所以 String 对象是不可变的。
1
private final char value[]
10.说说深拷贝和浅拷贝?
-
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址;
因此,可能会出现出现浅拷贝时释放同一个内存的错误。
-
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。
11. 【新增】介绍一下JDK1.8的新特性?
JDK1.8新增了非常多的特性,如:
- Lambda表达式:Lambda允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
- 方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法:默认方法就是一个在接口里面有了一个实现的方法。
- 新工具:新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API:加强对日期与时间的处理。
- Optional类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn,JavaScript引擎:JDK1.8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
12. 【新增】java一个程序能不能有多个main方法?一个类里呢?
-
一个程序里,多个class都有main方法
可以,默认第一个为入口,其余为普通函数。
1
2
3
4
5
6
7
8class Class1 {
public static void main(String[] args) {
}
}
public class Class2 {
public static void main(String[] args) {
}
} -
一个类有多个main方法
可以,其余就相当是重载。但是具有以下sigature(签名)的主要方法将被视为app入口点:
1
public static void main(String[] args)
1.1.2 关键字
1.请你讲讲Java里面的final关键字是怎么用的?
-
修饰类:表示不能被继承,final类 成员变量 可以设为final;但final类所有方法 ,都被隐式指定为final方法;
-
修饰方法:防任何继承类修改它的含义 ; 在早期的Java实现版本中,会将final方法转为内嵌调用,效率会更高;
-
修饰变量、引用:基本类型的话一旦初始化不能修改;引用类型,不能指定其他对象 。
2. 【重点】请你谈谈关于Synchronized和lock ?
-
Synchronized:是一个关键字,修饰类、方法 或 代码块 ,保证在同一时刻最多只有一个线程执行该段代码;
作用范围:
- 修饰一个类/静态方法,作用的对象是这个类的所有对象。
- 修饰一个方法/代码块,作用的对象是调用这个方法/代码块的对象。
-
Lock:是一个接口,Lock能完成synchronized所实现的所有功能。
Lock接口是不能直接实例化的,需要靠它的实现类ReentrantLock来进行实例化。
-
区别:
- 锁释放:synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;Lock不会主动适应
unLock()
释放,必须手动在finally
释放。; - 线程等待: Lock可以让等待锁的线程可以响应中断,线程可以中断去干别的事务;而synchronized却不行,使用synchronized时,等待的线程会一直等待下去;
- 成功获取锁: 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- 锁释放:synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;Lock不会主动适应
-
3. instanceof关键字的作用?
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例。
1 | int i = 0; |
1 | Integer integer = new Integer(1); |
4. final有哪些用法?
-
被final修饰的类不可以被继承 ;
-
被final修饰的方法不可以被重写,而且JVM会尝试将其内联,以提高运行效率;
-
被final修饰的变量不可以被改变;
-
被final修饰的引用,那么表示引用不可变,引用指向的内容可变;
-
被final修饰的常量,在编译阶段会存入常量池中。
-
5. static都有哪些用法 ?
-
修饰静态变量和静态方法 :都属于类的静态资源,类实例所共享 ;
-
修饰静态块:用于初始化操作。
1
2
3
4
5public calss PreCache{
static{
//执行相关操作
}
} -
修饰静态包:在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用方法名。
1
2
3
4
5
6
7
8
9
10import static java.lang.Math.*;
public class Test
{
public static void main(String[] args)
{
//System.out.println(Math.sin(20));传统做法
System.out.println(sin(20));
}
}
6. 谈一谈transient关键字?
参考 : Java中的关键字 transient
-
Java中序列化操作
Java中对象的序列化指的是将对象转换成以【字节序列】的形式来表示,这些字节序列包含了对象的数据和信息。
当然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象,要不然序列化后干嘛呢,所以序列化后的字节序列都是可以恢复成Java对象的,这个过程就是反序列化。
- 一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输,一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化。
-
关于transient关键字
Java中transient关键字的作用,向虚拟机表明:
transient
变量不是对象的持久状态的一部分。简单地说,就是让某些被修饰的成员属性变量不被序列化,例如:
-
类中的字段值可以根据其它字段推导出来,如一个长方形类有三个属性:长度、宽度、面积(示例而已,一般不会这样设计),那么在序列化的时候,面积这个属性就没必要被序列化了;
-
其它,看具体业务需求吧,哪些字段不想被序列化;
-
7.1 HashMap中源码modCount为什么用tranisent修饰?
modCount主要用于判断HashMap是否被修改(像put、remove操作的时候,modCount都会自增)。
对于这种变量,一开始可以为任何值,0当然也是可以(new出来、反序列化出来、或者克隆clone出来的时候都是为0的),没必要持久化其值。
1.1.3 Java异常
1. Java常见异常和分类?
常见分为两类,Error和Exception :
- Error :指程序无法恢复的异常情况,对于其所有类型,都不要求程序处理。
- 常见错误:Stackoverflow,outOfMemory
- Exception: 程序有可能恢复的错误,又分为IOException & RuntimeException ,常见错误:
- IOException:FileNotFoundExcepetion
- RuntimeException : 空指针,参数不合法,类未找到等
2. OOM产生原因和分析?
OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError 。
-
java.lang.OutOfMemoryError: Java heap space (堆溢出)
-
产生原因
- 内存泄漏;
- 堆分配太小;
-
解决办法
- 内存泄漏要手动去释放内存,比如数据库连接池,单例模式
- 通过虚拟机参数
-Xms,-Xmx
等修改,对内存大小
-
-
java.lang.OutOfMemoryError: PermGen space (永久代(方法区)溢出)
-
产生原因
即方法区溢出了:
- 一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区 ;
- 过多的常量尤其是字符串也会导致方法区溢出。
-
解决办法
- 永久代的内存分配增大 :-XX:PermSize和-XX:MaxPermSize
-
-
java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。
线程栈相关的内存异常有两个:
- StackOverflowError(方法调用层次太深,内存不够新建栈帧)
- OutOfMemoryError(线程太多,内存不够新建线程)
-
java.lang.OutOfMemoryError: Metaspace
Java中普通I/O采用输入/输出流方式实现,输入流InputStream( 终端—>直接内存->JVM),输出流(JVM->直接内存->终端),这一过程中有kenel与JVM之间的拷贝(很多次)。
为了使用直接内存,Java是有一块区域叫DirectBuffer,不是JavaHeap而是cHeap的一部分。
但由于直接内存没有被java虚机完全托管,若使用不当,也容易触发溢出,导致宕机。
3. try catch finally,try里有return,finally还执行么?
执行,并且finally的执行早于try里面的return :
- 不管有木有出现异常,finally块中代码都会执行;
- 当try和catch中有return时,finally仍然会执行;
- finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的。
4.说说你是怎么处理异常的?
try-catch-finally
-
try 块负责监控可能出现异常的代码
-
catch 块负责捕获可能出现的异常,并进行处理
-
finally 块负责清理各种资源,不管是否出现异常都会执行
-
其中 try 块是必须的,catch 和 finally 至少存在一个标准异常处理流程
5. web网页卡怎么排查?cpu100%怎么排查?OOM怎么排查?
-
web网页卡顿
- 用户端:硬件配置低、资源不足;CPU 或者内存资源不足, 比如用户是否使用了 Chrome 这种 “吃内存大户” 的浏览器并且打开了很多网页?
- 网络分析:DNS 解析慢;未设置 CDN,如果没有设置 CDN, 在跨线路访问(比如用户是铁通, 但是服务器部署在联通, 这种情况就是跨线路), 地理位置相差很远 等情况 ;用户端的带宽不足或所处环境网络不佳;
- 服务端:服务端响应慢,性能比较差 。
-
cpu100%
-
执行
top
命令:查看所有进程占系统CPU的排序;极大可能排第一个的就是咱们的java进程(COMMAND列)。PID那一列就是进程号。
-
执行top -Hp 进程号命令:查看java进程下的所有线程占CPU的情况;
-
执行printf "%x\n 10命令 :后续查看线程堆栈信息展示的都是十六进制,为了找到咱们的线程堆栈信息,咱们需要把线程号转成16进制。
-
执行jstack 进程号 | grep 线程ID” 查找某进程下–>
线程ID(jstack堆栈信息中的nid)=0xa
的线程状态。代码中有大量消耗CPU的操作,导致CPU过高,系统运行缓慢:
-
jstack,可直接定位到代码行。例如某些复杂算法,甚至算法BUG,无限循环递归等等。
-
如果有死锁,会直接提示关键字:deadlock。步骤4,会打印出业务死锁的位置。
-
-
执行jstat -gcutil 进程号 统计间隔毫秒 统计次数(缺省代表一致统计)”,查看某进程GC持续变化情况,如果发现返回中FGC很大且一直增大–>确认Full GC!
也可以使用“jmap -heap 进程ID”查看一下进程的堆内从是不是要溢出了,特别是老年代内从使用情况一般是达到阈值(具体看垃圾回收器和启动时配置的阈值)就会进程Full GC。
jstat命令监控GC情况,可以看到Full GC次数非常多,并且次数在不断增加。
-
执行jmap -dump:format=b,file=filename 进程ID,导出某进程下内存heap输出到文件中。
-
-
OOM
先通过内存映像工具对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏还是内存溢出。
1
jmap -dump:format=b,file=$java_pid.hprof #java_pid为java进程ID
然后看具体是报什么错:很明显下面是堆溢出。
1
2
3
4
5
6
7
8
9java.lang.OutOfMemoryError: Java heap space
Dumping heap to oom.out ...
Heap dump file created [3196858 bytes in 0.016 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:700)
at java.lang.StringBuilder.append(StringBuilder.java:214)
at jvm.OomDemo.main(OomDemo.java:13)