当前位置:   article > 正文

java面经总结

java面经

8.25 快手一面:

1、自我介绍

2、项目中人脸检测算法是否了解

3、hashMap怎么实现?用了什么结构?

4、编程:非递归实现二叉树后序遍历;递归实现n的全排列

 

8.29 美团电面一面:

1、java有哪些新建对象的方法

2、clone是浅拷贝还是深拷贝?两种拷贝有什么区别?

3、equals重写为什么需要重写hashcode函数?如果不改写会怎么样?

4、数组和链表区别?

5、hashMap怎么实现?用了什么结构?怎么扩容?负载因子是什么

6、hashMap和hashTable区别?多线程情况下使用hashMap,应该做什么改变?concurrentHashMap和hashTable区别?哪个性能好

7、二分查找,数组,链表,哈希表查找元素的时间复杂度

8、synchronized能修饰什么?

9、线程池有哪几种?分别的应用情况

10、younggc是什么?(oldgc?)

11、垃圾回收机制?

 

百度:

1、抽象类和接口的区别

2、== 和 equals()的区别

3、string buffer和string builder的区别

4、spring框架,springboot微服务;

5、静态类的特征、优点;

6、java string对象 “+”和append链接两个字符串之间的差异;

7、Java中的8种基本类型是什么? (*)

8、HashMap和Hashtable有什么区别?(***)

9、Override 和 Overload 的区别(**)

10、什么是强引用,软引用,弱引用,虚引用?(***)

11、递归的删除一个文件夹(**)

 

快手:

1、快排,动规,线程池,垃圾回收机制,spring,和mysql

2、

Integer a=17;
Integer b=17;
Integer c=1000;
Integer d=1000;
System.out.println(a==b);
System.out.println(c==d);

为什么输出是true & false

-128~127是true,超出就是false。

 

一、java基础

1、Java中的8种基本类型是什么?(百度)

整型:byte(8) short(16) int(32) long(64)

浮点型:float(32) double(64)

逻辑型:boolean (1)

字符型:char(16)

2、== 和 equals()的区别(百度)

== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。

equals用来比较的是两个对象的内容是否相等

3、string buffer和string builder的区别(百度)

当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

每次对String的操作都会生成新的String对象和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

4、java string对象 “+”和append链接两个字符串之间的差异;(百度)

相比String每次+都重新创建一个String对象,重新开辟一段内存不同,StringBuilder和StringBuffer的append都是直接把String对象中的char[]的字符直接拷贝到StringBuilder和StringBuffer的char[]上,效率比String的+高得多。当然,当StringBuilder和StringBuffer的char[]长度不够时,也会重新开辟一段内存。

5、静态类的特征、优点;(百度)

静态有一些特点:

1.全局唯一,任何一次的修改都是全局性的影响

2.只加载一次,优先于非静态

3.使用方式上不依赖于实例对象。

4.生命周期属于类级别,从JVM 加载开始到JVM卸载结束。

优点:不需要在进行实例化。静态变量的值,直接赋新值即可,不需要参数传递,之后可以直接进行参数引用即可;静态方法可以直接通过"类名.方法"的形式进行方法调用。通常方法被多次调用,并且方法中没有动态方法引用的时候使用比较方便。
缺点:初始化加载到内存,如果后续没被引用,加大了内存负担和程序运行负担,影响程序运行效率(一般很小),并且静态变量如果多处被引用赋值,很可能导致参数值混乱(如果是不变的值,最后加上final修饰,强制不能修改)。

6、JAVA静态内部类和非静态内部类的区别:

非静态内部类看做外部类的非静态成员,静态内部类可以看做外部类的静态成员。

(1)非静态内部类实例化必须有关联的外部类的实例化对象,静态内部类不需要。

(2)非静态内部类对象中隐式保存了指向外部类对象的引用,因此可以访问外部类的所有成员(方法和字段,无论静态还是非静态)。而静态内部类只能访问外部类的静态成员。

(3)非静态内部类中不能有静态的成员(方法或字段),静态内部类中可以有。

静态内部类只是嵌套在外部类里面,仅此而已,外部类对象和静态内部类对象之间并没有什么关系。

