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(); } }
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(); } }
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); } }
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(); } } }
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; }
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; }
阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,但是由于该请求操作需要的条件不满足,那么就会一直在那等待,直至条件满足;
非阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,如果该请求操作需要的条件不满足,会立即返回一个标志信息告知条件不满足,不会一直在那等待。
这就是阻塞和非阻塞的区别。也就是说阻塞和非阻塞的区别关键在于当发出请求一个操作时,如果条件不满足,是会一直等待还是返回一个标志信息。
阻塞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操作中的第一个阶段的处理方式,是一直等待还是直接返回状态信息。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; }
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();
//模拟数据 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();
在第一篇中,我们介绍了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()方法能够把所有的状态变化设置为初始化时的值,如下图所示: