java基础阶段几个面试题

1.说出你对面向对象的理解android

在我理解,面向对象是向现实世界模型的天然延伸,这是一种“万物皆对象”的编程思想。在现实生活中的任何物体均可以归为一类事物,而每个个体都是一类事物的实例。面向对象的编程是以对象为中心,以消息为驱动,因此程序=对象+消息。
面向对象有三大特性,封装、继承和多态。
封装就是将一类事物的属性和行为抽象成一个类,使其属性私有化,行为公开化,提升了数据的隐秘性的同时,使代码模块化。这样作使得代码的复用性更高。
继承则是进一步将一类事物共有的属性和行为抽象成一个父类,而每个子类是一个特殊的父类--有父类的行为和属性,也有本身特有的行为和属性。这样作扩展了已存在的代码块,进一步提升了代码的复用性。
若是说封装和继承是为了使代码重用,那么多态则是为了实现接口重用。多态的一大做用就是为了解耦--为了解除父子类继承的耦合度。若是说继承中父子类的关系式IS-A的关系,那么接口和实现类之之间的关系式HAS-A。简单来讲,多态就是容许父类引用(或接口)指向子类(或实现类)对象。不少的设计模式都是基于面向对象的多态性设计的。算法

2.JVM的内存区及其GC算法
https://blog.csdn.net/anjoyandroid/article/details/78609971
元空间:jdk1.8取消了持久代新增了元空间,并将方法区放在元空间中编程

3.集合框架下的各类接口和实现类有哪些,分别有啥特色设计模式

https://blog.csdn.net/sdgihshdv/article/details/72566485
https://blog.csdn.net/suifeng629/article/details/82179996
https://blog.csdn.net/C18298182575/article/details/87167323api

4.string类有啥特色,有哪些经常使用的API数组

1.String类对象的相等判断使用equals()方法完成,“==”实现的是地址数值的比较
2.字符串内容一旦声明则不可改变,String类对象内容的改变是依靠引用关系的变动实现的。
3.String类有两种实例化方式,使用直接赋值能够不产生垃圾空间,而且能够自动入池,不要使用构造方法赋值。安全

indexOf()检索字符串中某个字符或某段字符的下标。
lastIndexOf()
和indexOf相似,不过是查找最后一个出现的位置。
str.lastIndexOf(str,index),从下标index往前查找最后一个出现的位置
substring()返回一个字符串的子字符串
charAt(index)返回下标对应的字符
trim()去掉字符串先后的空格
startsWith()/endsWith()检测字符串是否已制定字符串开头或结尾,返回值是boolean
split()/根据括号内的字符串分离字符串,返回值是一个字符串数组
....多线程

5.stringBuilder和stringBuffer的区别?并发

运行速度:StringBuilder >StringBuffer >String
线程安全:StringBuilder是线程不安全的,而StringBuffer是线程安全的
String:适用于少许的字符串操做的状况
StringBuilder:适用于单线程下在字符缓冲区进行大量操做的状况
StringBuffer:适用多线程下在字符缓冲区进行大量操做的状况app

为何StringBuilder是不安全的?
char[] value;
int count;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
对于count + =len;不是一个原子操做 两个线程同时执行假设都是 计数器为 10 执行完后 就会变成11 而不是12

什么是原子操做:
简单的例子:
转帐,A转给B100,由于停电,致使A转出了100,B却没收到100,因此要把100回滚给A。
原子操做就是多线程下各线程同时执行失败且同时成功,在两个线程下,因为count继承于父类AbstractStringBuilder,当
其中一个线程对coun执行+len后,另外一线程取到的count值仍为原来的count值,故+len后和上一个线程获得的结果同样,
故线程不安全
而stringBuffer中源码:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
当一个线程访问append后会当即上锁,从而另外一个线程没法访问append方法,故是线程安全的
在多线程下,stringBuffer下各线程须要频繁的加锁解锁操做,从而须要运行更长的时间,虽然stringBuilder不须要加锁解锁,
当因为线程不安全性,更适用于单线程

