Java基础面试题
1 集合有哪些?数据结构?初始长度?扩容机制?哪些是线程安全的?hashmap的底层原理?
1 基本概念
1 Java中所有的集合类都位于java.util包下,主要由两个接口派生出来,分别是Collection和Map
- 1 Collection包含了List和Set两大分支。
- 2 Map是一个映射接口。
- 3 Set 、Map、List可以看做集合的三大类。
2 遍历集合的工具有Iterator和Enumeration
3 操作数组集合的两个工具类: Arrays和Collection
4 Java中的集合主要分为四类:
- 1 List列表:有序的,可重复的;
- 2 Queue队列:有序,可重复的;
- 3 Set集合:不可重复;
- 4 Map映射:无序,键唯一,值不唯一
2 集合的种类
1 Collection
- 1 List:
- 1 ArrayList
- 2 LinkedList
- 3 vector
- Stack
- 4 ArrayQueue
- 2 Set:
- 1 HashSet
- LinkedHashSet
- 2 SortedSet
- TreeSet
- 3 EnumSet
3 Queue
- 1 Dqueue
- 1 ArrayDequeue
- 2 LinkedList
- 2 PriorityQueue
2 Map
- 1 HashMap
- LinkedHashMap
- 2 TreeMap
3 数据结构
List
- 1 ArrayList: 结构: 数组
- transient Object[] elementData; // 默认的长度是10
- 2 LindedList: 结构: 双向链表
private static class Node<E> { E item;// 实际存入的元素 Node<E> next;// 下一个节点的引用 Node<E> prev;// 前一个节点的引用 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
- 3 hashSet: 结构: map集合, 哈希表
- private transient HashMap<E, Object> map;
- 4 LinkedHashSet: 结构: map集合, 哈希表+双向链表
- 5 TreeSet: 红黑树
- 6 Map: 哈希表
- 7 hashMap: 哈希表
- 8 LinkedHashMap: 哈希表+双向链表
- 9 TreeMap: 红黑树
4 初始长度
1 arrayList初始长度10
2 Vector初始长度10
3 hashSet初始长度为16
4 hashmap初始长度为16
5 linkedList 是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好
5 扩容机制
1 arraylist: 扩容为原容量的0.5倍+1, 如10扩容后为16
加载因子为1, 即存储数据超过容量时扩容
2 vector: 扩容为原容量的1倍, 如10扩容后为20
加载因子为1
3 hashSet
- 1 加载因子为0.75, 即当元素个数超过长度的0.75倍后扩容
- 2 每次扩容为原容量的一倍
4 hashmap
- 1 加载因子0.75
- 2 扩容为原容量的一倍
6 哪些是线程安全的
1 Collection集合中提供了synchronizedxxx(xxx)方法把对应的xxx集合(collection,list, map, set等)集合转换为线程安全的集合
2 vector, vector, hashTable, properties, ConcurrentHashMap是线程安全的
- 1 vector和ArrayList类似, 但是比ArrayList多了个同步机制(线程安全)
- 2 hashTable中的每个方法都加了synchronized
- 3 ConcurrentHashMap:是一种高效但是线程安全的集合
- 其实现机制是使用了部分锁, 效率上比hashTable的方法上加synchronized锁的效率要高
- 4 Stack: 栈,也是线程安全的, 继承于Vector
3 ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的
7 hashmap的底层原理
1 哈希表: 数组+链表+红黑树
2 key的哈希值/数组长度=索引
- 1 索引相同的key产生哈希冲突, 其内容存储到该索引对应的链表中
- 2 如果在链表中值也相同的话, 则值覆盖
- 3 链表长度超过8, 则改为红黑树
8 HashMap底层数据结构
1 底层数据结构
- 1 JDK1.7及之前: 数组+链表
- 2 JDK1.8: 数组+链表+红黑树
2 HashMap添加元素分析
- 1 根据一个元素的哈希值和数组长度计算下标来准确定位该元素的位置
- 1 如为了使元素时分布均匀会使用取模运算: index=hashCode % arr.length
- 1 实际使用的不是取模运算, 而是与运算, 与运算(n-1) & hash取代取模运算hash%length, 两种方式记算出来的结果是一致的(n就是length), 即: (length-1)&hash = hash%length
- 2 计算机的运行效率: 加法(减法)>乘法>除法>取模, 取模运算效率最低,而与运算(位操作)的效率是远远高于取模运算的
- 2 index即为要插入的元素的数组索引位置
- 2 哈希冲突: 两个不同的元素计算后会得出相同的索引位置, 这时就需要链表和红黑树了
- 冲突的元素会在该索引处以链表(或红黑树)的形式保存
- 3 链表: 当链表的长度过长时, 查询效率较低, 就需要使用红黑树来存储
- 4 红黑树
- 1 红黑树是一棵接近于平衡的二叉树,其查询时间复杂度为O(logn), 远远比链表的查询效率高
- 2 但如果链表长度不到一定的阈值, 直接使用红黑树代替链表也是不行的
- 1 因为红黑树的自身维护的代价也是比较高的
- 2 每插入一个元素都可能打破红黑树的平衡性
- 3 这就需要每时每刻对红黑树再平衡(左旋, 右旋, 重新着色)
3 数组的长度必须是2的指数次幂
- 1 HashMap中数组的初始长度为16
- 空参的HashMap初始容量是16, 默认加载因子为0.75
- 2 由于一个元素存储时HashMap底层使用了与运算代替了取模运算
- 1 只有当数组的长度为2的指数次幂时, 两种方式计算的结果才一样, 否则计算结果不一样
- 2 但最重要的一点, 是要保证定位出来的值是在数组的长度之内的, 不能超出数组长度, 并且减少哈希碰撞, 让每个位都可能被取到
- 1 当数组长度为2的指数次幂时
- 1 (length-1) & hash 的结果可以取到所有的数组索引值
- 2 2的指数次幂-1 的二进制的结果无论是几位二进制, 所有的二进制位都是1
- 1 如 16-1 = 15, 二进制为 1111
- 2 1111 与 hash(任意值)的二进制 与运算后, 结果为 ----, "-"表示0或1都可以
- 1 这样结果就可以取到 0-15 之间的所有索引值
- 2 当数组长度不为2的指数次幂时
- (length-1) & hash 的结果不能取到所有的数组索引值
- 如: (7-1) & hash 的结果在索引范围 0-6之间,但是 7-1 = 6 的二进制(这里使用4位)为: 0110; 0110与hash(任意值)的二进制进行与运算, 所得结果必定为 0--0, 首尾二进制和末尾一定为0,这种情况下0-6索引之间的某些索引是无法取到的, 如 0001(1), 0011(3), 0101(5),即计算结果无法得出 1, 3, 5, 的索引值, 数组中这些索引值位置也无法存储元素
4 HashMap的加载因子(负载因子)要设置为0.75
- 1 加载因子: 即HashMap的数组空间使用了多少时对数组进行扩容
- 2 加载因子如果定的太大, 比如1, 即一直等数组填满才扩容,虽然达到了最大的数组空间利用率, 但会产生大量的哈希碰撞, 同时产生更多的链表
- 3 如果设置的过小, 比如0.5, 保证了数组空间很充足, 减少了哈希碰撞, 这种情况下查询效率很高, 但消耗了大量空间
5 链表长度大于等于8时转成了红黑树
- 1 链表长度大于等于8时转成红黑树正是遵循泊松分布
- 1 泊松分布: 适合描述单位时间内随机事件发生的概率
- 2 如果红黑树节点数少于6, 再次转换为链表
- 2 根据泊松分布计算得出的概率(HashMap源码中提供)
存储元素个数: 概率
0: 0.60653066
1: 0.30326533
2: 0.07581633
3: 0.01263606
4: 0.00157952
5: 0.00015795
6: 0.00001316
7: 0.00000094
8: 0.00000006
- 3 根据上述概率可以看到当链表中的元素个数为8时的概率已经很小了
6 计算机的运行效率: 加法(减法)>乘法>除法>取模
- 1 赋值运算(x=y): 0.8ms
- 2 加法运算(x+y): 41.45ms
- 3 减法运算: 42.95ms
- 可见, 在计算机内部实现中, 把减法变成加法的求补码过程是较快的
- 4 乘法运算: 58.25ms
- 5 除法运算: 1210.2ms
- 6 取模运算: 1178.15ms
- 7 位操作比 + , -, *, /快
9 hashTable是线程安全的
1 默认长度为11, 加载因子0.75, 每次扩容为2倍+1
2 数据结构也是哈希表: 数组+链表
10 ConcurrentHashMap
1 数据结构
- 1 jdk1.7其数据结构是: 一个Segment数组和多个HashEntry组成
- 2 jdk1.8其数据结构是: 哈希表, 即 Node数组+链表+红黑树
2 桶上链表长度达到 8 个或者以上,并且数组长度为 64 以下时只会触发扩容而不会将链表转为红黑树
3 加载因子0.75
2 线程的创建?开启?状态?sleep和wait的区别, 线程池?死锁?如何保证线程安全?
1 线程创建的方式
1 继承Thread类, 重写run()方法
2 实现Runnable接口, 重写run()方法
3 实现Callable接口, 重写call方法
4 使用线程池
2 启动线程, 对应上面四种方式
1 创建自定义类的对象, 使用start()方法
new 自定义类().start()
2 创建Thread类对象, 把Runnable接口的实现类作为参数传递过去
new Thread(new Runnable).start()
3 new Thread(new FutureTask<E>(new Callable())).start();
3 线程状态
1 NEW: 新建状态
2 Runnable: 可运行状态
3 Blocked: 锁阻塞状态
4 Waitting: 无限等待状态
5 Timed waiting: 计时等待状态
6 Terminated: 被终止状态
状态
创建, 就绪, 运行, 阻塞, 死亡
如何停止一个线程?
- 1 没有直接停止线程的方法, 只能等线程运行完毕
- 2 如果要手动停止, 你可以使用volatile布尔变量来退出run()方法的循环或者是取消任务来中断线程
4 sleep和wait的区别
1 sleep: 让当前线程睡眠, 在同步方法或代码块中不释放锁
该方法可以写在任何位置
2 wait: 让当前线程等待, 同时释放已经拿到的锁
该方法必须写在同步方法或同步块中
5 线程池
1 线程池的组成
- 1 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
- 2 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
- 3 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
- 4 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
2 一个服务器完成一项任务所需时间为: T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
3 线程池类ThreadPool
- 1 默认初始化5个工作线程
- 2 我们可以通过execute(List<Runnable> task)方法将自己的任务传递进去
- 1 或使用其他重载的方法
- execute(Runnable task)
- execute(Runnable[] task)
- 1 自己的任务实现Runnable接口, 重写其中的run()方法
- 2 但只是把自己的任务加入了线程池, 什么时候执行由线程池决定
4 线程池有几种
- 1 Executors.newFixedThreadPool, 创建固定线程数的线程池,使用的是LinkedBlockingQueue无界队列,线程池中实际线程数永远不会变化
- 2 Executors.newSingleThreadExecutor (单线程线程池),创建只有一个线程的线程池,使用的是LinkedBlockingQueue无界队列,线程池中实际线程数只有一个
- 3 Executors.newCachedThreadPool (弹性缓存线程池),创建可供缓存的线程池,该线程池中的线程空闲时间超过60s会自动销毁,使用的是SynchronousQueue特殊无界队列
- 4 Executors.newScheduledThreadPool (定时器线程池),创建可供调度使用的线程池(可延时启动,定时启动),使用的是DelayWorkQueue无界延时队列
- 5 Executors.newWorkStealingPool 拥有多个任务队列的线程池,jdk1.8提供的线程池,底层使用的是ForkJoinPool实现,创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu核数的线程来并行执行任务
- 6 ForkJoinPool
5 线程池参数
- corePoolSize,线程池中的常驻核心线程数
- int maximumPoolSize,线程池能够容纳同时执行的最大线程数,此值必须大于等于1
- long keepAliveTime,多余的空闲线程的存活时间。
- TimeUnit unit,keepAliveTime的单位
- BlockingQueue<Runnable> workQueue,任务队列,被提交但尚未被执行的任务
- ThreadFactory threadFactory,表示生成线程池中工作线程的线程工厂,用于创建线程一般默认即可
- RejectedExecutionHandler handler拒绝策略,表示当队列满了并且工作线程大于等于线程池最大线程数(maximumPoolSize)时如何处理
6 死锁
1 死锁介绍: 死锁就是线程1需要请求的资源被线程2持有, 而线程2想要获取的资源被线程1持有
- 1 两个线程都不释放自己的资源, 但又无法获取所需的资源而进入阻塞的一种状态
- 2 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者进程在运行过程中,请求和释放资源的顺序不当而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
2 死锁产生的条件
- 1 有多把锁
- 2 有多个线程
- 3 有同步代码块嵌套, 即一个线程完成一个任务可能需要拿到多把锁
3 死锁解决方法
- 1 杀死一个或多个进程(最简单的方法)
- 2 抢占: 即从一个线程中获取资源, 将其分配给其他线程, 但可能会导致一些问题
- 3 回滚: 系统可以定期记录每个进程的状态, 在产生死锁时, 将所有内容回滚到最后一个检查点
- 然后重新启动, 但以不同的方式分配资源, 以免发生死锁
- 4 按照顺序加锁
- 1 即如果有两把锁, 必须先获取第一把锁之后才能获取第二把锁
- 2 不能直接先获取第二把锁
- 5 不要用synchronized无限等待, 可以使用ReentranLock实现超时等待
- 即拿到第一把锁之后,在一定时间后拿不到第二把锁,就释放已经拿到的锁
4 活锁: 死锁的变体
- 例子: 两个人在走廊上面对面相遇, 每个人一边走一边让对方通过,但最终却在没有任何进展的情况下左右摇摆, 因为他们总是以相同的方式移动同时
5 只有一个锁, 如果这个锁是互斥锁时, 也可能会产生死锁
- 如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁;
- 然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,。因此就永远处于挂起等待状态了
7 如何保证线程安全?
1 线程安全在三个方面体现
- 1 原子性: 供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized)
- 2 可见性: 一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile)
- 3 有序性: 一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序
2 解决方法
- 1 使用原子类
- 1 在Java.util.concurrent.atomic包下定义了一些对"变量"操作的"原子类"
- 2 原子类又称为乐观锁, cas机制 compare and swap
- 2 使用 synchronized 关键字
- 3 使用 Lock 锁
- lock()获得锁
3 ==和equals的区别?
1 介绍: 值类型是存储在内存中的栈中的, 而引用类型的变量在栈中存储的只是一个地址值
而这个引用类型中的具体数据是存储在堆中的
2 ==操作比较的是两个变量的值是否相等
对于引用型变量表示的是两个变量在堆中存储的地址是否相同, 即栈中的内容是否相同
3 equals操作表示的两个变量是否是对同一个对象的引用,即堆中的内容是否相同
4 ==比较的是两个对象的地址,而equals比较的是两个对象的内容
4 对反射的理解? 获取class类的方式?如何用反射获取私有属性的Field对象?
1 反射的理解
1 该机制是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。
2 反射的定义: 计算机科学中的反射(reflection) 指计算机程序在运行时(runtime)
- 1 可以访问、检测和修改它本身状态或行为的一种能力。
- 2 通俗说,反射就是程序在运行的时候能够“观察”并且修改自己的行为,
- 3 是程序对自身的反思、自检、修改。英文 reflection 本身就有反思的意思。
- 4 而反思一词听起来让程序有了生命力,也说明了反射只有在程序运行时才能起作用,
- 5 因为程序代码只有在CPU上“跑”起来才有“生命力”。
3 正射: 在Java程序中,在没有用到反射相关的类的时候,我们都是在做正射
4 在写程序的时候, 我们在调用某个方法时, 会自己主动思考该类中是否有这个方法
而反射做的事就是将这种“思考”交给程序自身, 由程序在运行时确定类中是否有我们需要调用的方法, 进而去调用它
5 反射的作用,即在运行时访问、修改对象的状态和行为
2 获取class类的方式
1 调用运行时类本身的.class属性: Class<类名> c = 类名.class;
2 通过运行时类的对象获取: Class c = 对象.getClass();
3 通过Class的静态方法获取:体现反射的动态性: Class c = Class.forName(全限定类名);
4 通过类的加载器
ClassLoader classLoader = this.getClass().getClassLoader();// 获取类加载器
Class c = classLoader.loadClass(全限定类名);// 加载类
3 如何使用反射获取类的私有变量的Field对象: 使用暴力反射
1 先获取类的class对象: c
2 获取变量的的Field数组: Field[] fields = c.getDeclaredFields();
3 变量数组, 在调用Field类中的方法之前, 先使用暴力反射方法setAccessible, 开启私有方法的访问权限
- 1 field.setAccessible(true);值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查
- 2 然后在调用field中的其他方法
5 常用的设计模式有哪些? 在项目中哪里用到了? 单例中懒汉饿汉的优缺点?
1 常用的设计模式有哪些
1 模板模式
- jdbcTemplate
2 装饰设计模式
- Java IO的API是典型的应用。对扩展开放,对修改关闭
3 单例设计模式
- 1 在spring的IOC依赖注入的时候,默认的是采用单例模式
- 2 多线程中线程池的设计一般也采用单例设计模式,这是由于线程池要方便对池中的线程进行控制
4 多例设计模式
5 动态代理模式
- 1 spring中的AOP
- 2 基于接口的和基于子类(cglib)的动态代理
- 3 目标接口实现了接口, 则默认使用jdk动态代理, 即接口的动态代理, 但是可以强制使用cglib动态代理
- 如果目标对象没有实现接口, 只能用cglib动态代理
6 工厂设计模式
- BeanFactory, ApplicationContext
观察者模式
- 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
- spring中Observer模式常用的地方是listener的实现。如ApplicationListener。发布者-订阅者模式,接受通知
2 在项目中哪里用到了
3 单例中懒汉饿汉的优缺点
1 懒汉式
- 1 懒汉式是典型的时间换空间
- 1 每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间
- 2 当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间
- 2 懒汉式的线程安全, 需要自己在获取对象的方法上加上synchronized
2 饿汉式
- 1 饿汉式是典型的空间换时间
- 1 当类装载的时候就会创建类实例,不管你用不用,先创建出来, 但是浪费空间
- 2 每次调用的时候,就不需要再判断了,节省了运行时间
- 2 饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的
6 jdk1.8的新特性有哪些?
1 Lambda表达式
2 函数式接口
1 Function类型接口
2 Consumer系列
3 Predicate系列
4 Supplier系列
3 方法引用和构造器调用
4 Stream API: 并行流和串行流
5 接口中的默认方法和静态方法
1 可以通过parallel()与sequential()方法在并行流和串行流之间进行切换
2 jdk1.8并行流使用的是fork/join框架进行并行操作
- 1 Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
- 2 关键字:递归分合、分而治之。
- 3 采用 “工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线 程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中
6 新时间日期API
7 hashMap数据结构的优化, 添加了红黑树
8 Optional
Optional仅仅是一个容器,可以存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查
9 并行数组
最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序
7 session的实现原理? session的生命周期? session如何存储数据?
1 session的实现原理
1 客户对向服务器端发送请求后,Session 创建在服务器端,返回Sessionid给客户端浏览器保存在本地,
2 当下次发送请求的时候,在请求头中传递sessionId获取对应的从服务器上获取对应的Sesison
2 session的生命周期
1 一次会话范围, 即打开浏览器到关闭浏览器
2 注意:
- 1 关闭浏览器后session并不是销毁了, session是保存在服务器端的
- 1 关闭浏览器只是销毁了客户端保存的sessionid
- 2 只要拿到这个id, 那么还可以再次获取到对应的session信息
2 一个浏览器只有一个session
3 session如何存储数据
在服务器端有一个session池,用来存储每个用户提交session中的数据
8 类加载机制? 代码块的执行顺序?
1 类加载机制
1 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制
2 类的整个生命周期包括: 加载(Loading)、验证(Verification)、准(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段
3 其中验证、准备、解析3个部分统称为连接(Linking)
4 加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的, 类的加载过程必须按照这种顺序按部就班地开始, 而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)
2 代码块的执行顺序
1 静态代码块: 在当前类第一次被使用时, 会执行该代码块
2 构造代码块: 在执行类的构造方法之前执行
3 局部代码块: 在方法内部, 执行方法时执行
9 cookie和session的区别?
1 cookie: 客户端技术, 数据保存在客户端, 数据不安全, 存储数据大小4kb
2 session: 服务器端技术, 数据保存在服务器, 数据相对安全, 存储数据大小根据服务器容量确定
10 java中字符串方法有哪些? string stringbuild stringbuffer的区别?
1 java中字符串方法
1 获取字符串长度: int length()
2 比较字符串内容
- 1 boolean equals(Object anObject)
- 2 boolean equalsIgnoreCase(String anotherString)
3 根据索引获取字符:char charAt(int index)
4 截取字符串中的一部分
- 1 String substring(int beginIndex)
- 2 String substring(int beginIndex, int endIndex)
5 大小写转换
- 1 toUpperCase()
- 2 toLowerCase()
6 替换字符串中的内容
- 1 public String replace(String s, String replacement)
- 2 public String replaceAll(String regex,String replacement)
7 拼接字符串:String concat(String str)
8 判断字符串是否为空:boolean isEmpty()
9 判断字符串中是否包含给定的字符串:boolean contains(CharSequence s)
10 判断字符串是否以给定的字符串结尾:boolean endsWith(String suffix)
11 判断字符串是否以给定的字符串开头:boolean startsWith(String prefix)
12 切分字符串:String[] split(String regex)
13 移除首尾空格:trim()
14 返回字符串对应的字节数组
- 1 byte[] getBytes()
- 2 byte[] getBytes(Charset charset)
15 把字符串转换为字符数组:char[] toCharArray();
16 返回指定字符串在此字符串中第一次出现的索引位置: public int indexOf(String str)
2 string stringbuild stringbuffer的区别
1 String的值是不可变的,这就导致每次对String的操作都会生成新的String对象
- 1 这样不仅效率低下,而且大量浪费有限的内存空间
- 2 例: string s = "123", s = s + "234"
- 1 s的变化是指针的变化, 首先指针指向"123", 系统会给"123"开辟一个空间
- 2 s = s + "234"; "234"开辟一个空间, "123234"有开辟一个空间
- 3 最终s指向"123234"的空间地址
- 4 这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费
2 当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类
- 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
3 StringBuilder 类在 Java 5 中被提出
4 string stringbuild stringbuffer 的区别
- 1 区别一:
- 1 string: 不可变字符序列
- 2 stringbuild: 可变字符序列, 线程不安全, 执行速度快
- 3 stringbuffer: 可变字符序列, 线程安全, 执行速度慢
- 2 字符修改上的区别
- 3 初始化上的区别
- String可以赋值为null, 另外两个不可以, 会报错
5 使用方式
- 1 如果要操作少量的数据用 String
- 2 多线程操作字符串缓冲区下操作大量数据 StringBuffer
- 3 单线程操作字符串缓冲区下操作大量数据 StringBuilder
11 jvm调优和垃圾回收机制?
jvm调优知识简介
堆与栈的概念
- 堆和栈是程序运行的关键:栈是运行时的单位,而堆是存储的单位。
- 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
- 在Java中一个线程就会相应有一个线程栈与之对应,而堆则是所有线程共享的
- 栈存储的信息都是跟当前线程(或程序)相关信息的,包括:局部变量、程序运行状态、方法返回值等等。
- 堆只负责存储对象信息。
简单来说:堆中存的是对象,栈中存的是基本数据类型和堆中对象的引用。
jvm调优: 性能调优
1 性能调优的目的
- 1 减少垃圾回收执行的次数, 以及执行的时间
- 2 使用较小的内存占用来获得较高的吞吐量或者较低的延迟
2 性能调优的手段
- 1.使用JDK提供的内存查看工具,如JConsole和Java VisualVM
- 2.控制堆内存各个部分所占的比例
- 3.采用合适的垃圾收集器
3 假设线程池、连接池、程序代码等都已经做过优化,效果(系统吞吐量、响应性能)仍然不理想,我们就可以考虑JVM调优了
4 什么情况下需要做jvm调优
- 1 heap 内存(老年代)持续上涨达到设置的最大内存值;
- 2 Full GC 次数频繁;
- 3 GC 停顿时间过长(超过1秒);
- 4 应用出现OutOfMemory 等内存异常;
- 5 应用中有使用本地缓存且占用大量内存空间;
- 6 系统吞吐量与响应性能不高或下降
5 JVM调优原则
- 1 多数的Java应用不需要在服务器上进行JVM优化;
- 2 多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;
- 3 在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);
- 4 减少创建对象的数量;
- 5 减少使用全局变量和大对象;
- 6 JVM优化是到最后不得已才采用的手段;
- 7 在实际使用中,分析GC情况优化代码比优化JVM参数更好;
6 JVM调优目标
- 1 GC低停顿
- 2 GC低频率
- 3 低内存占用
- 4 高吞吐量
7 JVM调优量化目标(示例)
- 1 Heap 内存使用率 <= 70%;
- 2 Old generation内存使用率<= 70%;
- 3 avgpause <= 1秒;
- 4 Full gc 次数0 或 avg pause interval >= 24小时 ;
- 5 注意:不同应用,其JVM调优量化目标是不一样的。
8 JVM调优经验
- 1. JVM调优经验总结
JVM调优的一般步骤为:
第1步:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
第2步:确定JVM调优量化目标
第3步:确定JVM调优参数(根据历史JVM参数来调整);
第4步:调优一台服务器,对比观察调优前后的差异;
第5步:不断的分析和调整,直到找到合适的JVM参数配置;
第6步:找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。
- 2. JVM调优重要参数解析
- 注意:不同应用,其JVM最佳稳定参数配置是不一样的。
配置: -server
重要参数(可调优)解析:
-Xms12g:初始化堆内存大小为12GB。
-Xmx12g:堆内存最大值为12GB 。
-Xmn2400m:新生代大小为2400MB,包括 Eden区与2个Survivor区。
-XX:SurvivorRatio=1:Eden区与一个Survivor区比值为1:1。
-XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory 异常可以上调这个值。
-XX:+DisableExplicitGC:禁止运行期显式地调用 System.gc() 来触发fulll GC。
- 注意: Java RMI的定时GC触发机制可通过配置-Dsun.rmi.dgc.server.gcInterval=86400来控制触发的时间。
-XX:CMSInitiatingOccupancyFraction=60:老年代内存回收阈值,默认值为68。
-XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。
-XX:ParallelGCThreads=8:新生代并行收集器的线程数。
-XX:MaxTenuringThreshold=10:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
-XX:CMSFullGCsBeforeCompaction=4:指定进行多少次fullGC之后,进行tenured区 内存空间压缩。
-XX:CMSMaxAbortablePrecleanTime=500:当abortable-preclean预清理阶段执行达到这个时间时就会结束。
JVM调优工具
1 console: jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很详细的跟踪。详细说明参考这里
2 JProfiler: 商业软件,需要付费。功能强大。详细说明参考这里
3 VisualVM: JDK自带,功能强大,与JProfiler类似。推荐
jvm调优参数
1 -Xmx4g:堆内存最大值为4GB
2 -Xms4g:初始化堆内存大小为4GB
3 -Xmn1200m:设置年轻代大小为1200MB
4 -Xss512k:设置每个线程的堆栈大小
5 -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代),设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
6 -XX:SurvivorRatio=8:设置年轻代中Eden区与Survivor区的大小比值。设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
7 -XX:PermSize=100m:初始化永久代大小为100MB。
8 -XX:MaxPermSize=256m:设置持久代大小为256MB。
9 -XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代
- 1 对于年老代比较多的应用,可以提高效率
- 2 如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论
垃圾回收机制
0 JVM内存由几个部分组成:堆、方法区、栈、程序计数器、本地方法栈
- 1 JVM垃圾回收仅仅针对公共内存区域即:堆和方法区进行
1 分代管理
- 1 将堆和方法区按照对象不同年龄进行分代
- 堆中会频繁创建对象,基于一种分代的思想,按照对象存活时间将堆划分为新生代和旧生代两部分
- 1 我们不能一次垃圾回收新生代存活的对象就放入旧生代,而是要经过几次GC后还存活的对象,我们才放入旧生代
- 2 所以我们又把新生代再次划分为Eden区和两个Survivor区
- 3 让对象创建在Eden区
- 4 然后在两个Survivor之间反复复制,最后仍然存活的对象才复制到旧生代中。
- 2 方法区存放的是常量、加载的字节码文件信息等
- 信息相对稳定。因为不会频繁创建对象,所以不需要分代,直接GC即可。
2 新生代
- 1.所有新对象创建发生在Eden区,Eden区满后触发新生代上的minor GC,将Eden区和非空闲Survivor区存活对象复制到另一个空闲的Survivor区中。
- 2.永远保证一个Survivor是空的,新生代minor GC就是在两个Survivor区之间相互复制存活对象,直到Survivor区满为止。
- 经过大概15次后, 还存活的对象会gc到老年代
3 旧生代(老年代)
- 1 Eden区满后触发minor GC将存活对象复制到Survivor区,Survivor区满后触发minor GC将存活对象复制到旧生代。
- 2 经过新生代的两个Survivor之间多次复制,仍然存活下来的对象就是年龄相对比较老的,就可以放入到旧生代了,随着时间推移,如果旧生代也满了,将触发Full GC,针对整个堆(包括新生代、旧生代和持久代)进行垃圾回收。
4 持久代(1.5之前)(之后就改为元空间了)
持久代如果满,将触发Full GC
5如果新生代太小,会导致频繁GC,而且大对象对直接进入旧生代引发full gc
如果新生代太大,会诱发旧生代full gc,而且新生代的gc耗时会延长
建议新生代占整个堆1/3合适2 垃圾回收
- 要执行gc关键在于两点,一是检测出垃圾对象,二是释放垃圾对象所占用的空间。
- 1 检测垃圾对象
- 检测出垃圾对象一般有两种算法:引用计数法, “可达性分析”算法
- 1 引用计数法因为无法检测对象之间相互循环引用的问题,基本没有被采用
- 现在主流的语言的垃圾收集中检测垃圾对象主要还是“可达性分析”方法
- 2 “可达性分析”算法描述?
- 通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,
- 1 搜索所走过的路径称为引用链(Reference Chain),
- 2 当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。
- 所以JVM判断对象需要存活的原则是:能够被一个根对象到达的对象。
- 什么是能够到达呢: 就是对象A中引用了对象B,那么就称A到B可达。
- GCRoot对象集合?
- a.java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
- b.方法区中的类静态属性引用的对象。
- c.方法区中的常量引用的对象。
- d.本地方法栈中JNI本地方法的引用对象。
- 2 释放空间
- 垃圾回收算法: 在检测出垃圾对象之后,需要按照特定的垃圾回收算法进行内存回收
- 复制(Copying)
- 标记-清除(Mark-Sweep)
- 标记-整理(Mark-Compact)
- 分代(Generational Collection),借助前面三种算法实现
12 java中锁的种类和基本原理?
1 synchronized同步锁(对象锁)
1 同一时刻, 一个同步锁只能被一个线程访问
2 以对象为依据,通过synchronized关键字来进行同步,实现对竞争资源的互斥访问
3 哪个线程先执行带synchronized关键字的方法或synchronized代码块,哪个线程就有该方法或该代码块所持有的锁,其他线程只能呈现等待状态,
- 前提是多个线程访问同一个对象
4 synchronized的用途
- 1 锁方法
- 1 synchronized修饰普通方法 :在修饰方法的时候默认是当前对象作为锁的对象
- 2 Java中每个对象都有一个锁或者称为监视器,当访问某个对象的synchronized方法时,表示将该对象上锁,而不仅仅是为该方法上锁。
- 3 这样如果一个对象的synchronized方法被某个线程执行时,其他线程无法访问该对象的任何synchronized方法(但是可以调用其他非synchronized的方法)。直至该synchronized方法执行完
- 1 但是如果在synchronized方法中调用的wait方法, 则其他线程能方位该对象的其他synchronized方法
- 原因为: 执行wait后会线程会释放自己已经获得的锁
- 2 但如果使用sleep()方法是, 不能访问其他的synchronized方法, sleep不释放锁
- 2 静态的synchronized方法调用情况
- 1 使用的锁是synchronized方法所在对象对应的Class对象
- 2 当调用一个对象的静态synchronized方法时,它锁定的并不是synchronized方法所在的对象,而是synchronized方法所在对象对应的Class对象。这样,其他线程就不能调用该类的其他静态synchronized方法了,但是可以调用非静态的synchronized方法
- 3 锁代码块
- 1 使用synchronized创建同步代码块: 在修饰代码块的时候需要一个reference对象作为锁的对象.
- 2 synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的, 同样的方法也只是普通的非同步方法
- 4 修饰类时候: 在修饰类时候默认是当前类的Class对象作为锁的对象
- 即在使用synchronized修饰代码块时, 用"类名.class"作为锁对象
5 同步锁又成为悲观锁
6 synchronized关键字不能继承, 即子类中重写的父类的带有synchronized方法后, 子类中的方法不是同步方法, 而是一个普通方法
- 为什么不能被继承?
- 虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分
7 注意:
- 1 在定义接口方法时不能使用synchronized关键字
- 2 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步
- 3 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制
8 总结
- 1 作用于方法时,锁住的是对象的实例(this)
- 2 当作用于静态方法时,锁住的是Class实例
- 3 synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块
- 4 synchronized 作用于一个class实例时,锁住的是所有以该class为锁的代码块
2 独占锁(可重入的互斥锁)
1 互斥,即在同一时间点,只能被一个线程持有
2 可重入,即可以被单个线程多次获取
3 根据锁的获取机制,它分为“公平锁”和“非公平锁”
- Java中通过ReentrantLock实现独占锁,默认为非公平锁
3 公平锁
1 是按照通过CLH等待线程按照先来先得的规则, 线程依次排队, 公平的获取锁, 是独占锁的一种
- 1 加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
2 java中, ReetrantLock中有一个Sync类型的成员变量sync
- 1 sync它的实例为FairSync类型的时候,ReetrantLock为公平锁
- 2 设置sync为FairSync类型,只需——Lock lock = new ReetrantLock(true)
4 非公平锁
1 是当线程要获取锁时,它会无视CLH等待队列而直接获取锁
- 加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
2 ReetrantLock默认为非公平锁,或Lock lock = new ReetrantLock(false)。
5 共享锁
1 能被多个线程同时获取、共享的锁。即多个线程都可以获取该锁,对该锁对象进行处理
2 典型的就是读锁——ReentrantReadWriteLock.ReadLock
- 即多个线程都可以读它,而且不影响其他线程对它的读,但是大家都不能修改它
3 CyclicBarrier, CountDownLatch和Semaphore也都是共享锁
6 读写锁
1 维护了一对相关的锁
- 1 “读取锁”用于只读操作,它是“共享锁”,能同时被多个线程获取
- 2 “写入锁”用于写入操作,它是“独占锁”,只能被一个线程锁获取
2 读写锁为ReadWriteLock 接口定义,其实现类是ReentrantReadWriteLock,包括内部类ReadLock和WriteLock
- 1 方法readLock()、writeLock()分别返回读操作的锁和写操作的锁。
7 全局锁
1 实现全局锁有两种方式
- 1 将synchronized关键字用在static方法上
- 1 synchronized加到static静态方法上是对Class类上锁
- 2 而synchronized加到非static方法上是给对对象上锁, Class锁可以对类的所有对象实例起作用
2 用synchronized对类的Class对象进行上锁
- synchronized(class)代码块的作用与synchronized static方法的作用一样
13 collection和collections的区别
1 Collection 是一个 集合框架的父接口
1 它提供了对集合对象进行基本操作的通用接口方法
2 Collection接口在 Java 类库中有很多具体的实现
3 Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式
2 Collections是一个操作集合的工具类, 服务于Java的Collection框架
1 它包含有各种有关集合操作的, 静态多态方法
2 提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作
14 java如何跳出循环?
1 continue, break, return
1 continue:中止本次循环,继续下次循环。continue以后的循环体中的语句不会继续执行,下次循环继续执行,循环体外面的会执行
2 break:直接结束一个循环,跳出循环体。break以后的循环体中的语句不会继续执行,循环体外面的会执行
- 注意: 只跳出一层循环
3 return:return的功能是结束一个方法。 一旦在循环体内执行return,将会结束该方法,循环自然也随之结
- 可以跳出多层循环
2 循环标记使用(标签变量)
1 场景: 多层循环,如果在里层循环,达到某个条件后,结束指定循环 2 测试代码 public static void main(String[] args) { ee:for (int i = 0; i < 3; i++) { for (int j = 0; j <3 ; j++) { System.out.println("j==========" + j); break ee;//跳出指定标记的循环 } System.out.println("i=" + i); } System.out.println("跳出循环"); } 3 测试代码执行结果: j==========0
3 利用try…catch…
1 给整个for循环加上try...catch...
2 如果在循环中需要退出, 就抛出一个异常即可: throw new Exception("跳出循环");
4 利用标识变量
即定义一个全局变量, 在for循环中进行判断,
15 排序有哪些? 原理是什么?
1 冒泡排序
1 原理: 相邻元素比较, 后面的比前面的小, 就交换位置, 把最大的元素冒泡出来(即排到最后)
2 时间复杂度:n的平方。原地修改数组
3 但是性能会比较差
2 快速排序
3 选择排序
1 原理:
- 1 先选择出最小的元素, 记录其索引位置, 然后和第一个索引位置的元素交换位置
- 2 然后选择出第2小的元素, 和第二个索引位置的元素交换
- 3 ...
2 时间复杂度:n的平方。原地修改数组
4 插入排序
1 原理
- 1 把所有元素分为两组,已排序的和未排序的
- 2 找到未排序组中的第一个元素,向已经排序的组中进行插入, 与已排序的数据进行比较, 将元素插入到合适的位置
- 3 倒序遍历已经排序的元素,
2 时间复杂度:n的平方。原地修改数组
3 简单直观且稳定的排序算法
5 希尔排序(插入排序的改良版)
6 归并排序
16 什么是堆栈?什么是内存溢出?有垃圾回收机制为什么还会出现内存溢出的情况?
1 堆与栈是两个相对的概念:堆指先进先出(first in first out),栈指先进后出(first in last out)
2 这里的堆栈就是栈
1 堆是一个树状数据结构
2 而栈是一个链表, 栈是先进后出的, 有入栈和出栈的操作, 只在栈的一端进行操作
3 在java中堆一般用来存储对象和数组,栈一般用来存储方法和基本类型
- 栈的存取速度比堆快。栈中的数据是可以共享的
3 内存溢出
1 应用系统中存在无法回收的内存
2 或使用的内存过多
3 最终使得程序运行要用到的内存大于虚拟机能提供的最大内存
4 有垃圾回收机制为什么还会出现内存溢出的情况
1 垃圾回收机制只能回收没有引用的对象,也就是说只能回收没有“指针”的对象,对于非引用类对象,垃圾回收机制就不能起作用
2 比如说,如果打开过多的数据库连接,那么这些不能被垃圾回收机制所处理。由于一般情况下很少有人打开过多的数据库连接,所以很少有人注意到这一点。
3 还有一种可能就是死循环也会出现内存泄漏,但是不是所有的死循环都会出现内存泄漏现象。对ArrayList操作的时候才有可能出现
17 内存模型的理解?
1 java内存模型(Java Memory Model,JMM)
1 就是一种符合内存模型规范的,
2 屏蔽了各种硬件和操作系统访问差异的
3 保证了java程序在各种平台下对内存的访问都能保证效果一致的机制和规范
4 作用: 为了保证并发编程中可以满足原子性、可见性以及有序性
2 Java内存模型所有的变量都存储在主内存,
1 每条线程有自己的工作内存,线程的工作内存保存了该线程中用到的变量的主内存副本拷贝
2 线程对变量所有的操作都必须在工作内存中进行,不能直接读写主内存
3 不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步
3 JMM是一种规范
1 目的是解决由于多线程通过共享内存进行通信时,存在
- 1 本地内存数据不一致
- 2 编译器会对代码指令重排序
- 3 处理器会对代码乱序执行等带来的问题
2 目的就是保证并发编程场景中的原子性、可见性和有序性。
4 Java内存模型的实现
java提供了一系列和并发处理相关的关键字,比如volatile、synchronized、final、concurrent包等
- synchronized保证了原子性可见性和有序性
- volatile保证了可见性和有序性,但是不能保证原子性。volatile禁止指令重排序
- final保证了可见性
5 JVM内存空间分为五部分,分别是:方法区、堆、Java虚拟机栈、本地方法栈、程序计数器
1 方法区主要用来存放类信息、类的静态变量、常量、运行时常量池等,方法区的大小是可以动态扩展
2 堆主要存放的是数组、类的实例对象、字符串常量池等
3 Java虚拟机栈是描述JAVA方法运行过程的内存模型,Java虚拟机栈会为每一个即将执行的方法创建一个叫做“栈帧”的区域,该区域用来存储该方法运行时需要的一些信息
- Java虚拟机栈是运行Java方法的区域
4 本地方法栈是运行本地方法的内存模型
5 程序计数器是一个比较小的内存空间,用来记录当前线程正在执行的那一条字节码指令的地址
- 1 如果当前线程正在执行的是本地方法,那么此时程序计数器为空
2 程序计数器有两个作用:
- 1、字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制,比如我们常见的顺序、循环、选择、异常处理等
- 2、在多线程的情况下,程序计数器用来记录当前线程执行的位置,当线程切换回来的时候仍然可以知道该线程上次执行到了哪里
18 泛型的理解?
1 泛型是JDK5中引入的特性,它提供了编译时类型安全监测机制,该机制允许在编译时监测到非法的类型
2 它的本质是参数化类型,即所操作的数据类型被指定为一个参数
1 参数化类型:就是将原来的具体的类型参数化,然后在使用/调用时传入具体的类型
2 例:List<E>,ArrayList<E>: 调用时:List<String> list = new ArrayList<String>();
3 这种参数类型可以用在类、方法、接口中,分别被称为泛型类、泛型方法、泛型接口
3 当没有指定泛型时,默认为Object类型
4 泛型的好处:
1.类型参数化,通用、可以像方法一样的参数一样传递,非常实用。
2.安全、编译时检查类型是否正确,降低类型强转报错率。
3.提高代码重用率。
5 类型擦除
1 泛型信息只存在于代码编译阶段
2 在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除
6 泛型转译
19 java的基本类型是什么, int占几个字节?byte占几个字节?
1 byte(1), short(2), int(4), long(8), float(4), double(8), boolean(1), char(2)
2 char
- char在Java中占用2字节, Java编译器默认使用Unicode编码,因此2字节可以表示所有字符
- 如果使用UTF-8编码, 则char存储字符占用的字节数是不定的, 1到3个字节
20 常见的异常有哪些? 异常处理的方式有哪些?
1 常见的异常有哪些
(1)NullPointerException 当应用程序试图访问空对象时,则抛出该异常。
(2)SQLException 提供关于数据库访问错误或其他错误信息的异常。
(3)IndexOutOfBoundsException指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
(4)NumberFormatException当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
(5)FileNotFoundException当试图打开指定路径名表示的文件失败时,抛出此异常。
(6)IOException当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
(7)ClassCastException当试图将对象强制转换为不是实例的子类时,抛出该异常。
(8)ArrayStoreException试图将错误类型的对象存储到一个对象数组时抛出的异常。
(9)IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
(10)ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
(11)NegativeArraySizeException如果应用程序试图创建大小为负的数组,则抛出该异常。
(12)NoSuchMethodException无法找到某一特定方法时,抛出该异常。
(13)SecurityException由安全管理器抛出的异常,指示存在安全侵犯。
(14)UnsupportedOperationException当不支持请求的操作时,抛出该异常。
(15)RuntimeException是那些可能在Java虚拟机正常运行期间抛出的异常的超类
2 异常处理的方式有哪些
方案一(处理异常):使用try...catch...finally语句
方案二(抛出异常):throws 异常类名;
方案三(产生异常):throw 异常对象;
21 枚举的了解?
1.所有地方都能查到的解释:(证明很基本,也很重要)
a.枚举类是一种特殊的类,它和普通的类一样,有自己的变量、方法和构造器。
- 它的构造器只能使用private访问修饰符,所以无法从外部调用构造器,构造器只能在构造枚举值时被调用
b.一个java源文件中只能有一个public类型的枚举类,而且该原文件的名字也必须和该枚举类的名字一致。
- 也就是在一个文件中只能有一个public修饰的枚举类。这里不包括内部类哈,指的是公共访问的.java文件的入口。
c.枚举类和class,interface地位是等同的,枚举也能实现接口。
d.枚举类的对象是有限且固定的,常用于状态、类型
e.枚举类默认集成了java.lang.Enum 类,并实现了java.lang.Seriablizable 和 java.lang.Comparable 两个接口
f.所有的枚举值都默认是public static final的,不用重复声明,而且枚举值应该显式的在枚举类第一行列出,否则无法产生实例
- 非抽象的枚举类不能再派生子类。
g.枚举值列举的时候也可以实现该枚举类实现的接口方法。
2.枚举值常用的方法:
a.String toString() 默认返回的枚举的名称,可自定义进行重写
b.int ordinal() 返回枚举对象的索引值,根据枚举值声明的顺序而定,从0开始;
c.String name() 返回枚举值
d.static values() 包含所有的枚举实例的数组,可用来遍历
e.int compareTo(E o) 用于与制定枚举对象比较顺序,只能同类型比较如果该对象位于指定对象之后则返回正整数反之返回负整数,否则返回零,整数值为二者顺序之差。
f.boolean equals() 比较两个枚举实例的引用。
22 final, finally, finalize关键字的区别?volaile关键字的了解?
1 Final是一个修饰符
2 Finally, 是try...catch...finally
3 finalize(), object类中的一个方法, 是GC (garbage collector垃圾回收)运行机制的一部分
当gc判断一个对象没有其他的引用(不存在该对象的引用), 调用该方法回收对象的资源
4 任何被volatile修饰过的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于volaile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是有序的
1 volatile解决共享变量的可见性,有序性(volatile修改的变量禁止代码重排), 不能解决原子性
2 解决可见性, 每个线程都有自己的工作空间, java内存模型把所有的变量存储在主内存
1 单个线程使用时, 会将变量复制一份到自己的工作空间, 执行完成后再同步到主内存
2 而volatile关键字修饰变量时, 该变量只存在于主内存, 单个线程使用时不会复制变量副本, 而是直接操作主内存的变量
23 在一个list中存放的String类型的字符串, 如何实现把其中所有带"王"的字符串从list中去除?
1 遍历list集合判断后赋给另一个list集合
2 用赋给set集合再返回给list集合
3 解决方法
1 方法一: 普通for倒着遍历list集合, 并删除重复数据
2 方法二: 普通for正序遍历list集合, 每删除一个元素后, 加入循环变量i的值-1操作
3 方法三: 迭代器iterator遍历
Iterator it = list.iterator(); while(it.hasNext()){ String x = it.next(); if(x.equals(“del”)){ it.remove();// 注意这里使用的是迭代器的remove()方法, 不能使用list的remove() } }
4 注意: 使用增强for遍历集合并删除数据, 会出现 ConcurrentModificationException //这是一个并发修改异常报错
原因: 调用list.remove()方法导致modCount和expectedModCount的值不一致而报异常
24 String a="123", String b="123", a+=b; 共产生了几个对象
1 共产生了2个对象: "123", "123123"
2 这两个对象都是常量池中的对象, 而不是new出来的对象
25 如何序列化和反序列化?序列化的目的?
0 序列化需要实现Serializable接口
1 如何序列化和反序列化
1 ObjectOutputStream代表对象输出流:
它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
2 ObjectInputStream代表对象输入流:
它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
2 序列化的目的
1 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
2 当你想用套接字在网络上传送对象的时候;
3 当你想通过RMI传输对象的时候
3 序列化的意义
1 将对象或者异常等写入文件,通过文件传输信息
2 将对象或者异常通过网络进行传输
3 把内存中的对象状态保存到一个文件中或者数据库中时
private static class Node<E> {E item;// 实际存入的元素Node<E> next;// 下一个节点的引用Node<E> prev;// 前一个节点的引用Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}}