Java 基础 - API

API

API: 应用程序编程接口, 即已经写好的东西, 可以直接使用

String

  • 字符串的内容是不会更改的
String name = "abc";
name = "def";
// name = "def" 是创建了一个新的字符串, 然后把引用赋给了 name
  • 构建方法
String s = "abc"; // 直接赋值
String s = new String(); // 创建一个空字符串
String s = new String("abc"); // 根据传递的字符串内容构建一个新字符串
char[] chs = {'a', 'b', 'c'};
String s = new String(chs); // 根据字符数组
byte[] bytes = {97, 98, 99, 100};
String s = new String(bytes); // 根据字节数组
  • 使用双引号直接赋值时, 如果该字符串在串池中已存在, 会复用字符串
String s1 = "abc";
String s2 = "abc";
// s1, s2 指向同一个串池中的地址
  • 使用 new 创建时, 每次 new 会在堆内存中开辟一个新空间, 不会 复用
char[] chs = {'a', 'b', 'c'};
String s1 = new String(chs);
String s2 = new String(chs);
// s1, s2 指向不同的堆内存中的地址
  • 字符串比较

    • == 比较基本数据类型时, 比较数据值; 比较引用数据类型时, 比较地址值
      因此对于 String, 比较的是 地址值

    • s1.equals(s2) 比较内容, 完全一样才是 true

    • s1.equalsIgnoreCase(s2) 比较内容, 忽略大小写

  • 访问字符 s1.charAt(i)

  • 转成字符数组 char[] chs = s1.toCharArray();

StringBuilder

可以看成一个容器, 创建之后里面的内容可以改变
因此拼接字符串时, 不会每次产生一个新的字符串, 提高效率

StringBuilder s = new StringBuilder("abc");
s.append("def");
s.reverse();
int len = s.length();
String str = s.toString();

StringJoiner

StringJoiner s = new StringJoiner(间隔符号, 开始符号, 结束符号);
e.g. StringJoiner s = new StringJoiner("---", "(", ")");
s.add("abc"); // 应该是在中间添加数据
int len = s.length();
String str = s.toString();

System

System.exit(0); // 虚拟机正常停止, 非 0 是异常停止
long l = System.currentTimeMillis(); // 从时间原点到运行这行代码的时间毫秒值形式
// 可以两行代码之间相减检查中间代码的运行时间
System.arraycopy(arr1, 0, arr2, 0, 10);
// System.arraycopy(数据源数组(被拷贝), 从数据源第几个索引开始拷贝, 目的地数组, 目的地索引, 拷贝的个数);
// 若 arr1, arr2 都是基本数据类型, 则类型需要一样
// 若 arr1, arr2 都是引用数据类型, 则子类数组可以把地址值拷贝给父类数组

Runtime

Runtime r1 = Runtime.getRuntime(); // 获取 Runtime 对象, 地址值固定(final)
r1.exit(0); // 停止虚拟机
int cpuProcessors = r1.availableProcessors(); // 获取 CPU 的线程数
long maxM = r1.maxMemory(); // JVM 能从系统获取总内存大小(单位 byte)
long totM = r1.totalMemory(); // JVM 已从系统获取总内存大小(单位 byte)
long freeM = r1.freeMemory(); // JVM 剩余内存(单位 byte)
r1.exac("写 cmd 命令");

Object

toString

Student stu = new Student();
String s = stu.toString(); // 返回对象的字符串表示形式(地址值)
sout(s); // 等同于 sout(stu);
// 可以重写 toString 方法以打印对象的属性信息

equals

Student s1 = new Student();
Student s2 = new Student();
boolean result = s1.equals(s2); // 比较地址值, 为 false
// 可以重写 equals 方法以比较对象的属性信息
// String.equals() 先判断参数是否为字符串, 是字符串再比较内容
// StringBuilder.equals() 继承 Object 的, 比较地址值

clone

