2019金三银四跳槽面试必问题(底层面试题)

代码千万条,注释第一行。
命名不规范,测试泪两行。

  1. 从源码的角度介绍一下String这个类?
    String是用final修饰的,所以它是一个不可变的类,创建String类后,所有对它的更改,都会重新创建一个新的String

看一下关键源码:

public final class String

    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    /** The value is used for character storage. */
    private final char value[];   //final类型char数组
    
    //省略其他代码
}

从以上代码片段中可以看到,String类在开始处就定义了一个final类型的char数组value,也就是说通过String类定义的字符串中的所有字符都是存储在这个final类型的char数组中的

从源码的角度看一下String类对字符串的截取:

public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        
        int subLen = value.length - beginIndex;
        
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        
        //当对原来的字符串进行截取的时候,返回的结果是新建的对象
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

当对字符串从第beginIndex个字符开始进行截取时,返回的结果是重新new出来的对象,所以,对String字符串进行 " 插入 " 和 " 删除 " 操作时会产生大量的临时变量

  1. String,StringBuffer,StringBuilder的区别?
    运行速度即执行速度
    StringBuilder > StringBuffer > String
    String最慢的原因如下:
    String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建,该对象就是不可更改的,但StringBuilder和StringBuffer两者时变量,是可以更改的,所以从执行速度上来看,String最慢
    线程安全
    在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的,如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法带有synchronized关键字,所有是线程安全的
    总结
    String:适用于少量的字符串操作的情况
    StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
    StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

  2. 在使用多线程时,volatile关键字的作用是什么?
    volatile关键字的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
    使用volition关键字增加了实例变量在多个线程间的可见性,但volition不支持原子性。

  • volition和synchronized关键字比较:
    ❶ volition是线程同步间的轻量级实现,所以volition性能肯定比synchronized性能好,volition只能修饰变量
    ❷ 多线程访问volition不会发生阻塞,而synchronized会阻塞
    ❸ volition能保证数据的可见性,而不能保证原子性,而synchronized即可以保证原子性,也可以间接保证可见性
    ❹ volition解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性

volition主要使用的场合是在多个线程中可以感知共享变量被更改了,并且可以获得最新的值使用,多线程读取共享变量可以获取最新值使用

  1. 简要介绍synchronized和lock,以及它们的区别?

从层面上:
synchronized是Java的关键字,在jvm层面上
Lock是一个类

锁的释放:
synchronized:以获取锁的线程执行完同步代码,释放锁,线程发生异常,jvm会让线程释放锁
Lock:在finally中必须释放锁,不然容易造成线程死锁

锁的获取:
synchronized:假设A线程获得锁,B线程等待,如果A线程阻塞,B线程会一直等待
Lock:以如下几个方法获得锁
① lock() 以阻塞的方式获取锁,也就是说,如果获取到了锁,立即返回,如果别的线程持有锁,当前线程等待,直到获取锁后返回

② tryLock() 以非阻塞的方式获取锁,只是尝试性的去获取一下锁,如果获取到锁,立即返回true,否则,立即返回false

③ tryLock(long timeout, TimeUnit unit) 如果获取了锁,立即返回true,否则会等待参数给定的时间单元,在等待的过程中,如果获取到了锁,就返回true,如果等待超时,返回false

④ lockInterruptibly() 如果获取了锁,立即返回,如果没有获取锁,当前线程处于休眠状态,直到获得锁

性能
synchronized:少量同步
Lock:大量同步

  1. 说一种会使用到多线程的业务场景?

场景一:
一个业务逻辑有很多次的循环,每次循环之间没有影响,比如验证1万条url路径是否存在,正常情况要循环1万次,逐个去验证每一条URL,这样效率会很低,假设验证一条需要1分钟,总共就需要1万分钟,这时可以用多线程,将1万条URL分成50等份,开50个线程,每个线程只需验证200条,这样所有的线程执行完远小于1万分钟的

场景二:
需要知道一个任务的执行进度,比如我们常看到的进度条,实现方式可以是在任务中加入一个整形属性变量(这样不同方法可以共享),任务执行一定程度就给变量值加1,另外开一个线程按时间间隔不断去访问这个变量,并反馈给用户

