计算机学习笔记 Java基础与底层原理详解② Java运算符、字符串相关方法
课程复习笔记Java运算符、字符串与IO详解本次课程内容非常扎实从底层的位运算原理出发深入剖析了Java中字符串的不可变性及其优化方案并讲解了控制台输入输出的处理技巧。第一部分运算符与移位运算移位运算是计算机底层非常高效的运算方式常用于替代乘除法以提升性能。1. 核心概念补码运算底层原理计算机内部所有数据的运算包括移位都是基于补码进行的而不是原码。原码与补码原码是人类阅读的表示方式最高位为符号位补码是计算机存储和运算的方式。正数的原码、反码、补码相同负数的补码是原码符号位不变其余位取反加1。2. 移位运算详解左移 ()规则符号位保持不变数值位整体向左移动低位补0高位溢出的部分被丢弃。例子4 2。4的二进制0000 0100左移2位0001 0000(即16)计算验证$4 \times 2^2 16$。有符号右移 ()规则符号位保持不变数值位整体向右移动。高位空缺部分补符号位正数补0负数补1低位溢出的部分被丢弃。例子16 2结果为 4-16 2结果为 -4。无符号右移 ()规则不考虑符号位整体向右移动高位空缺部分统一补0。特点负数经过无符号右移后会变成很大的正数。3. 移位运算的高级应用快速乘法利用移位运算和加法可以实现任意整数的快速乘法底层乘法器的原理。原理将乘数拆解为2的幂次方之和二进制拆分。优势对于万亿级别的大数运算二进制加法次数远少于十进制乘法效率极高。4. 运算符优先级与结合性优先级括号() 自增自减 算术运算 移位运算 比较运算 逻辑运算 赋值运算。结合性大多数从左到右赋值运算从右到左。第二部分Java字符串深度解析字符串是Java开发中最常用的对象理解其底层机制对性能优化至关重要。1. String的不可变性与内存机制定义String类被final修饰且内部字符数组也是final的。一旦创建其内容无法改变。内存机制字符串常量池直接使用字面量赋值如String s abc时JVM会先检查常量池中是否有该字符串。若有直接引用若无创建并放入池中。堆内存使用new String(abc)时会在堆内存中强制创建一个新的对象即使常量池中已存在。拼接陷阱使用拼接字符串时底层会创建StringBuilder调用append最后调用toString()这会再次创建一个新的String对象。频繁拼接会产生大量垃圾对象。2. 空串与Null的区别空串 ()是一个有效的String对象长度为0指向内存空间常量池或堆可以调用方法。Null不指向任何对象没有分配内存调用方法会抛出NullPointerException。3. String常用方法详解与代码示例equals(Object anObject)用法比较两个字符串的内容是否完全相同。这是字符串比较的正确方式而比较的是内存地址。代码示例String s1 hello; String s2 new String(hello); System.out.println(s1 s2); // false地址不同 System.out.println(s1.equals(s2)); // true内容相同compareTo(String anotherString)用法按字典顺序比较两个字符串。返回值为两个字符串第一个不同字符的ASCII码差值。如果完全相同返回0。代码示例String s1 abc; String s2 abd; System.out.println(s1.compareTo(s2)); // -1 (c - d)indexOf(String str)用法返回指定子字符串在此字符串中第一次出现处的索引。如果未找到返回-1。代码示例String s hello world; System.out.println(s.indexOf(world)); // 6 System.out.println(s.indexOf(java)); // -1lastIndexOf(String str)用法返回指定子字符串在此字符串中最右边出现处的索引。代码示例String s hello hello; System.out.println(s.lastIndexOf(hello)); // 6length()用法返回此字符串的长度字符数。代码示例String s Java; System.out.println(s.length()); // 4substring(int beginIndex, int endIndex)用法截取字符串包含起始索引不包含结束索引。代码示例String s 012345; System.out.println(s.substring(1, 4)); // 123replace(CharSequence target, CharSequence replacement)用法将此字符串中所有出现的指定字符序列替换为另一个。注意这会返回一个新的字符串。代码示例String s hello; String newS s.replace(l, p); System.out.println(s); // hello (原字符串不变) System.out.println(newS); // heppotoUpperCase()/toLowerCase()用法将字符串中的所有字符转换为大写或小写。代码示例String s Hello; System.out.println(s.toUpperCase()); // HELLOtrim()用法返回一个新字符串其值为此字符串并删除了首尾的空白字符空格、制表符等。代码示例String s hello world ; System.out.println(s.trim()); // hello worldcharAt(int index)用法返回指定索引处的char值。代码示例String s abc; System.out.println(s.charAt(1)); // b字符串.split( 按固定值分割字符串String s dog cat cat dog; String[] words s.split( ); // 结果words [dog, cat, cat, dog]把字符串转成数组toCharArray()char[] sArr s.toCharArray();方法用法说明代码示例toCharArray()将字符串转为 char 数组abc.toCharArray()→[a,b,c]getBytes()将字符串转为字节数组可指定编码你好.getBytes(UTF-8)toLowerCase()转小写你只整理了 toUpperCaseHELLO.toLowerCase()→hellostrip()(Java 11)去除首尾空白支持 Unicode 空白比 trim 更准确 hello .strip()→hellostripLeading/Trailing()(Java 11)仅去除首部/尾部空白 hi .stripLeading()→hi repeat(int count)(Java 11)重复拼接字符串 n 次ab.repeat(3)→abababindent(int n)(Java 12)每行缩进 n 个空格并追加换行符hi.indent(4)→ hi\n方法用法说明代码示例contains(CharSequence)判断字符串是否包含指定子串返回 booleanhello.contains(ell)→truestartsWith(String)判断是否以指定前缀开头hello.startsWith(he)→trueendsWith(String)判断是否以指定后缀结尾hello.endsWith(lo)→trueisEmpty()判断字符串长度是否为 0.isEmpty()→trueisBlank()(Java 11)判断字符串是否为空或仅含空白字符 \t .isBlank()→trueequalsIgnoreCase(String)忽略大小写比较内容是否相同Hello.equalsIgnoreCase(hello)→truematches(String regex)判断字符串是否匹配正则表达式123.matches(\\d)→true4. 可变字符串StringBuilder与StringBuffer底层结构基于字符数组实现默认初始容量为16。当容量不足时自动扩容通常是原容量的2倍2。性能优势直接在原数组上修改避免了频繁创建新对象。区别StringBuilderJDK 1.5引入非线程安全效率高适用于单线程。StringBufferJDK 1.0引入线程安全方法加了synchronized效率稍低适用于多线程。5. StringBuilder/StringBuffer常用方法详解append(...)用法将各种类型的数据字符串、数字、字符等追加到序列的末尾。这是最常用的方法。代码示例StringBuilder sb new StringBuilder(Hello); sb.append( ).append(World).append(123); System.out.println(sb.toString()); // Hello World123StringBuilder sb new StringBuilder(World); sb.insert(0, Hello ); System.out.println(sb); // 输出Hello WorldtoString()用法将当前可变字符串对象转换为一个不可变的String对象。调用此方法会创建一个新的String对象。代码示例StringBuilder sb new StringBuilder(test); String s sb.toString(); // 创建一个新的String对象第三部分输入输出与格式化1. 输出 (Output)Java 提供了多种方式来将数据打印到控制台主要区别在于是否换行以及是否支持格式化。System.out.println(...): 输出内容后自动换行。System.out.print(...): 输出内容后不换行光标停留在当前行末尾。System.out.printf(...): 格式化输出用法类似于 C 语言的printf函数。它不会自动换行需要手动添加%n或\n。常用格式化占位符占位符说明示例%d十进制整数System.out.printf(数量: %d, 10);%f浮点数System.out.printf(价格: %.2f, 19.9);(保留两位小数)%s字符串System.out.printf(姓名: %s, 张三);%x十六进制整数System.out.printf(十六进制: %x, 255);%n换行符 (平台无关)System.out.printf(第一行%n第二行);代码示例public class OutputExample { public static void main(String[] args) { int count 100; double price 123.456; String product Java课程; // 1. println: 输出后换行 System.out.println(--- 使用 println ---); System.out.println(商品: product); System.out.println(数量: count); System.out.println(价格: price); // 输出多位小数 // 2. print: 输出后不换行 System.out.println(--- 使用 print ---); System.out.print(商品: product , ); System.out.print(数量: count , ); System.out.println(价格: price); // 最后手动换行 // 3. printf: 格式化输出 System.out.println(--- 使用 printf ---); // %s: 字符串, %d: 整数, %.2f: 保留两位小数的浮点数, %n: 换行 System.out.printf(商品: %s, 数量: %d, 价格: %.2f元%n, product, count, price); // 演示宽度和对齐 (8表示总宽度为8个字符右对齐) System.out.printf(数字对齐演示: [%8d]%n, 123); System.out.printf(数字对齐演示: [%-8d]%n, 123); // -表示左对齐 } }2. 输入 (Input)在 Java 中通常使用java.util.Scanner类来获取用户的控制台输入。创建 Scanner 对象import java.util.Scanner; // 导入 Scanner 类 public class InputExample { public static void main(String[] args) { // 创建 Scanner 对象System.in 代表标准输入流键盘 Scanner sc new Scanner(System.in); // ... 使用 sc 对象读取输入 ... sc.close(); // 使用完毕后关闭 Scanner释放资源 } }常用读取方法方法说明nextInt()读取一个int类型的整数。nextDouble()读取一个double类型的浮点数。next()读取下一个单词以空格、制表符或换行符作为分隔符。nextLine()读取一整行文本以换行符作为分隔符。hasNext()判断是否还有下一个输入 token返回boolean值。代码示例与“回车符吞噬”问题这是一个非常经典且重要的问题。当nextInt()、nextDouble()等方法与nextLine()混用时nextLine()可能会“跳过”输入。问题重现import java.util.Scanner; public class ScannerBugExample { public static void main(String[] args) { Scanner sc new Scanner(System.in); System.out.print(请输入您的年龄: ); int age sc.nextInt(); // 1. 用户输入 25 然后按回车 System.out.print(请输入您的姓名: ); String name sc.nextLine(); // 2. 程序“跳过”了这一步 System.out.println(年龄: age , 姓名: name); // 姓名是空字符串 sc.close(); } }问题解析用户输入25并按下回车键输入缓冲区的内容是25\n。sc.nextInt()读取了数字25但没有读取后面的换行符\n。此时缓冲区还剩下\n。程序执行到sc.nextLine()它的作用是读取一整行直到遇到换行符。它立刻发现了缓冲区里残留的\n于是认为一行已经读取完毕返回一个空字符串。这就造成了“跳过输入”的假象。解决方案在nextInt()之后额外调用一次sc.nextLine()来“吃掉”这个残留的换行符。import java.util.Scanner; public class ScannerFixedExample { public static void main(String[] args) { Scanner sc new Scanner(System.in); System.out.print(请输入您的年龄: ); int age sc.nextInt(); // --- 解决方案增加一行 sc.nextLine() 来吸收回车符 --- sc.nextLine(); // ---------------------------------------------------- System.out.print(请输入您的姓名: ); String name sc.nextLine(); // 现在可以正常输入了 System.out.println(年龄: age , 姓名: name); sc.close(); } }第四部分方法性能测试在算法优化中我们需要量化代码的执行时间。1. 测试原理利用系统当前时间戳毫秒级的差值来计算代码段的运行耗时。2. 具体实现代码public class PerformanceTest { public static void main(String[] args) { // 1. 获取开始时间戳 long startTime System.currentTimeMillis(); // 2. 执行需要测试的代码例如大量字符串拼接 StringBuilder sb new StringBuilder(); for (int i 0; i 10000; i) { sb.append(test); } String result sb.toString(); // 3. 获取结束时间戳 long endTime System.currentTimeMillis(); // 4. 计算并输出耗时 System.out.println(代码执行耗时: (endTime - startTime) 毫秒); } }