面试总结---编程语言部分(java)

[TOC]

1. java集合

1.1 常用集合的导图

java集合导图

1.2 Collection和Map

java集合类分为collection 和 map两类

Collection

  • List(有序、可重复)

    • ArrayList :线程不安全,底层用数组实现,按插入先后排序,随机访问快,插入与删除慢。
    • LinkedList :线程不安全的,底层基于链表的数据结构实现,访问慢但插入元素与删除元素比较快。LinkedList类实现了Queue接口,可当做队列使用
    • Vector :线程安全的, 实现了Cloneable接口,即实现clone()函数。它能被克隆。Vector 实现Serializable接口,支持序列化。
  • Set(无序、不可重复)

    • HashSet :线程不安全的,值可以为null,层使用数据结构的hash算法实现的,因此具有很好的存取,查找的性能。在add对一个对象的添加要重写equals和hashcode,保证对象不重复性。
    • TreeSet :线程不安全的, 底层使用数据结构红黑树算法进行维护的。可通过实现compareTo(Object obj) 来进行来进行排序
    • LinkedHashSet :线程不安全的, 底层使用链表来进行维护的。里面的顺序是值插入的顺序,插入的时候性能不如hashSet而查询的时候性能会比hashSet更好。
  • Queue(队列,先进先出)

    • PriorityQueue :保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序

    常用队列操作方法:

    1
    2
    3
    4
    5
    6
    void add(Object e);//将指定元素加入此队列的尾部。
    Object element();//获取队列头部的元素,但是不删除该元素。
    boolean offer(Object e);//将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)方法更好。
    Object peek(); //获取队列头部的元素,但是不删除该元素,如果此队列为空,则返回null。
    Object poll();//获取队列头部的元素,并删除该元素,如果此队列为空,则返回null。
    Object remove();//获取队列头部的元素,并删除该元素。

Map

  • HashMap :线程不安全的,基于哈希表实现。Key值和value值都可以为null
  • HashTable :线程安全的,基于哈希表实现。Key值和value值不能为null
  • TreeMap :线程不安全的,保存元素key-value不能为null,允许key-value重复;遍历元素时随机排列

2. java并发

  • 并行:表示两个线程同时做事情。
  • 并发:表示一会做这个事情,一会做另一个事情,存在着调度。单核 CPU 不可能存在并行(微观上)。

java实现多线程的两种方式:继承 Thread 类和实现 Runnable 接口

2.1 java多线程中名词的概念

多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。
具体详细内容可点击这里查看

  • 临界区

    临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。

  • 阻塞与非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其它所有需要这个资源的线程就必须在这个临界区中进行等待,等待会导致线程挂起。这种情况就是阻塞。
此时,如果占用资源的线程一直不愿意释放资源,那么其它所有阻塞在这个临界区上的线程都不能工作。阻塞是指线程在操作系统层面被挂起。阻塞一般性能不好,需大约8万个时钟周期来做调度。
非阻塞则允许多个线程同时进入临界区。

  • 死锁

死锁是进程死锁的简称,是指多个进程循环等待他方占有的资源而无限的僵持下去的局面。

  • 活锁

假设有两个线程1、2,它们都需要资源 A/B,假设1号线程占有了 A 资源,2号线程占有了 B 资源;由于两个线程都需要同时拥有这两个资源才可以工作,为了避免死锁,1号线程释放了 A 资源占有锁,2号线程释放了 B 资源占有锁;此时 AB 空闲,两个线程又同时抢锁,再次出现上述情况,此时发生了活锁。

简单类比,电梯遇到人,一个进的一个出的,对面占路,两个人同时往一个方向让路,来回重复,还是堵着路。
如果线上应用遇到了活锁问题,恭喜你中奖了,这类问题比较难排查。

  • 饥饿

饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。

2.2 java实现并发的方式

一般都是通过加锁进行实现。

2.2.1 synchronize关键字

  • 用法1
1
2
3
4
5
	public class Test{
public synchronized void print(){
....;
}
}
  • 用法2
1
2
3
4
5
6
7
public class Test{
public void print(){
synchronized(this){//锁住本对象
...;
}
}
}
  • 用法3
