Java八股文(高阶)背诵版

目录

1、谈谈 JVM 的内存结构和内存分配

2、 解释内存中的栈 (stack) 、堆 (heap) 和方法区 (method area) 的用法

3、java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和 suspend()方法为何不推荐使用?

4、sleep() 和 wait() 有什么区别?

6、简述 synchronized java.util.concurrent.locks.Lock 的异同?

7、谈谈 synchronized 和 ReentrantLock 的区别

8、线程的基本概念

10、程序、进程、线程之间的关系

11、创建线程有几种方式,分别是什么?

12、线程的生命周期

13、线程 currentThread()与 interrupt()方法的使用

14、线程状态

15、启动线程的方式?start or run?

16、在 java 中守护线程和本地线程区别?

17、死锁与活锁的区别,死锁与饥饿的区别?

18、线程的调度策略

19、Java 中用到的线程调度算法是什么?

20、线程之间是如何通信的?

21、为什么 wait(), notify()和 notifyAll ()必须在同步方法或者同步块中被调用?

22、什么是线程池?有哪几种创建方式?

23、四种线程池的创建:

24、线程池的优点?

25、volatile 关键字的作用

26、死锁的原因

27、什么是 java 序列化,如何实现 java 序列化?

28、Java 序列化中如果有些字段不想进⾏序列化,怎么办?

29、Collections ⼯具类和 Arrays ⼯具类常⻅⽅法总结

30、 深拷⻉ vs 浅拷⻉

31、 ArrayList 与 Vector 区别呢?为什么要⽤Arraylist 取代 Vector 呢?

32、 HashMap 的底层实现

33、 ConcurrentHashMap 和 Hashtable 的区别

34、 ThreadLocal

35、简单的介绍⼀下强引⽤,软引⽤,弱引⽤,虚引⽤

37、 字节流与字符流的区别

38、怎么判断指定路径是否为目录

39、怎么获取指定路径下的全部文件

40、 BIO,NIO,AIO 有什么区别?

41、 JDBC 中的 PreparedStatement 相比 Statement 的好处

43、关系数据库中连接池的机制是什么?

44、List、Set 和 Map 的区别?

45、Collection 和 Collections 的区别。

46、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是 equals()? 它们有何区别?

47、HashMap 与 HashTable 的区别

48、Java 中有多少种数据结构,分别是什么?

49、Arraylist 和 linkdlist 的区别

50、List 遍历方式有多少种

51、Map 怎么遍历

52、怎么获取 Map 所有的 key,所有的 value

53、获取 Class 的实例有几种方式

54、怎么获取类中所有的方法,所有属性

55、JDBC 常用接口有哪些?

56、Statement 中 execute、executeUpdate、executeQuery 这三者的区别

57、jdbc 中怎么做批量处理的?

58、什么是 json

59、json 与 xml 的区别

60、XML 和 HTML 的区别?

61、XML 文档定义有几种形式?它们之间有何本质区别?

62、什么是 java 反射机制?

63、知道类的加载过程吗

64、知道哪些类加载器

65、什么是双亲委派模型

66、hashmap 的底层实现

67、什么是 java 内存泄漏,怎么预防?


1、谈谈 JVM 的内存结构和内存分配

a) JVM 内存模型

JDK1.8之前

Java八股文(高阶)背诵版_第1张图片

JDK1.8

Java八股文(高阶)背诵版_第2张图片

  1. 方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。常数池,源代码中的命名常量、String 常量和 static 变量保存在方法区。
  2. JavaStack 是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。最典型的 Stack 应用是方法的调用,Java 虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的 方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。所谓的虚拟机栈就是常说的栈。本地方法栈和虚拟机栈所发挥的作⽤⾮常相似,区别是: 虚拟机栈为虚拟机执⾏ Java ⽅法 (也就是字节码)服务,⽽本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务。 在HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
  3. Java 堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java 对象的内存总是在 heap 中分配。我们每天都在写代码,每天都在使用 JVM 的内存。
  4. 程序计数器:字节码解释器通过改变程序计数器来依次读取指令,从⽽实现代码的流程控制,如:顺序执⾏、选择、循环、异常处理。在多线程的情况下,程序计数器⽤于记录当前线程执⾏的位置,从⽽当线程被切换回来的时候能够知道该线程上次运⾏到哪个位置。
  5. JDK 1.8 的时候,⽅法区(HotSpot的永久代)被彻底移除了(JDK1.7就已经开始了),取⽽代之是元空间,元空间使⽤的是直接内存。

b) java 内存分配

  1. 基础数据类型直接在栈空间分配;
  2. 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
  3. 引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
  4. 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
  5. 局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待 GC 回收;
  6. 方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
  7. 字符串常量在 DATA 区域分配,this 在堆空间分配;
  8. 数组既在栈空间分配数组名称,又在堆空间分配数组实际的大小!

2、 解释内存中的栈 (stack) 、堆 (heap) 和方法区 (method area) 的用法

通常一个基本数据类型的变量,一个对象的引用,以及函数调用的现场保存都使用 JVM 中的栈空间;而通过 new 关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,现在的垃圾收集器都采用分代收集算法,故堆空间还可以细分为新生代和老生代,再具体一点可以分为 Eden、Survivor(又可分为From Survivor 和 To Survivor)、Tenured;

方法区和堆都是各个线程共享的内存区域,用于存储已经被 JVM 加载的类信息、常量、静态变量、JIT 编译器编译后的代码等数据;

程序中的字面量(literal)如直接书写的 100、"hello" 和常量都是放在常量池中,常量池是方法区的一部分。

栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过 JVM 的启动参数来进行调整(栈空间用光了会引发 StackOverflowError,而堆和常量池空间不足则会引发 OutOfMemoryError)

3、java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和 suspend()方法为何不推荐使用?

实现线程有两种方式:1.继承 Thread 类,重写 run 方法,在调用 start 方法。

实现 Runnable 接口,重写 run 方法。在传给 Thread 构造器,调用时调用 Thread 的 start 方法。

用 synchronized 关键字修饰同步方法 。

不使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread 类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程

4、sleep() 和 wait() 有什么区别?

sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。

wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

5、当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可进入此对象的其它方法?

分几种情况:

    1. 其他方法前是否加了 synchronized 关键字,如果没加,则能。
    2. 如果这个方法内部调用了 wait,则可以进入其他 synchronized 方法。
    3. 如果其他个方法都加了 synchronized 关键字,并且内部没有调用 wait,则不能。
    4. 如果其他方法是 static,它用同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是 this。

6、简述 synchronized java.util.concurrent.locks.Lock 的异同?

答: Lock 是 Java 5 以后引入的新的 API,和关键字 synchronized 相比主要相同点:Lock 能完成 synchronized 所实现的所有功能;主要不同点:Lock 有比synchronized 更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且最好在 finally 块中释放(这是释放外部资源的最好的地方)。

7、谈谈 synchronized 和 ReentrantLock 的区别

  1. synchronized 依赖于 JVM ⽽ ReentrantLock 依赖于 API

synchronized 是依赖于 JVM 实现的,虚拟机团队在 JDK1.6 为 synchronized 关键字进⾏了很多优化,这些优化都是在虚拟机层⾯实现的,并没有直接暴露给开发人员。

ReentrantLock 是 JDK 层⾯实现的(也就是 API 层⾯,需要 lock() 和 unlock() ⽅法配合try/finally 语句块来完成)

        2.ReentrantLock ⽐ synchronized 增加了⼀些⾼级功能

①等待可中断,可以通过lock.lockInterruptibly()来实现中断等待锁的线程的机制,使其放弃等待改为处理其他事情。

②可实现公平锁;ReentrantLock可以指定是公平锁还是⾮公平锁。⽽synchronized只能是⾮公平锁。

③可实现选择性通知(锁可以绑定多个条件)ReentrantLock类结合Condition实例可以实现“选择性通知”,而使用synchronized关键的话执行notifyAll()会通知所有等待线程。

8、线程的基本概念

一个程序中可以有多条执行线索同时执行,一个线程就是程序中的一条执行线索,每个线程上都关联有要执行的代码,即可以有多段程序代码同时运行,每个程序至少都有一个线程,即 main 方法执行的那个线程。如果只是一个 cpu,它怎么能够同时执行多段程序呢?这是从宏观上来看的,cpu 一会执行 a 线索,一会执行 b 线索,切换时间很快,给人的感觉是 a,b 在同时执行,好比大家在同一个办公室上网,只有一条链接到外部网线,其实,这条网线一会为 a 传数据,一会为 b 传数据,由于切换时间很短暂,所以,大家感觉都在同时上网。

9、什么是多线程

线程是程序执行流的最小单元,相对独立、可调度的执行单元,是系统独立调度和分派

CPU 的基本单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

10、程序、进程、线程之间的关系

程序是一段静态的代码,是应用软件执行的蓝本。

进程是程序一次动态执行的过程,它对应了从代码加载、执行完毕的一个完整过程,这也是进程开始到消亡的过程。

线程是进程中独立、可调度的执行单元,是执行中最小单位。

一个程序一般是一个进程,但可以一个程序中有多个进程。

一个进程中可以有多个线程,但只有一个主线程。

Java 应用程序中默认的主线程是 main 方法,如果 main 方法中创建了其他线程,JVM

就会执行其他的线程。

11、创建线程有几种方式,分别是什么?

