ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

面试题(更新于2021.05)

2021-05-11 18:59:50  阅读:179  来源: 互联网

标签:面试题 return String 2021.05 Spring 更新 线程 使用 方法


目录

JavaSE基础

1.谈谈对OOP的理解

2.JDK 和 JRE 的区别?

3.重写和重载

4.==与equals()的区别?

5.try、catch、finally的执行顺序

6.final 在 java 中有什么作用?

7.基本数据类型有哪些?

8.String底层原理

9.String str="i" 与 String str=new String(“i”) 一样吗?

10.String、StringBuffer、StringBuilder的区别?

11. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

12.如何将字符串反转?

13.String 类的常用方法都有那些?(待补充)

14.抽象类必须要有抽象方法吗?

15.普通类和抽象类有哪些区别?

16.抽象类能使用 final 修饰吗?

17.接口和抽象类

18.java 中 IO 流分为几种?

19.BIO、NIO、AIO 有什么区别?

20.Files的常用方法都有哪些?

集合面试题(常见)

1.集合有哪些?

2.List、Set、Map 的区别?

3.数组和 list 之间的转换

4.ArrayList扩容机制

5.ArrayList 、Vector和LinkedList 的区别?

6.Array 和 ArrayList 有何区别?

7.在 Queue 中 poll()和 remove()有什么区别?

8.如果要保证ArraList线程安全,有几种方式?

9.HashMap与HashTable的区别?

10.HashSet 的实现原理?

11.hashmap的底层原理,会不会产生哈希冲突?

12.HashMap遍历?

13.Set 遍历方法?

14.HashSet和TreeSet的区别?

15.底层数据结构

16.迭代器 Iterator 是什么?

17.Iterator 怎么使用?有什么特点?

18.Iterator 和 ListIterator 有什么区别?

19.怎么确保一个集合不能被修改?

线程面试题(必问)

1.创建线程有哪些方法(4种)?

2.线程的生命周期

3.Runnable和Callable的区别?

4.start()和run()方法有什么区别?

5.线程池有哪些参数

6.线程池有几种(5种)?拒绝策略有几种(4种)?阻塞队列有几种(3种)?

7.死锁

9.volatile与synchronized有什么区别?

10.wait()和sleep()的区别?

11.乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

12.什么是可重入锁(ReentrantLock)?

数据库面试题(必问)

1.索引

2.两种存储引擎

3.数据库优化

4.数据库事务

5.存储过程

7.内连接、左外连接和右外连接的区别?

8.数据库的三大范式?

9.防止sql注入的方法?

spring面试题

1.你对spring的理解?与springmvc和springboot的区别?(经常被问到)

2.spring 的优缺点

3.spring的核心技术(IOC、DI、AOP)

4.Bean的作用域

5.Bean的生命周期

6.Spring 支持的事务管理类型

springmvc面试题

1.什么是Spring MVC ?简单介绍下你对springMVC的理解?

2.SpringMVC的流程?

3.Springmvc的优点

4.Spring MVC的主要组件?

6.SpringMVC怎么样设定重定向和转发的?

7.GET和POST区别?

8.cookie和session的区别?

9.spring mvc中的mvc指的是什么?

10.spring MVC中拦截器与过滤器的用处?加载顺序?

springboot面试题

1.为什么要用springboot?

2.Spring Boot有哪些优点?

3.Spring Boot有哪些核心配置文件

4.Spring Boot 的配置文件有哪几种格式

5.Spring Boot 实现热部署有哪几种方式

6.运行SpringBoot有几种方式?

7.springboot四大特性

8.什么是Swagger?你用Spring Boot实现了吗?

9.什么是FreeMarker模板?

10.springboot常用注解?

mybatis面试题

1.MyBatis 的好处是什么,为什么用mybatis等?(多次被问到)

2.mybatis与hibernate的区别在哪里?(多次被问到)

3.#{}和${}的区别是什么?

4.Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?

5.Mybatis是如何进行分页的?分页插件的原理是什么?

6.Mybatis执行批量插入,能返回数据库主键列表吗?

7.mybatis传递参数的方式(4种)?

8.mybatis中模糊查询(mysql)

事务面试题

1.事务的7种传播级别

2.数据隔离级别

3.Java有几种类型的事务?

设计模式

1.设计模式分类

2.设计模式的优点

3.设计模式的六大原则及其含义

4.常见的单例模式以及各种实现方式的优缺点,哪一种最好,手写常见的单例模式

5.设计模式在实际场景的应用(待补充)

6.Spring中用到了哪些设计模式

7.MyBatis中用到了哪些设计模式

8.动态代理

消息队列

1.为什么使用消息队列?(解耦、异步、削峰)

2.使用了消息队列会有什么缺点

3.消息队列如何选型?

4.消息队列由哪些角色组成?

5.消息队列有哪些使用场景?

数据结构

1.排序算法有哪些?

2.以下算法的时间复杂度

3.数组和链表的区别

JVM面试题

1.内存模型以及分区,需要详细到每个区放什么。

2.简述 java 垃圾回收机制?

3.java 类加载过程?

servlet面试题

1.Servlet的生命周期?

2.forward() 与redirect()的区别

3.四种会话跟踪技术

4.Request对象的主要方法

5.Servlet执行时一般实现哪几个方法?

6.GenericServlet和HttpServlet有什么区别?

7.九大内置对象

Linux常用命令(被问到比较少)


JavaSE基础

1.谈谈对OOP的理解

  • 首先面向对象是一种编程思想

  • 万物皆对象。我的电脑是一个对象,我的手机是一个对象等等,OOP可以理解为使用代码来模拟现实生活

  • 三大特性:封装、继承和多态

(1)封装:就是隐藏类的内部信息,不允许外部程序直接访问,而是通过getter(获取)和setter(设置)方法完成,提高数据的安全性。

(2)继承:父类的基本特征和行为,子类也会有,子类也可以改变这些特征和行为。

(3)多态:就是多个对象调用同一个方法,可能会得到的是不同的结果。


2.JDK 和 JRE 的区别?

  • JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。

  • JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。

具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。


3.重写和重载

  • 方法重载

是指同一个类中的多个方法具有相同的名字,参数的数量、类型、顺序不同;与返回值无关

  • 方法重写

    子类继承父类,方法名、参数列表相同,方法体不同 子类中不能重写父类中的final方法 子类中必须重写父类中的abstract方法


4.==与equals()的区别?

  • ==号在比较基本数据类型时比较的是值,而在比较两个对象时比较的是两个对象的地址值

  • equals()方法存在于Object类中,其底层依赖的是==号,如果类没有重写Object中的equals()方法的类中,则调用equals()方法其实和使用==号的效果一样

== 解读

对于基本类型和引用类型 == 的作用效果是不同的,如下所示:

  • 基本类型:比较的是值是否相同;

  • 引用类型:比较的是引用是否相同;

代码示例:

String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true

代码解读:因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。

equals 解读

equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。

首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:

class Cat {
    public Cat(String name) {
        this.name = name;
    }
​
    private String name;
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
}
​
Cat c1 = new Cat("王磊");
Cat c2 = new Cat("王磊");
System.out.println(c1.equals(c2)); // false

输出结果出乎我们的意料,竟然是 false?这是怎么回事,看了 equals 源码就知道了,源码如下:

public boolean equals(Object obj) {
        return (this == obj);
}

原来 equals 本质上就是 ==

那问题来了,两个相同值的 String 对象,为什么返回的是 true?代码如下:

String s1 = new String("老王");
String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true

同样的,当我们进入 String 的 equals 方法,找到了答案,代码如下:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。

总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。


5.try、catch、finally的执行顺序

结论:

  • 不管有木有出现异常,finally块中代码都会执行;

  • 当try和catch中有return时,finally仍然会执行;

  • finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;

  • finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

举例:
情况1:try{} catch(){}finally{} return;
            显然程序按顺序执行。
情况2:try{ return; }catch(){} finally{} return;
          程序执行try块中return之前(包括return语句中的表达式运算)代码;
         再执行finally块,最后执行try中return;
         finally块之后的语句return,因为程序在try中已经return所以不再执行。
情况3:try{ } catch(){return;} finally{} return;
         程序先执行try,如果遇到异常执行catch块,
         有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,
                     最后执行catch块中return. finally之后也就是4处的代码不再执行。
         无异常:执行完try再finally再return.
情况4:try{ return; }catch(){} finally{return;}
          程序执行try块中return之前(包括return语句中的表达式运算)代码;
          再执行finally块,因为finally块中有return所以提前退出。