6.线程建立的3种方式,线程阻塞的API有哪些及其之间的区别?

Runnable,Thread,经过 Callable 和 Future 建立线程。
1. 继承Thread类来建立一个线程, 并实现run方法(线程须要完成的功能); 构建子类对象,start()启动线程
2. 实现Runnable接口来建立一个线程, 实现Runnable,实现run()方法; 将Runnable接口类的对象做为参数传递给Thread类对象, 并调用start()方法;
3. 实现Callable接口来建立一个线程, 先定义一个Callable的实现类, 重写call()方法, call()有返回值; 两种执行方式:
1). 借助FutureTask执行, 建立Callable实现类的对象, 并做为参数传递给FutureTask, FutureTask做为参数传递给Thread类的对象, 并执行start()方法;
2). 借助线程池来执行, 先建立线程池, 而后调用线程池的submit方法, 并将Callable实现列做为参数传入,

方法二的好处:
1. 能够将一个Runnable实现类传递给多个线程对象, 适合用多个相同程序代码的编程处理同一个资源
2. Thread类建立线程是采用继承的方式, 而Java中只能单继承, 若是某个子类的须要建立线程只能采用实现Runnable接口或者实现Callable接口的方式.

方法三的好处:
1. 有返回值
2. call()能够抛出异常
3. 运行Callable任务能够获得一个Future兑现,表示异步计算的结果. 它提供了检测计算是否完成的方法(isDone())以等待计算的完成,并检索计算的结果.

线程阻塞api:
sleep()方法; 该方法容许指定以ms为单位的一段时间做为参数, 它使得线程在指定的时间内进入阻塞状态,不能获得CPU时间, 指定时间已过,线程从新进入可执行状态.
suspend()和resume()方法;配套使用, suspend()使得线程进入阻塞状态,且不会自动恢复, 必须将其对应的resume()调用, 才可使线程进入可执行状态.
yield(); 使得线程放弃当前分得的CPU时间, 可是不使线程阻塞, 即线程仍然处于可执行状态;
wait()和notify()方法;配套使用,若wait()有参数,至关于sleep(但能够经过notify强行唤醒), wait()没有参数,至关于suspend(), 须要经过notify唤醒

sleep(0)和sleep(1)和不要sleep的区别:
sleep(0),若是线程调度器的可运行队列中有大于或等于当前线程优先级的就绪线程存在,操做系统会将当前线程从处理器上移除,调度其余优先级高的就绪线程运行;若是可运行队列中的没有就绪线程或全部就绪线程的优先级均低于当前线程优先级,那么当前线程会继续执行,就像没有调用 Sleep(0)同样。
Sleep(1),会引起线程上下文切换:调用线程会从线程调度器的可运行队列中被移除一段时间,这个时间段约等于 timeout 所指定的时间长度。为何说约等于呢?是由于睡眠时间单位为毫秒,这与系统的时间精度有关。一般状况下,系统的时间精度为 10 ms,那么指定任意少于 10 ms但大于 0 ms 的睡眠时间,均会向上求值为 10 ms。

7.抽象类和接口的区别?有了抽象类为啥还要接口?

①.一类能够实现多个接口但只能继承自一个抽象类,从抽象类派生出的子类一样能够实现接口,从而,咱们能得出一个结论:接口是为Java实现多继承而存在的
②.抽象类中能够存在非抽象的方法,可接口不能存在非抽象的方法,而且接口里面的方法只是一个声明,必须用 public abstract来修饰,没有具体的实现
③.抽象方法中的成员变量能够被不一样的修饰符修饰,而接口中的成员变量默认都是静态常量
④.抽象类是对对象进行的抽象,而接口是一种行为规范,这一点是比较重要的.
(因此为何有了接口还要有抽象类)

8.冒泡排序,选择排序,快速排序(了解)

