Java笔试面试题整理第七波

1、super的作用

    在Java中super指代父类对象(直接父类),也就是说,super相当于是一个直接new出来的父类对象,所以可以通过它来调用父类的那些非private修饰的变量、方法(对于我们普通new出来的对象来说,也就只能访问那些非private的成员变量、方法了,这里的访问是指通过“对象名.变量名或方法名”的形式)。所以,super这个对象也就是一个普通对象,同样遵循访问控制修饰符的准则
    然而,对于子类来说,子类通过继承就直接拥有了父类的非private变量、方法,也就可以在子类中直接使用,再加一个super来修饰,岂不是显得有点多余了?正常情况下来说,是有点多余了(但是可以明确提示我们这是调用的父类变量或方法),但super关键字主要是用在以下两种情况中:
(1)发生了重写的情况
    重写也分为两种情况,一个是重写了父类的方法;一个是重写了父类的成员变量;
重写父类方法的情况
public class A {
    String name = "lly";
    protected void getName(){
        System.out.println("父类getName->"+ name);
    }
}
public class B extends A {
    String nameB = "llyB";
    @Override
    protected void getName() {
        System.out.println("子类getName->"+nameB);
        super.getName();
    }
    public static void main(String[] args) {
        B b = new B();
        b.getName();
    }
}
打印如下:
子类getName->llyB
父类getName->lly

在子类B中,我们重写了父类的getName方法,如果在重写的getName方法中我们去调用了父类的相同方法,必须要通过super关键字显示的指明出来
如果不明确出来,按照子类优先的原则,相当于还是再调用重写的getName()方法,此时就形成了死循环,执行后会报java.lang.StackOverflowError异常。

重写父类变量的情况
我们将B类简单改造一下:
public class B extends A {
    String name = "llyB";
    @Override
    protected void getName() {
        name = super.name;
        System.out.println("子类getName->"+name);
    }
    public static void main(String[] args) {
        B b = new B();
        b.getName();
    }
}
此时子类B中有一个和父类一样的字段(也可以说成父类字段被隐藏了),为了获得父类的这个字段我们就必须加上super,如果没有加,直接写成name = name;不会报错,只是会警告,表示此条语句没有任何意义,因为此时都是访问的子类B里面的那么字段。

我们通过super是不能访问父类private修饰的变量和方法的,因为这个只属于父类的内部成员,一个对象是不能访问它的private成员的。

(2)在子类的构造方法中
编译器会自动子类构造函数的第一句加上 super()来调用父类的无参构造器;此时可以省略不写。如果想写上的话必须在子类构造函数的第一句,可以通过super来调用父类其他重载的构造方法,只要相应的把参数传过去就好。

因此,super的作用主要在下面三种情况下:
1、调用父类被子类重写的方法;
2、调用父类被子类重定义的字段(被隐藏的成员变量);
3、调用父类的构造方法;
其他情况,由于子类自动继承了父类相应属性方法,关键字super可以不显示写出来。

2、关于构造方法

如果一个类中没有写任何的构造方法JVM会生成一个默认的无参构造方法。在继承关系中,由于在子类的构造方法中,第一条语句默认为调用父类的无参构造方法(即默认为super(),一般这句话省略了)。所以当在父类中定义了有参构造函数,都是没有定义无参构造函数时,IDE会强制要求我们定义一个相同参数类型的构造器。这也是我们在Android中自定义组件去继承其他View是经常被要求定义几个构造函数的原因。

以下子类B的情形是错误不能通过编译的:
public class A {
    public A(String s){  }
}
public class B extends A {    //编译错误,JVM默认给B加了一个无参构造方法,而在这个方法中默认调用了super(),但是父类中并不存在该构造方法
    String name = "llyB";
}
public class B extends A {    //同样编译错误,相同的道理,虽然我们在子类中自己定义了一个构造方法,但是在这个构造方法中还是默认调用了super(),但是父类中并不存在该构造方法
    String name = "llyB";
    public B(String s){}
}
此时就需要显示的去调用父类构造方法了,如下:
public class B extends A {    //正确编译
    String name = "llyB";
    public B(String s){
        super(s);
    }
}
所以,只要记住, 在子类的构造方法中,只要里面没有显示的通过super去调用父类相应的构造方法,默认都是调用super(),即无参构造方法,因此要确保父类有相应的构造方法

