赞
踩
1、获取本章节PDF请在微信公众号【IT学长】回复“java-01”
2、获取完整Java面试资料请在微信公众号【IT学长】回复“java-pdf”,面试资料包括:
Java是一种完全面向对象的编程语言,具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点,它吸收了c++的优点,去掉了c++中多继承,指针等让人难于理解的概念。java语言采用Unicode编码标准。
JDK(Java Development Kit)是针对 Java 开发员的产品,是整个 Java 的核心,包括了 Java 运行环境 JRE、Java 工具和 Java 基础类库。
Java Runtime Environment(JRE)是运行 JAVA 程序所必须的环境的集合,包含 JVM 标准实现及 Java 核心类库。
JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,是整个 java 实现跨平台的最核心的部分,能够运行以 Java 语言写作的软件程序。
所谓的跨平台就是Java源码经过一次编译以后,可以在不同的操作系统上运行。
原理:经过编译的 .class 文件运行在Java虚拟机上,并非直接运行在操作系统上,只要安装满足不同操作系统的JVM(JAVA虚拟机)即可。
Java和C++都是面向对象语言。因此都有面向对象的基本特性封装,继承,多态。它们的区别如下:
说明 | 所占内存大小(字节) | 取值范围 | 默认值 | |
---|---|---|---|---|
byte | Java中最小的数据类型 | 1 | − 2 7 {-2^7} −27~ 2 7 {2^7} 27-1 | 0 |
short | 短整型 | 2 | − 2 15 {-2^{15}} −215~ 2 15 {2^{15}} 215-1 | 0 |
int | 整型 | 4 | − 2 31 {-2^{31}} −231~ 2 31 {2^{31}} 231-1 | 0 |
long | 长整型 | 8 | − 2 63 {-2^{63}} −263~ 2 63 {2^{63}} 263-1 | 0L |
float | 单精度 | 4 | -3.40E+38 ~ +3.40E+38 | 0 |
double | 双精度 | 2 | -1.79E+308 ~ +1.79E+308 | 0 |
char | 字符型 | 2 | 0~65535 | null |
boolean | 布尔型 | 1 | true,false | false |
类,接口类型,数组类型,枚举类型,注解类型
基本数据类型在被创建时,会在栈上分配空间,直接将之存储在栈中。而引用数据类型在被创建时,首先会在栈上分配空间,将其引用存在栈空间中,然后在堆中开辟内存,值存放在堆内存中,栈中的引用指向堆中的地址。
在Java5以前,expr支持 byte,short,int,char 四种数据类型,在Java5以后,又多了枚举enum类型,Java7又增加了string类型,到目前并比支持long类型。
int 是基本数据类型,默认值是0
integer是引用类型,是int 的包装类,默认值是 null
自动拆箱:将包装类型自动转化为对应的基本数据类型
自动装箱:将基本类型自动转化为对应的引用类型
计算2^3效率最高的方法是:2<<(3-1)
四舍五入的原理是在原来的参数上加0.5,然后进行向下取整。
不正确。3.4是双精度类型,赋值给float需要强制类型转换,float f=(float)3.4,可以写成 float f=3.4F。
short s1 = 1; s1 = s1 + 1 不正确。因为 1是int类型,因此 s1+1 也是int类型,在执行 s1=s1+1 时,需要将int类型的s1+1赋值给short类型的s1,大转小可能会有精度损失,无法显示转化。
short s1 = 1; s1 += 1 正确。因为 s1+=1 相当于s1=(short)(s1+1),存在隐含的强制类型转换。
定义:注释是用来解释说明程序的文字。分为:
Java中的访问修饰符有:public,private,protected,以及不写(默认)。
重写: 至少发生在两个类中,并且类与类具有继承或者实现关系,表示子类中的方法具有与父类方法中完全相同的方法名称,返回值,参数。子类中的方法覆盖父类的方法,体现了多态性。
重载: 发生在同一个类中,多个方法名称相同,参数类型,个数和顺序不同的方法发生重载现象,与返回值无关。
&:无论左边true还是false,右边也会进行判断。
&&:如果左边为false,有边就不会进行判断,因此&&比&效率高。
注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。
public Person(String name, int age) {
this.name = name;
this.age = age;
}
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this(name);
this.age = age;
}
}
在java中,final关键字可以修饰类,变量和方法。被final修饰后以下特点:
在外面的循环语句前定义一个标号
ok:
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
system.out.println("i="+i+",j="+j);
if(j==5)break ok;
}
}
如果两个对象equals()方法相等则它们的hashCode返回值一定要相同,如果两个对象的hashCode返回值相同,但它们的equals()方法不一定相等。
两个对象的hashCode()返回值相等不能判断这两个对象是相等的,但两个对象的hashcode()返回值不相等则可以判定两个对象一定不相等。
接口中的方法都是抽象的,抽象类中可以有抽象方法,也可以有非抽象方法。
在JDK1.8以后接口中也可以有用defaule关键字修饰的普通方法
接口是一种规范,Java中的接口:interface
静态变量 | 非静态变量 | |
---|---|---|
调用方式 | 静态变量只能通过 “ 类名.变量名 ” 调用 | 非静态变量通过实例化对象名调用 |
共享方式 | 静态变量是全局变量,被类的所有实例化对象共享 | 非静态变量是局部变量,不共享 |
相互访问方式 | 静态变量无法访问非静态变量 | 非静态变量可以访问静态变量 |
值传递: 在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中。
引用传递: 引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间,而引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
方法 | 说明 |
---|---|
split() | 把字符串分割成字符串数组 |
indexOf() | 从指定字符提取索引位置 |
append() | 追加字符或字符串 |
trim() | 去掉字符串两端的空格 |
replace() | 替换 |
hashCode() | 返回字符串的哈希值 |
subString() | 截取字符串 |
equals() | 比较字符串是否相等 |
length() | 获取字符串长度 |
concat() | 将指定字符串连接到此字符串的结尾 |
"=="比较的是两个字符串的内存地址。 "equals"比较的是两个字符串的实际值
String: 字符串常量,底层用 final 关键字修饰,底层实际在维护 char 类型的字符数组,当每次对String进行改变时,都需要生成一个新的String对象,然后将指针指向一个新的对象。
//底层用 final 关键字修饰,底层实际在维护 char 类型的字符数组
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
StringBuilder: 字符串变量,线程安全,用于多线程操作
StringBuffer : 字符串变量,非线程安全,用于单线程操作
final: 修饰符,java 中的关键字。可用于修饰类,变量,方法,有最终的意思。
修饰的对象 | 说明 |
---|---|
final 修饰类 | 表明该类不能被其他类所继承,但要注意:final类中所有的成员方法都会隐式的定义为final方法。 |
final 修饰变量 | final成员变量表示常量,只能被赋值一次,赋值后其值不再改变 |
final 修饰方法 | final 修饰的方法不能被重写 |
finally: finally 是在异常里经常用到的, 就是 try 和 cach 里的代码执行完以后,必须要执行的方法,我们经常在 finally 里写一些关闭资源的方法,比如说关闭数据库连接,或者关闭 IO 流。
finalize: finalize是方法名,Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
Java中不允许多继承,比如类A不能同时继承类B和类C,若要有次类需求,考虑用接口。
HashMap 和 Hashtable是Map接口的实现类,它们大体有一下几个区别:
实现类 | 特征 |
---|---|
HashMap | 线程不安全的键值对集合,允许 null 值,key 和 value 都可以 |
HashTable | 线程安全的键值对集合,不允许 null 值,key 和 value 都不可以 |
TreeMap | 能够把它保存的记录根据键排序,默认是按升序排序 |
在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的键值对会被放在同一个位桶里,当桶中元素较多时,通过key值查找的效率较低。
而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8),时,将链表转换为红黑树,这样大大减少了查找时间。
当我们创建 hashmap 时 会先创建一个数组,当我们用 put 方法存数据时,先根据 key 的 hashcode 值计算出 hash 值,然后用这个哈希值确定在数组中的位置,再把 value 值放进去,如果这个位置本来没放 东西,就会直接放进去,如果之前就有,就会生成一个链表,把新放入的值放在头部,当用 get 方法取值时,会先根据 key 的 hashcode 值计算出 hash 值,确定位置,再根据 equals 方法从该位置上的链表中取出该 value 值。
对象Hash的前提是实现equals()和hashCode()两个方法,那么HashCode()的作用就是保证对象返回唯一hash值,但当两个对象计算值一样时,这就发生了碰撞冲突。如下将介绍如何处理冲突,当然其前提是一致性hash。
解决hash碰撞有以下几种方法:
开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1) 其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1,称线性探测再散列。如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。如果di取值可能为伪随机数列。称伪随机探测再散列。
当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止。
将所有关键字为同义词的记录存储在同一线性链表中。如下:
当hashmap中的元素个数超过数组大小的loadFactor倍时,就会进行数组扩容,loadFactor的默认值为 0.75,也就是说,默认情况下,数组大小为 16,那么当hashmap 中元素个数超过 16*0.75=12 的时候,就把数组的大小扩展为2*16=32,即扩大一倍。然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 hashmap 中元素的个数,那么预设元素的个数能够有效的提高 hashmap 的性能。比如说,我们有 1000 个元素new HashMap(1000), 但是理论上来讲 new HashMap(1024)更合适,不过上面annegu 已经说过,即使是 1000,hashmap 也自动会将其设置为 1024。 但是newHashMap(1024)还不是更合适的,因为 0.75*1000<1000,也就是说为了让0.75*size>1000, 我们必须这样 newHashMap(2048)才最合适,避免了resize 的问题。
ArrayList 使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者 删除元素时需要设计到位移操作,所以比较慢。
LinkedList 使用双向链接方式存储数据,每个元素都记录前后元素的指针, 所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快,然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或 后几个元素速度比较快。
ArrayList 与 LinkedList 都是线程不安全的。
ArrayList是经常会被用到的,一般情况下,使用的时候会像这样进行声明:
List arrayList = new ArrayList();
如果像上面这样使用默认的构造方法,初始容量被设置为10。当ArrayList中的元素超过10个以后,会重新分配内存空间,使数组的大小增长到16。
可以通过调试看到动态增长的数量变化:10->16->25->38->58->88->…
也可以使用下面的方式进行声明:
List arrayList = new ArrayList(4);
将ArrayList的默认容量设置为4。当ArrayList中的元素超过4个以后,会重新分配内存空间,使数组的大小增长到7。
可以通过调试看到动态增长的数量变化:4->7->11->17->26->…
那么容量变化的规则是什么呢?请看下面的公式:
((旧容量 * 3) / 2) + 1
使用 Vector
使用 Collections 中的方法 synchronizedList 将 ArrayList 转换为线程安全的 List
使用 java.util.current 包下的 CopyOnWriteArrayList(推荐)
这个NIO是JDK1.7以后有的,它们俩的主要区别是 :
start()方法被用来启动新创建的线程,而且 start()内部调用了 run()方法,这和直接调用 run()方法的效果不一样。当你调用 run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它
才有用武之地。而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一
个线程能继续运行。
Java 程序中 wait 和 sleep 都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放 CPU 资源或者让当前线程停止执行一段时间,但不会释放锁。
多个线程同时运行一段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。同一个实例对象在被多个线程使用的情况下也不会出现计算失误,也是线程安全的,反之则是线程不安全的。
Volatile: 一个共享变量(类的成员变量、类的静态成员量)被volatile修饰之后,那么就具备了两层语义:
a.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
b.禁止进行指令重排序。但是它并不能保证操作的原子性。
应用场景:在只涉及可见性,针对变量的操作只是简单的读写(保证操作的原子性)的情况下可以使用volatile来解决高并发问题,如果这时针对变量的操作是非原子的操作,这时如果只是简单的i++式的操作,可以使用原子类atomic类来保证操作的原子性(采用CAS实现),如果是复杂的业务操作,那么舍弃volatile,采用锁来解决并发问题(synchronized或者Lock)。
实线程一般具有五种状态,即创建、就绪、运行、阻塞、终止。
a.等待阻塞:运行( running )的线程执行 o.wait ()方法,JVM 会把该线程放 入等待队列( waitting queue )中。
b.同步阻塞:运行( running )的线程在获取对象的同步锁时, 若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
c.其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t.join ()方法,或者发出了 I / O 请求时,JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
//同步代码块格式:
synchronized(监视对象){
//需要同步的代码 ;
}
解释:监视对象有三种:对象、String、.class 文件(只要是不变的对象都可以做监 视对象)
//同步方法定义格式:
synchronized 方法返回值 方法名称(参数列表){
}
//在方法上加 synchronized,是把当前对象做为监视器
Lock lock = new ReentrantLock();//(可以在类中直接 new)
lock.lock(); //中间的代码块进行加锁 lock.unlock();
Synchronized的局限性:
使用 volatile 关键字。基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式。
使用Object类的wait() 和 notify() 方法。Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
注意: wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数 有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
从JDK1.5 开始,JavaAPI 提供了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个 任务;数目固定的线程池或者是缓存线程池。
建议自己通过 new 关键字创建 newThreadPoolExecutor
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。