加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

【IT面试题——语言基础类】Java(二)

(2012-06-14 10:18:24)
分类: 技术类原创
11.Java序列化的原理及认识

简介
Java中的序列化机制能够将一个实例对象的状态信息写入到一个字节流中,使其可用通过socket进行传输或持久化存储到数据库或文件系统中,然后在需要的时候通过字节流中的信息进行对象的重构。序列化机制在java中有着广泛的应用,EJB,RMI等技术都是以此为基础的。
Java的序列化机制只序列化对象的属性值,而不会去序列化方法。因为方法是不带状态的,只要知道类定义就可以load到这个类的方法。序列化真正保存的只是对象属性的值以及对象的类型

大部分情况下,开发人员只需要了解①被序列化的类需要实现Serializable接口②使用ObjectInputStream和ObjectOutputStream进行对象的读写。这两个类可以通过FileInputStream和FileOutputStream进行文件读写,或通过socket进行二进制序列传输。

参考以下文章继续深入学习:

12.Java中Collection框架实现比较需要实现什么接口?用Java实现一种排序。

可以通过实现java.lang.Comparable或者java.util.Comparator接口。
前者需要实现的方法是compareTo(Object o),而后者是compare(Object o1,Object o2)。
Comparable表明该类与同类对象之间是可以比较的,这个类对象组成的集合就可以直接使用sort()方法排序。
Comparator则可以看成是将算法和数据分类的算法实现,相比而言Comparable与类已经绑定了。实现了Comparator接口的对象组合(数组或Collection对象)则可以用sort(Object<T>,Comparator)方法来排序。
关于自己实现排序的部分可以参考 算法题 部分

更多关于集合框架的知识
或点击这里下载 

13.Java中类中变量的加载顺序,以及静态代码块的作用?

Java类中的初始化顺序为 静态变量、静态块->变量、块->构造器,每次加载一个类,就对这个类按该顺序进行初始化,其中静态变量和静态块仅初始化一次。而静态变量和静态块的先后顺序则取决于它们在类中的定义顺序。
在加载一个类时,按上面的顺序首先初始化所有的静态变量,然后执行所有的静态代码块,下来才是非静态变量、执行非静态块,最后再执行构造器(constructor)。
考虑继承的情况下,又是怎么样的顺序呢?
JVM会先按这样的顺序执行初始化:
父类静态变量、静态代码块->
子类静态变量、静态代码块->
父类非静态变量、代码块、构造器->
子类非静态变量、代码块、构造器

静态代码块的作用是用来提供静态变量的统一初始化(Thinking in Java)。
具体代码可以参考TIJ中static data initialization一节的例子。

参考:
Thinking in Java.

14.HashMap和Hashtable的区别是什么?

(注意这里Hashtable的t是小写的……)
Hashtable类继承自Dictionary类,而HashMap是Java1.2引入的Map接口的一个实现。
HashMap允许空键值(null key)。
最大的区别是,Hashtable的方法是Synchronized,而HashMap不是,这意味着多线程访问HashMap的时候,需要自己实现同步;同时由于没有同步方法,HashMap的效率可能要高于Hashtable。
上面的Hashtable是synchronized意味着在一个时间点只能有一个线程可以修改哈希表,任何线程在执行Hashtable的更新操作前需要获取对象锁,其他线程等待锁的释放。
自己实现HashMap同步的时候,一般通过如下方法:
Map m = Collections.synchronizedMap(nnew HashMap());

在并发访问量比较大的情况下,可以考虑使用ConcurrentHashMap。

更多参考文章:

15.接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?

接口可以继承接口,但继承的时候要用extends而不是implements。
抽象类可以实现接口。
抽象类可以继承实体类(前提是实体类要有明确的构造方法,子类会默认调用这个实体类的构造方法,所谓明确指的是子类可以访问到的)。

16.安装JDK之后如何设置JDK系统的PATH,CLASSPATH?他们的作用是什么?

