1,字符串
new String(“abc”)创建了几个对象?
一个或两个,如果常量池中原来有“abc”,则只创建一个对象;如果常量池中原来没有字符串“abc”,那么就会创建两个对象。
String s="abc";
String s1="ab"+"c";
System.out.println(s==s1);
输出 true ,因为"ab"+"c"在编译器就被转换为"abc",存放在常量区,因此输出结果为true。
2,Java定义了一个基类(java.lang.Throwable)作为所有异常的父类。Error、Exception、RuntimeException都是Throwable的子类,因此都能使用throw抛出。
Java中,常见的运行时异常(runtime exception)有:NullPointerException、ClassCastException、ArrayIndexOutOfBoundsException、ArrayStoreException、BufferOverflowException、ArithmeticException等。。
运行时异常不能用try catch 捕获。
3,Java IO流 流的主要作用是 为了改善程序性能且使用方便。
分为字节流 和字符流。
字节流以字节(8bit)为单位,包含两个抽象类:InputStream输入流 和 OutputStream输出流。
字符流以字符(16bit)为单位,一次可以读取多个字节,包含两个抽象类:Reader输入流 和 Writer输出流。
字节流和字符流最主要的区别是: 字节流在处理输入输出时不会用到缓存,而字符流用到了缓存。
Java IO类在设计时用到了 Decorator(装饰者)模式。
4,NIO(NonBlocking IO)和IO的区别
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读 取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该 缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻 塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
选择器(Selectors)
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
5,Socket通信
常见笔试题
打开ServerSocket连接,
accept 接受连接
read 读取数据
send 发送数据
close 关闭连接
服务器端:
package Java基础知识.Socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
public class Server {
public static void main(String[] args) {
// TODO Auto-generated method stub
BufferedReader br=null;
PrintWriter pw=null;
try{
ServerSocket server=new ServerSocket(2000);
Socket socket=server.accept();
//获取输入流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取输出流
pw = new PrintWriter(socket.getOutputStream(),true);
String s=br.readLine();//获取接收的数据
pw.println(s);//发送相同的数据给客户端
}catch(Exception e){
e.printStackTrace();
}finally{
try{
br.close();
pw.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
客户端:
package Java基础知识.Socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.*;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
BufferedReader br=null;
PrintWriter pw=null;
try{
Socket socket=new Socket("localhost",2000);
//获取输入流与输出流
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw=new PrintWriter(socket.getOutputStream(),true);
//向服务器发送数据
pw.println("Hello ");
String s=null;
while(true){
s=br.readLine();
if(s!=null){
break;
}
}
System.out.println(s);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
br.close();
pw.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
在非阻塞IO (NIO)出现之前,Java通过传统的Socket实现基本的网络通信功能,
NIO采用轮询的方式在处理多线程请求时不需要上下文的切换,而采用多线程的实现方式在线程之间切换时需要上下文的切换,同时也需要进行压栈和出栈的操作,因此,NIO有较高的执行效率。
且NIO采用Buffer保存数据(如ByteBuffer、CharBuffer等),简化了对流数据的管理。
NIO在网络编程中有着非常重要的作用,与传统的Socket方式相比,由于NIO采用了非阻塞的方式,在处理大量并发请求时,使用NIO要比Socket效率高出很多。
6,Java序列化
两种方式:实现Serializable接口(writeObject、readObject)、Externalizable(writeExternal、readExternal)
声明为static或者transient的数据成员不能被序列化。即Java在序列化时不会实例化被static或者transient修饰的变量
因为:static代表类的成员,transient(透明的,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持,代表对象的临时数据)。
什么情况下需要序列化(将对象转化为流 ,便于传输):
1)需要通过网络来发送对象,或对象的状态需要被持久化到数据库或文件中。
2)序列化能实现深复制,即可用复制引用的对象。
package Java基础知识;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.*;
public class 序列化 implements Serializable{
private String name;
private int age;
public 序列化(){
this.name=name;
this.age=age;
}
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 static void main(String[] args) {
// TODO Auto-generated method stub
序列化 p=new 序列化();
ObjectOutputStream oos=null;
ObjectInputStream ois=null;
try{
FileOutputStream fos=new FileOutputStream("perple.out");
oos=new ObjectOutputStream(fos);
oos.writeObject(p);
oos.close();
}catch(Exception e){
序列化 p1;
try{
FileInputStream fis=new FileInputStream("perple.out");
ois=new ObjectInputStream(fis);
p1=(序列化)ois.readObject();
System.out.println("name:"+p1.getName());
System.out.println("age:"+p1.getAge());
ois.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
}
}
反序列化(将流转化为对象)
一个Java程序运行 从上到下的环境次序 是 :
Java程序、JRE/JVM、操作系统、硬件。
java文件被 javac指令编译为 .class后缀的 字节码 文件, 再由JVM执行。java程序经编译后会产生字节码。。
类加载步骤:
1)装载。 找到对应的class文件,然后导入
2)检查 检查待加载的class文件的正确性。
3)准备。 给类中的静态变量分配存储空间。
4)解析。给符号引用转换成直接引用。
5)初始化。 对静态变量和静态代码块 执行初始化工作。
7,垃圾回收(GC Garbage Collection)
回收程序中不再使用的内存。
垃圾回收器负责完成3项任务: 分配内存、确保被引用对象的内存不被错误地回收以及回收不再被引用的对象的内存空间。
只要有一个以上的变量引用该对象,该对象就不会被回收。
垃圾回收器,使用有向图来记录和管理内存中的所有对象,通过有向图来识别哪些对象是“可达的”(有变量引用它就是“可达的”),所有“不可达”对象都是可被垃圾回收的。。
垃圾回收算法:
1)引用计数算法:效率较低,JVM没有采用。
2)追踪回收算法
3)压缩回收算法
4)复制回收算法
5)按代回收算法
是否可以主动通知JVM进行垃圾回收?
不能实时地调用垃圾回收器对某个对象或所有对象进行垃圾回收。可以通过System.gc()方法来“通知”垃圾回收器运行,当然,JVM不会保证垃圾回收器马上就会运行。
8,内存泄漏
有两种情况:一,在堆中申请的空间没有被释放:
二,对象已不再被使用,还依然在内存中保留着。Java中内存泄漏主要为第二种情况。
内存泄漏的原因:
1)静态集合类。 如HashMap、 Vector 。 它们 的生命周期与程序一致,容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。
2)各种连接、 如数据库连接、网络连接 以及 IO连接等。 不再使用时,需要调用close 方法来释放与数据库的连接。
3)监听器
4)变量不合理的作用域。
9, 容器
Collection : Set 、 List
Map
1)Set接口有两个实现类: HashSet 、TreeSet (实现了SortedSet接口,TreeSet容器中的元素是有序的)
2)List 接口的实现类 : LinkedList 、ArrayList 、Vector
3)Map 接口的实现类 : HashMap 、 TreeMap、LinkedHashMap、 WeakHashMap、IdentityHashMap。
HashMap是基于散列表实现的,采用对象的HashCode 可以进行快速查询。 LinkedHashMap 采用列表来维护内部的顺序。 TreeMap基于红黑树的数据结构来实现的,内部元素是按需排列的。
10,迭代器
1)使用容器的迭代器 iterator()方法 返回一个Iterator, 然后通过Iterator 的next()方法返回第一个元素。
2)使用Iterator的 hasNext()方法判断容器中是否还有元素,如果有,可以使用next()方法获取下一个元素。
3)可以通过remove()方法删除迭代器返回的元素。
使用iterator()方法时经常会遇到 ConcurrentModificationException异常,通常是由于使用Iterator遍历容器的同时 又对容器进行增加或者删除的操作导致的,或者由于多线程操作导致。
单线程下的解决办法: 在遍历的过程中把需要删除的对象保存到一个集合中,等遍历结束后再调用removeAll()方法来删除,或者使用 iter.remove()方法。
多线程的解决办法: 使用线程安全容器代替非线程安全的容器,如 ConcurrentHashMap、 CopyOnWriteArrayList等,也可以把对容器的操作放到 synchronized 代码块中,
Iterator 和 ListIterator的区别:
Iterator 只能正向遍历集合,适用于获取、移出元素。 ListIterator 继承自 Iterator,专门针对 List,可以从两个方向来遍历 List,同时支持元素的修改。
11, ArrayList、Vector、LinkedList的区别
ArrayList、Vector:
支持用下标访问元素,同时索引数据的速度比较快,但是在插入删除元素时需要移动元素,执行比较慢。ArrayList、Vector都有一个初始化的容量,Vector 每次默认扩充为原来的2倍,ArrayList默认扩充为原来的1.5倍。
区别在于 : Vector是线程安全的,而ArrayList 是线程非安全的,
LinkedList 采用双向链表实现,插入删除效率高,非线程安全。
12,HashMap、HashTable、ConcurrentHashMap的区别:
ConcurrentHashMap 也是一个基于散列的Map ,但它使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。
ConcurrentHashMap 并不是将每个方法都在同一个锁上同步并使得每次只能有一个线程访问容器,而是使用一种粒度更细的加锁机制来实现更大程度的共享,这种机制称为 分段锁, 在这种机制中,任意数量的读取线程可以并发地访问Map, 执行读取操作的线程和执行写入操作的线程可以并发地访问Map,
ConcurrentHashMap 带来的结果是: 在并发访问环境下将实现更高的吞吐量,而在单线程环境中只损失非常小的性能。不会抛出ConcurrentModificationException,因此不需要在迭代过程中对容器加锁。在ConcurrentHashMap 中没有实现对Map以提供独占访问。在Hashtable和 synchronizedMap中,获得Map的锁能防止其他线程访问这个Map,
大多数情况下,ConcurrentHashMap 来代替同步Map能进一步提高代码的可伸缩性,只有当应用程序需要加锁Map以进行独占访问时,才应该放弃使用ConcurrentHashMap 。
1,尽量将域声明为final类型,除非需要它们是可变的。
2,不可变对象一定是线程安全的。
不可变对象能极大地减低并发编程的复杂性,它们更为简单而且安全,可以任意共享而无须使用加锁或保护性复制等机制。
3,用锁来保护每个可变变量。
4,当保护同一个不变性条件中所有变量时,要使用同一个锁。
5,如果从多个线程中访问同一个可变变量时没有同步机制,那么程序会出现问题。
Map处理冲突的方法: 开放地址法、再hash法、链地址法等,
HashMap使用的是 链地址法来解决冲突,
从HashMap 中通过key 查找value时,首先调用key 的hashCode()方法来获取到key 对应的hash值 h,确定键为key 的所有值存储的首地址,如果 h 对应的key值有多个,那么程序会接着遍历所有key, 通过调用 key的equals()方法来判断 key的内容是否相等,只有当 equals方法返回值为 true时,对应的value才是正确的结果。
13, Collection 和Collections的区别
Collection 是一个集合接口,主要 List 和Set,提供了对集合对象进行基本操作的通用接口方法。
Collections 是针对集合类的 一个包装类。提供一系列的静态方法,Collections类不能实例化。
多线程
1,线程和进程的区别
线程的4种状态: 运行、就绪、挂起、结束。
一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据、堆空间、及一些进程级的资源(如打开的文件)),但是各个线程拥有自己的栈空间,
1)使用多线程减少程序的响应时间。
2)与进程相比,线程的创建和切换开销更小,便于数据共享。
3)使用多线程能简化程序结构,使程序便于理解和维护
2,实现多线程的方法;
1)继承 Thread类, 复写 run方法
2)实现runnable 接口,复写run方法
3,start()方法和run()方法的区别
系统通过调用线程类的start()方法来启动一个线程,只有通过调用线程类的start()方法才能真正实现多线程。
将一个 用户线程设置为守护线程的方法就是在调用start()方法之前调用对象的setDaemon(true)方法。 若以上参数设置为false, 则表示的是用户进程模式。
join()方法: 让调用该方法的线程在执行完run()方法后,再执行join方法后面的代码。