3、transient关键字用法

【参考:http://www.cnblogs.com/lanxuezaipiao/p/3369962.html】
当用transient关键字修饰一个变量时,这个变量将不会参与序列化过程。也就是说它不会在网络操作时被传输,也不会再本地被存储下来,这对于保护一些敏感字段(如密码等...)非常有帮助。

当我们一个对象实现了Serializable接口,这个对象的所有字段和方法就可以被自动序列化。当我们持久化对象时,可能有一个一些特殊字段我们不想让它随着网络传输过去,或者在本地序列化缓存起来,这时我们就可以在这些字段前加上transient关键字修饰,被transient修饰变量的值不包括在序列化的表示中,也就不会被保存下来。这个字段的生命周期仅存在调用者的内存中
如下一个例子:
public class UserBean implements Serializable{
    private static final long serialVersionUID = 856780694939330811L;
    private String userName;
    private transient String password;    //此字段不需要被序列化
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
测试类:
public class Test {
    public static void main(String[] args) {
        UserBean bean = new UserBean();
        bean.setUserName("lly");
        bean.setPassword("123");
        System.out.println("序列化前--->userName:"+bean.getUserName()+",password:"+bean.getPassword());

        //下面序列化到本地
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("e:/userbean.txt"));
            oos.writeObject(bean);//将对象序列化缓存到本地
            oos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //下面从本地反序列化缓存出来
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/userbean.txt"));
            bean = (UserBean) ois.readObject();
            ois.close();
            System.out.println("反序列化后获取出的数据--->userName:"+bean.getUserName()+",password:"+bean.getPassword());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
打印结果如下:
序列化前--->userName:lly,password:123
反序列化后获取出的数据--->userName:lly,password:null

看到,password反序列化后的值为null,说明它没有被保存到本地,因为我们给它加上了transient修饰。

注意:
(1)从上面可以看到,被transient修饰后,反序列化后不能获取到值;
(2)transient只能修饰变量,不能修饰方法,修饰我们自定义的对象变量时,这个对象一定要实现Serializable接口;
(3)static变量不能被序列化,即使它被transient修饰。因为static修饰的变量是属于类的,而我们序列化是去序列化对象的。
上面的例子中如果给userName加上static修饰,反序列化后依然能够获取到值,但是这个时候的值是JVM 内存中对应的static的值,因为static修饰后,它属于类不属于对象,存放在一块单独的区域,直接通过对象也是可以获取到这个值的。上面的第三点依然成立。

4、下面哪些类可以被继承

下面哪些类可以被继承? Java.lang.Thread、java.lang.Number、java.lang.Double、java.lang.Math、 java.lang.ClassLoader
A、Thread    B、Number    C、Double    D、Math    E、ClassLoader

正确答案:ABE
1、对于Thread,我们可以继承它来创建线程;
2、Byte、Short、Integer、Long、Float、Double几个数字类型都是继承自Number;
3、Byte、Short、Integer、Long、Float、Double、Boolean、Character几个基本类型的包装类,以及String、Math它们都是被定义为public final class,因此这几个都不能被继承;
4、我们可以自定义类加载器来实现加载功能,因此ClassLoader是可以被继承的。

5、for和foreach遍历的比较