总结:
使用多线程就是为了充分利用cpu的资源,提高程序执行效率,当发现一个业务逻辑执行效率特别低,耗时特别长,就可以考虑使用多线程

  1. 配置线程池的时候,最大线程数的设置取决于什么?

❶ 对于不同性质的任务来说,CPU密集型任务应配置尽可能小的线程,如配置CPU个数 + 1 的线程数,IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,而对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了

❷ 若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置的越大,才能更好的利用CPU

❸ 具体合理线程池值大小,需要结合系统实际情况

估算合理值的公式:
最佳线程数目 = ((线程等待时间 + 线程CPU时间)/ 线程CPU时间)* CPU数目

  1. 从源码的角度介绍一下HashMap?

❶ HashMap的底层基于一个数组来进行数据的存储和取出,继承于Map,通过put和get方法来操作数据的存和取

HashMap中的关键属性:
① initialCapacity:为HashMap的存储的初始化空间的大小,可以通过构造方法来指定其大小,也可以不指定,采用默认大小16

② loadFactor:加载因子,当HashMap的存储容量达到了一定上限之后,如果还需要进行数据的存储,就会利用加载因子对其进行扩容操作,一般而言,扩容大小为现在容量的0.75倍

③ threadshold:扩容阀值,扩容阀值 = HashMap总容量 * 加载因子,当HashMap的容量大于或者等于扩容阀值的时候就会去执行扩容,扩容的容量为当前HashMap总容量的两倍

从源码角度分析常用的put和get方法
① 解析put源码:

    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
            
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
        int i = indexFor(hash, table.length);
        
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
 
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

当我们将key和value添加进入HashMap的时候,首先其会去判断table是否为空,这个table其实是一个数组,当判断这个table为空的时候,它回去调用infalteTable() 方法,解析infalteTable() 方法

private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);
 
        // Android-changed: Replace usage of Math.min() here because this method is
        // called from the <clinit> of runtime, at which point the native libraries
        // needed by Float.* might not be loaded.
        float thresholdFloat = capacity * loadFactor;
        if (thresholdFloat > MAXIMUM_CAPACITY + 1) {
            thresholdFloat = MAXIMUM_CAPACITY + 1;
        }
 
        threshold = (int) thresholdFloat;
        table = new HashMapEntry[capacity];
    }

这个inflateTable方法是在对HashMap进行初始化容量操作,其初始化容量为capacity * loadFacctor,也就是上面说的:初始化容量 * 加载因子

下来HashMap会去判断你存储的key是否为空,如果为空,则调用putForNullKey() 方法来进行空key的操作

接下来会对key进行一次HashCode的计算,然后遍历table数组,判断是否有相同的key,如果发现有相同的key,则将key所携带的新的value替换掉之前旧的value,从而确保key的唯一性,之后进行addEntry() 方法

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
 
        createEntry(hash, key, value, bucketIndex);
    }

在addEntry() 方法中首先会对数组需要存储的大小和阀值进行一次比较,如果发现要存储的已经超过了threshold阀值,那么就会进入resize() 方法对其扩容

void resize(int newCapacity) {
        HashMapEntry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
 
        HashMapEntry[] newTable = new HashMapEntry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

在resize() 方法里面仅仅只是初始化了一个新的更大的table数组,并且把老的数组从新添加进入新的table中

在addEntry() 方法中最后调用了creatEntry() 方法,在creatEntry() 方法中,查看发现如果在bucket桶内发生了hash的碰撞,则将其转化为链表的形式进行存储(在JDK1.8之前是以链表的形式存储的,在JDK1.8中是以红黑树的形式进行存储的

② 解析get源码:

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
 
        return null == entry ? null : entry.getValue();
    }

get() 方法先判断key是否为空,如果为空,则调用相应的getForNullKey() 方法进行处理,不为空调用getEntry() 方法

final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }
 
        int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
        for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

在getEntry() 方法中对key进行了一次hash操作,之后通过这个hash来进行查找,如果发现hash值相等,则通过比较key的值来进行查找,最终找到想要的e将其返回,不然则返回为空

  1. 说一说项目中都如何使用HashMap?(什么情况下使用)
    当存在映射关系时使用HashMap,如现在需要按照员工号来存储大量的员工信息,那么就可以使用HashMap,将员工号作为键,员工对象作为值来存储到HashMap中

  2. ConcurrentHashMap和Hashtable有什么区别?
    它们都可以用来多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间,因为ConcurrentHashMap引入了分割,不论它变得多大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map,换而言之,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map

  3. 说一说有哪些SQL优化的经验?

