1. 简单说下什么是跨平台
术语:操作系统指令集、屏蔽系统之间的差异
系统梳理了 Java 基础面试高频考点,包括跨平台原理、八种基本数据类型、面向对象四大特征、包装类型机制、String 类特性、集合框架对比、常见异常处理及多线程创建方式。通过源码分析与代码示例,深入讲解 HashMap、ArrayList、线程池等核心知识点,助力求职者夯实基础。

术语:操作系统指令集、屏蔽系统之间的差异
由于各种操作系统所支持的指令集不是完全一致,所以在操作系统之上加个虚拟机可以来提供统一接口,屏蔽系统之间的差异。
有八种基本数据类型。
| 数据类型 | 字节 | 默认值 |
|---|---|---|
| byte | 1 | 0 |
| short | 2 | 0 |
| int | 4 | 0 |
| long | 8 | 0 |
| float | 4 | 0.0f |
| double | 8 | 0.0d |
| char | 2 | '\u0000' |
| boolean | 4 | false |
各自占用几字节也记一下。
面向对象的编程语言有封装、继承、抽象、多态等 4 个主要的特征。
术语:让基本类型也具有对象的特征
| 基本类型 | 包装器类型 |
|---|---|
| boolean | Boolean |
| char | Character |
| int | Integer |
| byte | Byte |
| short | Short |
| long | Long |
| float | Float |
| double | Double |
为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型 Collection 时就一定要使用包装类型而非基本类型)因为容器都是装 object 的,这是就需要这些基本类型的包装器类了。
自动装箱:new Integer(6);,底层调用:Integer.valueOf(6)
自动拆箱:int i = new Integer(6);,底层调用 i.intValue(); 方法实现。
Integer i = 6;
Integer j = 6;
System.out.println(i == j);
答案在下面这段代码中找:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
== 较的是两个引用在内存中指向的是不是同一对象(即同一内存空间),也就是说在内存空间中的存储位置是否一致。如果两个对象的引用相同时(指向同一对象时),"=="操作符返回 true,否则返回 false。equals 用来比较某些特征是否一样。我们平时用的 String 类等的 equals 方法都是重写后的,实现比较两个对象的内容是否相等。我们来看看 String 重写的 equals 方法:
它不止判断了内存地址,还增加了字符串是否相同的比较。
public boolean equals(Object anObject) {
//判断内存地址是否相同
if (this == anObject) {
return true;
}
// 判断参数类型是否是 String 类型
if (anObject instanceof String) {
// 强转
String anotherString = (String)anObject;
int n = value.length;
// 判断两个字符串长度是否相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 一一比较 字符是否相同
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
java 中 String、StringBuffer、StringBuilder 是编程中经常使用的字符串类,他们之间的区别也是经常在面试中会问到的问题。现在总结一下,看看他们的不同与相同。
String 底层使用一个不可变的字符数组 private final char value[]; 所以它内容不可变。StringBuffer 和 StringBuilder 都继承了 AbstractStringBuilder 底层使用的是可变字符数组:char[] value;StringBuilder 是线程不安全的,效率较高;而 StringBuffer 是线程安全的,效率较低。通过他们的 append() 方法来看,StringBuffer 是有同步锁,而 StringBuilder 没有:
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
StringBuilder 与 StringBuffer 有公共父类 AbstractStringBuilder。
最后,操作可变字符串速度:StringBuilder > StringBuffer > String,这个答案就显得不足为奇了。
set 根据 equals 和 hashcode 判断,一个对象要存储在 Set 中,必须重写 equals 和 hashCode 方法
之前专门有写过 ArrayList 和 LinkedList 源码的文章。
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
}
}
执行上段代码是有问题的,会抛出 ConcurrentModificationException 异常。
原因:调用 list.remove() 方法导致 modCount 和 expectedModCount 的值不一致。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
解决办法:在迭代器中如果要删除元素的话,需要调用 Iterator 类的 remove 方法。
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove(); //注意这个地方
}
}
}
相同点:
不同点:
什么是 fail-fast? 就是最快的时间能把错误抛出而不是让程序执行。
Java 5 提供了 ConcurrentHashMap,它是 HashTable 的替代,比 HashTable 的扩展性更好。
ConcurrentHashMap 将整个 Map 分为 N 个 segment(类似 HashTable),可以提供相同的线程安全,但是效率提升 N 倍,默认 N 为 16。
HashMap 可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);
答案:字节流
字节流:传递的是字节(二进制),
字符流:传递的是字符
我们并不支持下载的文件有没有包含字节流 (图片、影像、音源),所以考虑到通用性,我们会用字节流。
这个之前自己做过总结,也算比较全面。
public class CreatThreadDemo1 extends Thread{
/**
* 构造方法:继承父类方法的 Thread(String name);方法
* @param name
*/
public CreatThreadDemo1(String name){
super(name);
}
@Override
public void run() {
while (!interrupted()){
System.out.println(getName()+"线程执行了...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CreatThreadDemo1 d1 = new CreatThreadDemo1("first");
CreatThreadDemo1 d2 = new CreatThreadDemo1("second");
d1.start();
d2.start();
d1.interrupt(); //中断第一个线程
}
}
常规方法,不多做介绍了,interrupted 方法,是来判断该线程是否被中断。(终止线程不允许用 stop 方法,该方法不会施放占用的资源。所以我们在设计程序的时候,要按照中断线程的思维去设计,就像上面的代码一样)。
public class CreatThreadDemo2 implements Runnable {
@Override
public void run() {
while (true){
System.out.println("线程执行了...");
}
}
public static void main(String[] args) {
//将线程任务传给线程对象
Thread thread = new Thread(new CreatThreadDemo2());
//启动线程
thread.start();
}
}
Runnable 只是来修饰线程所执行的任务,它不是一个线程对象。想要启动 Runnable 对象,必须将它放到一个线程对象里。
public class CreatThreadDemo3 extends Thread{
public static void main(String[] args) {
//创建无参线程对象
new Thread(){
@Override
public void run() {
System.out.println("线程执行了...");
}
}.start();
//创建带线程任务的线程对象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行了...");
}
}).start();
//创建带线程任务并且重写 run 方法的线程对象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable run 线程执行了...");
}
}){
@Override
public void run() {
System.out.println("override run 线程执行了...");
}
}.start();
}
}
创建带线程任务并且重写 run 方法的线程对象中,为什么只运行了 Thread 的 run 方法。我们看看 Thread 类的源码,可以看到 Thread 实现了 Runnable 接口,而 Runnable 接口里有一个 run 方法。所以,我们最终调用的重写的方法应该是 Thread 类的 run 方法。而不是 Runnable 接口的 run 方法。
public class CreatThreadDemo4 implements Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CreatThreadDemo4 demo4 = new CreatThreadDemo4();
FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask 最终实现的是 runnable 接口
Thread thread = new Thread(task);
thread.start();
System.out.println("我可以在这里做点别的业务逻辑...因为 FutureTask 是提前完成任务");
//拿出线程执行的返回值
Integer result = task.get();
System.out.println("线程中运算的结果为:"+result);
}
//重写 Callable 接口的 call 方法
@Override
public Object call() throws Exception {
int result = 1;
System.out.println("业务逻辑计算中...");
Thread.sleep(3000);
return result;
}
}
Callable 接口介绍:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
返回指定泛型的 call 方法。然后调用 FutureTask 对象的 get 方法得道 call 方法的返回值。
public class CreatThreadDemo5 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时器线程执行了...");
}
},0,1000); //延迟 0,周期 1s
}
}
public class CreatThreadDemo6 {
public static void main(String[] args) {
//创建一个具有 10 个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
long threadpoolUseTime = System.currentTimeMillis();
for (int i=0;i<10;i++){
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行了...");
}
});
}
long threadpoolUseTime1 = System.currentTimeMillis();
System.out.println("多线程用时"+(threadpoolUseTime1-threadpoolUseTime));
//销毁线程池
threadPool.shutdown();
threadpoolUseTime = System.currentTimeMillis();
}
}
lambda 表达式不懂的,可以看看我的 java8 新特性文章。
public class CreatThreadDemo7 {
public static void main(String[] args) {
List<Integer> values = Arrays.asList(10,20,30,40);
//parallel 平行的,并行的
int result = values.parallelStream().mapToInt(p -> p*2).sum();
System.out.println(result);
//怎么证明它是并发处理呢
values.parallelStream().forEach(p-> System.out.println(p));
}
}
输出:
200 40 10 20 30
怎么证明它是并发处理呢,他们并不是按照顺序输出的。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online