7、接口和抽象类的区别

抽象类是单一继承,接口是多重实现,子类只能有一个父类,而子类可以实现多个接口。

抽象类表示“从属”,是对种类的抽象,实现接口表示“组合”关系,是对行为的抽象。

接口中全是抽象方法,抽象类中可以有抽象方法,也可有方法体的方法。

接口中无构造方法,抽象类可有构造方法。

接口中的不可以有private方法和变量,接口中的变量只能为public static final,方法必须是 public abstract的(注意不能是static类型的),抽象类中可以有private方法和变量,因为抽象类中可以有具体的方法实现,在这些具体方法实现中可以用自己定义为private的变量和private方法。它不同于接口,因为接口中不能有方法的具体实现,其所有的方法都要求实现它的类来具体实现,所以,接口中的私有变量和私有方法就永远不会用到,所以接口中不会有private变量和private方法。

8、HashMap和Hashtable有什么区别?(百度)

HashMap允许键和值是null,而Hashtable则不允许键或者值是null。

Hashtable是同步的,而HashMap不是,所以HashMap更适用于单线程环境,Hashtable则适用于多线程环境。

9、Override 和 Overload 的区别(百度)

它们都是Java中多态的表现形式。

        重写(Override)是父类与子类之间多态性的一种表现。当子类中定义的某方法与其父类的某方法有相同的方法名和参数,我们就说该方法被重写 (Override),当我们调用子类的对象使用该方法时,将调用子类重写后的方法,父类中的方法则被覆盖。

        重载(Overload)是一个类中多态性的一种表现。如果在一个类中定义了多个相同方法名的方法,但它们的方法参数(参数个数或参数类型货参数顺序)不一致,则称为方法的重载。方法的重载与返回值的类型无关,与参数列表有关。

10、JAVA7之前 switch

括号中只能放int类型,之后可以放String和枚举类型。(其中byte short char可以自动提升为int类型,因此也可以放到括号里)。

11、类加载过程,即通过class文件创建相应的Class对象的过程。

一、装载(以二进制的形式读入class文件到方法区中,并在堆中实例化Class类的对象)

二、链接 (包括验证 准备 和 解析)

三、初始化

12、算法复杂度

13、遍历Set

set过程中删除元素不可使用set.remove() 而应使用迭代器删除iterator.remove()

14、递归的删除一个文件夹(百度)

/**

     * 删除文件夹下所有的文件

     */

    public static void delete(String path){

        File f=new File(path);

        //如果是目录,先递归删除

        if(f.isDirectory()){

            String[] list=f.list();

            for(int i=0;i<list.length;i++){

                //递归删除目录下的文件

                delete(path+"//"+list[i]);

            }

        }  

        //最后删除最外的空文件夹

        f.delete();

    }

GC垃圾收集器

15在java语言中,可作为GC Roots的对象包括下面几种:(可达性分析算法)

虚拟机栈(栈帧中的本地变量表)中引用的对象;

方法区中类静态属性引用的对象;

方法区中常量引用的对象;

本地方法栈中JNI(即一般说的native方法)引用的对象。

16、什么是强引用,软引用,弱引用,虚引用?(百度)

⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。  ps:强引用其实也就是我们平时A = new A()这个意思。
软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(网页缓存,图片缓存)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

SoftReference<String> sr = new SoftReference<String>(new String("hello"));

⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

WeakReference<String> sr = new WeakReference<String>(new String("hello"));

⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

二、多线程,多进程

1、要区分线程控制 线程同步和线程通信

线程控制是sleep() yield()和join()等操作

线程同步是volatile和synchronized

线程通信是wait() notify() 阻塞队列等.

某种意义上,线程同步也是线程通信的一种。

2、每个线程拥有自己的栈空间,所有线程共享堆空间.

栈是线程私有的,堆是线程共享的.

3、线程和进程的区别

进程是系统资源分配的基本单位,线程是CPU调度的基本单位.

每个进程具有自己独立的地址空间,进程之间是相互独立的。

所有线程之间共享父进程的地址空间。

进程上下文切换比线程开销大得多。

4、进程间通信的方法

共享内存:将一块公共的物理内存映射到多个进程的私有地址空间。

