Java 最常见的面试题--1

文章引用参考来自:https://mp.weixin.qq.com/s/q_SduX1NfyDtA-l-3tiXEA
文章只是作为自己的学习笔记,借鉴了网上的许多案例

Java 基础

1. JDK 和 JRE 有什么区别?

  • JDK提供java开发和运行环境 - - - 编写 java 程序,需要安装 JDK。
  • jre提供运行环境 - - -需要运行 java 程序,只需安装 JRE 就可以了
    所以jdk包含 jre

2. == 和 equals 的区别是什么?

1

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

有趣的是下面的:
2

原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。当我们进入 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;
}

总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
3. 两个对象的 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
        **/

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

  • final 修饰的类叫最终类,该类不能被继承。
  • final 修饰的方法不能被重写。
  • final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

5. String 属于基础的数据类型吗?
String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。
另外 :java引用数据类型分3种:类,接口,数组;

6. java 中操作字符串都有哪些类?它们之间有什么区别?
操作字符串的类有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer
7. String str="i"与 String str=new String("i")一样吗?
不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String("i") 则会被分到堆内存中。
8. 如何将字符串反转?
使用 StringBuilder 或者 stringBuffer 的** reverse()** 方法。

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

9. String 类的常用方法都有那些?

  • indexOf():返回指定字符的索引。
  • charAt():返回指定索引处的字符。
  • replace():字符串替换。
  • trim():去除字符串两端空白。
  • split():分割字符串,返回一个分割后的字符串数组。
  • getBytes():返回字符串的 byte 类型数组。
  • length():返回字符串长度。
  • toLowerCase():将字符串转成小写字母。
  • toUpperCase():将字符串转成大写字符。
  • substring():截取字符串。
  • equals():字符串比较。

10. 抽象类必须要有抽象方法吗?
用abstract修饰的类就是抽象类,并不是说抽象类中必须有抽象方法,即使一个类中的方法全部实现过,也可以用abstract修饰为抽象类,所以抽象类不一定都有抽象方法。
下面代码中是一个没有抽象方法的抽象类:

abstract class DemoClass{
    public void printMessage(String msg){
        System.out.println(msg);
    }
}

看完这段代码,我不尽又想,这个类可以被实例化吗?我怎么去调用该类中的公共方法呢?通过

DemoClass d = new DemoClass(); 

这明显是不行的,抽象类不能被实例化,即使是一个没有抽象方法的抽象类,也同样不能被实例化
11. 普通类和抽象类有哪些区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。
12. 抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类,如下图所示,编辑器也会提示错误信息:
13. 接口和抽象类有什么区别?

  • 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
  • 构造函数:抽象类可以有构造函数;接口不能有。
  • main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
  • 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
  • 访问修饰符:接口中的方法默认使 public 修饰;抽象类中的方法可以是任意访问修饰符。

14. java 中 IO 流分为几种?
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。

15. BIO、NIO、AIO 有什么区别?
首先理解 同步 异步 阻塞 和 非阻塞
先来个例子理解一下概念,以银行取款为例:

  • 同步 : 自己亲自出马拿银行卡到银行取自己的钱
    (使用同步IO时,Java自己处理IO读写);
  • 异步 : 委托一个人拿银行卡到银行取钱,然后把钱给你
    (使用异步IO时,Java将IO读写委托给OS(操作系统)处理,需要将数据缓冲区地址和大小传给OS(相当于银行卡和密码),当然OS需要支持异步IO操作API)
  • 阻塞 : 你在ATM排队取钱,你只能排队等待前面的取钱完毕,你才能取钱
    (使用阻塞IO时,Java调用会一直阻塞到读写完成才返回)
  • 非阻塞 : 在柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去
    (使用非阻塞IO时,如果不能读写Java调用就会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)
再来说

Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,用在连接数目比较小且固定的架构
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用于连接数目多且连接比较短(轻操作)的架构
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,用于连接数目多且连接比较长(重操作)的架构。
16. Files的常用方法都有哪些?

  • Files.exists():检测文件路径是否存在。
  • Files.createFile():创建文件。
  • Files.createDirectory():创建文件夹。
  • Files.delete():删除一个文件或目录。
  • Files.copy():复制文件。
  • Files.move():移动文件。
  • Files.size():查看文件个数。
  • Files.read():读取文件。
  • Files.write():写入文件。
    17. java 容器都有哪些?
    常用容器目录

容器

18. Collection 和 Collections 有什么区别?
1、java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
2、java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
19. List、Set、Map 之间的区别是什么?

20. HashMap 和 Hashtable 有什么区别?
第一、继承不同。
  public class Hashtable extends Dictionary implements Map
  public class HashMap extends AbstractMap implements Map