1
2
3
4
5
6
7
public class Test{
private String a = "test";
public void print(){
synchronized(a){//锁住a对象
...;
}
}
  • 静态方法的锁
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test{
public synchronized static void execute(){
...;
}
}
//效果同
public class Test{
public static void execute(){
synchronized(Test.class){
...;
}
}
}

2.2.2 java.util.Lock的使用

Lock能实现synchronized完成的所以功能,不同点是:Lock有比synchronize更精确的线程语义和更好的性能,synchronize会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。

1
2
3
4
5
6
7
8
9
final Lock lock = new ReentrantLock();
while(true){
lock.lock();//加锁
try{
j--;
}finally{
lock.unlock();//释放锁
}
}

2.2.3 使用线程池

通过上面的介绍,完全可以开发一个多线程的程序,为什么还要引入线程池呢。主要是因为上述单线程方式存在以下几个问题:

  • 线程的工作周期:线程创建所需时间为 T1,线程执行任务所需时间为 T2,线程销毁所需时间为 T3,往往是 T1+T3 大于 T2,所有如果频繁创建线程会损耗过多额外的时间;
  • 如果有任务来了,再去创建线程的话效率比较低,如果从一个池子中可以直接获取可用的线程,那效率会有所提高。所以线程池省去了任务过来,要先创建线程再去执行的过程,节省了时间,提升了效率;
  • 线程池可以管理和控制线程,因为线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;
  • 线程池提供队列,存放缓冲等待执行的任务。

四种线程池的使用

  • newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