clone 需要重写, 并让类实现 Cloneable 接口

  • e.g. User

    public class User implements Cloneable { // 注意 Cloneable
    // ...
    @Override
    public String toString() {
    return name + ", " + id;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException { // 注意 throws CloneNotSupportedException
    // 调用父类中的 clone 方法
    return super.clone();
    }
    }
  • Cloneable 这个接口中没有抽象方法, 表示当前接口是一个标记行接口

  • 实现 Cloneable 表示当前类的对象可以被克隆

  • e.g.

    public static void main(String[] args) throws CloneNotSupportedException { // 注意 throws CloneNotSupportedException
    User u1 = new User("zhangsan", "123");
    User u2 = (User)u1.clone(); // 注意强转
    // 会调用重写的 toString
    System.out.println(u1);
    System.out.println(u2);
    }
  • 浅克隆

    • 基本数据类型: 完全拷贝数据值
    • 引用数据类型: 完全拷贝地址值
    • Object.clone() 是浅克隆
  • 深克隆

    • 基本数据类型: 完全拷贝数据值
    • 字符串: 复用
    • 引用数据类型: 重新创建新的
    • 重写 clone 以深克隆 e.g.
    // 假设 User 中有一个成员为 data 数组
    @Override
    protected Object clone() throws CloneNotSupportedException {
    int[] data = this.data;
    int[] newData = new int[data.length];
    for (int i = 0; i < data.length; i++) {
    newData[i] = data[i];
    }
    User u = (User)super.clone(); // 浅克隆
    u.data = newData; // 对引用数据类型的成员特别处理
    return u;
    }
    • 实际开发中, 一般使用序列化再反序列化
    • 使用第三方工具如 Jackson 或者 Gson, 将对象转换为 JSON 字符串, 再从 JSON 字符串重建新的对象

Objects

一个对象工具类, 注意不是 Object, 因为有 s

boolean flag = Objects.isNull(对象);
boolean flag = Objects.nonNull(对象);
boolean flag = Objects.equals(对象1, 对象2);
  • isNullnonNull 相反
  • equals 会先作非空判断, 再比较两个对象

BigInteger

BigInteger 是 Java 提供的高精度计算类, 可以很方便地解决高精度问题。

初始化

import java.io.PrintWriter;
import java.math.BigInteger;
class Main {
static PrintWriter out = new PrintWriter(System.out);
public static void main(String[] args) {
// 将字符串以十进制的形式创建 BigInteger 对象
BigInteger a = new BigInteger("12345678910");
out.println(a); // 12345678910
// 将字符串以指定进制的形式创建 BigInteger 对象
BigInteger b = new BigInteger("1E", 16);
out.println(b); // 30
// 获取一个范围为 0 ~ 2^31-1 的随机数
BigInteger c = new BigInteger(31, new Random());
out.println(c); // 一个范围为 0 ~ 2^31-1 的随机数
// 参数不超过 long, 静态方法获取对象, 内部有优化
// 会将 -16 ~ 16 先创建好 BigInteger 对象, 多次获取不会重新创建
BigInteger d = BigInteger.valueOf(100);
out.println(d); // 100
out.close();
}
}

对象一旦创建, 内部记录的值不能发生改变

基本运算

以下均用 this 代替当前 BigIntger :

函数名 功能
abs() 返回 this 的绝对值
negate() 返回 - this
add(BigInteger val) 返回 this + val
subtract(BigInteger val) 返回 this - val
multiply(BigInteger val) 返回 this * val
divide(BigInteger val) 返回 this / val
remainder(BigInteger val) 返回 this % val
divideAndRemainder(BigInteger val) 返回一个数组, 包含 this / val 和 this % val
mod(BigInteger val) 返回 this mod val
pow(int e) 返回 this^e
and(BigInteger val) 返回 this & val
or(BigInteger val) 返回 this
not() 返回 ~ this
xor(BigInteger val) 返回 this ^ val
shiftLeft(int n) 返回 this << n
shiftRight(int n) 返回 this >> n
max(BigInteger val) 返回 this 与 val 的较大值
min(BigInteger val) 返回 this 与 val 的较小值
bitCount() 返回 this 的二进制中不包括符号位的 1 的个数
bitLength() 返回 this 的二进制中不包括符号位的长度
getLowestSetBit() 返回 this 的二进制中最右边的位置
compareTo(BigInteger val) 比较 this 和 val 值大小
toString() 返回 this 的 10 进制字符串表示形式
toString(int radix) 返回 this 的 raidx 进制字符串表示形式
intValue(BigInteger val) 转为 int 类型, 超出范围数据出错

BigDecima

  • 用于小数的精确运算
  • 用来表示很大的小数

正则表达式

  • 校验字符串是否满足规则
  • 在一段文本中查找满足要求的内容

如何书写

字符类

代码 范围
[abc] a,b,c
[^abc] 除 a,b,c
[a-zA-z] a-z, A-Z
[a-d[m-p]] a-d, m-p
[a-z&&[def]] d,e,f
[a-z&&[^bc]] a, d-z
[a-z&&[^m-p]] a-l, q-z

预定义字符(只匹配一个字符)

字符 范围
. 任意字符
\d 数字
\D 非数字
\s 一个空白字符
\S 非空白字符
\w 英文, 数字, 下划线
\W 一个非单词字符(非"英文, 数字, 下划线")

练习

[[D:/java_code/regex/Test.java|手机,座机,邮箱]]

插件

any-rule, 支持 idea, vscode

爬虫

基础爬取和有条件的爬取

String str = "Java有Java8,Java11,还有Java17.";
// 基础知识
{
// 获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
// 获取文本匹配器的对象
Matcher m = p.matcher(str);
// 寻找. 如果有, 在底层记录子串的起始索引和结束索引+1
boolean b = m.find();
System.out.println(b);
// 方法会根据 find 记录的索引进行字符串的截取
String s = m.group();
System.out.println(s);
// 再寻找. 如果有, 在底层记录第二个子串的起始索引和结束索引+1
b = m.find();
System.out.println(b);
// 方法会根据 find 记录的第二个子串的索引进行字符串的截取
s = m.group();
System.out.println(s);
// 因此应该用 while
while (m.find()) {
System.out.println(m.group());
}
}
// 有条件的爬取
// 爬取忽略大小写的 Java8, Java11, Java17 但只取出 Java
{
Pattern p = Pattern.compile("(?i)Java(?=8|11|17)");
Matcher m = p.matcher(str);
while (m.find()) {
System.out.println(m.group());
}
}
// 爬取 Java8, Java11, Java17 并取出版本号
{
Pattern p = Pattern.compile("(?i)Java(?:8|11|17)");
Matcher m = p.matcher(str);
while (m.find()) {
System.out.println(m.group());
}
}
// 爬取除了 Java8, Java11, Java17 的 Java 文本
{
Pattern p = Pattern.compile("(?i)Java(?!8|11|17)");
Matcher m = p.matcher(str);
while (m.find()) {
System.out.println(m.group());
}
}

贪婪爬取与非贪婪爬取

暂略

捕获分组和非捕获分组

暂略

时间相关类

暂略

包装类

用一个对象, 把基础数据类型包起来

byte -> Byte
short -> Short
char -> Character
int -> Integer
long -> Long
float -> Float
double -> Double
boolean -> Boolean

创建

public static void main(String[] args) {
Integer i1 = new Integer(1);
Integer i2 = new Integer("1");
Integer i3 = Integer.valueOf(123);
Integer i4 = Integer.valueOf("123");
Integer i5 = Integer.valueOf("123", 8);
sout(i5); // 8 进制的 123 -> 10 进制的 83
// valueOf 会获取提前创建的 -128 ~ 127 的对象
Integer i6 = Integer.valueOf(127);
Integer i7 = Integer.valueOf(127);
sout(i6 == i7); // true
}

JDK5 以后可以自动装箱, 自动拆箱, 赋值和运算直接写即可

Integer i1 = 10;
Integer i2 = 20;
Integer i3 = i1 + i2;
int i4 = i1;

Integer 成员方法

String str1 = Integer.toBinaryString(100); // 把整数转成 2 进制
String str2 = Integer.toOctalString(100); // 把整数转成 8 进制
String str3 = Integer.toHexString(100); // 把整数转成 16 进制
int i = Integer.parseInt("123"); // 字符串类型的整数转成 int
// (8 种包装类, 除 Character 都有对应的 parseXxx 方法)

Arrays

操作数组的工具类

int[] a = {1, 2, 3};
String s = Arrays.toString(a); // 数组拼接成字符串
int pos = Arrays.binarySearch(a, 2); // 二分查找
int[] b = Arrays.copyOf(a, 10); // 拷贝数组(原数组, 新数组长度)
int[] c = Arrays.copyOfRange(a, 1, 2); // 拷贝数组, 指定范围
Arrays.fill(a, 0); // 填充数组
Arrays.sort(a); // 默认方式排序数组
Arrays.sort(a, cmp); // 指定方式排序数组

cmp 举例

Integer[] arr = {2, 3, 1, 5, 6, 7, 8, 4, 9};
// 底层原理:
// 插入排序 + 二分查找
// 认为前面有序, 后面无序
// 遍历无序序列, 记当前遍历元素为 o1
// 向有序序列中插入 o1, 先利用二分查找确定 o1 的插入点
// 比较 o1 与当前二分到的插入点的元素 o2 的规则为 compare
Arrays.sort(arr, new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2; // 升序排序
}
// 返回值:
// 负数: 表示当前要插入的元素是"小"的, 放在前面
// 正数: 表示当前要插入的元素是"大"的, 放在前面
// 0 : 表示当前要插入的元素和现在的元素一样, 也会放在后面
});

