博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
面试(基础篇)
阅读量:6153 次
发布时间:2019-06-21

本文共 11261 字,大约阅读时间需要 37 分钟。

一、基础部分

1. Java 中 i++ 操作符是线程安全的吗?

不是线程安全的操作。它涉及到多个指令,如读取变量值增加,然后存储回内存,这个过程可能会出现多个线程交差。

2. a = a + b 与 a += b 的区别

+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。 a+b 会出现编译错误,但是 b += a 没问题,可是 b += a 会存在丢失位数 ,如下:

byte a = 127;byte b = 127;// b = a + b; 编译出错,需要的是byte找到的为intb += a; // 结果为-2

3. 3*0.1 == 0.3 将会返回什么?true 还是 false?

false,因为有些浮点数不能完全精确的表示出来。

4. 为什么 Java 中的 String 是不可变的(Immutable)?

原因:①字符串池;②安全;③在类加载机制中使用大量的字符串;④多线程之间共享;⑤优化和性能。缺点:由于 String 是不可变的,因此会产生大量的临时对象,这就为垃圾收集器创造了压力。Java 设计人员已经考虑过了,使用字符串池中是他们减少字符串垃圾的解决方案。字符串池位于 JVM 的方法区(PermGen 空间)中,与 JVM 堆相比,它非常有限,当字符串过多时会导致 OutOfMemoryError。从 Java7 开始,已经将字符串池搬迁至堆中。

5. Java 中的编译期常量是什么?使用它又什么风险?  

编译期常量就是所谓的 public final static 常量。 由于在编译时就确定了值,在使用的场合会直接写成值。而不是直接到原来的类中读取。这样会有一个问题。 如果类 A 提供了常量  类 B 使用了常量。并都进行了编译。然后,又修改了类 A 的源码,调用系统进行编译。系统发现类 A 是新的代码,编译了,类 B 仍然是旧的代码,就不进行编译,使用旧的类。所以导致类 A 的修改无法反映到类 B 中。这样造成了读取变量的值不同的风险

6. String、StringBuilder、StringBuffer 深入理解,各自的使用场景。

面试题 – 请说出下面程序的输出。

String s1 = "Programming";String s2 = new String("Programming");String s3 = "Program" + "ming";System.out.println(s1 == s2); //falseSystem.out.println(s1 == s3); //trueSystem.out.println(s1 == s1.intern()); //true

补充:String 对象的 intern 方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与 String 对象的 equals 结果是 true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用。

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

8. 静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?

Static Nested Class 是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化,其语法看起来挺诡异的,如下所示。

