java基础面试
- java中有几种类型的流?
字符流和字节流。字节流继承 inputStream 和 OutputStream, 字符流继承自 InputSteamReade r和 OutputStreamWriter 。
JAVA 序列化的三种方式
- 序列化和反序列化
序列化:可以将对象转化成一个字节序列,便于存储。
反序列化:将序列化的字节序列还原
优点:可以实现对象的"持久性”, 所谓持久性就是指对象的生命周期不取决于程序。 序列化方式一: 实现Serializable接口(隐式序列化)
- 通过实现Serializable接口,这种是隐式序列化(不需要手动),这种是最简单的序列化方式,会自动序列化所有非static和 transient关键字修饰的成员变量。
序列化方式二:实现Externalizable接口。(显式序列化)
- Externalizable接口继承自Serializable, 我们在实现该接口时,必须实现writeExternal()和readExternal()方法,而且只能通过手动进行序列化,并且两个方法是自动调用的,因此,这个序列化过程是可控的,可以自己选择哪些部分序列化
- 序列化方式三:实现Serializable接口+添加writeObject()和readObject()方法。(显+隐序列化)
- 序列化和反序列化
关于包装类和基本数据类型的理解;
- 类型转换:低精度数据可以隐式地转成高精度数据,即自动向上转型,但高精度数据不能自动向上转型成低精度数据,但可以通过强制方式进行转型,但向下转型容易造成精度丢失
- 自动拆装箱:其实,包装类不过是把基本类型的值和一些常用的方法封装在了一起。包装类的核心是里面的成员value,这个value的类型就是基本类型。拆箱意思是把包装类型转换成基本类型(实际上就是把包装类里的value成员变量的值赋给基本类型);装箱则是把基本类型转换成包装类型(即将包装里的value成员初始化成对应的基本类型的值)。
- Integer a=100,b=100时候a==b返回true,而Integer c=1000,d=1000时候c==d返回false,即if条件需要i在-128和127之间,那么返回i+128作为整型数组 cache的下标,用来放在缓存中。这样也就是说任意一个相同数值的Integer的数,如果在-128和127之间,那么它们之间的内存地址是相同的。
- int a=100 integer b=100 a==b ? true,一个Integer 与 int比较,先将Integer转换成int类型,再做值比较,
- ==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。
- equals方法是用于比较两个独立对象的内容是否相同
- String a=new String("foo");
String b=new String("foo");
两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式a==b将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。 - 双等号== 比较的是地址是否相同,如果是对象的话,比较的是地址是否相同,如果是基本类型的话比较的就是值是否相同,equals比较的是两个对象存储的值是否相同,而不是比较的地址
继承Thread类创建线程类
- 定义一个继承Thread类的子类,并重写该类的run()方法;
- 创建Thread子类的实例,即创建了线程对象;
- 调用该线程对象的start()方法启动线程。
实现Runnable接口创建线程类
- 定义 Runnable 接口的实现类,并重写该接口的 run() 方法;
- 创建 Runnable 实现类的实例,并以此实例作为 Thread 的target对 象,即该 Thread 对象才是真正的线程对象。
通过Callable和Future创建线程
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建Callable实现类的实例,使用 FutureTask 类来包装Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值。
- 使用 FutureTask 对象作为Thread对象的target创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值
java线程的生命周期
- 新建状态:用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。
- 就绪状态:处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
- 运行状态:处于就绪状态的线程,如果获得了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了cpu资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。
- 阻塞状态: 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
- 死亡状态:当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。
java线程同步
- 同步方法
- 同步代码块
- 使用特殊域变量(volatile)实现线程同步
- 使用重入锁(Lock)实现线程同步
线程池
合理利用线程池能够带来三个好处。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- 使用Executors工厂类产生线程池
- List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口
- Set下有HashSet,LinkedHashSet,TreeSet
- List下有ArrayList,Vector,LinkedList
- Map下有Hashtable,LinkedHashMap,HashMap,TreeMap
- Collection接口下还有个Queue接口,有PriorityQueue类
List 有序,可重复
- ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高 - Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低 - LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
- ArrayList
Set 无序,唯一,可以用来去重
HashSet
底层数据结构是哈希表。(无序,唯一)- 如何来保证元素唯一性?
依赖两个方法:hashCode() 和 equals()
- 如何来保证元素唯一性?
- LinkedHashSet
底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
- 由链表保证元素有序
- 由哈希表保证元素唯一
- TreeSet
底层数据结构是红黑树。(唯一,有序)
- 如何保证元素排序的呢?
自然排序
比较器排序 - 如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定
Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。
- TreeMap 是有序的,HashMap 和 HashTable 是无序的。
- Hashtable 的方法是同步的,HashMap 的方法不是同步的。这是两者最主要的区别。
- Hashtable是线程安全的,HashMap不是线程安全的。
- HashMap效率较高,Hashtable效率较低。
- 如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。
- Hashtable不允许null值,HashMap允许null值(key和value都允许)
- 父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap
- java程序经过一次编译之后,将java代码编译为字节码也就是class文件,然后在不同的操作系统上依靠不同的java虚拟机进行解释,最后再转换为不同平台的机器码,最终得到执行。
JVM内存空间包含:方法区、java堆、java栈、本地方法栈。
- 方法区是各个线程共享的区域,存放类信息、常量、静态变量。
- java堆也是线程共享的区域,我们的类的实例就放在这个区域,可以想象你的一个系统会产生很多实例,因此java堆的空间也是最大的。如果java堆空间不足了,程序会抛出OutOfMemoryError异常。
- java栈是每个线程私有的区域,它的生命周期与线程相同,一个线程对应一个java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫“栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操作栈
- 本地方法栈角色和java栈类似,只不过它是用来表示执行本地方法的,本地方法栈存放的方法调用本地方法接口,最终调用本地方法库,实现与操作系统、硬件交互的目的。
java参数传递
- 基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的
- 对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。
String , StringBuffer 与 StringBuilder 的区别
- String: 是对象不是原始类型.为不可变对象,一旦被创建,就不能修改它的值.对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.String 是final类,即不能被继承
对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
- StringBuffer:是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象。它只能通过构造函数来建立对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer中赋值的时候可以通过它的 append方法 .
- StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。 每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量。
- 如果要操作少量的数据用 = String
- 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
- try···catch语句
在try代码块中抛出异常之后,立即转到catch代码块执行或者退栈到上一层方法处寻找catch代码块。 finally语句:任何情况下都必须执行的代码
由于异常会强制中断正常流程,这会使得某些不管在任何情况下都必须执行的步骤被忽略,从而影响程序的健壮性。使用finally语句,不管try代码块中是否出现了异常,都会执行finally代码块。
在某些情况下,把finally的操作放在try···catch语句的后面,这也能保证这个操作被执行。这种情况尽管在某些情况下是可行的,但不值得推荐,以为它有两个缺点:- @把与try代码块相关的操作孤立开来,使程序结构松散,可读性差。
- @影响程序的健壮性。假如catch代码块继续抛出异常,就不会执行catch代码块之后的操作。
- throws子句:声明可能会出现的异常
如果一个方法可能会抛出异常,但没有能力来处理这种异常,可以在方法声明处用throws子句来声明抛出异常。
一个方法可能会出现多种异常,throws子句允许声明抛出多个异常,中间用“,”隔开。
异常声明是接口(概念上的接口)的一部分,在JavaDoc文档中应描述方法可能抛出某种异常的条件。根据异常声明,方法调用者了解到被调用方法可能抛出的异常,从而采取相应的措施:捕获异常,或者声明继续抛出异常。 - throw语句:抛出异常
throw语句用于抛出异常。
值得注意的是,有throw语句抛出的对象必须是java.lang.Throwable类或者其他子类的实例。
- try···catch语句
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
- java重载和重写的区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分
1 条评论
《迎春阁之风波国语》动作片高清在线免费观看:https://www.jgz518.com/xingkong/27240.html