第二、Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
第三、Hashtable中,key和value都不允许出现null值。在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
第四、两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
第五、哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
第六、Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
21. 如何决定使用 HashMap 还是 TreeMap?
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
22. 说一下 HashMap 的实现原理?
通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

  • 描述HashMap:基于Map接口实现、允许null键/值、非同步、不保证有序(比如插入的顺序)、也不保证序不随时间变化。
  • 在HashMap中有两个很重要的参数,容量(Capacity)和负载因子(Load factor)简单的说,Capacity就是buckets(桶)的数目,Load factor就是buckets填满程度的最大比例。如果对迭代性能要求很高的话不要把capacity设置过大,也不要把load factor设置过小。当bucket填充的数目(即hashmap中元素的个数)大于capacity*load factor时就需要调整buckets的数目为当前的2倍
  • put函数大致的思路为:
    对key的hashCode()做hash,然后再计算index;
    如果没碰撞直接放到bucket里;
    如果碰撞了,以链表的形式存在buckets后;
    如果碰撞导致链表过长(大于等于某个限制),就把链表转换成红黑树;
    如果节点已经存在就替换old value(保证key的唯一性)
    如果bucket满了(超过load factor*当前的 capacity),就要resize。
  • get函数的实现大致思路如下:
    bucket里的第一个节点,直接命中;
    如果有冲突,则通过key.equals(k)去查找对应的entry
    若为树,则在树中通过key.equals(k)查找,O(logn);
    若为链表,则在链表中通过key.equals(k)查找,O(n)。
  • hash的实现
    是通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。
  • HashMap的大小超过了负载因子(load factor)定义的容量
    如果超过了负载因子(默认0.75),则会重新调整一个原来长度两倍的HashMap,并且重新调用hash方法。
    23. ArrayList 和 LinkedList 的区别是什么?
    最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
    24. 如何实现数组和 List 之间的转换?
    List转换成为数组:调用ArrayList的toArray方法。
    数组转换成为List:调用Arrays的asList方法。
    25.ArrayList 和 Vector 的区别是什么?
  • ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
  • Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
  • ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。
  • Vector提供indexOf(obj, start)接口,ArrayList没有。
  • Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。
    26. Array 和 ArrayList 有何区别?
    Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
    Array大小是固定的,ArrayList的大小是动态变化的
    Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。
    27. 在 Queue 中 poll()和 remove()有什么区别?
    poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
    28. 哪些集合类是线程安全的?
  • vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
  • statck:堆栈类,先进后出。
  • hashtable:就比hashmap多了个线程安全。
  • enumeration:枚举,相当于迭代器。
    29.迭代器 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中插入和删除元素。
    30. Iterator 和 ListIterator 有什么区别?
    1.png
2.png

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

多线程

31.并行和并发有什么区别?
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。

  • 简而言之就是并发是多个事件在同一时间段执行,而并行是多个事件在同一时间点执行。

32.线程和进程的区别?
简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
33.守护线程是什么?
守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程。
34.创建线程有哪几种方式?
①. 继承Thread类创建线程类
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
创建Thread子类的实例,即创建了线程对象。
调用线程对象的start()方法来启动该线程。

public class ThreadTest extends Thread{
 
    public void run(){
        for(int i=0;i<=10;i++){
            System.out.println(i);
        }       
    }
    
    public static void main(String[] args) {
        ThreadTest thread1=new ThreadTest();
        ThreadTest thread2=new ThreadTest();
        thread1.start();
        thread2.start();
    }
}

②. 通过Runnable接口创建线程类
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
调用线程对象的start()方法来启动该线程。

public class ThreadTest implements Runnable{
    
    @Override
    public void run() {
        for(int i=0;i<=10;i++){
            System.out.println(i);
        }   
    }
    public static void main(String[] args) {
        ThreadTest threadTest=new ThreadTest();
        Thread theard=new Thread(threadTest);
        theard.start();
    }
}

③. 通过Callable和Future创建线程
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象的target创建并启动新线程。
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

public class ThreadTest implements Callable<Integer>{
 
    @Override
    public Integer call() throws Exception {
        int count =0;
        for(int i=0;i<=10;i++){
            count=count+i;
        }
        return count;   
    }
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ThreadTest test=new ThreadTest();
        FutureTask<Integer> thread = new FutureTask<>(test);
        new Thread(thread,"有返回值的线程").start();  
        System.out.println(thread.get());
    }
    
}

采用实现Runnable、Callable接口的方式创见多线程时
(1)优点:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
采用继承Thread类方式:
(1)优点:
编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点
:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
相同点:
35.说一下 runnable 和 callable 有什么区别
相同点
两者都是接口;(废话)
两者都可用来编写多线程程序;
两者都需要调用Thread.start()启动线程;
不同点:
两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。
36.线程有哪些状态?
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。

  • 创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  • 就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  • 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  • 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
  • 死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪

37.sleep() 和 wait() 有什么区别?
sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块(synchronized是Java中的关键字,是一种同步锁,具体用法http://www.importnew.com/21866.html中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程
38. notify()和 notifyAll()有什么区别?

  • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

  • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。

  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

39. 线程的 run()和 start()有什么区别?
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。

  • start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

  • run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

40.创建线程池有哪几种方式?
①. newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
②. newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
③. newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
④. newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPool {
 
    public static void main(String[] args) {
        // 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
        // 这里如果去除sleep,则你会发现在这个循环中创建了新的线程,因为前一个任务没有执行完,所以创建新线程执行下一个任务。
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
        
        // 创建一个定长的线程池,也是根据需要去调用线程,比如线程定为100个,而循环只有10个,那么也只会用到前10个进程。
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            newFixedThreadPool.execute(new Runnable() {
                public void run() {
                    // Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
        
        // 创建一个定长线程池,支持定时及周期性任务执行。
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            newScheduledThreadPool.schedule(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----delay 3 seconds");
                }
            }, 3, TimeUnit.SECONDS);
        }
        
        // 按顺序来执行线程任务 但是不同于单线程,这个线程池只是只能存在一个线程,这个线程死后另外一个线程会补上。
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            newSingleThreadExecutor.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
    }
}

41.线程池都有哪些状态?
线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。
线程池各个状态切换框架图:

42.线程池中 submit()和 execute()方法有什么区别?

  • 接收的参数不一样
  • submit有返回值,而execute没有
    用到返回值的例子,比如说我有很多个做validation(确认)的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。然后我就可以把所有失败的原因综合起来发给调用者。
  • submit方便Exception处理
    意思就是如果你在你的task里会抛出checked或者unchecked exception,
    而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

43.在 java 程序中怎么保证多线程的运行安全?
线程安全在三个方面体现:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
看大佬;https://blog.csdn.net/weixin_40459875/article/details/80290875

44 .多线程锁的升级原理是什么?
在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
锁升级的图示过程:


45.什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
46.死锁的四个必要条件:
互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系
47.ThreadLocal 是什么?有哪些使用场景?
在多线程环境下,如何防止自己的变量被其它线程篡改
首先,它是一个数据结构,有点像HashMap,可以保存"key : value"键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。

ThreadLocal<String> localName = new ThreadLocal();
localName.set("小红");
String name = localName.get();

在线程1中初始化了一个ThreadLocal对象localName,并通过set方法,保存了一个值 小红,同时在线程1中通过 localName.get()可以拿到之前设置的值,但是如果在线程2中,拿到的将是一个null。这就是ThreadLocal保证了各个线程的数据互不干扰。

  • 最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等
    数据库连接:
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
    public Connection initialValue() {  
        return DriverManager.getConnection(DB_URL);  
    }  
};  
  
public static Connection getConnection() {  
    return connectionHolder.get();  
} 

Session管理:

private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

48.synchronized 底层实现原理?
https://www.cnblogs.com/mingyao123/p/7424911.html
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
49.synchronized 和 volatile 的区别是什么?
(1).volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
(2).volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
(3).volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
(4).volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
(5).volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
50.synchronized 和 Lock 有什么区别?
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
51.synchronized 和 ReentrantLock 区别是什么?
synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:

ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
ReentrantLock可以获取各种锁的信息
ReentrantLock可以灵活地实现多路通知

另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。
52.说一下 atomic 的原理?
当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。

反射

53. 什么是反射?
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力
Java反射:
在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法

Java反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法。
54.什么是 java 序列化?什么情况下需要序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

什么情况下需要序列化:
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;

import java.io.*;
import java.util.Date;

public class ObjectSaver {
    public static void main(String[] args) throws Exception {
        /*其中的  D:\\objectFile.obj 表示存放序列化对象的文件*/
        //序列化对象
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\objectFile.obj"));
        Customer customer = new Customer("小红", 24);    
        out.writeObject("你好!");    //写入字面值常量
        out.writeObject(new Date());    //写入匿名Date对象
        out.writeObject(customer);    //写入customer对象
        out.close();

        
        //反序列化对象
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\objectFile.obj"));
        System.out.println("obj1 " + (String) in.readObject());    //读取字面值常量
        System.out.println("obj2 " + (Date) in.readObject());    //读取匿名Date对象
        Customer obj3 = (Customer) in.readObject();    //读取customer对象
        System.out.println("obj3 " + obj3);
        in.close();
    }
}