1.PATH环境变量是系统在执行命令时的查找路径,如javac/java等,系统根据路径去寻找对应的exe可执行文件。将 jdk1.X/bin文件夹设置为路径,则可以在任意目录下执行javac/java等工具了。
2.CLASSPATH变量的作用是指定JVM的类搜索路径,比如java xxx.class命令,要使用这个已经编译好的类xxx.class就得知道它的路径才行。有一些默认的包必须加载(jdk安装目录下lib目录种的dt.jar和tool.jar)。

17.关于Java中的多线程。

已经有文章总结的很好了。这里就不再做搬运工的活,直接上连接:

Java多线程编程总结  51CTO上的一篇博文,总结的很详细,思路清晰,很适合学习。
Java多线程总结      cnblog上的一篇博文,来自博客《一个人的旅行》,有大量代码示例参考。

18.Java中的I/O
(不知不觉从面试题拐到技术基础分类学习了。。。这样也好)

同样上连接:
Java中的IO整理      from cnblog。仍然来自博客《一个人的旅行》,覆盖了javaIO的全部内容,但没有涉及到新IO。同样有大量代码示例讲解,是参考学习Java IO的好文章。
NIO入门             from IBM developWorks. 这是一系列的Java NIO的实用教程,从高级概念到底层的编程细节,非常详细的介绍了NIO库,非常推荐!

19.Java中的反射机制是什么?

反射是Java语言的一个特性,它允许程序在运行时(不是编译时)来进行自我检查并且对内部的成员进行操作。例如允许获取一个java的类的所有成员变量和方法并显示出来,然后可以进一步直接对变量进行操作,或者调用其中的方法(但不能看到方法的具体定义),甚至还可以生成对象实体。
一个最常见的例子就是使用JDBC时候,必然用到的这句:
Class.forName("com.mysql.jdbc.Driver");
这句话通过查找类名把该类加载到了JVM中。


没连接说个毛,上连接:
Java反射机制的学习    from blogjava.该文的参考文献才是学习JAVA反射的宝库
Java反射机制初探      from cnblogs. 
Java反射详解          from cnblogs. 一个人的旅行,同样案例驱动,适合学习。

20.Java中的实现多线程有哪两种方法?它们的区别是什么?

实现多线程有两种方法:
1. 继承Thread类并实现run方法。
2. 实现Runnable接口并实现Run方法。

实际上Thread类也实现了Runnable接口,定义如下:
public class Thread extends Object implements Runnable

但是,Runnable接口中没有start方法所以得用Thread.start来启动实现了Runnable的类。

一般情况下在实战项目中多线程常用Runnable方法,这是为什么呢?看下面的例子就明白了:

package multithread;

    public class MyThreadUsingThread extends Thread {
        private int num = 0 ;
        public void run(){
            num++;
            System.out.println(num);
        }
    }

package multithread;

    public class MyThreadUsingRunnable implements Runnable{
        private int num = 0;
        public void run(){
            num++;
        System.out.println(num);
        }
    }

package multithread;

