java面试总结(初级研发工程师)

一、String、StringBuffer和StringBuilder类

1、Stirng:不可变(“aaa”+“bbb”时,会创建一个新的String对象,消耗资源)、实现了equals和hashCode方法(Object类中的equals是地址比较,String类中重写equal,是值比较)
2、StringBuilder:可变(通过.append进行拼接)、非线程安全
3、StringBuffer:可变(通过.append进行拼接)、线程安全的(方法中使用了synchronized同步锁关键字,与StringBuilder相比效率会变低。因此,单线程不要求线程安全且需要进行字符串拼接操作时,一般使用StringBuffer.append)

String对象真的不可变吗?
虽然value是final修饰的,只是说明value不能再重新指向其他的引用。但是value指向的数组可以改变,一般情况下我们是没有办法访问到这个value指向的数组的元素。But,反射,对,反射可以,牛逼吧。可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。
public static void main(String[] args) throws Exception {
String str = “Hello World”;
System.out.println(“修改前的str:” + str);
System.out.println(“修改前的str的内存地址” + System.identityHashCode(str));
// 获取String类中的value字段
Field valueField = String.class.getDeclaredField(“value”);
// 改变value属性的访问权限
valueField.setAccessible(true);
// 获取str对象上value属性的值
char[] value = (char[]) valueField.get(str);
// 改变value所引用的数组中的字符
value[3] = ‘?’;
System.out.println(“修改后的str:” + str);
System.out.println(“修改前的str的内存地址” + System.identityHashCode(str));
}

二、java中的集合及源码分析

List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口
List接口下常用的实现类有ArrayList,LinkedList,Vector:有序集合,数据是允许重复

Map接口下常用的实现类有HashMap,Hashtable,ConcurrentHashMap、LinkedHashMap,TreeMap

Set接口下常用的实现类有HashSet,LinkedHashSet,TreeSet

1.1、ArrayList:内部实现为数组,初始容量DEFAULT_CAPACITY为10
扩容机制:oldCapacity + (oldCapacity >> 1),相当于:原数组的长度+原数组的长度/2(1.5倍)
扩容的具体实现:重新创建一个容量为原容量1.5倍的数组,遍历原数组,将每一个元素添加到新数组中
特点:数组,非线程安全,查询快,插入删除慢
应用场景:不要求线程安全,且查询操作较多,插入删除操作较少
1.2、LinkedList:内部实现为链表(双向链表)
特点:非线程安全,非线性表,查询慢,插入删除快
应用场景:不要求线程安全,且查询操作较少,插入删除操作较多
1.3、Vector:与ArrayList类似,内部实现为数组,初始容量为10。与ArrayList的区别在于,Vector的方法中使用了synchronized关键字,是线程安全的,与ArrayList相比效率会变低
扩容机制:增长为原来的两倍
特点:线程安全
应用场景:要求线程安全

链表图解:
java面试总结(初级研发工程师)

2.1、HashMap:非线程安全,jdk1.8之前的内部实现为数组+链表,jdk1.8的内部实现为数组+链表+红黑树
初始容量为16,加载因子为0.75
HashMap中的四个构造方法,有两个构造方法经常被忽略,但很重要(单独分享HashMap的源码)
1、无参构造 HashMap()
2、HashMap(Map map)
3、参数为初始容量 HashMap(int initialCapacity)
4、参数为初始容量和加载因子 HashMap(int initcapacity,float loadFactor)
扩容机制:存储的内容达到容量的0.75倍时则进行扩容,扩大为原始容量的一倍
数组+链表的图解:
java面试总结(初级研发工程师)
HashMap的内部实现(重点):
Map为key、value的存储形式,key和value都可为null,但key不可重复,重复的key会覆盖。
hashCode的特点:两个不同的key之间hashCode可能相同(hash碰撞)、hashCode相同,值不一定相同、hashCode不同,值肯定不同
首先创建一个容量为16的数组,向map中put元素时,首先通过key.hashCode()方法,获取key的hash值,通过hash & (length-1)来确定该元素在数组中的下标(类似于取模运算,也就是通常所说的做除法取余数,获得的余数肯定小于数组长度,保证元素的位置不超出数组长度)

扩容的具体实现:重新创建一个容量为原容量1.5倍的数组,由于数组的长度发生变化,因此存在一个重新hash并resize的过程(也就是对所有元素在数组中的位置进行重新调整,原因:上方所说,在对其中一组key和value进行位置确定时,通过hash值和数组的长度运算获得,扩容时数组的长度发生变化,因此相对应的位置也会发生变化)。因此,每一次扩容都会重新遍历数组中的所有元素,重新hash并重新确定位置,是一个复杂的过程。当Map的容量确定时,一般需要设置map的初始容量,避免过多的resize操作,消耗资源。

