JAVA基础问题

一、运算符与数据类型

1. &和&&的区别

  • 作为运算符

    • &:按位与运算符,对应每一位进行与运算。
int a = 5; // 二进制:0101
int b = 3; // 二进制:0011
int result = a & b; // 结果:0001,即1
  • 作为逻辑运算符

    • &&:短路与运算符,如果左侧表达式为假,右侧表达式将不执行。
boolean x = false;
boolean y = (x && (5 / 0 > 1)); // 不会抛出异常,因为左侧为false

2. int和Integer的区别

  • int是基本数据类型,直接存储整数值,而Integerint的包装类,是一个对象,包含额外的方法。

  • int的默认值是0,而Integer的默认值是null。

  • 比较时

    • intint可以直接用==判断。
    • intInteger比较时,Integer会自动拆箱为int
    • Integer之间的比较,如果在[-128, 127]范围内可以用==,否则用equals方法。
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;

System.out.println(a == b); // true
System.out.println(c == d); // false
System.out.println(c.equals(d)); // true

3. 接口和抽象类的区别

  • 语法层面

    • 抽象类可以提供方法的实现,而接口只能声明方法。
    • 接口中的成员变量是public static final,而抽象类可以有各种类型的成员变量。
  • 设计层面

    • 抽象类是对类的抽象,接口是对行为的抽象。
  • 选择依据

    • 如果希望定义一些具有默认实现的方法,请选择抽象类。
    • 如果需要支持多重继承,请使用接口。
      // 抽象类Animal,表示动物
      abstract class Animal {
          // 抽象方法sound,子类必须实现该方法
          abstract void sound();
      }
      
      // 接口CanFly,表示能够飞的能力
      interface CanFly {
          // 方法fly,任何实现该接口的类都需要提供具体实现
          void fly();
      }
      
      // 类Bird,继承自Animal并实现CanFly接口
      class Bird extends Animal implements CanFly {
          // 实现Animal类中的抽象方法sound
          void sound() {
              // 输出鸟的叫声
              System.out.println("啾啾");
          }
       
          // 实现CanFly接口中的方法fly
          public void fly() {
              // 输出鸟飞得很高
              System.out.println("飞得很高");
          }
      }
    • 抽象类Animal:定义了动物的基本特性,强制子类实现sound()方法。
    • 接口CanFly:定义了能够飞的能力,要求实现该接口的类提供fly()方法。
    • 类Bird:表示具体的鸟,继承自Animal,并实现CanFly接口,提供了鸟的叫声和飞行的具体实现

二、树的理解

1. 树的由来

树结构结合了数组的高效检索和链表的高效操作,适合需要动态插入和删除的数据结构。树的层次结构使得数据组织更加灵活,能够有效地进行搜索和存取。

2. 树的分类

  • 根据分支数量

    • 二叉树(每个节点最多两个子节点)
    • 多叉树(每个节点可以有多个子节点)
  • 根据节点有序性

    • 查找树(如二叉搜索树):左子树的值小于节点值,右子树的值大于节点值。
    • 无序树:没有特定的顺序。
  • 根据用途

    • 红黑树:自平衡的二叉查找树,保证搜索效率。
    • AVL树:另一种自平衡树,要求左右子树高度差不超过1。

3. 常见树的特点

  • 普通二叉查找树:可能出现倾斜,导致效率下降。
  • AVL树:通过旋转操作保持平衡,查询效率高,适合查找频繁的场景。
  • 红黑树:通过颜色标记保持平衡,适合内存中的数据存储,插入和删除操作更快。

4. TreeMap和HashMap的区别

  • TreeMap是红黑树实现,支持有序操作,查找和插入的时间复杂度为O(log n)。

  • HashMap是通过数组和链表/红黑树实现,通常在性能上更优,查找和插入的时间复杂度为O(1)(在理想情况下)。

Map<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "One");
hashMap.put(2, "Two");

Map<Integer, String> treeMap = new TreeMap<>();
treeMap.put(2, "Two");
treeMap.put(1, "One"); // 会按键的顺序排列

三、HashMap面试汇总

1. jdk8引入红黑树的原因

链表长度增加后,检索效率急剧降低,因此在链表长度超过8时,将其转换为红黑树以提高效率。这种设计可以有效地减少hash冲突,提高性能。

2. 解决hash冲突的方式

  • 当链表长度小于8时,保持链表结构;超过8时,转换为红黑树以提高查询性能。这样可以在高负载情况下保持良好的性能。

3. 默认加载因子0.75的原因

  • 这是时间和空间的平衡,避免过度扩容和hash冲突。加载因子为0.75时,HashMap在达到75%容量时会进行扩容,这样可以在保证性能的同时,合理利用内存。

4. put方法的流程

  1.  首先根据 key 的值计算 hash 值,找到该元素在数组中存储的下标;
  2. 如果数组是空的,则调用 resize 进行初始化;
  3. 如果没有哈希冲突直接放在对应的数组下标里;
  4. 如果冲突了,且 key 已经存在,就覆盖掉 value;
  5. 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;
  6. 如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,链表插入键值对,若 key 存在,就覆盖掉 value。

    public class HashMap<K, V> {
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认初始容量16
        static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认负载因子
        static final int TREEIFY_THRESHOLD = 8; // 链表转红黑树的阈值
        static final int MIN_TREEIFY_CAPACITY = 64; // 最小扩容容量
    
        Node<K, V>[] table; // 哈希表
        int size; // 存储的键值对数量
        int threshold; // 扩容阈值
    
        // 节点类
        static class Node<K, V> {
            final int hash;
            final K key;
            V value;
            Node<K, V> next; // 链表中的下一个节点
    
            Node(int hash, K key, V value, Node<K, V> next) {
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
        }
    
        public HashMap() {
            this.table = (Node<K, V>[]) new Node[DEFAULT_INITIAL_CAPACITY];
            this.threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        }
    
        public V put(K key, V value) {
            if (key == null) {
                return putForNullKey(value);
            }
    
            int hash = hash(key); // 计算hash值
            int index = indexFor(hash, table.length); // 计算存储下标
    
            if (table[index] == null) {
                table[index] = new Node<>(hash, key, value, null);
                if (size++ >= threshold) {
                    resize(); // 判断是否需要扩容
                }
                return null;
            }
    
            Node<K, V> node = table[index];
            while (true) {
                if (node.hash == hash && (node.key.equals(key))) {
                    V oldValue = node.value;
                    node.value = value; // 覆盖旧值
                    return oldValue;
                }
                if (node.next == null) {
                    break;
                }
                node = node.next;
            }
    
            node.next = new Node<>(hash, key, value, null);
            if (nodeCount(index) > TREEIFY_THRESHOLD) {
                if (table.length < MIN_TREEIFY_CAPACITY) {
                    resize(); // 扩容
                } else {
                    treeify(index); // 链表转红黑树
                }
            }
            return null;
        }
    
        // 计算hash值
        static int hash(Object key) {
            int h = key.hashCode();
            return h ^ (h >>> 16); // 高位和低位结合
        }
    
        // 计算存储下标
        static int indexFor(int hash, int length) {
            return hash & (length - 1); // 取模运算
        }
    
        // 计算链表节点数
        private int nodeCount(int index) {
            Node<K, V> node = table[index];
            int count = 0;
            while (node != null) {
                count++;
                node = node.next;
            }
            return count;
        }
    
        // 扩容方法
        private void resize() {
            // 省略扩容具体实现
        }
    
        // 链表转红黑树
        private void treeify(int index) {
            // 省略红黑树转换具体实现
        }
    
        // 处理key为null的情况
        private V putForNullKey(V value) {
            // 处理逻辑
            return null; // 省略具体实现
        }
    }

    代码解析

  7. 计算Hash值:通过hash方法计算key的hash值,并与16位右移后的hash值进行异或操作,以增强hash值的分布性。

  8. 计算存储下标:通过indexFor方法计算将元素存储在数组中的下标。

  9. 处理空数组:如果数组的对应位置为空,直接插入新节点,并判断是否需要扩容。

  10. 处理哈希冲突

    • 遍历链表,如果找到相同的key,则覆盖value
    • 如果当前节点是链表的最后一个节点,则将新节点插入到链表末尾。
  11. 链表转红黑树:当链表的长度超过8时,判断当前数组的容量,如果小于64则扩容,否则将链表转换为红黑树。

  12. 扩容和树化逻辑:在resizetreeify方法中实现扩容和链表转换为红黑树的具体逻辑。

5. 为什么要右移16位?

在HashMap中,hash值通常是32位的整数。右移16位的目的是为了将高16位和低16位结合起来,以减少hash冲突并提高hash值的分布均匀性。通过这种方式,可以更好地利用数组的空间,减少在同一桶中的元素数量,从而提高查找效率。右移与异或操作结合,可以使得hash值的分布更加均匀。

int h = hash(key);
int hashValue = h ^ (h >>> 16); // 右移16位并进行异或

6. 为什么Hash值要与length-1相与?

在HashMap中,数组的长度通常是2的幂。通过与length-1进行与运算,可以有效地将hash值映射到数组的有效索引范围内。这个操作可以利用二进制运算的特性,使得计算更加高效。通过这种方式,hash值可以被快速转换为数组的索引,避免了模运算的开销。

int index = hashValue & (table.length - 1); // 确保索引在有效范围内

四、String和序列化相关问题

1. String的不可变性

  • 不可变性保证了字符串的安全性,避免意外修改影响其他引用。由于字符串是不可变的,多个线程可以安全地共享同一个字符串实例,避免了同步问题。

String str = "Hello";
str.concat(" World"); // 不会改变原来的字符串
System.out.println(str); // 输出 "Hello"

2. char数组比String更适合存储密码

  • char数组可以被清空,降低安全风险,而String一旦创建无法更改,可能会在内存中保留敏感信息。这种设计可以有效避免密码泄露的风险。

char[] password = new char[10];
// 使用后清空
Arrays.fill(password, '\0');

3. Java序列化

将对象转为二进制格式以便存储或传输,使用Serializable接口。序列化可以将对象的状态保存到文件中,便于后续的恢复。

public class User implements Serializable {
    private String name;
    private transient String password; // 不序列化

    // getters and setters
}

4. serialVersionUID的作用

用于验证序列化和反序列化时类版本的一致性,确保反序列化过程中类的版本和序列化时的版本一致,以避免出现类版本不一致导致的问题。

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
}

五、线程相关问题

1. 创建线程的方式

  • 直接继承Thread类。

  • 实现Runnable接口。

  • 实现Callable接口。

  • 通过线程池。

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running");
    }
}

Runnable task = () -> System.out.println("Runnable running");

2. Thread和Runnable的区别

  • Thread是类,而Runnable是接口,多个Thread对象可以执行同一Runnable逻辑,推荐使用Runnable以避免Java单继承的限制。

3. wait和notify的使用

需在同步块中调用,以保证线程持有对象的锁。

synchronized (lock) {
    lock.wait(); // 释放锁并等待
}

synchronized (lock) {
    lock.notify(); // 通知等待线程
}

4. 线程的生命周期

包括新建、就绪、运行、阻塞和死亡状态,理解线程的生命周期有助于进行更有效的线程管理。

5. 线程池的理解

合理利用线程池能够降低资源消耗,提高响应速度,增强可管理性。线程池可以复用线程,避免频繁创建和销毁线程带来的开销。

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {
    System.out.println("Task executed");
});
executor.shutdown();

六、IO相关问题

1. 同步、异步、阻塞、非阻塞的区别

  • 同步阻塞:客户端在发起请求后会等待服务器响应,直到接收到数据才能继续执行后续操作,造成资源的浪费。
  • 同步非阻塞:客户端发起请求后可以继续执行其他操作,服务端在处理请求时不会阻塞客户端。
  • 异步阻塞:客户端不等待响应,而是通过回调函数处理响应数据。
  • 异步非阻塞:客户端和服务端都可以并行处理其他任务,效率高。

2. BIO、NIO、AIO的区别

  • BIO(Blocking I/O):传统的Java I/O,采用阻塞模式,适合小规模的应用。
  • NIO(Non-blocking I/O):引入了非阻塞模式,使用选择器(Selector)来管理多个连接。
  • AIO(Asynchronous I/O):引入了异步非阻塞模式,客户端发起请求后无需等待响应,服务器通过回调处理结果。

3. 字节流和字符流的介绍

  • 字节流:用于处理原始二进制数据,适合所有类型的数据(如图像、音频、视频等)。

  • 字符流:用于处理文本数据,支持字符编码,适合处理文本文件,如txt、csv等。

// 字节流
FileInputStream fis = new FileInputStream("image.jpg");
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
    // 处理字节数据
}
fis.close();

// 字符流
FileReader fr = new FileReader("file.txt");
BufferedReader br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
    System.out.println(line);
}
br.close();

七、JavaWEB面试题

1. 网络编程的本质

网络编程是指在计算机网络中进行程序开发,旨在实现不同计算机之间的数据交换。它基于请求/响应模型,客户端发送请求,服务器处理请求并返回响应。

2. 主要问题解决

  • 主机定位:通过IP地址定位主机,IP地址分为公有IP和私有IP,公有IP可以在互联网上唯一标识一台主机。
  • 数据传输:TCP(传输控制协议)提供可靠的数据传输,确保数据包的顺序和完整性。

3. 网络协议

网络协议是指在计算机之间进行数据交换时所遵循的规则和约定。它确保数据在网络中传输的可靠性和正确性。

  • 常见的网络协议包括:
    • HTTP/HTTPS:用于Web数据传输。
    • FTP:用于文件传输。
    • SMTP:用于电子邮件传输。
    • TCP/IP:基础的网络通信协议,确保数据包的传输。

4. OSI七层与TCP/IP四层的关系

  • OSI模型分为七层:应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。
  • TCP/IP模型简化为四层:应用层、传输层、网络层和链路层。
  • 对应关系:
    • 应用层(OSI层1-7)对应TCP/IP的应用层。
    • 传输层对应TCP/IP的传输层。
    • 网络层对应TCP/IP的网络层。
    • 数据链路层和物理层合并为TCP/IP的链路层。

5. TCP原理

TCP是面向连接的协议,通过三次握手建立连接,确保连接的可靠性。

  • 三次握手

    1. 客户端发送SYN请求,表示希望建立连接。
    2. 服务器回应SYN-ACK,表示同意连接并请求确认。
    3. 客户端发送ACK确认,连接建立。
  • 四次挥手

    1. 客户端发送FIN,表示希望关闭连接。
    2. 服务器回应ACK,确认客户端关闭请求。
    3. 服务器发送FIN,表示也希望关闭连接。
    4. 客户端回应ACK,连接关闭。
  • TCP的优点是提供可靠的数据传输,保证数据的顺序和完整性,适用于需要高可靠性的场景,如Web浏览、文件传输等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值