class Customer implements Serializable {
    private String name;
    private int age;
    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}
/*
obj1 你好!
obj2 Fri Mar 29 06:40:41 UTC 2019
obj3 name=小红, age=24
*/

55. 动态代理是什么?有哪些应用?
动态代理:
当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。

56.怎么实现动态代理?
首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy(习惯性将其称为代理类,因为调用他的newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。

对象拷贝

57. 为什么要使用克隆?
想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了。克隆分浅克隆和深克隆,浅克隆后的对象中非基本对象和原对象指向同一块内存,因此对这些非基本对象的修改会同时更改克隆前后的对象。深克隆可以实现完全的克隆,可以用反射的方式或序列化的方式实现。

58.如何实现对象克隆?
有两种方式:
1). 实现Cloneable接口并重写Object类中的clone()方法;
2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class MyUtil {

    private MyUtil() {
        throw new AssertionError();
    }

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);

        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        return (T) ois.readObject();

        // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
    }
}

下面是测试代码:

import java.io.Serializable;

/**
 * 人类
 */
class Person implements Serializable {
    private static final long serialVersionUID = -9102017020286042305L;

    private String name;    // 姓名
    private int age;        // 年龄
    private Car car;        // 座驾

    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
    }

}
/**
 * 小汽车类
 */
class Car implements Serializable {
    private static final long serialVersionUID = -5713945027627603702L;

    private String brand;       // 品牌
    private int maxSpeed;       // 最高时速

    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }

}
class CloneTest {

