java学习总结(面试题持续更新)

java学习总结(面试题持续更新),第1张

1. 面向对象VS面向过程

场景:程序要连接数据库

面向对象:市面上大家都使用的是什么组件,或那个框架完成的,我直接拿来使用。如何使用对象,组织者

面向过程:我该怎么连接数据库,怎么具体实现。如何创建对象,执行者

1.1 java的三大特性:封装、继承、多态

封装:隐藏内部实现,对外提供接口,提高安全性,复用性

继承:公用的内容,抽取作为父类,提高复用性

多态:包含重写,重载。

多态是同一个行为具有多个不同的表现形式或形态的能力;多态就是同一个接口使用不同的实例而执行不同 *** 作; 2. JDK、JRE、JVM

jdk:开发工具包,提供运行环境(jre)和开发环境,包含编译java源文件的编译器javac,还有调试和分析工具。

jre:java运行环境仅包含java虚拟机(jvm)和一些基础类库

jvm:java虚拟机,运行字节码文件

2.2. java的跨平台

主要是通过jvm实现的,在不同平台(win,mac,linux…)都有自己的jvm,而只要是编译后的字节码文件都可以通过任何平台的jvm运行,从而实现跨平台

3. ==和equals的区别

== : 比较的的是具体的值 (基本数据类型) ,如果比较引用数据类型 也是比较引用地址

equals :默认比较的是引用指向的地址(object类的方法),可以进行重写

3.1 new之后的对象,在栈中都是指向堆内存 4. final关键字

修饰类:类不可被继承,最终类

修饰方法:该方法不可被重写

修饰变量:那么这个变量就成为一个常量

修饰基本数据类型,那么这个值不可改变

修饰引用数据类型,指向的堆内存不可改变

5. String,StringBuffer,StringBuilder

String跟后两者的区别:String 是final类每次声明都是不可变对象,每次 *** 作都会new新的对象

StringBuffer StringBuilder都是在原有对象进行 *** 作 如果要经常改变字符串的内容 使用这两者

StringBuffer是线程安全的,StringBuilder是不安全的

效率:StringBuilder > StringBuffer > String

5.1 线程安全与不安全

线程安全:在多环境下,这个对象的访问不需要加入额外的同步控制, *** 作的数据依然是正确的

线程不安全:反之,数据不正确

StringBuffer安全是因为源码每个方法中都加入了synchronized关键字

在开发中,优先选用StringBuilder(性能高),要解决线程安全(高并发、多线程情况下)——>多线程要考虑,栈内存中数据是共享,还是每个线程独享,如果是线程独享,不必考虑安全问题

6. 抽象类和接口的区别

1,语法层面

1.1,JDK1.8之前

​ 抽象类:方法可以有抽象的,也可以有非抽象(正常的方法,具体的实现), 有构造器

​ 接口:方法都是抽象,属性都是常量,默认有public static final修饰

1.2,JDK1.8之后

接口里面可以有实现的方法,注意要在方法的声明上加上default或者static

interface IEatable{
    public default void eat(){}
}

2,开发设计层面

抽象类:同一类事物的抽取,比如针对Dao层 *** 作的封装,如,BaseDaoImpl,BaseServiceImpl

接口:通常更像是一种标准的制定,定制系统之间对接的标准规范 (解耦)

例子:
- 1,单体项目,分层开发,接口作为各层之间交互的纽带,在controller中注入IUserService,在Service注入IUserDao
- 2,分布式项目,面向服务的开发,抽取服务service,这个时候,就会产生服务的提供者和服务的消费者两个角色
- 这两个角色之间的纽带,依然是接口

最后区分几个概念:

多继承,多重继承,多实现

多继承:接口可以多继承,类只支持单继承
- 多重继承:A->B->C(爷孙三代的关系)
- 多实现:Person implements IRunable,IEatable(记忆联想-符合多项国际化标准) 7. 递归

递归:方法内部调用方法自身

找到规律找到递归出口 7.1 递归为什么会栈内存溢出

在调用方法时候会在栈中开辟空间 栈帧,调用一次,会创建一个,所以,如果递归次数太多,会导致栈内存溢出。

7.2 N的阶乘
N! = (N-1)! * n;
5! = 1*2*3*4*5;
4! = 1*2*3*4;

public static int getResult(int n){
	if(n < 0){
		throw new RuntimeException("非法参数");
	}
	if(n == 1 || n == 1){
		return 1; //递归出口
	}
	return getResult(n-1) * n;
}
7.3 斐波那契

数字:1,1,2,3,5,8,13,21 … 求,第n个数是多少

规律:每个数字都是前两个数字之和
递归出口:第一项和第二项都是1
public int getFeiBo(int n){
	if(n < 0){
		throw new RuntimeException("非法参数");
	}
	if(n == 1 || n == 2){
		return 1;
	}else{
		return getFeiBo(n-2) + getFeiBo(n-1);
	}
}

8. 什么是向上转型?什么是向下转型?

向上转型:父类引用指向子类对象。(安全)

向下转型:父类对象强转为子类对象。(不安全)

9.Integer int

Integer 引用数据类型(包装类);

int 基本数据类型