情况5:try{} catch(){return;}finally{return;}
          程序执行catch块中return之前(包括return语句中的表达式运算)代码;
          再执行finally块,因为finally块中有return所以提前退出。
情况6:try{ return;}catch(){return;} finally{return;}
          程序执行try块中return之前(包括return语句中的表达式运算)代码;
          有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
                       则再执行finally块,因为finally块中有return所以提前退出。
          无异常:则再执行finally块,因为finally块中有return所以提前退出。

最终结论:任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
                  如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
                  编译器把finally中的return实现为一个warning。

测试代码:

public class FinallyTest  
{
    public static void main(String[] args) {
	System.out.println(new FinallyTest().test());;
    }

    static int test(){
        int x = 1;
	try{
	    x++;
	    return x;
	}finally{
	    ++x;
	}
    }
}
结果是2。

分析:

在try语句中,在执行return语句时,要返回的结果已经准备好了,就在此时,程序转到finally执行了。 在转去之前,try中先把要返回的结果存放到不同于x的局部变量中去,执行完finally之后,在从中取出返回结果, 因此,即使finally中对变量x进行了改变,但是不会影响返回结果。 它应该使用栈保存返回值。


6.final 在 java 中有什么作用?

  • final 修饰的类叫最终类,该类不能被继承。

  • final 修饰的方法不能被重写。

  • final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。


7.基本数据类型有哪些?

  • 整数类型:byte、short、int、long

  • 浮点类型:float、double

  • 字符类型:char

  • 布尔类型:boolean


8.String底层原理

String的底层使用的是char数组。这个char数组和String这个类都是final修饰的,所以不可变。


9.String str="i" 与 String str=new String(“i”) 一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。


10.String、StringBuffer、StringBuilder的区别?

  • String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

  • StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer


11. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

不对,两个对象的 hashCode()相同,equals()不一定 true。

代码示例:

String str1 = "通话";
String str2 = "重地";
System.out.println(String.format("str1:%d | str2:%d",  str1.hashCode(),str2.hashCode()));
System.out.println(str1.equals(str2));

执行的结果:

str1:1179395 | str2:1179395

false

代码解读:很显然“通话”和“重地”的 hashCode() 相同,然而 equals() 则为 false,因为在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。


12.如何将字符串反转?

使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

示例代码:

// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba

13.String 类的常用方法都有那些?(待补充)

  • indexOf():返回指定字符的索引。

  • charAt():返回指定索引处的字符。

  • replace():字符串替换。

  • trim():去除字符串两端空白。

  • split():分割字符串,返回一个分割后的字符串数组。

  • getBytes():返回字符串的 byte 类型数组。

  • length():返回字符串长度。

  • toLowerCase():将字符串转成小写字母。

  • toUpperCase():将字符串转成大写字符。

  • substring():截取字符串。

  • equals():字符串比较。


14.抽象类必须要有抽象方法吗?

不需要,抽象类不一定非要有抽象方法。

示例代码:

abstract class Cat {
    public static void sayHi() {
        System.out.println("hi~");
    }
}

上面代码,抽象类并没有抽象方法但完全可以正常运行。


15.普通类和抽象类有哪些区别?

  • 普通类不能包含抽象方法,抽象类可以包含抽象方法。

  • 抽象类不能直接实例化,普通类可以直接实例化。


16.抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类,如下图所示,编辑器也会提示错误信息:

final定义抽象类


17.接口和抽象类

Java接口可以理解为一种特殊的类,里面全部是由全局常量公共的抽象方法所组成,是一种规范。

相同点:

  • 都不能被实例化

  • 接口的实现类或抽象类的子类都只有实现了抽象方法后才能实例化。

不同点:

  • 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。

  • 构造函数:抽象类可以有构造函数;接口不能有。

  • main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。

  • 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。

  • 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

  • 接口支持多继承,类只允许单继承


18.java 中 IO 流分为几种?

  • 按功能来分:输入流(input)、输出流(output)。
  • 按类型来分:字节流和字符流。

字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。


19.BIO、NIO、AIO 有什么区别?

  • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。

  • NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。

  • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。


20.Files的常用方法都有哪些?

  • Files.exists():检测文件路径是否存在。

  • Files.createFile():创建文件。

  • Files.createDirectory():创建文件夹。

  • Files.delete():删除一个文件或目录。

  • Files.copy():复制文件。

  • Files.move():移动文件。

  • Files.size():查看文件个数。

  • Files.read():读取文件。

  • Files.write():写入文件。

 

集合面试题(常见)

1.集合有哪些?

Collection接口和Map接口是所有集合框架的父接口:

  • Collection接口的子接口包括:Set接口和List接口

List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等;

Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等;

  • Map接口的实现类主要有:

    HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等


2.List、Set、Map 的区别?


3.数组和 list 之间的转换

  • List转换成为数组:调用ArrayList的 toArray 方法。

  • 数组转换成为List:调用Arrays的 asList 方法。


4.ArrayList扩容机制

ArrayList集合大小如果创建时没有指定,则默认为0,若已经指定集合大小,则初始值为指;

当第一次添加数据的时候,集合大小扩容为10,

第二次及其后续每次按照

int oldCapacity = elementData.length; 
newCapacity = oldCapacity+(oldCapacity>>1)

5.ArrayList 、Vector和LinkedList 的区别?

  • ArrayList:底层基于数组,线程不安全,查询和修改效率高,但是增加和删除效率低,如果需要大量的查询和修改则可以选择ArrayList;

  • LinkedList:底层双向链表结构,线程不安全,查询和修改效率低,但是增加和删除效率高,如果需要大量的添加和删除则可以选择LinkedList;

  • Vector:如果创建Vector时没有指定容量,则默认容量为10,底层基于数组实现,线程是安全的,底层 采用synchronized同步方法进行加锁,很少用


6.Array 和 ArrayList 有何区别?

  • Array可以容纳基本类型和对象,而ArrayList只能容纳对象。

  • Array是指定大小后不可变的,而ArrayList大小是可变的。

  • Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。


7.在 Queue 中 poll()和 remove()有什么区别?

poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。


8.如果要保证ArraList线程安全,有几种方式?

  1. 自己重写一个ArrayList集合类,根据业务一般来说,add/set/remove加锁

  2. 采用synchronized加锁:

    List<Object> list = Collections.synchronizedList(new ArrayList<>());
  3. 采用 ReentrantLock加锁:

    new CopyOnWriteArrayList<>().add("");

9.HashMap与HashTable的区别?

  • HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类;

  • HashMap允许K/V都为null;后者K/V都不允许为null;

  • HashMap没有考虑同步,是线程不安全的,效率上比hashTable要高;Hashtable使用了synchronized关键字,是线程安全的;

  • hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。

  • 默认初始大小和扩容方式不同。HashMap 默认初始大小 16,容量必须是 2 的整数次幂,扩容时将容量变为原来的2倍;Hashtable 默认初始大小 11,扩容时将容量变为原来的 2 倍加 1。

  • 迭代器不同。HashMap 的 Iterator 是 fail-fast 迭代器;Hashtable 还使用了 enumerator 迭代器。


10.HashSet 的实现原理?

  • HashSet底层由HashMap实现

  • HashSet的值存放于HashMap的key上

  • HashMap的value统一为PRESENT


11.hashmap的底层原理,会不会产生哈希冲突?

 


12.HashMap遍历?

两种方式遍历:使用EntrySet遍历;使用KeySet 遍历


13.Set 遍历方法?

  • 迭代遍历

Set<String> set = new HashSet<String>();  
Iterator<String> it = set.iterator();  
while (it.hasNext()) {  
  String str = it.next();  
  System.out.println(str);  
}  
  • for循环遍历

for (String str : set) {  
      System.out.println(str);  
} 

14.HashSet和TreeSet的区别?

HashSet的存储方式:哈希码算法,加入的对象需要实现hashcode()方法;

TreeSet的存储方式:按序存放,想要有序就要实现Comparable接口

不同点:

  • HashSet由哈希表(实际上是一个HashMap实例)支持,不保证set的迭代顺序,并允许使用null元素。
  • 基于HashMap实现,API也是对HashMap的行为进行了封装,可参考HashMap

