Java面试题3

七、Java的 IO

  1. Java 中有几种类型的流
    按照流的方向:输入流(inputStream)和输出流(outputStream)。 按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。如 FileReader)和处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。)
    按照处理数据的单位: 字节流和字符流。字节流继承于 InputStream 和 OutputStream,字符流继承于 InputStreamReader 和 OutputStreamWriter。

  2. 字节流如何转为字符流
    字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。 字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。

  3. 如何将一个 java 对象序列化到文件里
    在 java 中能够被序列化的类必须先实现 Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用。
    //对象输出流
    ObjectOutputStream objectOutputStream =
    new ObjectOutputStream(new FileOutputStream(new File(“D://obj”)));
    objectOutputStream.writeObject(new User(“zhangsan”, 100));
    objectOutputStream.close();
    //对象输入流
    ObjectInputStream objectInputStream =
    new ObjectInputStream(new FileInputStream(new File(“D://obj”)));
    User user = (User)objectInputStream.readObject();
    System.out.println(user);
    objectInputStream.close();

  4. 字节流和字符流的区别
    字节流读取的时候,读到一个字节就返回一个字节; 字符流使用了字节流读到一个或多个字节(中文对应的字节 数是两个,在 UTF-8 码表中是 3 个字节)时。
    先去查指定的编码表,将查到的字符返回。
    字节流可以处理所有类型数 据,如:图片,MP3,AVI 视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符 流,除此之外都用字节流。
    字节流主要是操作 byte 类型数据,以 byte 数组为准,主要操作类就是 OutputStream、 InputStream字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。
    所以字符流是由 Java 虚拟机将字节转化为 2 个字节的 Unicode 字符为单位的字符而成的, 所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用 字符流好点。
    在程序中一个字符等于两个字节,java 提供了 Reader、Writer 两个专门操作字符流的类。

  5. List 的三个子类的特点
    ArrayList 底层结构是数组,底层查询快,增删慢。
    LinkedList 底层结构是链表型的,增删快,查询慢。
    voctor 底层结构是数组 线程安全的,增删慢,查询慢。

  6. List 和 Map、Set 的区别
    1.结构特点
    List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;List 中存储的数据是有顺序,并 且允许重复;
    Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,Set 中存储的数据是无 序的,且不允许有重复,
    但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的);

2.实现类
List 接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还 存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不 便于插入删除;Vector:基于数组实现,线程安全的,效率低)。
Map 接口有三个实现类(HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null 键;HashTable:线程安全,低效,不支持 null 值和 null 键;LinkedHashMap:是 HashMap 的一个子类,保存了 记录的插入顺序;SortMap 接口:TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。
Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重 写 equals()和 hashCode()方法;LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底 层使用的是 LinkedHashMp)。

3.区别
List 集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过 list.get(i)方法来获取集合中的元素;
Map 中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对 象可以重复;
Set 集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet 类,可以按照默认顺序,也可以通过实现Java.util.Comparator接口来自定义排序 方式。

  1. HashMap 和 HashTable 有什么区别?
    HashMap 是线程不安全的,HashMap 是一个接口,是 Map 的一个子接口,是将键映射到值得对象,不允许键值重复,允许空键和空值;由于非线程安全,HashMap 的效率要较
    HashTable 的效率高一些. HashTable 是线程安全的一个集合,不允许 null 值作为一个 key 值或者 Value 值;
    HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时 候需要自己为它的方法实现同步;

  2. 数组和链表分别比较适合用于什么场景,为什么?

1.数组和链表简介
在计算机中要对给定的数据集进行若干处理,首要任务是把数据集的一部分(当数据量非常大时,可能只能一部 分一部分地读取数据到内存中来处理)或全部存储到内存中,然后再对内存中的数据进行各种处理。
例如,对于数据集 S{1,2,3,4,5,6},要求 S 中元素的和,首先要把数据存储到内存中,然后再将内存中的 数据相加。
当内存空间中有足够大的连续空间时,可以把数据连续的存放在内存中,各种编程语言中的数组一般都是按这种 方式存储的(也可能有例外),如图 1(b);当内存中只有一些离散的可用空间时,想连续存储数据就非常困难了, 这时能想到的一种解决方式是移动内存中的数据,把离散的空间聚集成连续的一块大空间,如图 1(c)所示,这样做 当然也可以,但是这种情况因为可能要移动别人的数据,所以会存在一些困难,移动的过程中也有可能会把一些别人的重要数据给丢失。另外一种,不影响别人的数据存储方式是把数据集中的数据分开离散地存储到这些不连续空间中, 如图(d)。这时为了能把数据集中的所有数据联系起来,需要在前一块数据的存储空间中记录下一块数据的地址,这样只要知道第一块内存空间的地址就能环环相扣地把数据集整体联系在一起了。C/C++中用指针实现的链表就是这种

存储形式。

由上可知,内存中的存储形式可以分为连续存储和离散存储两种。因此,数据的物理存储结构就有连续存储和离 散存储两种,它们对应了我们通常所说的数组和链表,

2.数组和链表的区别
数组是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效 率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大
小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现 越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低
链表是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需 要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任 意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)

3.链表和数组使用场景
数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建 的线性表较稳定。
链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。

4.跟数组相关的面试题
用面向对象的方法求出数组中重复value 的个数,按如下个数输出:
1 出现:1 次
3 出现:2 次
8 出现:3 次
2 出现:4 次
int[] arr = {1,4,1,4,2,5,4,5,8,7,8,77,88,5,4,9,6,2,4,1,5};

  1. Java 中 ArrayList 和 Linkedlist 区别?
    ArrayList 和 Vector 使用了数组的实现,可以认为 ArrayList 或者 Vector 封装了对内部数组的操作,比如向数组 中添加,删除,插入新的元素或者数据的扩展和重定向。
    LinkedList 使用了循环双向链表数据结构。与基于数组的 ArrayList 相比,这是两种截然不同的实现技术,这也决 定了它们将适用于完全不同的工作场景。

LinkedList 链表由一系列表项连接而成。一个表项总是包含 3 个部分:元素内容,前驱表和后驱表,如图所示:

在下图展示了一个包含 3 个元素的 LinkedList 的各个表项间的连接关系。在 JDK 的实现中,无论 LikedList 是否 为空,链表内部都有一个 header 表项,它既表示链表的开始,也表示链表的结尾。表项 header 的后驱表项便是链表 中第一个元素,表项 header 的前驱表项便是链表中最后一个元素。

  1. List a=new ArrayList()和 ArrayList a =new ArrayList()的区别?
    List list = new ArrayList();这句创建了一个 ArrayList 的对象后把上溯到了 List。
    此时它是一个 List 对象了,有些 ArrayList 有但是 List 没有的属性和方法,它就不能再用了。
    而 ArrayList list=new ArrayList();创建一对象则保留了 ArrayList 的所有属性。
    所以需要用到 ArrayList 独有的方法的时候不能用前者。实例代码如下:
    List list = new ArrayList();
    ArrayList arrayList = new ArrayList();
    list.trimToSize(); //错误,没有该方法。
    arrayList.trimToSize(); //ArrayList里有该方法。

  2. 要对集合更新操作时,ArrayList 和 LinkedList 哪个更适合?
    1.ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。
    2.如果集合数据是对于集合随机访问 get 和 set,ArrayList 绝对优于 LinkedList,因为 LinkedList 要移动指针。
    3.如果集合数据是对于集合新增和删除操作 add 和 remove,LinedList 比较占优势,因为 ArrayList 要移动数据。
    ArrayList 和 LinkedList 在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下:
    1.对 ArrayList 和 LinkedList 而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList 而言,主 要是在内部数组中增加一项,指向所添加的元素,偶 尔可能会导致对数组重新进行分配;而对 LinkedList 而言,这 个开销是统一的,分配一个内部 Entry 对象。
    2.在 ArrayList 的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在 LinkedList 的中间 插入或删除一个元素的开销是固定的。
    3.LinkedList 不支持高效的随机元素访问。
    4.ArrayList 的空间浪费主要体现在在 list 列表的结尾预留一定的容量空间,而 LinkedList 的空间花费则体现在 它的每一个元素都需要消耗相当的空间
    可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList 会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用 LinkedList 了。

  3. Map 中的 key 和 value 可以为 null 么?
    HashMap 对象的 key、value 值均可为 null。
    HahTable 对象的 key、value 值均不可为 null。
    且两者的的 key 值均不能重复,若添加 key 相同的键值对,后面的 value 会自动覆盖前面的 value,但不会报错。

九、Java的多线程和并发库
对于 Java 程序员来说,多线程在工作中的使用场景还是比较常见的,而仅仅掌握了 Java 中的传统多线程机制,还是不够的。在 JDK5.0 之后,Java 增加的并发库中提供了很多优秀的 API,在实际开发中用的比较多。因此在看具体 的面试题之前我们有必要对这部分知识做一个全面的了解。
一.多线程基础知识–传统线程机制的回顾
( 1 ) 传统使用类 Thread 和接口 Runnable 实现
1.在 Thread 子类覆盖的 run 方法中编写运行代码 方式一

2.在传递给 Thread 对象的 Runnable 对象的 run 方法中编写代码

( 2 ) 定实现时器 Timer 和 TimerTask
Timer 在实际开发中应用场景不多,一般来说都会用其他第三方库来实现。但有时会在一些面试题中出现。 下面我们就针对一道面试题来使用 Timer 定时类。
1.请模拟写出双重定时器(面试题)

( 3 ) 线程互斥与同步
在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台 打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理出错,因此线程同步的主要任务是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。 当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。

  1. 间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享 CPU,共享 I/O 设备,所谓间接相 互制约即源于这种资源共享,打印机就是最好的例子,线程 A 在使用打印机时,其它线程都要等待。
  2. 直接相互制约。这种制约主要是因为线程之间的合作,如有线程 A 将计算结果提供给线程 B 作进一步处理,那么线程 B 在线程 A 将数据送达之前都将处于阻塞状态。

间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程 A 和线程 B 互斥访问某 个资源则它们之间就会产个顺序问题——要么线程 A 等待线程 B 操作完毕,要么线程 B 等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。

2.在 java中 wait和 sleep方法的不同?
最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂 停执行。

  1. synchronized和 volatile关键字的作用
    一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:
    1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是 立即可见的。
    2)禁止进行指令重排序。
    volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
    synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
    1.volatile 仅能使用在变量级别;
    synchronized 则可以使用在变量、方法、和类级别的

2.volatile 仅能实现变量的修改可见性,并不能保证原子性;
synchronized 则可以保证变量的修改可见性和原子性

  1. volatile 不会造成线程的阻塞;
    synchronized 可能会造成线程的阻塞。

  2. volatile 标记的变量不会被编译器优化;
    synchronized 标记的变量可以被编译器优化

4.请说出同步线程及线程调度相关的方法?
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,

而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

5.启动一个线程是调用 run()方法还是 start()方法?
启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并 执行,这并不意味着线程就会立即运行。
run()方法是线程启动后要进行回调(callback)的方法。

十、Java内部类
1.静态嵌套类 (Static Nested Class) 和内部类(Inner Class)的不同?
静态嵌套类:Static Nested Class 是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例 化。
内部类:需要在外部类实例化后才能实例化,其语法看起来挺诡异的。

  1. 下面的代码哪些地方会产生编译错误?
    class Outer {
    class Inner {}
    public static void foo() {
    new Inner();
    }
    public void bar() {
    new Inner();
    }
    public static void main(String[] args) {
    new Inner();
    }
    }
    注意:Java 中非静态内部类对象的创建要依赖其外部类对象,上面的面试题中 foo 和 main 方法都是静态方法,静态方法中没有 this,也就是说没有所谓的外部类对象,因此无法创建内部类对象,如果要在静态方法中创建内 部类对象,可以这样做:new Outer().new Inner();

三、Java中的设计模式&回收机制

  1. 你所知道的设计模式有哪些
    Java 中一般认为有 23 种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。下面列 出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。
    总体来说设计模式分为三大类:
    创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
    结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
    行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模 式、状态模式、访问者模式、中介者模式、解释器模式。

  2. 单例设计模式
    最好理解的一种设计模式,分为懒汉式和饿汉式。
    饿汉式:

懒汉式:

  1. 工厂设计模式
    工厂模式分为工厂方法模式和抽象工厂模式。
    工厂方法模式
    工厂方法模式分为三种:普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。 多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。

静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。 普通工厂模式

多个工厂方法模式

该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而 多个工厂方法模式是提供多个工厂方法,分别创建对象。

静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

抽象工厂模式 工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
public interface Provider {
public Sender produce();
}
public interface Sender {
public void send();
}
public class MailSender implements Sender {
@Override
public void send() {
System.out.println(“this is mail sender!”);
}
}
public class SmsSender implements Sender {

你可能感兴趣的:(面试题)