集合

单列集合: Collection, 双列集合: Map

Java 集合对应 C++ STL

集合类不能直接存储基础数据类型, 因为其设计用于存储对象, 只能存储对应的包装类

List<Integer> a = new ArrayList<>(); // vector
List<Integer> a = new LinkedList<>(); // list
Set<Integer> s = new HashSet<>(); // unordered_set
Set<Integer> s = new TreeSet<>(); // set
Map<String, Interger> mp = new HashMap<>(); // unordered_map
Map<String, Interger> mp = new TreeMap<>(); // map
Stack<Integer> st = new Stack<>(); // stack
Queue<Integer> q = new LinkedList<>(); // queue
Deque<Integer> q = new ArrayDeque<>(); // deque
PriorityQueue<Integer> q = new PriorityQueue<>(); // priority_queue

Collection

单列集合的祖宗接口(是个接口, 不能直接创建其对象, 只能创建实现类的对象)

Collection<String> coll = new ArrayList<>();
coll.add("123"); // 向 set 中添加元素, 元素已存在时返回 false
coll.clear();
coll.remove("123"); // 删除成功返回 true
bool flag1 = coll.contains("123"); // 底层依赖 equals 进行判断, 如果集合存储自定义对象, 需要重写 equals 以比较对象属性而非地址值
bool flag2 = coll.isempty();
int size = coll.size();

遍历

迭代器遍历

Collection<String> coll = new ArrayList<>();
Iterator<String> it = coll.iterator(); // 默认指向 0 索引对应的位置
while (it.hasNext()) {
String str = it.next(); // it.next() 可以大概理解为 C++ 中 *(it++), 返回当前对应的元素, 并移动指针
sout(str);
}
  • 迭代器遍历时, 不能使用集合的增加或删除操作, 但可以 it.remove() 在遍历时删除元素

增强 for 遍历

for(String s : coll) {
s = "xxx";
}
// 修改增强 for 中的变量, 不会改变集合中原本的数据

Lambda 遍历

// 匿名内部类
// 底层遍历每一个元素, 传递给 accept 方法
coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
sout(s);
}
});
// Lambda
coll.forEach(s -> sout(s));

List

List<Integer> list = new ArrayList<>();
list.add(1);
list.remove(0); // 移除 0 索引元素
Integer i = Integer.valueOf(1);
list.remove(i); // 移除元素 1
// 上面两个 remove, 本质是方法调用时如果有重载现象, 优先调用实参和形参一致的那个方法
List<String> list = new ArrayList<>();
list.add("abc");
String s = list.set(0, "123"); // 返回被修改的元素 abc, 并修改 0 索引上元素为 123
s = list.get(0); // 123
  • 列表迭代器: 可以遍历集合时添加元素
    ListIterator<String> it = list.listIterator(); // 默认指向 0 索引
    while (it.hasNext()) {
    String s = it.next();
    if ("bbb".equals(s)) {
    it.add("qqq");
    }
    }

泛型

泛型: 可以在编译阶段约束操作的数据类型, 并进行检查
格式: <数据类型>
注意: 泛型只支持引用数据类型

  • 如果没有泛型, 可以给集合添加任意类型的元素, 默认为 Object, 不能使用类的特有行为
  • Java 的泛型是伪泛型
  • 指定泛型的具体类型后, 可以传入其子类类型

泛型类

编写一个类, 如果不确定类型, 可以定义为泛型类

修饰符 class 类名<类型> {}

比如