socket通信

消息队列:对消息队列有写权限的进程可以向消息队列中添加新的消息,对消息队列有读权限的进程可以从消息队列中读取新的消息。

5、线程通信问题要考虑同步监视器的锁池和等待池。

锁池和等待池中的线程都处于阻塞状态。

不同线程竞争CPU时间片问题。

6、为什么这些操作线程的方法要定义在object类中呢?

简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。

7、线程池参数

       核心线程池大小

       最大线程池大小

       最长等待时间

       任务队列大小

       拒绝处理任务的处理器

       核心工作线程是否超时退出

三、锁

1、产生死锁的四个必要条件:

  (1)   互斥条件:使用的资源是不能共享的。

(2) 请求与保持条件(hold and wait):线程持有一个资源并等待获取一个被其他线程持有的资源。

(3) 不剥夺条件:线程正在持有的资源不能被其他线程强行剥夺。

(4) 循环等待条件:线程之间形成一种首尾相连的等待资源的关系。

这四个条件是死锁的必要条件 ,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

2、synchronized和lock区别

1、synchronized是在JVM层面上实现的,而ReentrantLock是在代码层面上实现的。

2、lock比起synchronized增加了 响应中断锁等候 和 定时锁等候

3、synchronized(重量级锁)是悲观锁,lock为乐观锁。

4、lock可以设置为公平锁,即每次等待时间最长的线程获得锁。

3、如何解决死锁?

有三个方法:

1.进程在申请资源时,一次性得请求他所需要的所有资源。若无法满足则不能执行。

2.进程在申请新的资源时,释放已占有的资源。后面若还需要它们,则需要重新申请。

3.将系统中的资源顺序编号,规定进程只能依次申请资源。

四、Spring

1、SpringMVC

DispathcerServlet的 doServie方法处理请求:

1、设置上下文信息即request对象。

2、利用 handler mapping 找到请求对应的处理器。

3、将处理器进行适配

4、调用处理器(即页面控制器)的具体的业务对象的方法处理请求,返回ModelAndView

5、逻辑视图名指定的逻辑视图渲染数据,结果返回给用户。

五、数据库

1、数据库索引

分为  聚集索引   和   辅助索引,聚集索引决定数据的物理存储顺序,辅助索引是和数据分离出来的。

数据库索引从另一方面又分为B+树索引和哈希索引,B+树索引为传统意义上的索引是主要索引,3哈希索引是自适应的不能人为控制。

2、数据库引擎MySQL有两种InnoDB和MyISAM,区别有:

1、InnoDB支持事务,MyISAM不支持

2、InooDB支持行级锁,MyISAM不支持

3、InnoDB有聚集索引(聚集索引以主键为索引),索引决定数据的物理存储顺序,辅助索引的叶节点里存储的是主键值。而MyISAM数据和索引分开,索引中叶节点里存储的是行号。

3、事务

是一条或者多条数据库操作的集合,具有ACID四个特性。即原子性、一致性、隔离性 和持久性。

4、四种事务隔离级别

事务隔离级别

脏读

不可重复读

幻读