2.2、Hashtable:与HashMap类似,最大的区别在于HashMap非线程安全,HashTabe为线程安全。且HashMap允许key或value为null,而HashTable不允许key或value为null(会报错)

2.3、ConcurrentHashMap:效率较高的线程安全的map

应用场景:不需要线程安全,选择HashMap,需要线程安全选择ConcurrentHashMap

3.1、HashSet:内部实现为HashMap,HashSet的add方法,直接调用HashMap的put方法
public boolean add(E e) {
return map.put(e,new Object())==null;
}
但只存key,value为定值(new Object)

三、八种基本类型

byte 1字节 8位 -128~127
short 2字节 16位 -32768~32767
int 4 32位 ±21亿
long 8 后边加L或l(可以不加) -9223372036854775808 ~ 9223372036854775807 19位
char 2 16位
float 4 32位 加F或f(整数可以不加f,小数必须加f)
double 8 64位
bollean 1

四、类加载器

Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。
引导类加载器(bootstrap class loader):启动类加载器,由C++实现,没有父类。
它用来加载 Java 的核心库(jre/lib/rt.jar),是用原生C++代码来实现的,并不继承自java.lang.ClassLoader。
加载扩展类和应用程序类加载器,并指定他们的父类加载器,在java中获取不到。
扩展类加载器(extensions class loader):拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null
它用来加载 Java 的扩展库(jre/ext/*.jar)。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader
它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
自定义类加载器(custom class loader):自定义类加载器,父类加载器肯定为AppClassLoader。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

双亲委派机制(双亲委托机制):
双亲委派模型的过程:如果一个类加载器收到了类加载的请求,首先不会自己去加载,而是把请求为派给自己的父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类反馈自己无法完成这个加载请求时,子类加载器才会尝试自己完成
java面试总结(初级研发工程师)
能不能自己写个类叫java.lang.String?

答案:通常不可以,但可以采取另类方法达到这个需求。
解释:为了不让我们写String类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而String类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的String,自己写的String类根本没有机会得到加载。

五:关于Object类

Object有哪些公用方法
Object是所有类的父类,任何类都默认继承Object

1、clone 保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
2、equals 在Object中与==是一样的,子类一般需要重写该方法。
3、hashCode 该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
4、getClass final方法,获得运行时类型
5、wait 使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。 wait() 方法一直等待,直到获得锁或者被中断。 wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生
1、其他线程调用了该对象的notify方法。 2、其他线程调用了该对象的notifyAll方法。 3、其他线程调用了interrupt中断该线程。 4、时间间隔到了。 5、此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
6、notify 唤醒在该对象上等待的某个线程。
7、notifyAll 唤醒在该对象上等待的所有线程。
8、toString 转换成字符串,一般子类都有重写,否则打印句柄。

六、内存分析:java面试总结(初级研发工程师)

栈空间(stack),连续的存储空间,遵循后进先出的原则,用于存放局部变量。
堆空间(heap),不连续的空间,用于存放new出的对象,或者说是类的实例。
方法区(method),方法区在堆空间内,用于存放①类的代码信息;②静态变量和方法;③常量池(字符串敞亮等,具有共享机制)。
Java中除了基本数据类型,其他的均是引用类型,包括类、数组等等。
数据类型的默认值
基本数据类型默认值:
数值型:0
浮点型:0.0
布尔型:false
字符型:\u0000
引用类型:null
变量初始化
成员变量可不初始化,系统会自动初始化;
局部变量必须由程序员显式初始化,系统不会自动初始化。

七:Throwable、Error和Exception类的关系

java面试总结(初级研发工程师)

八、多线程

1、继承Thread类,重写run方法,通过start方法启动线程
2、实现Runnable接口,并实现接口的run()方法,通过start方法启动线程,无返回值,不能抛出异常
3、实现Callable接口,重写call()方法,通过start方法启动线程,可以有返回值,可以抛出异常

九、gc垃圾回收机制

(1)对新生代的对象的收集称为minor GC;
(2)对旧生代的对象的收集称为Full GC;
(3)程序中主动调用System.gc()强制执行的GC为Full GC。

JVM GC怎么判断对象可以被回收了?
· 对象没有引用
· 作用域发生未捕获异常
· 程序在作用域正常执行完毕
· 程序执行了System.exit()
· 程序发生意外终止(被杀线程等)

新生代空间的构成与逻辑
为了更好的理解GC,我们来学习新生代的构成,它用来保存那些第一次被创建的对象,它被分成三个空间:
· 一个伊甸园空间(Eden)
· 两个幸存者空间(Fron Survivor、To Survivor)
默认新生代空间的分配:Eden : Fron : To = 8 : 1 : 1

1、内存溢出
内存溢出指的是程序在申请内存的时候,没有足够大的空间可以分配了。

2、内存泄露
内存泄露指的是程序在申请内存之后,没有办法释放掉已经申请到内存,它始终占用着内存,即被分配的对象可达但无用。内存泄露一般都是因为内存中有一块很大的对象,但是无法释放。
从定义上可以看出,内存泄露终将导致内存溢出。

十:乱七八糟

堆内存的特点就是:先进先出(笔试题)
栈内存的特点就是:先进后出

java一个类只允许继承一个类,但是可以实现多个接口
java:封装、继承、多态
访问修饰符:public>protected>default>private
有抽象方法的类一定是抽象类
方法重载:方法名相同、方法的参数个数不同或类型不同、与返回值无关
try的使用:try-catch和try-finally,try不能单独使用
十进制转二进制、十六进制 以及二进制、十六进制转换为十进制(笔试题)

9,栈和队列的共同点是( )
A.都是先进先出
B.都是先进后出
C.只允许在端点处插入和删除元素
D.没有共同特点

答案:C

10,下列关于修饰符混用的说法,错误的是()
A.abstract不能与final并列修饰同一个类
B.abstract类中不可以有private的成员
C.abstract方法必须在abstract类中
D.static方法中能处理非static的数据

答案:D
静态方法是属于类的,而普通方法是属于对象的。
属于类的静态方法可以在对象不存在的时候就能访问到,而普通方法必须先new一个对象才能用这个对象访问。当我们访问调用静态方法的时候(使用类名.静态方法名)这个时候就没有对象创建,所以普通方法是访问不到的。为了避免这种错误,所以java就不允许在静态方法中访问非静态方法。

十一:oracle性能优化

java面试总结(初级研发工程师)
常用:
left join:左连接
java面试总结(初级研发工程师)
right join:右连接
java面试总结(初级研发工程师)
inner join:内连接
java面试总结(初级研发工程师)
full join:全连接
java面试总结(初级研发工程师)

oracle中的表名、字段名不区分大小写,oracle会自动转换为大写再进行增删改查的操作。内容区分大小写
避免使用in和not in,因为in和not in不走索引,降低查询效率。可以通过exsits代替in,not exsits代替not in
分组查询时,先where过滤再分组优于先分组再having过滤
不走索引的查询方式(降低查询效率):<>、not in 、not exist、like “%_” 百分号在前、字符型字段为数字时在where条件里不添加引号、is null、is not null
select * from tablename where a=1 and b=2
oracle中,where条件为逆序,也就是先过滤b=1的条件,再过滤a=1的条件,因此where末端的过滤条件,应当选择能最大范围过滤查询结果的字段

十二、序列化

序列化问题:
在进行序列化时,判断是否为字符串、数组、枚举、序列化接口,否则直接抛出异常
Serializable 和 Externalizable
Java类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法进行序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。
如果读者看过Serializable的源码,就会发现,他只是一个空的接口,里面什么东西都没有。Serializable接口没有方法或字段,仅用于标识可序列化的语义。但是,如果一个类没有实现这个接口,想要被序列化的话,就会抛出java.io.NotSerializableException异常。
transient
transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
序列化是将对象的状态信息转换为可存储或传输的形式的过程。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。
而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致,这个所谓的序列化ID,就是我们在代码中定义的serialVersionUID。

十三、线程安全

volatile和synchronized特点
首先需要理解线程安全的两个方面:执行控制和内存可见。

执行控制的目的是控制代码执行(顺序)及是否可以并发执行。

内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。

synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作。

volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。

设计模式(选择性的看看):

单例模式:某个类只能有一个实例,提供一个全局的访问点。
简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。

适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
装饰模式:动态的给对象添加新的功能。
代理模式:为其他对象提供一个代理以便控制这个对象的访问。
亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。

模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
观察者模式:对象间的一对多的依赖关系。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
中介者模式:用一个中介对象来封装一系列的对象交互。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。

(80%为手敲,有错误请评论指出,共同进步。)