    public static void main(String[] args) {
        try {
            Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
            Person p2 = MyUtil.clone(p1);   // 深度克隆
            p2.getCar().setBrand("BYD");
            // 修改克隆的Person对象p2关联的汽车对象的品牌属性
            // 原来的Person对象p1关联的汽车不会受到任何影响
            // 因为在克隆Person对象时其关联的汽车对象也被克隆了
            System.out.println(p1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。
59.深拷贝和浅拷贝区别是什么?
浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝(例:assign())

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝(例:JSON.parse()和JSON.stringify(),但是此方法无法复制函数类型)
.JSON.parse(jsonString): 在一个字符串中解析出JSON对象

var str = '[{"href":"baidu.com","text":"test","orgId":123,"dataType":"curry","activeClass":"haha"}]';

JSON.parse(str);

结果:


JSON.stringify(obj) : 将一个JSON对象转换成字符串

var obj = [{"href":"baidu.com","text":"test","orgId":123,"dataType":"curry","activeClass":"haha"}];

JSON.stringify(obj);
结果
[{"href":"baidu.com","text":"test","orgId":123,"dataType":"curry","activeClass":"haha"}]

Java Web

60. jsp 和 servlet 有什么区别?
jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
jsp更擅长表现于页面显示,servlet更擅长于逻辑控制。
Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到。
Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。
61. jsp 有哪些内置对象?作用分别是什么?
JSP有9个内置对象:
1.request:封装客户端的请求,其中包含来自GET或POST请求的参数;
2.response:封装服务器对客户端的响应;
3.pageContext:通过该对象可以获取其他对象;
4.session:封装用户会话的对象;
5.application:封装服务器运行环境的对象;
6.out:输出服务器响应的输出流对象;
7.config:Web应用的配置对象;
8.page:JSP页面本身(相当于Java程序中的this);
9.exception:封装页面抛出异常的对象。
62.说一下 jsp 的 4 种作用域?
JSP中的四种作用域包括page、request、session和application,具体来说:

  • page代表与一个页面相关的对象和属性。
  • request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。
  • session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
  • application代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。

63.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的一种方式。
64.说一下 session 的工作原理?
其实session是一个存在服务器上的类似于一个散列表格的文件。里面存有我们需要的信息,在我们需要用的时候可以从里面取出来。类似于一个大号的map吧,里面的键存储的是用户的sessionid,用户向服务器发送请求的时候会带上这个sessionid。这时就可以从中取出对应的值了。
65.如果客户端禁止 cookie 能实现 session 还能用吗?
Cookie与 Session,一般认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。但为什么禁用Cookie就不能得到Session呢?因为Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。

假定用户关闭Cookie的情况下使用Session,其实现途径有以下几种:

  • 设置php.ini配置文件中的“session.use_trans_sid = 1”,或者编译时打开-
    打开了“--enable-trans-sid”选项,让PHP自动跨页传递Session ID。
  • 手动通过URL传值、隐藏表单传递Session ID。
  • 用文件、数据库等形式保存Session ID,在跨页过程中手动调用。

66.spring mvc 和 struts 的区别是什么?
一、拦截机制的不同
  Struts2是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype,然后通过setter,getter吧request数据注入到属性。Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了,只能设计为多例。

SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架。在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改。

Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大。

二、底层框架的不同
  Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。Filter在容器启动之后即初始化;服务停止以后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁。

三、性能方面
  Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。所以,SpringMVC开发效率和性能高于Struts2。

四、配置方面
  spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高。

附注:拦截器与过滤器的区别 :

拦截器是基于java的反射机制的,而过滤器是基于函数回调。
拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
67.如何避免 sql 注入?

  • PreparedStatement(简单又有效的方法)
public class UserDaoImpl {
    public void insert(User user) throws SQLException {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;    
        try{
            conn = JdbcUtils.getConnection();
            String sql = "insert into user(id,name,age) values(?,?,?)";
            st = conn.prepareStatement(sql);
            st.setInt(1, user.getId());
            st.setString(2, user.getName());
            st.setInt(3, user.getAge());
            int num = st.executeUpdate();
            if(num>0){
                System.out.println("添加成功");
            }else{
                System.out.println("添加失败");
            }
            
        }finally{
            JdbcUtils.release(conn, st, rs);
        }
    }
}
  • 使用正则表达式过滤传入的参数
要引入的包:

import java.util.regex.*;

正则表达式:

private String CHECKSQL = “^(.+)\\sand\\s(.+)|(.+)\\sor(.+)\\s$”;

判断是否匹配:

Pattern.matches(CHECKSQL,targerStr);

下面是具体的正则表达式:

检测SQL meta-characters的正则表达式 :/(\%27)|(\’)|(\-\-)|(\%23)|(#)/ix

修正检测SQL meta-characters的正则表达式 :/((\%3D)|(=))[^\n]*((\%27)|(\’)|(\-\-)|(\%3B)|(:))/i

典型的SQL 注入攻击的正则表达式 :/\w*((\%27)|(\’))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix

检测SQL注入,UNION查询关键字的正则表达式 :/((\%27)|(\’))union/ix(\%27)|(\’)

检测MS SQL Server SQL注入攻击的正则表达式:/exec(\s|\+)+(s|x)p\w+/ix

等等…..
  • 字符串过滤

//过滤特殊字符
    public static String stringFilter(String str) throws PatternSyntaxException { 
        // 只允许字母和数字 // String regEx ="[^a-zA-Z0-9]"; 
        // 清除掉所有特殊字符 
        String regEx="[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。 ,、?]"; 
        Pattern p = Pattern.compile(regEx); 
        Matcher m = p.matcher(str);
        return m.replaceAll(" ").trim();

  • JSP中调用该函数检查是否包函非法字符
package sql_inj;
import java.net.*;
import java.io.*;
import java.sql.*;
import java.text.*;
import java.lang.String;
public class sql_inj{
public static boolean sql_inj(String str){
String inj_str = "'|and|exec|insert|select|delete|update|
count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,";
//这里的东西还可以自己添加
String[] inj_stra=inj_str.split("\\|");
for (int i=0 ; i &lt; inj_stra.length ; i++ ){
 if (str.indexOf(inj_stra[i])&gt;=0){
      return true;
    }
}
      return false;
 }
}
  • JSP页面判断代码
function check(a)
{
fibdn = new Array (”‘” ,”\\”,”/”);
i=fibdn.length;
j=a.length;
for (ii=0; ii<i; ii++){ 
  for (jj=0; jj<j; jj++){
    temp1=a.charAt(jj);
    temp2=fibdn[ii];
    if (tem’; p1==temp2){ return 0; }
}
}
return 1;
}

68. 什么是 XSS 攻击,如何避免?
XSS攻击又称CSS,全称Cross Site Script (跨站脚本攻击),其原理是攻击者向有XSS漏洞的网站中输入恶意的 HTML 代码,当用户浏览该网站时,这段 HTML 代码会自动执行,从而达到攻击的目的。XSS 攻击类似于 SQL 注入攻击,SQL注入攻击中以SQL语句作为用户输入,从而达到查询/修改/删除数据的目的,而在xss攻击中,通过插入恶意脚本,实现对用户游览器的控制,获取用户的一些信息。 XSS是 Web 程序中常见的漏洞,XSS 属于被动式且用于客户端的攻击方式。

  • 例子:
    有一个input输入框,需要用户输入名字,用户却输入一个恶意脚本,<script>alert(document.cookie)</script>,
    或者用户输入一个HTML,在标签中嵌入恶意脚本,如src,href,css style等。如:<IMG SRC="javascript:alert('XSS');">; <BODY BACKGROUND="javascript:alert('XSS')">

XSS防范的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码。
69.什么是 CSRF 攻击,如何避免?
CSRF(Cross-site request forgery)也被称为 one-click attack或者 session riding,中文全称是叫跨站请求伪造。一般来说,攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等。攻击者利用网站对请求的验证漏洞而实现这样的攻击行为,网站能够确认请求来源于用户的浏览器,却不能验证请求是否源于用户的真实意愿下的操作行为。

如何避免:

  1. 验证 HTTP Referer 字段
    HTTP头中的Referer字段记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,而如果黑客要对其实施 CSRF攻击,他一般只能在他自己的网站构造请求。因此,可以通过验证Referer值来防御CSRF 攻击。
在 filter 中对请求的 Referer 验证代码如下
 // 从 HTTP 头中取得 Referer 值

 String referer=request.getHeader("Referer");

 // 判断 Referer 是否以 wg.example 开头

 if((referer!=null) &&(referer.trim().startsWith(“wg.example”))){

    chain.doFilter(request, response);

 }else{

request.getRequestDispatcher(“error.jsp”).forward(request,response);

 } 

以上代码先取得 Referer 值,然后进行判断,当其非空并以 eg.example 开头时,则继续请求,否则的话可能是 CSRF 攻击,转到 error.jsp 页面。
  1. 使用验证码
    关键操作页面加上验证码,后台收到请求后通过判断验证码可以防御CSRF。但这种方法对用户不太友好。

  2. 在请求地址中添加token并验证
    CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有token或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于session之中,然后在每次请求时把token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。
    对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成
    http://url?csrftoken=tokenvalue
    而对于 POST 请求来说,要在 form 的最后加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,这样就把token以参数的形式加入请求了。

  3. 在HTTP 头中自定义属性并验证
    这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。

异常

70.throw 和 throws 的区别?
throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。而throw则是指抛出的一个具体的异常类型。
1、throws出现在方法函数头;而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

public class testThrows {
 
    public static void function() throws NumberFormatException {
        String s = "abc";
        System.out.println(Double.parseDouble(s));
    }
 
    public static void main(String[] args) {
        try {
            function();
        } catch (NumberFormatException e) {
            System.err.println("非数据类型不能强制类型转换。");
            //e.printStackTrace(); 
        }
    }
}
运行结果:
非数据类型不能强制类型转换。

编程习惯:
1.在写程序时,对可能会出现异常的部分通常要用try{...}catch{...}去捕捉它并对它进行处理;
2.用try{...}catch{...}捕捉了异常之后一定要对在catch{...}中对其进行处理,那怕是最简单的一句输出语句,或栈输入e.printStackTrace();
3.如果是捕捉IO输入输出流中的异常,一定要在try{...}catch{...}后加finally{...}把输入输出流关闭;
4.如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理。
71.try-catch-finally的执行过程

public class Demo {
    public static void main(String[] args) {
        System.out.println("tcf:" + tcf());
    }
    public static int tcf() {
        String test_null = null;
        /* 当try与catch中均有return时,无异常,
        finally依旧执行 :输出 try,finally,返回1*/
     
         try {
         System.out.println("try");
         return 1;
         } catch (NullPointerException e) {
         System.out.println("catch");
         return 2;
         } finally {
         System.out.println("finally");
         }
         
/**-------------------------------------------------------------------------------------
         当try与catch中均有return时,有异常
        finally依旧执行 :输出 try,catch,finally,返回2 */

        /*
        try {
            System.out.println("try");
            System.out.println(test_null.equals("123"));
            return 1;
        } catch (NullPointerException e) {//空指针
            System.out.println("catch");
            return 2;
        } finally {
            System.out.println("finally");
        }*/
        

/** -------------------------------------------------------------------------------------------
         当try与catch,finally中均有return时,有异常
        finally依旧执行,并输出finally返回的值 :输出 try,catch,finally,返回3**/
       /* try {
            System.out.println("try");
            System.out.println(test_null.equals("123"));
            return 1;
        } catch (NullPointerException e) {
            System.out.println("catch");
            return 2;
        } finally {
            System.out.println("finally");
            return 3;
        }*/
    }

/** 综述,不管有无异常,只要存在finally,那么finally均会执行,
且finally中有return时,会返回finally中return的值,
且finally是在return之后执行的,只是会把finally之前的return的值保存起来,
等运行结束后再进行返回**/
}

72. final、finally、finalize 有什么区别?

  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System的gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾。 finalize()方法并没有什么鸟用。

73.try-catch-finally 中哪个部分可以省略?
catch和finally语句不能同时省略

public class Test {
    public int chang(){
        int chang = 0;
        try {
            chang = 10;
            return chang+=10;
        }
        finally{
            System.out.println("finally中的结果:"+chang);
            chang = 30;
        }
         
    }
    public static void main(String[] args) {
        Test te = new Test();
        int i = te.chang();
        System.out.println("main中的最后的结果:"+i);
    }
}
结果:
finally中的结果:20
main中的最后的结果:20
分析结果:
1、先执行try中return后面的表达式,然后执行finally的语句
2、返回值是try中return后面的表达式计算出来的值,即使finally中对chang进行了修改,
也不会修改最后的返回值。try中先把计算出来的值放到不同于chang的局部变量中,
执行完finally后在取出结果。应该是保存在函数栈中的。

74. 常见的异常类有哪些?

  • NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
    SQLException:提供关于数据库访问错误或其他错误信息的异常。

  • IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。

  • NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。

  • FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。

  • IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。

  • ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。

  • ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。

  • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。

  • ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。

  • NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。

  • NoSuchMethodException:无法找到某一特定方法时,抛出该异常。

  • SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。

  • UnsupportedOperationException:当不支持请求的操作时,抛出该异常。

  • RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。

网络

75.http 响应码 301 和 302 代表的是什么?有什么区别?
答:301,302 都是HTTP状态的编码,都代表着某个URL发生了转移。
区别:
301 代表永久性转移。
302 代表暂时性转移
76.请求转发(Forward)和重定向(Redirect)的区别

  • forward(转发):
    是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,因为这个跳转过程实在服务器实现的,并不是在客户端实现的所以客户端并不知道这个跳转动作,所以它的地址栏还是原来的地址.
  • redirect(重定向):
    是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
    转发是服务器行为,重定向是客户端行为。

区别:

  1. 从地址栏显示来说
    forward是还是原来的地址.
    redirect是显示的是新的URL.
  2. 从数据共享来说
    forward:转发页面和转发到的页面可以共享request里面的数据.
    redirect:不能共享数据.
  3. 从运用地方来说
    forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
    redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等
  4. 从效率来说
    forward:高.
    redirect:低.

换句话说:
重定向
其实是两次request,第一次,客户端request A,服务器响应,并response回来,(这里运用到了302状态码)告诉浏览器,你应该去B。这个时候IE可以看到地址变了,而且历史的回退按钮也亮了。重定向可以访问自己web应用以外的资源。在重定向的过程中,传输的信息会被丢失。
请求转发
是服务器内部把对一个request/response的处理权,移交给另外一个对于客户端而言,它只知道自己最早请求的那个A,而不知道中间的B,甚至C、D。 传输的信息不会丢失。
77.简述 tcp 和 udp的区别?

  • TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
  • TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
  • Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
  • UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
  • 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
  • TCP对系统资源要求较多,UDP对系统资源要求较少。
    78. tcp 为什么要三次握手,两次不行吗?为什么?
    为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。
    79. OSI 的七层模型都有哪些?
  • 应用层:网络服务与最终用户的一个接口。
  • 表示层:数据的表示、安全、压缩。
  • 会话层:建立、管理、终止会话。
  • 传输层:定义传输数据的协议端口号,以及流控和差错校验。
  • 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。
  • 数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能。
  • 物理层:建立、维护、断开物理连接。
    80.get 和 post 请求有哪些区别?
    w3c的答案:
    GET在浏览器回退时是无害的,而POST会再次提交请求。
    GET产生的URL地址可以被Bookmark,而POST不可以。
    GET请求会被浏览器主动cache,而POST不会,除非手动设置。
    GET请求只能进行url编码,而POST支持多种编码方式。
    GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
    GET请求在URL中传送的参数是有长度限制的,而POST么有。
    对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
    GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
    GET参数通过URL传递,POST放在Request body中。
    其实:GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同
    详情:https://www.oschina.net/news/77354/http-get-post-different
    81. 如何实现跨域?
    什么是跨域?
    同源:就是指协议、域名、端口相同
    同源策略: 限制了一个源中加载文本或者脚本与来自其他源中资源的交互方式,当浏览器执行一个脚本时会检查是否同源,只有同源的脚本才会执行,如果不同源即为跨域
    在项目中可能会需要在一个域名下请求另外一个域名的资源,跨域的几种实现方式:
  • 1.jsonp
    利用了script标签不受同源策略的限制,在页面中动态插入了script,script标签的src属性就是后端api接口的地址,并且以get的方式将前端回调处理函数名称告诉后端,后端在响应请求时会将回调返还,并且将数据以参数的形式传递回去。
    <script type="text/javascript" src="http://web.cn/js/demo.js"></script> 平时最多就是这个了
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>

<script>
    var demo1 = function (data) {
        alert("我是后台通过jsonp传来的数据:" + data.name);
    }
</script>
<script src="http://远程服务器域名或ip地址(如:192.168.2.33)/jsonp/jsonp.js"></script>  
</body>
</html>
//js
demo1({"name": "lin"}); /*在远程服务器上的js代码,
也就是说该服务器上的数据我们都可以传入函数,那么html执行的时候也就获取到了这些远程数据.
其实只要有src属性的标签都可以借助同样的原理进行跨域请求,例如img, iframe*/
  • ** 2.document.domain + iframe跨域**
    此方案仅限主域相同,子域不同的跨域应用场景。

1.)父窗口:http://www.demo.com/a.html

<iframe id="iframe" src="http://**child**.demo.com/b.html"></iframe>
<script>
    document.domain = 'demo.com';
    var user = 'root';
</script>

2.)子窗口:http://child.demo.com/b.html

<script>
    document.domain = 'demo.com';
    // 获取父窗口中变量
    alert('get js-data from parent ---> ' + window.parent.user);
</script>
  • 3.location.hash + iframe跨域
    实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

具体实现:A域:a.html c.html B域:b.html,a与b不同域只能通过hash值单向通信,c与b也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象
a.html:A域.com/a.html

<iframe id="iframe" src="http://B域.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 向b.html传hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
 
    // 开放给同域c.html的回调方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>

c.html:A域.com/c.html

<script>
    // 监听b.html传来的hash值
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>

b.html:B域.com/b.html

<iframe id="iframe" src="http://A域.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>
  • 4.window.name + iframe跨域
    通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。
    1.)a.html:http://www.demo1.com/a.html
var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');

    // 加载跨域页面
    iframe.src = url;

    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
            callback(iframe.contentWindow.name);
            destoryFrame();

        } else if (state === 0) {
            // 第1次onload(跨域页)成功后,切换到同域代理页面
            iframe.contentWindow.location = 'http://www.demo1.com/proxy.html';
            state = 1;
        }
    };