读未提交(read-uncommitted

不可重复读(read-committed

可重复读(repeatable-read

串行化(serializable

mysql中默认事务隔离级别是可重复读,不会锁住读取到的行。

 读取已提交内容  和  可重复读 两种隔离级别通过MCVV(多版本并发控制)实现.

读取已提交内容:MVCC  读不加锁 写加锁

(读取历史版本)相对于可重复读,是最近的历史版本。

可重复读:MVCC+Read View +Gap锁 读不加锁写加锁(读取历史版本)

Read View保证事务过程中读取的一致性,即可重复读

Gap锁(解决当前读)+Read View(解决快照读) 解决幻读问题

MySQL的可重复读模式下,不会出现幻读。

在读取提交内容 和 可重复读 模式下,读操作都不加锁, 在串行化模式下读加锁

六、计算机网络

1、TCP 三次握手 四次挥手

SYN→

←ACK SYN

ACK→

FIN→

←ACK

←FIN

ACK→

http://www.centos.bz/wp-content/uploads/2012/08/100327002629.png

http://www.centos.bz/wp-content/uploads/2012/08/100327022731.jpg

2、TCP 三次握手 四次挥手 的本质,交换了什么,如何保证建立连接?????

在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器 进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED状态,完成三次握手。

通过这样的三次握手,客户端与服务端建立起可靠的双工的连接,开始传送数据。

三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的。

3、HTTP状态码

2XX  成功

3XX  重定向

4XX  请求错误

5XX 6XX 服务器错误

4、TCP第三次握手失败怎么办?

答:当客户端收到服务端的SYN+ACK应答后,其状态变为ESTABLISHED,并会发送ACK包给服务端,准备发送数据了。如果此时ACK在网络中丢失,过了超时计时器后,那么Server端会重新发送SYN+ACK包,重传次数根据/proc/sys/net/ipv4/tcp_synack_retries来指定,默认是5次。如果重传指定次数到了后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接。但是Client认为这个连接已经建立,如果Client端向Server写数据,Server端将以RST包响应,方能感知到Server的错误。

七、设计模式

1、设计模式

单例模式 工厂模式   策略模式  适配器模式  生产者消费者模式 观察者模式

 

  1. java有哪些新建对象的方法
  1. 使用new关键字:这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们还可以调用任意的构参函数(无参的和有参的)。比如:Student = new Student();
  2. 使用Class类的newInstance方法:我们也可以使用Class类的newInstance方法创建对象,这个newInstance方法调用无参的构造器创建对象,如:Student student2 = (Student)Class.forName("根路径.Student").newInstance(); 或者:Student stu = Student.class.newInstance();
  3. 使用Constructor类的newInstance方法:本方法和Class类的newInstance方法很像,java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。如: Constructor<Student> constructor = Student.class.getInstance(); Student stu = constructor.newInstance(); 这两种newInstance的方法就是大家所说的反射,事实上Class的newInstance方法内部调用Constructor的newInstance方法。这也是众多框架Spring、Hibernate、Struts等使用后者的原因。
  4. 使用Clone的方法:无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去,用clone方法创建对象并不会调用任何构造函数。要使用clone方法,我们必须先实现Cloneable接口并实现其定义的clone方法。如:Student stu2 = <Student>stu.clone();这也是原型模式的应用。
  5. 使用反序列化:当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。如:ObjectInputStream in = new ObjectInputStream (new FileInputStream("data.obj")); Student stu3 = (Student)in.readObject();

 

  1. clone是浅拷贝还是深拷贝?两种拷贝有什么区别?

clone方法执行的是浅拷贝。

浅拷贝:我们这里说的浅拷贝是指我们拷贝出来的对象内部的引用类型变量和原来对象内部引用类型变量是同一引用(指向同一对象)。但是我们拷贝出来的对象和新对象不是同一对象。

    简单来说,新(拷贝产生)、旧(元对象)对象不同,但是内部如果有引用类型的变量(比如string),新、旧对象引用的都是同一引用。

深拷贝全部拷贝原对象的内容,包括内存的引用类型也进行拷贝

  1. 数组和链表区别?

数组在内存中连续,链表不连续;

数组静态分配内存(固定大小),链表动态分配内存(不指定大小);

数组元素在栈区(访问速度快,自由度小),链表元素在堆区(速度慢,自由度大);

数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);

数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。

数组的特点是:寻址容易,插入和删除困难。

链表的特点是:寻址困难,插入和删除容易。

     综合以上,对于快速访问数据,不经常有添加删除操作的时候选择数组实现,而对于经常添加删除数据,对于访问没有很高要求的时候选择链表。

  1. hashMap怎么实现?用了什么结构?怎么扩容?负载因子是什么(重点

实现原理拉链法,我们可以理解为“链表的数组” ,HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对和next引用。(数组为主体,链表来解决哈希冲突)

 

Next

 

// 存储时: 
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值 
int index = hash % Entry[].length; 
Entry[index] = value;

// 取值时: 
int hash = key.hashCode(); 
int index = hash % Entry[].length; 
return Entry[index];

举例:12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部数组中存储的是最后插入的元素。只需要简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表O(n),然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C。(c->b->a)

//实际存储的key-value键值对的个数

transient int size; (transient关键字标记的成员变量不参与序列化过程)

//当前HashMap的容量大小,默认为16

int initialCapacity;

//阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到

int threshold; = capacity*loadFactory

//threshold = capacity*loadFactory,当hashMap的size==threshold且发生了哈希冲突时,就会开始扩容

//负载因子,代表了table的填充度有多少,默认是0.75(大了节省内存空间,但容易导致哈希冲突,降低查询性能;小了查询性能好,但浪费内存空间)

final float loadFactor;

//用于快速失败,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException

transient int modCount;

Hashmap的扩容:

需要满足两个条件:当前数据存储的数量(即size())大小必须大于等于阈值;当前加入的数据是否发生了hash冲突

每次扩容大小是之前的两倍initialCapacity = initialCapacity * 2;(2^n)

哈希冲突:如果两个不同对象的hashCode mod length的结果相同(hashcode不相同),这种现象称为hash冲突。

hashcode相同(即key相同),那么value会被新的值覆盖。

  1. HashMap的equals方法重写为什么需要重写hashcode函数?如果不改写会怎么样?

首先equals与hashcode间的关系是这样的:

A、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;

B、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)  。但是同一个类的不同对象,当两者拥有相同hashcode的时候,则一定相等。 

假设两个对象,重写了其equals方法,其相等条件是属性相等,就返回true。如果不重写hashcode方法,其返回的依然是两个对象的内存地址值,必然不相等。这就出现了equals方法相等,但是hashcode不相等的情况。这不符合hashcode的规则。

 

  1. hashMap和hashTable区别?
  1. HashMap允许键和值是null,而Hashtable则不允许键或者值是null。
  2. Hashtable是同步的,而HashMap不是。
  3. 所以HashMap更适用于单线程环境,Hashtable则适用于多线程环境。

HashMap的key==null时,将元素添加到Entry[0]链表的表头;Key!=null但value==null

在对应位置插入null值。

 

  1. 多线程情况下使用hashMap,应该做什么改变?ConcurrentHashMap和HashTable区别?哪个性能好

三种解决方案:

  1. HashTable替换HashMap
  2. Collections.synchronizedMap将HashMap包装起来
  3. ConcurrentHashMap替换HashMap

ConcurrentHashMap和HashTable区别:

它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。

性能比较:ConcurrentHashMap性能更好!HashTable效率低下

缺点:当遍历ConcurrentMap中的元素时,需要获取所有的segment 的锁,使用遍历时慢。锁的增多,占用了系统的资源。使得对整个集合进行操作的一些方法(例如 size() 或 isEmpty() )的实现更加困难,因为这些方法要求一次获得许多的锁,并且还存在返回不正确的结果的风险。

  1. 二分查找,数组,链表,哈希表查找元素的时间复杂度

方法

查找

插入

二分查找

O(logN)

O(lgN+N/2)

数组

O(1)

O(N)

链表

O(N)

O(1)

哈希表(HashMap)

O(1)

O(1)

 

  1. synchronized能修饰什么?
  1. 修饰synchronized(obj任意自定义对象)

public int synMethod(int a1){

           synchronized(a1) {

            //一次只能有一个线程进入

           }

      }

使用synchronized(obj)进行同步操作,只要别的线程不访问这个同步代码块就不会堵塞!也就是说可以访问别的同步代码块。

  1. 修饰synchronized(this)

 public void bar() {

      synchronized(this) {

         //access both x and y here

      }

      //do something here - but don't use shared resources

   }

当一个线程访问类的一个synchronized (this)同步代码块时,其它线程对同一个类中其它的synchronized (this)同步代码块的访问将是堵塞,但是可以访问其他的非同步代码块

  1. 修饰synchronized(*.class)

synchronized (*.class) {

                      

  }

对整个class进行加锁,整个类一次只能由一个线程访问!对于含有静态方法和静态变量的代码块的同步,我们通常用synchronized(*.class)来加锁.

  1. 方法声明时使用(放在范围操作符(public等)之后,返回类型声明(void等)之前):public synchronized void synMethod() {

        //方法体

      }

对整个class进行加锁,整个类一次只能由一个线程访问!

 

10、线程池有哪几种?分别的应用情况

A. newCachedThreadPool:创建一个缓存线程池

底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)

通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可用线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。

适用:执行很多短期异步的小程序或者负载较轻的服务器

B. newFixedThreadPool创建一个定长线程池

底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列

通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)

适用:执行长期的任务,性能好很多

C.  newSingleThreadExecutor: 创建一个单线程线程池

底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列

通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)

