面试笔试题
jvm
jvm的结构:类加载器(启动类加载器、扩展、应用)、运行时数据区、执行引擎、本地库接口
运行时数据区:
程序计数器、虚拟机栈、方法区、堆、本地方法栈
类的加载过程:
- 加载:根据查找路径找到相应的 class 文件然后导入;
- 检查:检查加载的 class 文件的正确性;
- 准备:给类中的静态变量分配内存空间;
- 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
- 初始化:对静态变量和静态代码块执行初始化工作。
三种类加载器共同协作,使用双亲委托的方式把一个类加载到内存中,
JVM是如何识别到该调用哪个方法的:
调用方法的时候,jvm会把在内存中的方法区获取到这个要调用的方法的代码,并且把它们加载到栈中(入栈),在栈区中一行行的运行这些代码
JVM是根据 类名+方法名+方法描述符(形参+返回类型) 来识别到底该调用哪一个方法的。
我们再从JVM层面分析下,JVM里面是通过哪里指令来实现方法的调用的:
- invokestatic:调用静态方法
- invokeinterface:调用接口方法(多态)
- invokespecial:调用非静态私有方法、构造方法(包括super)
- invokevirtual:调用非静态非私有方法(多态)
- invokedynamic:动态调用(Java7引入的,第一次用却是在Java8中,用在了Lambda表达式和默认方法中,它允许调用任意类中的同名方法,注意是任意类,和重载重写不同)(动态 ≠ 多态)
那么这些指令又是怎么来调用方法的呢?(invokedynamic和这些有点不一样,稍后单独解释下)
在编译的过程中,JVM并不知道目标方法的具体内存地址,此时编译器会用”符号引用”来表示该方法(加载阶段)。当JVM进行到“解析”阶段的时候,这些引用会被替换为直接引用,这个时候就知道需要去哪里调用到方法了!
对于静态绑定的方法,直接引用就是直接指向方法的指针,而对于动态绑定的方法,直接引用其实指向方法表中的一个索引。
方法表是一个数组,每个数组元素指向一个当前类及其父类中非private的实例方法,样子如下所示:
由于动态绑定相比于静态绑定,在寻找方法时要出多好一个内存解析的动作,例如获取调用者类型,获取方法表,获取方法表的索引值等等,还是有点开销的,虽然这些开销是必须的。所以JVM中引入了一些优化的技术: 内存缓联+方法内联。
动态绑定调用优化技术:
内存缓联:
说白了就是缓存,缓存的调用者的类型已经改类型所对应的目标方法,如果以后执行时,直接拿寻找缓存中对应的方法,不然就要去方法表中查询了(类似与操作系统的pagecache)。(非动态绑定的算法是不需要缓存的)
方法内联:
任何一个方法调用,除非它被内联,否则都会有固定开销。这些开销主要是保存程序在改方法中的执行位置,以及新建、压入和弹出新方法所使用中的栈帧。对于一些非常简单的方法例如getter/setter,这部分开销耗费的时间可能超过方法本身。
方法内联就是将调用函数的表达式直接用函数的函数体来直接替换,这样就减少了寻址开销,虽然可能会增加目标程序的代码量(增加空间开销),这是一个很重要的优化方法,由JVM来实现。
独特的Invokedynamic:
前面四种指令是吧符号引用替换为直接引用,直接指到内存中的地址,也就是说他们需要指导方法所在类名,方法名以及方法描述符。但是像Lambda表达式这种,尤其是scala,只要方法描述符(又叫方法签名)对上了,可以使用引用到其他类中的方法(任意Function类中的apply)。
也就是说需要有这么一个指令,可以允许程序将调用点连接到任意符合条件的方法上,这就需要用到反射机制了,但是反射调用有反复权限检查的开销,所以JVM在底层引入了轻量级的方法句柄(MethodHandle)的概念,并由JVM对它做一些优化(如方法内联)。
可以这么理解Reflection API设计失误诶Java语言服务的,而MethodHandle则为所有运行在JVM之上的语言服务。
GC是什么? 为什么要有GC?
GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。
GC算法:标记清楚法、标记整理法、复制算法、
JDK 和 JRE 有什么区别
- JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
- JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
能否控制垃圾回收器去回收内存垃圾:
不行,可以调用API中的相应方法去建议垃圾回收器去回收,但不一定回真的回收。
java.lang.System.gc();
java.lang.Runtime.gc();
面向对象
创建对象有几种方法:
- new:
- 反射:
- 序列化和反序列化:
- clone
“==”和“equals”的区别:
-
”==“
- 基本类型:比较的是值是否相同;
- 引用类型:比较的是引用是否相同;
-
“equals”
- 引用类型比较:对象的地址值,
- String 和 Integer重写了 equals方法,变成了值得比较
面向对象的特征:
-
分装:避免重复性使用代码。
定义方法,可以视为对一段同样作用代码的封装,来降低代码重复性
定义一个类,接口等等,也可以视为对具有相同特性的代码块的封装。
同样关键字enum,也可视为对Enum类的封装。 -
继承:继承主要实现重用代码,节省开发时间。
无论继承类还是实现接口,都是因为父类(接口)中有一段重复性代码(属性或功能)同样适用于子(实现)类,为避免重复封装这段代码,所以才有了继承的体现。 -
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
重载(overload)和重写(override)是实现多态的两种主要方式。
- 接口多态性。
- 继承多态性。
- 通过抽象类实现的多态性。
抽象类和接口的区别:
-
默认方法实现:抽象类可以有默认的方法实现;接口不能有默认的方法实现。
-
实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。接口不可以实现接口,可以继承一个或多个接口,
- 构造函数:抽象类可以有构造函数;接口不能有。
- main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
- 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
- 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
普通类和抽象类的区别:
- 普通类不能包含抽象方法,抽象类可以包含抽象方法。
- 抽象类不能直接实例化,普通类可以直接实例化。
final 和 finally 、finalize 的区别:
- final 修饰的类叫最终类,该类不能被继承。
- final 修饰的方法不能被重写。
- final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
- finally后边的代码块,不管try-catch会不会报错,都会执行
- finalize: 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法。
重写与重载:
- 重载:
- 发生在同一个类中
- 相同的方法名
- 参数列表不同
- 不看返回值,如果出现了只有返回值不同的“重载”,是错的。
- 重写:
- 发生在子类与父类中
- 相同的方法名
- 相同的参数列表
- 返回值相同 或者 子类方法的返回值是父类方法返回值类型的子类
- 访问修饰符相同 或者 子类方法的修饰符范围 大于 父类
- 抛出的异常相同 或者 子类方法抛出的异常 小于父类
this 和 super:
- this 调用的是子类的构造器、属性、方法
- super 调用的是父类的构造器、属性、方法
基本数据类型:
int short long double char boolean float byte
String 是引用类型
java的类型转换:
-
java的类型转换:
小的类型自动转化为大的类型
-
强制类型转换:
java 中操作字符串都有哪些类:
- String、StringBuffer、StringBuilder
- String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
- StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
- 字符串反转:使用 StringBuilder 或者 stringBuffer 的 reverse() 方法
&和&&的区别
&是位运算符,表示按位与运算,&&是逻辑运算符,表示逻辑与(and)
&既可以作为二进制数字的位运算符,也可以作为布尔表达式中的逻辑运算符,但是作为逻辑运算符的时候,&并没有&&符合的那种短路的功能。
请你解释什么是值传递和引用传递?
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.
一般认为,java内的传递都是值传递.
集合
Java容器有哪些
Collection map
List set map
Arraylist HashSet HashMap
linkedList LinkedHashSet TreeMap(二叉树排序)
Vector TreeSet(可排序) Hashtable
ConcurrentHashMap
Collection 和 Collections 有什么区别?
- Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。
- Collections 是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供的排序方法: Collections. sort(list)。
List、Set、Map 之间的区别是什么?
- List 有序可重复
- Set 无序不可重复(使用hashcode和equals方法判断当前对象和集合中的对象是否重合)
- Map 通过键值对存放数据、k值必须唯一,value值可以重复
Java 中的 HashMap 的工作原理是什么?
Java 中的 HashMap 是以键值对(key-value)的形式存储元素的。HashMap 需要一个 hash 函数,它使用 hashCode()和 equals()方法来向集合添加和检索元素。当调用 put()方法的时候,HashMap 会计算 key 的 hash 值,然后把键值对存储在集合中合适的索引上。如果 key已经存在了,value 会被更新成新值。HashMap 的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。
HashMap 和 Hashtable 有什么区别?
- 存储:HashMap 允许 key 和 value 为 null,而 Hashtable 不允许。
- 线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。
- 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。
如何决定使用 HashMap 还是 TreeMap?
对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。
实现数组和 List 之间的转换?
- 数组转 List:使用 Arrays. asList(array) 进行转换。
- List 转数组:使用 List 自带的 toArray() 方法。
数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?
Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
Array 大小是固定的,ArrayList 的大小是动态变化的。
ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
Array只有属性没有方法可以调用。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
ArrayList 和 LinkedList 的区别是什么?
-
ArrayList内部使用数组来实现List集合的特点,所以在使用访问元素的时候可以使用数组自带的下标来访问元素,效果比高。但是在ArrayList中插入元素的话,其他元素就会要移动位置,这种操作效率就会低一些
-
LinkedList内部使用链表来实现List集合的特点,所以在LinkList中进行元素插入的时候,效率比较高。但是如果要使用下标来访问元素的时候,LinkedList的效率就会比较低,因为链表中本来没有下标,它的这个下标是模拟出来的。(Arraylist中的下标是数组自带的)
ArrayList数组实现,通过下标访问效率高,在集合的中间位置插入数据效率低。
LinkedList链表实现,通过下标访问效率低,在集合的中间位置插入数据效率高。
Vector 线程安全的集合。
HashSet 和 TreeSet 有什么区别?
HashSet 是由一个 hash 表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是 O(1)。
另一方面,TreeSet 是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是 O(logn)。
Iterator 和 ListIterator 的区别是什么?
Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List。
Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向。
ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等
集合遍历方式
-
List集合:迭代器、for循环、forEach(jdk1.5以上)
-
set集合:迭代器、增强的for循环、forEach
-
map集合:
- 增强的for循环
- 迭代器 (遍历过程中删除元素推荐使用)
- entrySet遍历
- jdk1.8之后map集合可以用forEach加Lambda表达式的方式遍历
keySet方法 取出所有key值
values方法 取出所有value值
entrySet方法 取出所有的键值对
Set里的元素是不能重复的,那么用什么方法来区分重复与否呢?
当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来和其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。
如何往set集合中添加重复的对象:
重写equals方法,让equals永远等于true
TreeMap底层实现:
TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点。
io流
Java 中 IO 流分为几种
-
按功能来分:输入流(input)、输出流(output)。
-
按类型来分:字节流和字符流。
字节流:继承自InputStream和OutputStream
字符流:继承自InputStreamReader和OutputStreamWriter
-
管道流:有四种管道流,PipedInputStream,PipedOutputStream,PipedReader和PipedWriter。在多个线程或进程中传递数据的时候管道流非常有用。
字节流和字符流的区别是:
- 字节流操作的最基本的单元是字节,字符流操作的基本单位是字符
- 字节流默认不使用缓冲区,字符流使用缓冲区。
- 在硬盘上的所有文件都是以字节形式存在的(图片,声音,视频),而字符只在内存中才会形成。
- 字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
序列化、反序列化:
- 序列化:对象转换为字节序列的过程。
- 反序列化:字节序列恢复为对象的过程。
使用可序列化、实现序列化
把一个对象写入数据源或者从一个数据源读出来,使用可序列化,需要实现Serializable接口
常见方法:
- flush,字符流中刷新缓存区的方法。
- close,关闭io流。
- read,读字节,write,写字节。
OutputStream里面的write()是什么意思?write(byte b[], int off, int len)这个方法里面的三个参数分别是什么意思
- write将指定字节传入数据源
- Byte b[ ]是byte数组,b[off]是传入的第一个字符,b[off+len-1]是传入的最后的一个字符 ,len是实际长度
转换处理流OutputStreamWriter 可以将字节流转为字符流
打印出不同类型的数据到数据源
- Printwriter 可以打印各种数据类型
BufferedReader主要是用来做什么的,它里面有那些经典的方法
-
将读取的内容存在内存里面
-
readLine()方法读取一行文本
-
这种处理流的构造器需要传入字点流。
BufferedReader bufferedReader = newBufferedReader(new InputStreamReader(inStream,"UTF-8"));
拷贝
缓冲流
关闭流:
- 一般放在finally语句块中(finally 语句一定会执行)执行。
- 多个流互相调用只关闭最外层的流即可。
把对象写入磁盘中,需要实现什么接口:
ObjectInputStream,需要实现Serializable接口
把我们控制台的输出改成输出到一个文件里面
SetOut(printWriter,printStream)重定向
线程
线程:
-
前台线程:执行线程,比如main线程
-
后台线程:守护线程,比如gc(垃圾回收器)
cpu时间片:
操作系统为了方便管理和调度cpu的共享使用,就把cpu的使用时间划分成了一个个很短的时间段
并行和并发有什么区别?
- 并行:多个处理器或多核处理器同时处理多个任务。
-
并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。
线程和进程的区别?
进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。
创建线程有哪几种方式?
- 继承 Thread 重新 run 方法;
- 实现 Runnable 接口;(可以处理同一资源,实现资源共享)
isAlive:判断线程是否是否存活
stop:终止线程(过时方法,存在安全隐患)
interrupt:打断一个线程阻塞状态
join:t.join()方法会使所有线程(除同一时刻正在运行的其他线程)都暂停并等待t的执行完毕
wait:交出cpu使用权
notify:
notifyAll:
yield:线程让步,让出cpu执行权,不释放锁。
-
新建:没有调用start方法的时候
-
可运行:调用start方法的之后
-
运行:抢到cpu执行权
-
阻塞
- 等待锁(等待其他对象释放锁之后)
- 无线等待(对象调用wait() 方法,等待去对象调用notify()或者interrupt方法打断、其他对象调用join方法)
- 有限等待(sleep方法、join方法被其他线程独享调用)
-
死亡
sleep() 和 wait() 有什么区别?
- 类的不同:sleep() 来自 Thread,wait() 来自 Object。
- 释放锁:sleep() 不释放锁;wait() 释放锁。
- 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
notify()和 notifyAll()有什么区别?
notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制
- wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
- notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
- notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
线程同步的方法:
多线程并发访问(同时争夺执行权去执行代码),然后变成了一个一个线程按照顺序依次访问
synchronized可以代码块进行加锁,锁的是这个代码中的代码,加锁之后,这个代码块中的代码只能让当前线程来执行,其他线程是不允许进来执行的,除非当前线程把这个代码块给执行完了或者把锁给释放了,那么其他线程才有机会进来执行。
锁:
- 静态方法:当前类对象
- 非静态方法:this
- 代码块:手动指定一个对象
可以最大程度的利用线程,节省资源消耗,它通过利用已有的线程多次循环执行多个任务从而提高系统的处理能力。
通过java.util.concurrent.ThreadPoolExecutor类来创建线程池,一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是Runnable类型对象的run()方法。
线程t1拿着t2线程需要的锁不释放,线程t2拿着t1线程需要的锁不释放。
两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。
预防死锁
设置加锁顺序、设置加锁时限、死锁检测、设置加锁顺序
synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。JDK1.5以后引入了自旋锁、锁粗化、轻量级锁,偏向锁来有优化关键字的性能。
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
synchronized 底层实现原理
synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 对此进行了大刀阔斧地改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。
单例模式:
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例
饿汉模式:(线程安全)
该模式在类被加载时就会实例化一个对象
ublic class Person {
//饿汉式单例
private static Person person = new Person();
private Person(){}
public static Person getInstance(){
return person;
}
}
懒汉模式:(线程不安全)
在需要对象时才会生成单例对象(比如调用getInstance方法)
public class User {
//懒汉式单例,只有在调用getInstance时才会实例化一个单例对象
public static User user;
private User(){
}
public static User getInstance(){
if(user==null){ //step 1.
user = new User(); //step 2
}
return user;
}
}
异常
error和exception有什么区别?
- error 表是一种比较严重的错误情况,这种情况发生之后,程序自身无法处理不能恢复运行,那么就会停止运行。比如说内存溢出。
- exception 是我们已经提前知道的可能会发生的异常情况,这种情况发生之后,如果程序中对异常进行了处理(try-catch),那么程序还可以恢复到正常情况继续往下运行。
throw 和 throws 的区别
- throw:是真实抛出一个异常。
- throws:是声明可能会抛出一个异常。
try-catch-finally 中哪个部分可以省略?
try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。
- 运行时异常
- ArithmeticException 算数运算异常,由于除数为0引起的异常;
- ClassCastException 类型转换异常,不存在子父类关系;
- ArrayStoreException 由于数组存储空间不够引起的异常;
- NullPointerException 空指针异常;
- IndexOutOfBoundsExcention 数组下标越界;
- ConcurrentModificationException 并发修改异常;
- NoSuchElementException 找不到元素异常;
- 编译时异常:
- OException 输入输出流异常
- FileNotFoundException 文件找不到的异常
- ClassNotFoundException 类找不到异常
- DataFormatException 数据格式化异常
- NoSuchFieldException 没有匹配的属性异常
- NoSuchMethodException 没有匹配的方法异常
- SQLException 数据库操作异常
- TimeoutException 执行超时异常
网络:
HTTP状态码
HTTP状态码2xx,3xx,4xx,5xx分别是什么意思?这个是最基本的了,这个得熟练掌握,如果这个状态码都分不清,基本功就很弱了,印象分会大打折扣!
- 200 请求已成功,请求所希望的响应头或数据体将随此响应返回。
- 201 请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 URI 已经随Location 头信息返回
- 202 服务器已接受请求,但尚未处理
- 301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
- 302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
- 303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
- 304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
- 305 (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。
- 307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
- 401 当前请求需要用户验证。如果当前请求已经包含了 Authorization 证书,那么401响应代表着服务器验证已经拒绝了那些证书
- 403 服务器已经理解请求,但是拒绝执行它。与401响应不同的是,身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交
- 404 请求失败,请求所希望得到的资源未被在服务器上发现
- 500 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现。
- 501 服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
- 502 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
- 503 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。
请求方式:
- get:请求指定的页面信息,并返回实体主体
- post:向指定资源提交数据进行处理请求,数据包含在请求体中
- put:从客户端向服务器端传送的数据取代指定的文档的内容
- delete:请求服务器删除指定的页面
- head:类似于get请求,只不过返回的响应中没有具体的内容,用于报头。
- connect:http/1.1中预留给能够将连接改为管道方式的代理服务器。
- options:允许客户端查看服务器的性能
- trace:回显服务器收到的请求,主要用于测试或诊断
- patch:是对put方法的补充,用来对一直资源进行局部更新
http、tcp协议:
-
HTTP协议即超文本传送协议、 HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接,从建立连接到关闭连接的过程称为“一次连接”。
在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。
-
TCP协议是一个面向连接的、可靠的协议。 建立起一个TCP连接需要经过“三次握手”(请求,确认,建立连接)。
TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。所以,HTTP协议是建立在TCP/IP之上的一个协议。
三次握手:
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
反射
Java反射机制的作用?
(1)在运行时判断任意一个对象所属的类。
(2)在运行时判断任意一个类所具有的成员变量和方法。
(3)在运行时任意调用一个对象的方法。
(4)在运行时构造任意一个类的对象
什么是反射?
反射就是动态加载对象,并对对象进行剖析。在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能成为Java反射机制。
什么是 java 序列化?什么情况下需要序列化?
简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。
什么情况下需要序列化:
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
对象拷贝
为什么要使用克隆?
克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了。
如何实现对象克隆?
- 实现 Cloneable 接口并重写 Object 类中的 clone() 方法。
- 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
深拷贝和浅拷贝区别是什么?
- 浅克隆:当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
- 深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将复制
算法:
将数组进行从小到大排序 {8,2,12,6,5,16}
选择排序:
int len = arr.length;
//最小值当前的位置
int now_index;
//最小值应用到什么位置
int should_index;
for (int i=0;i<len-1;i++) {
should_index = i;
now_index = i;
for (int j=i+1;j<len;j++) {
if (arr[j]<arr[now_index]) {
now_index = j;
}
}
if (now_index != should_index) {
int temp = arr[now_index];
arr[now_index] = arr[should_index];
arr[should_index] = temp;
}
}
for (int num : arr) {
System.out.println(num);
}
冒泡排序:
int[] arr = {5,9,8,4,6};
for (int i=0;i<arr.length;i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if (arr[j]>arr[j+1]) {
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
for (int num : arr) {
System.out.println(num);
}
插入排序:
public void test1(){
int[] arr = {2,1,4,9,8,5,3};
System.out.println(Arrays.toString(arr));
//排序操作
//每轮操作完核心目标:
// 1 拿到右边当前要操作的数据
// 2 找出左边一个合适的插入位置
// 3 最后把右边当前操作的数据,插入到左边合适的位置
// 当前在右边拿到的第一个要进行操作的数据
int currentValue;
// 需要把数据在左边插入的位置
int insertPosition;
//外层循环,控制比较的轮数
//同时,变量i的值,还是每一轮我们要操作的右边第一个数字的下标
for(int i=1;i<arr.length;i++){
//提前保存好我们当前要操作的值
currentValue = arr[i];
//假设当前变量i的值就是要插入的位置,因为这个数据有可能是原位置不动的。
insertPosition = i;
//内存循环,控制每轮比较的次数,以及比较的顺序
//同时,变量j的值,还是左边数据中从大到小的下标值
//例如:1 2 4 8 9 | 5 3 这个时候
//我们拿着数字5 要依次和左边的9 8 4 2 1 比较
//9 8 4 2 1的下标顺序就是 3 2 1 0 ,这就是j的值的变化规律
for(int j=i-1;j>=0;j--){
//每次比较,如果发现arr[j]比当前要操作的数字大
if(arr[j]>currentValue){
//就把这个大的数字往后移动一个位置,就是往后赋值
arr[j+1] = arr[j];
//然后记录一下这个位置,因为这个位置很可能是要插入的位置,到底是不是这个位置,需要和下一个数字比较后才知道
insertPosition = j;
}else{
//如果发现一个比currentValue值还小的值,那么这个值的上一个比较的位置就是我们要找的插入的位置,结束当前循环
break;
}
}
//内层循环结束后,我们把当前要操作的值currentValue,插入到指定位置insertPosition,如果insertPosition和当前i值相等,说明当前操作的这个值currentValue是不需要移动的。
if(insertPosition != i){
//进行值的插入
//把当前右边第一个值,插入到左边合适的位置
arr[insertPosition] = currentValue;
}
System.out.println("\t"+Arrays.toString(arr));
}
System.out.println(Arrays.toString(arr));
}
素数:
金字塔:
九九乘法表:
#{}:表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
“%”#{name}”%”
${}:表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
上传的附件