Integer i1 = new Integer(66);
Integer i2 = new Integer(66);
System.out.println(i1 == i2); //false 两个都是new出来的 所以指向的堆内存不同 false
// 自动装箱范围:-128 -- +127 之间 也就不是新new出来的对象  查看Integer.valueof源码
Integer i3 = 66;
Integer i4 = 66;
int i5 = 66;
System.out.println(i3 == i4); //true i3,i4都是使用基本数据类型进行赋值 进行自动装箱 (jdk1.5之后)Integer.valueof();
System.out.println(i3 == i5); //true 引用数据类型跟基本数据类型进行比较时候 会进行 自动拆箱 就是将引用类型转成基本类型

Integer i6 = 128;
Integer i7 = 128;
int i8 = 128;
System.out.println(i6 == i7); //false 
System.out.println(i6 == i8); //true
10. 重写和重载的区别

重载:发生在一个类中,多个方法 方法名相同,参数列表不同(顺序,类型,都可)就叫做重载。与返回值无关

重写:发生在两个父类跟子类之间,子类对父类方法的重新编写,满足:方法名相同,参数列表相同,子类方法访问权限只能大于父类

11. List和Set的区别

list:有序,不可重复

set:无序,不可重复 (无序:加入集合的顺序 != 遍历的顺序)

12.ArrayList 和LinkedList的区别

底层数据结构

ArrayList 底层是数组结构,是一块连续的内存空间LinkedList底层是双向链表,是不连续的内存

常规答案

ArrayList查询快,因为内存是连续的,方便寻址,但是增删慢,因为需要发生数据迁移

LinkedList查询慢,通过指针查找,但增删快,只需要改变前后结点的指针指向

通过下标查询,ArrayList快,如果指定元素,查询所在位置,两者相同 ArrayList有下标

增删:

如果是中间或者非结尾

ArrayList因为底层为数组保证内存连续,所以,插入位置后的所有元素都要移动,导致速度变慢LinkedList因为底层是链表,只需要改变所在位置前后元素的结点指向即可

如果是结尾

ArrayList因为底层为数组,可计算位置,快速定位直接 *** 作

LinkedList有两个指针,头指针,尾指针,要在末尾找会从尾指针开始找

末尾插入,两者类似

在向List中存入固定个数的元素 ArrayList更节省内存(初始化容量)

ArrayList 扩容

创建一个新的数组,长度是原数组的1.5倍(位运算)将原数组迁移到当前数组

双向链表中在元素A和B中间插入元素C

伪代码:

C.pre = A

C.next = A.next

A…next.pre = C

A.next = C

13. HashSet 存储原理

HashSet 不可重复的原因:hashSet.add方法使用的是map集合,将add的东西作为key,所以不可重复

HashSet底层使用HashMap实现存储

13.1 为什么使用hash算法,有什么优势,解决了什么问题?

主要是解决数据的唯一性判断的效率问题,保证为O(1)的复杂度

存储数据,底层用的是数组,数组实现唯一性就是遍历,这种效率比较低,所以采用hashcode采用hash算法,通过存储对象的hashcode,然后在跟数组长度-1做运算,得到我们要的存储在数组中的下表,如果此时这个下表没有其他元素,那么就不用比较,直接存储。hash冲突:不同的元素,或对象,hash值相同这时候就需要比较,使用equals,如果equals相等 则是重复的 不插入,如果不相等,就将当前位置的next指针指向要插入的元素,从而形成链表 13.2 什么是hash表

本质是一个数组,数组元素构成链表

13.3 链表的优化

jdk1.8之后,防止链表过长,会将链表优化为红黑树,使用二分查找比较,这样效率就更快了

14. ArrayList 和 Vector

区别:前者线程安全,后者不安全

类似于StringBuffer 和 StringBuilder的区别

15. HashTable & HashMap & ConcurrentHashMap HashTable 线程安全,内部有锁的控制HashMap 线程不安全,内部没有锁 优点:效率高缺点:如果有多个线程同时 *** 作,并且线程共享栈帧,那么就会产生线程安全的问题,甚至会出现死锁 ConcurrentHashMap 使用的是分段锁,降低锁粒 jdk8 以前 将数据分段,执行分段锁(分离锁),核心把锁的范围变小,这样出现并发冲突的概率就变小在put时候,key会首先进过hash运算,拿到所在的hash段,然后只锁当前段 相当于是折中jdk7 和 jdk8中的区别 jdk1.7采用的是链表 jdk1.8采用的链表+红黑树发生hash冲突时候 jdk1.7 采用链表 jdk1.8默认使用链表 链表长度超过8且数组容量超过64时时候使用红黑树 并发安全的实现 JDK1.7采用分段锁的方式,而JDK1.8采用CAS和synchronized的组合模式 查询时间复杂度 JDK1.7采用链表的方式,时间复杂度为O(n),而JDK1.8在采用红黑树的方式时,时间复杂度为O(log(n)) 开发中选择 HashMap 不是多线程的情况如果HashMap是个全局对象,并且线程共享map对象 选择ConcurrentHapMapHashTable 不选择 效率低 16. 栈 stack 是如何实现的

特点:FILO(First In Last Out)先进后出

底层是使用数组

入栈:数组尾部直接插入
public E push(E item) {
    addElement(item);

    return item;
}

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1); // 判断数组容量是否充足
    elementData[elementCount++] = obj;
}
出栈:尾部出
第一种:获取并从栈中删除
public synchronized E pop() {
    E       obj;
    int     len = size();

    obj = peek();
    removeElementAt(len - 1);

    return obj;
}
第二种:获取栈顶元素
public synchronized E peek() {
    int     len = size();

    if (len == 0)
        throw new EmptyStackException();
    return elementAt(len - 1);
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1294909.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存