// E 表示类型不确定, 可以写成 T, K, V
public class MyArrayList<E> {
Object[] obj = new Object[10];
int size;
public boolean add(E e) {
obj[size] = e;
size++;
return true;
}
public E get(int index) {
return (E)obj[index];
}
@Override
public String toString() {
return Arrays.toString(obj);
}
}

泛型方法

修饰符 <类型> 返回值类型 方法名(类型 变量名) {}

比如

public static <E> void addAll(ArrayList<E> list1, ArrayList<E> list2) {
if (list2 == null) {
return;
}
list2.forEach(e -> list1.add(e));
}
public static <E> void addAll(ArrayList<E> list, E...e) {
for (E element : e) {
list.add(element);
}
}

泛型接口

修饰符 interface 接口名<类型> {}

比如

public interface List<E> extends Collection<E> {
}
  • 两种使用方式
    • 实现类给出具体类型
    public class MyArrayList implements List<String> {
    }
    • 实现类延续泛型, 创建实现类对象时再确定泛型
    public class MyArrayList implements List<E> {
    }

注意

泛型不具备继承性, 但是数据具备继承性
(泛型里面写什么类型, 那么只能传递什么类型的数据)

通配符

如何不确定类型, 但又想限定类型的一定 范围, 需要使用通配符

// 可以传入继承 xxx 类的数据(包括 xxx)
public static void method(ArrayList<? extends xxx> list) {
}
// 可以传入是 xxx 类父类的数据(包括 xxx)
public static void method(ArrayList<? super xxx> list) {
}

Hash

hashCode

  • 哈希值是根据 Objrct.hashCode() 计算得到的 int 类型整数
  • 默认使用地址值计算
  • 一般要重写 hashCode, 使用对象内部的属性值计算哈希值
@Override
public int hashCode() {
return Objects.hash(name, age);
}
  • 哈希表存储自定义对象要重写 equalshashCode

LinkedHashSet

相较 HashSet, 可以保证数据存取有序(底层使用双链表记录添加顺序)

Tree

默认排序

  • 默认排序/自然排序: Javabean 类实现 Comparable 接口, 重写里面的抽象方法, 指定比较规则
public class Student implements Comparable<Student> {
private int age;
public Student() {
}
public Student(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public iint compareTo(Student o) {
return this.getAge() - o.getAge();// 只看年龄, 升序排序
}
// this 表示当前要添加的元素
// o 表示当前比较的已经在红黑树存在的元素
// 返回值:
// 负数: 要添加元素"小", 存左边
// 正数: 要添加元素"大", 存右边
// 0: 要添加元素已经存在, 去重舍弃
}

比较器排序

  • 比较器排序: 创建 TreeSet 对象时, 传递比较器 Comparator 指定规则
// 当前需求: 先按长度排序, 再按字典序排序
// String 自带的字典序排序方法不满足当前需求, 采用比较器排序
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int diff = o1.length() - o2.length();
return diff == 0 ? o1.compareTo(o2) : diff;
}
});
// Comparator 是一个函数式接口, 可以 Lambda
TreeSet<String> ts = new TreeSet<>((o1, o2) -> {
int diff = o1.length() - o2.length();
return diff == 0 ? o1.compareTo(o2) : diff;
});

Map

键值对: Entry

Map<String, String> mp = new HashMap<>();
mp.put("1", "b");
mp.put("1", "c"); // put 添加/覆盖(如果键已经存在, 会把原有的键值对象覆盖, 返回被覆盖的值, 此处返回 "b"; 添加返回 null)
mp.remove("1"); // 根据键删除, 并返回值 "c"
mp.clear();
boolean flag1 = mp.containsKey("1");
boolean flag2 = mp.containsValue("2");
boolean tag = mp.isEmpty();
int size = mp.size();
// 遍历1
Set<String> keys = mp.keySet(); // 获取所有的键
for (String key : keys) {
String value = mp.get(key); // key -> value
}
// 遍历2
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
}
// 遍历3
mp.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
sout(key + "=" + value);
}
});
mp.forEach((key, value) -> sout(key + "=" + value));

HashMap

  • 存储自定义对象(作为键)需要重写 hashCodeequals

  • [[D:/java_code/student_hash/Student.java|Student]]

LinkedHashMap

相较 HashMap, 可以保证数据存取有序(底层使用双链表记录添加顺序)

TreeMap

  • 可对键排序

  • 存储自定义对象(作为键)需要在类中重写 compareTo

    // 先按年龄升序, 再按姓名字典序
    @Override
    public int compareTo(Student o) {
    int diff = this.getAge() - o.getAge();
    return diff == 0 ? this.getName().compareTo(o.getName()) : diff;
    }

Collections

集合工具类

addAll, shuffle, sort, binarySearch, copy, fill, max/min, swap

不可变集合

可变参数: 底层是一个数组, 只不过不需要我们创建

public static int getSum(int...args) {
int sum = 0;
for (int i : args) {
sum += i;
}
return sum;
}
  • 方法的形参中, 只能写一个可变参数
  • 如果有可变参数, 还有其他的参数, 可变参数要写在最后

不可变集合: 不能添加, 不能删除, 不能修改, 只能查询

// 除 Map 外使用了可变参数
List<String> list = List.of("abc", "def");
Set<String> set = Set.of("abc", "def"); // 区别于 List.of, Set.of 里面的参数需要唯一
Map<String, String> map = Map.of("a", "1", "b", "2"); // a->1, b->2; Map 参数最多 20 个, 即 10 个键值对

Stream 流

ArrayList<String> list = new ArrayList<>();
// 输出以 a 为开头且长度为 3 的元素
list.stream().filter(name -> name.startswith("a")).filter(name -> name.length() == 3).forEach(name -> sout(name));
  • 先获取一条流, 把数据放上去
  • 利用 Stream 流的 API 进行操作
    • 过滤, 转换 中间方法
    • 统计, 打印 终结方法

