51、设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1,写出程序。
以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题。
public class ThreadTest1 { private int j; public static void main(String args[]){ ThreadTest1 tt=new ThreadTest1(); Inc inc=tt.new Inc(); Dec dec=tt.new Dec(); for(int i=0;i<2;i++){ Thread t=new Thread(inc); t.start(); t=new Thread(dec); t.start(); } } private synchronized void inc(){ j++; System.out.println(Thread.currentThread().getName()+"-inc:"+j); } private synchronized void dec(){ j--; System.out.println(Thread.currentThread().getName()+"-dec:"+j); } class Inc implements Runnable{ public void run(){ for(int i=0;i<100;i++){ inc(); } } } class Dec implements Runnable{ public void run(){ for(int i=0;i<100;i++){ dec(); } } } }
52、ArrayList和Vector的区别
它们都实现了list接口,都是集合类,存储数据是有序的,底层是以数组形式存储,都可以按照索引位置取出元素,并且数据可以重复
(1)同步性:
Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,
(2)数据增长:
Vector增长原来的一倍,ArrayList增加原来的0.5倍。
53、HashMap和Hashtable的区别?
两者都是实现Map接口
1.HashMap允许键和值是null,而hashtable不允许键和值是null
2.HashMap是线程不安全的,HashTable是线程安全的。Hashmap通过get(key)得到元素,那么内部是通过哈希算法做到的,HashMap通过键的hashCode来快速的存取元素。
54.HashMap和HashSet的区别?
1.HashSet底层是用HashMap实现的,HashMap实现map接口,HashSet实现set接口
public HashSet() {
map = new HashMap<E,Object>();
}
2.HashMap添加元素用put(key,value),而HashSet添加元素用add(key)
3.HashMap使用键对象来计算hashcode值,而HashSet使用成员对象来计算hashcode值
4.HashMap存储键值对,而HashSet仅仅存储对象
5.HashMap执行效率比HashSet高。
55.Java中HashMap工作原理?
Java中HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,Value会被更新成新值。
Java中HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此可能会被集合认为是相等的。而且这两个方法也用来发现重复元素。
56.Hashmap多线程下如何解决并发问题?
1:在外部包装HashMap,实现同步机制。
2:使用Map m = Collections.synchronizedMap(new HashMap(...));,这里就是对HashMap做了一次包装
3:使用java.util.HashTable,效率最低(线程安全)
4:使用java.util.concurrent.ConcurrentHashMap,相对安全,效率较高
57.Java中hashmap遍历的2种方法:keySet()和entrySet()
第一种:效率高,一定要经常用
Map map=new HashMap(); Iterator it=map.entrySet().iterator(); while(it.hasNext()){ Map.Entry entry=(Map.Entry)it.next(); Object key=entry.getKey(); Object value=entry.getValue(); }
第二种:效率低,尽量少用
Map map=new HashMap(); Iterator it=map.keySet().iterator(); while(it.hasNext()){ Object key=it.next(); Object value=map.get(key);
58、List 和 Map 区别?
一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List中存储的数据是有顺序,并且允许重复;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。
59、List、Map、Set三个接口存取元素时各有什么特点?
List与Set它们都是单列元素的集合,所以它们有一个功共同的父接口Collection。Set有序存储数据,且不允许有重复的元素,List有序存储数据,且元素可以重复。
Map是双列集合,实现Map接口而不是Collcetion接口,无序存储数据,存储数据为键值对,键不能重复,值可以重复,键和值都可以为null。
60、说出ArrayList,Vector, LinkedList的存储性能和特性
ArrayList和Vector都是使用数组方式存储数据,它们都允许直接按序号索引元素,查询数据快而插入数据慢。
Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,
LinkedList使用双向链表实现存储,插入速度较快。
61、去掉一个Vector集合中重复的元素 ?
Vector newVector = new Vector(); For (int i=0;i<vector.size();i++){ Object obj = vector.get(i); if(!newVector.contains(obj); newVector.add(obj); }
还有一种简单的方式,HashSet set = new HashSet(vector);
62、Collection 和 Collections的区别。
Collection是集合类的上级接口,继承与他的接口主要有Set 和List
Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
63、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
Set里的元素是不能重复的,元素重复与否是使用equals()方法进行判断的。
equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。
64、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
对。
如果对象要保存在HashSet或HashMap中,它们的equals相等,那么,它们的hashcode值就必须相等。
如果不是要保存在HashSet或HashMap,则与hashcode没有什么关系了,这时候hashcode不等是可以的,例如arrayList存储的对象就不用实现hashcode,当然,我们没有理由不实现,通常都会去实现的。
65、java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?
字节流,字符流。字节流继承于InputStream OutputStream,字符流继承于InputStreamReader OutputStreamWriter。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。
66、解释内存中的栈(stack)、堆(heap)和静态存储区的用法?(Java创建对象如何存储的?)
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间;而通过new关键字和构造器创建的对象放在堆空间;程序中的字面量(literal)如直接书写的100、“hello”和常量都是放在静态存储区中。栈空间操作最快但是也很小,通常大量的对象都是放在堆空间,整个内存包括硬盘上的虚拟内存都可以被当成堆空间来使用。
String str = new String(“hello”);
上面的语句中str放在栈上,用new创建出来的字符串对象放在堆上,而“hello”这个字面量放在静态存储区。
67、什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用。
我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,例如,要将java对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个java对象变成某个格式的字节流再传输,但是jre本身就提供了这种支持,我们可以调用OutputStream的writeObject方法来做,如果要让java
帮我们做,要被传输的对象必须实现serializable接口,这样javac编译时就会进行特殊处理,编译的类才可以被writeObject方法操作,这就是所谓的序列化。需要被序列化的类必须实现Serializable接口,其中没有需要实现的方法,implements
Serializable只是为了标注该对象是可被序列化的。
例如在web开发中,如果对象被保存在了Session中,tomcat在重启时要把Session对象序列化到硬盘,这个对象就必须实现Serializable接口。如果对象要经过分布式系统进行网络传输,这就需要在网络上传输对象,被传输的对象就必须实现Serializable接口。
68、描述一下JVM加载class文件的原理机制?
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
69、heap和stack有什么区别?
java的内存分为两类,一类是栈内存,一类是堆内存。栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。
堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如使用new创建的对象都放在堆里,所以它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中。
70、GC是什么? 为什么要有GC?
GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。
71、垃圾回收的优点和原理。并考虑2种回收机制。
Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。
72、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
73、什么时候用assert?
assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。在实现中,assertion就是在程序中的一条语句,它对一个boolean表达式进行检查,一个正确程序必须保证这个boolean表达式的值为true;如果该值为false,说明程序已经处于不正确的状态下,assert将给出警告或退出。一般来说,assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后,assertion检查通常是关闭的。
package com.huawei.interview; public class AssertTest { public static void main(String[] args) { int i = 0; for(i=0;i<5;i++){ System.out.println(i); } //假设程序不小心多了一句--i; --i; assert i==5; } }
74、java中会存在内存泄漏吗?
所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象编程了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。由于Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的,例如下面的代码可以看到这种情况的内存回收:
package com.huawei.interview; import java.io.IOException; public class GarbageTest { public static void main(String[] args) throws IOException { try { gcTest(); } catch (IOException e) { e.printStackTrace(); } System.out.println("has exited gcTest!"); System.in.read(); System.in.read(); System.out.println("out begin gc!"); for(int i=0;i<100;i++){ System.gc(); System.in.read(); System.in.read(); } } private static void gcTest() throws IOException { System.in.read(); System.in.read(); Person p1 = new Person(); System.in.read(); System.in.read(); Person p2 = new Person(); p1.setMate(p2); p2.setMate(p1); System.out.println("before exit gctest!"); System.in.read(); System.in.read(); System.gc(); System.out.println("exit gctest!"); } private static class Person{ byte[] data = new byte[20000000]; Person mate = null; public void setMate(Person other){ mate = other; } } }
java中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
内存泄露的另外一种情况:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。
75、写一个Singleton出来。(面试必考)
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。
第一种:饱汉模式
public class SingleTon {
private SingleTon(){//私有化构造方法
}
//实例化放在静态代码块里可提高程序的执行效率,但也可能更占用空间
//创建私有静态当前对象,注意这是private 只供内部调用
private final static SingleTon instance = new SingleTon();
//返回当前对象,这里提供了一个供外部访问本class的静态方法,可以直接访问
public static SingleTon getInstance(){
return instance;
}
}
第二种:饥汉模式
public class SingleTon {
private SingleTon(){}
//创建静态对象为null
private static instance = null;
//保证返回当前方法线程同步
//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次
//使用时生成实例,提高了效率!
public static synchronized SingleTon getInstance(){
if(instance == null){
instance = new SingleTon();
}
return instance;
}
}
备注:首先说下单例模式好处,然后自己一定要写出2中单例模式,这样可以体现出你技术水平,而不是背面试得出来的,当前若是结合你实际工作中运用那么会更好。
76.
常见笔试题Java代码查错
1.
abstract class Name { private String name; public abstract boolean isStupidName(String name) {} }
答案: 错。编译不通过,abstract method必须以分号结尾,且不带花括号。
2.
public class Something { void doSomething () { private String s = ""; int l = s.length(); } }
答案: 错。局部变量前不能放置任何访问修饰符 (private,public和protected)。final可以用来修饰局部变量,
final如同abstract都是非访问修饰符。
3.
abstract class Something { private abstract String doSomething (); }
答案: 错。abstract的methods不能以private修饰。abstract的methods就是让子类implement(实现)具体细节的,抽象类修饰符为public或者protected。
4.
public class Something { public int addOne(final int x) { return ++x; } }
答案: 错。int x被修饰成final,意味着x不能在addOne method中被修改。
5.
public class Something { public static void main(String[] args) { Other o = new Other(); new Something().addOne(o); } public void addOne(final Other o) { o.i++; } } class Other { public int i; }
答案: 正确。在addOne method中,参数o被修饰成final。如果在addOne method里我们修改了o的指向为o=new Other();
那么就是错误错的,但这里修改的是o的成员变量而o的指向并没有改变。
6.
class Something { int i; public void doSomething() { System.out.println("i = " + i); } }
答案: 正确。输出的是"i = 0"。int i属于实例变量或叫成员变量),实例变量有默认值。int的默认是0。
这里就考察int与Integer的区别了,int是基本数据类型,默认值为0,而Integer是包装类型,默认值为null
7.
class Something { final int i; public void doSomething() { System.out.println("i = " + i); } }
答案: 错。final int i是个final的实例变量或叫成员变量)。final的实例变量没有默认值,必须在构造器结束之前被赋予一个明确的值。可以修改为"final int i = 0;"。
8.
public class Something { public static void main(String[] args) { Something s = new Something(); System.out.println("s.doSomething() returns " + doSomething()); } public String doSomething() { return "Do something ..."; } }
答案: 错。看上去在main里call doSomething没有什么问题,毕竟两个methods都在同一个class里。但仔细看,main是static的。静态方法能直接访问非静态方法。可改成"System.out.println("s.doSomething() returns
" + s.doSomething());"。同理,静态方法不能访问非静态方法的成员变量或者实例变量。
这里考察知识点:静态方法与非静态方法的区别,静态方法时类的方法,直接通过类名调用,而非静态方法必须创建实例对象,通过实例对象引用才能调用里面方法。
9.
此处,Something类的文件名叫OtherThing.java
class Something { private static void main(String[] something_to_do) { System.out.println("Do something ..."); } }
答案: 正确。从来没有人说过Java的Class名字必须和其文件名相同。但public class的名字必须和文件名相同。
10.
interface A{ int x = 0; } class B{ int x =1; } class C extends B implements A { public void pX(){ System.out.println(x); } public static void main(String[] args) { new C().pX(); } }
答案:错误。在编译时会发生错误(错误描述不同的JVM有不同的信息,意思就是未明确的x调用,两个x都匹配(就像在同时import java.util和java.sql两个包时直接声明Date一样)。对于父类的变量,可以用super.x来明确,而接口的属性默认隐含为 public
static final.所以可以通过A.x来明确。
11.
interface Playable { void play(); } interface Bounceable { void play(); } interface Rollable extends Playable, Bounceable { Ball ball = new Ball("PingPang"); } class Ball implements Rollable { private String name; public String getName() { return name; } public Ball(String name) { this.name = name; } public void play() { ball = new Ball("Football"); System.out.println(ball.getName()); } }
这个错误不容易发现。
答案: 错。"interface Rollable extends Playable, Bounceable"没有问题。interface可继承多个interfaces,所以这里没错。问题出在interface Rollable里的"Ball ball = new Ball("PingPang");"。任何在interface里声明的interface variable (接口变量,也可称成员变量),默认为public static final。也就是说"Ball
ball = new Ball("PingPang");"实际上是"public static final Ball ball = new Ball("PingPang");"。在Ball类的Play()方法中,"ball = new Ball("Football");"改变了ball的指向,而这里的ball来自Rollable interface,Rollable interface里的ball是public
static final的,final的object是不能被改变指向的。因此编译器将在"ball = new Ball("Football");"这里显示有错。