zoukankan      html  css  js  c++  java
  • Java笔试面试题整理第七波


    转载至:http://blog.csdn.net/shakespeare001/article/details/51388516
    作者:山代王(开心阳)

    1、super的作用

        在Java中super指代父类对象(直接父类),也就是说,super相当于是一个直接new出来的父类对象,所以可以通过它来调用父类的那些非private修饰的变量、方法(对于我们普通new出来的对象来说,也就只能访问那些非private的成员变量、方法了,这里的访问是指通过“对象名.变量名或方法名”的形式)。所以,super这个对象也就是一个普通对象,同样遵循访问控制修饰符的准则
        然而,对于子类来说,子类通过继承就直接拥有了父类的非private变量、方法,也就可以在子类中直接使用,再加一个super来修饰,岂不是显得有点多余了?正常情况下来说,是有点多余了(但是可以明确提示我们这是调用的父类变量或方法),但super关键字主要是用在以下两种情况中:
    (1)发生了重写的情况
        重写也分为两种情况,一个是重写了父类的方法;一个是重写了父类的成员变量;
    重写父类方法的情况
    1. public class A {  
    2.     String name = ”lly”;  
    3.     protected void getName(){  
    4.         System.out.println(”父类getName->”+ name);  
    5.     }  
    6. }  
    7. public class B extends A {  
    8.     String nameB = ”llyB”;  
    9.     @Override  
    10.     protected void getName() {  
    11.         System.out.println(”子类getName->”+nameB);  
    12.         super.getName();  
    13.     }  
    14.     public static void main(String[] args) {  
    15.         B b = new B();  
    16.         b.getName();  
    17.     }  
    18. }  
    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类简单改造一下:
    1. public class B extends A {  
    2.     String name = ”llyB”;  
    3.     @Override  
    4.     protected void getName() {  
    5.         name = super.name;  
    6.         System.out.println(”子类getName->”+name);  
    7.     }  
    8.     public static void main(String[] args) {  
    9.         B b = new B();  
    10.         b.getName();  
    11.     }  
    12. }  
    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的情形是错误不能通过编译的:
    1. public class A {  
    2.     public A(String s){  }  
    3. }  
    4. public class B extends A {    //编译错误,JVM默认给B加了一个无参构造方法,而在这个方法中默认调用了super(),但是父类中并不存在该构造方法  
    5.     String name = ”llyB”;  
    6. }  
    7. public class B extends A {    //同样编译错误,相同的道理,虽然我们在子类中自己定义了一个构造方法,但是在这个构造方法中还是默认调用了super(),但是父类中并不存在该构造方法  
    8.     String name = ”llyB”;  
    9.     public B(String s){}  
    10. }  
    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){}
    }
    此时就需要显示的去调用父类构造方法了,如下:
    1. public class B extends A {    //正确编译  
    2.     String name = ”llyB”;  
    3.     public B(String s){  
    4.         super(s);  
    5.     }  
    6. }  
    public class B extends A {    //正确编译
        String name = "llyB";
        public B(String s){
            super(s);
        }
    }
    所以,只要记住,在子类的构造方法中,只要里面没有显示的通过super去调用父类相应的构造方法,默认都是调用super(),即无参构造方法,因此要确保父类有相应的构造方法

    3、transient关键字用法

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

    当我们一个对象实现了Serializable接口,这个对象的所有字段和方法就可以被自动序列化。当我们持久化对象时,可能有一个一些特殊字段我们不想让它随着网络传输过去,或者在本地序列化缓存起来,这时我们就可以在这些字段前加上transient关键字修饰,被transient修饰变量的值不包括在序列化的表示中,也就不会被保存下来。这个字段的生命周期仅存在调用者的内存中
    如下一个例子:
    1. public class UserBean implements Serializable{  
    2.     private static final long serialVersionUID = 856780694939330811L;  
    3.     private String userName;  
    4.     private transient String password;    //此字段不需要被序列化  
    5.     public String getUserName() {  
    6.         return userName;  
    7.     }  
    8.     public void setUserName(String userName) {  
    9.         this.userName = userName;  
    10.     }  
    11.     public String getPassword() {  
    12.         return password;  
    13.     }  
    14.     public void setPassword(String password) {  
    15.         this.password = password;  
    16.     }  
    17. }  
    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;
        }
    }
    测试类:
    1. public class Test {  
    2.     public static void main(String[] args) {  
    3.         UserBean bean = new UserBean();  
    4.         bean.setUserName(”lly”);  
    5.         bean.setPassword(”123”);  
    6.         System.out.println(”序列化前—>userName:”+bean.getUserName()+“,password:”+bean.getPassword());  
    7.   
    8.         //下面序列化到本地  
    9.         ObjectOutputStream oos = null;  
    10.         try {  
    11.             oos = new ObjectOutputStream(new FileOutputStream(“e:/userbean.txt”));  
    12.             oos.writeObject(bean);//将对象序列化缓存到本地  
    13.             oos.flush();  
    14.         } catch (FileNotFoundException e) {  
    15.             e.printStackTrace();  
    16.         } catch (IOException e) {  
    17.             e.printStackTrace();  
    18.         }finally{  
    19.             if(oos != null){  
    20.                 try {  
    21.                     oos.close();  
    22.                 } catch (IOException e) {  
    23.                     e.printStackTrace();  
    24.                 }  
    25.             }  
    26.         }  
    27.   
    28.         //下面从本地反序列化缓存出来  
    29.         try {  
    30.             ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“e:/userbean.txt”));  
    31.             bean = (UserBean) ois.readObject();  
    32.             ois.close();  
    33.             System.out.println(”反序列化后获取出的数据—>userName:”+bean.getUserName()+“,password:”+bean.getPassword());  
    34.         } catch (FileNotFoundException e) {  
    35.             e.printStackTrace();  
    36.         } catch (IOException e) {  
    37.             e.printStackTrace();  
    38.         } catch (ClassNotFoundException e) {  
    39.             e.printStackTrace();  
    40.         }  
    41.     }  
    42. }  
    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万的随机数。如下:        
    1. List<Integer> list = new ArrayList<Integer>();  
    2.         Integer[] list = new Integer[10000000];  
    3.         for(int i = 0; i < 10000000; i++){//先生成1000万随机数  
    4.             list[i] = Math.round(100000);//随机数只是咋100000以内  
    5.         }  
    6.         long size = list.length;  
    7.         long start = System.currentTimeMillis();  
    8.         int a;  
    9.         for(int i = 0; i < size; i++){  
    10.             a =list[i];  
    11.         }  
    12.         long end = System.currentTimeMillis();  
    13.         System.out.println(”耗时—>”+(end-start));  
    14. 经过多次运行,发现平均耗时7ms左右。将上面的for循环改成foreach形式,如下:  
    15.         for(Integer i : list){  
    16.             a = i;  
    17.         }  
    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的情况:
    1. List<Integer> list = new ArrayList<Integer>();  
    2.         for(int i = 0; i < 10000000; i++){  
    3.             list.add(Math.round(100000));  
    4.         }  
    5.         long size = list.size();  
    6.         long start = System.currentTimeMillis();  
    7.         int a;  
    8.         for(int i = 0; i < size; i++){  
    9.             a =list.get(i);  
    10.         }  
    11.         long end = System.currentTimeMillis();  
    12.         System.out.println(”耗时—>”+(end-start));  
    13. 多次运行后,平均运行时间为21ms左右,将上面的for循环改成foreach形式,如下:  
    14.         for(Integer i : list){  
    15.             a = i;  
    16.         }  
    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送往通道中。如下图所示:

    NIO 为Java的所有基本类型都提供了缓存对象,如ByteBuffer、IntBuffer。。。

    一个传统IO读写数据的例子:    
    1. public static String readFromStream(InputStream is) throws IOException{  
    2.         ByteArrayOutputStream baos = new ByteArrayOutputStream();  
    3.         byte[] buffer = new byte[1024];  
    4.         int len = 0;  
    5.         while((len = is.read(buffer))!=-1){  
    6.             baos.write(buffer, 0, len);  
    7.         }  
    8.         is.close();  
    9.         String result = baos.toString();  
    10.         baos.close();  
    11.         return result;  
    12.     }  
    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 读取数据:        
    1. FileInputStream in = new FileInputStream(“e:\lly.txt”);  
    2.         FileChannel fileChannel = in.getChannel();  
    3.         //创建一个ByteBuffer缓冲区  
    4.         ByteBuffer buffer = ByteBuffer.allocate(10);  
    5.         //从通道读入数据到缓冲区  
    6.         fileChannel.read(buffer);  
    7.         //重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为0    
    8.         buffer.flip();  
    9.   
    10.         while(buffer.hasRemaining()){  
    11.             byte b = buffer.get();//从缓冲区中取数据到内存中  
    12.             System.out.print(((char)b));  
    13.         }  
    14.         in.close();  
    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写数据:       
    1. //模拟数据  
    2.    byte[] data = { 8311110910132,    
    3.            9812111610111546};  
    4.    FileOutputStream out = new FileOutputStream(“e:\lly.txt”);  
    5.    FileChannel fileChannel = out.getChannel();  
    6.    //创建一个ByteBuffer缓冲区  
    7.    ByteBuffer buffer = ByteBuffer.allocate(20);  
    8.    //先将数据写入到Buffer中  
    9.    for(int i = 0; i < data.length; i++){  
    10.        buffer.put(data[i]);  
    11.    }  
    12.    //重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为0   
    13.    buffer.flip();  
    14.    //把buffer中的数据送入到通道中  
    15.    fileChannel.write(buffer);  
    16.    out.close();  
         //模拟数据
            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的值不会再发生变化,而其它两个个将会随着使用而变化。四个属性值分别如图所示:


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


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

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

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


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


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


    (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重点内容的总结,算法的总结…)

    【参考文章:


  • 相关阅读:
    018_STM32程序移植之_串口接收中文
    003_软件安装之_Visual Studio 2012
    001_C#我的第一个串口上位机软件
    017_STM32程序移植之_AS608指纹模块
    016_STM32程序移植之_舵机
    015_STM32程序移植之_NRF24L01模块
    014_STM32程序移植之_L298N电机驱动模块
    002_89C52_Proteus_DAC0832_输出50HZ,正弦波,三角波,矩形波,锯齿波
    001_89C52之_Proteus_ADC0809采集电压
    001_电子工程师招聘笔试题及详细解析
  • 原文地址:https://www.cnblogs.com/Arry10/p/7731766.html
Copyright © 2011-2022 走看看