获取

  • 单列集合
    ArrayList<String> list = new ArrayList<>();
    Collectinos.addAll(list, "a", "b", "c");
    Stream<String> streamList = list.stream();
    streamList.forEach(s -> sout(s));
  • 双列集合
    mp.keySet().stream().forEach(s -> sout(s));
    mp.entrySet().stream().forEach(s -> sout(s));
  • 数组
    int[] arr = {1, 2, 3};
    Arrays.stream(arr).forEach(s -> sout(s));
  • 一堆同类型的零散数据
    Stream.of(1, 2, 3).forEach(s -> sout(s));
    // Stream.of 也可以传递数组
    // 基本数据类型数组: 会将整个数组当成一个元素, 打印地址值
    // 引用数据类型数据: 打印元素

中间方法

  • filter - 过滤
  • limit - 获取前几个元素
  • skip - 跳过前几个元素
  • distinct - 元素去重, 依赖 hashCode, equals
  • concat(Stream a, Stream b) - 合并两个流为一个流
  • Stream<R> map(Functino<T, R> mapper) - 转换流中的数据类型
  • 注意
    • 中间方法返回新的 Stream 流, Stream 流只能用一次, 建议链式编程
    • 修改 Stream 流中的数据, 不会影响原来集合或数组中的数据

终结方法

  • 输出
list.stream().forEach(s -> sout(s));
  • 统计
long cnt = list.stream.count();
  • 收集

    • toArray()
    Object[] arr = list.stream().toArray();
    list.stream().toArray(new IntFunction<? extends Object[]>() {
    @Override
    public Object[] apply(int value) {
    return new Object[0];
    }
    });
    // e.g.
    String[] arr = list.stream().toArray(new IntFunction<String[]>() {
    @Override
    public String[] apply(int value) {
    return new String[value];
    }
    });
    // Lambda
    String[] arr = list.stream().toArray(value -> new String[value]);
    • collect()

      • List
      List<String> newList = list.stream()
      .filter(s -> "男".equals(s.split("-")[1]))
      .collect(Collectors.toList()); // 流中的数据: "xxx-男-18"
      • Map (注意选取的键不能重复)
      Map<String, Integer> map = list.stream()
      .filter(s -> "男".equals(s.split("-")[1]))
      .collect(Collectors.toMap(
      // 参数1 键的生成规则 <流中的数据类型, 键的数据类型>
      // apply 参数是流中的数据类型, 返回值类型为键的数据类型
      new Function<String, String>() {
      @Override
      public String apply(String s) {
      return s.split("-")[0]; // "xxx"
      }
      },
      // 参数2 值的生成规则 <流中的数据类型, 值的数据类型>
      // apply 参数是流中的数据类型, 返回值类型为值的数据类型
      new Function<String, Integer>() {
      @Override
      public Integer apply(String s) {
      return Integer.parseInt(s.split("-")[2]); // 18
      }
      })); // 流中的数据: "xxx-男-18"
      // Lambda
      Map<String, Integer> map = list.stream()
      .filter(s -> "男".equals(s.split("-")[1]))
      .collect(Collectors.toMap(
      s -> s.split("-")[0],
      s -> Integer.parseInt(s.split("-")[2])
      ));

File

  • File 对象表示路径, 可以是文件, 也可以是文件夹

  • 路径可以存在, 也可以不存在

  • 绝对路径带盘符, 相对路径默认在当前项目下找

  • 三种构造方法

    String path = "D:\\temp\\temp.txt";
    File f = new File(path);
    sout(f); // "D:\temp\temp.txt"
    String parent = "D:\\temp";
    String child = "temp.txt";
    File f = new File(parent, child);
    sout(f); // "D:\temp\temp.txt"
    File parent = new File("D:\\temp");
    String child = "temp.txt";
    File f = new File(parent, child);
    sout(f); // "D:\temp\temp.txt"

判断, 获取

isDirectory - 判断是否为文件夹
isFile - 判断是否为文件
exists - 判断是否存在
length - 返回文件大小 (字节数量)
getAbsolutePath - 返回绝对路径
getPath - 返回定义文件时使用的路径
getName - 返回文件的名称, 带后缀
lastModified - 返回文件的最后修改时间 (时间毫秒值)

创建, 删除

createNewFile - 创建一个新的空的文件
mkdir - 创建单极文件夹
mkdirs - 创建多级文件夹
delete - 删除文件或空文件夹 (不走回收站)

获取并遍历

public File[] listFiles() 获取当前路径下所有内容, 包含文件, 文件夹和隐藏文件

  • 路径不存在/路径是文件/需要权限才能访问的文件夹, 返回 null
  • 空文件夹 - 返回长度为 0 的数组
File[] arr = File.listRoots(); // 获取系统中所有的盘符
String[] arrString = f.list(); // 获取当前路径下所有内容
File[] arrFile = f.listFiles(); // 获取当前路径下所有内容
String[] arrString = f.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
File src = new File(dir, name);
return src.isFile() && name.endsWith(".txt");
}
});
File[] arrFile = f.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isFile() && pathname.getName().endsWith(".txt");
}
});
File[] arrFile = f.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
File src = new File(dir, name);
return src.isFile() && name.endsWith(".txt");
}
});

IO 流

用于读写文件中的数据(可以读写文件, 或网络中的数据...)

  • 分类
    • 方向
      • 输入流 - 读取
      • 输出流 - 写出
    • 操作文件
      • 字节流 - 所有类型文件
      • 字符流 - 纯文本文件
        • word, excel 不是纯文本文件
        • txt, md 是纯文本文件

