一、基础语法与控制结构
1.取模%运算:计算时先忽略正负号,再根据被除数符号决定结果的符号,即结果符号和被除数符号一致:
12%-5=2,-12%5=-2,-12%-5=-2。
2.switch语句中的变量:
jdk1.7之前可以是byte,short ,int ,char 及其包装类,jdk1.7之后加入String及枚举enum。
3.java中整型默认的是int,浮点默认的是double. 低类型可以赋给高类型的,反过来要强制转换。
double d=5.3e12;正确
float f=11.1; 错误 double类型的11.1 转成 float,是需要强制转换的,可以写成11.1f
int i=0.0; 错误 double类型的0.0 转成 int,也是需要强制转换的
Double oD=3; 错误 int 无法转为封装类型Double,Double oD = 3.0可以, 会把double类型的3.0自动装箱为Double
4.Math.floor() 表示向下取整,返回double类型 (floor---地板) Math.floor(-4.2) = -5.0
Math.ceil() 表示向上取整,返回double类型 (ceil---天花板)Math.ceil(5.6) = 6.0
Math.round() 四舍五入,返回int类型 Math.round(-4.6) = -5
二、数组与字符串
1.java数组复制效率:
System.arraycopy(native方法) > clone > Arrays.copyOf(底层调用System.arraycopy) > for循环逐个复制
2.String不可变,无论是s+=“aa”还是s=“aa”,不是在原对象上修改,而是创建了一个新对象然后再指向s,因此String是值传递。
2.正则表达式
三、类与对象
1.抽象类中有构造函数,接口中无。jdk1.8之后,接口可以定义非抽象方法,default或static修饰的方法。
2.main方法也可以重载!!!
3.== 对象比较的是地址,基本数据类型比较的是值。Integer i=59;int j =59;Integer k=Integer.valueOf(59); i==j比较的是值(将包装类型i拆箱,再与j比较);i==k比较的是地址,在-127到128之间,i是从常量池中取值;在-127到128之间,Integer的valueOf方法也是从常量池中取值,这时i==k;当超过这个范围的时候,会在内存中new一个对象给k,这时i==k为false。String、Byte等包装类也有常量池。Integer m=
new
Integer(
59
),如果是new的对象,无论大小是否在-127到128之间,都不是从常量池中取值,所以m与i、k地址都不一样。
4.一个文件中可以有多个public class。外部类只能有一个public,但是内部类可以是public的。
5.初始化对象的执行顺序:父类静态域(静态变量/静态代码块) > 子类静态域 > 父类普通成员变量构造函数(要先将类中成员初始化)>构造块 > 父类构造方法 > 子类普通成员变量(要先将类中成员初始化)>构造块> 子类构造方法
6.hashcode()和equals()方法
两者都为Object中的方法,用来对比两个对象是否相等。
(以下情况都是未重写hahscode()和equals())
equals()相等的两个对象,它们的hashcode()肯定相等,也就是用equals()比较是绝对可靠的,但是它的效率低;
相同对象的hashcode()一定相等,但是hashcode相等的两个对象不一定是同一个对象(hash冲突),它们的equals不一定相等,也就是hashcode()不是绝对可靠的,但是它效率高;
因此,每当需要对比时,先用hashcode()去对比,如果不一样,那么这两个对象肯定不相等,如果hashcode()相同,再去对比equals(),如果相等,则这两个对象一定相等。
四、集合
1.类图:
2.HashMap不能保证元素的顺序,LinkedHashMap可以保持数据的插入顺序,TreeMap可以按照键值进行排序(可自定比较器)。Hashtable是不允许key和value的值为空的,元素也是无序的,HashMap允许key和value的值为空的,这样的键只有一个,:源代码if(key = null) {putForNullKey(value);},因此不能用get()来判断集合中是否有key,要用containsKey().
Hashtable是线程安全的,HashMap是线程不安全的。
Hashtable直接使用对象的hashcode,而hashmap重新计算hash值。
hashtable初始数组默认大小为11,扩容为old*2+1,hashmap默认是16,且容量一定是2的指数。
hashtable和hashmap遍历都使用了Iterator,但是hashtable还是用了Enumeration的方式。
3.HashMap中采用是分离链表法(也叫链地址法)解决哈希冲突,在JDK1.6
,JDK1.
7
中,HashMap采用位桶+链表实现,即使用链表处理冲突,JDK1.8
中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(
8
)时,将链表转换为红黑树,这样大大减少了查找时间。
Hashset 底层是调用 hashmap 来存储数据的:HashSet会将元素存储在HashMap的key集合中并通过这个来去重,然后为value赋一个static空对象PRESENT。
Thread、ThreadLocal和ThreadLocalMap源码分析
ThreadLocalMap是ThreadLocal的一个静态内部类,它使用开放定/地址法来处理散列冲突。ThreadLocalMap通过key(ThreadLocal类型)的hashcode来计算数组存储的索引位置i。如果i位置已经存储了对象,那么就往后挪一个位置依次类推,直到找到空的位置,再将对象存放。另外,在最后还需要判断一下当前的存储的对象个数是否已经超出了阈值(threshold的值)大小,如果超出了,需要重新扩充并将所有的对象重新计算位置。
4. 线程安全的map(阅读源码)
(1)HashMap,TreeMap 未进行线程同步,是线程不安全的。
(2)HashTable 和 ConcurrentHashMap 都是线程安全的。区别在于他们对加锁的范围不同,HashTable 对整张Hash表进行加锁(synchronized),而ConcurrentHashMap将Hash表分为16桶(segment),每次只对需要的桶进行加锁。
(3)Collections 类提供了synchronizedXxx()方法,可以将指定的集合包装成线程同步的集合。如,
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
Map map = Collections.synchronizedMap(new HashMap());
五、IO
1.类图
2.序列化对象(序列化中的transient和static):具体的序列化由ObjectOutputStream和ObjectInputStream完成。transient修饰的变量不能被序列化(反序列化出来该对象的这个变量取默认值),static变量不管加没加transient都不可以被序列化(static修饰的变量是类变量,与对象无关,序列化是存储对象的状态,所以不能被序列化)。如果是在同一台机器上的同一个程序中,先序列化,再反序列化,该变量的值是有的,因为类变量会先被加载到内存中,反序列化出来时该变量是没有值的,但是内存中之前已经加载了类变量,类变量可以通过类名.变量取调用,也可以通过对象去调用,所以就会使用内存中之前加载的类变量,而不是默认值。如果换台机器或者换个程序,直接从文件中反序列化该对象,那么该变量则为默认值,因为类变量并没有没先加载到内存中去。
六、多线程
1.原子性:原子(atom)本意是“不能被进一步分割的最小粒子”,原子操作(atomic operation)意为"不可被中断的一个或一系列操作" 。原子操作就是不可再分的操作,即一件事要么做就做到底做完,要么就不做。在多线程程序中原子操作是一个非常重要的概念,它常常用来实现一些同步机制。由不可分性可知,原子性是拒绝多线程操作的(只有分解为多步操作,多个线程才能对其操作)。++ x , x ++这样的操作在多线程环境下是需要同步的。 因为这种语句包含三步:从内存中读x的值到寄存器中,对x寄存器加1,再把新值写回x所处的内存地址。 而对于x=y这样的操作,从内存中读取x值和y值都是原子操作的,但是还要把y赋值给x,这两个合起来就变成非原子操作了,因此也需要同步。 x=1只需将1写入内存一条指令,属于原子操作,不需要同步。同步是害怕在操作过程的时候被其他线程也进行读取操作,一旦是原子性的操作就不会发生这种情况。 因为一步到位的操作,其他线程不可能在中间干涉。
2.线程方法:
- sleep()
在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。sleep()使当前线程进入阻塞状态,在指定时间内不会执行。
- wait()
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待(让路给其他线程)。线程会释放掉它占有的锁,从而使别的线程有机会抢占该锁。 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常wait()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
- yield
暂停当前正在执行的线程对象。yield不会释放锁,只是让出CPU,从“运行状态”到“就绪状态”。
yield()只是使当前线程重新回到可执行(就绪)状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会。
- join
等待该线程终止。 底层调用wait,也会释放掉锁。
等待调用join方法的线程结束,再继续执行,会释放掉锁。如:t.join();//用于等待t线程运行结束,若无此句,main线程则会执行完毕,导致结果不可预测。
3.volatile
七、jvm
2. jvm规范大图
3.Java是解释型还是编译型语言?JIT、HotSpot Vm?
八、反射
九、异常
1.java提供了两种异常错误的异常类,分别为ERROR和EXCEPTION,错误和异常,他们的父类是Throwable.
(1)error表示程序在运行期间出现了严重的错误,并且该错误是不可恢复的。(编译器不检查)
(2)Exception是程序本身可以处理恢复的异常,是编译器可以捕捉到的。分两类:运行时异常(runtime exception,也叫非检查异常uncheck)和非运行时异常(checked exception,也叫检查异常,编译异常)。
运行时异常(非检查异常)包括:RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常(检查异常编译异常)包括:RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
2.throws和throw的区别:throws关键字是用在方法或类上,表示可能会发生指定的异常;而throw关键字表示抛出指定的异常,用在方法内。
3.假设利用return语句从try语句块中退出,在方法返回前,finally语句会被执行,如果finally中又return语句,则这个返回值会覆盖原始的返回值。
十、分布式
1.分布式服务器通信不可以使用管道,因为管道是半双工的,虽然可以双向,但是同一时间只能有一个方向传输,而分布式服务器需要双向通信。
十一、web
1.Servlet容器响应Web客户请求流程
HttpServlet容器响应Web客户请求流程如下:
1)Web客户向Servlet容器发出Http请求;
2)Servlet容器解析Web客户的Http请求;
3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息;
4)Servlet容器创建一个HttpResponse对象;
5)Servlet容器调用HttpServlet的service方法,这个方法中会根据request的Method来判断具体是执行doGet还是doPost,把HttpRequest和HttpResponse对象作为service方法的参数传给HttpServlet对象;
6)HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;
7)HttpServlet调用HttpResponse的有关方法,生成响应数据;
8)Servlet容器把HttpServlet的响应结果传给Web客户。
2.servlet生命周期
3.servlet相关类结构
4.servlet在多线程下其本身并不是线程安全的:
如果在类中定义成员变量,而在service中根据不同的线程对该成员变量进行更改,那么在并发的时候就会引起错误。最好是在方法中,定义局部变量,而不是类变量或者对象的成员变量。由于方法中的局部变量是在栈中,彼此各自都拥有独立的运行空间而不会互相干扰,因此才做到线程安全。