适用:一个任务一个任务执行的场景

D.  newScheduledThreadPool: 创建一个定长线程池

底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列

通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构

适用:周期性执行任务的场景

线程池:我们可以把并发执行的任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程。(存放线程的容器)

为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(线程太少浪费资源,太多占内存)

 

一个线程池包括以下四个基本组成部分:

1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

 

11、YoungGC是什么?(OldGC?)

MinorGC == YoungGC

MajorGC == OldGC

新生代 GC(Minor GC/ YoungGC):

指发生在新生代的垃圾收集动作(GC)。因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。

触发条件:当Eden区满时,触发Minor GC。 Survivor满不会引发GC。
老年代 GC(Major GC / Full GC/ OldGC):

指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。

触发条件:

当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代,

当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载

/*******************************************/

新生代(Young Generation

新生代的目标就是尽可能快速的收集掉那些生命周期短的对象,一般情况下,所有新生成的对象首先都是放在新生代的。

老年代(Old Generation

  老年代存放的都是一些生命周期较长的对象,就像上面所叙述的那样,在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。

永久代(Permanent Generation

  永久代主要用于存放静态文件,如Java类、方法等。

/*******************************************/

 

12、垃圾回收机制?

A. 那些内存需要回收?(对象是否可以被回收的两种经典算法: 引用计数法 和 可达性分析算法)(java用了可达性分析算法)

a. 引用计数法:

引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。

任何引用计数为0的对象实例可以被当作垃圾收集。

b. 可达性分析算法:

可达性分析算法是通过判断对象的引用链是否可达来决定对象是否可以被回收。

程序把所有的引用关系看作一张图,通过一系列的名为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的,如下图所示。

/*******************************************/

在Java中,可作为 GC Roots 的对象包括以下几种:

虚拟机栈(栈帧中的局部变量表)中引用的对象;

方法区中类静态属性引用的对象;

方法区中常量引用的对象;

本地方法栈中Native方法引用的对象;

/*******************************************/

B. 什么时候回收? (堆的新生代、老年代、永久代的垃圾回收时机,MinorGC 和 FullGC)

C. 如何回收?(三种经典垃圾回收算法(标记清除算法、复制算法、标记整理算法)及分代收集算法 和 七种垃圾收集器)

1、标记清除算法(产生内存碎片

标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收,如下图所示。

/*******************************************/

效率问题:标记和清除两个过程的效率都不高;

空间问题:产生太多不连续的内存碎片

https://i-blog.csdnimg.cn/blog_migrate/0cb9f4f0ee5bd83a0aa14a1b6b70df47.png

/*******************************************/

2、复制算法——新生代

复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。该算法示意图如下所示:

https://i-blog.csdnimg.cn/blog_migrate/e36575e822d16fb1e6501d7e037cfbb6.png

事实上,现在商用的虚拟机都采用这种算法来回收新生代。因为研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错。

3、标记整理算法(不会产生内存碎片)——老年代

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代,其作用原理如下图所示。

https://i-blog.csdnimg.cn/blog_migrate/b930200ed3e769680ee9d19aa635150f.jpeg

标记整理算法与标记清除算法最显著的区别是:标记清除算法不进行对象的移动,并且仅对不存活的对象进行处理;而标记整理算法会将所有的存活对象移动到一端,并对不存活对象进行处理,因此其不会产生内存碎片。标记整理算法的作用示意图如下:

https://i-blog.csdnimg.cn/blog_migrate/d306c89fcc6429927f43ce537977060d.png

4、分代收集算法(结合复制算法和标记清楚算法/标记整理算法

基于这样一个事实:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。

商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法Java堆内存一般可以分为新生代、老年代和永久代三个模块,如下图所示:

https://i-blog.csdnimg.cn/blog_migrate/d9077bd6bcc392cf93a0592a842c4177.jpeg

GC算法总结:

 

七种垃圾回收器:

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
  • ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
  • Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
  • Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
  • G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

 

13、Java中final、finally和finalize的区别

final修饰符(关键字)。A. 被final修饰的类,不能被子类继承。【因此一个类不能既被abstract声明,又被final声明】 B. 被final修饰的方法,在使用的过程中不能被修改,只能使用,即不能方法重写 C. 被声明为final的变量必须在声明时给出变量的初始值,使用的过程中不能被修改,只能读取。

finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。

finalize是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。 

 

14、栈、队列的底层实现原理

(stack)又名堆栈,它是一种先进后出(FILO)的线性表。

<1>优点:存取速度比堆快,仅次于寄存器,栈数据可以共享; 

<2>缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

可以用链表或数组实现。

JAVA中通过数组来实现队列。

java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,这就意味着,Stack也是通过数组实现的而非链表。】

队列(queue),它是一种先进先出(FIFO)的线性表。

可以用链表(LinkedList)或数组(Array/ArrayList)实现。

JAVA中通过LinkedList来实现队列。

 

15、ArrayList和LinkedList的大致区别:

1.ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。

2.对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。

3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

所以在我们进行对元素的增删查操作的时候,进行查操作时用ArrayList,进行增删操作的时候最好用LinkedList。

 

 

16、常用设计模式:

【单例模式 工厂模式   策略模式  适配器模式  生产者消费者模式 观察者模式】

1、单例模式 Singleton

如果一个类始终只能创建一个实例,则这个类被称为单例类,这种模式就被称为单例模式。

 一般建议单例模式的方法命名为:getInstance(),可以有参数,一般无参数。

优点:

1)减少创建Java实例所带来的系统开销

2)便于系统跟踪单个Java实例的生命周期、实例状态等。

2、简单工厂模式 StaticFactory Method

由一个工厂对象决定创建出哪一种产品类的实例。

优点:

让对象的调用者和对象创建过程分离,当对象调用者需要对象时,直接向工厂请求即可。

3、工厂模式 Factory Method

程序可以为不同产品类提供不同的工厂,不同的工厂类生产不同的产品(也就是一个工厂生产一个产品,通过选择不同的工厂获得不同的产品。)

4、抽象工厂模式 Abstract Factory Method

为了解决客户端代码与不同工厂类耦合的问题。在工厂类的基础上再增加一个工厂类,该工厂类不制造具体的被调用对象,而是制造不同工厂对象。(建立一个生产工厂的工厂,把原来工厂模式的工厂作为方法)

5、代理模式 Proxy Method

当客户端代码需要调用某个对象时,客户端实际上不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)。

6、命令模式 Command Method

某个方法需要完成某一个功能,完成这个功能的大部分步骤已经确定了,但可能有少量具体步骤无法确定,必须等到执行该方法时才可以确定。在Java中,传入该方法的是一个对象,该对象通常是某个接口的匿名实现类的实例,该接口通常被称为命令接口,这种设计方式也被称为命令模式。

7、策略模式 Strategy Method

策略模式用于封装系列的算法,这些算法通常被封装在一个被称为Context的类中,客户端程序可以自由选择其中一种算法,或让Context为客户端选择一种最佳算法——使用策略模式的优势是为了支持算法的自由切换。

可以考虑使用配置文件来指定用户使用哪种具体算法——这就彻底分离客户端代码和具体算法策略。

8、观察者模式 Observer Method

观察者模式定义了对象间的一对多依赖关系,让一个或多个观察者对象观察一个主题对象。当主题对象的状态发生变化时,系统能通知所有的依赖于此对象的观察者对象,从而使得观察者对象能够自动更新。

在观察者模式中,被观察的对象常常也被称为目标或主题(Subject),依赖的对象被称为观察者(Observer)。

9、生产者消费者模式 ProducerConsumer Method

生产者和消费者在同一时间段内共用同一存储空间,生产者向空间(仓库/缓冲区)里生产数据,而消费者取走数据。

优点:

1)解耦

2)支持并发

生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接。

3)支持忙闲不均

当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号