15.底层数据结构

  • Vector底层是数组。

  • Stack底层是Vector。

  • HashTable底层是链地址法组成的哈希表(即数组+单项链表组成)。

  • ArrayList底层是数组。

  • LinkedList底层是双向链表。

  • HashMap底层与HashTable原理相同,Java 8版本以后如果同一位置哈希冲突大于8则链表变成红黑树。

  • LinkedHashMap底层修改自HashMap,包含一个维护插入顺序的双向链表。

  • TreeMap底层是红黑树。

  • HashSet底层是HashMap。

  • LinkedHashSet底层是LinkedHashMap。

  • TreeSet底层是TreeMap。


16.迭代器 Iterator 是什么?

迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。


17.Iterator 怎么使用?有什么特点?

Java中的Iterator功能比较简单,并且只能单向移动:

  1. 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素;

    注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

  2. 使用next()获得序列中的下一个元素;

  3. 使用hasNext()检查序列中是否还有元素;

  4. 使用remove()将迭代器新返回的元素删除。 

Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。


18.Iterator 和 ListIterator 有什么区别?

  • Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。

  • Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。

  • ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。


19.怎么确保一个集合不能被修改?

使用 JDK中java.util.Collections 类,unmodifiable*** 方法赋值原集合。

当再修改集合时,会报错 java.lang.UnsupportedOperationException。从而确保自己定义的集合不被其他人修改。

public class TestCollectionUnmodify {
 
    static List<String> list = new ArrayList<String>();
    static Set<String> set = new HashSet<String>();
    static Map<String, String> map = new HashMap<String, String>();
    
    static {
        list.add("1");
        list.add("2");
        list.add("3");
        
        set.add("1");
        set.add("2");
        set.add("3");
        
        map.put("1", "1");
        map.put("2", "2");
        map.put("3", "3");
    }
    
    public static void main(String[] args) {
        list = Collections.unmodifiableList(list);
        set = Collections.unmodifiableSet(set);
        map = Collections.unmodifiableMap(map);
        listModify();
        setModify();
        mapModify();
    }
    
    public static void listModify() {
        list.add("4");
    }
    
    public static void setModify() {
        set.add("4");
    }
    
    public static void mapModify() {
        map.put("3", "4");
    }
}

 

线程面试题(必问)

1.创建线程有哪些方法(4种)?

  • 继承Thread类,重写run方法(其实Thread类本身也实现了Runnable接口)

  • 实现Runnable接口,重写run方法

  • 实现Callable接口,重写call方法(有返回值)

  • 使用线程池(有返回值)


2.线程的生命周期

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。

  • 新建:就是刚使用new方法,new出来的线程;

  • 就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;

  • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

  • 阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

  • 销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

完整的生命周期图如下:

 


3.Runnable和Callable的区别?

  • 相同点:两者都需要调用Thread.start启动线程;

  • 不同点

    Callable接口的call()方法有返回值;而实现Runnable接口的run()方法没有返回值;

    Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;


4.start()和run()方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法;

run( )方法是一个普通方法,当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动。

  1. start()方法功能介绍

    start()方法来启动线程,真正实现了多线程运行。

    start方法的作用就是将线程由创建状态,变为就绪状态。当线程创建成功时,线程处于创建状态,如果你不调用start( )方法,那么线程永远处于创建状态。调用start( )后,才会变为就绪状态,线程才可以被CPU运行。

  2. start()执行时间

    调用start( )方法后,线程的状态是就绪状态,而不是运行状态(关于线程的状态详细。线程要等待CPU调度,不同的

    JVM有不同的调度算法,线程何时被调度是未知的。因此,start()方法的被调用顺序不能决定线程的执行顺序。

注意:

由于在线程的生命周期中,线程的状态由创建到就绪只会发生一次,因此,一个线程只能调用start()方法一次,多次

启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。


5.线程池有哪些参数

  • 最常用的三个参数:

    corePoolSize:核心线程数

    queueCapacity:任务队列容量(阻塞队列)

    maxPoolSize:最大线程数

  • 三个参数的作用:

当线程数小于核心线程数时,创建线程;

当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列;

当线程数大于等于核心线程数,且任务队列已满时

     若线程数小于最大线程数,创建线程;

     若线程数等于最大线程数,抛出异常,拒绝任务


6.线程池有几种(5种)?拒绝策略有几种(4种)?阻塞队列有几种(3种)?

  • 五种线程池:

1. ExecutorService threadPool = null;
2. threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
3. threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
4. threadPool = Executors.newScheduledThreadPool(2);//一个能实现定时、周期性任务的线程池
5. threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
6. threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多   
  • 四种拒绝策略:

1. RejectedExecutionHandler rejected = null;
2. rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
3. rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
4. rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
5. rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
  • 三种阻塞队列:
1. BlockingQueue<Runnable> workQueue = null;
2. workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界
3. workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界
4. workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界

7.死锁

7.1 什么叫死锁?

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们

都将无法再向前推进。举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有

另外一个线程B,按照先锁b再锁a的顺序获得锁。


7.2 产生死锁的原因?

  1. 因为系统资源不足。

  2. 进程运行推进的顺序不合适。

  3. 资源分配不当等。


7.3 死锁产生的4个必要条件?

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。

  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。

  4. 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。


7.4 预防和处理死锁的方法?

(1)尽量不要在释放锁之前竞争其他锁
    一般可以通过细化同步方法来实现,只在真正需要保护共享资源的地方去拿锁,并尽快释放锁,这样可以有效降低  在同步方法里调用其他同步方法的情况
(2)顺序索取锁资源
    如果实在无法避免嵌套索取锁资源,则需要制定一个索取锁资源的策略,先规划好有哪些锁,然后各个线程按照一个顺序去索取,不要出现上面那个例子中不同顺序,这样就会有潜在的死锁问题
(3)尝试定时锁
    在索取锁的时候可以设定一个超时时间,如果超过这个时间还没索取到锁,则不会继续堵塞而是放弃此次任务

解除死锁的方法?

(1)剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
(2)撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

如何检测死锁?

(1)利用Java自带工具JConsole
(2)Java线程死锁查看分析方法

8.volatile底层是怎么实现的?

当一个变量定义为volatile后,它将具备两种特性:1. 可见性,2. 禁止指令重排序。

可见性:编译器为了加快程序运行速度,对一些变量的写操作会现在寄存器或CPU缓存上进行,最后写入内存。而在这个过程中,变量的新值对其它线程是不可见的。当对volatile标记的变量进行修改时,先当前处理器缓存行的数据写回到系统内存,然后这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。
处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。如果一个正在共享的状态的地址被嗅探到其他处理器打算写内存地址,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。

9.volatile与synchronized有什么区别?

  • volatile仅能使用在变量上,synchronized则可以使用在方法、类、同步代码块等等。

  • volatile只能保证可见性和有序性,不能保证原子性。而synchronized都可以保证。

  • volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.


10.wait()和sleep()的区别?

(1)sleep()不释放锁,wait()释放锁

(2)sleep()在Thread类中声明的,wait()在Object类中声明

(3)sleep()是静态方法,是Thread.sleep(); wait()是非静态方法,必须由“同步锁”对象调用

(4)sleep()方法导致当前线程进入阻塞状态后,当时间到或interrupt()醒来;wait()方法导致当前线程进入阻塞状态后,由notify或notifyAll()


11.乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。

乐观锁的实现方式:

使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。

java 中的 Compare and Swap 即 CAS ,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置 V 的值与预期原值 A 相匹配,那么处理器会自动将该位置值更新为新值 B。否则处理器不做任何操作。


12.什么是可重入锁(ReentrantLock)?

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。

在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:1. 重入性的实现原理;2. 公平锁和非公平锁。

重入性的实现原理

要想支持重入性,就要解决两个问题:

  1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;

  2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。

ReentrantLock支持两种锁:公平锁和非公平锁

何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。

 

数据库面试题(必问)

1.索引

1.1 使用场景?

 

1.2 索引分类?

MySQL索引包括普通索引、唯一索引、全文索引、单列索引、多列索引、空间索引

1.3 索引作用和优缺点?

索引就一种特殊的查询表,数据库的搜索可以利用它加速对数据的检索。

它很类似与现实生活中书的目录,不需要查询整本书内容就可以找到想要的数据。

索引可以是唯一的,创建索引允许指定单个列或者是多个列。

缺点是它减慢了数据录入的速度,同时也增加了数据库的尺寸大小

1.4 索引不会命中?

1.5主键和唯一索引?

 


2.两种存储引擎

  1. MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。

  2. InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。

主要区别:

  • MyISAM是非事务安全型的,而InnoDB是事务安全型的。

  • MyISAM锁的粒度是表级,而InnoDB支持行级锁定。

  • MyISAM支持全文类型索引,而InnoDB不支持全文索引。

  • MyISAM相对简单,所以在效率上要优于InnoDB,小型应用可以考虑使用MyISAM。

  • MyISAM表是保存成文件的形式,在跨平台的数据转移中使用MyISAM存储会省去不少的麻烦。

  • InnoDB表比MyISAM表更安全,可以在保证数据不会丢失的情况下,切换非事务表到事务表(alter table tablename type=innodb)。


3.数据库优化

(1)根据服务层面:配置mysql性能优化参数;

(2)从系统层面增强mysql的性能:优化数据表结构、字段类型、字段索引、分表,分库、读写分离等等。

(3)从数据库层面增强性能:优化SQL语句,合理使用字段索引。

(4)从代码层面增强性能:使用缓存和NoSQL数据库方式存储,如MongoDB/Memcached/Redis来缓解高并发下数据库查询的压力。

(5)减少数据库操作次数,尽量使用数据库访问驱动的批处理方法。

(6)不常使用的数据迁移备份,避免每次都在海量数据中去检索。

(7)提升数据库服务器硬件配置,或者搭建数据库集群。

(8)编程手段防止SQL注入:使用JDBC PreparedStatement按位插入或查询;正则表达式过滤(非法字符串过滤)

sql语句优化

1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉 及的列上建立索引。 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使 用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0 3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而 进行全表扫描。 4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索 引而进行全表扫描,如: select id from t where num=10 or num=20 可以这样查询: select id from t where num=10 union all select id from t where num=20 5.in 和 not in 也要慎用,否则会导致全表扫描,如: select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3 6.下面的查询也将导致全表扫描: select id from t where name like ‘%abc%’ 7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用 索引而进行全表扫描。如: select id from t where num/2=100 应改为: select id from t where num=100*2

8.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引 而进行全表扫描。如: select id from t where substring(name,1,3)=‘abc’–name以abc开头的id 应改为: select id from t where name like 'abc%' 9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算, 否则系统将可能无法正确使用索引。 10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索 引中的第一个字段作为条件时才能保证系统使用该索引, 否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。 11.不要写一些没有意义的查询,如需要生成一个空表结构: select col1,col2 into #t from t where 1=0 这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(…) 12.很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num) 13.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当 索引列有大量数据重复时,SQL查询可能不会去利用索引, 如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也 对查询效率起不了作用。 14.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降 低了 insert 及 update 的效率, 因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑, 视具体情况而定。 一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的 索引是否有必要。 15.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会 降低查询和连接的性能,并会增加存储开销。 这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字 型而言只需要比较一次就够了。 16.尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节 省存储空间, 其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。 17.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要 返回用不到的任何字段。 18.避免频繁创建和删除临时表,以减少系统表资源的消耗。 19.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当 需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好 使用导出表。 20.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代 替 create table,避免造成大量 log , 以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然 后insert。 21.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。 22.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行, 那么就应该考虑改写。 23.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决 问题,基于集的方法通常更有效。 24.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数 据时。 在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间 允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更 好。 25.尽量避免大事务操作,提高系统并发能力。 26.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合 理。


4.数据库事务

  • 事务:是一系列的数据库操作,是数据库应用的基本逻辑单位。

  • 四大特性:原子性、一致性、隔离性、持久性


5.存储过程

什么是存储过程?用什么来调用?

存储过程是一个预编译的SQL语句,优点是允许模块化的设计,就是说只需创建一次,以后在该程序中就可以调用多次。
​
如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。
​
调用:  1)可以用一个命令对象来调用存储过程。
​
2)可以供外部程序调用,比如:java程序。

