JAVA语言相关
1、String是最基本的数据类型吗?
不是。Java的基本类型有:byte short int long float double char boolean。
2、int 和 Integer 有什么区别
int 是基本数据类型,可以直接使用
Integer 是对象,是引用数据类型
Integer是int的包装类,int与integer可以相互转化。因为在一些java应用之中,比如泛型,集合,只接受对象(引用类型),所以必须把int类型转换为Integer实现。
3、String 的基本知识(实例化、比较、内部实现等)
1)== 比较的是引用对象的内存地址(堆内存对象的地址)
2)Object实际上是比较的是对象的内存地址,但是String ,Integer等大部分方法复写了equals函数(比较两个对象的字符串的内容)
3)当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
4)String的直接赋值,先检查池中是否有生成的对象,如果有就直接指向。
package org.oym.java;
public class StrDemo {
public static void main(String[] args) {
String str1 = "hello" ; //放在常量池中
String str2 = new String("hello") ; //放在堆中
String str3 = "hello" ; //指向常量池中的对象
String str4 = str2 ;
String str5 = str2.intern() ;
Object o1 = new Object() ;
Object o2 = new Object() ;
System.out.println(o1.equals(o2)); //false
System.out.println("str1 == str2 "+(str1 == str2)) ; //false
System.out.println("str2 == str4 "+(str2 == str4)); //true ;
System.out.println("str1 == str3 "+(str1 == str3)) ; //true
System.out.println("str1 == str5 "+(str1 == str5)) ; //true
}
}
5) String 是字符串常量。是不可改变对象。如果使用str+ = “hello”的形式,其实是先通过new StringBuffer()对象,然后再进行字符串的append,然后在使用new String()的方式转化为String对象。因此效率比较的慢,而且是新开辟的对象。
6)String类的本质是字符数组char[],其次String类是final的,是不可被继承的,这点可能被大多数人忽略,再次String是特殊的封装类型,使用String时可以直接赋值,也可以用new来创建对象,但是这二者的实现机制是不同的。还有一个String池的概念,Java运行时维护一个String池,池中的String对象不可重复,没有创建,有则作罢。String池不属于堆和栈,而是属于常量池。
7)hashCode()方法是返回字符串内容的哈希码,既然内容相同,哈希码必然相同,那他们就相等了,这个容易理解。
8)String各种实例化的对比
public static void main(String[] args) {
String str = "abc";
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str == str1); //true
System.out.println(str1 == "abc"); //true
System.out.println(str2 == "abc"); //false
System.out.println(str1 == str2); //false
System.out.println(str1.equals(str2)); //true
System.out.println(str1 == str2.intern()); //true
System.out.println(str2 == str2.intern()); //false
System.out.println(str1.hashCode() == str2.hashCode()); //true
System.out.println(str.hashCode() == str1.hashCode()); //true
}
9)String中equals源码
public boolean equals(Object anObject){
//如果是同一个对象
if (this == anObject)
{
return true;
}
//如果传递进来的参数是String类的实例
if (anObject instanceof String)
{
String anotherString = (String)anObject;
int n = count;//字符串长度
if (n == anotherString.count) //如果长度相等就进行比较
{
char v1[] = value;//取每一个位置的字符
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) //对于每一位置逐一比较
{
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
10)Hashcode的实现源码
private final char value[];//定义一个字符数组value,用于存储字符串里面的字符
private final int offset;//定义offset变量表示字符串第一个字符的下标索引
private final int count;//定义count变量表示字符串中字符的个数
private int hash; // Default to 0,默认值为0
public int hashCode() {
int h = hash;
if (h == 0 && count > 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
4.String,StringBuffer与StringBuilder的区别?
1)String 是字符串常量(如上解释)。
2)StringBuffer是对象本身进行操作,在经常要修改字符串的时候,一般推荐使用Stringbuffer。因为StringBuffer的效率比较的高。是线程安全的。在多线程下不用考虑同步问题。
3)StringBuilder 基本操作跟StringBuffer一样。但是不是线程安全的。在单线程下运行效率一般比StringBuffer要快
5.运行时异常与一般异常有何异同?
1)非运行时异常,java编译器要求我们必须处理。使用try..catch进行处理,或者用throws进行抛出
2)运行时异常, 当出现这样的异常时,总是由虚拟机 接管。出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。运行时异常是Exception的子类。异常总是继承Throwable
3)异常关键字:
try Catch finally 三者不能单独使用,三者可以组合成try..catch try...finally try..catch..finally 其中catch语句可以有多个,finally语句只有一个。
6、说出ArrayList,Vector, LinkedList的存储性能和特性
1)ArrayList 是最常用的方式。
2)Vector由于使用了synchronized方法(线程安全),性能上较ArrayList差。
3)而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
7、HashMap和Hashtable的区别。
1)HashMap 允许键值为空,并且HashMap是非线程安全的
2) Hashtable 是线程安全的,使用了synchronized方法(线程安全),效率上低于HashMap。
8、Collection 和 Collections的区别。
Collcetion : 属于集合类的接口,继承它的接口有list,set
Collections : 是集合类的一个帮助类,包含了一系列的排序,搜索,线程安全的静态方法
9、native、transient、strictfp、volitile
1)Native: 使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。
transient :为了安全,有时候不希望将某些变量序列化,此时可以使用transient关键字对其进行声明。
2)Strictfp(不常用):“strictfp 关键字可应用于类、接口或方法。使用 strictfp 关键字声明一个方法时,该方法中所有的float和double表达式都严格遵守FP-strict的限制,符合IEEE-754规范
3)Volatile:
在Java内存模型中,有main memory,每个线程也有自己的work memory (例如寄存器)。
为了性能,一个线程会在自己的 work memory中保持要访问的变量的副本。 Volatile的作用是保证work memory的改动一定在main memory可见,但是并不保证线程安全。因为work memory的从main memory读操作和main memory写操作不是在一个原子操作内完成。
10、对象序列化
1)在jvm结束之后 能够保存这个对象并生成自己码 存储到硬盘 在以后需要使用的时候再将这些字节码还原成对象
2)对象序列化保存的是对象的状态,静态变量不保存在其中
3)在远程方法调用或者网络传输中常常用到对象序列化
4)为了安全,有时候不希望将某些变量序列化,此时可以使用transient关键字对其进行声明。
5)序列化id :简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
11、final, finally, finalize的区别。
1)final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
2)finally是异常处理语句结构的一部分,表示总是执行。
3)finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。
12、如何启动线程?sleep() 和 wait() 有什么区别? (多线程问题)
多线程有两种实现方法,分别是继承Thread类与实现Runnable接口 利用继承Thread类创建的多个线程,虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰。而通过实现Runnable接口可以使多个线程拥有同一资源。所以一般在定义资源对象时实现Runnable接口。(start()方法采用native关键字,调用操作系统本地的方法)
13、死锁
由于两个或多个线程都无法得到相应的锁而造成的所有线程都处于等待锁的状态的现象
public class DeadLock implements Runnable {
Thief t = new Thief();
Man m = new Man();
public DeadLock() {
new Thread(this).start(); //犯人对男人说
m.say(t); //男人对犯人说
}
public void run() {
t.say(m);
}
public static void main(String args[]) {
new DeadLock();
}
}
class Thief {
public synchronized void say(Man m) {
System.out.println("给我钱,放了你的弟弟");
m.rel();
}
public synchronized void rel() {
System.out.println("得到了钱 没有被警察抓");
}
}
class Man {
public synchronized void say(Thief t) {
System.out.println("给我弟弟,给你钱");
t.rel();
}
public synchronized void rel() {
System.out.println("弟弟放了,同时报了警");
}
}
14、如何实现线程间的通信
可以通过3个方法来解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。其中,调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。需要注意的是:由于wait()方法在声明的时候被声明为抛出InterruptedException异常,因此,在调用wait()方法时,需要将它放入try…catch代码块中。另外,只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义。
15、实现简单的生产者、消费者
class BlockQueueDemo {
volatile int index = 0;
final int SIZE=10;
Object[] items = new Object[SIZE];
public synchronized void product() {
while (index >= SIZE) {
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object item = "item - "+index;
items[index]=item;
System.out.println(Thread.currentThread().getName()+",生产" + item.toString());
index++;
super.notify();
}
public synchronized void consumer() {
while (index == 0) {
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
Object item = items[index];
System.out.println(Thread.currentThread().getName()+",消费" + item.toString());
super.notify();
}
}
16、简述线程的几种状态
在Java当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
1)创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态;
2)就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态
3)运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
4)阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend等方法都可以导致线程阻塞。
5)死亡状态。如果一个线程的run方法执行结束,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪状态。
17、实现线程同步的方法有哪些?
线程同步指的是当一个线程占有某一公共资源时,保证其他线程不再占有该资源。
实现同步的方式,可以利用"同步代码块"或“同步方法”来实现同步,还可以使用wait()和notify()配套使用完成等等。如下所示:
synchronized(Object){
//共享资源的 关键代码
}
public synchronized void sale(){
...............
}
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
18、Stop和suspend方法为什么不推荐使用?
stop(),不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。
suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。与resume()配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态
19、线程间的管道通信
信息写入:PipedOutputStream 线程一
信息读出:PipedInputStream 线程二
管道连接:connect()
启动两个线程测试
public class PipedDemo {
public static void main(String args[]) {
Send s = new Send();
Receive r = new Receive();
try {
s.getPipe().connect(r.getPipe());//管道连接
} catch (IOException e) {
e.printStackTrace();
}
new Thread(s).start();
new Thread(r).start();
}
}
class Send implements Runnable { //发送信息,PipedOutputStream
PipedOutputStream out = null;
public Send() {
out = new PipedOutputStream();
}
public PipedOutputStream getPipe() {
return out;
}
public void run() {
Scanner s = new Scanner(System.in);
String str = s.nextLine();
//str = "hello world !!!" ;
try {
out.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Receive implements Runnable { //接受并且输出信息,PipedInputStream
PipedInputStream input = null;
public Receive() {
input = new PipedInputStream();
}
public PipedInputStream getPipe() {
return input;
}
public void run() {
byte b[] = new byte[1024];
try {
input.read(b);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(b));
}
}
20、同步和异步有何异同,在什么情况下分别使用他们?举例说明。
同步: 数据将在线程中共享。例如,某一个变量已经被其他线程修改过,或者修改过的变量,在以后有可能被其他线程用到。那么为了保证数据的一致性,常常使用同步进行共享
异步:如果对象执行了一个需要花很长时间才能执行完成的方法,并且不希望让其他应用等太长的时间,常常就使用异步来提高效率。
21、Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
1)Overload和 override 是java多态性的不同表现。Overload是一个类中,多态性的表现。Override是父类和子类中的多态性的表现。
2)如果子类定义的方法,与父类有相同的名称和参数,我们叫做override。
3)如果在同一个类中,定义方法名相同,参数个数或者参数类型不同的函数,那么这些函数构成重载
4)(仅仅返回值类型不一样不能构成重载)Overloaded的方法是可以改变返回值的类型
22、abstract class和interface有什么区别?
类别
抽象类
接口
成员方法
普通变量,全局变量,抽象方法,普通方法,构造方法
全局变量,抽象方法
继承
一个类只能继承一个抽象类,一个抽象类可以实现多个接口,一个抽象类可以包含多个接口
一个类可以实现多个接口 一个接口不能继承抽象类,只能实现多个接口
特点
单继承
表达一种能力
23、abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?
1) abstract的method 不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系!
2)native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用
3)方法上的synchronized同步所使用的同步锁对象是this,而抽象方法上无法确定this。
4)接口不能继承抽象函数,抽象类可以继承具体的类,抽象类可以有构造函数
24、说出数据连接池的工作机制是什么?
类似于线程池,属于享元模式中的对象池化一类解决方案。连接池是将已经创建好的连接保存在池中,当有请求来时,直接使用已经创建好的连接对数据库进行访问。一般需要考虑最大维持连接数 最大连接数 最大等待时间等参数。
25、 对象的排序问题
总结: 继承comparable接口,实现compareTo的方法。当没有继承comparable接口的实现方法的时候,可以通过Comparator辅助实现,Comparator采用的策略模式,有即插即用的作用。
public class CompareDemo implements Comparable {
private String name;
private int age;
public CompareDemo(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(CompareDemo o) {
if (this.age == o.age)
return 0;
else if (this.age < o.age)
return -1;
else
return 1;
}
public String toString() {
return name + " " + age;
}
public static void main(String[] args) {
CompareDemo cd[] = {new CompareDemo("zs", 11), new CompareDemo("ls", 12), new CompareDemo("ww", 13)};
java.util.Arrays.sort(cd);
for (int i = 0; i < cd.length; i++) {
System.out.println(cd[i]);
}
List list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
Collections.sort(list, new Comparator() {
@Override
public int compare(String o1, String o2) {
return Integer.parseInt(o1)-Integer.parseInt(o2);
}
});
}
}