字节流

OutputStream

  • FileOutputStream
    • 操作本地文件的字节输出流, 可以把程序中的数据写到本地文件
    • 步骤
      • 创建对象
      • 写数据
      • 释放资源
    public static void main(String[] args) throws IOException {
    FileOutputStream fos = new FileOutputStream("flow\\temp\\a.txt");
    fos.write(97); // a
    fos.close();
    }
    • 创建对象的参数是字符串也可以是 File 对象
    FileOutputStream fos = new FileOutputStream("flow\\temp\\a.txt");
    FileOutputStream fos = new FileOutputStream(new File("flow\\temp\\a.txt"));
    • 如果文件不存在会创建一个新的文件, 但要保证父级路径存在
    • 如果文件已存在, 会清空文件
    • write 写入整数参数在 ASCII 上对应的字符
    // a
    fos.write(97);
    // 97
    fos.write(57);
    fos.write(55);
    • 不释放资源, Java 会占用文件, 无法删除
    • 写出数据
      • 传入 byte 数组
      byte[] bytes = {97, 98, 99, 100};
      fos.write(bytes); // abcd
      byte[] bytes = {97, 98, 99, 100};
      fos.write(bytes, 2, 2); // 起始索引, 数量 -> cd
      • 获取 byte 数组
      String str = "abc";
      byte[] arr = str.getBytes(); // [97, 98, 99]
      • 换行
      String str1 = "abc";
      byte[] arr1 = str1.getBytes();
      fos.write(arr1);
      // windows: \r\n
      // Linux: \n
      // Mac: \r
      String wrap = "\r\n";
      byte[] arrWrap = wrap.getBytes();
      fos.write(arrWrap);
      String str2 = "123";
      byte[] arr2 = str2.getBytes();
      fos.write(arr2);
      fos.close();
      • 续写
      FileOutputStream fos = new FileOutputStream("flow\\temp\\a.txt", true);

InputStream

  • FileInputStream
    • 操作本地文件的字节输入流, 可以把本地文件中的数据读取到程序中来
    • 步骤
      • 创建对象
      • 读数据
      • 释放资源
    public static void main(String[] args) throws IOException {
    FileInputStream fis = new FileInputStream("flow\\temp\\a.txt");
    int b1 = fis.read();
    System.out.println((char)b1); // a
    fis.close();
    }
    • 如果文件不存在, 直接报错
    • 一次读一个字节, 读出来的是数据对应 ASCII 码
    • 如果读不到, read 会返回 -1
    • 循环读取
    FileInputStream fis = new FileInputStream("flow\\temp\\a.txt");
    int b;
    while ((b = fis.read()) != -1) {
    sout((char)b);
    }
    fis.close();

文件拷贝

  • 小文件
FileInputStream fis = new FileInputStream("D:\\temp\\movie.mp4");
FileOutputStream fos = new FileOutputStream("flow\\copy.mp4");
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
// 先开的后关闭
fos.close();
fis.close();

注意: 先开的后关闭

  • 大文件
    • 一次读取一个字节太慢
    • 可以一次读一个字节数组, 每次读取会尽可能把数组装满
    • 数组大小一般是 1024 的整数倍, 比如 1024 * 1024 * 5 即 5 兆 (1024 字节 为 1kb, 1024kb 为 1兆)
    FileInputStream fis = new FileInputStream("xxx");
    byte[] bytes = new byte[1024 * 1024 * 5];
    int len = fis.read(bytes); // 存到 bytes 中, 返回读到了多少个; 没读到返回 -1
    fis.close();
    • 数据是每次从前往后覆盖 bytes 数组, 因此数组中可能有上次的数据没有被覆盖, 直接 sout(new String(bytes)); 可能有误, 可以采用 sout(new String(bytes, 0, len));
FileInputStream fis = new FileInputStream("D:\\temp\\movie.mp4");
FileOutputStream fos = new FileOutputStream("flow\\copy.mp4");
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
// 先开的后关闭
fos.close();
fis.close();

IO 流中捕获异常

需要执行 close, 但可能 close 前面的代码就有异常, 跳转到 catch 导致 close 没有执行

  • finally