    1
    2
    3
    4
    5
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
    cachedThreadPool.execute(new Runnable() {
    public void run() {
    System.out.println(index);
    }
  • newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

1
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
  • newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行

    1
    2
    3
    4
    5
    6
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);  
    scheduledThreadPool.schedule(new Runnable() {
    public void run() {
    System.out.println("delay 3 seconds");
    }
    }, 3, TimeUnit.SECONDS); //延迟3秒执行
  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

    1
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

2.2.4 wait 和 sleep 理解

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object
类中的。 sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态
依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对
此对象调用notify()方法或notifyAll()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

3. java异常

3.1 异常类导图

异常导图

3.2 异常的使用

一般都是通过try···catch···finally 进行处理

3.2.1 try、catch、finally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
try{
//try块中放可能发生异常的代码。
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
//如果发生异常,则尝试去匹配catch块。

}catch(SQLException SQLexception){
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。
}catch(Exception exception){
//...
}finally{
//finally块通常是可选的。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}

需要注意的是:

  1. try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
  2. 每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。
  3. try{}里有一个return语句,那么紧跟在这个try后的finally{}里代码会在return之前执行,更准确的说,是在return中间执行,看下面例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public int test(){
    int x = 1;
    try{
    return x;
    }finally{
    System.out.print("执行了");
    ++x;
    }
    }
    //执行结果是 :执行了
    //但是 x = 1

3.2.2 抛出异常

  1. throws 抛到上一层(用在方法上)

    1
    2
    3
    4
    5
    public void foo() throws SQLException, IOException,ClassNotFloundException
    {
    //foo内部可以抛出 SQLException, IOException,ClassNotFloundException 类的异常
    //或者他们的子类的异常对象。
    }
  2. throw 抛出异常 (用在方法内部)

    1
    2
    3
    4
    public void save(User user){
    if(user == null)
    throw new IllegalArgumentException("User对象为空");
    }

4. java反射

4.1 反射的概述

反射是框架设计的灵魂

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

以上的总结就是什么是反射
反射就是把java类中的各种成分映射成一个个的Java对象

例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
这里写图片描述

4.2 反射中常用的方法

4.2.1 获得Class对象

  1. Object ——> getClass();

    1
    2
    Student stu1 = new Student();//产生一个Student对象,一个Class对象。  
    Class stuClass1 = stu1.getClass();//获得Class对象
  2. 任何数据类型(包括基本数据类型)都有一个“静态”的class属性

1
Class stuClass2 = Student.class;
  1. 通过Class类的静态方法:forName(String className)(常用)
    1
    Class stuClass3 = Class.forName("fanshe.Student");

4.2.2 获得构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
//1.加载Class对象  
Class clazz = Class.forName("fanshe.Student");

//可传递参数,根据参数的不同,获得的构造方法对象也不同
//2.获取所有公有构造方法
Constructor[] conArray = clazz.getConstructors();
//获得所有构造方法,包括公有和私有
conArray = clazz.getDeclaredConstructors();
//Constructor con = clazz.getConstructors(char.Class);
//如果是私有构造,可通过下面设置,即可调用newInstance
//con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
//调用构造方法
Object obj = con.newInstance();

4.2.3 获得成员属性并设值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//1.获取Class对象  
Class stuClass = Class.forName("fanshe.field.Student");
//2.获取所有公有的属性
//Field[] fieldArray = stuClass.getFields();
//获取所有的属性(包括私有、受保护、默认的)
//Field[] allFieldArray = stuClass.getDeclaredFields();
//获取name属性
Field f = stuClass.getField("name");
//获取构造方法并调用产生一个对象
Object obj = stuClass.getConstructor().newInstance();
//为obj对象的name属性设置值
f.set(obj, "刘德华");
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
//获取私有的属性
f = stuClass.getDeclaredField("phoneNum");
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "18888889999");
System.out.println("验证电话:" + stu.phoneNum);

4.2.4 获得成员方法并调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//获取Class对象  
Class stuClass = Class.forName("fanshe.method.Student");
//获取所有公有方法
//Method[] methodArray = stuClass.getMethods();
//获取所有的方法,包括私有的
//methodArray = stuClass.getDeclaredMethods();
//获取公有的show()方法,它有一个参数,为String类型的
Method m = stuClass.getMethod("show", String.class);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
//调用show 方法,并传递一个参数
m.invoke(obj, "刘德华");
//获取私有的show()方法,它有一个参数为int类型的
m = stuClass.getDeclaredMethod("show", int.class);
m.setAccessible(true);//解除私有限定
//调用并得到返回值
Object result = m.invoke(obj, 20);
System.out.println("返回值:" + result);

4.2.5 获得main 方法

1
2
3
4
5
6
//1.获取Student对象的字节码  
Class clazz = Class.forName("fanshe.main.Student");
//2.获取main方法
Method methodMain = clazz.getMethod("main", String[].class);
//3.调用main方法
methodMain.invoke(null, (Object)new String[]{});

5. java常用的设计模式

5.1 单例模式

单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例

  1. 饿汉模式
    1
    2
    3
    4
    5
    6
    7
    public class Singleton{
    private static Singleton instance = new Singleton(){}
    private Singleton(){}//将构造私有化
    public static Singleton getInstance(){
    return instance;
    }
    }

对于饿汉模式来说,这种写法已经很‘perfect’了,
唯一的缺点就是,由于instance的初始化是在类加载时进行的,
类加载是由ClassLoader来实现的,如果初始化太早,就会造成资源浪费

  1. 懒汉模式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Singleton{
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
    if(instance==null){
    return new Singleton();
    }
    return instance;
    }
    }

这种写法在单线程的时候是没问题的。
但是,当有多个线程一起工作的时候,如果有两个线程同时运行到 if (instance == null),
都判断为null(第一个线程判断为空之后,并没有继续向下执行,当第二个线程判断的时候instance依然为空)
最终两个线程就各自会创建一个实例出来。这样就破环了单例模式 实例的唯一性,

  1. 双重检查模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Singleton{
    //volatile关键字的一个作用是禁止指令重排,
    //把instance声明为volatile之后,对它的写操作就会有一个内存屏障
    //这样,在它的赋值完成之前,就不用会调用读操作。
    private static volatile Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
    if(instance==null){
    sysnchronized(Singleton.class){
    if(instance==null){
    return new Singleton();
    }
    }
    }
    return instance;
    }
    }
  2. 静态内部类实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Singleton {
    private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
    return SingletonHolder.INSTANCE;
    }
    }

它利用了ClassLoader来保证了同步,同时又能让开发者控制类加载的时机。从内部看是一个饿汉式的单例,但是从外部看来,又的确是懒汉式的实现

5.2 工厂模式

5.3 代理模式

5.4 适配器模式

5.5 状态模式

5.6 观察者模式

具体内容请移步:

java23中模式详解

java23中模式例子

-------------本文结束感谢您的阅读-------------