    document.body.appendChild(iframe);

    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};

// 请求跨域b页面数据
proxy('http://www.demo2.com/b.html', function(data){
    alert(data);
});

2.)proxy.html:http://www.demo1.com/proxy.html
中间代理页,与a.html同域,内容为空即可。
3.)b.html:http://www.demo2.com/b.html

<script>
    window.name = 'This is demo2 data!';
</script>
  • 5.postMessage跨域
    用法:postMessage(data,origin)方法接受两个参数
    data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
    origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
    它可用于解决以下方面的问题:
    a.) 页面和其打开的新窗口的数据传递
    b.) 多窗口之间消息传递
    c.) 页面与嵌套的iframe消息传递
    d.) 上面三个场景的跨域数据传递
    1.)a.html:http://www.demo1.com/a.html
<iframe id="iframe" src="http://www.demo2.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向demo2传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.demo2.com');
    };

    // 接受demo2返回数据
    window.addEventListener('message', function(e) {
        alert('data from demo2 ---> ' + e.data);
    }, false);
</script>

2.)b.html:http://www.demo2.com/b.html

<script>
    // 接收domain1的数据
    window.addEventListener('message', function(e) {
        alert('data from demo1 ---> ' + e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;

            // 处理后再发回demo1
            window.parent.postMessage(JSON.stringify(data), 'http://www.demo1.com');
        }
    }, false);