冒泡排序:什么是冒泡?好比说水底随机产生一些气泡,一块儿往上冒泡,越轻的气泡往上冒的越快
具体:12 34 10 78 67
若是从小到大排序:先将67和78比较,67比78小,依次往前比较,小的放前面,打的放后面,以此为一轮排序,而后再将新的数组重复上述过程,共须要n轮排序(n为元素个数);

选择排序:从一个数组里选出最小的元素放在数组第一位并交换位置,而后再将去掉第一位的数组找出最小元素并放在这个新数组第一位,
重复此操做。
12 34 10 78 67
第一轮:10| 34 12 78 67
第二轮:10 12| 34 78 67
第三轮:10 12 34| 78 67
第四轮:10 12 34 67| 78
排序结束

快速排序:基于基数排序。先取任意一基数,通常为数组第一个元素(因为当第一个元素为最小值(最大值)时会使排序出现错误,故有时候也取中间的元素),而后将比基数小的数做为一个数组,比基数大的数做为一个数组,再将新的两个数组分别递归排序。
经过基数分红两个数组的过程:12 34 10 78 67 8 假设数组为arr
取一基数temp=12 取low=0(数组第一位),high=5(数组最后一位)
第一轮:第一步:先从后往前比较:arr[high]=8<12=temp,结束这一步操做,high与low不变。若是这里arr[high]>12,则令high-1获得新的high将arr[high]与temp比较,依此下去直到arr[high]<temp,这种状况high发生改变,low不变。
第二步:再从前日后将arr[low]与temp比较,原理与第一步相同,由于arr[1]>temp,此时low=1,结束这一步操做。
第三步:交换arr[low]与arr[high]的值
第一轮结果:12 8 10 78 67 34(low=1,high=5)
第二轮:与第一轮同样,第一步,从arr[high]往前,直到arr[2]=10<12,此时high=2,结束这一步
第二步,从arr[low]日后,12,8,10都不大于12,到这里的时候,由于low=2=high,故比较,获得索引index=low=high=2
第二轮结果:12 8 10 78 67 34
由于index获得了值3,将arr[index]做为分界点将最后一轮结果数组[12 8 10 78 67 34]分为两个数组[12 8 10]和[78 67 34]
将新的到的两个数组重复进行上述操做
[12 8 10]->由于12为最大值,故取中间值8->[8]和[10 12]->[8]、[10]、[12]
[78 67 34]->取67,->[34]、[67 78]->[34]、[67]、[78]->[8]、[10]、[12]、[34]、[67]、[78]
(拓展:希尔排序、插入排序)

9.什么是死锁?如何避免死锁

死锁的定义:所谓死锁是指多个线程因竞争资源而形成的一种僵局(互相等待),若无外力做用,这些进程都将没法向前推动。

产生缘由:
1) 系统资源的竞争
一般系统中拥有的不可剥夺资源,其数量不足以知足多个进程运行的须要,使得进程在 运行过程当中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争 才可能产生死锁,对可剥夺资源的竞争是不会引发死锁的。
2) 进程推动顺序非法
进程在运行过程当中,请求和释放资源的顺序不当,也一样会致使死锁。例如,并发进程 P一、P2分别保持了资源R一、R2,而进程P1申请资源R2,进程P2申请资源R1时,二者都 会由于所需资源被占用而阻塞。

四个产生死锁的条件:
互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时如有其余进程请求该资源,则请求进程只能等待。
不剥夺条件:进程所得到的资源在未使用完毕以前,不能被其余进程强行夺走,即只能 由得到该资源的进程本身来释放(只能是主动释放)。
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其余进程占有,此时请求进程被阻塞,但对本身已得到的资源保持不放。
循环等待条件:存在一种进程资源的循环等待链,链中每个进程已得到的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有。

避免死锁:1.加锁顺序(线程按照必定的顺序加锁)2.加锁时限(线程尝试获取锁的时候加上必定的时限,超过期限则放弃对该锁的请求,并释放本身占有的锁)3.死锁检测https://blog.csdn.net/ls5718/article/details/51896159