java面试整理(二)
JavaSE 部分
100、Set 集合和 List 集合之间如何进行相互转换?
(1)集合对象的构造方法中是可以传入其它类型的集合的
// set->list
Set set1 = new HashSet();
List list1 = new ArrayList(set1);
// list->set
List list2 = new ArrayList();
Set set2 = new HashSet(list2);
(2)addAll()
(3)循环add
101、集合如何进行静态初始化?
除了我们通常使用的list.add() set.add() map.put()等方法为集合添加元素,也可以在初始化 块中添加元素。
List list = new ArrayList(){{ add(“a”);
}};
Map<String,String> map=new HashMap<String, String>(){{ put(“name”,“Tom”);
}};
102、如何保证 Set 集合中存入的顺序和取出时的顺序一
致?Map 呢?
使用 LinkedHashSet 可以使 Set 集合存入和取出时的顺序一致 使用 LinkedHashMap 可以使 Map 集合存入和取出时的顺序一致
103、如何将一个 List 中的所有元素打乱排放?
List list = new ArrayList() {
{
for (int i = 1; i <= 10; i++) {
this.add(i);
}
}
};
// 1、调用 Collections.shufftle 方法 Collections.shuffle(list); System.out.println(list);
// 2、将集合存入 HashSet 集合中,这里也有可能和之前顺序是一样的
// 和 hash 算法确定的元素位置有关
104、数组和集合之间如何进行相互转换?
// 集合->数组
Integer[] arr = list.toArray(new Integer[list.size()]);
// 数组->集合
List list2 = Arrays.asList(arr);
// 注意:数组转换后得到的集合是不能新增元素和删除元素的
// System.out.println(list2.add(2));
// System.out.println(list2.remove(0));
// 但是可以跟新某个索引位置上的元素
list2.set(0, 2);
105、Hashtable 的原理,它和 HashMap 的区别?
Hashtable 原理:Hashtable 是基于哈希表的实现。通过使用 put(Object key,Object value) 方法把两个对象进行关联,需要时用 get(Object key)取得与 key 关联的值对象。还可以查询 某个对象的索引值等等。这里的 get 方法查找一个对象时与 Vector 中的 get 方法在内部实现 时有很大不同,在 Hashtable 中查找一个键对象要比在一个 Vector 中快的多,那是因为
Hashtable 使用了一种哈希表的技术,在 Java 每个对象默认都有一个通过 Object 的 hashCode
方法获得的哈希码。Hashtable 就是利用这个哈希码快速查找到键对象的。 两者都实现了 Map 接口,是将唯一键映射到特定的值上。区别在于
(1) HashMap 没有排序,允许 null 键和 null 值,二 Hashtable 不允许
(2) HashMap 将 Hashtble 中的 conains 方法替换为 containsKey 方法和 containsValue
方法
(3) Hashtable 继承自 Directory 类,HashMap 是 Java1.2 引进的 Map 接口的实现。
(4) Hashtable 中的方法是同步的,HashMap 则不是,但是由于两者采用的 hash 和
rehash 算法大致一样,所以性能差异不大。
常用的 Properties 配置文件在 Java 中使用 Properties 对象来表示,由于 Properties 继承 了 Hashtable,所以其形式也是 k-v 对的形式。
106、ConcurrentHashMap 有什么特点?
Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好, 它里边的键值也不能为null,现在HashTable已经几乎不用,使用ConcurrentHashMap作为替 代方案,并且相比HashMap而言它是同步的。
107、HashMap 的设计原理?是如何解决什么冲突的?
(1)HashMap 是基于哈希表的 Map 接口的非同步实现。在 Java 编程语言中,最基本的结 构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个 基本结构来构造的,HashMap 也不例外。HashMap 实际上是一个“链表散列”的数据结构, 即数组和链表的结合体。HashMap 底层就是一个数组结构,数组中的每一项又是一个链表。 当新建一个 HashMap 的时候, 就会初始化一个数组。 Entry 就是数组中的元素, 每个 Map.Entry 其实就是一个 key-value 对,它持有一个指向下一个元素的引用,这就构成了链 表。
(2)HashMap 的存储:当我们往 HashMap 中 put 元素的时候,先根据 key 的 hashCode 重 新计算 hash 值,根据 hash 值得到这个元素在数组中的位置(即下标),如果数组该位置上 已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头, 最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
(3)HashMap 的读取:从 HashMap 中 get 元素时,首先计算 key 的 hashCode,找到数组中 对应位置的某一元素,然后通过 key 的 equals 方法在对应位置的链表中找到需要的元素。
(4)HashMap 的 rehash: 当 HashMap 中的元素越来越多的时候,hash 冲突的几率也就越 来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对 HashMap 的数组进行扩容,数组扩容这个操作也会出现在 ArrayList 中,这是一个常用的操作,而在 HashMap 数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位 置,并放进去,这就是 rehash。那么 HashMap 什么时候进行扩容呢?当 HashMap 中的元素 个数超过数组大小loadFactor(装填因子)时,就会进行数组扩容,loadFactor 的默认值为 0.75,这是一 个折中的取值。也就是说,默认情况下,数组大小为 16,那么当 HashMap 中元素个数超过 160.75=12 的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap 中 元素的个数,那么预设元素的个数能够有效的提高 HashMap 的性能。
对 HashMap 的数组进行扩容以后利用 rehash 来解决 Hash 冲突。
108、HashMap 中的装填因子有什么用?
Java 中,hash 的默认装填因子是 0.75,那么啥是装填因子捏?举个例子,你要对 5 个 对象进行 hash,而内存中,准备了 20 个位子,来放他们,那么,最后,就有 15 个空位啦, 那么装填因子就是 5/20 ,装填因子就为 0.45 啦,你装填因子越小,说明你备用的内存空 间越多,装填因子的选定,可以影响冲突的产生,装填因子越小,冲突越小
109、HashMap 和 HashSet 有什么关系?
HashSet 底层是采用 HashMap 实现的。
放进 HashSet 中的对象实际是通过这个 HashMap 的 key 来存储的。当调用 HashSet 的 add 方 法时,实际上是向 HashMap 中添加了一行 key-value 对,key 就是向 HashSet 添加的那个对 象,Vlaue 就是一个 Object 类型的常量。
110、TreeSet 和 TreeMap 中放入的元素需要满足什么条 件?
TreeSet 和 TreeMap 都是具有某种排序规则的集合: TreeSet:如果元素是不可比较的会引发类型转换异常,元素中的泛型参数对应的类型应该 要实现 Comparable 接口。包装类型以及 String 类型以及其它一些类型其实都已经实现了该 接口。另外,不要在 TreeSet 中存入空的元素,会引发空指针异常。
TreeMap:TreeMap 中的要求和 TreeSet 是类似的,只不过 TreeSet 中的要求是针对其中的元 素而言的,TreeMap 中的这些要求是针对其中的 key 而言的。
111、TreeSet 中如果放入的元素同时有父类和子类元素, 采用父类还是子类的 compareTo 方法进行比较?
如果子类和父类都复写了 compareTo 方法那么各自调用自己的 compareTo 方法, 如果子类 没有复写 compareTo 方法,那么调用的都是父类的 compareTo 方法。
112、简单描述二叉树(TreeSet)和红黑树(TreeMap)的 两种结构?
二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树
红黑树,顾名思义,通过红黑两种颜色域保证树的高度近似平衡。它的每个节点是一个
五元组:color(颜色),key(数据),left(左孩子),right(右孩子)和 p(父节点)。 红黑树的定义也是它的性质,有以下五条:
性质 1. 节点是红色或黑色
性质 2. 根是黑色
性质 3. 所有叶子都是黑色(叶子是 NIL 节点)
性质 4. 如果一个节点是红的,则它的两个儿子都是黑的
性质 5. 从任一节点到其叶子的所有简单路径都包含相同数目的黑色节点。
113、Collection 和 Collections 的区别?
Collection 是 java.util 下的接口,是某些集合的父接口,继承它的接口主要有 List 和 Set 接口。 Collections 是 java.util 下的类,是针对集合的帮助类,提供一系列静态方法实现对各种集合 的搜索、排序、线程安全化等操作。
114、队列、栈数据结构有什么区别?
面试前可以看一下 Java 中队列和栈相关的类以及里面的 API,另外也要能自己实现队列 或栈式数据结构。
队列是常用的数据结构,可以将队列看成特殊的线性表,队列限制了对线性表的访问方 式:只能从线性表的一端添加元素(offer),从另一端取出元素(poll),队列遵循先进先出的原则
(排队买饭买票) 凡是集合元素需要满足该顺序就用队列。
Deque 是 Queue 的子接口,定义了所谓的“双端队列”,即从队列的两端分别可以入 队和出队,LinkedList 实现了该接口,如果将 Deque 限制为从一端入队和出队,则可实现“栈”
(stack)的数据结构,对于栈而言,入栈称之为 push,出栈称之为 pop
栈遵循后进先出的原则 两头都允许进出时就使用双端队列 当元素要求先进后出时就使用栈
PS:还有一种称为循环队列的队列,也可以了解一下基本概念!
115、栈内存和堆内存有什么区别?
栈(stack)与堆(heap)都是 Java 用来在内存中存放数据的地方,两者区别在于:
1、 栈存放基本类型变量和对象引用,当超过作用域后释放;堆主要用来存放 new 出来的对 象(String 类型较特殊,要根据不同的创建方式区分,可以参考前面的情况,其它则在堆内存中)。
2、 堆可以动态地分配内存大小,生存期也不必事先告诉编译器,Java 的垃圾收集器会自动 收走这些不再使用的数据。存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。 栈的存取速度比堆要快,仅次于直接位于 CPU 中的寄存器,堆由于要在运行时动态分配 内存,存取速度较慢。
3、 栈中的数据可以共享,堆中的数据不能共享。
4、 栈是一种线性集合,其添加和删除元素的操作应在同一端完成,栈按照后进先出的顺序 进行处理;堆地址是不连续的,可以随机访问。
116、在 Java 中如何对 List 集合进行排序?
(1)冒泡排序
(2)可以使用 Java 中提供的 Collections.sort()方法进行,该方法返回 void,调用完毕直接影 响原集合中的顺序。
// 1.只传入集合对象
// 要求:集合中的泛型类型已经实现了 Comparable 接口并重写了其中的
//compareTo 方法,该方法的返回值:
// a.若当前对象大于给定的对象,那么返回值为大于 0 的整数(升序排列)
// b.若当前对象小于给定的对象,那么返回值为小于 0 的整数(降序排列)
// c.若两个对象相等,则应返回 0 Collections.sort(list);
// 2.一旦实现了 Comparable 接口以后,其比较逻辑就已经确定,如果希望在排序
//操作中临时指定比较规则,
// 则可以采用 Comparator 接口回调(匿名内部类)的方式,需要重写 Compare 方法
// a.o1>o2,返回值大于 0.升序排列
// b.o1<o2,返回值小于 0.降序排列
// c.o1=o2,返回值等于 0
// ps:集合中的泛型类型如果没有实现 Comparable 接口,也可以在调用该方法时临
//时指定比较规则
// 那么后面每次设计到该对象的排序,都得指定比较规则,稍显麻烦,
// 所以实现 Comparable 以后,指定的比较规则可以使针对大部分情况来讲的
// 而 Comparator 可以作为已经实现了 Comparable 以后又需要其它特殊比较的一
//种补充
Collections.sort(list, new Comparator() { public int compare(T o1, T o2) {
//T 是集合的泛型,要替换成自己想要的类型
};
});
117、Map 集合有什么特点?
是一种可以将键映射到值的对象,和 Collections 相比最大的特点就是,Map 中的元素 是以键值对的方式成对出现的。一个 Map 不能包含多个重复(以 equals 方法和 hashCode
方法作为参考依据)的键,但是可以包含重复的的值。
118、遍历 Map 集合的方式有哪些?
Map<Integer, String> map = new HashMap<Integer, String>();
// 1、获取所有键进行遍历
Set keys = map.keySet();
// 2、获取所有值进行遍历
Collection values = map.values();
// 3、获取所有键值对进行遍历
Set<Entry<Integer, String>> entries = map.entrySet(); for (Entry<Integer, String> entry : entries) {
int key = entry.getKey();
String value = entry.getValue();
}
119、DAO 设计思想在项目中的作用是什么?
Data Access Object,数据访问对象,将所有对数据源的访问操作抽象封装在一个公共API 中。接口中定义了此应用程序中将会用到的和数据库操作相关的方法。把底层的数据访问逻辑和高层的业务逻辑分开.实现DAO模式能够更加专注于编写数据访问代码.。而业务逻辑中 需要访问数据库时,只需要调用DAO中对应的方法即可实现操作,无需关注数据库的底层操 作,某种程度上使系统层次更明确,也降低了耦合度。
120、Java 中的垃圾回收(GC)机制是什么?垃圾回收器可以 马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回 收?列举两种回收机制?什么时候触发垃圾回收?如何降 低垃圾回收的触发频率?它能保证程序有足够的可用内存 吗?
第一问:垃圾回收机制(GC),当 Java 虚拟机发觉内存资源紧张时,则会自动地去清理无用 对象(没有被引用到的对象)所占用的内存空间。
第二问,第三问:可以通过 System.gc()方法,通知垃圾回收器立马回收内存,但是由于 Java 语言的规范,并不一定就能保证 GC 会马上会运行。 第四问:(1)增量收集器:增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这 会造成较小的应用程序中断。(2)分量收集器:这种收集器把堆栈分为两个或多个域,用 以存放不同寿命的对象。JVM 生成的新对象一般放在其中的某个域中,过一段时间,继续存 在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优
化性能。 第五问:垃圾回收由系统进行管理。在系统认为需要的时候自动启动一个线程。 第六问:尽量减少垃圾内存,也就是新建对象的数量,可以降低垃圾回收的频率。 第七问:垃圾回收机制无法保证有足够的内存。
121、进程和线程的概念?区别是什么?
进程
(1)进程是 OS 中运行的一个任务(一个应用程序运行在一个进程中)
(2)进程是包含了某些资源的内存区域,操作系统利用进程把他的工作划分为一些子单元
线程 (1)进程中包含的一个或多个执行单元称为线程,一个进程可以包含多个线程 (2)一个线程是进程中的一个顺序执行流 (3)进程中有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问 (4)线程只能丛属于一个进程并且它只能访问该进程所拥有的资源 (5)当操作系统创建一个进程后,该进程会自动创建名为主线程或首要线程的线程 (6)同类的多个线程共享一块内存区域和一组系统资源 (7)线程切换时负荷小,因此线程也叫轻量级进程
进程和线程的区别 (1)一个线程至少一个进程 (2)线程的划分尺度小于进程
(3)进程在执行过程中拥有独立的内存单元,而多个线程共享同一块内存区域 (4)线程和进程在执行过程中的区别:每个独立的进程有一个程序运行的入口,但是线程不 能独立执行,必须依存在应用程序中,一个应用程序有多个线程执行控制 (5)多线程的意义在于:一个应用程序中,有多个执行的部分(线程)可以同时执行,但操作系 统并没有将多个线程看成是多个独立的应用来实现调度和内存分配。
122、并发、并行的概念是什么?
并发:当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一 个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间 段分配给各个线程 执行,在一个时间段的线程代码运行时,其它线程处于挂起状。(本质是交替执行)
并行:应用能够同时执行不同的任务(真正意义上的同时)
123、什么是线程并发安全问题?如何解决?
多个线程并发读写同一个临界资源时会发生“线程并发安全问题” 常见的临界资源
多线程共享实例变量 多线程共享静态公共变量
若想解决线程安全问题,需要将异步操作变为同步操作
124、有哪些方式可以实现同步操作?
1、同步代码块
2、同步方法 3、Lock 锁
……
125、线程有哪些状态?这些状态之间如何互相转换?
126、什么是死锁?死锁的必要条件是什么?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成 的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系 统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁的条件:
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由 一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程 用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该 资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完 时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集 合{P0,P1,P2,···,Pn}中的 P0 正在等待一个 P1 占用的资源;P1 正在等待 P2 占用的 资源,……,Pn 正在等待已被 P0 占用的资源。
127、使用多线程的好处是什么?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执 行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。可以提 高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程 而不是等待,这样就大大提高了程序的效率。
128、创建线程的方式有哪些?
(1) 继承 Thread 类重写其中的 run 方法
(2) 实现 Runnable 接口重写其中的 run 方法
(3) 实现 calable 接口,重写 cal 方法,有返回值,可抛出线程异常
129、如何启动一个线程?
通过 Thread 的 start()方法启动线程
130、启动线程为什么不直接调用 Thread 中的 run 方法?
run 方法相当于只是在当前线程中调用了 Thread 中的 run 方法,而 start 方法是开辟了新的 线程并且调用了 Thread 中的 run 方法,从而可以实现多线程。
131、线程中的 stop()和 suspend()方法为何不推荐使用?
反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处 于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的 问题所在。suspend()方法容易发生死锁。
132、简单描述 ThreadLocal 的作用?
当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
public class TestThreadLocal {
private static ThreadLocal localInteger = new ThreadLocal() {
// 将ThreadLocal持有的值初始化为0
@Override
protected Integer initialValue() { return 1;
}
};
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName(); System.out.println(threadName + “开始执行…”);
// 1
System.out.println(threadName + " before:" + localInteger.get());
// 对ThreadLocal中的值更新
localInteger.set(5);
// 5
System.out.println(threadName + " after:" + localInteger.get());
}
}, “线程1”);
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName(); try {
// 线程1执行完毕后执行线程2
t1.join();
} catch (InterruptedException e) { e.printStackTrace();
}
System.out.println(threadName + “开始执行…”);
// 1, 虽然线程1中已经对ThreadLocal中的值进行了改变
// 但是此处打印的值还是1,说明,线程1中ThreadLocal的值和线程2中
//的ThreadLocal值是不共享的
// 用ThreadLocal维护的变量会为每个线程创建一个该变量副本
// 这些副本之间是彼此独立的,一个线程中ThreadLocal发生变化不会影
//响另一个线程ThreadLocal的值
System.out.println(threadName + " before:" + localInteger.get());
}
}, “线程2”);
t2.start();
}
}
133、同步和异步有什么区别,分别在什么情况下下面使用?
同步-有先后顺序的操作,相当于你干完我再干,如果多个线程执行时,下一个线程执 行过程中必须要用到上个线程的一些操作得到的结果,就必须按照顺序,一个个执行,就是
所谓的同步操作。
异步-多线程并发操作,相当于各干各的,如果多个程序执行时,下一个线程的执行过 程中不需要前面线程操作得到的结果,那就不必等待前面线程执行完毕再执行下面一个线 程,此时多个线程是可以同时执行的,就是所谓的异步操作。
134、synchronized 关键字的用法?
/**
- 方法–同步代码块
*/
public void method01() { synchronized (obj) {
}
}
/**
- 方法–修饰方法
*/
public synchronized void method02() {
}
public static synchronized void method03() {
}
Ps:synchronized 关键字用在方法上时对整个方法中的代码实现同步,同步代码块更灵 活,可以指定方法中需要同步的部分进行同步。
另外,synchronized 在作为代码块使用时,需要指定一个锁对象。 锁对象的选取需要遵循如下规则: 这个对象应该注意,多个需要同步的线程在访问该代码块时,看到的应该是同一个对象
的引用,否则达不到同步效果,通常用 this 作为同步锁对象。 如果在静态方法中无法使用 this 作为锁对象,可使用 类名.class 进行替代。
135、synchronized 和 Lock 锁的区别?
synchronized 和 Lock 锁区别:
(1)Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语 言实现的,Lock 则需要通过编程的方式来进行实现同步操作
(2)synchronized 在代码发生异常时,会自动释放线程占有的资源,因此不会导致死 锁的发生,而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能产 生死锁现象,因此使用 unLock()在 finally 块中释放锁
Lock 可以让等待的锁线程响应中断,而 synchronized 却不行,使用 synchronized
时,等待的线程会一直等待下去,不能够响应中断
(4)通过 Lock 可以知道有没有成功获得锁,而 synchronized 却不行
(5)Lock 可以提高多线程读写操作的效率
136、悲观锁和乐观锁的区别?
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修 改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁。 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会 修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据, 可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如 果提供类似于 write_condition 机制的其实都是提供的乐观锁。
137、悲观锁和乐观锁的使用场景分别是什么?
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读 场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。 但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行 retry,这 样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
138、乐观锁的实现方式有哪些?乐观锁有何缺点?
乐观锁的两种实现机制
1、版本号机制:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。
2、CAS 算法: CAS(compare and swap) 比较并交换,有三个操作数,内存地址V ,预期值B,要替换得到的目标子A;
乐观锁缺点:
1、ABA 问题:,有一个线程将A改为B,后有改为A,CAS会误认为内存值V没有改变,这称为CAS操作的ABA问题;
2、循环时间长,开销大
3、只能保证一个共享变量的原子操作
139、wait 和 sleep 方法的区别?
(1)拥有对象不同(sleep 是的 Thread,wait 可以是所有的引用类型对象) (2)wait 可以释放对象锁,sleep 保留对象锁
(3)wait 可以是任意对象来调用,sleep 只能是当前线程来调用
wait 可以通过 notify 或 notifyAll 随时唤醒,sleep 必须等到等待的时间结束后才能自动唤 醒,否则将引发异常
wait,notify,notifyAll 需要在同步代码块或同步方法中调用,sleep 可以再任何地方调用。
140、sleep 和 yield 的区别?
1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程
一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。 2、当线程执行了 sleep(long millis)方法后,将转到阻塞状态,参数 millis 指定睡眠时间;当线 程执行了 yield()方法后,让出时间片,将转到就绪状态,该方法中没有参数,因为让出时间 片的时间是不可预估的。
3、sleep()方法声明抛出 InterruptedException 异常,而 yield()方法没有声明抛出任何异常。 4、sleep()方法比 yield()方法具有更好的移植性。
141、notify 和 notifyAll 方法的区别?
notify 方法通知 某个正在等待 这个对象的控制权 的线程 可以继续运行。具体是哪一个是 不确定的。(JDK 中关于 notify 方法的部分注释如图所示)
notifyAll 方法通知所有等待这个对象控制权的线程继续运行。
142、简述线程池的概念及其原理?
线程池的作用:控制线程数量、重用线程 当一个应用程序创建大量的线程,并在任务结束后销毁,会给系统带来过度消耗,以及过度切换线程的危险。从而可能导致系统崩溃。为此需要使用线程池进行优化。 先在内存中创建一些线程,当服务器接受一个请求后,从线程池中取出一个空闲的线程
为之服务,用完之后不是销毁线程对象而是将其归还到线程池中。 在线程池模式中,任务是直接交给线程池的,而不是交给某一个具体的线程,线程池在
接到任务后,在内部查找空闲的线程,把任务交给它,一个线程同时只能执行一个任务,但 是可以先一个线程池提交多个任务。
143、线程池有哪些具体的实现?
Java 通过 Executors 提供四种线程池,分别为:
(1)new Cached ThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可 灵活回收空闲线程,若无可回收,则新建线程。
(2)new Fixed ThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会 在队列中等待。
(3)new Scheduled ThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
(4)new Single ThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来 执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
144、线程中常见的 API 及其作用?
(1)Thread.currentThread():获取当前代码片段的线程
(2)优先级设置线程优先级 void setPriority(int priority);
获取线程优先级 int getPriority():
(3) long getId():返回线程当前的表示符
String getName():用来返回当前线程的名字
(4) Thread.State getState():用来返回当前线程的状态,返回结果是一个枚举类型。
(5)void sleep:该方法会使当前线程进入阻塞状态指定毫秒,当阻塞指定毫秒后,当前线程会 重新进入 RUNNABLE 状态,等待时间片的分配
boolean isAlive():测试当前线程时候处于活动状态
(7) boolean isDaemon():判断当前的线程是否为守护线程(后台线程) Java 中有名的守护线程:GC(垃圾回收器):
GC 就是一个典型的守护线程,当我们的程序中不再有任何运行中的 Thread,程序就不会再 产生垃圾,垃圾回收器也就无事可做,所以垃圾回收线程是 Jvm 上仅剩的线程,此时,Java 虚拟机就会自动离开
void setDaemon(boolean):当参数为 true 时,该线程为守护线程(一定要在 start 方法调 用之前)
(8) void yield():该方法用于使当前线程主动让出此次 CPU 时间片回到 RUNNABLE 状态,等待 时间片的分配
void join():该方法用于等待当前线程的结束,该方法声明时会抛出 InterruptException
145、什么是输入流,什么是输出流?
输入流:程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数 据源读入到程序的通信通道
输出流:程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、 网络…)的通信通道。
146、Java 中有几种类型的流?我们一般常用的有哪些?
输入/输出 字节流顶级父类 字符流顶级父类
输入流 Inputstream Reader
输出流 OutputStream Writer
147、字节流和字符流的区别以及联系?
(1)字符流在读取文本时效率高于字节流 (2)字节流默认不使用缓冲区;字符流使用缓冲区。 (3)字节流以字节为单位进行读取,字符流以字符单位进行读取 (4)字符流有字节流衍生而来,因此必须借助于字节流实现 (5)字符流不能对单字节构成的文件进行读写,如视频、图片 联系:字符流底层其实是通过字节流实现的
148、缓冲流有什么好处?
缓冲输出流:内部维护一个缓冲区,每当向该流写数据时,都会将数据存入缓冲区,缓 冲区满了以后,会将数据一次性全部写出。
缓冲输入流:内部维护一个缓冲区,每当从该流读取数据时,都会先将数据存入缓冲区, 缓冲区满了以后,会将数据一次性读取出来。
好处:可以减少 I/O 操作的次数,提高系统的运行效率。在项目中如果需要实现文件的 复制,你一般是自己实现还是借助第三方工具类完成?用过哪些?
149、序列化和反序列化的概念?被序列化的对象需要满足 什么条件?
序列化:对象是保存在内存中的,有时需要将对象直接保存在硬盘上或者将对象传递到 另外一台计算机上,这时需要将对象转换为一个字节序列,这个过程就是序列化。
反序列化:需要将一个字节序列,将其转换为对应的对象,这个过程就是反序列化。
(1)被序列化的对象需要已经实现了 Serializable 接口。
(2)若存在继承关系:父类如果实现了序列化接口,子类自动也就实现了序列化接口。
(3)如果子类实现了序列化接口,父类没有实现,此时子类也是可以序列化的。但是 此时如果反序列化子类,则属于父类中的那一部分属性依然保持该属性对应的类 型的初始值。
150、Java 中有哪些常见的接口是标记接口。
标记接口:这种接口中没有任何需要实现的方法,它们的作用就是当某个类实现这个接 口的时候,我们就认为这个类拥有了接口标记的某种功能。
常用的标记接口有:Serializable、Cloneable、RandomAccess
151、在一个对象中什么样的属性不能被序列化?
静态变量以及用 transient 关键字修饰变量都不能被实例化。
152、serialVersionUID 序列化编号有什么作用?
通常实现 Serializable 接口的类需要提供一个常量 serialVersionUID,标明该类的版本,若 不显式声明,在对象序列化时会根据当前各类的各个方面综合计算该类的默认版本号,但不 同平台编译器实现有,所以想跨平台不同平台,都应显式声明版本号。
如果声明的类的对象在序列化时,之后随着要求的改变,更改了类的属性,当反序列化 时,就会出现 InvalidClassException,这样就会造成不兼容的问题,但当版本号相同时,就会 将不一样的属性以其对应类型的默认值就行反序列化,从而避开了不兼容问题。
153、谈谈对 NIO 流的理解,和普通 IO 流的区别在哪里?
NIO 即 New IO,这个库是在 JDK1.4 中才引入的。NIO 和 IO 有相同的作用和目的, 但实现方式不同,NIO 主要用到的是块,所以 NIO 的效率要比 IO 高很多。在 Java API 中提供了两套 NIO,一套是针对标准输入输出 NIO,另一套就是网络编程 NIO。
154、进制转换?
需要熟悉各种进制之间的转换(此处略)
155、原码、反码、补码的概念及计算?
略
156、++、–
前置++,前置–,先加(减)1 再取值 后置++,后置–,先取值,再加(减)1
157、break、continue 关键字的区别?
break:用在循环中,表示跳出单层循环,用在 switch……case 中表示跳出当前条件结构
continue:只能用在循环中,表示结束该趟循环,进入下一趟循环
158、什么是 Java 中变长参数?
在 Java 5 中提供了变长参数,允许在调用方法时传入不定长度的参数。变长参数是
Java 的一个语法糖,本质上还是基于数组的实现:
public class Args {
public static void main(String[] args) { String two = stringConcat(“A”, “B”);
String three = stringConcat(“A”, “B”, “C”);
}
public static String stringConcat(String… objs) { String sum = “”;
for (String s : objs) { sum += s;
}//for return sum;
}
159、为什么要引入集合的概念?
数组不是面向对象的,存在明显的缺陷,集合完全弥补了数组的一些缺点,比数组更灵 活更实用,可大大提高软件的开发效率而且不同的集合框架类可适用于不同场合。具体如下:
1)数组的效率高于集合类.
2)数组能存放基本数据类型和对象,而集合类中只能放对象。
3)数组容量固定且无法动态改变,集合类容量动态改变。
4)数组无法判断其中实际存有多少元素,length 只告诉了 array 的容量。
5)集合有多种实现方式和不同的适用场合,而不像数组仅采用顺序表方式。
6)集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性调用 即可实现各种复杂操作,大大提高软件的开发效率。
160、写出一个生产者/消费者模式的案例?
生产者,消费者模式也称有限缓冲问题,是一个多线程同步问题的经典案例。 该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”—
—在实际运行时会发生的问题。 生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与
此同时,消费者也在缓冲区消耗这些数据。 该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中
空时消耗数据。
public class Homework01 {
public static void main(String[] args) {
//缓冲区
Queue queue = new LinkedList<>();
//生产者线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 4; i++) { synchronized (queue) {
//假定有一个元素就视作满的情况
//如果缓冲区不为空,则不需要添加元素,如果缓冲区为空,需
要添加元素
while (queue.size() == 1) { System.out.println(“通知消费者进行消费…”); try {
queue.wait();
} catch (InterruptedException e) { e.printStackTrace();
}
}
System.out.println(“生产了:” + i); queue.offer(i);
//当缓冲区中有元素时,唤醒消费者
queue.notifyAll();
}
}
}
}, “生产者”).start();
//消费者线程
new Thread(new Runnable() {
@Override
public void run() {
//消费者被唤醒后才开始消费,此前不知道会被唤醒多少次 while (true) {
synchronized (queue) {
while (queue.size() == 0) { System.out.println(“缓冲区为空,等待生产者生产…”);
try {
queue.wait();
} catch (InterruptedException e) { e.printStackTrace();
}
}
//消费操作
int num = queue.poll();
//通知生产者进行生产 queue.notifyAll();
System.out.println(“消费了元素:” + num); if (num == 4) {
break;
}
}
}
}
}, “消费者”).start();
}
}
161、写出一个死锁的案例?
class DieLock extends Thread {
// 定义两个锁对象
public static final Object OBJA = new Object(); public static final Object OBJB = new Object(); public boolean flag;
public DieLock(boolean flag) { this.flag = flag;
}
@Override
public void run() { if (flag) {
synchronized (OBJA) { System.out.println(“if objA”); synchronized (OBJB) {
System.out.println(“else objA”);
}
}
} else {
synchronized (OBJB) { System.out.println(“else objB”); synchronized (OBJA) {
System.out.println(“else objA”);
}
}
}
}
}
public class Demo {
public static void main(String[] args) { DieLock d1 = new DieLock(true); DieLock d2 = new DieLock(false); d1.start();
d2.start();
}
}
有两把锁 objA、objB,两个线程 d1、d2, d1 为 true 会进入第一部分代码块,d2 进入 第二部分代码块,理想状态下,d1 和 d2 正常交叉走完代码块,但是由于两个线程抢 CPU 的执行权时,有可能出现 d1 走完第一部分的第一个锁后进入下一个锁时,d2 在第二部分代 码块还没有执行完第一个锁,所以会等待 d2 完成,然后两个线程进入不到对方的代码块中 以至于互相等待,因此出现死锁现象。
162、写出一个单例模式。
/**
- 懒汉模式:只有当对象需要被使用时才进行创建
*/
public class Singleton01 {
private static Singleton01 singleton01;
/**
- 一定要有私有构造,保证不能在外部被反复进行 new 创建
*/
private Singleton01() {
}
/**
- 提供一个公共的方法来获取这个唯一实例
-
- 懒汉模式的缺点:无法保证多线程并发的时候对象是唯一的,但是可以通过对方法 加同步关键字进行解决
*/
public static synchronized Singleton01 getInstance() {
//如果对象存在就不要重复构建,直接返回 if (singleton01 != null) {
return singleton01;
} else {
//如果对象不存在就创建这个对象 singleton01 = new Singleton01(); return singleton01;
}
}
}
Ps:
单例模式的要点:整个系统中只有一个该类型的实例、私有无参构造、线程并发问题 单例模式的实现:懒汉、饿汉、枚举、静态内部类、双重校验锁 思考:如何防止反射以及反序列化对单例模式的破坏?
163、写出工厂模式的实例代码?
(1)工厂方法模式
package com.sy.factory;
public class TestFactory01 {
public static void main(String[] args) {
Ford focus = FordFactory.getInstance(1); Ford mondeo = FordFactory.getInstance(2);
}
}
//工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
//万能类,上帝类:FordFactory 可以用来生产任何类型的对象
class FordFactory {
/**
- 用来生产福特汽车
- @param type 用于区分是哪一种类型
*/
public static Ford getInstance(int type) { switch (type) {
case 1: {
return new Focus();
}
case 2: {
return new Mondeo();
}
}
return null;
}
}
//抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
//语法上:使得各种不同的对象能够有一个统一的数据类型 abstract class Ford {
}
//具体产品角色:工厂类所创建的对象就是此角色的实例。在 java 中由一个具体类实现。
class Focus extends Ford { public Focus() {
System.out.println(“创建了福克斯…”);
}
}
class Mondeo extends Ford { public Mondeo() {
System.out.println(“创建了蒙迪欧…”);
}
}
//产品角色在扩展时不需要修改原有的产品类,只需要添加新的类即可
//问题:工厂角色在简单工厂方法模式中不符合开闭原则->因为产品增加后,需要对工厂中 的方法进行修改
(2)抽象工厂模式
package com.sy.factory;
public class TestFactory02 {
public static void main(String[] args) {
BMWFactory bmwx1Factory = new BMWX1Factory(); BMW bmwx1 = bmwx1Factory.getInstance();
}
}
//抽象工厂角色 interface BMWFactory {
public BMW getInstance();
}
//具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产 品的对象。
/**
- 生产 X1 系列的工厂
*/
class BMWX1Factory implements BMWFactory { public BMWX1Factory() {
System.out.println(“创建 X1 工厂”);
}
public BMW getInstance() { return new BMWX1();
}
}
/**
- 生产 X3 系列的工厂
*/
class BMWX3Factory implements BMWFactory { public BMWX3Factory() {
System.out.println(“创建 X3 工厂”);
}
public BMW getInstance() { return new BMWX3();
}
}
abstract class BMW {
}
class BMWX1 extends BMW { public BMWX1() {
System.out.println(“生产了宝马 X1…”);
}
}
class BMWX3 extends BMW { public BMWX3() {
System.out.println(“生产了宝马 X3…”);
}
}
164、写出一个适配器模式?
package com.sy.adpter; public class TestAdapter01 {
public static void main(String[] args) {
//此时 PS2 接口的功能是通过适配器来实现的
//而适配器中的功能调用的是原来 USB 的功能,
//通过适配器使得 PS 中的功能和 USB 中的功能一样,实现了转换操作
// PS2 ps2=new Adapter1();
// ps2.doPs2Function();
PS2 ps2=new Adapter2(new Usb()); ps2.doPs2Function();
}
}
/**
- USB 插头(优盘、数据线、鼠标、键盘)
*/
class Usb {
public void doUsbFunction() { System.out.println(“usb 需要完成的事情…”);
}
}
/**
- PS2 接口
*/
interface PS2 {
void doPs2Function();
}
/**
- 转换器:类适配器
*/
class Adapter1 extends Usb implements PS2 {
@Override
public void doPs2Function() { doUsbFunction();
}
}
/**
- 转换器:对象适配器
*/
class Adapter2 implements PS2 { private Usb usb;
public Adapter2(Usb usb) { this.usb = usb;
}
@Override
public void doPs2Function() { usb.doUsbFunction();
}
}
165、查找有哪几种方法?写代码实现二分查找
查找分为: 顺序查找、 二分查找、插值查找、斐波那契查找、树表查找、分 块查找、哈希查找
//基本思想是,将n个元素分成个数大致相同的两半(这里假设数组元素呈升序排列) 取a[n/2]与欲查找的x作比较,
// 如果x=a[n/2]则找到x,算法终止。
// 如 果x<a[n/2],则我们只要在数组a的左半部继续搜索x
// 如果x>a[n/2],则我们只要在数组a的右 半部继续搜索x。
public static void main(String[] args) {
int[] a = { 1, 421, 4, 1, 4, 124, 12, 41 };
System.out.println(binarySearch(a, 4));
System.out.println(binarySearch(a, 100));
}
public static int binarySearch(int[] arr, int key) {
int start = 0;
int end = arr.length - 1; while (start <= end) {
int middle = (start + end) / 2; if (key < arr[middle]) {
end = middle - 1;
} else if (key > arr[middle]) { start = middle + 1;
} else {
return middle;
}
}
return -1;
}
166、String 中的 replace 和 replaceAll 的区别?
replace:根据字符或字符串进行替换 repalceAll:根据正则进行替换
有些无聊的面试官喜欢问 String 有哪些常用方法。给他说几个就是了!
167、如 果 100 个 线 程 同 时 通 过 一 个 静 态 的
SimpleDateFormat 对象操作,会有什么问题?
public class A{
Private static SimpleDateFormat sdf=new SimpleDateFormat(“yyyy-MM-dd”); Void method(){
//以下做法是错误的,存在线程安全问题,因为 SimpleDateFormat 本身是线程不安
全的,如果多线程并发,很有可能会造成格式化日期不正确,因此在使用时应该用一次 创建一次,另外也可以使用 Java8 提供的 DateFormat 来改善它。
Sdf.format();
Sdf.format();
Sdf.format();
Sdf.format();
Sdf.format();
}
}
168、Iterator 和 ListIterator 区别是什么?
相同点
都是迭代器,当需要对集合中元素进行遍历不需要干涉其遍历过程时,这两种迭代器都 可以使用。
不同点
使用范围不同,Iterator 可以应用于所有的集合,Set、List 和 Map 和这些集合的子类型。 而 ListIterator 只能用于 List 及其子类型。
ListIterator 有 add 方法,可以向 List 中添加对象,而 Iterator 不能。
ListIterator 和 Iterator 都有 hasNext() 和 next() 方法 , 可 以实现 顺序 向后遍 历, 但 是 ListIterator 有 hasPrevious()和 previous()方法,可以实现逆向(顺序向前)遍历。Iterator 不 可以。
ListIterator 可以定位当前索引的位置,nextIndex()和 previousIndex()可以实现。Iterator
没有此功能。
都可实现删除操作,但是 ListIterator 可以实现对象的修改,set()方法可以实现。Iterator 仅能遍历,不能修改。
169、HashSet 中是如何确保元素不重复的?
由于 HashSet 底层是通过哈希算法来实现的,所以在此处,首先先去比较两个元素的 hashCode 方法的返回值是否一致,如果不相同,则立马判定不是同一个对象,如果 hashCode 返回值一样,则此时再通过 equals 方法来判断内容是否相同,如果内容相同,则
视为同一个元素,如果内容不同,则不是同一个元素。 对于自己定义的数据类类型,一般需要重写 hashCode 和 equals 方法,两个相同的元素
其 hashCode 和 equals 方法的返回值应该分别是一样的,如果 equals 比较中结果不同,
hashCode 有可能相同,但是应该尽量避免(防止频繁产生 rehash 操作)。
170、TreeSet 中如何保证元素是不重复的?
是由比较方法的返回值决定的
TreeSet 中的元素需要实现 Comparable 接口或者 new TreeSet<>(comparator)中临时指定 比较规则。元素的重复与否是由 Comparable 或 Comparator 接口中回调方法的值决定的
171、LinkedHashSet 的特点?
底层也是由哈希算法实现的,只不过其中通过 LinkedHashMap 保存了元素的存入序,所以对 于 LinkedHashSet 在使用时可以确保存入和迭代序是一致的,其内部其实依然是无序的。
172、BigDecimal、BigInteger 的区别?
BigDecimal:解决 float 和 double 在运算时产生的精度丧失问题,如果在实际开发中要
对数值进行精确计算的话,应该使用 BigDecimal。 BigInteger:解决整数或整数运算中,值的范围超出 long 型范围的情况。
173、如何删除一个 List 集合中的所有元素?
(1)clear()方法
(2)for 循环删除(从后往前删)
(3)通过 Iterator 进行删除
174、什么是正则表达式?
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这 些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一 种过滤匹配逻辑。
通过正则表达式可以对字符串数据进行一些复杂的匹配、查找、替换操作。常见的就是 将其用在数据格式的验证中。
要求:能写出常用的简单正则表达式,例如:邮箱、IP、手机号、身份证号、邮编…… 这里注意 Js 和 Java 中正则表达式用法上的区别
175、实际开发中,前端校验和服务端校验一定都需要做吗?
最好都做,前端校验可以保证在发送请求之前先校验数据,不符合要求就不发送数据,可以 降低服务端压力,但是一旦数据包在到服务端之前被拦截,对包数据进行修改,服务端依然 会得到不符合要求的数据。所以此时需要服务端校验,即使数据在中间被篡改,此时服务端 也会对数据再做一次校验。一般前后端和服务端都会对同一数据添加相同的校验规则,不过 也有一些校验逻辑只能通过服务端完成(例如:用户名是否已经存在)。
176、Properties 和 XML 的区别?
properties 配置文件,风格是一个属性对应于一个值(key = value)这样的键值匹对模式。 这样给我的感觉就是每一行 properties 配置文件的键值,对应着一次赋值。事实上,如果你 试验一下,在前后两行 properties 文件中给同一个属性赋不同值,得到的结果往往是属性是 后面赋值的值。properties 的局限性在于,只能处理赋值。
xml 配置文件则是一个树的结构,因此,可以处理各种各样定义好的情况,例如 add 一 个属性之类的,这在 properties 配置文件中就没有办法实现了。
总体来说:
properties 配置文件易于理解配置了哪些信息,适合于简单的属性配置。
xml 配置文件结构清晰,但是内容比较繁琐
177、JSON 和 XML 有何区别?
编码的可读性来说,XML 有明显的优势,毕竟人类的语言更贴近这样的说明结构。JSON 读起来更像一个数据块,读起来就比较费解了。不过适合机器阅读,例如:通过 JSON 的索 引 country.provinces[0].name 就能够读取这个值。
编码的手写难度来说,XML 还是舒服一些,好读当然就好写。不过写出来的字符 JSON 就明显少很多。去掉空白制表以及换行的话,JSON 就是密密麻麻的有用数据,而 XML 却包 含很多重复的标记字符。
两者也都能作为配置文件以及数据传输格式,一般在前后端数据交互中,还是使用 JSON
格式较多。
178、JDK1.7 中的新特性?
(1) switch 中增加对 String 类型的支持。
(2)数字字面量的改进
(3)catch 子句同时捕获多个异常
(4)try……with……resources
(5)变长参数的优化
179、JDK1.8 中的新特性?
(1)函数式接口
(2)Lambda 表达式
(3)Stream 操作
(4)default 方法
(5)LocalDate、LocalDateTime
180、如何删除一个非空目录?
/**
- 删除一个非空目录
/
public class Homework02 {
/* - 如果要通过 delete 方法删除目录,需要保证目录为空
- 1.如果目录为空则直接删除
- 2.如果目录不为空,需要将目录中的子项目删除
- 如果子项为单独的文件,则直接删除
- 如果子项为目录,则重复前面的操作
*/
public static void main(String[] args) {
delete(new File(“D:” + File.separator + “2”));
}
public static void delete(File file) { if (file.isDirectory()) {
//获取目录中所有的项目
File[] files = file.listFiles(); for (File f : files) {
delete(f);
}
}
file.delete();
}
}
181、如何复制一个非空目录?
/**
- 复制文件夹及其下面的所有内容,要求复制后的文件依然可用
*/
public class Homework02 {
public static void main(String[] args) {
copyDirectory(new File(“C:\Users\Administrator.USER-20190301IG\Desktop\笔记”), new File(“C:\Users\Administrator.USER-20190301IG\Desktop\笔记(1)”));
}
//删除操作:先删除子项,再删除目录
//复制操作:先构建目录,再复制文件
/**
- 目录复制
- @param from 要被复制的目录
- @param to 复制到后的新目录
*/
public static void copyDirectory(File from, File to) {
//(1)如果当前复制后的目录不存在,需要创建这个目录 if (!to.exists()) {
to.mkdirs();
}
File[] oldFiles = from.listFiles();
//(2)如果当前要复制的目录存在,去遍历原来的文件目录
for (File file : oldFiles) {
//a.如果目录中的子项目是单独的文件,直接通过 IO 流进行复制
if (file.isFile()) { try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(from + File.separator + file.getName()));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(to + File.separator + file.getName()))
) {
byte[] bytes = new byte[3 * 1024]; int len = -1;
while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len);
}
} catch (IOException e) { e.printStackTrace();
}
}
//b.如果目录中的子项目是一个目录,则创建这个目录,然后遍历原来对应的 这个文件,目录……
if (file.isDirectory()) {
copyDirectory(new File(from + File.separator + file.getName()), new File(to + File.separator + file.getName()));
}
}
}
}
182、如何计算一个非空目录的总大小?
/**
- 计算一个非空文件目录的总大小
/
public class Homework01 {
/* - 如果 file 就是单独的文件,则直接返回大小
- 如果 file 是文件目录,则需要遍历文件目录中的每一个子项目
- (1)如果子项目是文件,则直接累加其大小
- (2)如果子项目是目录,则继续遍历,重复前面的操作
*/
public static void main(String[] args) {
System.out.println(getSize(new File(“D:” + File.separator + “J2SE”)));
}
public static long getSize(File file) { if (file.isFile()) {
return file.length();
}
if (file.isDirectory()) {
File[] files = file.listFiles(); long size = 0;
for (File f : files) {
size += getSize(f);
}
return size;
}
return 0;
}
}
183、网络编程的三要素及各自的概念是什么?
IP 地址:网络中每一台计算机的唯一标识,通过 IP 地址找到指定的计算机。好比我要 和小明说话,首先我要到小明的住址找到小明(相当于通过 IP 找到指定计算机)。
端口:用于标识进程的逻辑地址,通过端口找到指定进程。好比小明用耳朵听我说(相 当于用端口接收)
协议:定义通信规则,符合协议则可以通信,不符合不能通信。好比我们对话不能使用 鸟语,需要作出规定彼此都要使用都能听懂的普通话(这就是协议的作用了)。
184、Socket 的是什么?
在 TCP/IP 协议中,“IP 地址+TCP 或 UDP 端口号”唯一标识网络通讯中的一个进程,“IP 地址+端口号”就称为 socket。
在 TCP 协议中,建立连接的两个进程各自有一个 socket 来标识,那么这两个 socket 组 成的 socket pair 就唯一标识一个连接。
socket 本身有“插座”的意思,业界通常称套接字,用来描述网络连接的一对一关系。 为 TCP/IP 协议设计的应用层编程接口称为 socket API。
185、TCP 和 UDP 的区别?
UDP 是用户数据报协议是无连接的,不可靠的,数据的到达时间,到达顺序,数据是否 能够到达及数据到达的完整性都是无法保证的,类似于寄信,适用于视频会议等对速度要求 高但对数据完整性要求不严格的场景,
TCP 是传输控制协议,数据的传输建立在连接之上,是可靠的,TELNET、FTP、HTTP 都
是建立在 TCP 之上的,因为有连接所以在 JAVA 中用输入输出流来传输数据。
186、使用 Socket 实现:从服务器读几个字符,再写入本 地控制台,写出服务器端和客户端的代码
服务端:
public class Server {
public static void main(String[] args) throws IOException {
//创建服务器的套接字对象,指定客户端通过哪个端口访问服务端 ServerSocket ss = new ServerSocket(7777);
//服务端等待来自客户端的请求
Socket s = ss.accept();
//收到客户端请求以后,将信息通过输出流发送给客户端 OutputStream out = s.getOutputStream(); out.write(“来自服务器的信息!\n”.getBytes());
// while (true) {
//
// }
}
}
客户端:
public class Client {
public static void main(String[] args) throws IOException {
//创建客户端套接字对象,指定要访问的服务端的ip以及端口号 Socket s = new Socket(“127.0.0.1”, 7777);
//从输入流读取来自服务器端的信息
InputStream in = s.getInputStream(); int b;
while ((b = in.read()) != -1) { System.out.write(b);
}
}
}
187、采用 Java 多线程技术,设计实现一个符合生产者和 消费者问题的程序。
对一个对象(枪膛)进行操作,其最大容量是 12 颗子弹。 生产者线程是一个压入线程,它不断向枪膛中压入子弹; 消费者线程是一个射出线程,它不断从枪膛中射出子弹。
生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者
处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生 产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。 为了解决这个问题于是引入了生产者和消费者模式。并发编程中使用生产者和消费者模式能 够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整 体处理数据的速度。
public class SafeThread {
public static void main(String[] args) {
SafeStack s = new SafeStack();
new Thread(new PushThread(s)).start();
new Thread(new PopThread(s)).start();
new Thread(new PopThread(s)).start();
}
}
interface StackInterface {
/**
- 压入子弹
/
void push(int n);
/* - 射出子弹
/
int[] pop();
}
/* - 生产者线程
*/
class PushThread implements Runnable {
private StackInterface s;
public PushThread(StackInterface s) {
this.s = s;
}
@Override
public void run() { while (true) {
Random r = new Random();
s.push(r.nextInt(10));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
- 消费者线程
/
class PopThread implements Runnable {
private StackInterface s;
public PopThread(StackInterface s) {
this.s = s;
}
@Override
public void run() {
while (true) {
System.out.println("->" + s.pop()[0] + “<-”);
try {
Thread.sleep(100);
} catch (InterruptedException e) { e.printStackTrace();
}
}
}
}
class SafeStack implements StackInterface { private int top = 0;
/* 枪膛对象 **/
private int[] values = new int[20]; private boolean dataAvailable = false;
@Override
public void push(int n) { synchronized (this) {
while (dataAvailable) { try {
wait();
} catch (InterruptedException e) { e.printStackTrace();
}
}
values[top] = n;
System.out.println(“压入数字” + n + “步骤 1 完成”); top++;
dataAvailable = true; notifyAll();
System.out.println(“压入数字完成”);
}
}
@Override
public int[] pop() { synchronized (this) {
while (!dataAvailable) { try {
wait();
} catch (InterruptedException e) { e.printStackTrace();
}
}
System.out.println(“弹出”); top–;
int[] test = { values[top], top }; dataAvailable = false;
// 唤醒正在等待压入数据的线程
notifyAll(); return test;
}
}
}
190、Java 中对象的创建方式有哪些?
(1)通过 new 构造方法来创建对象
(2)通过反射来创建对象
(3)通过反序列化来创建对象
(4)通过克隆来创建对象
191、深复制和浅复制的区别是什么?
浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然
指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。 深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。 那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换 言之,深复制把要复制的对象所引用的对象都复制了一遍。
192、什么是反射原理?
Java 的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、 探知、自审。使用在编译期并不知道的类。这样的特点就是反射。
193、简述类的加载机制原理?
Class 文件由类装载器装载后,在 JVM 中将形成一份描述 Class 结构的元信息对象,通过 该元信息对象可以获知 Class 的结构信息:如构造函数,属性和方法等,Java 允许用户借由 这个 Class 相关的元信息对象间接调用 Class 对象的功能。
虚拟机把描述类的数据从 class 文件加载到内存,并对数据进行校验,转换解析和初始 化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。
194、简述类加载器的分类及作用?
(1)作用
类加载器负责将.class 文件加载到内存中,并负责为之生成对应的 Class 对像。
(2)分类
JVM 在启动时,会形成由三个类加载器组合而成的初始类加载器层将结构。
A、BootStrap ClassLoader
此类负责加载 JVM 的核心类,通常被称之为引导或原始类装载器。这个类非常特殊, 它实际是不是 ClassLoader 的子类。而是由 SUN 公司自己定制的专用类。
B、AppClassLoader
此类负责加载用户项目 CLASSPATH 中的类。也称之为系统类,或应用类加载器。可以通 过以下方法直接获取此类:ClassLoader.getSystemClassLoader()
C、ExtClassLoader
此类被称为扩展类加载器。它负责加载 jre/lib/ext 目录下的所有类。所以,当有扩展类 需要加入到 JVM 时,建议将扩展类放到 ext 目录下,JVM 会自动为你加载它们。
195、什么是类加载器的父类委托机制?
父类委托机制是指,先由 parent(父)类装载器去装载需要加载的类,如果父类找不到再 由子类的装载器加载。
类装载器还使用了 Cache(缓存)机制,如果缓存中有这个 Class 则直接返回它,如果没有, 则从文件中读取并转换成 Class 对像,同时再将它 Cache 起来。这样做的目的是保证 Class 对像只被装载一次。这也就是为什么在修改了 Java 代码后,必须重新启动才会生效的原因。
196、举例说明反射原理的使用场景?
Java 的反射机制使它知道类的基本结构,这种对 Java 类结构探知的能力,我们称为 Java
类的“自审”。
用 IDEA 构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工 具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就 是利用了 Java 反射的原理,是对我们创建对象的探知、自审。
Java 的反射机制是 Java 特性之一,反射机制是构建框架技术的基础所在。(后面学习 的框架中都用到了反射原理)
197、反射原理常用相关的类有哪些,分别是什么作用?
(1)java.lang.Class:要正确使用 Java 反射机制就得使用 java.lang.Class 这个类。它是 Java 反射机制的起源。当一个类被加载以后,Java 虚拟机就会自动产生一个 Class 对象。通过这 个 Class 对象我们就能获得加载到虚拟机当中这个 Class 对象对应的方法、成员以及构造方法
的声明和定义等信息。
(2)java.lang.reflect.Constructor:该类代表类中的构造方法,通过该对象,可以完成类的实 例化。
(3)java.lang.reflect.Field:该类代表类中的属性,通过该类型对象,可以对属性的值进行 设置、修改。
(4)java.lang.reflect.Method:该类代表类中的方法,通过该类型对象,可以调用类中的方 法。
198、Java 中获取一个类的 Class 对象有哪些方式?
(1)Class.forName(“类的全局限定名”)
(2)类名.class
(3)调用对象实例中的 getClass 方法
(4)通过类加载器获取
199、上述获取 Class 对象的方式的异同是什么?
方法一不执行静态块和动态构造块 方法二执行静态块、不执行动态构造块 方法三需要创建对象,静态块和动态构造块均会执行
四种方式得到的 Class 对象都是同一个对象,因为类只会被加载一次,因此要判断是否 为某一类型,用==比较即可。
200、什么是注解?
注解(Annotation),也叫元数据。一种代码级别的说明。
它是 JDK1.5 开始引入的一个特性,与类、接口、枚举是在同一个层次。 它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说 明,注释。
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于 没有某种编辑,此后,编译器、开发工具或其它应用程序就可以利用反射来了解类及各种元 素上有无何种标记,有什么标记就去干对应的事。
注解本身除了对程序中的类、方法、属性……进行标记以外,不具备改变程序运行时逻 辑的行为。通过反射获取类、方法、属性……上的运行时注解信息,从而实现动态控制程序 运行的逻辑。因此要让注解生效,必须编写注解解析器来对注解进行解析
201、注解根据其作用有哪些分类?
(1)编写文档:通过代码里标识的元数据生成文档
(2)代码分析:通过代码里标识的元数据对代码进行分析
(3)编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查
202、什么是源注解,有哪些常用的源注解,说明它们的作
用?
负责注解其他注解,用来对其它注解类型作说明,在自定义注解时就需要用到元注解
(1)@Target
指明了 Annotation 所修饰的对象范围:Annotation 可被用于 packages、types(类、接 口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参 数和本地变量(如循环变量、catch 参数)。
(2)@Retention 定义类该注解被保留的时间长短:
某些仅出现在源代码中,而被编译器丢弃; 某些被编译在 class 文件中,但可能会被虚拟机忽略
某些装载时将被读取(并不影响 class 执行,注解与 class 在使用上是被分离的)
(3)@Document
用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被 例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。
(4)@Inherited
是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。 如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation
将被用于该 class 的子类。
203、自定义注解中的成员有什么需要注意的地方?
成员方法只能用 public 或默认这两个访问权修饰。 成员方法的返回类型只能是如下类型:
所有基本数据类型(包装类型也不可以)
String 类型 Class 类型 enum 类型
Annotation 类型
204、OOP 思想的理解?
面向对象的编程方法 OOP 是九十年代才流行的一种软件编程方法。它强调对象的“抽 象”、“封装”、“继承”、“多态”。我们讲程序设计是由“数据结构”+“算法”组成 的。从宏观的角度讲,OOP 下的对象是以编程为中心的,是面向程序的对象。把组件的实 现和接口分开,并且让组件具有多态性。oop 强调在程序构造中语言要素的语法。你必须得 继承,使用类,使用对象,对象传递消息
205、什么是面向接口编程?
面向接口编程就是先把客户的业务提取出来,作为接口。业务具体实现通过该接口的实 现类来完成。当客户需求变化时,只需编写该业务逻辑的新的实现类,通过更改配置文件(例 如 Spring 框架)中该接口的实现类就可以完成需求,不需要改写现有代码,减少对系统的影 响。采用基于接口编程的项目,业务逻辑清晰,代码易懂,方便扩展,可维护性强。
206、Set 中的 HashSet 和 TreeSet 的异同?
TreeSet 类不仅实现了 Set 接口,还实现了 java.util.SortedSet 接口,从而保证在遍历集合 时按照递增的顺序获得对象。遍历对象时可能是按照自然顺序递增排列,所以存入用 TreeSet 类实现的 Set 集合的对象必须实现 Comparable 接口;也可能是按照指定比较器递增排列, 即可以通过比较器对用 TreeSet 类实现的 Set 集合中的对象进行排序。
HashSet 类按照哈希算法来存取对象,当向集合中加入一个新对象时,会调用对象的
HashCode()方法得到对象的哈希码,然后根据这个码计算出对象在集合中存储的位置。
207、Iterator 方法在 Map 中,List 中,Set 中都有该方法吗? 为什么?
Map 没有
Java 集合类中 Map 接口下的相关类并没有像 Collection 接口的相关类一样实现 get()方 法,因此在要实现遍历输出的场景中没法直接用 get()方法来取得对象中的数据,但 Java 本 身提供了另一种遍历数据的方法,即用 Iterator 迭代器,虽然 Iterator 可以用来遍历读取数 据,但它本质上不是一种方法,它只是一种设计模式
208、简述线程优先级的概念?
线程调度的优先级,每个 OS 都有不同的实现,java 虚拟机为了兼容各种 OS 设定了 1-10
个优先级,理论上数字越大,优先级越高。
而某些 OS 可能只有 3-5 个线程,那么 jvm 会根据实际情况将 1-10 这 10 个数字与 OS 的线 程优先级做一个映射关系。
那么思考一下,很有可能 优先级 3 和 优先级 5 在 OS 中是同一个优先级。
java 中通过 setPriority(int)方法来设置一个线程的优先级。 在实际工作中,通常将优先 级设置为普通(5,默认),最大(10),最小(1)。
注意:不是说优先级高的线程一定会执行,只是获取时间片的几率高于其它线程而已!
209、简述线程中锁的概念?
Threading 模块为我们提供了一个类,Threading.Lock,锁。我们创建一个该类对象,在 线程函数执行前,“抢占”该锁,执行完成后,“释放”该锁,则我们确保了每次只有一个
线程占有该锁。这时候对一个公共的对象进行操作,则不会发生线程不安全的现象了。
210、简述线程调度的概念?
计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得 CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程 分配 CPU 的使用权。
注意:Java 种的线程调度是典型的抢占式调度!
211、简述垃圾回收机制及其特征?
java 的垃圾回收是 java 语言的重要功能之一,当程序创建对象、数组等引用类型实体时, 系统都会在堆内存中为之分配一块内存区,对象就保存在这块内存区中,当这块内存不在被 任何引用变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。垃圾回收机制有 如下特征:
垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如 数据库连接、 网络 IO 等资源)。
程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行.当对象永久性的失 去引用后,系统就会在何时的时候回收它所占的内存。
在垃圾回收机制回收任何对象之前,总会先调用它的 finalize()方法,该方法可能是该对象 重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收.
212、对象在内存中有哪些状态?
(1)可达状态:当一个对象被创建后,若有一个以上的引用变量使用它,则这个对象在程 序中处于可达状态,程序可通过引用变量来调用该对象的 Field 和方法。
(2)可恢复状态:如果程序中的某个对象不再有任何变量引用它,它就进入了可恢复状态。 在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系 统会调用所有可恢复对象的 finalize()方法进行资源清理。如果系统在调用 finalize()方法时重 新让一个引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象将进入不可达 状态。
(3)当对象与所有引用变量的关联都被切断,且系统已经调用所有对象的 finalize()方法后 依然没有使该对象变成可达状态,那么这个对象将永久的失去引用,最后变成不可达状态。 只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源。
213、垃圾回收有哪些常见的算法?
(1)标记清除算法 第一步,标记要回收的垃圾对象, 第二步就是清除被标记的垃圾对象。
标记清除算法会产,生大量的内存碎片,而且效率低.所以,为了解决这个问题,出现了复制 清除算法。
(2)复制清除算法 在要进行垃圾回收的时候,先将活着的对象整齐的复制到一块空闲区域,然后再将原来的
区域的垃圾全部清除. 效率高于标记清除算法,活着的对象是整齐排列的,没有内存碎片。 但是这个方法的缺点也很明显,那就是浪费空间。
(3)标记清理算法 将活着的对象一个接一个的按顺序排好,然后再清除变成垃圾的对象.这种方法不会造成
碎片,也不会造成内存的浪费.但是效率不高。
(4)分类算法
根据内存的不同,采用不同的垃圾回收方式(上面的 1,2,3)进行垃圾回收.
214、设计模式的六大原则(SOLID 原则)
(1)单一原则(Single Responsibility Principle):一个类只负责一项职责,尽量做到类的只 有一个行为原因引起变化。
(2)里氏替换原则(LSP liskov substitution principle):子类可以扩展父类的功能,但不能 改变原有父类的功能。
(3)依赖倒置原则(dependence inversion principle):面向接口编程.
(4)接口隔离(interface segregation principle):建立单一接口。使用多个专门的接口,而不使用单一的总接口,每一个接口应该承担一种相对独立的角色,互不干扰。
(5)迪米特原则(law of demeter LOD):最少知道原则,尽量降低类与类之间的耦合。、 一个对象应该对其他对象有最少的了解。一个软件实体应当尽可能少地与其他实体发生相互作用。
(6)开闭原则(open closed principle):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
215、Java 泛型中出现的?,super,extends 以及 T,E,K,V…… 分别是什么,有何区别?
(1)无限定通配符? 使用一个问号表示类型参数,虽然我们可以在用到的使用再指定具体的类型,但是如果
在用到的时候还不能确定具体的类型,就需要依靠通配符来解决。即引入了通配符,使得泛 型变成协变的。
(2)上边界限定通配符? extends ……
List<? extends Number> list1 = new ArrayList(); List<? extends Number> list2 = new ArrayList(); List<? extends Number> list3 = new ArrayList();
(3)下边界限定通配符? super ……
List<? super Integer> list4 = new ArrayList(); List<? super Integer> list5 = new ArrayList();
(4)T、E、V、K 等
以 List 或 Map 为例,其中的泛型参数定义为或者<K,V>,类中的方法中甚至将 E、K、V 等作为参数类型使用。此处和?最大的区别在于,这个类型其实是一个确定的类型,调用时 指定什么类型就是什么类型,例如 List,编译期间会将 E 自动擦除,替换成 String,类
中所有出现 E 的地方,也替换成 String。 这一类参数相当于实现了类中一些类型的同统一(包括参数、返回值等)。
216、如何把自己写的项目打成 jar 包?
https://blog.csdn.net/fengfengchen95/article/details/79915346
217、如何为自己编写的代码生成 JavaDoc 文档?
https://www.cnblogs.com/noKing/p/8006298.html
218、编写一个截取字符串的函数,输入为一个字符串和字 节数,输出为按字节截取的字符串。 但是要保证汉字不被 截半个,如"我 ABC"4,应该截为"我 AB",输入"我 ABC 汉 DEF",6,应该输出为"我 ABC"而不是"我 ABC+汉的半 个" (中文作为两个字节的情况下)
public static void main(String[] args) { System.out.println(truncateStr(“我ABC”, 4));
System.out.println(truncateStr(“我ABC汉DEF”, 6));
}
public static String truncateStr(String src, int len) { int byteNum = 0;
if (null == src) {
return “”;
}
byteNum = src.length(); byte bt[] = src.getBytes(); if (len > byteNum) {
len = byteNum;
}
String subStrx; if (bt[len] < 0) {
subStrx = new String(bt, 0, --len);
} else {
subStrx = new String(bt, 0, len);
}
return subStrx;
}
219、打印昨天的当前时刻
注意:时间方面要掌握 Date,Calendar,SimpleDateFormat 的用法 另外也可以适当学习三方时间库 jodatime 或者 jdk1.8 中提供的新的时间 API
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); Date date = new Date();
System.out.println(“当前时刻:” + sdf.format(date));
long yesterdayMills = date.getTime() - 24 * 60 * 60 * 1000L; date.setTime(yesterdayMills); System.out.println(“昨天当前时刻:” + sdf.format(date));
220、用 Java 代码实现栈
public class MyStack {
// 栈:遵循后进先出的原则
// 使用数组实现顺序栈
/** 栈中元素的初始化大小 /
// 栈的长度
private int length;
// 栈
private E[] array;
// 栈顶的下标
private int topIndex = -1;
/
StackByArry:构造方法
*
@param length
- 初始化栈的长度
/
public MyStack(int length) { this.length = length;
array = (E[]) new Object[length];
}
/*
push:入栈
@param obj
@throws Exception
*/
public void push(E obj) throws Exception { if (isFullStack()) {
throw new Exception(“栈已满”);
}
/**
}
array[++topIndex] = obj;
- offer: 出栈,取出栈顶元素
@return obj
@throws Exception
*/
public E offer() throws Exception { if (isEmptyStack()) {
throw new Exception(“栈已空”);
}
/**
}
E topObject = array[topIndex]; array[topIndex] = null; topIndex–;
return topObject;
isEmptyStack:判断栈是否为空
*
@return
/
public boolean isEmptyStack() { return (topIndex == -1);
}
/*
isFullStack:判断栈是否已满
*
@return
/
public boolean isFullStack() {
// TODO:栈如满了,可以考虑为栈进行扩容 return topIndex == (length - 1);
}
/*
- getLength:取得栈内的数据长度
- @return length
*/
public int getLength() { return topIndex + 1;
}
@Override
public String toString() {
return Arrays.toString(array);
}
public static void main(String[] args) throws Exception { MyStack stack = new MyStack(10); for (int i = 1; i <= 10; i++) {
stack.push(i);
}
System.out.println(“当前栈:” + stack); System.out.println("---------------"); System.out.println(“出栈元素:” + stack.offer()); System.out.println(“当前栈:” + stack); System.out.println("---------------"); System.out.println(“出栈元素:” + stack.offer()); System.out.println(“当前栈:” + stack); System.out.println("---------------"); System.out.println(“出栈元素:” + stack.offer()); System.out.println(“当前栈:” + stack); System.out.println("---------------"); System.out.println(“出栈元素:” + stack.offer()); System.out.println(“当前栈:” + stack); System.out.println("---------------");
System.out.println(“当前还有:” + stack.getLength() + “个元素”);
}
}