本文收录于《面试小抄》系列,Github地址(可下载pdf):https://github.com/cosen1024/... 国内Gitee(可下载pdf):https://gitee.com/cosen1024/J...
本文蕴含了String、包装类型、反射、泛型、序列化、异样和IO的常见面试题。
本文收录于《面试小抄》系列,Github地址:https://github.com/cosen1024/...
蕴含了Java根底、Java并发、JVM、MySQL、Redis、Spring、MyBatis、Kafka、操作系统和计算机网络等的常考面试题。
坐稳,要发车了~
1. 字符型常量和字符串常量的区别?
- 模式上: 字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符;
- 含意上: 字符常量相当于一个整型值( ASCII 值),能够加入表达式运算;字符串常量代表一个地址值(该字符串在内存中寄存地位,相当于对象;
- 占内存大小:字符常量只占2个字节;字符串常量占若干个字节(至多一个字符完结标记) (留神: char 在Java中占两个字节)。
2. 什么是字符串常量池?
java中常量池的概念次要有三个:全局字符串常量池
,class文件常量池
,运行时常量池
。咱们当初所说的就是全局字符串常量池
,对这个想弄明确的同学能够看这篇Java中几种常量池的辨别。
jvm为了晋升性能和缩小内存开销,防止字符的反复创立,其保护了一块非凡的内存空间,即字符串池,当须要应用字符串时,先去字符串池中查看该字符串是否曾经存在,如果存在,则能够间接应用,如果不存在,初始化,并将该字符串放入字符串常量池中。
字符串常量池的地位也是随着jdk版本的不同而地位不同。在jdk6中,常量池的地位在永恒代(办法区)中,此时常量池中存储的是对象。在jdk7中,常量池的地位在堆中,此时,常量池存储的就是援用了。在jdk8中,永恒代(办法区)被元空间取代了。
3. String str="aaa"与 String str=new String("aaa")一样吗?new String(“aaa”);
创立了几个字符串对象?
- 应用
String a = “aaa” ;
,程序运行时会在常量池中查找”aaa”字符串,若没有,会将”aaa”字符串放进常量池,再将其地址赋给a;若有,将找到的”aaa”字符串的地址赋给a。 - 应用String b = new String("aaa");`,程序会在堆内存中开拓一片新空间寄存新对象,同时会将”aaa”字符串放入常量池,相当于创立了两个对象,无论常量池中有没有”aaa”字符串,程序都会在堆内存中开拓一片新空间寄存新对象。
具体分析,见以下代码:
@Test public void test(){ String s = new String("2"); s.intern(); String s2 = "2"; System.out.println(s == s2); String s3 = new String("3") + new String("3"); s3.intern(); String s4 = "33"; System.out.println(s3 == s4); }
运行后果:
jdk6falsefalsejdk7falsetrue
这段代码在jdk6中输入是false false
,然而在jdk7中输入的是false true
。咱们通过图来一行行解释。
先来意识下intern()函数:
intern函数的作用是将对应的符号常量进入非凡解决,在JDK1.6以前 和 JDK1.7当前有不同的解决;
在JDK1.6中,intern的解决是 先判断字符串常量是否在字符串常量池中,如果存在间接返回该常量,如果没有找到,则将该字符串常量退出到字符串常量区,也就是在字符串常量区建设该常量;
在JDK1.7中,intern的解决是 先判断字符串常量是否在字符串常量池中,如果存在间接返回该常量,如果没有找到,阐明该字符串常量在堆中,则解决是把堆区该对象的援用退出到字符串常量池中,当前他人拿到的是该字符串常量的援用,理论存在堆中
JDK1.6
String s = new String("2");
创立了两个对象,一个在堆中的StringObject对象,一个是在常量池中的“2”对象。s.intern();
在常量池中寻找与s变量内容雷同的对象,发现曾经存在内容雷同对象“2”,返回对象2的地址。String s2 = "2";
应用字面量创立,在常量池寻找是否有雷同内容的对象,发现有,返回对象"2"的地址。System.out.println(s == s2);
从下面能够剖析出,s变量和s2变量地址指向的是不同的对象,所以返回false
String s3 = new String("3") + new String("3");
创立了两个对象,一个在堆中的StringObject对象,一个是在常量池中的“3”对象。两头还有2个匿名的new String("3")咱们不去探讨它们。s3.intern();
在常量池中寻找与s3变量内容雷同的对象,没有发现“33”对象,在常量池中创立“33”对象,返回“33”对象的地址。String s4 = "33";
应用字面量创立,在常量池寻找是否有雷同内容的对象,发现有,返回对象"33"的地址。System.out.println(s3 == s4);
从下面能够剖析出,s3变量和s4变量地址指向的是不同的对象,所以返回false
JDK1.7
String s = new String("2");
创立了两个对象,一个在堆中的StringObject对象,一个是在堆中的“2”对象,并在常量池中保留“2”对象的援用地址。s.intern();
在常量池中寻找与s变量内容雷同的对象,发现曾经存在内容雷同对象“2”,返回对象“2”的援用地址。String s2 = "2";
应用字面量创立,在常量池寻找是否有雷同内容的对象,发现有,返回对象“2”的援用地址。System.out.println(s == s2);
从下面能够剖析出,s变量和s2变量地址指向的是不同的对象,所以返回false
String s3 = new String("3") + new String("3");
创立了两个对象,一个在堆中的StringObject对象,一个是在堆中的“3”对象,并在常量池中保留“3”对象的援用地址。两头还有2个匿名的new String("3")咱们不去探讨它们。s3.intern();
在常量池中寻找与s3变量内容雷同的对象,没有发现“33”对象,将s3对应的StringObject对象的地址保留到常量池中,返回StringObject对象的地址。String s4 = "33";
应用字面量创立,在常量池寻找是否有雷同内容的对象,发现有,返回其地址,也就是StringObject对象的援用地址。System.out.println(s3 == s4);
从下面能够剖析出,s3变量和s4变量地址指向的是雷同的对象,所以返回true。
4. String 是最根本的数据类型吗?
不是。Java 中的根本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了根本类型(primitive type),剩下的都是援用类型(referencetype),Java 5 当前引入的枚举类型也算是一种比拟非凡的援用类型。
5. String有哪些个性?
- 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创立一个新的对象,再把援用指向该对象。不变模式的次要作用在于当一个对象须要被多线程共享并频繁拜访时,能够保证数据的一致性;
- 常量池优化:String 对象创立之后,会在字符串常量池中进行缓存,如果下次创立同样的对象时,会间接返回缓存的援用;
- final:应用 final 来定义 String 类,示意 String 类不能被继承,进步了零碎的安全性。
6. 在应用 HashMap 的时候,用 String 做 key 有什么益处?
HashMap 外部实现是通过 key 的 hashcode 来确定 value 的存储地位,因为字符串是不可变的,所以当创立字符串时,它的 hashcode 被缓存下来,不须要再次计算,所以相比于其余对象更快。
7. 包装类型是什么?根本类型和包装类型有什么区别?
Java 为每一个根本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了主动装箱/拆箱机制,把根本类型转换成包装类型的过程叫做装箱(boxing);反之,把包装类型转换成根本类型的过程叫做拆箱(unboxing),使得二者能够互相转换。
Java 为每个原始类型提供了包装类型:
原始类型: boolean,char,byte,short,int,long,float,double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
根本类型和包装类型的区别次要有以下 几点:
- 包装类型能够为 null,而根本类型不能够。它使得包装类型能够利用于 POJO 中,而根本类型则不行。那为什么 POJO 的属性必须要用包装类型呢?《阿里巴巴 Java 开发手册》上有具体的阐明, 数据库的查问后果可能是 null,如果应用根本类型的话,因为要主动拆箱(将包装类型转为根本类型,比如说把 Integer 对象转换成 int 值),就会抛出
NullPointerException
的异样。 包装类型可用于泛型,而根本类型不能够。泛型不能应用根本类型,因为应用根本类型时会编译出错。
List<int> list = new ArrayList<>(); // 提醒 Syntax error, insert "Dimensions" to complete ReferenceTypeList<Integer> list = new ArrayList<>();
因为泛型在编译时会进行类型擦除,最初只保留原始类型,而原始类型只能是 Object 类及其子类——根本类型是个特例。
- 根本类型比包装类型更高效。根本类型在栈中间接存储的具体数值,而包装类型则存储的是堆中的援用。 很显然,相比拟于根本类型而言,包装类型须要占用更多的内存空间。
8. 解释一下主动装箱和主动拆箱?
主动装箱:将根本数据类型从新转化为对象
public class Test { public static void main(String[] args) { // 申明一个Integer对象,用到了主动的装箱:解析为:Integer num = Integer.valueOf(9); Integer num = 9; } }
9是属于根本数据类型的,原则上它是不能间接赋值给一个对象Integer的。但jdk1.5 开始引入了主动装箱/拆箱机制,就能够进行这样的申明,主动将根本数据类型转化为对应的封装类型,成为一个对象当前就能够调用对象所申明的所有的办法。
主动拆箱:将对象从新转化为根本数据类型
public class Test { public static void main(String[] args) { / /申明一个Integer对象 Integer num = 9; // 进行计算时隐含的有主动拆箱 System.out.print(num--); } }
因为对象时不能间接进行运算的,而是要转化为根本数据类型后能力进行加减乘除。
9. int 和 Integer 有什么区别?
- Integer是int的包装类;int是根本数据类型;
- Integer变量必须实例化后能力应用;int变量不须要;
- Integer理论是对象的援用,指向此new的Integer对象;int是间接存储数据值 ;
- Integer的默认值是null;int的默认值是0。
10. 两个new生成的Integer变量的比照
因为Integer变量实际上是对一个Integer对象的援用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。
Integer i = new Integer(10000);Integer j = new Integer(10000);System.out.print(i == j); //false
11. Integer变量和int变量的比照
Integer变量和int变量比拟时,只有两个变量的值是向等的,则后果为true(因为包装类Integer和根本数据类型int比拟时,java会主动拆包装为int,而后进行比拟,实际上就变为两个int变量的比拟)
int a = 10000; Integer b = new Integer(10000); Integer c=10000; System.out.println(a == b); // true System.out.println(a == c); // true
12. 非new生成的Integer变量和new Integer()生成变量的比照
非new生成的Integer变量和new Integer()生成的变量比拟时,后果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)
Integer b = new Integer(10000); Integer c=10000; System.out.println(b == c); // false
13. 两个非new生成的Integer对象的比照
对于两个非new生成的Integer对象,进行比拟时,如果两个变量的值在区间-128到127之间,则比拟后果为true,如果两个变量的值不在此区间,则比拟后果为false
Integer i = 100;Integer j = 100;System.out.print(i == j); //trueInteger i = 128;Integer j = 128;System.out.print(i == j); //false
当值在 -128 ~ 127之间时,java会进行主动装箱,而后会对值进行缓存,如果下次再有雷同的值,会间接在缓存中取出应用。缓存是通过Integer的外部类IntegerCache来实现的。当值超出此范畴,会在堆中new出一个对象来存储。
给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf,源码如下:
public static Integer valueOf(String s, int radix) throws NumberFormatException { return Integer.valueOf(parseInt(s,radix)); }
/** * (1)在-128~127之内:动态常量池中cache数组是static final类型,cache数组对象会被存储于动态常量池中。 * cache数组外面的元素却不是static final类型,而是cache[k] = new Integer(j++), * 那么这些元素是存储于堆中,只是cache数组对象存储的是指向了堆中的Integer对象(援用地址) * * (2)在-128~127 之外:新建一个 Integer对象,并返回。 */public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) { return IntegerCache.cache[i + (-IntegerCache.low)]; } return new Integer(i); }
IntegerCache是Integer的外部类,源码如下:
/** * 缓存反对主动装箱的对象标识语义 -128和127(含)。 * 缓存在第一次应用时初始化。 缓存的大小能够由-XX:AutoBoxCacheMax = <size>选项管制。 * 在VM初始化期间,java.lang.Integer.IntegerCache.high属性能够设置并保留在公有零碎属性中 */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) { cache[k] = new Integer(j++); // 创立一个对象 } } private IntegerCache() {} }
14. 什么是反射?
反射是在运行状态中,对于任意一个类,都可能晓得这个类的所有属性和办法;对于任意一个对象,都可能调用它的任意一个办法和属性;这种动静获取的信息以及动静调用对象的办法的性能称为 Java 语言的反射机制。
15. 反射机制的优缺点有哪些?
长处:可能运行时动静获取类的实例,进步灵活性;可与动静编译联合Class.forName('com.mysql.jdbc.Driver.class');
,加载MySQL的驱动类。
毛病:应用反射性能较低,须要解析字节码,将内存中的对象进行解析。其解决方案是:通过setAccessible(true)敞开JDK的安全检查来晋升反射速度;屡次创立一个类的实例时,有缓存会快很多;ReflflectASM工具类,通过字节码生成的形式放慢反射速度。
16. 如何获取反射中的Class对象?
Class.forName(“类的门路”);当你晓得该类的全路径名时,你能够应用该办法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
类名.class。这种办法只适宜在编译前就晓得操作的 Class。
Class clz = String.class;
对象名.getClass()。
String str = new String("Hello");Class clz = str.getClass();
- 如果是根本类型的包装类,能够调用包装类的Type属性来取得该包装类的Class对象。
17. Java反射API有几类?
反射 API 用来生成 JVM 中的类、接口或则对象的信息。
- Class 类:反射的外围类,能够获取类的属性,办法等信息。
- Field 类:Java.lang.reflec 包中的类,示意类的成员变量,能够用来获取和设置类之中的属性值。
- Method 类:Java.lang.reflec 包中的类,示意类的办法,它能够用来获取类中的办法信息或者执行办法。
- Constructor 类:Java.lang.reflec 包中的类,示意类的构造方法。
18. 反射应用的步骤?
- 获取想要操作的类的Class对象,这是反射的外围,通过Class对象咱们能够任意调用类的办法。
- 调用 Class 类中的办法,既就是反射的应用阶段。
- 应用反射 API 来操作这些信息。
具体能够看上面的例子:
public class Apple { private int price; public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public static void main(String[] args) throws Exception{ //失常的调用 Apple apple = new Apple(); apple.setPrice(5); System.out.println("Apple Price:" + apple.getPrice()); //应用反射调用 Class clz = Class.forName("com.chenshuyi.api.Apple"); Method setPriceMethod = clz.getMethod("setPrice", int.class); Constructor appleConstructor = clz.getConstructor(); Object appleObj = appleConstructor.newInstance(); setPriceMethod.invoke(appleObj, 14); Method getPriceMethod = clz.getMethod("getPrice"); System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj)); }}
从代码中能够看到咱们应用反射调用了 setPrice 办法,并传递了 14 的值。之后应用反射调用了 getPrice 办法,输入其价格。下面的代码整个的输入后果是:
Apple Price:5Apple Price:14
从这个简略的例子能够看出,个别状况下咱们应用反射获取一个对象的步骤:
- 获取类的 Class 对象实例
Class clz = Class.forName("com.zhenai.api.Apple");
- 依据 Class 对象实例获取 Constructor 对象
Constructor appleConstructor = clz.getConstructor();
- 应用 Constructor 对象的 newInstance 办法获取反射类对象
Object appleObj = appleConstructor.newInstance();
而如果要调用某一个办法,则须要通过上面的步骤:
- 获取办法的 Method 对象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
- 利用 invoke 办法调用办法
setPriceMethod.invoke(appleObj, 14);
19. 为什么引入反射概念?反射机制的利用有哪些?
咱们来看一下 Oracle 官网文档中对反射的形容:
从 Oracle 官网文档中能够看出,反射次要利用在以下几方面:
- 反射让开发人员能够通过外部类的全路径名创建对象,并应用这些类,实现一些扩大的性能。
- 反射让开发人员能够枚举出类的全副成员,包含构造函数、属性、办法。以帮忙开发者写出正确的代码。
- 测试时能够利用反射 API 拜访类的公有成员,以保障测试代码覆盖率。
也就是说,Oracle 心愿开发者将反射作为一个工具,用来帮忙程序员实现本不可能实现的性能。
举两个最常见应用反射的例子,来阐明反射机制的弱小之处:
第一种:JDBC 的数据库的连贯
在JDBC 的操作中,如果要想进行数据库的连贯,则必须依照以上的几步实现
- 通过Class.forName()加载数据库的驱动程序 (通过反射加载,前提是引入相干了Jar包);
- 通过 DriverManager 类进行数据库的连贯,连贯的时候要输出数据库的连贯地址、用户名、明码;
- 通过Connection 接口接管连贯。
public class ConnectionJDBC { /** * @param args */ //驱动程序就是之前在classpath中配置的JDBC的驱动程序的JAR 包中 public static final String DBDRIVER = "com.mysql.jdbc.Driver"; //连贯地址是由各个数据库生产商独自提供的,所以须要独自记住 public static final String DBURL = "jdbc:mysql://localhost:3306/test"; //连贯数据库的用户名 public static final String DBUSER = "root"; //连贯数据库的明码 public static final String DBPASS = ""; public static void main(String[] args) throws Exception { Connection con = null; //示意数据库的连贯对象 Class.forName(DBDRIVER); //1、应用CLASS 类加载驱动程序 ,反射机制的体现 con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、连贯数据库 System.out.println(con); con.close(); // 3、敞开数据库 }
第二种:Spring 框架的应用,最经典的就是xml的配置模式。
Spring 通过 XML 配置模式装载 Bean 的过程:
- 将程序内所有 XML 或 Properties 配置文件加载入内存中;
- Java类外面解析xml或properties外面的内容,失去对应实体类的字节码字符串以及相干的属性信息;
- 应用反射机制,依据这个字符串取得某个类的Class实例;
- 动静配置实例的属性。
Spring这样做的益处是:
- 不必每一次都要在代码外面去new或者做其余的事件;
- 当前要改的话间接改配置文件,代码保护起来就很不便了;
- 有时为了适应某些需要,Java类外面不肯定能间接调用另外的办法,能够通过反射机制来实现。
模仿 Spring 加载 XML 配置文件:
public class BeanFactory { private Map<String, Object> beanMap = new HashMap<String, Object>(); /** * bean工厂的初始化. * @param xml xml配置文件 */ public void init(String xml) { try { //读取指定的配置文件 SAXReader reader = new SAXReader(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); //从class目录下获取指定的xml文件 InputStream ins = classLoader.getResourceAsStream(xml); Document doc = reader.read(ins); Element root = doc.getRootElement(); Element foo; //遍历bean for (Iterator i = root.elementIterator("bean"); i.hasNext();) { foo = (Element) i.next(); //获取bean的属性id和class Attribute id = foo.attribute("id"); Attribute cls = foo.attribute("class"); //利用Java反射机制,通过class的名称获取Class对象 Class bean = Class.forName(cls.getText()); //获取对应class的信息 java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean); //获取其属性形容 java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors(); //设置值的办法 Method mSet = null; //创立一个对象 Object obj = bean.newInstance(); //遍历该bean的property属性 for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) { Element foo2 = (Element) ite.next(); //获取该property的name属性 Attribute name = foo2.attribute("name"); String value = null; //获取该property的子元素value的值 for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) { Element node = (Element) ite1.next(); value = node.getText(); break; } for (int k = 0; k < pd.length; k++) { if (pd[k].getName().equalsIgnoreCase(name.getText())) { mSet = pd[k].getWriteMethod(); //利用Java的反射极致调用对象的某个set办法,并将值设置进去 mSet.invoke(obj, value); } } } //将对象放入beanMap中,其中key为id值,value为对象 beanMap.put(id.getText(), obj); } } catch (Exception e) { System.out.println(e.toString()); } } //other codes}
20. 反射机制的原理是什么?
Class actionClass=Class.forName(“MyClass”);Object action=actionClass.newInstance();Method method = actionClass.getMethod(“myMethod”,null);method.invoke(action,null);
下面就是最常见的反射应用的例子,前两行实现了类的装载、链接和初始化(newInstance办法实际上也是应用反射调用了<init>办法),后两行实现了从class对象中获取到method对象而后执行反射调用。
因反射原理较简单,上面简要形容下流程,想要具体理解的小伙伴,能够看这篇文章:https://www.cnblogs.com/youge...
- 反射获取类实例 Class.forName(),并没有将实现留给了java,而是交给了jvm去加载!次要是先获取 ClassLoader, 而后调用 native 办法,获取信息,加载类则是回调 java.lang.ClassLoader。最初,jvm又会回调 ClassLoader 进类加载!
newInstance() 次要做了三件事:
- 权限检测,如果不通过间接抛出异样;
- 查找无参结构器,并将其缓存起来;
- 调用具体方法的无参构造方法,生成实例并返回。
获取Method对象,
下面的Class对象是在加载类时由JVM结构的,JVM为每个类治理一个举世无双的Class对象,这份Class对象里保护着该类的所有Method,Field,Constructor的cache,这份cache也能够被称作根对象。
每次getMethod获取到的Method对象都持有对根对象的援用,因为一些重量级的Method的成员变量(次要是MethodAccessor),咱们不心愿每次创立Method对象都要从新初始化,于是所有代表同一个办法的Method对象都共享着根对象的MethodAccessor,每一次创立都会调用根对象的copy办法复制一份:
Method copy() { Method res = new Method(clazz, name, parameterTypes, returnType, exceptionTypes, modifiers, slot, signature, annotations, parameterAnnotations, annotationDefault); res.root = this; res.methodAccessor = methodAccessor; return res; }
调用invoke()办法。调用invoke办法的流程如下:
调用Method.invoke之后,会间接去调MethodAccessor.invoke。MethodAccessor就是下面提到的所有同名method共享的一个实例,由ReflectionFactory创立。
创立机制采纳了一种名为inflation的形式(JDK1.4之后):如果该办法的累计调用次数<=15,会创立出NativeMethodAccessorImpl,它的实现就是间接调用native办法实现反射;如果该办法的累计调用次数>15,会由java代码创立出字节码组装而成的MethodAccessorImpl。(是否采纳inflation和15这个数字都能够在jvm参数中调整)
以调用MyClass.myMethod(String s)为例,生成出的MethodAccessorImpl字节码翻译成Java代码大抵如下:
public class GeneratedMethodAccessor1 extends MethodAccessorImpl { public Object invoke(Object obj, Object[] args) throws Exception { try { MyClass target = (MyClass) obj; String arg0 = (String) args[0]; target.myMethod(arg0); } catch (Throwable t) { throw new InvocationTargetException(t); } }}
21. Java中的泛型是什么 ?
泛型是 JDK1.5 的一个新个性,泛型就是将类型参数化,其在编译时才确定具体的参数。这种参数类型能够用在类、接口和办法的创立中,别离称为泛型类、泛型接口、泛型办法。
22. 应用泛型的益处是什么?
远在 JDK 1.4 版本的时候,那时候是没有泛型的概念的,如果应用 Object 来实现通用、不同类型的解决,有这么两个毛病:
- 每次应用时都须要强制转换成想要的类型
- 在编译时编译器并不知道类型转换是否失常,运行时才晓得,不平安。
如这个例子:
List list = new ArrayList();list.add("www.cnblogs.com");list.add(23);String name = (String)list.get(0);String number = (String)list.get(1); //ClassCastException
下面的代码在运行时会产生强制类型转换异样。这是因为咱们在存入的时候,第二个是一个 Integer 类型,然而取出来的时候却将其强制转换为 String 类型了。Sun 公司为了使 Java 语言更加平安,缩小运行时异样的产生。于是在 JDK 1.5 之后推出了泛型的概念。
依据《Java 编程思维》中的形容,泛型呈现的动机在于:有许多起因促成了泛型的呈现,而最引人注意的一个起因,就是为了创立容器类。
应用泛型的益处有以下几点:
类型平安
- 泛型的次要指标是进步 Java 程序的类型平安
- 编译期间就能够查看出因 Java 类型不正确导致的 ClassCastException 异样
- 合乎越早出错代价越小准则
打消强制类型转换
- 泛型的一个附带益处是,应用时间接失去指标类型,打消许多强制类型转换
- 所得即所需,这使得代码更加可读,并且缩小了出错机会
潜在的性能收益
- 因为泛型的实现形式,反对泛型(简直)不须要 JVM 或类文件更改
- 所有工作都在编译器中实现
- 编译器生成的代码跟不应用泛型(和强制类型转换)时所写的代码简直统一,只是更能确保类型平安而已
23. Java泛型的原理是什么 ? 什么是类型擦除 ?
泛型是一种语法糖,泛型这种语法糖的基本原理是类型擦除。Java中的泛型基本上都是在编译器这个档次来实现的,也就是说:泛型只存在于编译阶段,而不存在于运行阶段。在编译后的 class 文件中,是没有泛型这个概念的。
类型擦除:应用泛型的时候加上的类型参数,编译器在编译的时候去掉类型参数。
例如:
public class Caculate<T> { private T num;}
咱们定义了一个泛型类,定义了一个属性成员,该成员的类型是一个泛型类型,这个 T 具体是什么类型,咱们也不晓得,它只是用于限定类型的。反编译一下这个 Caculate 类:
public class Caculate{ public Caculate(){} private Object num;}
发现编译器擦除 Caculate 类前面的两个尖括号,并且将 num 的类型定义为 Object 类型。
那么是不是所有的泛型类型都以 Object 进行擦除呢?大部分状况下,泛型类型都会以 Object 进行替换,而有一种状况则不是。那就是应用到了extends和super语法的有界类型,如:
public class Caculate<T extends String> { private T num;}
这种状况的泛型类型,num 会被替换为 String 而不再是 Object。这是一个类型限定的语法,它限定 T 是 String 或者 String 的子类,也就是你构建 Caculate 实例的时候只能限定 T 为 String 或者 String 的子类,所以无论你限定 T 为什么类型,String 都是父类,不会呈现类型不匹配的问题,于是能够应用 String 进行类型擦除。
实际上编译器会失常的将应用泛型的中央编译并进行类型擦除,而后返回实例。然而除此之外的是,如果构建泛型实例时应用了泛型语法,那么编译器将标记该实例并关注该实例后续所有办法的调用,每次调用前都进行安全检查,非指定类型的办法都不能调用胜利。
实际上编译器不仅关注一个泛型办法的调用,它还会为某些返回值为限定的泛型类型的办法进行强制类型转换,因为类型擦除,返回值为泛型类型的办法都会擦除成 Object 类型,当这些办法被调用后,编译器会额定插入一行 checkcast 指令用于强制类型转换。这一个过程就叫做『泛型翻译』。
24. 什么是泛型中的限定通配符和非限定通配符 ?
限定通配符对类型进行了限度。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译谬误。
非限定通配符 ?,能够用任意类型来代替。如List<?>
的意思是这个汇合是一个能够持有任意类型的汇合,它能够是List<A>
,也能够是List<B>
,或者List<C>
等等。
25. List<? extends T>和List <? super T>之间有什么区别 ?
这两个List的申明都是限定通配符的例子,List<? extends T>能够承受任何继承自T的类型的List,而List<? super T>能够承受任何T的父类形成的List。例如List<? extends Number>能够承受List<Integer>或List<Float>。
26. 能够把List<String>
传递给一个承受List<Object>
参数的办法吗?
不能够。真这样做的话会导致编译谬误。因为List<Object>能够存储任何类型的对象包含String, Integer等等,而List<String>却只能用来存储String。
List<Object> objectList;List<String> stringList;objectList = stringList; //compilation error incompatible types
27. 判断ArrayList<String>
与ArrayList<Integer>
是否相等?
ArrayList<String> a = new ArrayList<String>();ArrayList<Integer> b = new ArrayList<Integer>();Class c1 = a.getClass();Class c2 = b.getClass();System.out.println(c1 == c2);
输入的后果是 true。因为无论对于 ArrayList 还是 ArrayList,它们的 Class 类型都是始终的,都是 ArrayList.class。
那它们申明时指定的 String 和 Integer 到底体现在哪里呢?
答案是体现在类编译的时候。当 JVM 进行类编译时,会进行泛型查看,如果一个汇合被申明为 String 类型,那么它往该汇合存取数据的时候就会对数据进行判断,从而防止存入或取出谬误的数据。
补充: Array中能够用泛型吗?
不能够。这也是为什么 Joshua Bloch 在 《Effective Java》一书中倡议应用 List 来代替 Array,因为 List 能够提供编译期的类型平安保障,而 Array 却不能。
28. Java序列化与反序列化是什么?
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列复原为Java对象的过程:
序列化:序列化是把对象转换成有序字节流,以便在网络上传输或者保留在本地文件中。核心作用是对象状态的保留与重建。咱们都晓得,Java对象是保留在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着隐没了。
而序列化提供了一种计划,能够让你在即便JVM停机的状况下也能把对象保留下来的计划。就像咱们平时用的U盘一样。把Java对象序列化成可存储或传输的模式(如二进制流),比方保留在文件中。这样,当再次须要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。
- 反序列化:客户端从文件中或网络上取得序列化后的对象字节流,依据字节流中所保留的对象状态及形容信息,通过反序列化重建对象。
29. 为什么须要序列化与反序列化?
简要形容:对内存中的对象进行长久化或网络传输, 这个时候都须要序列化和反序列化
深刻形容:
- 对象序列化能够实现分布式对象。
次要利用例如:RMI(即近程调用Remote Method Invocation)要利用对象序列化运行近程主机上的服务,就像在本地机上运行对象时一样。
- java对象序列化不仅保留一个对象的数据,而且递归保留对象援用的每个对象的数据。
能够将整个对象档次写入字节流中,能够保留在文件中或在网络连接上传递。利用对象序列化能够进行对象的"深复制",即复制对象自身及援用的对象自身。序列化一个对象可能失去整个对象序列。
- 序列化能够将内存中的类写入文件或数据库中。
比方:将某个类序列化后存为文件,下次读取时只需将文件中的数据反序列化就能够将原先的类还原到内存中。也能够将类序列化为流数据进行传输。
总的来说就是将一个曾经实例化的类转成文件存储,下次须要实例化的时候只有反序列化即可将类实例化到内存中并保留序列化时类中的所有变量和状态。
- 对象、文件、数据,有许多不同的格局,很难对立传输和保留。
序列化当前就都是字节流了,无论原来是什么货色,都能变成一样的货色,就能够进行通用的格局传输或保留,传输完结当前,要再次应用,就进行反序列化还原,这样对象还是对象,文件还是文件。
30. 序列化实现的形式有哪些?
实现Serializable接口或者Externalizable接口。
Serializable接口
类通过实现 java.io.Serializable
接口以启用其序列化性能。可序列化类的所有子类型自身都是可序列化的。序列化接口没有办法或字段,仅用于标识可序列化的语义。
如以下例子:
import java.io.Serializable;public class User implements Serializable { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '}'; }}
通过上面的代码进行序列化及反序列化:
public class SerializableDemo { public static void main(String[] args) { //Initializes The Object User user = new User(); user.setName("cosen"); System.out.println(user); //Write Obj to File try (FileOutputStream fos = new FileOutputStream("tempFile"); ObjectOutputStream oos = new ObjectOutputStream( fos)) { oos.writeObject(user); } catch (IOException e) { e.printStackTrace(); } //Read Obj from File File file = new File("tempFile"); try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { User newUser = (User)ois.readObject(); System.out.println(newUser); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } }}//OutPut://User{name='cosen'}//User{name='cosen'}
Externalizable接口
Externalizable
继承自Serializable
,该接口中定义了两个形象办法:writeExternal()
与readExternal()
。
当应用Externalizable
接口来进行序列化与反序列化的时候须要开发人员重写writeExternal()
与readExternal()
办法。否则所有变量的值都会变成默认值。
public class User implements Externalizable { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); } @Override public String toString() { return "User{" + "name='" + name + '}'; }}
通过上面的代码进行序列化及反序列化:
public class ExternalizableDemo1 { public static void main(String[] args) { //Write Obj to file User user = new User(); user.setName("cosen"); try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"))){ oos.writeObject(user); } catch (IOException e) { e.printStackTrace(); } //Read Obj from file File file = new File("tempFile"); try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))){ User newInstance = (User) ois.readObject(); //output System.out.println(newInstance); } catch (IOException | ClassNotFoundException e ) { e.printStackTrace(); } }}//OutPut://User{name='cosen'}
两种序列化的比照
实现Serializable接口 | 实现Externalizable接口 |
---|---|
零碎主动存储必要的信息 | 程序员决定存储哪些信息 |
Java内建反对,易于实现,只须要实现该接口即可,无需任何代码反对 | 必须实现接口内的两个办法 |
性能略差 | 性能略好 |
31. 什么是serialVersionUID?
serialVersionUID 用来表明类的不同版本间的兼容性
Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比拟,如果雷同就认为是统一的,能够进行反序列化,否则就会呈现序列化版本不统一的异样。
32. 为什么还要显示指定serialVersionUID的值?
如果不显示指定serialVersionUID, JVM在序列化时会依据属性主动生成一个serialVersionUID, 而后与属性一起序列化, 再进行长久化或网络传输. 在反序列化时, JVM会再依据属性主动生成一个新版serialVersionUID, 而后将这个新版serialVersionUID与序列化时生成的旧版serialVersionUID进行比拟, 如果雷同则反序列化胜利, 否则报错.
如果显示指定了, JVM在序列化和反序列化时依然都会生成一个serialVersionUID, 但值为咱们显示指定的值, 这样在反序列化时新旧版本的serialVersionUID就统一了.
在理论开发中, 不显示指定serialVersionUID的状况会导致什么问题? 如果咱们的类写完后不再批改, 那当然不会有问题, 但这在理论开发中是不可能的, 咱们的类会一直迭代, 一旦类被批改了, 那旧对象反序列化就会报错. 所以在理论开发中, 咱们都会显示指定一个serialVersionUID, 值是多少无所谓, 只有不变就行。
33. serialVersionUID什么时候批改?
《阿里巴巴Java开发手册》中有以下规定:
想要深刻理解的小伙伴,能够看这篇文章:https://juejin.cn/post/684490...
34. Java 序列化中如果有些字段不想进行序列化,怎么办?
对于不想进行序列化的变量,应用 transient 关键字润饰。
transient
关键字的作用是控制变量的序列化,在变量申明前加上该关键字,能够阻止该变量被序列化到文件中,在被反序列化后,transient
变量的值被设为初始值,如 int 型的是 0,对象型的是 null。transient 只能润饰变量,不能润饰类和办法。
35. 动态变量会被序列化吗?
不会。因为序列化是针对对象而言的, 而动态变量优先于对象存在, 随着类的加载而加载, 所以不会被序列化.
看到这个论断, 是不是有人会问, serialVersionUID也被static润饰, 为什么serialVersionUID会被序列化? 其实serialVersionUID属性并没有被序列化, JVM在序列化对象时会主动生成一个serialVersionUID, 而后将咱们显示指定的serialVersionUID属性值赋给主动生成的serialVersionUID。
36. Error 和 Exception 区别是什么?
Java 中,所有的异样都有一个独特的先人 java.lang
包中的 Throwable
类。Throwable
类有两个重要的子类 Exception
(异样)和 Error
(谬误)。
Exception
和 Error
二者都是 Java 异样解决的重要子类,各自都蕴含大量子类。
Exception
:程序自身能够解决的异样,能够通过catch
来进行捕捉,通常遇到这种谬误,应答其进行解决,使应用程序能够持续失常运行。Exception
又能够分为运行时异样(RuntimeException, 又叫非受查看异样)和非运行时异样(又叫受查看异样) 。Error
:Error
属于程序无奈解决的谬误 ,咱们没方法通过catch
来进行捕捉 。例如,零碎解体,内存不足,堆栈溢出等,编译器不会对这类谬误进行检测,一旦这类谬误产生,通常应用程序会被终止,仅靠应用程序自身无奈复原。
37. 非受查看异样(运行时异样)和受查看异样(个别异样)区别是什么?
非受查看异样:包含 RuntimeException
类及其子类,示意 JVM 在运行期间可能呈现的异样。 Java 编译器不会查看运行时异样。例如:NullPointException(空指针)
、NumberFormatException(字符串转换为数字)
、IndexOutOfBoundsException(数组越界)
、ClassCastException(类转换异样)
、ArrayStoreException(数据存储异样,操作数组时类型不统一)
等。
受查看异样:是Exception 中除 RuntimeException
及其子类之外的异样。 Java 编译器会查看受查看异样。常见的受查看异样有: IO 相干的异样、ClassNotFoundException
、SQLException
等。
非受查看异样和受查看异样之间的区别:是否强制要求调用者必须解决此异样,如果强制要求调用者必须进行解决,那么就应用受查看异样,否则就抉择非受查看异样。
38. throw 和 throws 的区别是什么?
Java 中的异样解决除了包含捕捉异样和解决异样之外,还包含申明异样和拋出异样,能够通过 throws 关键字在办法上申明该办法要拋出的异样,或者在办法外部通过 throw 拋出异样对象。
throws 关键字和 throw 关键字在应用上的几点区别如下:
- throw 关键字用在办法外部,只能用于抛出一种异样,用来抛出办法或代码块中的异样,受查异样和非受查异样都能够被抛出。
- throws 关键字用在办法申明上,能够抛出多个异样,用来标识该办法可能抛出的异样列表。一个办法用 throws 标识了可能抛出的异样列表,调用该办法的办法中必须蕴含可解决异样的代码,否则也要在办法签名中用 throws 关键字申明相应的异样。
举例如下:
throw 关键字:
public static void main(String[] args) { String s = "abc"; if(s.equals("abc")) { throw new NumberFormatException(); } else { System.out.println(s); } //function();}
throws 关键字:
public static void function() throws NumberFormatException{ String s = "abc"; System.out.println(Double.parseDouble(s)); } public static void main(String[] args) { try { function(); } catch (NumberFormatException e) { System.err.println("非数据类型不能转换。"); //e.printStackTrace(); }}
39. NoClassDefFoundError 和 ClassNotFoundException 区别?
NoClassDefFoundError 是一个 Error 类型的异样,是由 JVM 引起的,不应该尝试捕捉这个异样。引起该异样的起因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作产生在运行期间,即编译时该类存在,然而在运行时却找不到了,可能是变异后被删除了等起因导致。
ClassNotFoundException 是一个受查看异样,须要显式地应用 try-catch 对其进行捕捉和解决,或在办法签名中用 throws 关键字进行申明。当应用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动静加载类到内存的时候,通过传入的类门路参数没有找到该类,就会抛出该异样;另一种抛出该异样的可能起因是某个类曾经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。
40. Java常见异样有哪些?
- java.lang.IllegalAccessError:守法拜访谬误。当一个利用试图拜访、批改某个类的域(Field)或者调用其办法,然而又违反域或办法的可见性申明,则抛出该异样。
- java.lang.InstantiationError:实例化谬误。当一个利用试图通过Java的new操作符结构一个抽象类或者接口时抛出该异样.
- java.lang.OutOfMemoryError:内存不足谬误。当可用内存不足以让Java虚拟机调配给一个对象时抛出该谬误。
- java.lang.StackOverflowError:堆栈溢出谬误。当一个利用递归调用的档次太深而导致堆栈溢出或者陷入死循环时抛出该谬误。
- java.lang.ClassCastException:类造型异样。假如有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O结构为类B的实例时抛出该异样。该异样常常被称为强制类型转换异样。
- java.lang.ClassNotFoundException:找不到类异样。当利用试图依据字符串模式的类名结构类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异样。
- java.lang.ArithmeticException:算术条件异样。譬如:整数除零等。
- java.lang.ArrayIndexOutOfBoundsException:数组索引越界异样。当对数组的索引值为正数或大于等于数组大小时抛出。
- java.lang.IndexOutOfBoundsException:索引越界异样。当拜访某个序列的索引值小于0或大于等于序列大小时,抛出该异样。
- java.lang.InstantiationException:实例化异样。当试图通过newInstance()办法创立某个类的实例,而该类是一个抽象类或接口时,抛出该异样。
- java.lang.NoSuchFieldException:属性不存在异样。当拜访某个类的不存在的属性时抛出该异样。
- java.lang.NoSuchMethodException:办法不存在异样。当拜访某个类的不存在的办法时抛出该异样。
- java.lang.NullPointerException:空指针异样。当利用试图在要求应用对象的中央应用了null时,抛出该异样。譬如:调用null对象的实例办法、拜访null对象的属性、计算null对象的长度、应用throw语句抛出null等等。
- java.lang.NumberFormatException:数字格局异样。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格局时,抛出该异样。
- java.lang.StringIndexOutOfBoundsException:字符串索引越界异样。当应用索引值拜访某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异样。
41. try-catch-finally 中哪个局部能够省略?
catch 能够省略。更为严格的说法其实是:try只适宜解决运行时异样,try+catch适宜解决运行时异样+一般异样。也就是说,如果你只用try去解决一般异样却不加以catch解决,编译是通不过的,因为编译器硬性规定,一般异样如果抉择捕捉,则必须用catch显示申明以便进一步解决。而运行时异样在编译时没有如此规定,所以catch能够省略,你加上catch编译器也感觉无可非议。
实践上,编译器看任何代码都不悦目,都感觉可能有潜在的问题,所以你即便对所有代码加上try,代码在运行期时也只不过是在失常运行的根底上加一层皮。然而你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异样进行捕捉而非向上抛出解决。如果是一般异样,编译器要求必须用catch捕捉以便进一步解决;如果运行时异样,捕捉而后抛弃并且+finally开头解决,或者加上catch捕捉以便进一步解决。
至于加上finally,则是在不论有没捕捉异样,都要进行的“开头”解决。
42. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会执行,在 return 前执行。
在 finally 中扭转返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行结束之后再向调用者返回其值,而后如果在 finally 中批改了返回值,就会返回批改后的值。显然,在 finally 中返回或者批改返回值会对程序造成很大的困扰,Java 中也能够通过晋升编译器的语法查看级别来产生正告或谬误。
代码示例1:
public static int getInt() { int a = 10; try { System.out.println(a / 0); a = 20; } catch (ArithmeticException e) { a = 30; return a; /* * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回门路就造成了 * 然而呢,它发现前面还有finally,所以继续执行finally的内容,a=40 * 再次回到以前的门路,持续走return 30,造成返回门路之后,这里的a就不是a变量了,而是常量30 */ } finally { a = 40; } return a;}//执行后果:30
代码示例2:
public static int getInt() { int a = 10; try { System.out.println(a / 0); a = 20; } catch (ArithmeticException e) { a = 30; return a; } finally { a = 40; //如果这样,就又从新造成了一条返回门路,因为只能通过1个return返回,所以这里间接返回40 return a; }}// 执行后果:40
43. JVM 是如何解决异样的?
在一个办法中如果产生异样,这个办法会创立一个异样对象,并转交给 JVM,该异样对象蕴含异样名称,异样形容以及异样产生时应用程序的状态。创立异样对象并转交给 JVM 的过程称为抛出异样。可能有一系列的办法调用,最终才进入抛出异样的办法,这一系列办法调用的有序列表叫做调用栈。
JVM 会顺着调用栈去查找看是否有能够解决异样的代码,如果有,则调用异样解决代码。当 JVM 发现能够解决异样的代码时,会把产生的异样传递给它。如果 JVM 没有找到能够解决该异样的代码块,JVM 就会将该异样转交给默认的异样处理器(默认处理器为 JVM 的一部分),默认异样处理器打印出异样信息并终止应用程序。
想要深刻理解的小伙伴能够看这篇文章:https://www.cnblogs.com/qdhxh...
44. Java的IO 流分为几种?
- 依照流的方向:输出流(inputStream)和输入流(outputStream);
- 依照实现性能分:节点流(能够从或向一个特定的中央读写数据,如 FileReader)和解决流(是对一个已存在的流的连贯和封装,通过所封装的流的性能调用实现数据读写, BufferedReader);
- 依照解决数据的单位: 字节流和字符流。别离由四个抽象类来示意(每种流包含输出和输入两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其余多种多样变动的流均是由它们派生进去的。
45. 字节流如何转为字符流?
字节输出流转字符输出流通过 InputStreamReader 实现,该类的构造函数能够传入 InputStream 对象。
字节输入流转字符输入流通过 OutputStreamWriter 实现,该类的构造函数能够传入 OutputStream 对象。
46. 字符流与字节流的区别?
- 读写的时候字节流是按字节读写,字符流按字符读写。
- 字节流适宜所有类型文件的数据传输,因为计算机字节(Byte)是电脑中示意信息含意的最小单位。字符流只可能解决纯文本数据,其余类型数据不行,然而字符流解决文本要比字节流解决文本要不便。
- 在读写文件须要对内容按行解决,比方比拟特定字符,解决某一行数据的时候个别会抉择字符流。
- 只是读写文件,和文件内容无关时,个别抉择字节流。
47. 什么是阻塞IO?什么是非阻塞IO?
IO操作包含:对硬盘的读写、对socket的读写以及外设的读写。
当用户线程发动一个IO申请操作(本文以读申请操作为例),内核会去查看要读取的数据是否就绪,对于阻塞IO来说,如果数据没有就绪,则会始终在那期待,直到数据就绪;对于非阻塞IO来说,如果数据没有就绪,则会返回一个标记信息告知用户线程以后要读的数据没有就绪。当数据就绪之后,便将数据拷贝到用户线程,这样才实现了一个残缺的IO读申请操作,也就是说一个残缺的IO读申请操作包含两个阶段:
1)查看数据是否就绪;
2)进行数据拷贝(内核将数据拷贝到用户线程)。
那么阻塞(blocking IO)和非阻塞(non-blocking IO)的区别就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是始终期待,还是间接返回一个标记信息。
Java中传统的IO都是阻塞IO,比方通过socket来读数据,调用read()办法之后,如果数据没有就绪,以后线程就会始终阻塞在read办法调用那里,直到有数据才返回;而如果是非阻塞IO的话,当数据没有就绪,read()办法应该返回一个标记信息,告知以后线程数据没有就绪,而不是始终在那里期待。
深刻理解可看这篇文章:https://mp.weixin.qq.com/s/p5...
48. BIO、NIO、AIO的区别?
- BIO:同步并阻塞,在服务器中实现的模式为一个连贯一个线程。也就是说,客户端有连贯申请的时候,服务器就须要启动一个线程进行解决,如果这个连贯不做任何事件会造成不必要的线程开销,当然这也能够通过线程池机制改善。BIO个别实用于连贯数目小且固定的架构,这种形式对于服务器资源要求比拟高,而且并发局限于利用中,是JDK1.4之前的惟一抉择,但好在程序直观简略,易了解。
- NIO:同步并非阻塞,在服务器中实现的模式为一个申请一个线程,也就是说,客户端发送的连贯申请都会注册到多路复用器上,多路复用器轮询到有连贯IO申请时才会启动一个线程进行解决。NIO个别实用于连贯数目多且连贯比拟短(轻操作)的架构,并发局限于利用中,编程比较复杂,从JDK1.4开始反对。
- AIO:异步并非阻塞,在服务器中实现的模式为一个无效申请一个线程,也就是说,客户端的IO申请都是通过操作系统先实现之后,再告诉服务器利用去启动线程进行解决。AIO个别实用于连贯数目多且连贯比拟长(重操作)的架构,充沛调用操作系统参加并发操作,编程比较复杂,从JDK1.7开始反对。
49. Java IO都有哪些设计模式?
应用了适配器模式和装璜器模式
适配器模式:
Reader reader = new INputStreamReader(inputStream);
把一个类的接口变换成客户端所期待的另一种接口,从而使本来因接口不匹配而无奈在一起工作的两个类可能在一起工作
- 类适配器:Adapter类(适配器)继承Adaptee类(源角色)实现Target接口(指标角色)
- 对象适配器:Adapter类(适配器)持有Adaptee类(源角色)对象实例,实现Target接口(指标角色)
装璜器模式:
new BufferedInputStream(new FileInputStream(inputStream));
一种动静地往一个类中增加新的行为的设计模式。就性能而言,装璜器模式相比生成子类更为灵便,这样能够给某个对象而不是整个类增加一些性能。
- ConcreteComponent(具体对象)和Decorator(形象装璜器)实现雷同的Conponent(接口)并且Decorator(形象装璜器)外面持有Conponent(接口)对象,能够传递申请。
- ConcreteComponent(具体装璜器)笼罩Decorator(形象装璜器)的办法并用super进行调用,传递申请。
这里也举荐一个我收集的计算机书籍仓库,仓库目前有上百本经典cs电子书,看经典的书籍会更悟得深~
点此链接即可中转书单,计算机必看经典书籍(含pdf下载)
Github也有相应仓库,https://github.com/cosen1024/...
欢送star。