存储过程的优缺点?

优点:
​
1)存储过程是预编译过的,执行效率高。
​
2)存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。
​
3)安全性高,执行存储过程需要有一定权限的用户。
​
4)存储过程可以重复使用,可减少数据库开发人员的工作量。
​
缺点:移植性差
//创建存储过程
CREATE PROCEDURE userData(
    IN id INT
)
BEGIN
    SELECT * from userdata WHERE userflag = id;
END;
​
其中IN是传进去的变量;
​
drop procedure userData;//销毁这个存储过程
​
call userData(2) //调用存储过程

6.DDL和DML

DDL (Data Definition Language 数据定义语言)

create table 创建表    
alter table  修改表   
drop table 删除表   
truncate table 删除表中所有行    
create index 创建索引   
drop index  删除索引 
当执行DDL语句时,在每一条语句前后,oracle都将提交当前的事务。如果用户使用insert命令将记录插入到数据库后,执行了一条DDL语句(如create table),此时来自insert命令的数据将被提交到数据库。当DDL语句执行完成时,DDL语句会被自动提交,不能回滚。 

DML (Data Manipulation Language 数据操作语言)

insert 将记录插入到数据库 
update 修改数据库的记录 
delete 删除数据库的记录 
当执行DML命令如果没有提交,将不会被其他会话看到。除非在DML命令之后执行了DDL命令或DCL命令,或用户退出会话,或终止实例,此时系统会自动发出commit命令,使未提交的DML命令提交。

7.内连接、左外连接和右外连接的区别?

  • 左连接:左边有的,右边没有的为null

  • 右连接:左边没有的,右边有的为null

  • 内连接:显示左边右边共有的

  • 全连接:左连接和右连接的并集


8.数据库的三大范式?

  • 第一范式

    数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。(保持数据的原子性)

  • 第二范式

    在满足第一范式的基础上,实体的每个非主键属性完全函数依赖于主键属性(消除部分依赖)

  • 第三范式

    在满足第二范式的基础上,在实体中不存在非主键属性传递函数依赖于主键属性。(表中字段[非主键]不存在对主键的传递依赖)


9.防止sql注入的方法?

1.把应用服务器的数据库权限降至最低

2.(简单又有效的方法)PreparedStatement

采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。
​
使用好处:
​
(1).代码的可读性和可维护性.
​
(2).PreparedStatement尽最大可能提高性能.
​
(3).最重要的一点是极大地提高了安全性.
​
原理:
​
sql注入只对sql语句的准备(编译)过程有破坏作用
​
而PreparedStatement已经准备好了,执行阶段只是把输入串作为数据处理,
​
而不再对sql语句进行解析,准备,因此也就避免了sql注入问题.

3.使用正则表达式过滤传入的参数

4.字符串过滤

5.对进入数据库的特殊字符进行转义处理

6.JSP页面判断代码

7.使用专门的SQL 注入检测工具测试,如sqlmap、SQLninja等

 

spring面试题

1.你对spring的理解?与springmvc和springboot的区别?(经常被问到)

Spring 是个Java企业级应用的开源开发框架。

Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。

Spring 框架目标是简化Java企业级应用开发,它使得开发者只需要关心业务需求。

spring与springmvc的区别:

Spring是IOC和AOP的容器框架,SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring

spring与springboot的区别:

Spring boot是一个在Spring 的基础上搭建的全新的微框架,其目的是简化Spring的搭建和开发过程。

用最简练的语言概括就是:

Spring 是一个“引擎”

Spring MVC 是基于Spring的一个 MVC 框架 ;

Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。


2.spring 的优缺点

Spring 的核心概念是IOC和AOP,这两个核心服务的对象算是bean(POJO),定位是一个轻量级的框架

它具备以下优点:

spring中避免了关键字new造成的耦合问题;

spring本身就是一个工厂,不需要再编写工厂类了;

spring不需要进行明确的引用关系的传递,直接通过配置完成;

所有框架几乎都可以在spring中整合在一起使用;

spring编程=factory设计模式+proxy设计模式

当然,它的缺点也是不少的:

spring基于大量的xml 配置文件,使得我们花了大量的时间放在配置上,拖慢了开发的进度,springboot 问世后,提倡代码优于配置解决了这个问题。

spring 的内容太庞大,随便打断点查看的时候会出现十几二十层代码,阅览性不强,在实际开发的过程中spring的角色更像是胶水一样,充当整合各种技术的角色,同时作为bean的容器。

3.spring的核心技术(IOC、DI、AOP)

  • IOC:控制反转,把创建对象的控制权利由代码转移到spring的配置文件中,对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

    实现原理:工厂模式加反射机制

    两种 IOC 容器:

    • BeanFactory - BeanFactory 就像一个包含 bean 集合的工厂类。它会在客户端要求时实例化 bean。

    • ApplicationContext - ApplicationContext 接口扩展了 BeanFactory 接口。它在 BeanFactory 基础上提供了一些额外的功能。

  • DI:依赖注入,在程序运行期间,由外部容器动态地将依赖对象注入到组件中。简单定义就是当一个对象需要另一个对象时,可以把另一个对象注入到对象中去。

    注入的方式:

    • 构造注入

    • Set注入

    • 接口注入

  • AOP:面向切面编程,系统中有很多各不相干的类的方法,在这众多方法中加入某种系统功能的代码,如加入日志,权限判断等,AOP可以实现横切关注点(如日志,安全,缓存和事务管理)与他们所影响的对象之间的解耦。

    实现原理:动态代理

    Spring AOP and AspectJ AOP 区别:

    • Spring AOP 基于动态代理方式实现;AspectJ 基于静态代理方式实现。

    • Spring AOP 仅支持方法级别的 PointCut;提供了完全的 AOP 支持,它还支持属性级别的 PointCut。

    实现AOP 功能采用的是代理技术,调用代理类,代理类与目标类具有相同的方法声明。

    AOP 在spring中主要表现在两个方面:提供声明式的事务管理 、spring支持用户自定义切面。

    AOP主要包括通知(Advice)切点(PointCut)连接点(JoinPoint)

    springboot 中使用AOP的代码实例:

    <!-- aop -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    @Aspect
    @Component
    public class ControllerInterceptor {
     
        private final Logger logger = LogManager.getLogger(this.getClass());
        @Pointcut("execution(public * com.example.homework.controller..*(..))")
        public void controllerMethodPointcut(){}
     
        @Before("controllerMethodPointcut()") //指定拦截器规则
        public Object interceptor(JoinPoint jp){
            MethodSignature signature = (MethodSignature) jp.getSignature();
            Method method = signature.getMethod(); //获取被拦截的方法
            String methodName = method.getName(); //获取被拦截的方法名
            logger.info("interceptor ***************************");
            logger.info("methodName: "+methodName);
            return null;
        }
    }

4.Bean的作用域

  • singleton:

    Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象。Singleton作用域是Spring中的缺省作用域。

  • prototype:

每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态,而singleton全局只有一个对象。

  • request:

在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。

  • session:

在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。

  • global Session:

在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。


5.Bean的生命周期

Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;

  1. 实例化一个Bean--也就是我们常说的new;

  2. 按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;

  3. 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值

  4. 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);

  5. 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);

  6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;

  7. 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。

  8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、;

    注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。

  9. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法

  10. 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

 

以上10步骤可以作为面试或者笔试的模板,另外我们这里描述的是应用Spring上下文Bean的生命周期,如果应用Spring的工厂也就是BeanFactory的话去掉第5步就Ok了。


6.Spring 支持的事务管理类型

  • 程序化事务管理:在此过程中,在编程的帮助下管理事务。它为您提供极大的灵活性,但维护起来非常困难。

  • 声明式事务管理:在此,事务管理与业务代码分离。仅使用注解或基于 XML 的配置来管理事务。


 

 

springmvc面试题

1.什么是Spring MVC ?简单介绍下你对springMVC的理解?

Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。


2.SpringMVC的流程?

  • 用户发送请求至前端控制器DispatcherServlet;

  • DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;

  • 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;

  • DispatcherServlet 调用 HandlerAdapter处理器适配器;

  • HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);

  • Handler执行完成返回ModelAndView;

  • HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;

  • DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;

  • ViewResolver解析后返回具体View;

  • DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)

  • DispatcherServlet响应用户。


3.Springmvc的优点

  • 可以支持各种视图技术,而不仅仅局限于JSP;

  • 与Spring框架集成(如IoC容器、AOP等);

  • 清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。

  • 支持各种请求资源的映射策略。


4.Spring MVC的主要组件?

  1. 前端控制器 DispatcherServlet(不需要程序员开发)

    作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。

  2. 处理器映射器HandlerMapping(不需要程序员开发)

    作用:根据请求的URL来查找Handler

  3. 处理器适配器HandlerAdapter

    注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。

  4. 处理器Handler(需要程序员开发)

  5. 视图解析器 ViewResolver(不需要程序员开发)

    作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)

  6. 视图View(需要程序员开发jsp)

    View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)


6.SpringMVC怎么样设定重定向和转发的?

  • 转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"

  • 重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"


7.GET和POST区别?

注解配置:@RequestMapping注解里面加上method=RequestMethod.GET

  • url可见性

    get,参数url可见;

    post,url参数不可见

  • 数据传输上

    get,通过拼接url进行传递参数;

    post,通过body体传输参数

  • 缓存性

    get请求是可以缓存的

    post请求不可以缓存

  • 后退页面的反应

    get请求页面后退时,不产生影响

    post请求页面后退时,会重新提交请求

  • 传输数据的大小

    get一般传输数据大小不超过2k-4k(根据浏览器不同,限制不一样,但相差不大)

    post请求传输数据的大小根据php.ini 配置文件设定,也可以无限大

  • 安全性

    这个也是最不好分析的,原则上post肯定要比get安全,毕竟传输参数时url不可见,但也挡不住部分人闲的没事在那抓包玩。安全性个人觉得是没多大区别的,防君子不防小人就是这个道理。对传递的参数进行加密,其实都一样。


8.cookie和session的区别?

  • cookie数据存放在客户的浏览器或内存,session数据放在服务器上

  • cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session

  • session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用cookie

  • 单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的cookie不能3K。

  • 将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在cookie中

  • 由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。

  • 思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。

  • Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。所以,总结一下:Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。


9.spring mvc中的mvc指的是什么?

10.spring MVC中拦截器与过滤器的用处?加载顺序?

 

 

 

springboot面试题

1.为什么要用springboot?

能够简化搭建Spring项目的过程,省去了繁杂的配置过程,对开发者更友好

  • 用来简化Spring应用的初始搭建以及开发过程,使用特定的方式来进行配置

  • 创建独立的Spring引用程序main方法运行

  • 嵌入的tomcat无需部署war文件

  • 简化maven配置

  • 自动配置Spring添加对应的功能starter自动化配置


2.Spring Boot有哪些优点?

  • 能够独立运行:内置了servlet容器

  • 配置简单:搭建项目不再需要繁琐的配置

  • 自动配置:根据当前类路径下的类和jar包自动配置bean

  • 无代码生成和XML配置:在配置过程中没有代码生成,也不需要XML文件,完全通过注解实现

  • 应用监控:提供监控服务,便于做健康检测


3.Spring Boot有哪些核心配置文件

  1. bootstrap(.yml或者.properties):bootstrap是应用程序的父上下文,拥有更高的加载优先级,主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。

  2. application(.yml或者.properties):主要用于Sprint Boot项目的自动化配置。


4.Spring Boot 的配置文件有哪几种格式

.properties和.yml两种格式,主要的区别在于书写方式不同

(1) .properties

app.user.name = javastack

(2) .yml

app:
user:
  name: javastack

5.Spring Boot 实现热部署有哪几种方式

主要有两种方式:

  • Spring Loaded

  • Spring-boot-devtools


6.运行SpringBoot有几种方式?

  • 打包用命令或者放到容器中运行

  • 用Maven或Gradle插件运行

  • 直接执行main方法运行


7.springboot四大特性

  1. Starter添加项目依赖

  2. bean的自动化配置

  3. Spring Boot CLI与Groovy的高效配合

    Spring Boot CLI充分利用了Spring Boot Starter和自动配置的魔力,并添加了一些Groovy的功能。它简化了Spring的开发流程,通过CLI,我们能够运行一个或多个Groovy脚本,并查看它是如何运行的。在应用的运行过程中,CLI能够自动导入Spring类型并解析依赖

  4. Spring Boot Actuator

    完成的主要功能就是为基于Spring Boot的应用添加多个有用的管理端点


8.什么是Swagger?你用Spring Boot实现了吗?

Swagger 广泛用于可视化 API,使用 Swagger UI 为前端开发人员提供在线沙箱。Swagger 是用于生成 RESTful Web 服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与服务器相同的速度更新。当通过 Swagger 正确定义时,消费者可以使用最少量的实现逻辑来理解远程服务并与其进行交互。因此,Swagger消除了调用服务时的猜测。


9.什么是FreeMarker模板?