适当去掉外键约束
有外键约束会影响插入和删除性能,如果程序能够保证数据的完整性,那在设计数据库时就去掉外键

适当的索引
如:对于历史数据不太关注的场景,比如订单,通常来说,几年前的订单数据,很少会需要查询到,最近的订单才是最常用的,那么可以把订单日期作为索引的一个列创建

尽量不要有空判断的语句
因为空判断将导致全表扫描,而不是索引扫描,对于空判断这种情况,可以考虑对这个列创建数据库默认值

尽量不要使用不等于条件
因为,这会导致全表扫描对于不等于这种情况,考虑改为范围查询解决

尽量不要使用or条件
因为,会导致全表扫描,可以改为分别查询,然后union all

尽量不要使用左右模糊查询
因为,会导致全表扫描,对于左右模糊查询的情况,改为右侧模糊查询,这样是可以索引查找的

尽量避免一次性返回大数据量
可以考虑分页返回

  1. 使用到索引的类型都有哪些?

普通索引
普通索引是mysql中的基本索引类型,允许在定义索引的列中插入重复值和空值,它的唯一任务是加快对数据的访问速度

CREATE INDEX 索引名 ON 表名(列名)

删除索引:

DROP INDEX 索引名 ON 表名

唯一索引
唯一索引不允许两行具有相同的索引值

CREATE UNIQUE INDEX 索引名 ON 表名(列名)

全文索引
全文索引的作用是在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值,主要用于在大量文本文字中搜索字符串

CREATE FULLTEXT INDEX 索引名 ON 表名(列名)

主键索引
在数据库关系图中为表定义一个主键将自动创建主键索引,主键索引是唯一索引的特殊类型

复合索引
在创建索引时,并不是只能对其中一列创建索引,与创建主键一样,可以将多个列合作为索引

空间索引
空间索引是对空间数据类型的列建立的索引

  1. 如何判断一条SQL索引是否生效?
    使用explain命令
    explain显示了MySQL如何使用索引来处理select语句以及连接表,可以帮助选择更好的索引,使用方法,在select语句前加上explain就可以了,里面的key列就是实际上使用的索引
    代码如下:
explain select * from 表名
  1. 如何实现一个ID生成器
    生成ID生成器的方案:https://blog.csdn.net/hero272285642/article/details/79168601
    生成ID生成器的代码实现:https://blog.csdn.net/fay_maplemei/article/details/79925019

  2. bigDecmail通常用于修饰什么样的数据?
    修饰关于的数据
    注意:在使用两个BigDecmail类型相乘是使用如下代码:

        int count = 2;
		double price = 1.8;
		BigDecimal d = new BigDecimal(Double.toString(price));
		BigDecimal h = new BigDecimal(count);
		BigDecimal c = d.multiply(h);
  1. BigDecmail和Double有什么区别?

❶ 对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类,而且使用BigDecimal类也可以进行大数的操作

❷ 两个BigDecimal值不能使用 " +,-,*,/ " 进行加减乘除,要使用 " add,substract,multiply,divide "

❸ 两个BigDecimal值比较使用compareTo方法,比较结果有-1,0,1,分别表示小于,等于,大于,对于0,可以使用BigDecimal.ZERO表示

❹ 提供(相对)精确的除法运算,当发生除不尽的情况时,有scale参数指定精度,以后的数字四舍五入

  1. 简要介绍一下Spring的六大模块?