public class CompareThreadAndRunnable {
    public static void main(String[] argsthrows InterruptedException{
        for(int i = 0;i < 10i++){
            Thread t = new MyThreadUsingThread();
            t.start();
        }
        
        Thread.sleep(1000);

        MyThreadUsingRunnable r = new MyThreadUsingRunnable();
        for (int i = 0i < 10i++) {
            (new Thread(r)).start();
        }
    }
}


上面的程序中,前者输出的是10个1,后者输出的是1到10。
也就是说,前者是10个独立的实例分别启动线程,而后者是一个实例多次启动线程



实际上,后者输出的不一定是1到10,还有可能会发生重复读然后写出两个相同的数的情况,即使把num设为static类型也无法避免,有可能是因为多核CPU的原因,许多文章中都没有意识到这一点,下面给出验证该错误的方法:

还是利用本题中的例子,修改MyThreadUsingRunnable,在其run()方法中添加大量的冗余运算以延长线程运行时间,提高线程间冲突错误发生的概率,冗余代码可以是
1. 大量重复的num++;num--;
2. 大量重复的形如num = num + 256;num = num - 256;的代码
3. 上述两种的重复综合等
总之目的就是num的逻辑输出值不变,但延长线程的运行时间。

然后,利用下面21题的截获控制台输出流的方法,利用Set或其他自定义方法验证是否有重复的数字输出。
笔者在run方法中添加了100多行上述冗余代码后,启动1000个Runnable线程同时更新数据(不出冲突的话num会增加到1000),冲突出现率高于七成。这说明这种方式仍然存在多线程的资源访问共享问题,实战中多线程下的数据访问需要更进一步的机制如加锁同步等方式实现,一个例子如下,把run()中的代码块变为同步代码块:
synchronized(this){
num++;
num--;
...
}



21.如何截获Java控制台的输出流以便进行进一步处理?
 
利用管道流PipedOutputStream,PipedInputStream即可,原理图如下:

下面仅就从控制台读取进行代码实例讲解,更普适“从输出流中读取”相关主题可以参考 Merlin Hughes的《彻底转变流》

利用管道流截取控制台输出(即System.out)主要步骤:

1. 建立管道
    PipedInputStream pipeIS = new PipedInputStream();
     PipedOutputStream pipeOS = new PipedOutputStream();
   try {
           pipeOS.connect(pipeIS);
} catch (IOException e{
         e.printStackTrace();
}

 2.保存原来的控制台输出流以便后面恢复控制台输出
   PrintStream oldOut = System.out;

 3.将现有标准输出指向管道输出流
   PrintStream ps = new PrintStream(pipeOS);
    System.setOut(ps);
  
 4.建立Reader从管道输入流读取数据
   InputStreamReader iReader = new InputStreamReader(pipeIS);
    BufferedReader bReader = new BufferedReader(iReader);
    String str = bReader.readLine();   
 需要注意的是bReader.readLine()会在输出流关闭之后抛出IOException,错误信息是"Write end dead"。在这里需要自己处理下异常才行。

 完整的示例代码如下,对20题中的CompareThreadAndRunnable类重新改写,利用管道把两个线程在控制台的输出全部读取,然后再输出。

-----------------------------------代码示例-------------------------------------------------
____________________________________________________________________________________________
——————package multithread———class CompareThreadAndRunnable—————————————

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;

public class CompareThreadAndRunnable {
    public static void main(String[] argsthrows InterruptedException{        
            
        
        PipedInputStream pipeIS = new PipedInputStream();
        PipedOutputStream pipeOS = new PipedOutputStream();
        
        try {
            pipeOS.connect(pipeIS);
        } catch (IOException e{
            e.printStackTrace();
        }

        PrintStream ps = new PrintStream(pipeOS);
        PrintStream oldOut = System.out;
        System.setOut(ps);
        
        for(int i = 0;i < 10i++){
            Thread t = new MyThreadUsingThread();
            t.start();
        }
        
        Thread.sleep(1000);

        MyThreadUsingRunnable r = new MyThreadUsingRunnable();
        for (int i = 0i < 10i++) {
            (new Thread(r)).start();
        }
        //截取控制台输出

        InputStreamReader iReader = new InputStreamReader(pipeIS);
        BufferedReader bReader = new BufferedReader(iReader);
        
        String str;
        ArrayList<String> strArray = new ArrayList<String>();
        try{
            str = bReader.readLine();
            for (; str.length() > 0;) {
                strArray.add(str);
                str = bReader.readLine();
            }
        }catch (IOException e{
            if(!e.getMessage().equalsIgnoreCase("Write end dead")){
                e.printStackTrace();
            }
        }

        System.setOut(oldOut);
        System.out.println("length is " + strArray.size());
        for (int i = 0i < strArray.size(); i++) {
            System.out.println("ME: " + strArray.get(i));
        }

    }
}
-----------------------------------代码示例结束-------------------------------------------


核心部分参考了《在Java程序中截获控制台输出


0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有