数组是Java中最基础也是最常用的数据结构之一。作为开发者,熟练掌握数组的各种操作方法能极大提升编码效率。下面我将结合多年开发经验,详细介绍Java数组的核心API及实用技巧。
创建数组是第一步,Java提供了多种初始化方式:
java复制// 声明并初始化
int[] arr1 = {1, 2, 3, 4, 5};
// 先声明后赋值
int[] arr2 = new int[5];
arr2[0] = 10;
// 动态初始化
int size = 5;
int[] arr3 = new int[size];
访问元素时要注意数组边界,常见的ArrayIndexOutOfBoundsException就是越界访问导致的:
java复制// 正确访问方式
int first = arr1[0];
// 错误示例(会抛出异常)
int wrong = arr1[5]; // 数组长度是5,最大索引是4
经验:在访问数组元素前,务必检查索引是否在0到array.length-1范围内
java.util.Arrays类提供了丰富的静态方法,是处理数组的瑞士军刀。下面重点讲解几个核心方法:
Arrays.sort()使用的是经过优化的双轴快速排序算法,时间复杂度为O(n log n):
java复制int[] numbers = {3, 1, 4, 1, 5, 9, 2, 6};
// 全排序
Arrays.sort(numbers); // [1, 1, 2, 3, 4, 5, 6, 9]
// 部分排序(对索引2到5的元素排序)
Arrays.sort(numbers, 2, 6); // [3, 1, 1, 2, 5, 9, 4, 6]
注意:部分排序时toIndex是不包含的,这与String.substring()等方法的约定一致
二分查找的前提是数组已排序,否则结果不可预测:
java复制int[] sorted = {10, 20, 30, 40, 50};
int index = Arrays.binarySearch(sorted, 30); // 返回2
// 查找不存在的元素
int notFound = Arrays.binarySearch(sorted, 35); // 返回-4
返回值解读:
复制数组时,新数组与原数组是独立的对象:
java复制int[] original = {1, 2, 3};
// 完整复制
int[] copy1 = Arrays.copyOf(original, original.length);
// 扩展复制(多余元素填充默认值)
int[] copy2 = Arrays.copyOf(original, 5); // [1, 2, 3, 0, 0]
// 范围复制
int[] copy3 = Arrays.copyOfRange(original, 1, 3); // [2, 3]
性能提示:对于大型数组,System.arraycopy()比Arrays.copyOf()更高效
处理二维及以上数组时,deepToString()和deepEquals()特别有用:
java复制int[][] matrix = {{1, 2}, {3, 4}};
// 打印多维数组
System.out.println(Arrays.deepToString(matrix)); // [[1, 2], [3, 4]]
// 深度比较
int[][] matrix2 = {{1, 2}, {3, 4}};
boolean isEqual = Arrays.deepEquals(matrix, matrix2); // true
字符串操作是日常开发中最频繁的任务之一。Java提供了String、StringBuilder和StringBuffer三种字符串处理类,各有适用场景。
String是不可变对象,所有修改操作都会返回新字符串。
常见的坑:使用==比较字符串内容(应该用equals())
java复制String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1 == s2); // false(比较引用)
System.out.println(s1.equals(s2)); // true(比较内容)
忽略大小写的比较:
java复制String s3 = "Hello";
System.out.println(s1.equalsIgnoreCase(s3)); // true
substring()有两个重载版本:
java复制String text = "Hello, World!";
// 从索引7到末尾
String world = text.substring(7); // "World!"
// 指定范围(包含开始,不包含结束)
String hello = text.substring(0, 5); // "Hello"
注意:substring()创建新字符串会共享原字符串的char[],可能导致内存泄漏。Java 7u6后已修复
split()使用正则表达式分割字符串:
java复制String csv = "apple,orange,banana";
String[] fruits = csv.split(",");
// 限制分割次数
String[] parts = "a:b:c:d".split(":", 2); // ["a", "b:c:d"]
复杂分割示例:
java复制String log = "2023-08-01 14:30:00 [INFO] User logged in";
String[] logParts = log.split("\\s+\\[|\\]\\s+");
// ["2023-08-01", "INFO", "User logged in"]
StringBuilder适用于频繁修改字符串的场景,避免了String的不可变特性带来的性能问题。
java复制StringBuilder sb = new StringBuilder();
sb.append("Hello")
.append(", ")
.append("World!")
.insert(5, " there")
.delete(11, 13);
System.out.println(sb.toString()); // "Hello there, World!"
测试拼接10000次字符串:
java复制// 使用String(产生大量临时对象)
long start = System.currentTimeMillis();
String result = "";
for (int i = 0; i < 10000; i++) {
result += "a";
}
long end = System.currentTimeMillis();
System.out.println("String耗时: " + (end - start) + "ms");
// 使用StringBuilder
start = System.currentTimeMillis();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
builder.append("a");
}
end = System.currentTimeMillis();
System.out.println("StringBuilder耗时: " + (end - start) + "ms");
实测结果:
经验法则:在循环内或频繁修改字符串时,务必使用StringBuilder
replaceAll()和matches()方法支持正则表达式:
java复制String phone = "123-456-7890";
// 验证格式
boolean isValid = phone.matches("\\d{3}-\\d{3}-\\d{4}");
// 替换所有非数字字符
String digits = phone.replaceAll("[^0-9]", ""); // "1234567890"
// 格式化日期
String date = "20230801";
String formatted = date.replaceAll("(\\d{4})(\\d{2})(\\d{2})", "$1/$2/$3"); // "2023/08/01"
Math类提供了各种数学运算的静态方法:
java复制// 基本运算
double max = Math.max(10.5, 20.3); // 20.3
double abs = Math.abs(-15); // 15
// 取整运算
double ceil = Math.ceil(3.2); // 4.0
double floor = Math.floor(3.8); // 3.0
long round = Math.round(3.5); // 4
// 随机数
double random = Math.random(); // [0.0, 1.0)
int dice = (int)(Math.random() * 6) + 1; // 模拟骰子
三种保留小数位的方法对比:
java复制double value = 3.1415926535;
// 方法1:Math.round
double rounded = Math.round(value * 100) / 100.0; // 3.14
// 方法2:DecimalFormat
DecimalFormat df = new DecimalFormat("#.##");
String formatted = df.format(value); // "3.14"
// 方法3:String.format
String strFormatted = String.format("%.2f", value); // "3.14"
选择建议:
- 需要继续计算:用Math.round
- 需要显示输出:用String.format
- 需要复杂格式:用DecimalFormat
Java提供了两种比较对象的方式:Comparable和Comparator。
让类实现Comparable接口定义默认排序:
java复制class Product implements Comparable<Product> {
private String name;
private double price;
// 构造方法等省略...
@Override
public int compareTo(Product other) {
// 按价格排序
return Double.compare(this.price, other.price);
}
}
使用示例:
java复制List<Product> products = Arrays.asList(
new Product("Laptop", 999.99),
new Product("Phone", 699.99),
new Product("Tablet", 399.99)
);
Collections.sort(products); // 自动按价格升序排列
Comparator可以在不修改类的情况下定义多种排序规则:
java复制// 按名称排序
Comparator<Product> byName = Comparator.comparing(Product::getName);
// 按价格降序
Comparator<Product> byPriceDesc = Comparator.comparing(Product::getPrice).reversed();
// 组合排序:先按类别,再按价格
Comparator<Product> complex = Comparator
.comparing(Product::getCategory)
.thenComparing(Product::getPrice);
Java 8的lambda表达式让Comparator更简洁:
java复制// 传统写法
Comparator<Product> oldWay = new Comparator<Product>() {
@Override
public int compare(Product p1, Product p2) {
return p1.getName().compareTo(p2.getName());
}
};
// Lambda写法
Comparator<Product> newWay = (p1, p2) -> p1.getName().compareTo(p2.getName());
| 场景 | 选择 | 理由 |
|---|---|---|
| 类有自然排序规则 | Comparable | 作为类的默认排序方式 |
| 需要多种排序方式 | Comparator | 灵活定义不同排序规则 |
| 不能修改类源码 | Comparator | 外部定义比较逻辑 |
| 需要组合排序 | Comparator | 使用thenComparing()链式调用 |
实际项目中,我建议:
java复制// 不好:需要多次扩容
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
// 好:一次分配足够空间
List<Integer> optimizedList = new ArrayList<>(1000);
java复制int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];
// 高效复制
System.arraycopy(src, 0, dest, 0, src.length);
java复制// 反模式
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次循环都创建新String对象
}
// 正确做法
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
builder.append(i);
}
String optimizedResult = builder.toString();
java复制// 预估最终长度约10000
StringBuilder sb = new StringBuilder(10000);
java复制// 每次调用都重新编译正则
String output = input.replaceAll("\\s+", "");
// 更好:预编译正则
private static final Pattern WHITESPACE = Pattern.compile("\\s+");
String optimizedOutput = WHITESPACE.matcher(input).replaceAll("");
ArrayIndexOutOfBoundsException:总是检查数组访问边界
NullPointerException:数组或字符串为null时调用方法
java复制String[] array = null;
System.out.println(array.length); // NPE
String str = null;
System.out.println(str.length()); // NPE
java复制// 可能在不同平台表现不同
byte[] bytes = "中文".getBytes();
// 明确指定编码
byte[] safeBytes = "中文".getBytes(StandardCharsets.UTF_8);
java复制// 危险:多线程共享
private static SimpleDateFormat unsafeFormat = new SimpleDateFormat("yyyy-MM-dd");
// 安全方案1:每次创建新实例
SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd");
// 安全方案2:使用ThreadLocal
private static final ThreadLocal<SimpleDateFormat> safeFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
java复制StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.add("Apple").add("Orange").add("Banana");
String result = joiner.toString(); // "[Apple, Orange, Banana]"
java复制int[] numbers = {1, 2, 3, 4, 5};
// 求平均值
double avg = Arrays.stream(numbers).average().orElse(0);
// 过滤和收集
int[] evens = Arrays.stream(numbers)
.filter(n -> n % 2 == 0)
.toArray();
java复制String str = " Hello Java 11 ";
// 去除首尾空白
String trimmed = str.strip(); // 比trim()更严格
// 判断空白字符串
boolean isBlank = " ".isBlank(); // true
// 按行分割
String lines = "Line1\nLine2\nLine3";
List<String> lineList = lines.lines().collect(Collectors.toList());
java复制var list = new ArrayList<String>(); // 自动推断为ArrayList<String>
var map = new HashMap<Integer, String>();
java复制public Map<String, Integer> wordFrequency(String text) {
if (text == null || text.isBlank()) {
return Collections.emptyMap();
}
return Arrays.stream(text.toLowerCase().split("\\W+"))
.filter(word -> !word.isEmpty())
.collect(Collectors.groupingBy(
word -> word,
Collectors.summingInt(word -> 1)
));
}
java复制public List<Product> parseProducts(Path csvFile) throws IOException {
return Files.lines(csvFile)
.skip(1) // 跳过标题行
.map(line -> line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)")) // 处理含逗号的字段
.filter(fields -> fields.length >= 3)
.map(fields -> new Product(
fields[0].trim(),
Double.parseDouble(fields[1].trim()),
fields[2].trim()
))
.collect(Collectors.toList());
}
java复制// 按多个条件排序员工:部门、薪资降序、入职时间
Comparator<Employee> complexComparator = Comparator
.comparing(Employee::getDepartment)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparing(Employee::getHireDate);
List<Employee> sortedEmployees = employees.stream()
.sorted(complexComparator)
.collect(Collectors.toList());
官方文档:
书籍推荐:
在线练习:
在多年的Java开发中,我发现数组和字符串处理有以下几个关键点:
理解不可变性:String的不可变特性既是优点也是性能陷阱,要根据场景选择合适的类
掌握工具类:Arrays和Collections等工具类能极大简化代码,要熟悉其常用方法
注意边界条件:数组越界、空指针、编码问题等是常见错误来源
性能敏感处优化:在循环或高频调用处使用StringBuilder、预分配数组等优化手段
保持代码可读性:合理使用Java 8 Stream和Lambda表达式,但不要过度复杂化
最后分享一个实用技巧:在处理复杂字符串操作时,可以先用正则表达式测试工具(如regex101.com)验证正则表达式的正确性,再写入代码,能节省大量调试时间。