Spring Core
Core模块是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能,Spring的所有功能都是借助IOC实现的(IOC基本概念:https://blog.csdn.net/w1316022737/article/details/85255886)

Context
Context模块提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源,相当于资源注入,提供了对国际化的支持

ORM
Spring的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支持常用的Hibernate,ibtas,jdao等框架的支持,Spring本身并不对ORM进行实现,仅对常见的ORM框架进行封装,并对其进行管理

DAO
Spring提供对JDBC的支持,对JDBC进行封装,允许JDBC使用Spring资源,并能统一管理JDBC事务

WEB
WEB模块提供对常见框架如:Struts1,WEBWORK(Struts2),JSF的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器

MVC
WEB MVC模块为Spring提供了一套轻量级的MVC实现,在Spring的开发中,即可以用Struts也可以用Spring自己的MVC框架,使用更加的灵活

AOP
AOP模块是Spring的AOP库,提供了AOP(拦截器)并提供常用的拦截器,供用户自定义和配置

  1. SpringIOC基于标签是怎样注入的,基于注解呢?
    基于标签的注入:https://www.cnblogs.com/shin6758/p/5248165.html
    基于注解的注入:https://www.cnblogs.com/cdf-opensource-007/p/6441296.html

  2. SpringAOP的底层实现原理是什么?

AspectJ的底层技术
AspectJ的底层技术是静态代理,即用一种AspectJ支持的特定语言编写切面,通过一个命令来编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,编译时增强的性能比运行时增强性能好

Spring AOP
Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类

JDK动态代理只能为接口创建动态代理实例,而不能对类创建动态代理,需要获得被目标类的接口信息(应用JAVA的反射技术),生成一个实现了代理接口的动态代理类(字节码),在通过反射机制获得动态代理类的构造函数,利用构造函数生成动态代理类的实例对象,在调用具体方法前调用invokeHandler方法来处理

CGLib动态代理需要依赖asm包,把被代理对象类的class文件加载进来,修改其字节码生成子类

  1. 说一说SpringBean的生命周期?
    https://www.cnblogs.com/jing-daye/p/5469068.html

  2. 讲一讲对Spring事务的理解?

Spring事务的本质其实就是数据库对事务的支持,如果没有数据库的事务支持,Spring是无法提供事务功能的,对于纯JDBC操作数据库,想要用到事务,按以下步骤:

获取连接  Connection  con = DriverManager.getConnection();
开启事务  con.setAutoCommit(true/false);
执行CRUD
提交事务/回滚事务  con.commit() / con.rollback();
关闭连接  con.close();

使用Spring的事务管理功能后,我们不再写开启事务和提交事务,而是由Spring自动完成

配置文件开启注解驱动,在相关的类和方法上通过注解@Transaction标识

Spring在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启提交事务,异常回滚事务)

  1. 举一个事务会失效的场景?
    ❶ 数据库引擎:在MySQL数据库中只有是InnoDB引擎的时候事务才能生效
    ❷ 在MVC中,controler和service要分开扫描,因为加载controler的时候同时加载了其它带有事务的service模块就会导致事务失效

  2. 说一说事务并发会引起哪些情况?
    脏读:
    对于两个事务A和B,A读取了已经被B更新但还没有被提交的字段,如B进行回滚,A读取的内容就是临时且无效的
    不可重复读:
    对于两个事务A和B,A读取了一个字段,然后B更新了该字段,A再次读取同一个字段,值就不同了
    幻读:
    对于两个事务A和B,A从表中读取了一个字段,然后B在表中插入了一些新的行,如果A再次读取同一个表,就会多几行数据

  3. 介绍一下事务有哪几种隔离级别?

@Transaction(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读和不可重复读)基本上不使用

@Transaction(isolation = Isolation.READ_COMMITTED)
读取已提交数据(会出现不可重复读和幻读)

@Transaction(isolation = Isolation.REPEATABLE_READ)
可重复读(会出现幻读)

@Transaction(isolation = Isolation.SERIALIZABLE)
串行化

MySQL:默认为REPEATABLE_READ级别

  1. 介绍一下事务有哪几种传播属性?

@Transactional(propagation = Propagation.REQUIRED)
如果有事务,那么加入事务,没有的话新建一个(默认情况下)