FreeMarker 是一个基于 Java 的模板引擎,最初专注于使用 MVC 软件架构进行动态网页生成。使用 Freemarker 的主要优点是表示层和业务层的完全分离。程序员可以处理应用程序代码,而设计人员可以处理 html 页面设计。最后使用freemarker 可以将这些结合起来,给出最终的输出页面。


10.springboot常用注解?

  1. 启动注解 @SpringBootApplication

    查看源码可发现,@SpringBootApplication是一个复合注解,包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解

  2. @Controller和@Restcontroller

    @Controller 用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。

    @Restcontroller用来将json/xml数据发送到前台页面,而不是返回视图页面。

    区别:

    • @RestController加在类上面的注解,使得类里面的每个方法都将json/xml返回数据加返回到前台页面中。

    • @Controller加在类上面的注解,使得类里面的每个方法都返回一个试图页面。

    • @Controller和@ResponseBody(加在方法/类上面)一起使用,和@RestController的作用相同。

  3. @RequestMapping

    @RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

  4. @Resource和@Autowired

    @Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。

    区别:

    • @Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配

    • @Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了

    • Spring属于第三方的,J2EE是Java自己的东西,因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。

  5. @PathVariable

    用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。

  6. @RequestParam

    @RequestParam用于将请求参数区数据映射到功能处理方法的参数上

  7. @SessionAttributes

    @SessionAttributes即将值放到session作用域中,写在class上面。  

    @SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外(value 属性值),

    还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中(types 属性值)

  8. @ResponseBody 

    该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。

  9.  

 

 

 

mybatis面试题

1.MyBatis 的好处是什么,为什么用mybatis等?(多次被问到)

  • MyBatis 把 sql 语句从 Java 源程序中独立出来,放在单独的 XML 文件中编写,给程序的维护带来了很大便利。

  • MyBatis 封装了底层 JDBC API 的调用细节,并能自动将结果集转换成 Java Bean 对象,大大简化了 Java 数据库编

    程的重复工作。

  • 因为 MyBatis 需要程序员自己去编写 sql 语句,程序员可以结合数据库自身的特点灵活控制 sql 语句,因此能够实现

    比 Hibernate 等全自动 orm 框架更高的查询效率,能够完成复杂查询。


2.mybatis与hibernate的区别在哪里?(多次被问到)

  • 两者最大的区别

    针对简单逻辑,Hibernate和MyBatis都有相应的代码生成工具,可以生成简单基本的DAO层方法;

    针对高级查询,Mybatis需要手动编写SQL语句,以及ResultMap;而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于业务流程。

  • 开发难度对比

    Hibernate的开发难度要大于Mybatis。主要由于Hibernate比较复杂、庞大,学习周期较长;

    而Mybatis则相对简单一些,并且Mybatis主要依赖于sql的书写,让开发者感觉更熟悉。

  • sql书写比较

    Mybatis的SQL是手动编写的,所以可以按需求指定查询的字段。不过没有自己的日志统计,所以要借助log4j来记录日志;

    Hibernate也可以自己写SQL来指定需要查询的字段,但这样就破坏了Hibernate开发的简洁性。不过Hibernate具有自己的日志统计。

  • 数据库扩展性比较

    Mybatis由于所有SQL都是依赖数据库书写的,所以扩展性,迁移性比较差;

    Hibernate与数据库具体的关联都在XML中,所以HQL对具体是用什么数据库并不是很关心。

  • 缓存机制比较(如果不知道缓存机制,建议不要说)

    相同点:Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。

    不同点:Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。

    MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

    两者比较:因为Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。

    而MyBatis在这一方面,使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用。否则,脏数据的出现会给系统的正常运行带来很大的隐患。


3.#{}和${}的区别是什么?

${} 是Properties文件中的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver;

#{}是sql的参数占位符,Mybatis会将sql中的#{}替换为?号,在sql执行前会使用PreparedStatement的参数设置方法,按序给sql的?号占位符设置参数值,比如ps.setInt(0, parameterValue),#{item.name}的取值方式为使用反射从参数对象中获取item对象的name属性值,相当于param.getItem().getName()。

不同点

#{}是预编译处理,${}是字符串替换。

Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值,可以有效的防止SQL注入,提高系统安全性;

在处理${}时,就是把${}替换成变量的值,一般用于传入数据库对象,例如传入表名。

用法:

select * from user where name = #{name}; 
select * from user where name = '${name}'; //必须加单引号

4.Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?

<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,加上动态sql的9个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。

bind 元素标签可以从 OGNL 表达式中创建一个变量井将其绑定到上下文中,MyBatis中使用mysql的模糊查询字符串拼接(like) 中也涉及到bind的使用。创建一个 bind 元素标签的变量后 ,就可以在下面直接使用,使用 bind 拼接字符串不仅可以避免因更换数据库而修改 SQL,也能预防 SQL 注入。

<select id="getEmpsTestInnerParameter" resultType="com.hand.mybatis.bean.Employee">
       <!-- bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值 -->
       <bind name="bindeName" value="'%'+eName+'%'"/> eName是employee中一个属性值
       SELECT * FROM emp 
       <if test="_parameter!=null">
         where ename like #{bindeName}
       </if>
   </select>

5.Mybatis是如何进行分页的?分页插件的原理是什么?

Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10

mybatis的两种分页方式:RowBounds和PageHelper


6.Mybatis执行批量插入,能返回数据库主键列表吗?

能,JDBC都能,Mybatis当然也能。

  1. 对于支持生成自增主键的数据库:增加 useGenerateKeys和keyProperty ,<insert>标签属性。

  2. 不支持生成自增主键的数据库:使用<selectKey>。


7.mybatis传递参数的方式(4种)?

  • 顺序传递参数

    mapper.java文件:

    public User selectUser(String name, int deptId);

    mapper.xml文件:

    <select id="selectUser" resultType="com.wyj.entity.po.User">
        select * from user where userName = #{0} and deptId = #{1}
    </select>
  • 注解@Param传递参数

    mapper.java文件:

    public User selectUser(@Param("userName") String name, int @Param("deptId") id);

    mapper.xml文件:

    <select id="selectUser" resultType="com.wyj.entity.po.User">
        select * from user where userName = #{userName} and deptId = #{deptId}
    </select>
  • 使用Map集合传递参数

    mapper.java文件:

    public User selectUser(Map<String, Object> params);

    mapper.xml文件:

    <select id="selectUser" parameterType="java.util.Map" resultType="com.wyj.entity.po.User">
        select * from user where userName = #{userName} and deptId = #{deptId}
    </select>
  • 使用JavaBean实体类传递参数

    mapper.java文件:

    public User selectUser(User user);

    mapper.xml文件:

    <select id="selectUser" parameterType="com.wyj.entity.po.User" resultType="com.wyj.entity.po.User">
        select * from user where userName = #{userName} and deptId = #{deptId}
    </select>

8.mybatis中模糊查询(mysql)

<select id="selectLikeTwo" resultType="com.bjpowernode.domain.Student">
 select * from student where name like "%" #{name} "%"
</select>

 

 

 

事务面试题

1.事务的7种传播级别

1) PROPAGATION_REQUIRED ,默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。

2)PROPAGATION_SUPPORTS ,从字面意思就知道,supports,支持,该传播级别的特点是,如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作。应用场景较少。

3)PROPAGATION_MANDATORY , 该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。

4)PROPAGATION_REQUIRES_NEW ,从字面即可知道,new,每次都要一个新事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。 怎么处理整个业务需求呢?就是通过这个PROPAGATION_REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。

5)PROPAGATION_NOT_SUPPORTED ,这个也可以从字面得知,not supported ,不支持,当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。

这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了。用当前级别的事务模板抱起来就可以了。

6)PROPAGATION_NEVER ,该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!这个级别上辈子跟事务有仇。

7)PROPAGATION_NESTED ,字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。


2.数据隔离级别

  • Serializable (可串化读):最严格的级别,事务串行执行,资源消耗最大;

  • REPEATABLE READ(可重复读,MySQL默认隔离级别):保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。

  • READ COMMITTED(已提交读) :大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。

  • Read Uncommitted(未提交读) :保证了读取过程中不会读取到非法数据。



3.Java有几种类型的事务?

Java事务的类型有三种:JDBC事务、JTA(Java Transaction API)事务、容器事务。

 

设计模式

1.设计模式分类

常见的设计模式:

单例模式、工厂模式、建造模式、观察者模式、适配器模式、代理模式、装饰模式.


