JAVA基础面试题

来源:互联网 发布:iphone闪存检测软件 编辑:程序博客网 时间:2024/06/03 16:24

ArrayList和LinkedList 的区别?

ArrayList和LinkedList都实现了List接口,有以下的不同点:

1、ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。

2、相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。

3、LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

ArrayList和LinkedList具体使用哪一个以下 总结的使用规则:

1:事先能预知元素数量时,应优先选择ArrayList,并且在构造中进行初始化
2:事先不能预知元素数量时,根据不同的迭代需要选择ArrayList或者LinkedList
3:如果有很多的remove操作时,应优先选择LinkedList
4:需要顺序迭代,也就是从第一个元素开始一个一个地访问到最后一个时,应优先选择LinkedList
5:需要随机访问,也就是使用get(int)方法取任意下标访问时,应优先选择ArrayList

StringBuilder与 StringBuffer

    StringBuilder:线程非安全的
    StringBuffer:线程安全的
  当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。
    
对于三者使用的总结: 1.如果要操作少量的数据用 = String
          2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
          3.多线程操作字符串缓冲区 下操作大量数据 =

StringBuffer    public static void main(String[] args) {        String s1 = "Programming";        String s2 = new String("Programming");        String s3 = "Program" + "ming";        System.out.println(s1 == s2);        System.out.println(s1 == s3);        System.out.println(s1 == s1.intern());}        //结果  false  true  true        //String对象的intern方法会得到字符串对象在常量池中对应的版本的引用        //(如果常量池中有一个字符串与String对象的equals结果是true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用。 

&和&&的区别?

答:&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

抽象类(abstract class)和接口(interface)有什么异同?

答:抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?

sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第66题中的线程状态转换图)。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。

请说出与线程同步以及线程调度相关的方法。

wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

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

编程实现文件拷贝。
(这个题目在笔试的时候经常出现,下面的代码给出了两种实现方案)

import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public final class MyUtil {    private MyUtil() {        throw new AssertionError();    }    public static void fileCopy(String source, String target) throws IOException {        try (InputStream in = new FileInputStream(source)) {            try (OutputStream out = new FileOutputStream(target)) {                byte[] buffer = new byte[4096];                int bytesToRead;                while((bytesToRead = in.read(buffer)) != -1) {                    out.write(buffer, 0, bytesToRead);                }            }        }    }    public static void fileCopyNIO(String source, String target) throws IOException {        try (FileInputStream in = new FileInputStream(source)) {            try (FileOutputStream out = new FileOutputStream(target)) {                FileChannel inChannel = in.getChannel();                FileChannel outChannel = out.getChannel();                ByteBuffer buffer = ByteBuffer.allocate(4096);                while(inChannel.read(buffer) != -1) {                    buffer.flip();                    outChannel.write(buffer);                    buffer.clear();                }            }        }    }}

阐述JDBC操作数据库的步骤。

下面的代码以连接本机的Oracle数据库为例,演示JDBC操作数据库的步骤。

// 加载驱动。 Class.forName("oracle.jdbc.driver.OracleDriver");//创建连接。 Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger");//创建语句。 PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?");ps.setInt(1, 1000);ps.setInt(2, 3000);执行语句。 1ResultSet rs = ps.executeQuery();处理结果。 123while(rs.next()) {    System.out.println(rs.getInt("empno") + " - " + rs.getString("ename"));}关闭资源。 finally {    if(con != null) {        try {            con.close();        } catch (SQLException e) {            e.printStackTrace();        }    }}

使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?

要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略);要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。
在进行数据库编程时,连接池有什么作用?
由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行TCP的三次握手,释放连接需要进行TCP四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。池化技术在Java开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于Java的开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。

事务的ACID是指什么?

原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;
- 一致性(Consistent):事务结束后系统状态是一致的;
- 隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
- 持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败。通过日志和同步备份可以在故障发生后重建数据。

于事务,在面试中被问到的概率是很高的,可以问的问题也是很多的。首先需要知道的是,只有存在并发数据访问时才需要事务。当多个事务访问同一数据时,可能会存在5类问题,包括3类数据读取问题(脏读、不可重复读和幻读)和2类数据更新问题(第1类丢失更新和第2类丢失更新)。
脏读(Dirty Read):A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。
时间
转账事务A
取款事务B
T1

开始事务
T2
开始事务

T3

查询账户余额为1000元
T4

取出500元余额修改为500元
T5
查询账户余额为500元(脏读)

T6

撤销事务余额恢复为1000元
T7
汇入100元把余额修改为600元

T8
提交事务

不可重复读(Unrepeatable Read):事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。
时间
转账事务A
取款事务B
T1

开始事务
T2
开始事务

T3

查询账户余额为1000元
T4
查询账户余额为1000元

T5

取出100元修改余额为900元
T6

提交事务
T7
查询账户余额为900元(不可重复读)

幻读(Phantom Read):事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行。
时间
统计金额事务A
转账事务B
T1

开始事务
T2
开始事务

T3
统计总存款为10000元

T4

新增一个存款账户存入100元
T5

提交事务
T6
再次统计总存款为10100元(幻读)

第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了。
时间
取款事务A
转账事务B
T1
开始事务

T2

开始事务
T3
查询账户余额为1000元

T4

查询账户余额为1000元
T5

汇入100元修改余额为1100元
T6

提交事务
T7
取出100元将余额修改为900元

T8
撤销事务

T9
余额恢复为1000元(丢失更新)

第2类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。
时间
转账事务A
取款事务B
T1

开始事务
T2
开始事务

T3

查询账户余额为1000元
T4
查询账户余额为1000元

T5

取出100元将余额修改为900元
T6

提交事务
T7
汇入100元将余额修改为1100元

T8
提交事务

T9
查询账户余额为1100元(丢失更新)

数据并发访问所产生的问题,在有些场景下可能是允许的,但是有些场景下可能就是致命的,数据库通常会通过锁机制来解决数据并发访问问题,按锁定对象不同可以分为表级锁和行级锁;按并发事务锁定关系可以分为共享锁和独占锁,具体的内容大家可以自行查阅资料进行了解。
直接使用锁是非常麻烦的,为此数据库为用户提供了自动锁机制,只要用户指定会话的事务隔离级别,数据库就会通过分析SQL语句然后为事务访问的资源加上合适的锁,此外,数据库还会维护这些锁通过各种手段提高系统的性能,这些对用户来说都是透明的(就是说你不用理解,事实上我确实也不知道)。ANSI/ISO SQL 92标准定义了4个等级的事务隔离级别,如下表所示:
隔离级别
脏读
不可重复读
幻读
第一类丢失更新
第二类丢失更新
READ UNCOMMITED
允许
允许
允许
不允许
允许
READ COMMITTED
不允许
允许
允许
不允许
允许
REPEATABLE READ
不允许
不允许
允许
不允许
不允许
SERIALIZABLE
不允许
不允许
不允许
不允许
不允许
需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。
获得一个类的类对象有哪些方式?
方法1:类型.class,例如:String.class
- 方法2:对象.getClass(),例如:”hello”.getClass()
- 方法3:Class.forName(),例如:Class.forName(“java.lang.String”)

如何通过反射创建对象?

答:
- 方法1:通过类对象调用newInstance()方法,例如:String.class.newInstance()
- 方法2:通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance(“Hello”);

用Java写一个单例类。

答:
- 饿汉式单例

“`
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
懒汉式单例 public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance(){
if (instance == null) instance = new Singleton();
return instance;
}}