@Transactional(propagation = Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务

@Transactional(propagation = Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行原来的事务

@Transactional(propagation = Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常

@Transactional(propagation = Propagation.NEVER)
必须在一个没有事务中执行,否则抛出异常

@Transactional(propagation = Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务,如果其他bean没有声明事务,那就不用事务

  1. JDK7与JDK8的区别有哪些,使用过哪些JDK8的特性?
    JDK8新特性: https://blog.csdn.net/w1316022737/article/details/86644451

  2. MQ数据丢失时都用过哪些处理方式?
    这块来说一说RabbitMQ丢失数据时的处理方式,其他的MQ本博主还没有深层次的进行研究过

❶ 生产者丢数据
RabbitMQ提供transaction和confirm模式来确保生产者不丢消息

transaction机制就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit()),缺点就是吞吐量下降了

使用confirm模式,一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID,一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果RabbitMQ没能处理该消息,则会发送一个Nack消息给你,可以进行重试操作

❷ 消息队列丢数据
一般是开启持久化磁盘的配置,这个持久化配置可以和confirm机制配合使用,可以在消息持久化磁盘后,再给生产者发送一个Ack信号,这样,如果消息持久化磁盘之前,RabbitMQ阵亡了,那么生产者收不到Ack信号,生成者会自动重新发送(如何持久化:将queue的持久化标识durable设置为true,发送消息的时候将deliveryMode设置为2,这样设置以后,RabbitMQ就算挂了,重启后也能恢复数据)

❸ 消费者丢数据
启动手动确认模式可以解决

① 自动确认模式:消费者挂掉,待ack的消息回归到队列中,消费者抛出异常,消息会不断的被重发,直到处理成功,不会丢失消息,即便服务挂掉,没有处理完成的消息会重回队列,但是异常会让消息不断重试

② 手动确认模式:如果消费者来不及处理就死掉时,没有响应ack时会重复发送一条消息给其他消费者,如果监听程序处理异常了,且未对异常进行捕获,会一直重复接收消息,然后一直抛异常,如果对异常进行了捕获,但是没有在finally里ack也会一直重复发送消息(重试机制)

③ 不确定模式:acknowledge = “none” 不使用确认机制,只要消息发送完成会立即在队列中移除,无论客户端异常还是断开,只要发送完就移除,不会重发

  1. 讲解一下Activiti工作流?
    https://blog.csdn.net/cs_hnu_scw/article/details/79059965
    https://blog.csdn.net/clk_esunny/article/details/80336073

  2. SpringCloud中使用过哪些注解,有什么作用?
    https://blog.csdn.net/qq_36522306/article/details/80246760

  3. 集群系统如何保证或验证两台服务器接口调用时事务是否生效?
    待解决

  4. 集群系统登录时如何确保对应服务器获取到了登录信息?
    https://www.cnblogs.com/yangwenxin/p/6704670.html

  5. 高并发下Redis如何保持数据一致性?
    https://blog.csdn.net/greensomnuss/article/details/81625201

  6. 高并发下Redis热数据异常失效时该怎么处理?
    https://www.jianshu.com/p/d5a3668d4dad

  7. SpringBoot中如何实现定时任务?
    https://blog.csdn.net/u013456370/article/details/79411952

  8. MyBatis中如何实现批量更新数据?
    https://blog.csdn.net/xu1916659422/article/details/77971696/

  9. 有两个定时任务,当第二个影响到第一个时,怎么处理?
    使用多线程进行延迟处理

  10. 项目开发中用到过哪些设计模式?了解哪些设计模式?

设计模式分为三大类:

创建型模式共五种: 工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式共七种: 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式共十一种: 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

单例设计模式
实现方式:饿汉式(静态代码块和静态方法),饱汉式,双重检索
应用:数据库连接池,因为数据库连接池是一种数据库资源,使用数据库连接池主要是为了节省打开或者关闭数据库连接所造成的效率损耗
多线程中线程池的设计一般也采用单例设计模式
计数器一般也采用单例设计模式

享元设计模式
应用:在一个系统中,如果有多个相同的对象,那么只共享一份就好了,不必每个都去实例化一个对象,可以节省空间

代理设计模式
应用:机房通过代理模式同步到其他机房,来避免机房宕机

  1. tomcat的三种运行模式是什么?
    分别是:bio,nio和apr
    详解:https://blog.csdn.net/lijunchao1/article/details/76175559

  2. 说一说git的常用命令?

git add 文件名 ##是添加文件的命令,即把文件添加到仓库里面

git add . ##是添加所有文件的命令,上面的命令只是根据具体选择来添加的

git status ##是查看仓库当前的状态或者结果的命令

git commit -m “备注说明” ##是提交文件的命令,

git push ##是推出送文件到github上的命令,这是提交代码最后一步。

git diff 文件名 ##是查看文件被修改过的状态,即改文件修改了什么

git branch dev ##是创建dev分支

git checkout dev ##是切换分支,把分支切换到dev上

git branch ##是查看分支

git checkout -b dev ##是创建分支,然后切换到dev分支,即是上面两个命令的合并

git pull ##拉取
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值