2.设计模式的优点

  • 设计模式可在多个项目中重用。

  • 设计模式提供了一个帮助定义系统架构的解决方案。

  • 设计模式吸收了软件工程的经验。

  • 设计模式为应用程序的设计提供了透明性。

  • 设计模式是被实践证明切实有效的,由于它们是建立在专家软件开发人员的知识和经验之上的。


3.设计模式的六大原则及其含义

  1. 单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。主要作用实现代码高内聚,低耦合。

  2. 开闭原则:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

  3. 里氏替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。里氏替换原则是实现开闭原则的方式之一

  4. 依赖倒置原则:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

  5. 接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

  6. 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。


4.常见的单例模式以及各种实现方式的优缺点,哪一种最好,手写常见的单例模式

饿汉式

  • 优点:不用加锁可以确保对象的唯一性,线程安全。

  • 缺点:初始化对象会浪费不必要的资源,未实现延迟加载。

懒汉式

  • 优点:实现了延时加载。

  • 缺点:线程不安全,想实现线程安全,得加锁(synchronized),这样会浪费一些不必要的资源。

双重检测锁式Double Check Lock--DCL):

  • 优点:资源利用率高,效率高。

  • 缺点:第一次加载稍慢,由于java处理器允许乱序执行,偶尔会失败。


5.设计模式在实际场景的应用(待补充)

单例:连接数据库,记录日志


6.Spring中用到了哪些设计模式

  1. 工厂模式:spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

  2. 代理模式:Spring的AOP就是代理模式的体现。

  3. 单例模式:比如在创建bean的时候。

  4. 策略模式:spring在实例化对象的时候使用到了。

  5. 工厂方法:Spring中的FactoryBean就是典型的工厂方法模式。

  6. 观察者模式:常用的地方是Listener的实现,spring中ApplicationListener就是观察者的体现。


7.MyBatis中用到了哪些设计模式

  1. Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;

  2. 工厂模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;

  3. 单例模式,例如ErrorContext和LogFactory;

  4. 代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;

  5. 组合模式,例如SqlNode和各个子类ChooseSqlNode等;

  6. 模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;

  7. 适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;

  8. 装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;

  9. 迭代器模式,例如迭代器模式PropertyTokenizer;


8.动态代理

cglib,jdk

 

消息队列

1.为什么使用消息队列?(解耦、异步、削峰)

主要解决应用耦合,异步消息,流量削锋等问题。

可实现高性能,高可用,可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。

  • 解耦

    传统模式:系统间耦合性太强

    中间件模式:

    中间件模式的优点:

    将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。

  • 异步

    传统模式:一些非必要的业务逻辑以同步的方式运行,太耗费时间;

    中间件模式:

    将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快相应速度

  • 削峰

    传统模式:并发量大的时间,所有的请求直接怼到数据库,造成数据库连接异常

    中间件模式:


2.使用了消息队列会有什么缺点

  • 系统可用性会降低

  • 考虑一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等


3.消息队列如何选型?

特性ActiveMQRabbitMQRocketMQkafka
开发语言javaerlangjavascala
单机吞吐量万级万级10万级10万级
时效性ms级us级ms级ms级以内
可用性高(主从架构)高(主从架构)非常高(分布式架构)非常高(分布式架构)
功能特性成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富MQ功能比较完备,扩展性佳只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。

4.消息队列由哪些角色组成?

  • 生产者(Producer):负责产生消息。

  • 消费者(Consumer):负责消费消息

  • 消息代理(Message Broker):负责存储消息和转发消息两件事情。

    转发消息分为推送拉取两种方式:

拉取(Pull),是指 Consumer 主动从 Message Broker 获取消息

推送(Push),是指 Message Broker 主动将 Consumer 感兴趣的消息推送给 Consumer 。


5.消息队列有哪些使用场景?

一般来说,有下面使用场景:

  • 应用解耦

  • 异步处理

  • 流量削峰

  • 消息通讯

  • 日志处理

其中,应用解耦、异步处理是比较核心的

 

数据结构

1.排序算法有哪些?

插入排序,冒泡排序,选择排序,快速排序,堆排序,归并排序,基数排序,希尔排序等。


2.以下算法的时间复杂度

冒泡排序法 O(n^2)

插入排序法 O(n^2)

堆排序法 O(nlog2 n)

二叉树排序法 最差O(n2)平均O(n*log2n)

快速排序法 最差O(n2)平均O(n*log2n)

希尔排序法 O(nlog n) 不稳定


3.数组和链表的区别

  • 从逻辑结构

    数组必须实现定于固定的长度,不能适应数据动态增减的情况,即数组的大小一旦定义就不能改变。当数据增加是,可能超过原先定义的元素的个数;当数据减少时,造成内存浪费;

    链表动态进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。

  • 从内存存储

    数组从栈中分配空间(用new则在堆上创建),对程序员方便快速,但是自由度小;

    链表从堆中分配空间,自由度大但是申请管理比较麻烦

  • 从访问方式

    数组在内存中是连续的存储,因此可以利用下标索引进行访问;

    链表是链式存储结构,在访问元素时候只能够通过线性方式由前到后顺序的访问,所以访问效率比数组要低。


4.解决哈希冲突的方法

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。

  • 线性探测法

  • 平方探测法

  • 伪随机序列法

  • 拉链法

 

 

JVM面试题

1.内存模型以及分区,需要详细到每个区放什么。

  • JVM 分为堆区和栈区,还有方法区,初始化的对象放在堆里面,引用放在栈里面, class 类信息常量池(static 常量和 static 变量)等放在方法区 new:  方法区:主要是存储类信息,常量池(static 常量和 static 变量),编译后的代码(字节码)等数据  堆:初始化的对象,成员变量 (那种非 static 的变量),所有的对象实例和数组都要在堆上分配

  • 栈:栈的结构是栈帧组成的,调用一个方法就压入一帧,帧上面存储局部变量表,操作数栈,方法出口等信息,局部变量表存放的是 8 大基础类型加上一个应用类型,所以还是一个指向地址的指针  本地方法栈:主要为 Native 方法服务  程序计数器:记录当前线程执行的行号


2.简述 java 垃圾回收机制?

在 java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚

拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。


3.java 类加载过程?

  • 加载

加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:

  1. 通过一个类的全限定名获取该类的二进制流。

  2. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。

  3. 在内存中生成该类的 Class 对象,作为该类的数据访问入口。

  • 验证

验证的目的是为了确保 Class 文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成

以下四钟验证:

  1. 文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.

  2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。

  3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。

  4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。

  • 准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进

行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象

一起分配在 Java 堆中。

public static int value=123;*//在准备阶段 value 初始值为 0 。在初始化阶段才会变 *

为 123
  • 解析

该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

  • 初始化

初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。

到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。

 

 

 

servlet面试题

1.Servlet的生命周期?

加载和实例化、初始化、处理请求以及服务结束

Servlet被服务器实例化后,容器运行其init方法,

请求到达时运行其service方法,

service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,

当服务器决定将实例销毁的时候调用其destroy方法


2.forward() 与redirect()的区别

forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,其实客户端浏览器只发了一次请求,所以它的地址栏中还是原来的地址,session,request参数都可以获取。

redirect就是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,相当于客户端浏览器发送了两次请求。


3.四种会话跟踪技术

1)page否是代表与一个页面相关的对象和属性。一个页面由一个编译好的 Java servlet 类(可以带有任何的 include 指令,但是没有 include 动作)表示。这既包括 servlet 又包括被编译成 servlet 的 JSP 页面

2)request是是代表与 Web 客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件(由于 forward 指令和 include 动作的关系)

3)session是是代表与用于某个 Web 客户机的一个用户体验相关的对象和属性。一个 Web 会话可以也经常会跨越多个客户机请求

4)application是是代表与整个 Web 应用程序相关的对象和属性。这实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域


