本文主要是介绍2024面试offer收割宝典百度篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.JAVA 中的几种基本数据类型是什么,各自占用多少字节。
- byte: 占用 1 个字节(8位),取值范围是-128到127。
- short:占用 2 个字节(16位),取值范围是-32,768到32,767。
- int : 占用 4 个字节(32位),取值范围是 2^{31} (-2,147,483,648) 到 2^{31-1} (2,147,483,647)。
- long : 占用8个字节(64位),取值范围是-2^{63} (-9,223,372,036,854,775,808) 到 -2^{63-1} (9,223,372,036,854,775,807)。
- float : 占用4个字节(32位),用于存储单精度浮点数。
- double:占用8个字节(64位),用于存储双精度浮点数。
- char : 占用2个字节(16位),用于存储Unicode字符,取值范围从\u0000到\uffff(包括两端)。
- boolean : 在Java虚拟机(JVM)内部,boolean类型的变量不是直接按字节存储的。虽然它没有明确指定其字节数,但在内存中通常会使用1个字节或更高效的方式来表示 true 或 false(具体实现依赖于JVM,但对程序员来说是透明的)。在标准Java语法层面,并不关心boolean类型的确切字节数,因为它的值只有两种可能。
2.String 类能被继承吗,为什么?
String 类是不能被继承的。这是因为 String 类被声明为 final 类。
在Java中,使用 final 关键字修饰的类表示它是不可扩展的,也就是说,你不能从一个final 类派生出新的子类。
设计上将 String 类声明为 final 的原因主要有以下几点:
- 安全性和不变性:String 类型的对象代表不可变的字符序列。由于它的不变性,确保了多线程环境下的安全性以及字符串常量池的高效运作。如果允许继承并修改其行为,可能会破坏这种不变性和安全性。
- 设计决策:Java的设计者为了避免由于继承而带来的潜在问题(例如方法覆盖导致的行为改变),选择将一些核心库中的关键类如 String 设计为不可继承的。
- 性能优化 :虽然现代JVM可能不再强制要求这样做,但早期的Java实现中,编译器可以对 final 类的方法进行内联等优化,提升运行效率。
因此,尽管在技术层面上可以通过创建一个新的类来模仿或扩展 String 类的功能,但在Java语言规范下,直接继承并扩展 String 类是不允许的。
3.String,Stringbuffer,StringBuilder 的区别 ?
Java中的String、StringBuilder和StringBuffer类都是用来处理字符串的,但它们之间存在显著的区别:
1.不可变性 (Immutable)
- String:是不可变的。一旦创建了一个String对象,它的值就不能改变。每次对String进行拼接、替换等操作时,都会创建一个新的String对象,并丢弃旧的对象。因此在大量修改字符串内容的操作中,会产生较多的临时对象,可能造成性能问题。
2. 可变性 (Mutable)
- StringBuilder:是可变的,它允许高效的字符串内容修改。当你需要频繁地对字符串进行修改(比如通过append()方法添加字符或子串)时,使用StringBuilder可以避免不必要的内存分配和拷贝操作,从而提升效率。
- StringBuffer:同样也是可变的,其功能与StringBuilder相似,也支持高效地修改字符串内容。
3. 线程安全性 (Thread Safety)
- String:由于不可变性,多个线程同时读取一个共享的String对象不会出现问题,因此从某种程度上说,在多线程环境下它是安全的。
- StringBuilder:不是线程安全的,如果在多线程环境中不采取任何同步措施直接共享使用同一个StringBuilder对象,可能会导致数据不一致或其他并发问题。
- StringBuffer: 是线程安全的 ,它的所有方法都被synchronized关键字修饰,这意味着在多线程环境下,即使多个线程同时访问和修改StringBuffer对象,也能保证内部状态的一致性和正确性。然而,这种同步机制会带来一定的性能开销,所以在单线程环境或者不需要考虑线程安全的场景下,通常更倾向于使用StringBuilder以获得更好的性能。
总结来说:
- 当你需要构建一个只读且不会修改的字符串时,使用String是最合适的。
- 在单线程环境中,需要频繁修改字符串内容时,推荐使用StringBuilder以提高效率。
- 在多线程环境中,当多个线程需要共同修改同一字符串内容时,应选择StringBuffer来确保线程安全。
4.ArrayList 和 LinkedList 有什么区别。
ArrayList 和 LinkedList 是 Java 中两种常用的动态数组实现的 List 接口的数据结构,它们之间的主要区别在于 底层数据结构、性能特点和内存占用:
1. 底层数据结构:
- ArrayList:基于动态数组实现。它内部维护了一个可扩容的数组,元素在数组中是连续存储的。
- LinkedList:基于双向链表实现。它的每个节点(也称为元素)都包含一个指向前一个节点和后一个节点的引用。
2. 随机访问效率:
- ArrayList:由于其内部是数组结构,支持通过索引进行快速随机访问,时间复杂度为 O(1)。
- LinkedList:不直接支持随机访问,需要从头或尾部开始遍历到指定位置才能访问某个元素,因此随机访问的时间复杂度为 O(n)。
3. 插入和删除操作效率:
- ArrayList:当在列表中间进行插入或删除操作时,尤其是非末尾位置,除了要执行相应的插入或删除动作外,可能还需要移动后续元素来填补空位或者填充新插入元素的位置,所以插入和删除的时间复杂度通常为 O(n)。
- LinkedList:由于链表结构可以方便地改变节点间的连接关系,所以在链表中的插入和删除操作通常更快,尤其是在非首尾位置插入或删除元素时,时间复杂度为 O(1)。
4. 内存占用:
- ArrayList:除了存储元素本身外,还存在一定的空间开销(主要是数组对象),但整体上空间利用率较高,尤其当列表接近满容量时。
- LinkedList:每个元素都需要额外的空间存储前后节点的引用,因此相比 ArrayList,相同数量元素下 LinkedList 的内存占用会更高。
5. 扩容机制:
- ArrayList:当添加元素导致容量不足时,会自动扩容至当前容量的一定倍数(通常是1.5倍),这个过程可能导致较大的系统开销,特别是在大容量场景下。
- LinkedList:不需要考虑扩容问题,因为它可以根据需要动态添加或移除节点。
6. 初始化和空间预估:
- ArrayList:在创建时可以指定初始容量,对于已知大小范围的应用场景,合理预估初始容量可以减少扩容带来的性能损失。
- LinkedList:无需预先指定容量,其容量随着元素的增加而增长。
综上所述,在选择使用 ArrayList 还是 LinkedList 时,应根据应用场景的需求权衡:
如果更关注高效随机访问,则选择 ArrayList;
如果更关注频繁的插入和删除操作,并且对随机访问速度要求不高,则可以选择 LinkedList。
5.讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候,他们的执行顺序。
在Java中,当创建一个类的实例(对象)时,包括父类和子类,初始化顺序遵循以下规则:
1. 父类静态变量:
首先执行的是父类中的静态变量初始化和静态块(static {}),如果有的话。这些静态成员在类加载时就被初始化,并且只会初始化一次。
2. 子类静态变量:
紧接着是子类中的静态变量初始化和静态块。同样地,这些静态成员也是在类加载时只初始化一次,与实例化无关。
3. 父类构造过程:
当调用子类构造函数时,首先会隐式调用父类的无参数构造函数(如果没有显式指定super()语句的话)。如果有带参数的构造函数,则需要通过super(...)明确调用父类构造函数。
在父类构造函数内部,首先进行父类实例变量的初始化,然后执行父类构造函数体中的代码。
4. 父类实例字段:
实例字段是在构造函数执行前由JVM自动完成初始化的,因此无论构造函数内部如何编写,父类实例字段总是先于任何构造器代码执行。
5. 子类实例字段:
在父类构造函数完成后,开始初始化子类的实例字段。
6. 子类构造函数:
最后执行的是子类自身的构造函数体中的代码。
所以整个实例化顺序可以总结为:
- 父类静态变量初始化
- 子类静态变量初始化
- 父类构造函数(包含实例字段初始化)
- 子类构造函数(包含实例字段初始化)
请注意,构造函数内的代码执行顺序,以及字段初始化的顺序,严格遵循它们在源代码中的声明顺序。
6.sql 优化有哪些?如何创建索引?创建索引的原则?索引的优缺点?
SQL优化主要包括以下几个方面:
1.查询语句优化:
- 避免全表扫描,尽可能使用索引字段进行过滤和排序。
- 减少JOIN操作的数量和复杂度,优先考虑内连接,并确保连接字段上有合适的索引。
- 使用EXISTS、IN代替子查询(在某些情况下)。
- 尽量避免在WHERE子句中对NULL值进行条件判断,因为NULL值不走索引。
- 避免在函数内部使用列名,这可能导致无法利用索引。
2.数据表设计优化:
- 数据规范化,减少冗余和更新异常。
- 分区表策略:对于大型表,可以采用分区表技术,将数据分成多个物理部分,从而提高查询效率。
- 选择合适的数据类型,减小存储空间。
3.索引创建与优化:
- 根据查询模式创建索引,主要针对 WHERE、JOIN 和 ORDER BY 子句中的列。
- 创建复合索引(组合索引),按照最频繁的查询顺序排列列。
- 对于经常用于分组和聚合操作的列创建索引。
- 索引不宜过多,过犹不及。过多的索引会占用更多存储空间,影响插入、删除、更新等DML操作性能。
4.创建索引的原则:
- 频繁出现在WHERE子句中的列。
- 主键和唯一性约束列通常需要创建索引。
- 经常参与ORDER BY、GROUP BY、DISTINCT操作的列。
- 复合索引应考虑查询的覆盖范围和查询条件的排列顺序。
- 不应该为很少使用的列或只有少量不同值的列创建索引。
5.索引的优点:
- 提高查询速度,尤其是针对大表的点查询和范围查询。
- 可以减少磁盘I/O,通过B树索引直接定位到所需数据。
- 在排序和分组时,如果能够利用索引,可以大大降低CPU消耗。
6.索引的缺点:
- 占用额外的存储空间,增加数据库大小。
- 创建和维护索引需要时间成本,会影响INSERT、UPDATE和DELETE操作的速度,因为这些操作不仅要修改数据本身,还要维护索引结构。
- 当数据分布极不均匀或者表行数较小的时候,索引可能并不会带来明显的性能提升,反而会拖慢整体性能。
在实际应用中,创建索引需根据业务场景、数据规模和访问模式综合分析,既要考虑查询效率也要考虑更新效率。同时,定期分析数据库运行状况并调整索引策略也是必要的。
7.sql 如何去重?
在SQL中,删除重复数据保留唯一行通常涉及到找到重复记录并确定要保留哪一行。以下是一个通用的步骤,假设我们有一个名为 users 的表,其中包含 id 字段作为主键,并且我们想根据 name 字段去重:
-- 1. 首先找出重复的名字
CREATE TEMPORARY TABLE temp_table AS
SELECT name, MIN(id) as id
FROM users
GROUP BY name HAVING COUNT(*) > 1;-- 2. 确定哪些是重复项(非主键最小值对应的记录)
DELETE u1
FROM users u1
JOIN temp_table t ON u1.name = t.name AND u1.id != t.id;-- 此时,已经删除了除每个名字的第一个出现之外的所有重复项
注意:
- 上述代码假定你想要保留的是具有最小 id 值的那个重复记录。
- 在执行删除操作前,请务必备份你的数据,因为这个操作不可逆。
另外,如果你的数据表很大或者需要考虑性能问题,可以创建一个临时表来存储去重后的结果,然后用临时表替换原始表的内容,以避免大表操作带来的性能压力和锁定问题。
但在许多数据库系统中,直接更新原表可能更为高效,具体取决于你的数据库管理系统以及表的具体情况。
对于没有主键的情况,可以根据实际业务需求选择合适的列作为“保留依据”(如:最新时间、最大或最小的某个字段等)。如果是MySQL 8.0+版本,还可以使用 ROW_NUMBER() 窗口函数来实现更复杂的去重逻辑。
下面是一个使用 ROW_NUMBER() 函数的例子:
WITH CTE AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id) AS row_numFROM users
)
DELETE FROM CTE WHERE row_num > 1;
8.内连接和外连接的区别
内连接(INNER JOIN)和外连接(OUTER JOIN)是SQL查询中两种不同的表关联方式,它们的主要区别在于结果集中包含的数据行的范围。
内连接 (INNER JOIN):
- 内连接返回的是两个或多个表中满足连接条件的记录。如果某一行在所有参与连接的表中都没有匹配项,则不会出现在结果集中。
- 当你在ON子句中指定连接条件时,只有那些在连接列上相匹配的记录才会被包含在结果集里。
SELECT column1, column2
FROM table1
INNER JOIN table2
ON table1.key = table2.key;
外连接:
外连接分为左外连接(LEFT OUTER JOIN / LEFT JOIN)、右外连接(RIGHT OUTER JOIN / RIGHT JOIN)、全外连接(FULL OUTER JOIN)三种类型。
- 左外连接 (LEFT JOIN):返回左表中的所有记录以及与右表中匹配的记录。对于左表中有但右表中没有匹配项的记录,结果集中对应的右表字段将填充NULL值。
SELECT column1, column2FROM table1LEFT JOIN table2ON table1.key = table2.key;
- 右外连接 (RIGHT JOIN):与左外连接相反,返回右表中的所有记录以及与左表中匹配的记录。当右表中有而左表中没有匹配项时,左表字段在结果集中填充NULL值。
SELECT column1, column2FROM table1RIGHT JOIN table2ON table1.key = table2.key;
- 全外连接 (FULL OUTER JOIN):返回左表和右表中所有的记录组合,即使两边不完全匹配。不匹配的记录在另一边的结果中使用NULL填充
SELECT column1, column2FROM table1FULL OUTER JOIN table2ON table1.key = table2.key;
总结:
- 内连接只显示匹配的行;
- 左外连接会显示左表的所有行,即使在右表中找不到匹配项;
- 右外连接则会显示右表的所有行,即使在左表中找不到匹配项;
- 全外连接则会显示左右两表的所有行,并在无法匹配的情况下用NULL填充缺失的一方。
这篇关于2024面试offer收割宝典百度篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!