    针对for和foreach两种循环,我用10万、100万、1000万级别大小的List集合数据进行了测试一下,发现整体来说foreach的执行时间和普通for循环的时间差别不大。对于这两个的比较,我网上查了一下,有说foreach效率高一点的,有说for效率高一点的。
    网上说for效率高的,主要是在针对遍历集合类的数据时,for表现要稍微好一点,因为foreach内部它使用的是Iterator迭代器的执行方式。因此下面分两种结构来测试:
先来测试对数组遍历的时间快慢,数组里面保存1000万的随机数。如下:         
List<Integer> list = new ArrayList<Integer>();
        Integer[] list = new Integer[10000000];
        for(int i = 0; i < 10000000; i++){//先生成1000万随机数
            list[i] = Math.round(100000);//随机数只是咋100000以内
        }
        long size = list.length;
        long start = System.currentTimeMillis();
        int a;
        for(int i = 0; i < size; i++){
            a =list[i];
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时--->"+(end-start));
经过多次运行,发现平均耗时7ms左右。将上面的for循环改成foreach形式,如下:
        for(Integer i : list){
            a = i;
        }
经过多次运行后,发现平均耗时5ms左右。
再来测试遍历集合List的情况:
List<Integer> list = new ArrayList<Integer>();
        for(int i = 0; i < 10000000; i++){
            list.add(Math.round(100000));
        }
        long size = list.size();
        long start = System.currentTimeMillis();
        int a;
        for(int i = 0; i < size; i++){
            a =list.get(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时--->"+(end-start));
多次运行后,平均运行时间为21ms左右,将上面的for循环改成foreach形式,如下:
        for(Integer i : list){
            a = i;
        }
多次运行后,平均运行时间为24ms左右。

经过上面一些简单的实验,我们发现,两种方式在遍历数组的时候时间普遍都较快,在遍历集合的时候耗时会更久一些在遍历数组时,foreach的表现要稍微好一点,在遍历集合的时候,for的表现要好一点。但是不管哪种情况,for和foreach这两种遍历方式时间都相差不大。因此对于这两者的比较在时间效率来说应该相差不会很大(上面没有测试复杂数据的情况,以及其他集合结果的情况,可能不准确)。主要是在对于两者的应用场景上的选择:
(1)普通for循环可以给定下标,因此当我们需要这个信息时,我们可以选用普通for循环来操作遍历;
(2)foreach在代码结构上更加清晰、简单;
(3)foreach在遍历的时候会锁定集合中的对象,期间不能修改,而for中可以修改集合中的元素。

6、Java IO与NIO

(本小节内容大部分来源于参考文章
Java NIO(Java non-blocking IO)是JDK1.4以后推出的,相对于原来的IO来说,Java NIO是一种非阻塞的IO方式,它为所有的基本类型提供了缓存支持。
一般来说,I/O操作包括:对硬盘的读写、对socket的读写以及外设的读写。
阻塞和非阻塞:

        阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,但是由于该请求操作需要的条件不满足,那么就会一直在那等待,直至条件满足;

非阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,如果该请求操作需要的条件不满足,会立即返回一个标志信息告知条件不满足,不会一直在那等待。

  这就是阻塞和非阻塞的区别。也就是说阻塞和非阻塞的区别关键在于当发出请求一个操作时,如果条件不满足,是会一直等待还是返回一个标志信息。

阻塞IO和非阻塞IO:

当用户线程发起一个IO请求操作(本文以读请求操作为例),内核会去查看要读取的数据是否就绪,对于阻塞IO来说,如果数据没有就绪,则会一直在那等待,直到数据就绪

对于非阻塞IO来说,如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪。当数据就绪之后,便将数据拷贝到用户线程,这样才完成了一个完整的IO读请求操作

也就是说一个完整的IO读请求操作包括两个阶段:

  1)查看数据是否就绪;

  2)进行数据拷贝(内核将数据拷贝到用户线程)。

  那么阻塞(blocking IO)和非阻塞(non-blocking IO)的区别就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是一直等待,还是直接返回一个标志信息

  Java中传统的IO都是阻塞IO,比如通过socket来读数据,调用read()方法之后,如果数据没有就绪,当前线程就会一直阻塞在read方法调用那里,直到有数据才返回;而如果是非阻塞IO的话,当数据没有就绪,read()方法应该返回一个标志信息,告知当前线程数据没有就绪,而不是一直在那里等待。

同步IO和异步IO:

       从字面的意思可以看出:同步IO即 如果一个线程请求进行IO操作,在IO操作完成之前,该线程会被阻塞

  而异步IO为 如果一个线程请求进行IO操作,IO操作不会导致请求线程被阻塞

  事实上,同步IO和异步IO模型是针对用户线程和内核的交互来说的:

  对于同步IO:当用户发出IO请求操作之后,如果数据没有就绪,需要通过用户线程或者内核不断地去轮询数据是否就绪,当数据就绪时,再将数据从内核拷贝到用户线程

  而异步IO:只有IO请求操作的发出是由用户线程来进行的,IO操作的两个阶段都是由内核自动完成,然后发送通知告知用户线程IO操作已经完成。也就是说在异步IO中,不会对用户线程产生任何阻塞。

  这是同步IO和异步IO关键区别所在,同步IO和异步IO的关键区别反映在数据拷贝阶段是由用户线程完成还是内核完成。所以说异步IO必须要有操作系统的底层支持。(即同步IO是用户线程不断的轮询、有数据之后进行拷贝,而异步IO是内核完成这两个步骤,与用户线程无关。)

阻塞IO和非阻塞IO是反映在当用户请求IO操作时,如果数据没有就绪,是用户线程一直等待数据就绪,还是会收到一个标志信息这一点上面的。也就是说,阻塞IO和非阻塞IO是反映在IO操作的第一个阶段,在查看数据是否就绪时是如何处理的。

注意同步IO和异步IO与阻塞IO和非阻塞IO是不同的两组概念,同步IO和异步IO考虑的是由哪个线程(用户线程or内核线程)来完成IO的处理,而阻塞IO和非阻塞IO,针对的是IO操作中的第一个阶段的处理方式,是一直等待还是直接返回状态信息。

五种IO模型:
见下面第一篇参考文章

Java NIO:
Java NIO中比较核心的三个概念是:通道(Channel)、缓冲区(Buffer)、选择器(Selector)。
(1)通道(Channel):
通道和传统IO中的Stream类似,但传统IO中的Stream是单向的,比如InputStream只能进行读(read)操作,OutputStream只能进行写(write)操作,而NIO中的通道是双向的,既可以进行读也可以进行写操作。常用的有以下几种通道:
  • FileChannel  --从文件读或者向文件写入数据
  • SocketChanel   --以TCP来向网络连接的两端读写数据
  • ServerSocketChannel   --服务器端通过ServerSocketChanel能够监听客户端发起的TCP连接,并为每个TCP连接创建一个新的SocketChannel来进行数据读写
  • DatagramChannel   --以UDP协议来向网络连接的两端读写数据

(2)缓冲区(Buffer):
Buffer实际上是一个容器,是一个连续数组。Channel提供从文件、网络读取数据的渠道,使用Java NIO时,所有数据处理(读取或写入)必须经由Buffer。即读入数据时,先通过通道Channel获取数据渠道,然后将数据写入到缓冲区Buffer中,往外写数据时,先把内存中的数据放到Buffer中,然后再从Buffer送往通道中。如下图所示:
Java笔试面试题整理第七波_第1张图片
NIO 为Java的所有基本类型都提供了缓存对象,如ByteBuffer、IntBuffer。。。

一个传统IO读写数据的例子:     
public static String readFromStream(InputStream is) throws IOException{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while((len = is.read(buffer))!=-1){
            baos.write(buffer, 0, len);
        }
        is.close();
        String result = baos.toString();
        baos.close();
        return result;
    }
传统IO在读取数据时,直接从InputStream中读入到byte[]数组中,如is.read(buffer),在写数据时,直接将内存数据写到输出流中,如baos.write(buffer,0,len);

使用NIO读取数据:按照上图,读取数据时,从通道中将数据读入到Buffer中,写数据时,将内存数据先写到Buffer,再送入通道。
    NIO 读取数据:         
FileInputStream in = new FileInputStream("e:\\lly.txt");
        FileChannel fileChannel = in.getChannel();
        //创建一个ByteBuffer缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(10);
        //从通道读入数据到缓冲区
        fileChannel.read(buffer);
        //重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为0  
        buffer.flip();

        while(buffer.hasRemaining()){
            byte b = buffer.get();//从缓冲区中取数据到内存中
            System.out.print(((char)b));
        }
        in.close();
    NIO写数据:        
     //模拟数据
        byte[] data = { 83, 111, 109, 101, 32,  
                98, 121, 116, 101, 115, 46};
        FileOutputStream out = new FileOutputStream("e:\\lly.txt");
        FileChannel fileChannel = out.getChannel();
        //创建一个ByteBuffer缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(20);
        //先将数据写入到Buffer中
        for(int i = 0; i < data.length; i++){
            buffer.put(data[i]);
        }
        //重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为0 
        buffer.flip();
        //把buffer中的数据送入到通道中
        fileChannel.write(buffer);
        out.close();
可以看到,Java NIO在数据处理方面需要通过Buffer来缓存,与传统IO处理方式相比,NIO相当于是数据块处理,传统IO是一个一个字节处理。其实,在传统IO后面出现了BufferInputStream、BufferOutputStream这种也是缓存数据块的处理方式。
同时,我们看到在读或写时都调用了Buffer的buffer.flip();这个方法非常重要,它是用来设置缓冲对象的一些状态信息,在读get()和写put()之前,都需要调用buffer.flip();来设置状态。

关于flip的具体作用请参考这篇文章,非常具体形象(http://blog.csdn.net/wuxianglong/article/details/6612246)。下面的文章来自这篇文章:
”--------------引用-------------

在第一篇中,我们介绍了NIO中的两个核心对象:缓冲区和通道,在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如果我们使用get()方法从缓冲区获取数据或者使用put()方法把数据写入缓冲区,都会引起缓冲区状态的变化。本文为NIO使用及原理分析的第二篇,将会分析NIO中的Buffer对象。

在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪:

position:指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。

limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。

capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。

以上四个属性值之间有一些相对大小的关系:0 <= position <= limit <= capacity。如果我们创建一个新的容量大小为10的ByteBuffer对象,在初始化的时候,position设置为0,limit和 capacity被设置为10,在以后使用ByteBuffer对象过程中,capacity的值不会再发生变化,而其它两个个将会随着使用而变化。四个属性值分别如图所示:

Java笔试面试题整理第七波_第2张图片

现在我们可以从通道中读取一些数据到缓冲区中,注意从通道读取数据,相当于往缓冲区中写入数据。如果读取4个自己的数据,则此时position的值为4,即下一个将要被写入的字节索引为4,而limit仍然是10,如下图所示:

Java笔试面试题整理第七波_第3张图片

下一步把读取的数据写入到输出通道中,相当于从缓冲区中读取数据,在此之前,必须调用flip()方法,该方法将会完成两件事情:

1. 把limit设置为当前的position值 
2. 把position设置为0

由于position被设置为0,所以可以保证在下一步输出时读取到的是缓冲区中的第一个字节,而limit被设置为当前的position,可以保证读取的数据正好是之前写入到缓冲区中的数据,如下图所示:


现在调用get()方法从缓冲区中读取数据写入到输出通道,这会导致position的增加而limit保持不变,但position不会超过limit的值,所以在读取我们之前写入到缓冲区中的4个自己之后,position和limit的值都为4,如下图所示:


在从缓冲区中读取数据完毕后,limit的值仍然保持在我们调用flip()方法时的值,调用clear()方法能够把所有的状态变化设置为初始化时的值,如下图所示:

Java笔试面试题整理第七波_第4张图片


(3)选择器(Selector)
Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。如下如所示:

要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。
如下的一个完整例子,例子来源于(http://weixiaolu.iteye.com/blog/1479656)
  1. package cn.nio;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.InetSocketAddress;  
  5. import java.nio.ByteBuffer;  
  6. import java.nio.channels.SelectionKey;  
  7. import java.nio.channels.Selector;  
  8. import java.nio.channels.ServerSocketChannel;  
  9. import java.nio.channels.SocketChannel;  
  10. import java.util.Iterator;  
  11.   
  12. /** 
  13.  * NIO服务端 
  14.  * @author 小路 
  15.  */  
  16. public class NIOServer {  
  17.     //通道管理器  
  18.     private Selector selector;  
  19.   
  20.     /** 
  21.      * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 
  22.      * @param port  绑定的端口号 
  23.      * @throws IOException 
  24.      */  
  25.     public void initServer(int port) throws IOException {  
  26.         // 获得一个ServerSocket通道  
  27.         ServerSocketChannel serverChannel = ServerSocketChannel.open();  
  28.         // 设置通道为非阻塞  
  29.         serverChannel.configureBlocking(false);  
  30.         // 将该通道对应的ServerSocket绑定到port端口  
  31.         serverChannel.socket().bind(new InetSocketAddress(port));  
  32.         // 获得一个通道管理器  
  33.         this.selector = Selector.open();  
  34.         //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,  
  35.         //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。  
  36.         serverChannel.register(selector, SelectionKey.OP_ACCEPT);  
  37.     }  
  38.   
  39.     /** 
  40.      * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 
  41.      * @throws IOException 
  42.      */  
  43.     @SuppressWarnings("unchecked")  
  44.     public void listen() throws IOException {  
  45.         System.out.println("服务端启动成功!");  
  46.         // 轮询访问selector  
  47.         while (true) {  
  48.             //当注册的事件到达时,方法返回;否则,该方法会一直阻塞  
  49.             selector.select();  
  50.             // 获得selector中选中的项的迭代器,选中的项为注册的事件  
  51.             Iterator ite = this.selector.selectedKeys().iterator();  
  52.             while (ite.hasNext()) {  
  53.                 SelectionKey key = (SelectionKey) ite.next();  
  54.                 // 删除已选的key,以防重复处理  
  55.                 ite.remove();  
  56.                 // 客户端请求连接事件  
  57.                 if (key.isAcceptable()) {  
  58.                     ServerSocketChannel server = (ServerSocketChannel) key  
  59.                             .channel();  
  60.                     // 获得和客户端连接的通道  
  61.                     SocketChannel channel = server.accept();  
  62.                     // 设置成非阻塞  
  63.                     channel.configureBlocking(false);  
  64.   
  65.                     //在这里可以给客户端发送信息哦  
  66.                     channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));  
  67.                     //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。  
  68.                     channel.register(this.selector, SelectionKey.OP_READ);  
  69.                       
  70.                     // 获得了可读的事件  
  71.                 } else if (key.isReadable()) {  
  72.                         read(key);  
  73.                 }  
  74.   
  75.             }  
  76.   
  77.         }  
  78.     }  
  79.     /** 
  80.      * 处理读取客户端发来的信息 的事件 
  81.      * @param key 
  82.      * @throws IOException  
  83.      */  
  84.     public void read(SelectionKey key) throws IOException{  
  85.         // 服务器可读取消息:得到事件发生的Socket通道  
  86.         SocketChannel channel = (SocketChannel) key.channel();  
  87.         // 创建读取的缓冲区  
  88.         ByteBuffer buffer = ByteBuffer.allocate(10);  
  89.         channel.read(buffer);  
  90.         byte[] data = buffer.array();  
  91.         String msg = new String(data).trim();  
  92.         System.out.println("服务端收到信息:"+msg);  
  93.         ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());  
  94.         channel.write(outBuffer);// 将消息回送给客户端  
  95.     }  
  96.       
  97.     /** 
  98.      * 启动服务端测试 
  99.      * @throws IOException  
  100.      */  
  101.     public static void main(String[] args) throws IOException {  
  102.         NIOServer server = new NIOServer();  
  103.         server.initServer(8000);  
  104.         server.listen();  
  105.     }  
  106.   
  107. }  


  1. package cn.nio;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.InetSocketAddress;  
  5. import java.nio.ByteBuffer;  
  6. import java.nio.channels.SelectionKey;  
  7. import java.nio.channels.Selector;  
  8. import java.nio.channels.SocketChannel;  
  9. import java.util.Iterator;  
  10.   
  11. /** 
  12.  * NIO客户端 
  13.  * @author 小路 
  14.  */  
  15. public class NIOClient {  
  16.     //通道管理器  
  17.     private Selector selector;  
  18.   
  19.     /** 
  20.      * 获得一个Socket通道,并对该通道做一些初始化的工作 
  21.      * @param ip 连接的服务器的ip 
  22.      * @param port  连接的服务器的端口号          
  23.      * @throws IOException 
  24.      */  
  25.     public void initClient(String ip,int port) throws IOException {  
  26.         // 获得一个Socket通道  
  27.         SocketChannel channel = SocketChannel.open();  
  28.         // 设置通道为非阻塞  
  29.         channel.configureBlocking(false);  
  30.         // 获得一个通道管理器  
  31.         this.selector = Selector.open();  
  32.           
  33.         // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调  
  34.         //用channel.finishConnect();才能完成连接  
  35.         channel.connect(new InetSocketAddress(ip,port));  
  36.         //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。  
  37.         channel.register(selector, SelectionKey.OP_CONNECT);  
  38.     }  
  39.   
  40.     /** 
  41.      * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 
  42.      * @throws IOException 
  43.      */  
  44.     @SuppressWarnings("unchecked")  
  45.     public void listen() throws IOException {  
  46.         // 轮询访问selector  
  47.         while (true) {  
  48.             selector.select();  
  49.             // 获得selector中选中的项的迭代器  
  50.             Iterator ite = this.selector.selectedKeys().iterator();  
  51.             while (ite.hasNext()) {  
  52.                 SelectionKey key = (SelectionKey) ite.next();  
  53.                 // 删除已选的key,以防重复处理  
  54.                 ite.remove();  
  55.                 // 连接事件发生  
  56.                 if (key.isConnectable()) {  
  57.                     SocketChannel channel = (SocketChannel) key  
  58.                             .channel();  
  59.                     // 如果正在连接,则完成连接  
  60.                     if(channel.isConnectionPending()){  
  61.                         channel.finishConnect();  
  62.                           
  63.                     }  
  64.                     // 设置成非阻塞  
  65.                     channel.configureBlocking(false);  
  66.   
  67.                     //在这里可以给服务端发送信息哦  
  68.                     channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));  
  69.                     //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。  
  70.                     channel.register(this.selector, SelectionKey.OP_READ);  
  71.                       
  72.                     // 获得了可读的事件  
  73.                 } else if (key.isReadable()) {  
  74.                         read(key);  
  75.                 }  
  76.   
  77.             }  
  78.   
  79.         }  
  80.     }  
  81.     /** 
  82.      * 处理读取服务端发来的信息 的事件 
  83.      * @param key 
  84.      * @throws IOException  
  85.      */  
  86.     public void read(SelectionKey key) throws IOException{  
  87.         //和服务端的read方法一样  
  88.     }  
  89.       
  90.       
  91.     /** 
  92.      * 启动客户端测试 
  93.      * @throws IOException  
  94.      */  
  95.     public static void main(String[] args) throws IOException {  
  96.         NIOClient client = new NIOClient();  
  97.         client.initClient("localhost",8000);  
  98.         client.listen();  
  99.     }  
  100.   
  101. }  
---------------------“

下一篇将介绍反射作用及原理、泛型相关知识、解析XML的几种方式、java与C++的对比、java1.7和1.8的新特性、设计模式、JNI相关知识,还望多多指导与支持~~

(Java总结篇即将告一段落,后面会开始Android重点内容的总结,算法的总结...)

【参考文章:
Java NIO:浅析I/O模型
Java NIO:NIO概述
Java NIO使用及原理分析 (一)
Java NIO使用及原理分析(二)
Java NIO系列教程(一) Java NIO 概述
Java NIO原理 图文分析及代码实现


你可能感兴趣的:(java基础,面试题,Java面试题,java笔试题)