4.Request对象的主要方法

  • setAttribute(String name,Object):设置名字为name的request的参数值

  • getAttribute(String name):返回由name指定的属性值

  • getAttributeNames():返回request对象所有属性的名字集合,结果是一个枚举的实例

  • getCookies():返回客户端的所有Cookie对象,结果是一个Cookie数组

  • getCharacterEncoding():返回请求中的字符编码方式

  • getContentLength():返回请求的Body的长度

  • getHeader(String name):获得HTTP协议定义的文件头信息

  • getHeaders(String name):返回指定名字的request Header的所有值,结果是一个枚举的实例

  • getHeaderNames():返回所以request Header的名字,结果是一个枚举的实例

  • getInputStream():返回请求的输入流,用于获得请求中的数据

  • getMethod():获得客户端向服务器端传送数据的方法

  • getParameter(String name):获得客户端传送给服务器端的有name指定的参数值

  • getParameterNames():获得客户端传送给服务器端的所有参数的名字,结果是一个枚举的实例

  • getParameterValues(String name):获得有name指定的参数的所有值

  • getProtocol():获取客户端向服务器端传送数据所依据的协议名称

  • getQueryString():获得查询字符串

  • getRequestURI():获取发出请求字符串的客户端地址

  • getRemoteAddr():获取客户端的IP地址

  • getRemoteHost():获取客户端的名字

  • getSession([Boolean create]):返回和请求相关Session

  • getServerName():获取服务器的名字

  • getServletPath():获取客户端所请求的脚本文件的路径

  • getServerPort():获取服务器的端口号

  • removeAttribute(String name):删除请求中的一个属性

5.Servlet执行时一般实现哪几个方法?

  • public void init(ServletConfig config)

  • public ServletConfig getServletConfig()

  • public String getServletInfo()

  • public void service(ServletRequest request,ServletResponse response)

  • public void destroy()

6.GenericServlet和HttpServlet有什么区别?

  • GenericServlet是一个通用的协议无关的Servlet,它实现了Servlet和ServletConfig接口。

  • 继承自GenericServlet的Servlet应该要覆盖service()方法。

  • 最后,为了开发一个能用在网页上服务于使用HTTP协议请求的Servlet,你的Servlet必须要继承自HttpServlet。

7.九大内置对象

  • application

  • page

  • request

  • response

  • session

  • exception

  • out

  • config

  • pageContext

 

Linux常用命令(被问到比较少)

用户操作命令(比较常用):

  • Su:切换用户命令

  • Sudo:一系统管理员的身份执行命令

  • Passwd:用于修改用户的密码

  • 改变目录和查看当前目录命令

  • Cd:进入工作目录

  • Cd ..:会退到上一级命令

  • Pwd:显示当前用户所在工作目录位置

查看进程ps命令:

  • -a,查看所有

  • -u,以用户(user)的格式显示

  • -x, 显示后台进程运行参数

  • -ef,以全格式显示进程所有信息,包括父进程Pid,创建人,创建时间,进程号。等等

一般项目中,我们首先要查询一个进程,并对其进行删除会用一下命令

ps -a | grep helloworld 或 ps -ef |grep helloworld 或者 其他

查询到helloworld相关的进程,我们通过kill命令来操作该进程号删除该进程,kill -9 13492

显示目录和文件的命令:

  • Ls:用于查看所有文件夹的命令。

  • Dir:用于显示指定文件夹和目录的命令 Tree: 以树状图列出目录内容

  • Du:显示目录或文件大小

修改目录,文件权限和属主及数组命令:

  • Chmod:用于改变指定目录或文件的权限命令。

  • Chown:用于改变文件拥有属性的命令。

  • Chgrp:用于改变文件群组的命令。

  • Chattr:用于设置文件具有不可删除和修改权限。

  • Lsattr:用于显示文件或目录的隐藏属性。

创建和删除目录的命令:

  • Mkdir:用于创建目录

  • Rmdir:用于删除空的目录

  • Rm -f:用于删除不为空的目录

创建和删除,重命名,复制文件的命令:

  • Touch:创建一个新的文件

  • Vi:创建一个新的文件

  • Rm:删除文件或目录

  • Mv:重命名或移动文件的命令

  • Cp:复制命令

  • Scp:用于将本地的文件或目录复制到远程服务器

  • Wget:用于下载ftp或http服务器文件到本地。

显示文件内容的命令:

  • Cat:用于显示指定文件的全部内容

  • More:用分页的形式显示指定文件的内容

  • Less:用分页的形式显示指定文件的内容,区别是more和less翻页使用的操作键不同。

  • Head:用于显示文件的前n行内容。

  • Tail:用于显示文件的后n行内容。

  • Tail -f:用于自动刷新的显示文件后n行数据内容。

查找命令:

  • Find:查找指定目录或文件的命令。

  • Whereis:查找指定的文件源和二进制文件和手册等

  • Which:用于查询命令或别名的位置。

  • Locate:快速查找系统数据库中指定的内容。

  • Grep:在指定的文件或标准输出,标准输入内,查找满足条件的内容。

关机和重启计算机的命令:

  • Shutdown:-r 关机后立即重启

 -k 并不真正的关机,而只是发出警告信息给所有用户
​ -h 关机后不重新启动

  • Poweroff:用于关机和关闭电源

  • Init:改变系统运行级别

    0级用于关闭系统
    1级用于单一使用者模式
    2级用来进行多用户使用模式(但不带网络功能)
    3级用来进行多用户使用模式(带网络全功能)
    4级用来进行用户自定义使用模式
    5级表示进入x  windows时的模式
    6级用来重启系统

  • Reboot: 用于计算机重启 Halt:用于关闭计算机系统

压缩和打包命令(比较常用):

  • Tar:用于多个文件或目录进行打包,但不压缩,同时也用命令进行解包

  • Gzip:用于文件进行压缩和解压缩命令,文件扩展名为.gz结尾。

  • Gunzip:用于对gzip压缩文档进行解压缩。

  • Bzip2:用于对文件或目录进行压缩和解压缩

  • Bzcat:用于显示压缩文件的内容。

  • ComPRess/un compress: 压缩/解压缩.Z文件

  • Zcat:查看z或gz结尾的压缩文件内容。

  • Gzexe:压缩可执行的文件

  • Unarg:解压缩.arj文件

  • Zip/unzip:压缩解压缩.zip文件

文件连接命令:

  • Ln:为源文件创建一个连接,并不将源文件复制一份,即占用的空间很小。

可以分为软连接和硬链接:

软连接:也称为符号连接,即为文件或目录创建一个快捷方式。

硬链接:给一个文件取多于一个名字,放在不同目录中,方便用户使用。

Ln命令参数如下:

  • -f:在创建连接时,先将与目的对象同名的文件或目录删除。

  • -d:允许系统管理者硬链接自己的目录。

  • -i:在删除与目的对象同名文件或目录时先询问用户。

  • -n:在创建软连接时,将目的对象视为一般的文件。

  • -s:创建软连接,即符号连接。

  • -v:在连接之前显示文件或目录名。

  • -b:将在连接时会被覆盖或删除的文件进行备份。

其他命令

  • Who:显示系统中有那些用户在使用。

     -ami  显示当前用户

    -u:显示使用者的动作/工作

    -s:使用简短的格式来显示

    -v:显示程序版本

  • Free:查看当前系统的内存使用情况

  • Uptime:显示系统运行了多长时间

  • Ps:显示瞬间进程的动态

  • Pstree:以树状方式显示系统中所有的进程

  • Date:显示或设定系统的日期与时间。

  • Last:显示每月登陆系统的用户信息

  • Kill: 杀死一些特定的进程

  • Logout:退出系统

  • Useradd/userdel:添加用户/删除用户

  • Clear:清屏

  • Passwd:设置用户密码

vi编辑器

首先用vi命令打开一个文件

末行模式命令:

  • :n,m w path/filename 保存指定范围文档( n表开始行,m表结束行)

  • :q! 对文件做过修改后,强制退出

  • :q 没有对文件做过修改退出

  • Wq或x 保存退出

  • dd 删除光标所在行

  • : set number 显示行号

  • :n 跳转到n行

  • :s 替换字符串 :s/test/test2/g /g全局替换 /也可以用%代替

  • / 查找字符串

网络通信常用的命令

  • Arp:网络地址显示及控制

  • ftp:文件传输

  • Lftp:文件传输

  • Mail:发送/接收电子邮件

  • Mesg:允许或拒绝其他用户向自己所用的终端发送信息

  • Mutt E-mail 管理程序

  • Ncftp :文件传输

  • Netstat:显示网络连接.路由表和网络接口信息

  • Pine:收发电子邮件,浏览新闻组

  • Ping:用于查看网络是否连接通畅

  • Ssh:安全模式下远程登陆

  • Telnet:远程登录

  • Talk:与另一用户对话

  • Traceroute:显示到达某一主机所经由的路径及所使用的时间。

  • Wget:从网路上自动下载文件

  • Write:向其它用户终端写信息 Rlogin:远程登录

 

 

 

 

 

标签:面试题,return,String,2021.05,Spring,更新,线程,使用,方法
来源: https://blog.csdn.net/tj1996/article/details/116659368

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有