try {
FileOutputStream fos = new FileOutputStream("xxx");
} catch () {
} finally {
// finally 中的代码一定被执行, 除非虚拟机停止
fos.close();
}
  • 文件拷贝
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D:\\temp\\movie.mp4");
fos = new FileOutputStream("flow\\cpoy.mp4");
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
} finally {
if (fos != null) { // 否则会空指针异常
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 简化方案
    • JDK7
    // 只有实现了 AutocCloseable 的类, 才可以在小括号中创建对象
    try (FileInputStream fis = new FileInputStream("D:\\temp\\movie.mp4");
    FileOutputStream fos = new FileOutputStream("flow\\copy.mp4")) {
    int len;
    byte[] bytes = new byte[1024 * 1024 * 5];
    while ((len = fis.read(bytes)) != -1) {
    fos.write(bytes, 0, len);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    • JDK9
    FileInputStream fis = new FileInputStream("D:\\temp\\movie.mp4");
    FileOutputStream fos = new FileOutputStream("flow\\copy.mp4");
    try (fis; fos) {
    int len;
    byte[] bytes = new byte[1024 * 1024 * 5];
    while ((len = fis.read(bytes)) != -1) {
    fos.write(bytes, 0, len);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

字符集

  • 计算机

    • 任意数据都是以二进制的形式存储的
    • 最小的存储单元是一个字节
  • ASCII 字符集中一个英文占一个字节

a -> 97 (0110 0001), 一个字节 8
  • GBK 字符集: 包含国家标准 GB13000-1 中全部中日韩汉字和 BIG5 中所有汉字

  • Unicode 字符集: 国际标准字符集

  • 简体中文版 Windows 默认使用 GBK

    • 一个英文一个字节存储
    • 一个汉字两个字节存储; 高位字节二进制一定以 1 开头, 转成十进制之后是一个负数
    • 第一位是 0/1 区分 英文/汉字
  • Unicode 万国码

    • UTF-8 编码规则: 用 1~4 个字节保存
      • ASCII - 1 个字节
        0xxxxxxx
      • 叙利亚文 - 2 个字节
        110xxxxx 10xxxxxx
      • 中东文字(包含简体中文) - 3 个字节
        1110xxxx 10xxxxxx 10xxxxxx
      • 其他语言 - 4 个字节
        11110xxx 10xxxxxx 10xxxxxx
    汉 -> 27721 (01101100 01001001)
    1110xxxx 10xxxxxx 10xxxxxx
    0110 110001 001001 <- 01101100 01001001
    11100110 10110001 10001001
  • 为什么有乱码

    • 读取数据未读完整个汉字: 字节流一次读一个字节, 而中文有 3 个字节
    • 编码和解码方式不统一
  • 防止乱码

    • 不要用字节流读取文本文件
    • 编码解码用一个码表, 一个编码方式
  • Java 编码

public byte[] getBytes() 默认方式编码 UTF-8
public byte[] getBytes(String charsetName) 指定方法编码
  • Java 解码
String(byte[] bytes) 默认方式解码
String(byte[] bytes, String charsetName) 指定方式解码

字符流

字符流 = 字节流 + 字符集

输入流: 一次读一个字节, 遇到中文一次读多个字节
输出流: 底层把数据按照指定的编码方式编码, 变成字节再写到文件中

Reader

  • FileReader
    • public int read() 读取之后, 底层会进行解码转成十进制, 并返回这个十进制; 十进制表示在字符集上的数字
    FileReader fr = new FileReader("xxx");
    int ch;
    while ((ch = fr.read()) != -1) {
    System.out.print((char)ch);
    }
    fr.close();
    • public int read(char[] buffer) 一次读取多个, 然后解码并强转, 返回字符到字符数组中
    char[] chars = new char[2];
    int len;
    while ((len = fr.read(chars)) != -1) {
    System.out.print(new String(chars, 0, len));
    }
    fr.close();

Writer

  • FileWriter
    • write
    void write(int c) // 根据字符集, 把整数转成字符
    void write(String str)
    void write(String str, int off, int len)
    void write(char[] cbuf)
    void write(char[] cbuf, int off, int len)

练习 - 拷贝文件夹

// 递归
// src 拷贝来源, dest 拷贝到的位置
private static void copydir(File src, File dest) throws IOException {
dest.mkdirs();
File[] files = src.listFiles();
if (files == null) {
// ...
}
for (File file : files) {
if (file.isFile()) {
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest, file.getName()));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
} else {
copydir(file, new File(dest, file.getName()));
}
}
}

缓冲流

缓冲流自带长度为 8192 的缓冲区
Bufferedxxx 把基本流 xxx 包装成高级流, 提高读取数据的性能

字节缓冲流

public BufferedInputStream(InputStream is)
public BufferedOutputStream(OutputStream os)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("xxx"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("xxx"));
// 一次读取一个
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
// 一次读取多个
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read()) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
bis.close();

字符缓冲流

public BufferedReader(Reader r)
public BufferedWriter(Writer w)
  • BufferedReader 特有方法
    public String readLine() // 读一行至回车停止, 但不会读入回车
  • BufferedWriter 特有方法
    public void newLine() // 跨平台的换行

转换流

字符流和字节流之间的桥梁

  • 利用转换流按照指定字符编码读取

    InputStreamReader isr = new InputStreamReader(new FileInputStream("xxx"), "GBK");
    int ch;
    while ((ch = isr.read()) != -1) {
    System.out.print((char)ch);
    }
    isr.close();
    // JDK11 后, 被替代为:
    FileReader fr = new FileReader("xxx", Charset.forName("GBK"));
    int ch;
    while ((ch = fr.read()) != -1) {
    System.out.print((char)ch);
    }
    fr.close();
    // 输出流同理
  • 将本地 GBK 文件转成 UTF-8

    InputStreamReader isr = new InputStreamReader(new FileInputStream("flow\\temp\\a.txt"), "GBK");
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("flow\\temp\\a.txt"), "UTF-8");
    int b;
    while ((b = isr.read()) != -1) {
    osw.write(b);
    }
    osw.close();
    isr.close();
    // 替代为:
    FileReader fr = new FileReader("flow\\temp\\a.txt", Charset.forName("GBK"));
    FileWriter fw = new FileWriter("flow\\temp\\a.txt", Charset.forName("UTF-8"));
    int b;
    while ((b = fr.read()) != -1) {
    fw.write(b);
    }
    fw.close();
    fr.close();

序列化流/对象操作输出流

把 Java 中的对象写到本地文件中

// 构造方法
public ObjectOutputStream(OutputStream out)
// 成员方法
public final void writeObject(Object obj)
  • 用对象输出流把对象保存到文件会出现 NotSerializableException 异常
  • 需要让 Javabean类 实现 Serializalbe 接口
// Serializable 里面没有抽象方法, 为标记型接口 (比如还有 Cloneable)
// 表示这个类可以被序列化
public class Student implements Serializable {
private String name;
private int age;
}

反序列化流/对象操作输入流

把序列化到本地文件中的对象读取到程序中来

// 构造方法
public ObjectInputStream(InputStream in)
// 成员方法
public Object readObject()

序列化流和反序列化流细节

(1) 对 Student 修改后 (比如加了一个 private String ID), Javabean类 的版本号改变, 如果反序列化修改前的序列化的对象就会出错

解决方案: 固定版本号

  1. 自己设定版本号
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
}
  1. 设置 idea 以自动生成版本号

(2) 有的信息不想序列化

解决方案: transient 关键字

public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// transient 瞬态关键字
// 不会把当前属性序列化到本地
private transient String address;
}