创建线程有三种方式:

  1. 是继承 Thread 类,创建格式如下:Thread thread = new Thread();
  2. 是实现 Runnable 接口,创建格式如下: Thread thread = new Thread(new Runnable()); 其实 Thread 类实现了 Runnable 接口
  3. 是实现 Callable 接口
    Callable oneCallable = new SomeCallable();
    FutureTask oneTask = new FutureTask(oneCallable);
    Thread oneThread = new Thread(oneTask); 
    oneThread.start();

  4. 通过线程池方式,获取线程
    package com.myjava.thread; 
    import java.util.concurrent.ExecutorService; 
    import java.util.concurrent.Executors; 
    public class ThreadPool { 
        private      static int POOL_NUM = 10;
        public static void main(String[] agrs){
    
            ExecutorService executorService = Executors.newFixedThreadPool(5); 
            for (int i = 0; i < POOL_NUM; i++) {
    
                RunnableThread thread = new RunnableThread(); executorService.execute(thread);
    
            }
    
        } 
    } 
    class RunnableThread implements Runnable{ 
        private  int THREAD_NUM = 10;
    
        public void run() { 
            for (int i = 0; i 

12、线程的生命周期

新建—就绪 –运行—阻塞—销毁新建:就是使用 new 方法,new 出来的线程就绪:当调用线程的 start()方法后,这时候线程属于等待 CPU 分配资源阶段,谁先抢到 cpu 资源谁开始执行运行:当就绪的线程被调度并且获得 cpu 资源时,便进入运行状态,run 方法定义了线程的操作和功能阻塞:在运行状态的时候,可能因为某些原因导致运行状态线程进入阻塞状态,比如 sleep() 和 wait()之后线程就属于阻塞状态,这时需要其它机制将处于阻塞状态的线程唤醒,比如 notify()或者 notifyAll()方法,唤醒的线程不会立即执行 run()方法,它们要再次等待 cpu 分配资源进入运行状态。

销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致终止,那么现场就被销毁,释放资源。

Java八股文(高阶)背诵版_第3张图片

13、线程 currentThread()与 interrupt()方法的使用

currentThread()方法是获取当前线程

interrupt()唤醒休眠线程,休眠线程发生 InterruptedException 异常

14、线程状态

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
  3. 运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。
  5. 死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。

15、启动线程的方式?start or run?

启动线程的方式为调用线程的 start()方法。

调用线程的 run()方法将不会开启线程,而是在原来的主线程上运行。

16、在 java 中守护线程和本地线程区别?

java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。

任何线程都可以设置为守护线程和用户线程,通过方法 Thread.setDaemon(boolean);true 则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在

Thread.start()之前调用,否则运行时会抛出异常。

两者的区别:

唯一的区别是判断虚拟机(JVM)何时离开,Daemon 是为其他线程提供服务,如果全部的 User

Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。也可以理解为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的线程;举例:JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。

扩展:ThreadDump 打印出来的线程信息,含有 daemon 字样的线程即为守护进程,可能会有:服务守护进程、编译守护进程、windows 下的监听 Ctrl+break的守护进程、Finalizer 守护进程、引用处理守护进程、GC 守护进程。

17、死锁与活锁的区别,死锁与饥饿的区别?

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

产生死锁的必要条件:

  1. 互斥条件:所谓互斥就是进程在某一时间内独占资源。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。 4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。

饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。

死锁与饥饿的区别在于,进程会处于饥饿状态是因为持续的有其他优先级更高的进程请求相同的资源,不像死锁,饥饿能够被解开,比如当其他高优先级的进程都终止时并且没有更高优先级的进程到达。

Java 中导致饥饿的原因:

  1. 高优先级线程吞噬所有的低优先级线程的 CPU 时间。
  2. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
  3. 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。

18、线程的调度策略

线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:

  1. 线程体中调用了 yield 方法让出了对 cpu 的占用权利
  2. 线程体中调用了 sleep 方法使线程进入睡眠状态
  3. 线程由于 IO 操作受到阻塞
  4. 另外一个更高优先级线程出现
  5. 在支持时间片的系统中,该线程的时间片用完

19、Java 中用到的线程调度算法是什么?

采用时间片轮转的方式。可以设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别需要,尽量不要用,防止线程饥饿。

20、线程之间是如何通信的?

当线程间是可以共享资源时,线程间通信是协调它们的重要的手段。Object 类中 wait()\notify()\notifyAll()方法可以用于线程间通信关于资源的锁的状态。

21、为什么 wait(), notify()和 notifyAll ()必须在同步方法或者同步块中被调用?

当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

22、什么是线程池?有哪几种创建方式?

线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考

虑使用线程池来提升系统的性能。java 提供了一个 java.util.concurrent.Executor 接口的实现用于创建线程池。

23、四种线程池的创建:

  1. newCachedThreadPool 创建一个可缓存线程池
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。

24、线程池的优点?

  1. 重用存在的线程,减少对象创建销毁的开销。
  2. 可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  3. 提供定时执行、定期执行、单线程、并发数控制等功能。

25、volatile 关键字的作用

对于可见性,Java 提供了 volatile 关键字来保证可见性。

当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

扩展:从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详

细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。

26、死锁的原因

  1. 是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环。例如:线程在获得了锁 A 并且没有释放的情况下去申请锁 B,这时,另一个线程已经获得了锁 B,在释放锁 B 之前又要先获得锁 A,因此闭环发生,陷入死锁循环。
  2. 默认的锁申请操作是阻塞的。所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对 象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用其它对象的延时方法和同步方法

27、什么是 java 序列化,如何实现 java 序列化?

通俗的说,就是可以将内存中 Java 对象可以写在硬盘上(序列化到硬盘上),反序列化就是讲硬盘的内容读取到内存中去;java 是通过实现 Serializable 接口,实现的序列化,Serializable 接口里面没有任何的方法,只是个标示接口。

28、Java 序列化中如果有些字段不想进⾏序列化,怎么办?

1.使用static修饰,序列化保存的是对象的状态,而static修饰的字段属于类的状态。

2.使⽤ transient 关键字修饰。

transient 关键字的作⽤是:阻⽌实例中那些⽤此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。

3.Externalizable:他是Serializable接口的子类,可以使用这个接口的writeExternal() 和readExternal()方法可以指定序列化哪些属性。

29、Collections ⼯具类和 Arrays ⼯具类常⻅⽅法总结

Collections:

1.排序

void reverse(List list)//反转

void shuffle(List list)//随机排序 void sort(List list)//按自然排序的升序排序 void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑 void swap(List list, int i , int j)//交换两个索引位置的元素 voidrotate(Listlist,intdistance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。

2.查找

intbinarySearch(Listlist,Objectkey)//对List进行二分查找,返回索引,注意List

必须是有序的 int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比 intmin(Collection coll)

int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由 Comparatator类控制。类比int min(Collection coll, Comparator c) void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。

int frequency(Collection c, Object o)//统计元素出现次数 intindexOfSubList(Listlist,Listtarget)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).

boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素

3. 同步控制(不推荐使用)

synchronizedCollection(Collection c) //返回指定 collection 支持的同步(线程安全的)collection。 synchronizedList(List list)//返回指定列表支持的同步(线程安全的)List。 synchronizedMap(Map m) //返回由指定映射支持的同步(线程安全的)Map。 synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。

Arrays:

排序 : sort()

查找 : binarySearch()

比较: equals()

填充 : fill()

转列表: asList()

转字符串 : toString()

复制: copyOf()

30、 深拷⻉ vs 浅拷⻉

  1. 浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉。
  2. 深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容。

Java八股文(高阶)背诵版_第4张图片

31、 ArrayList 与 Vector 区别呢?为什么要⽤Arraylist 取代 Vector 呢?

Vector 类的所有⽅法都是同步的。可以由两个线程安全地访问⼀个Vector对象、但是⼀个线程访问Vector的话代码要在同步操作上耗费⼤量的时间。

Arraylist不是同步的,所以在不需要保证线程安全时建议使⽤Arraylist。

32、 HashMap 的底层实现

JDK1.8之前

JDK1.8之前HashMap底层是数组和链表结合在⼀起使⽤也就是链表散列。HashMap 通过 key 的hashCode经过扰动函数处理过后得到hash值,然后通过(n - 1) & hash判断当前元素存放的位置(这⾥的n指的是数组的⻓度),如果当前位置存在元素的话,就判断该元素与要存⼊的元素的hash值以及key是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。所谓扰动函数指的就是HashMap的hash ⽅法。使⽤hash⽅法也就是扰动函数是为了防⽌⼀些实现⽐较差的 hashCode(),相⽐于 JDK1.8 的 hash ⽅法 ,JDK 1.7 的 hash ⽅法的性能会稍差⼀点点,因为毕竟扰动了 4 次。

所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建⼀个链表数组,数组中每⼀格就是⼀个链表。若遇到hash冲突,则将冲突的值加到链表中即可。

JDK1.8之后 相⽐于之前的版本, JDK1.8之后在解决哈希冲突时有了⼤的变化,当链表⻓ 度⼤于阈值(默认为8)时,将链表转化为红⿊树,以减少搜索时间。

33、 ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的⽅式上不同。

底层数据结构:

JDK1.7的 ConcurrentHashMap 底层采⽤分段的数组+链表实现,JDK1.8采⽤的数据结构跟

HashMap1.8的结构⼀样,数组+链表/红⿊⼆叉树。

Hashtable和JDK1.8之前的HashMap 的底层数据结构类似都是采⽤ 数组+链表 的形式,数组是 HashMap的主体,链表则是主要为了解决哈希冲突⽽存在的;实现线程安全的⽅式(重要):

1.在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进⾏了分割分段(Segment),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。到了 JDK1.8 的时候已经摒弃了Segment的概念,⽽是直接⽤ Node数组+链表+红⿊树的数据结构来实现,并发控制使⽤ synchronized和 CAS 来操作。整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本;

② Hashtable(同⼀把锁) :使synchronized 来保证线程安全,效率常低下。当⼀个线程访问同步⽅法时,其他线程也访问同步⽅法,可能会进⼊阻塞或轮询状态,如使⽤ put 添加元素,另⼀个线程不能使⽤ put 添加元素,也不能使⽤ get,竞争会越来越激烈

34、 ThreadLocal

通常情况下,创建的变量是可以被任何⼀个线程访问并修改的。ThreadLocal可以实现每⼀个线程都有⾃⼰的专属本地变量。ThreadLocal 类主要解决的是让每个线程绑定⾃⼰的值,创建了⼀个 ThreadLocal 变量之后,访问这个变量的每个线程都会有这个变量的本地副本。他们可以使⽤ get() 和 set() ⽅法来获取默认值或将其值更改为当前线程所存的副本的值,从⽽避免线程安全问题。

35、简单的介绍⼀下强引⽤,软引⽤,弱引⽤,虚引⽤

强引(StrongReference) :我们使⽤的⼤部分引⽤实际上都是强引⽤,这是使⽤最普遍的引⽤。如果⼀个对象具有强引⽤,垃圾回收器绝不会回收它。当内存空间不⾜,Java虚拟机宁愿抛出 OutOfMemoryError错误,使程序异常终⽌,也不会靠随意回收具有强引⽤的对象来解决内存不⾜问题。

软引(SoftReference) :如果⼀个对象只具有软引⽤,如果内存空间⾜够,垃圾回收器就不会回收它,如果内存空间不⾜了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使⽤。软引⽤可⽤来实现内存敏感的⾼速缓存。

弱引用(WeakReference) :弱引⽤与软引⽤的区别在于:只具有弱引⽤的对象拥有更短暂的⽣命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,⼀旦发现了只具有弱引⽤的对象,不管当前内存空间⾜够与否,都会回收它的内存。不过,由于垃圾回收器是⼀个优先级很低的线程, 因此不⼀定会很快发现那些只具有弱引⽤的对象。

虚引(PhantomReference):"虚引⽤"顾名思义,就是形同虚设,与其他⼏种引⽤都不同,虚引⽤并不会决定对象的⽣命周期。如果⼀个对象仅持有虚引⽤,那么它就和没有任何引⽤⼀样,在任何时候都可能被垃圾回收。

软引⽤和软引用都可以和引⽤队列(ReferenceQueue)联合使⽤,如果软引⽤或者弱引用所引⽤的对象被垃圾回收,JAVA 虚拟机就会把这个软引⽤加⼊到与之关联的引⽤队列中。

而虚引⽤必须和引⽤队列(ReferenceQueue)联合使⽤。当垃圾回收器准备回收⼀个对象时,如果发现它还有虚引⽤,就会在回收对象的内存之前,把这个虚引⽤加⼊到与之关联的引⽤队列中。特别注意,在程序设计中⼀般很少使⽤弱引⽤与虚引⽤,使⽤软引⽤的情况多,这是因为软引⽤可以加速JVM对垃圾内存的回收速度,可以维护系统的运⾏安全,防⽌内存溢出(OutOfMemory)等问题的产生

37、 字节流与字符流的区别

把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式起名为 IO 流,对应的抽象类为 OutputStream 和 InputStream ,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。

在应用中,经常要完全是字符的一段文本输出去或读进来,用字节流可以吗?计算机中

的一切最终都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,人家专门提供了字符流的包装类。

底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向 IO 设别写入或读取字符串提供了一点点方便。

字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,

其实是转成该字符的某种编码的字节形式,读取也是反之的道理。

讲解字节流与字符流关系的代码案例: 
import java.io.BufferedReader; 
import java.io.FileInputStream;
import java.io.FileOutputStream; 
import java.io.FileReader; 
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class IOTest { 
    public static void main(String[] args) throws Exception {
        String str = "中国人";
	    /*FileOutputStream fos	= new FileOutputStream("1.txt");
        fos.write(str.getBytes("UTF-8")); fos.close();*/
        /*FileWriter fw = new FileWriter("1.txt"); fw.write(str); fw.close();*/
        PrintWriter pw = new PrintWriter("1.txt","utf-8"); pw.write(str); pw.close();
        /*FileReader fr = new FileReader("1.txt"); char[] buf = new char[1024]; int len =                 fr.read(buf);
        String myStr = new String(buf,0,len);
        System.out.println(myStr);*/
        /*FileInputStream fr = new FileInputStream("1.txt"); byte[] buf = new byte[1024];         int len = fr.read(buf);
        String myStr = new String(buf,0,len,"UTF-8");
        System.out.println(myStr);*/
        BufferedReader br = new BufferedReader( new InputStreamReader( new         FileInputStream("1.txt"),"UTF-8"));
        String myStr = br.readLine(); br.close();
        System.out.println(myStr);
    }
}

总结:很简单,字符流的底层就是字节流。而字符流主要是读取文本文件内容的,可以一个字符一个字符的读取,也可以一行一行的读取文本文件内容。而字节流读取单位为 byte.byte 作为计算机存储最基本单位,可以用字节流来读取很多其他格式的文件,比如图片视频等等。

基于 B/S 和 C/S 的文件传输都可以采用字节流的形式。

38、怎么判断指定路径是否为目录

File f = new File(fileName);  //构造文件 File 类 f.isDirectory();     //判断是否为目录

39、怎么获取指定路径下的全部文件

File f = new File(filePath);   //构造文件 File 类

String[] fileName = f.list();    //获取目录下的文件名

File[] files = f.listFiles();    //获取目录下的文件

40、 BIO,NIO,AIO 有什么区别?

BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写⼊必须阻塞在⼀个线程内等待其完成。在活动连接数不是特别⾼(⼩于单机 1000)的情况下,这种模式比较适用,可以让每⼀个连接专注于⾃⼰的 I/O 并且编程模型简单,也不⽤过多考虑系统的过载、限流等问题。线程池本身可以缓冲⼀些系统处理不了的连接或请求。但是,当⾯对⼗万甚⾄百万级连接的时候,传统的 BIO 模型是处理不了的的。

NIO (Non-blocking/New I/O): NIO 是⼀种同步⾮阻塞的 I/O 模型,它⽀持⾯向缓冲的,基于通道的 I/O 操作⽅法。 NIO提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和ServerSocketChannel 两种不同的套接字通道实现,两种通道都⽀持阻塞和⾮阻塞两种模式。阻塞模式比较简单,但是性能和可靠性都不好;⾮阻塞模式正好与之相反。对于低负载、低并发的应⽤程序,可以使⽤同步阻塞 I/O 来提升开发速率和更好的维护性;对于⾼负载、⾼并发的(⽹络)应⽤,应使⽤ NIO 的⾮阻塞模式来开发

AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引⼊了 NIO 的改进版 NIO 2, 它是异步⾮阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,应⽤操作之后会直接返回,不会堵塞,当后台处理完成,操作系统会通知相应的线程进⾏后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在⽹络操作中,提供了⾮阻塞的⽅法,但是 NIO 的 IO ⾏为还是同步的。对于 NIO 来说,业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程

⾃⾏进⾏ IO 操作,IO 操作本身是同步的。⽬前来说 AIO 的应⽤还不是很⼴泛,Netty 之前也尝试使⽤过 AIO,不过在后来又放弃了。

41、 JDBC 中的 PreparedStatement 相比 Statement 的好处

  1. 提高性能:在使用 preparedStatement 对象执行 sql 时候,命令被数据库编译和解析,然后被放到命令缓冲区,然后每当执行同一个 preparedStatement 时候,他就被再解析一次,但不会在编译,在缓冲区中可以发现预编译的命令,并且可以重新使用。如果你要写 Insert update delete 最好使用 preparedStatement,在有大量用户的企业级应用软件中,经常会执行相同的 sql,使用 preparedStatement 会增加整体的性能。
  2. 安全性:PreparedStatement 可以防止 sql 注入。

 42、写一个用 jdbc 连接实例。

package com.seecen.stream; 
import java.sql.*;
public class TestJDBC {
    /**
    *	1、实例话驱动类
    *	2、建立到数据库的连接
    *	3、将数据发送到数据库中
    *	4、执行语句(select 语句)
    *	5、关闭
    *	@param args
    */ 
    public static void main(String[] args) {
        ResultSet rs = null;
        Statement stmt = null; Connection conn = null; 
        try {
            Class.forName("oracle.jdbc.driver.OracleDriver"); conn	=	                                        DriverManager.getConnection("jdbc:oracle:thin:@192.168.0.1:1521:yuewei","scott", "tiger");
            stmt = conn.createStatement(); 
            rs = stmt.executeQuery("select * from dept"); 
            while(rs.next()) {
                System.out.println(rs.getString("deptno"));
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally { 
            try { 
                if(rs != null) {
                    rs.close(); 
                    rs = null; 
                }
                if(stmt != null) { 
                    stmt.close();
                    stmt = null;
                }
                if(conn != null) {
                    conn.close();
                    conn = null;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

43、关系数据库中连接池的机制是什么?

前提:为数据库连接建立一个缓冲池。

1:从连接池获取或创建可用连接

2:使用完毕之后,把连接返回给连接池

3:在系统关闭前,断开所有连接并释放连接占用的系统资源

4:能够处理无效连接,限制连接池中的连接总数不低于或者不超过某个限定值。

44、List、Set 和 Map 的区别?

  1. List 和 Set 是 Collection 的子接口,map 不是。
  2. List 的底层是数组的方式实现,Set 是散列表的方式实现,map 是键值对的方式。
  3. list 是有序可重复的,Set 是无序不可重复的,map 是有序,key 不重复,value 可重复
  4. list 和 Set 可直接使用 itertator 来进行遍历,map 只能通过先遍历 Key 在遍历 value.

45、Collection 和 Collections 的区别。

Collection 是集合类的上级接口,继承与他的接口主要有 Set 和 List.

Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

46、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是 equals()? 它们有何区别?

Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。

equals()和==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。

47、HashMap 与 HashTable 的区别

  1. 继承不同public class Hashtable extends Dictionary implements Mappublic class HashMap    extends AbstractMap implements Map
  2. Hashtable 中的方法是同步的,而 HashMap 中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用 Hashtable,但是要使用 HashMap 的话就要自己增加同步处理了。
  3. Hashtable 中,key 和 value 都不允许出现 null 值,在 HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null。当 get()方法返回 null 值时,即可以表示 HashMap 中没有该键,也可以表示该键所对应的值为 null。因此,在 HashMap 中不能由 get()方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey()方法来判断。
  4. Hashtable 和 HashMap 它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable 中 hash 数组默认大小是 11,增加的方式是 old*2+1。HashMap 中 hash 数组的默认大小是 16,而且一定是 2 的指数

48、Java 中有多少种数据结构,分别是什么?

List:是列表,有下标值,存储元素可以重复,遍历元素是有序的。

Set:是散列集,无下标值,存储元素不可重复,遍历元素时无序的。

Map:是以键值对存储,一个 key 一个 value,key 不可以重复,value 可以重复。

数组:指定类型,固定长度,元素存储地址是连续的。

树:元素以树形结构存储,只有一个根节点。

栈:元素是先进后出,后进先出。

向量:动态数组,可以存储任何类型元素,动态长度,元素存储地址是连续的。

队列:元素存储是排列有序的,一定保证先进的先出,后进的后出。

49、Arraylist 和 linkdlist 的区别

相同点:

ArrayList 和 Linklist 都是接口 List 的实现类,里面的数据都是有序可重复的。

区别:

ArrayList: 采用的是数组形式保存对象的,访问速度更快,而 Linklist 的插入和删除元素的速度更快

50、List 遍历方式有多少种

  1. 下标遍历
  2. Iterator 遍历
  3. Foreach 遍历(最快)

51、Map 怎么遍历

先调用keySet()方法获取所有的key,在遍历key获取所有的元素

52、怎么获取 Map 所有的 key,所有的 value

Map 调用keySet()方法获取所有的key值,是一个Set集合

Map 调用 values()方法获取所有的 value 值,是一个 Collection 集合 set 和 list 集合父类

53、获取 Class 的实例有几种方式

Class demo1=Class.forName("Reflect.Demo");     //使用 Class 类

Class demo2=new Demo().getClass();      //通过对象

Class demo3=Demo.class;       //通过类

54、怎么获取类中所有的方法,所有属性

获取所有方法:

Class demo = Class.forName("Reflect.Demo");

Method[] methods = Demo. getDeclaredMethods();

获取所有属性:

Class demo = Class.forName("Reflect.Demo");

Field[] fields = demo .getDeclaredFields();

55、JDBC 常用接口有哪些?

1、Connection

用来与数据库建立连接

2、Statement

用来执行 sql 语句

3、ResultSet

用于接收结果集

4、PreparedStatement

是 Statement 子接口,执行 sql 语句,预编译,防止 sql 注入,安全性高

56、Statement 中 execute、executeUpdate、executeQuery 这三者的区别

Statement 接口提供了三种执行 SQL 语句的方法:executeQuery、executeUpdate 和 execute。使用哪一个方法由 SQL 语句所产生的内容决定。

方法 executeQuery

用于产生单个结果集的语句,例如 SELECT 语句。 被使用最多的执行 SQL 语句的方法是 executeQuery。这个方法被用来执行 SELECT 语句,它几乎是使用最多的 SQL 语句。

方法 executeUpdate

用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或 DELETE 语句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一个整数,指示受影响的行数(即更新计数)。对于 CREATE TABLE 或 DROP TABLE 等不操作行的语句, executeUpdate 的返回值总为零。使用 executeUpdate 方法是因为在 createTableCoffees 中的 SQL 语句是 DDL(数据定义语言)语句。创建表,改变表,删除表都是 DDL 语句的例子,要用 executeUpdate 方法来执行。你也可以从它的名字里看出,方法 executeUpdate 也被用于执行更新表 SQL 语句。实际上,相对于创建表来说,executeUpdate 用于更新表的时间更多,因为表只需要创建一次,但经常被更新。

方法 execute:

用于执行返回多个结果集、多个更新计数或二者组合的语句。因为多数程序员不会需要该高级功能execute 方法应该仅在语句能返回多个 ResultSet 对象、多个更新计数或 ResultSet 对象与更新计数的组合时使用。当执行某个已存储过程或动态执行未知 SQL 字符串(即应用程序程序员在编译时未知)时,有可能出现多个结果的情况,尽管这种情况很少见。因为方法 execute 处理非常规情况,所以获取其结果需要一些特殊处理并不足为怪。例如,假定已知某个过程返回两个结果集,则在使用方法 execute 执行该过程后,必须调用方法 getResultSet 获得第一个结果集,然后调用适当的 getXXX 方法获取其中的值。要获得第二个结果集,需要先调用 getMoreResults 方法,然后再调用 getResultSet 方法。如果已知某个过程返回两个更新计数,则首先调用方getUpdateCount ,然后调用 getMoreResults,并再次调用 getUpdateCount。对于不知道返回内容,则情况更为复杂。如果结果是 ResultSet 对象,则方法 execute 返回 true;如果结果是 Java int,则返回 false。如果返回 int,则意味着结果是更新计数或执行的语句是 DDL 命令。在调用方法 execute 之后要做的第一件事情是调用 getResultSet 或 getUpdateCount。调用方法 getResultSet 可以获得两个或多个 ResultSet 对象中第一个对象;或调用方法 getUpdateCount 可以获得两个或多个更新计数中第一个更新计数的内容。当 SQL 语句的结果不是结果集时,则方法 getResultSet 将返回 null。这可能意味着结果是一个更新计数或没有其它结果。在这种情况下,判断 null 真正含义的唯一方法是调用方法 getUpdateCount,它将返回一个整数。这个整数为调用语句所影响的行数;如果为 -1 则表示结果是结果集或没有结果。如果方法 getResultSet 已返回 null(表示结果不是 ResultSet 对象),则返回值 -1 表示没有其它结果。也就是说,当下列条件为真时表示没有结果(或没有其它结果):((stmt.getResultSet() == null) && (stmt.getUpdateCount() == -1))如果已经调用方法 getResultSet 并处理了它返回的 ResultSet 对象,则有必要调用方法 getMoreResults 以确定是否有其它结果集或更新计数。如果 getMoreResults 返回 true,则需要再次调用 getResultSet 来检索下一个结果集。如上所述,如果 getResultSet 返回 null,则需要调用 getUpdateCount 来检查 null 是表示结果为更新计数还是表示没有其它结果。当 getMoreResults 返回 false 时,它表示该 SQL 语句返回一个更新计数或没有其它结果。因此需要调用方法 getUpdateCount 来检查它是哪一种情况。在这种情况下,当下列条件为真时表示没有其它结果:

((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))

下面的代码演示了一种方法用来确认已访问调用方法 execute 所产生的全部结果集和更新计数:

stmt.execute(queryStringWithUnknownResults); while (true) {

int rowCount = stmt.getUpdateCount();

if (rowCount > 0) { // 它是更新计数

System.out.println("Rows changed = " + count); stmt.getMoreResults(); continue;

}

if (rowCount == 0) { // DDL 命令或 0 个更新

System.out.println(" No rows changed or statement was DDL command"); stmt.getMoreResults(); continue;

}

// 执行到这里,证明有一个结果集

// 或没有其它结果

ResultSet rs = stmt.getResultSet; if (rs != null) {

. . . // 使用元数据获得关于结果集列的信息

while (rs.next()) {

. . . // 处理结果

stmt.getMoreResults(); continue;

}

break; // 没有其它结果

57、jdbc 中怎么做批量处理的?

1、使用 Statement 批量处理
public static void batchStatement() {
    Connection conn = null; 
    Statement sta = null; 
    try { 
        conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        sta = conn.createStatement(); 
        for(int i=1; i<=10000; i++) {
	        String sql = "insert into	t_emp	(f_id,	f_no, f_name,	f_sex,	f_salary,
f_birthday, f_departId) values (t_emp_seq.nextval, '" + i +"', '葫芦娃" + i + "', '男', " + i + ",to_date('2016-09-05','yyyy-MM-dd'), 1)";//把 sql 语句加入批处理
            sta.addBatch(sql);
        }//统一执行批处理的 sql 语句
        int[] rows = sta.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally { 
            try {
                sta.close(); 
                conn.close();
           } catch (SQLException e) {
                e.printStackTrace();
           }
       }
}
2、使用 PreparedStatement 批量处理
public static void batchPreparedStatement() {
    Connection conn = null; 
    PreparedStatement ps = null; 
    try {
        conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 
        ps = conn.prepareStatement("insert into t_emp (f_id, f_no, f_name, f_sex,
f_salary, f_birthday, f_departId) values (t_emp_seq.nextval, ?,?,?,?,?,?)");
        for(int i=1; i<=10000; i++) { 
            ps.setString(1, String.valueOf(10 + i)); 
            ps.setString(2, "奥特曼" + i); 
            ps.setString(3, "女"); ps.setDouble(4, i); 
            ps.setTimestamp(5, new Timestamp(System.currentTimeMillis())); 
            ps.setInt(6, 2);
            //加入批处理
            ps.addBatch();
        }
        //统一执行批处理的 sql 语句
        int[] rows = ps.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally { 
            try {
                ps.close(); 
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

58、什么是 json

Json 是一种字符串数据格式,一般用于数据传输格式。

json 字符串中[]对应 JSONArray, {}对应 JSONObject

59、json 与 xml 的区别

(1).可读性方面。

JSON 和 XML 的数据可读性基本相同,JSON 和 XML 的可读性可谓不相上下,一边是建议的语法,一边是规范的标签形式,XML 可读性较好些。

(2).可扩展性方面。

XML 天生有很好的扩展性,JSON 当然也有,没有什么是 XML 能扩展,JSON 不能的。

(3).编码难度方面。

XML 有丰富的编码工具,比如 Dom4j、JDom 等,JSON 也有 json.org 提供的工具,但是 JSON 的编码明显比 XML 容易许多,即使不借助工具也能写出 JSON 的代码,可是要写好 XML 就不太容易了。

(4).解码难度方面。

XML 的解析得考虑子节点父节点,让人头昏眼花,而 JSON 的解析难度几乎为 0。这一点

XML 输的真是没话说。

(5).流行度方面。

XML 已经被业界广泛的使用,而 JSON 才刚刚开始,但是在 Ajax 这个特定的领域,未来的发展一定是 XML 让位于 JSON。到时 Ajax 应该变成 Ajaj(Asynchronous Javascript and JSON) 了。

(6).解析手段方面。

JSON 和 XML 同样拥有丰富的解析手段。

(7).数据体积方面。

JSON 相对于 XML 来讲,数据的体积小,传递的速度更快些。

(8).数据交互方面。

JSON 与 JavaScript 的交互更加方便,更容易解析处理,更好的数据交互。

(9).数据描述方面。

JSON 对数据的描述性比 XML 较差。

(10).传输速度方面。

JSON 的速度要远远快于 XML

60、XML 和 HTML 的区别?

  1. 设计上的区别:XML 用来存储数据,重点在于数据本身,HTML 用来定义数据,重在数据的显示模式。
  2. XML 可扩展性强,因为他本身就是可拓展性标记语言,课创建个性化的标记语言,提供更多数据操作。
  3. XML 语法比 HTML 严格。
  4. 起始标签和结束标签要匹配
  5. 嵌套标签不能相互嵌套
  6. 区分大小写
  7. XML 属性必须放在引号中,HTML 可有可无。
  8. XML 必须有相应值,但 HTML 可以有不带属性的属性名。

61、XML 文档定义有几种形式?它们之间有何本质区别?

  1. 两种形式 dtd schema。
  2. 本质区别:schema 本身是 xml 的,可以被 XML 解析器解析(这也是从 DTD 上发展 schema 的根本目的),

62、什么是 java 反射机制?

Java 中的反射首先是能够获取到 Java 中要反射类的字节码,获取字节码有三种方法,

Class.forName(className),类名.class,this.getClass()。然后将字节码中的方法,变量,构造函数等映射成相应的 Method、Filed、Constructor 等类,这些类提供了丰富的方法可以被我们所使用。是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。

63、知道类的加载过程吗

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类进行初始化。

  1. 加载:是将 class 文件读入内存,并为之创建一个 Class 对象。任何类被使用时系统都会建立一个Class 对象。
  2. 连接:
    1. 验证是否有正确的内部结构,并和其他类协调一致。
    2. 准备负责为类的静态成员分配内存,并设置默认初始化值。
    3. 解析将类的二进制数据中的符号引用替换为直接。

3. 初始化:在类首次被”主动使用”时执行初始化,为类(静态)变量赋予正确的初始值。

64、知道哪些类加载器

JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由

Java 实现且全部继承⾃ java.lang.ClassLoader :

  1. BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载%JAVA_HOME%/lib ⽬录下的jar包和类或者或被 -Xbootclasspath 参数指定的路径中的所有类。
  2. ExtensionClassLoader(扩展类加载器) :主要负责加载⽬录 %JRE_HOME%/lib/ext ⽬录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。
  3. AppClassLoader(应⽤程序类加载器) :⾯向我们⽤户的加载器,负责加载当前应⽤ classpath下的所有jar

65、什么是双亲委派模型

当需要加载一个类的时候,子类加载器并不会马上去加载,而是依次去请求父类加载器加载,一直往上请求到最高类加载器:启动类加载器。当启动类加载器加载不了的时候,依次往下让子类加载器进行加载。当达到最底下的时候,如果还是加载不到该类,就会出现ClassNotFound的情况。

好处:保证了程序的安全性。例子:比如我们重新写了一个String类,加载的时候并不会去加载到我们自己写的String类,因为当请求上到最高层的时候,启动类加载器发现自己能够加载Strin类,因此就不会加载到我们自己写的String类了。

66、hashmap 的底层实现

HashMap 实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

HashMap 底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个 HashMap 的时候,就会初始化一个数组。

HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。 HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 hash 算法来决定其在数组中的存储位置,在根据 equals 方法决定其在该数组位置上的链表中的存储位置;当需要取出一个 Entry 时,map.get(key)也会根据 hash 算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出该 Entry。

67、什么是 java 内存泄漏,怎么预防?

内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。

长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是 java 中内存泄露的发生场景。具体主要有如下几大类:

  1. 静态集合类引起内存泄露:
  2. 当集合里面的对象属性被修改后,再调用 remove()方法时不起作用。
  3. 各种连接未关闭,比如 IO 流

你可能感兴趣的:(java,jvm,开发语言)