/** * 扑克类(一副扑克) * @author 骆昊 * */public class Poker {    private static String[] suites = {"黑桃", "红桃", "草花", "方块"};    private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};     private Card[] cards;     /**     * 构造器     *      */    public Poker() {        cards = new Card[52];        for(int i = 0; i < suites.length; i++) {            for(int j = 0; j < faces.length; j++) {                cards[i * 13 + j] = new Card(suites[i], faces[j]);            }        }    }     /**     * 洗牌 (随机乱序)     *      */    public void shuffle() {        for(int i = 0, len = cards.length; i < len; i++) {            int index = (int) (Math.random() * len);            Card temp = cards[index];            cards[index] = cards[i];            cards[i] = temp;        }    }     /**     * 发牌     * @param index 发牌的位置     *      */    public Card deal(int index) {        return cards[index];    }     /**     * 卡片类(一张扑克)     * [内部类]     * @author 骆昊     *     */    public class Card {        private String suite;   // 花色        private int face;       // 点数         public Card(String suite, int face) {            this.suite = suite;            this.face = face;        }         @Override        public String toString() {            String faceStr = "";            switch(face) {            case 1: faceStr = "A"; break;            case 11: faceStr = "J"; break;            case 12: faceStr = "Q"; break;            case 13: faceStr = "K"; break;            default: faceStr = String.valueOf(face);            }            return suite + faceStr;        }    }}
View Code

测试代码:

class PokerTest {     public static void main(String[] args) {        Poker poker = new Poker();        poker.shuffle();                // 洗牌        Poker.Card c1 = poker.deal(0);  // 发第一张牌        // 对于非静态内部类Card        // 只有通过其外部类Poker对象才能创建Card对象        Poker.Card c2 = poker.new Card("红心", 1);    // 自己创建一张牌         System.out.println(c1);     // 洗牌后的第一张        System.out.println(c2);     // 打印: 红心A    }}
View Code

面试题 – 下面的代码哪些地方会产生编译错误?

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();

9. Java 中会存在内存泄漏吗,请简单描述。

理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生。例如 Hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。下面例子中的代码也会导致内存泄露。

import java.util.Arrays;import java.util.EmptyStackException; public class MyStack
{    private T[] elements;    private int size = 0;     private static final int INIT_CAPACITY = 16;     public MyStack() {        elements = (T[]) new Object[INIT_CAPACITY];    }     public void push(T elem) {        ensureCapacity();        elements[size++] = elem;    }     public T pop() {        if(size == 0)             throw new EmptyStackException();        return elements[--size];    }     private void ensureCapacity() {        if(elements.length == size) {            elements = Arrays.copyOf(elements, 2 * size + 1);        }    }}
View Code

上面的代码实现了一个栈(先进后出 FILO)结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编写的各种单元测试。然而其中的 pop 方法却存在内存泄露的问题,当我们用 pop 方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过期引用(obsolete reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发 Disk Paging(物理内存与硬盘的虚拟内存交换数据),甚至造成 OutOfMemoryError。

10. 抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?

都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。

11. 深度克隆。

12. 获得一个类的类对象有哪些方式?

①类型.class;②对象.getClass();③Class.forName("对象名")。

13. 如何通过反射创建对象?

①通过类对象调用 newInstance() 方法,例如:String.class.newInstance();

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

14. 

15. 

二、集合

1. HashMap 的原理。

面试题 – 为什么恰当地设置 HashMap 的初始容量(initial capacity)是最佳实践?可以减少重散列的发生。

2. 多线程情况下 HashMap 死循环的问题。

3. ConcurrentHashMap 的工作原理及代码实现。 

4. HashSet 实现原理。

HashSet 中的元素,只是存放在了底层 HashMap 的 key 上,value使用一个static final的Object对象标识。

5. 保证插入顺序的 HashMap。

6. TreeMap、TreeSet 的排序时如何实现比较元素的?

7. 有没有可能两个不相等的对象有有相同的 hashcode?

8. 两个相同的对象会有不同的的 hashcode 吗?

9. Comparator 与 Comparable 有什么不同?

10. Collection 和 Collections 的区别?

11. 为什么在重写 equals 方法的时候需要重写 hashCode 方法?

12. Fast-Fail 机制。

推荐

三、多线程与并发编程

1. 用 wait-notify 写一段代码来解决生产者-消费者问题?

2. 用 Java 写一个线程安全的单例模式(Singleton)?

3. Java 中 sleep 方法和 wait 方法的区别?

4. 现在有 T1、T2、T3 三个线程,你怎样保证 T2 在 T1 执行完后执行,T3 在 T2 执行完后执行。

思路1:在 T1 线程启动之后,thread1.join(),同理在 T2 线程启动之后,thread2.join(),同理在 T2 线程启动之后,thread3.join()。

public class TestJoin {    public static void main(String[] args) {        Thread t1 = new MyThread("线程1");        Thread t2 = new MyThread("线程2");        Thread t3 = new MyThread("线程3");        try {            //t1先启动            t1.start();            t1.join();            //t2            t2.start();            t2.join();            //t3            t3.start();            t3.join();        } catch (InterruptedException e) {            e.printStackTrace();        }    }}class MyThread extends Thread {    public MyThread(String name) {        setName(name);    }    @Override    public void run() {        for (int i = 0; i < 5; i++) {            System.out.println(Thread.currentThread().getName() + ": " + i);            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
View Code

思路2:在 T2 线程执行前调用 thread1.join(),在 T3 线程执行前调用 thread2.join()。

public class TestJoin2 {    public static void main(String[] args) {        final Thread t1 = new Thread(() -> System.out.println("t1"));        final Thread t2 = new Thread(() -> {            try {                //引用t1线程,等待t1线程执行完                t1.join();            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("t2");        });        Thread t3 = new Thread(() -> {            try {                //引用t2线程,等待t2线程执行完                t2.join();            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("t3");        });        t3.start();        t2.start();        t1.start();    }}
View Code

5. 说说 CountDownLatch、CyclicBarrier 原理和区别 。

6. 说说 Semaphore 原理。

7. 说说 Exchanger 原理。

8. ThreadLocal 原理分析,ThreadLocal 为什么会出现 OOM,出现的深层次原理。

9. 讲讲线程池的实现原理。

10. 线程池的几种实现方式。

11. 线程的生命周期,状态是如何转移的。

12. 产生死锁的四个条件。

互斥、请求与保持、不剥夺、循环等待

13. 如何检查死锁。

通过jConsole检查死锁

14. volatile 实现原理。

禁止指令重排、刷新内存

15. synchronized 实现原理。

对象监视器

16. synchronized 与 lock 的区别。

17. AQS 同步队列。

18. 什么是 CAS。

CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。

19. 什么是 ABA 问题,JDK 是如何解决的?

因为 CAS 需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是 A,变成了 B,又变成了 A,那么使用 CAS 进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA 问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么 A-B-A 就会变成 1A-2B-3A。从 Java1.5 开始 JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。这个类的 compareAndSet 方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

20. 偏向锁、轻量级锁、重量级锁、自旋锁的概念。

推荐:《Java 并发编程的艺术》、《Java 多线程编程核心技术》

四、JVM

1. 。

2. 内存溢出 OOM 和堆栈溢出 SOE 的示例及原因、如何排查与解决。

3. 如何判断对象是否可以回收或存活。

4. 常见的 GC 回收算法及其含义。

5. 常见的 JVM 性能监控和故障处理工具类:jps、jstat、jmap、jinfo、jconsole 等。

6. JVM 如何设置参数。

7. JVM性能调优。

8. 类加载器、双亲委派模型、一个类的生命周期、类是如何加载到 JVM 中的。

9. 类加载的过程:加载、验证、准备、解析、初始化。

10.  

11. Java 内存模型 JMM。

五、设计模式

六、网络 I/O 基础

七、JDBC

1. SQL 注入

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

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

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

关闭外部资源的顺序应该和打开的顺序相反,也就是说先关闭 ResultSet、再关闭 Statement、在关闭 Connection。上面的代码只关闭了 Connection(连接),虽然通常情况下在关闭连接时,连接上创建的语句和打开的游标也会关闭,但不能保证总是如此,因此应该按照刚才说的顺序分别关闭。此外,第一步加载驱动在 JDBC 4.0 中是可以省略的(自动从类路径中加载驱动),但是我们建议保留。

3. Statemen t和 PreparedStatement 有什么区别?哪个性能更好?

与 Statement 相比,①PreparedStatement 接口代表预编译的语句,它主要的优势在于可以减少 SQL 的编译错误并增加 SQL 的安全性(减少 SQL 注射攻击的可能性);②PreparedStatement 中的 SQL 语句是可以带参数的,避免了用字符串连接拼接 SQL 语句的麻烦和不安全;③当批量处理 SQL 或频繁执行相同的查询时,PreparedStatement 有明显的性能上的优势,由于数据库可以将编译优化后的 SQL 语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。

补充:为了提供对存储过程的调用,JDBC API 中还提供了 CallableStatement 接口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的 SQL 语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦,因为每种数据库的存储过程在书写上存在不少的差别。

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

要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的 setFetchSize() 方法指定每次抓取的记录数(典型的空间换时间策略);要提升更新数据的性能可以使用 PreparedStatement 语句构建批处理,将若干SQL语句置于一个批处理中执行。

5. 在进行数据库编程时,连接池有什么作用?

由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行 TCP 的三次握手,释放连接需要进行 TCP 四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。池化技术在 Java 开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于 Java 的开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid 等。

 

转载于:https://www.cnblogs.com/westboy/p/8664662.html

你可能感兴趣的文章
有趣的数学书籍
查看>>
teamviewer 卸载干净
查看>>
eclipse的maven、Scala环境搭建
查看>>
架构师之路(一)- 什么是软件架构
查看>>
USACO 土地购买
查看>>
【原创】远景能源面试--一面
查看>>
B1010.一元多项式求导(25)
查看>>
10、程序员和编译器之间的关系
查看>>
配置 RAILS FOR JRUBY1.7.4
查看>>
AndroidStudio中导入SlidingMenu报错解决方案
查看>>
修改GRUB2背景图片
查看>>
Ajax异步
查看>>
好记性不如烂笔杆-android学习笔记<十六> switcher和gallery
查看>>
JAVA GC
查看>>
3springboot:springboot配置文件(外部配置加载顺序、自动配置原理,@Conditional)
查看>>
前端第七天
查看>>
图解SSH原理及两种登录方法
查看>>
【总结整理】JQuery基础学习---样式篇
查看>>
查询个人站点的文章、分类和标签查询
查看>>
基础知识:数字、字符串、列表 的类型及内置方法
查看>>