面向对象
Java代码优化
解析大文件的xml数据使用sax替代dom4j,使用分段批量提交来完成大数据量的插入。对于大批量字符串的拼接使用stringbuffer或者stringbuilder代替string进行+拼接。
根据业务情况使用缓存减少对数据库的访问。
单线程应尽量使用 HashMap, ArrayList,因为HashTable,Vector使用了同步机制,降低了性能。
在finally块中关闭流,断开连接,释放资源。
避免在循环条件中使用复杂表达式。
抽象类和接口的区别?
共同点:
1. 都可以写抽象方法
2. 都不能被实例化对象
3. 类继承抽象类需要重写抽象方法,类实现接口也必须重写抽象方法
不同点
1. 类和接口是两个不同的概念,抽象类是由abstract修饰的类;接口不是类,用interface定义
2. 接口中只能有抽象方法,抽象类中既可以有抽象方法,又可以有普通方法
3. 类是单继承多实现,实现一个抽象类,需要继承.实现接口需要implements
4. 接口定义的变量默认是publicstatic final的,而抽象类没有这个限制
final和abstract关键字的作用
final和abstract是功能相反的两个关键字,可以对比记忆
1) abstract可以用来修饰类和方法,不能用来修饰属性和构造方法;使用abstract修饰的类是抽象类,需要被继承,使用abstract修饰的方法是抽象方法,需要子类被重写。
2) final可以用来修饰类、方法和属性,不能修饰构造方法。使用final修饰的类不能被继承,使用final修饰的方法不能被重写,使用final修饰的变量的值不能被修改,所以就成了常量。
特别注意:final修饰基本类型变量,其值不能改变。但是final修饰引用类型变量,栈内存中的引用不能改变,但是所指向的堆内存中的对象的属性值仍旧可以改变。
final、finally、finalize的区别
1) final修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载。
2) finally在异常处理时提供finally 块来执行任何清除操作。如果有finally的话,则不管是否发生异常,finally语句都会被执行。
3) finalize方法名。Java 技术允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要清理工作。finalize() 方法是在垃圾收集器删除对象之前被调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。
==和equals的区别和联系
==:
a) 基本类型,比较的是值
b) 引用类型,比较的是地址
c) 不能比较没有父子关系的两个对象
equals()
a) 系统类一般已经覆盖了equals(),比较的是内容。
b) 用户自定义类如果没有覆盖equals(),将调用父类的equals(比如是Object),而Object的equals的比较是地址(return (this == obj);)
c) 用户自定义类需要覆盖父类的equals()
注意:Object的==和equals比较的都是地址,作用相同
访问修饰符public,private,protected,以及不写(默认)时的区别
修饰符 当前类 同 包 子 类 其他包
public √ √ √ √
protected √ √ √ ×
default √ √ × ×
private √ × × ×
常用类
Object类的六个常用方法
1. public boolean equals(java.lang.Object) 比较内容
2. public native int hashCode() 哈希码
3. public java.lang.StringtoString() 变成字符串
4. public final nativejava.lang.Class getClass() 获取类结构信息
5. protected void finalize()throws java.lang.Throwable 垃圾回收前执行的方法
6. protected native Object clone()throws java.lang.CloneNotSupportedException 克隆
7. public final void wait() throwsjava.lang.InterruptedException 多线程中等待功能
8. public final native voidnotify() 多线程中唤醒功能
9. public final native voidnotifyAll() 多线程中唤醒所有等待线程的功能
String、StringBuffer、StringBuilder区别与联系
1) String类是不可变类,即一旦一个String对象被创建后,包含在这个对象中的字符序列是不可改变的,直至这个对象销毁。
2) StringBuffer类则代表一个字符序列可变的字符串,可以通过append、insert、reverse、setChartAt、setLength等方法改变其内容。一旦生成了最终的字符串,调用toString方法将其转变为String
3) JDK1.5新增了一个StringBuilder类,与StringBuffer相似,构造方法和方法基本相同。不同是StringBuffer是线程安全的,而StringBuilder是线程不安全的,所以性能略高。通常情况下,创建一个内容可变的字符串,应该优先考虑使用StringBuilder
String 是最基本的数据类型吗
答:不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type),Java 5以后引入的枚举类型也算是一种比较特殊的引用类型。
集合
Collection集合
List(子接口):元素是有序的,元素可以重复。因为该集合体系有索引。
ArrayList:底层的数据结构使用的是数组结构。特点:查询速度很快。但是增删稍慢。线程不同步。因为底层采用数组的数据结构,而数组中的元素在堆内存中是连续分配的,而且有索引,所以查询快,增删稍慢。在使用迭代器遍历元素时,不能再使用集合的方法操作集合中的元素
调用集合的contains()或remove()方法时底层会调用equals方法,如果存入集合的对象没有实现equals(),则调用Object的equals()方法。
LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步。
Vector:底层是数组数据结构。线程同步。被ArrayList替代了。因为效率低。
Set(子接口):元素是无序(存入和取出的顺序不一定一致),元素不可以重复。
HashSet:底层数据结构是哈希表。是线程不安全的。不同步。
HashSet是如何保证元素唯一性的呢?
是通过元素的两个方法,hashCode和equals来完成。
如果元素的HashCode值相同,才会判断equals是否为true。
如果元素的hashcode值不同,不会调用equals。
注意,对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashcode和equals方法。
LinkedHashSet
TreeSet:可以对Set集合中的元素进行排序。
底层数据结构是二叉树。 保证元素唯一性的依据:compareTo方法return 0.
TreeSet排序的第一种方式:让元素自身具备比较性。元素需要实现Comparable接口,覆盖compareTo方法。这种方式也成为元素的自然顺序,或者叫做默认顺序。
TreeSet的第二种排序方式。当元素自身不具备比较性时,或者具备的比较性不是所需要的。这时就需要让集合自身具备比较性。在集合初始化时,就有了比较方式。创建一个比较器(实现Comparator接口,重写compare方法),在创建TreeSet集合时将比较器传递给集合.
Map集合
该集合存储键值对。一对一对往里存。而且要保证键的唯一性。
Hashtable:底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步的。jdk1.0.效率低。
HashMap:底层是哈希表数据结构,允许使用 null值和 null 键,该集合是不同步的。将hashtable替代,jdk1.2.效率高。
TreeMap:底层是二叉树数据结构。不同步。可以用于给map集合中的键进行排序。和Set很像。其实,Set底层就是使用了Map集合
遍历map集合的方法
第一种遍历map的方法,通过加强for循环map.keySet(),然后通过键key获取到value值
第二种只遍历键或者值,通过加强for循环
第三种Map.Entry<String, String>的加强for循环遍历输出键key和值value
第四种Iterator遍历获取,然后获取到Map.Entry<String, String>,再得到getKey()和getValue()
HashMap和HashTable的区别?
1.Map是一个以键值对存储的接口。Map下有两个具体的实现,分别是HashMap和HashTable.
2.HashMap是线程非安全的,HashTable是线程安全的,所以HashMap的效率高于HashTable.
3.HashMap允许键或值为空,而HashTable不允许键或值为空.
HashMap的底层原理
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。
当新建一个HashMap的时候,就会初始化一个数组。
Entry就是数组中的元素,每个 Entry 其实就是一个key-value对,
它持有一个指向下一个元素的引用,这就构成了链表。
HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。
HashMap 底层采用一个 Entry[] 数组来保存所有的key-value 对,
当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,
再根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,
也会根据hash算法找到其在数组中的存储位置,
再根据equals方法从该位置上的链表中取出该Entry。
Map的遍历有两种方式
public void test2(){
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("zhangsan", 1000);
map.put("lisi", 2000);
map.put("wangwu", 3000);
//方式一
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
for(Map.Entry<String, Integer> m:entrySet){
System.out.println(m.getKey()+","+m.getValue());
}
System.out.println("-----------------");
//方式二
Set<String> keys = map.keySet();
for(String key:keys){
System.out.println(map.get(key));
}
}
Set,List,Collection,Collections的区别?
1.List和Set都是接口,他们都继承于接口Collection,List是一个有序的可重复的集合,而Set的无序的不可重复的集合。Collection是集合的顶层接口,Collections是一个封装了众多关于集合操作的静态方法的工具类,因为构造方法是私有的,所以不能实例化。
2.List接口实现类有ArrayList,LinkedList,Vector。ArrayList和Vector是基于数组实现的,所以查询的时候速度快,而在进行增加和删除的时候速度较慢LinkedList是基于链式存储结构,所以在进行查询的时候速度较慢但在进行增加和删除的时候速度较快。又因为Vector是线程安全的,所以他和ArrayList相比而言,查询效率要低。
ArrayList和LinkedList的区别和联系
两者都实现了List接口,都具有List中元素有序、不唯一的特点。
ArrayList实现了长度可变的数组,在内存中分配连续空间。遍历元素和随机访问元素的效率比较高;
LinkedList采用链表存储方式。插入、删除元素时效率比较高
数组(Array)和列表(ArrayList)有什么区别?
下面列出了Array和ArrayList的不同点:
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
Comparable和Comparator接口是干什么的?列出它们的区别。
Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。
Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。
I/O流
字符流字节流联系区别
字符流和字节流是流的一种划分,按处理照流的数据单位进行的划分。两类都分为输入和输出操作。在字节流中输出数据主要是使用OutputStream完成,输入使的是InputStream,在字符流中输出主要是使用Writer类完成,输入流主要使用Reader类完成。这四个都是抽象类。字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的编码来处理,也就是要进行字符集的转化这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联的。
缓冲流的优点和原理
不带缓冲的流的工作原理:它读取到一个字节/字符,就向用户指定的路径写出去,读一个写一个,所以就慢了。带缓冲的流的工作原理:读取到一个字节/字符,先不输出,等凑足了缓冲的最大容量后一次性写出去,从而提高了工作效率
优点:减少对硬盘的读取次数,降低对硬盘的损耗。
多线程
掌握Executors可以创建的三种线程池的特点及适用范围。
1.继承Thread类,重写父类run()方法
2.实现runnable接口
3.使用ExecutorService、Callable、Future实现有返回结果的多线程(JDK5.0以后)
线程不安全与安全
多个线程访问同一个资源,导致结果和期望值不同,我们就说它是非线程安全的(线程不安全),反之我们就说它是线程安全的。
了解:
a.多个线程访问同一个资源(这里的资源通常指的是全局变量或者静态变量),如果每次运行结果和单线程运行的结果是一样的,就是线程安全的。
b.线程安全问题都是由全局变量及静态变量引起的。
c.若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;
若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程的几种状态。
新建状态(New):新创建了一个线程对象。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
如何实现线程同步?
当多个线程访问同一个数据时,容易出现线程安全问题,需要某种方式来确保资源在某一时刻只被一个线程使用。需要让线程同步,保证数据安全
线程同步的实现方案:同步代码块和同步方法,均需要使用synchronized关键字
线程同步的好处:解决了线程安全问题
线程同步的缺点:性能下降,可能会带来死锁
什么是死锁(deadlock)
两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。
1. 互斥 至少有一个资源处于非共享状态
2. 占有并等待
3. 非抢占
4. 循环等待
解决死锁,第一个是死锁预防,就是不让上面的四个条件同时成立。二是,合理分配资源。
三是使用银行家算法,如果该进程请求的资源操作系统剩余量可以满足,那么就分配。
如何确保N个线程可以访问N个资源同时又不导致死锁?
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。
因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
wait和sleep的区别
wait时会释放锁资源但sleep不会释放锁资源,wait通常和notify以及notifyAll结合使用,需要notify或者notifyAll对其进行唤醒,sleep通常在指定的时间内自动唤醒。
解决线程安全的问题的方案
a.通过加锁(synchronized)的方式解决线程安全问题
b.避免使用全局变量
c.使用ThreadLocal
java线程池
在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。设计模式
单例就是该类只能返回一个实例(实例只会创建一次)
单例所具备的特点:
1.私有的构造函数
2.私有的静态的全局变量
3.公共的静态的方法
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {
//私有的默认构造子
private Singleton1() {}
//已经自行实例化
private static final Singleton1 single = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}
懒汉式单例类.在第一次调用的时候实例化
public class Singleton2 {
//私有的默认构造子
private Singleton2() {}
//注意,这里没有final
private static Singleton2 single=null;
//静态工厂方法
public synchronized staticSingleton2 getInstance() {
if (single == null) {
single = new Singleton2();
}
return single;
}
}
代理模式
代理的共有优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。
Java静态代理:
代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,代理对象可以在调用目标对象相应方法前后加上其他业务处理逻辑。
缺点:一个代理类只能代理一个业务类。如果业务类增加方法时,相应的代理类也要增加方法。
Java动态代理:
Java动态代理是写一个类实现InvocationHandler接口,重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写,这个公共代理类在运行的时候才能明确自己要代理的对象,同时可以实现该被代理类的方法的实现,然后在实现类方法的时候可以进行增强处理。
实际上:代理对象的方法 = 增强处理 + 被代理对象的方法
JDK和CGLIB生成动态代理类的区别:
JDK动态代理只能针对实现了接口的类生成代理(实例化一个类)。此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑
CGLIB是针对类实现代理,主要是对指定的类生成一个子类(没有实例化一个类),覆盖其中的方法 。