(3) 序列化流写道文件中的数据不能修改, 一旦修改就无法再次读回来了

打印流

  • 只操作文件目的地, 不操作数据源
  • 特有的写出方法可以实现 数据原样写出
  • 特有的写出方法可以实现 自动刷新, 自动换行
    • 打印一次数据 = 写出 + 换行 + 刷新

字节打印流

PrintStream ps = new PrintStream(new FileOutputStream("xxx"), true, Charset.forName("UTF-8"));
ps.println(97); // 原样写出 97
ps.println(true); // 原样写出 true
ps.print("%s和%s", "a", "b"); // 写出 a和b
ps.close();

字符打印流

PrintWriter pw = new PrintWriter(new FileWriter("xxx"), true);
pw.println(97); // 原样写出 97
pw.println(true); // 原样写出 true
pw.print("%s和%s", "a", "b"); // 写出 a和b
pw.close();

特殊的打印流

PrintStream ps = System.out;
// 标准输出流, 不能关闭, 在系统中唯一
// 这个打印流在虚拟机启动时由虚拟机创建, 默认指向控制台
ps.println("123");

解压缩流

压缩包中的每一个文件在 Java 中为一个 ZipEntry 对象, 解压的本质就是把每一个 ZipEntry 按照层级拷贝到本地另一个文件夹里

public static void main(String[] args) throws IOException {
File src = new File("D:\\temp.zip");
File dest = new File("D:\\");
unzip(src, dest);
}
public static void unzip(File src, File dest) throws IOException {
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
if (entry.isDirectory()) {
File file = new File(dest, entry.toString());
file.mkdirs();
} else {
FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
int b;
while ((b = zip.read()) != -1) {
fos.write(b);
}
fos.close();
zip.closeEntry();
}
}
zip.close();
}

压缩流

  • 压缩文件
public static void toZip(File src, File dest) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, "a.zip")));
ZipEntry entry = new ZipEntry("a.txt");
zos.putNextEntry(entry); // 放到包里
FileInputStream fis = new FileInputStream(src);
int b;
while ((b = fis.read()) != -1) {
zos.write(b);
}
fis.close();
zos.closeEntry();
zos.close();
}
  • 压缩文件夹
public static void main(String[] args) throws IOException {
File src = new File("D:\\aaa");
File destParent = src.getParentFile(); // D:\\
File dest = new File(destParent, src.getName() + ".zip");
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
toZip(src, zos, src.getName()); // name 传入 aaa
zos.close();
}
public static void toZip(File src, ZipOutputStream zos, String name) throws IOException {
File[] files = src.listFiles();
for (File file : files) {
if (file.isFile()) {
ZipEntry entry = new ZipEntry(name + "\\" + file.getName()); // aaa\\a.txt 文件
zos.putNextEntry(entry);
FileInputStream fis = new FileInputStream(file);
int b;
while ((b = fis.read()) != -1) {
zos.write(b);
}
fis.close();
zos.closeEntry();
} else {
toZip(file, zos, name + "\\" + file.getName()); // aaa\\aa 文件夹
}
}
}

Commons-io

链接

有关 IO 操作的工具包, 作用是提高 IO 流的开发效率

  • 官网下载 commons-io-2.18.0-bin.zip (二进制文件), 并解压
  • 打开文件夹, 找到 commons-io-2.18.0.jar
  • JAVA PROJECTS 中打开 java_code, 找到 Referenced Libraries, 添加 commons-io-2.18.0.jar
// FileUtils 类
{
File src = new File("flow\\temp\\a.txt");
File dest = new File("flow\\temp\\copy.txt");
FileUtils.copyFile(src, dest);
}
{
File src = new File("D:\\aaa");
File dest = new File("D:\\bbb");
FileUtils.copyDirectory(src, dest); // 把 aaa 变成 bbb
}
{
File src = new File("D:\\aaa");
File dest = new File("D:\\bbb");
FileUtils.copyDirectoryToDirectory(src, dest); // 把 aaa 拷贝到 bbb 下
}
{
File src = new File("D:\\aaa");
FileUtils.deleteDirectory(src);
}
{
File src = new File("D:\\aaa");
FileUtils.cleanDirectory(src);
}
// 还有 IOUtils 类...

Hutool

官网

下载 jar 链接

// 创建 file 对象, 创建文件
{
File file = FileUtil.file("D:\\", "aaa", "bbb", "a.txt"); // 可多个参数拼接
System.out.println(file); // D:\aaa\bbb\a.txt
File touch = FileUtil.touch(file); // 根据参数创建空的文件, 如果父级路径不存在, 会把父级路径也创建出来
System.out.println(touch);
}
// 把集合中的数据写入文件, 覆盖模式
{
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "aaa", "bbb", "ccc");
File file = FileUtil.writeLines(list, "D:\\a.txt", "UTF-8");
System.out.println(file);
}
// 把集合中的数据写入文件, 续写模式
{
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "aaa", "bbb", "ccc");
File file = FileUtil.appendLines(list, "D:\\a.txt", "UTF-8");
System.out.println(file);
}
// 把文件中的数据读取到集合中
{
List<String> list = FileUtil.readLines("D:\\a.txt", "UTF-8");// 认为一行是一个数据
System.out.println(list);
}

练习

暂略
posted @   wxgmjfhy  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示