</script>
  • 6.跨域资源共享(CORS)
  • 7.Nodejs中间件代理跨域
  • 8.nginx代理跨域
  • 9.WebSocket协议跨域

82. JSONP 实现原理?
1.首先是利用script标签的src属性来实现跨域。
2.通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客户端通信。
3.由于使用script标签的src属性,因此只支持get方法

//前端
function jsonp(req){
    var script = document.createElement('script');
    var url = req.url + '?callback=' + req.callback.name;
    script.src = url;
    document.getElementsByTagName('head')[0].appendChild(script); 
}
function hello(res){
    alert('hello ' + res.data);
}
jsonp({
    url : '',
    callback : hello 
});
//服务端
var http = require('http');
var urllib = require('url');

var port = 8080;
var data = {'data':'world'};

http.createServer(function(req,res){
    var params = urllib.parse(req.url,true);
    if(params.query.callback){
        console.log(params.query.callback);
        //jsonp
        var str = params.query.callback + '(' + JSON.stringify(data) + ')';
        res.end(str);
    } else {
        res.end();
    }
    
}).listen(port,function(){
    console.log('jsonp server is on');
});

然而,这个实现虽然简单,但有一些不足的地方:
我们传递的回调必须是一个全局方法,我们都知道要尽量减少全局的方法。
需要加入一些参数校验,确保接口可以正常执行。请百度可靠的jsonp实现

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容