0%

Java基础温习

序言

锲而不舍,金石可镂。

今天来总结Java基础,来填本科的坑。

本篇会持续更新。

Java数据类型

基础类型:8种

​ int、float、double、float、double、char、byte、boolean

引用类型:5种

​ 类 / 数组 / 接口 / 枚举 / 注解

区别:

基本类型的变量存储原始值(常量),常量存放在堆上。

引用类型的变量存储地址,存储的是堆中对象的地址

从源代码到机器码,到底发生了什么?

参考陈树义

如下图所示,编译器可以分为:前端编译器、JIT 编译器和AOT编译器

image-20210804142030195

前端编译器 : 源代码->class字节码

javac

javac 编译器的处理过程可以分为下面四个阶段:

  • 第一个阶段:词法、语法分析。在这个阶段,javac 编译器会对源代码的字符进行一次扫描,最终生成一个抽象语法树AST。简单地说,在这个阶段 javac 编译器会搞懂我们的代码到底想要干嘛。就像我们分析一个句子一样,我们会对句子划分主谓宾,弄清楚这个句子要表达的意思一样。
  • 第二个阶段:填充符号表。我们知道类之间是会互相引用的,但在编译阶段,我们无法确定其具体的地址,所以我们会使用一个符号来替代。在这个阶段做的就是类似的事情,即对抽象的类或接口进行符号填充等到类加载阶段,javac 编译器会将符号替换成具体的内存地址。
  • 第三个阶段:注解处理。我们知道 Java 是支持注解的,在这个阶段会对注解进行分析,根据注解的作用将其还原成具体的指令集。
  • 第四个阶段:字节码生成。到了这个阶段,javac 编译器便会根据上面几个阶段分析出来的结果,进行字节码的生成,最终输出为 class 文件。

JIT编译器:class字节码->机器码

当源代码转化为字节码之后,其实要运行程序,有两种选择:

  • 一种是使用 Java 解释器解释执行字节码,启动快,但是运行慢

  • 另一种则是使用 JIT 编译器将字节码转化为本地机器代码,启动慢,但是运行快

原因很简单。

解释器不需要像 JIT 编译器一样,将所有字节码都转化为机器码,自然就少去了优化的时间。

当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。

机器码的运行效率肯定是高于 Java 解释器的。

实际情况中,为了运行速度以及效率,我们通常采用两者相结合的方式进行 Java 代码的编译执行。

image-20210804171444627

值传递与引用传递

发生在形参与实参传递的过程中。

值传递

基本类型的变量赋值就是值传递,相当于直接将常量值复制一份,函数接收的是原始值的一个copy。

此时内存中存在两个相等的基本类型,即实参和形参。

后面方法中的操作都是对形参这个值的修改,不影响实际参数的值

引用传递

也称为地址传递。方法调用时,实际参数的引用被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址。
在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对形参的操作将会影响到实际对象

接口与抽象类的区别

一个类只能继承一个父类,但是可以实现多个接口

抽象类

只要类中有抽象方法,那么他就是抽象类

抽象方法:abstract+只有方法名 默认public abstract修饰

final 与 abstract不共存

抽象类可以有成员变量、构造方法、抽象方法

只支持单继承

继承抽象类的子类必须extends,实现抽象类中的全部抽象方法

接口

接口中方法默认 public abstract修饰

一个类如果实现了接口,那么就需要实现接口的全部方法

接口中方法默认public abstract

接口中字段默认public static final

在JDK8中可以定义static静态方法 & default方法 default修饰方法的有默认方法体,实现类可以不重写default方法

接口支持多继承

编译类型与运行类型

经典例子:

1
2
3
4
Father child = new son();
编译类型 运行类型

child.doWork();

理解:

编译类型由声明该变量的类型决定声明啥是啥,运行类型由实际赋值给的变量决定new啥是啥

如果这两个类型不一样,那么就是多态。

属性不具有多态,也就是对象在调用属性的时候,总会访问它编译类型的属性,而不是运行类型。

首先声明了一个引用类型Father child,用它来指向son类的一个实例。

在程序运行时,首先会调用父类的构造器,然后再调用子类的构造器。

在编译过程中,会自动检查引用变量Father child的编译类型,发现是Father类,会检查是否拥有doWork方法。

但是在JVM实际运行中,调用的是子类的doWork方法,而不是父类的,这就是运行时多态。

找方法,先找子类:

如果子类没有,再去找父类。

方法重写override 与重载overload

Override 重写 (子类对父类方法重写)

名称+参数列表必须完全相同

返回值类型 如果不同的话,必须是兼容类型

构造方法 final 和 static方法 不能重写

Overload 重载 (一个方法内)

方法名称必须相同,参数必须不同

返回值没要求

系统属性

来自javadoc

image-20210630121515640

Scanner 类

java.lang.String : 接受用户的键盘输入

导包的一般步骤:

  1. 导包:

    import 包路径.类名称

    如果需要使用的目标类,和当前位于同一个包下,则可以省略导包语句不写。

    只有java.lang包下的内容不需要导包,其他的包都需要import语句。

  2. 创建:

    类名称 对象名 = new 类名称();

  3. 使用

    对象名.方法名();

Demo:

1
2
3
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();//接收一个int数字
String str = sc.next();//接收一个String字符串

匿名对象

创建对象的标准格式:

对象名
1
2
3
4

匿名对象就是只有右边的对象,没有左边的名字和赋值运算符。

``` new 类名称();

匿名对象只能使用唯一的一次。

如果确定有一个对象只需要使用唯一的一次,就可以用匿名对象。

1
2
int num = new Scanner(System.in).nextInt();
System.out.println("输入的是"+num);

Random类

java.util.Random 生成伪随机数。

1
2
3
4
Random r = new Random();
int i = r.nextInt();
//public int nextInt(int n)
//返回一个随机数,范围在[0,n)之间,左闭右开

enum枚举类

一般表示一组常量,例如一周有7天、一年有4个季节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Color
{
RED, GREEN, BLUE;
}

public class Test
{
// 执行输出结果
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}

enum内部方法

values(), ordinal() 和 valueOf() 方法

enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口。

values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中:

  • values() 返回枚举类中所有的值。
  • ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
  • valueOf()方法返回指定字符串值的枚举常量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
enum Color
{
RED, GREEN, BLUE;
}

public class Test
{
public static void main(String[] args)
{
// 调用 values()
Color[] arr = Color.values();

// 迭代枚举
for (Color col : arr)
{
// 查看索引
System.out.println(col + " at index " + col.ordinal());
}

// 使用 valueOf() 返回枚举常量,不存在的会报错 IllegalArgumentException
System.out.println(Color.valueOf("RED"));
// System.out.println(Color.valueOf("WHITE"));
}
}

Result:
RED at index 0
GREEN at index 1
BLUE at index 2
RED

enum枚举类成员

枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。

枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
enum Color
{
RED, GREEN, BLUE;

// 构造函数
private Color()
{
System.out.println("Constructor called for : " + this.toString());
}

public void colorInfo()
{
System.out.println("Universal Color");
}
}

public class Test
{
// 输出
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
c1.colorInfo();
}
}

Result:
Constructor called for : RED
Constructor called for : GREEN
Constructor called for : BLUE
RED
Universal Color

ArrayList类

java.util.ArrayList

大小可变的集合,长度动态增长

ArrayList list = new ArrayList<>();

成员方法

  • public boolean add(E e):将指定的元素添加到集合尾部
  • public E remove(int index):移除指定下标元素,返回被删除的元素。
  • public E get(int index):捕获此集合中指定位置上的元素。
  • public int size():返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。

包装类

ArrayList对象不能存储基本类型,只能存储引用类型的数据。

类似<int>不能写,但是存储基本数据类型对应的包装类型是可以的。所以,想要存储基本类型数据,必须转换后才能编写,转换写法如下:

image-20200428120710735

String类

java.lang.String 底层是字符数组

构造方法

  • public String(char[] value):通过当前参数中的字符数组来构造新的String。
  • public String(byte[] bytes):通过使用平台的默认字符集解码当前参数中的字节数组来构造新的 String。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class String_Demo01 {
public static void main(String[] args) {
// 创建字符串对象
String s1 = "hello";
String s2 = "hello";
String s3 = "HELLO";
// boolean equals(Object obj):比较字符串的内容是否相同
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // false
System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
//boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写
System.out.println(s1.equalsIgnoreCase(s2)); // true
System.out.println(s1.equalsIgnoreCase(s3)); // true
System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
}
}

获取功能的方法

  • public int length ():返回此字符串的长度。

  • public String concat (String str):将指定的字符串连接到该字符串的末尾。

  • public char charAt (int index) :返回指定索引处的 char值。

  • public int indexOf (String str) :返回指定子字符串第一次出现在该字符串内的索引。

  • public String substring (int beginIndex) :返回一个子字符串,从beginIndex开始截取字符串到字符 串结尾。

  • public String substring (int beginIndex, int endIndex) :返回一个子字符串,从beginIndex到 endIndex截取字符串。含beginIndex,不含endIndex。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class String_Demo02 {
public static void main(String[] args) {
//创建字符串对象
String s = "helloworld";
// int length():获取字符串的长度,其实也就是字符个数
System.out.println(s.length());
System.out.println("‐‐‐‐‐‐‐‐");
// String concat (String str):将将指定的字符串连接到该字符串的末尾.
String s = "helloworld";
String s2 = s.concat("**hello again");
System.out.println(s2);// helloworld**hello again
// char charAt(int index):获取指定索引处的字符
System.out.println(s.charAt(0));
System.out.println(s.charAt(1));
System.out.println("‐‐‐‐‐‐‐‐");
// int indexOf(String str):获取str在字符串对象中第一次出现的索引,没有返回‐1
System.out.println(s.indexOf("l"));
System.out.println(s.indexOf("owo"));
System.out.println(s.indexOf("ak"));
System.out.println("‐‐‐‐‐‐‐‐");
// String substring(int start):从start开始截取字符串到字符串结尾
System.out.println(s.substring(0));
System.out.println(s.substring(5));
System.out.println("‐‐‐‐‐‐‐‐");
// String substring(int start,int end):从start到end截取字符串。含start,不含end,左闭右开
System.out.println(s.substring(0, s.length()));
System.out.println(s.substring(3,8));
}
}

转换功能的方法

  • public char[] toCharArray ():将此字符串转换为新的字符数组。

  • public byte[] getBytes ():使用平台的默认字符集将该 String编码转换为新的字节数组。

  • public String replace (CharSequence target, CharSequence replacement):将与target匹配的字符串使用replacement字符串替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class String_Demo03 {
public static void main(String[] args) {
//创建字符串对象
String s = "abcde";
// char[] toCharArray():把字符串转换为字符数组
char[] chs = s.toCharArray();
for(int x = 0; x < chs.length; x++) {
System.out.println(chs[x]);
}
System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
// byte[] getBytes ():把字符串转换为字节数组
byte[] bytes = s.getBytes();
for(int x = 0; x < bytes.length; x++) {
System.out.println(bytes[x]);
}
System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
// 替换字母it为大写IT
String str = "itcast itheima";
String replace = str.replace("it", "IT");
System.out.println(replace); // ITcast ITheima
System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
}
}

Arrays 类

java.util.Arrays 此类包含用来操作数组的各种方法,比如排序和搜索等。其所有方法均为静态方法,调用起来 非常简单。

操作数组的方法

  • public static String toString(int[] a):返回指定数组内容的字符串表示形式。
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
// 定义int 数组
int[] arr = {2,34,35,4,657,8,69,9};
// 打印数组,输出地址值
System.out.println(arr); // [I@2ac1fdc4
// 数组内容转为字符串
String s = Arrays.toString(arr);
// 打印字符串,输出内容
System.out.println(s); // [2, 34, 35, 4, 657, 8, 69, 9]
}
  • public static void sort(int[] a):对指定的 int 型数组按数字升序进行排序。
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
// 定义int 数组
int[] arr = {24, 7, 5, 48, 4, 46, 35, 11, 6, 2};
System.out.println("排序前:"+ Arrays.toString(arr)); // 排序前:[24, 7, 5, 48, 4, 46, 35, 11, 6,
2]
// 升序排序
Arrays.sort(arr);
System.out.println("排序后:"+ Arrays.toString(arr));// 排序后:[2, 4, 5, 6, 7, 11, 24, 35, 46,
48]
}

StringBuilder 类

StringBuilder是个字符串的缓冲区,即它是一个容器,容器中可以装很多字符串。并且能够对其中的字符串进行各种操作。

它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容,默认16字符空间,超过自动扩充。

1
2
3
4
5
6
7
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder();
System.out.println(sb1); // (空白)
// 使用带参构造
StringBuilder sb2 = new StringBuilder("itcast");
System.out.println(sb2); // itcast
}

常用方法

StringBuilder常用的方法有2个:

  • public StringBuilder append(...):添加任意类型数据的字符串形式,并返回当前对象自身。
  • public String toString():将当前StringBuilder对象转换为String对象。

append方法

append方法具有多种重载形式,可以接收任意类型的参数。任何数据作为参数都会将对应的字符串内容添加到StringBuilder中。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo02StringBuilder {
public static void main(String[] args) {
//创建对象
StringBuilder builder = new StringBuilder();
//public StringBuilder append(任意类型)
StringBuilder builder2 = builder.append("hello");
//对比一下
System.out.println("builder:"+builder);
System.out.println("builder2:"+builder2);
System.out.println(builder == builder2); //true
// 可以添加 任何类型
builder.append("hello");
builder.append("world");
builder.append(true);
builder.append(100);
// 在我们开发中,会遇到调用一个方法后,返回一个对象的情况。然后使用返回的对象继续调用方法。
//链式编程
builder.append("hello").append("world").append(true).append(100);
System.out.println("builder:"+builder);
}
}

toString方法

通过toString方法,StringBuilder对象将会转换为不可变的String对象。如:

1
2
3
4
5
6
7
8
9
public class Demo16StringBuilder {
public static void main(String[] args) {
// 链式创建
StringBuilder sb = new StringBuilder("Hello").append("World").append("Java");
// 调用方法
String str = sb.toString();
System.out.println(str); // HelloWorldJava
}
}

集合

集合按照其存储结构可以分为两大类,分别是:

单列集合java.util.Collection

双列集合java.util.Map

Collection集合

单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是:

  • java.util.List

  • java.util.Set

其中,List的特点是元素有序、元素可重复。

Set的特点是元素无序,而且不可重复。

List接口的主要实现类有java.util.ArrayListjava.util.LinkedList

Set接口的主要实现类有java.util.HashSetjava.util.TreeSet

Collection常用功能

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e): 把给定的对象添加到当前集合中
  • public void clear() :清空集合中所有的元素
  • public boolean remove(E e): 把给定的对象在当前集合中删除
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象
  • public boolean isEmpty(): 判断当前集合是否为空
  • public int size(): 返回集合中元素的个数
  • public Object[] toArray(): 把集合中的元素,存储到数组中

List接口

java.util.List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了 List 接口的对象称为List集合。

在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过【索引】来访问集合中的指定元素。

另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。

List常用方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:

public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。

public E get(int index):返回集合中指定位置的元素。

public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。

public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

List集合特有的方法都是跟索引相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ListDemo {
public static void main(String[] args) {
// 创建List集合对象
List<String> list = new ArrayList<String>();
// 往 尾部添加 指定元素
list.add("图图");
list.add("小美");
list.add("不高兴");
System.out.println(list);
// add(int index,String s) 往指定位置添加
list.add(1,"没头脑");
System.out.println(list);
// String remove(int index) 删除指定位置元素 返回被删除元素
// 删除索引位置为2的元素
System.out.println("删除索引位置为2的元素");
System.out.println(list.remove(2));
System.out.println(list);
// String set(int index,String s)
// 在指定位置 进行 元素替代(改)
// 修改指定位置元素
list.set(0, "三毛");
System.out.println(list);
// String get(int index) 获取指定位置元素
// 跟size() 方法一起用 来 遍历的
for(int i = 0;i<list.size();i++){
System.out.println(list.get(i));
}
//还可以使用增强for
for(String str : list){
System.out.println(string);
}
}
}

Set接口

java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。

与 List 接口不 同的是, Set 接口中元素【无序】,并且都会以某种规则保证存入的元素不出现重复。

Map集合

image-20200429182137690

Collection 中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。

Map 中的集合,元素是成对存在的(理解为夫妻)。

每个元素由键与值两部分组成,通过键可以找对所对应的值。

Collection 中的集合称为单列集合, Map 中的集合称为双列集合。

需要注意的是, Map 中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。

Map常用子类

通过查看Map接口描述,看到Map有多个子类,这里我们主要讲解常用的HashMap集合、LinkedHashMap集合。

  • HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
  • LinkedHashMap:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的 hashCode()方法、equals()方法。

tips:Map接口中的集合都有两个泛型变量,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量的数据类型可以相同,也可以不同。

Map接口常用方法

Map接口中定义了很多方法,常用的如下:

public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。

public V remove(Object key): 把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。

public V get(Object key): 根据指定的键,在Map集合中获取对应的value。

public Set<k> keySet(): 获取Map集合中所有的键,存储到Set集合中。

public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MapDemo {
public static void main(String[] args) {
//创建 map对象
HashMap<String, String> map = new HashMap<String, String>();
//添加元素到集合
map.put("黄晓明", "杨颖");
map.put("文章", "马伊琍");
map.put("邓超", "孙俪");
System.out.println(map);
//String remove(String key)
System.out.println(map.remove("邓超"));
System.out.println(map);
// 想要查看 黄晓明的媳妇 是谁
System.out.println(map.get("黄晓明"));
System.out.println(map.get("邓超"));
}
}

tips: 使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中; 若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。

Entry 键值对对象

我们已经知道, Map 中存放的是两种对象,一种称为key(键),一种称为value(值),它们在在 Map 中是一一对应关系,这一对对象又称做 Map 中的一个 Entry(项) 。

Entry 将键值对的对应关系封装成了对象。即键值对对象,这样我们在遍历 Map 集合时,就可以从每一个键值对( Entry )对象中获取对应的键与对应的值。

既然Entry表示了一对键和值,那么也同样提供了获取对应键和对应值得方法:

public K getKey():获取Entry对象中的键。

public V getValue():获取Entry对象中的值。

在Map集合中也提供了获取所有Entry对象的方法:

public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。

Map集合遍历键值对的方式

键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值。

操作步骤与图解:

  1. 获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回。entrySet() 。
  2. 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象。
  3. 通过键值对(Entry)对象,获取Entry对象中的键与值。getKey()/getValue()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MapDemo02 {
public static void main(String[] args) {
// 创建Map集合对象
HashMap<String, String> map = new HashMap<String,String>();
// 添加元素到集合
map.put("胡歌", "霍建华");
map.put("郭德纲", "于谦");
map.put("薛之谦", "大张伟");
// 获取 所有的 entry对象 entrySet
Set<Entry<String,String>> entrySet = map.entrySet();
// 遍历得到每一个entry对象
for (Entry<String, String> entry : entrySet) {
// 解析
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"的CP是:"+value);
}
}

图解:

image-20200429190122568

高级用法 函数式编程

介绍几个有趣且强大的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 对key所属的value进行计算
default V compute(K key,BiFunction<? super K,? super V,? extends V> remappingFunction)
// 如果key还没有映射的value,则计算
default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
// 如果key存在映射的value,则计算
default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
// 遍历map的entry
default void forEach(BiConsumer<? super K,? super V> action)
// 返回key对应的value,如果不存在则返回defaultValue
default V getOrDefault(Object key,V defaultValue)
// 如果key还没有映射的value,则存放value与key映射
default V putIfAbsent(K key, V value)
// 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果
default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
// 判断key是否存在,如果不存在,则添加<key,value>
default V putIfAbsent(K key,V value)
// 如果 key 对应的 value 不存在,则添加键值对到 hashMap 中。如果存在,则返回通过 remappingFunction 重新计算后的值。
default V merge(K key,
V value,
BiFunction<? super V,? super V,? extends V> remappingFunction)

代码示例;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public static void ComputeTest(){
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
System.out.println(map);
// 让每一个value都加一
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
map.compute(entry.getKey(),(k,v)->{
return v+1;
});
}
System.out.println(map);
}

private static void ComputeIfPresentTest() {
Map<Integer, String> oldMap = new HashMap<>();
oldMap.put(1, "Google");
oldMap.put(2, "Baidu");
oldMap.put(3, "Bing");

Map<Integer, String> newMap = new HashMap<>();
newMap.put(2, "BaiduNew");
newMap.put(3, "BingNew");

System.out.println(oldMap);

// 遍历newMap,如果oldMap存在相同的key,那么就把oldmap中key对应的value改成新的
for (Map.Entry<Integer, String> newEntry : newMap.entrySet()) {
oldMap.computeIfPresent(newEntry.getKey(),(k,v)->{
v = newEntry.getValue();
return v;
});
}

System.out.println(oldMap);
}


private static void test2() {
Map<Integer, String> oldMap = new HashMap<>();
oldMap.put(1, "Google");
oldMap.put(2, "Baidu");
oldMap.put(3, "Bing");

Map<Integer, String> newMap = new HashMap<>();
newMap.put(2, "BaiduNew");
newMap.put(3, "BingNew");
newMap.put(4, "AllNew");

System.out.println(oldMap);
// 遍历newMap,对于oldMap里面不存在的key,计算一个新的entry返回
for (Map.Entry<Integer, String> newEntry : newMap.entrySet()) {
oldMap.computeIfAbsent(newEntry.getKey(),k->newEntry.getValue());
}

System.out.println(oldMap);
}

public static void replaceall() {
Map<Integer, String> map = new HashMap<>();
map.put(1, "Google");
map.put(2, "Baidu");
map.put(3, "Bing");
System.out.println(map);
// 全部替换为大写
map.replaceAll((k, v) -> {
return v.toUpperCase();
});

// map.replaceAll((k, v) -> v.toUpperCase());
// 写成一行去掉大括号也是可以的 默认都是return

System.out.println(map);
}

// 遍历entry
oldMap.forEach((k,v)->{
System.out.println(k+v);
});

private static void test3() {
Map<Integer, String> oldMap = new HashMap<>();
oldMap.put(1, "Google");
oldMap.put(2, "Baidu");
oldMap.put(3, "Bing");

System.out.println(oldMap);
// 如果4不存在,那么添加<4,"NEW">,如果4存在,那么把它的value改成k+"/"+v
oldMap.merge(4,"NEW",(k,v)->k+"/"+v);
System.out.println(oldMap);
}

Iterator迭代器

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.IteratorIterator接口也是Java集合中的一员,但它与CollectionMap接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:

  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。

下面介绍一下迭代的概念:

  • 迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

Iterator接口的常用方法如下:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。

接下来通过案例学习如何使用Iterator迭代集合中元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class IteratorDemo {
public static void main(String[] args) {
// 使用多态方式 创建对象
Collection<String> coll = new ArrayList<String>();

// 添加元素到集合
coll.add("串串星人");
coll.add("吐槽星人");
coll.add("汪星人");
//遍历
//使用迭代器 遍历 每个集合对象都有自己的迭代器
Iterator<String> it = coll.iterator();
// 泛型指的是 迭代出 元素的数据类型
while(it.hasNext()){ //判断是否有迭代元素
String s = it.next();//获取迭代出的元素
System.out.println(s);
}
}
}

实现原理:

image-20200428180357303

在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

增强for

1
2
3
4
5
6
7
8
9
10
11
12
public class NBFor {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("小河神");
coll.add("老河神");
coll.add("神婆");
//使用增强for遍历
for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
System.out.println(s);
}
}
}

Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型对象。例如都是存储字符串对象。因此在JDK5之后,新增了泛型(Generic)语法,让人们在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。

泛型

例如,API中的ArrayList集合:

1
2
3
4
5
6
class ArrayList<E>{ 
public boolean add(E e){ }

public E get(int index){ }
....
}

使用泛型: 即什么时候确定泛型。

在创建对象的时候确定泛型

例如,ArrayList<String> list = new ArrayList<String>();

此时,变量E的值就是String类型,那么我们的类型就可以理解为:

1
2
3
4
5
6
class ArrayList<String>{ 
public boolean add(String e){ }

public String get(int index){ }
...
}

此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:

1
2
3
4
5
6
class ArrayList<Integer> { 
public boolean add(Integer e) { }

public Integer get(int index) { }
...
}

举例自定义泛型类

1
2
3
4
5
6
7
8
9
10
11
12
public class MyGenericClass<MVP> {
//没有MVP类型,在这里代表 未知的一种数据类型 未来传递什么就是什么类型
private MVP mvp;

public void setMVP(MVP mvp) {
this.mvp = mvp;
}

public MVP getMVP() {
return mvp;
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class GenericClassDemo {
public static void main(String[] args) {
// 创建一个泛型为String的类
MyGenericClass<String> my = new MyGenericClass<String>();
// 调用setMVP
my.setMVP("大胡子登登");
// 调用getMVP
String mvp = my.getMVP();
System.out.println(mvp);
//创建一个泛型为Integer的类
MyGenericClass<Integer> my2 = new MyGenericClass<Integer>();
my2.setMVP(123);
Integer mvp2 = my2.getMVP();
}
}

含有泛型的方法

例如:

1
2
3
4
5
6
7
8
9
public class MyGenericMethod {	  
public <MVP> void show(MVP mvp) {
System.out.println(mvp.getClass());
}

public <MVP> MVP show2(MVP mvp) {
return mvp;
}
}

使用格式:调用方法时,确定泛型的类型

1
2
3
4
5
6
7
8
9
10
public class GenericMethodDemo {
public static void main(String[] args) {
// 创建对象
MyGenericMethod mm = new MyGenericMethod();
// 演示看方法提示
mm.show("aaa");
mm.show(123);
mm.show(12.45);
}
}

含有泛型的接口

定义格式:

1
修饰符 interface接口名<代表泛型的变量> {  }

例如:

1
2
3
4
5
public interface MyGenericInterface<E>{
public abstract void add(E e);

public abstract E getE();
}

使用格式:

1、定义类时确定泛型的类型

例如

1
2
3
4
5
6
7
8
9
10
11
public class MyImp1 implements MyGenericInterface<String> {
@Override
public void add(String e) {
// 省略...
}

@Override
public String getE() {
return null;
}
}

此时,泛型E的值就是String类型。

2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型

例如

1
2
3
4
5
6
7
8
9
10
11
public class MyImp2<E> implements MyGenericInterface<E> {
@Override
public void add(E e) {
// 省略...
}

@Override
public E getE() {
return null;
}
}

确定泛型:

1
2
3
4
5
6
7
8
9
/*
* 使用
*/
public class GenericInterface {
public static void main(String[] args) {
MyImp2<String> my = new MyImp2<String>();
my.add("aa");
}
}

通配符

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

通配符基本使用

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用表示未知通配符。

此时只能接受数据,不能往该集合中存储数据。

举个例子大家理解使用即可:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型

通配符高级使用—-受限泛型

之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限下限

泛型的上限

  • 格式类型名称 <? extends 类 > 对象名称
  • 意义只能接收该类型及其子类

泛型的下限

  • 格式类型名称 <? super 类 > 对象名称
  • 意义只能接收该类型及其父类型

比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();

getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错

getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);

}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}

可变参数

在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:

1
修饰符 返回值类型 方法名(参数类型... 形参名){ }

其实这个书写完全等价与

1
修饰符 返回值类型 方法名(参数类型[] 形参名){ }

只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。

JDK1.5以后。出现了简化操作。

… 用在参数上,称之为可变参数。

同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素,作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,再进行传递。这些动作都在编译.class文件时自动完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ChangeArgs {
public static void main(String[] args) {
int[] arr = { 1, 4, 62, 431, 2 };
int sum = getSum(arr);
System.out.println(sum);
// 6 7 2 12 2121
// 求 这几个元素和 6 7 2 12 2121
int sum2 = getSum(6, 7, 2, 12, 2121);
System.out.println(sum2);
}
/*
* 完成数组 所有元素的求和 原始写法
public static int getSum(int[] arr){
int sum = 0;
for(int a : arr){
sum += a;
}
return sum;
}
*/
//可变参数写法
public static int getSum(int... arr) {
int sum = 0;
for (int a : arr) {
sum += a;
}
return sum;
}
}

File类

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

构造方法

public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

public File(String parent, String child):从父路径名字符串和子路径名字符串创建新的 File实例。

public File(File parent, String child):从父抽象路径名和子路径名字符串创建新的 File实例。

构造举例,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 文件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);
// 文件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
// 通过父路径和子路径字符串
String parent = "d:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);
// 通过父级File对象和子路径字符串
File parentDir = new File("d:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);

获取功能的方法

public String getAbsolutePath() :返回此File的绝对路径名字符串。

public String getPath():将此File转换为路径名字符串。

public String getName():返回由此File表示的文件或目录的名称。

public long length() :返回由此File表示的文件的长度。

判断功能的方法

public boolean exists() :此File表示的文件或目录是否实际存在。

public boolean isDirectory() :此File表示的是否为目录。

public boolean isFile() :此File表示的是否为文件。

创建删除的方法

public boolean createNewFile():当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。

public boolean delete() :删除由此File表示的文件或目录。

public boolean mkdir():创建由此File表示的目录。

public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。

目录遍历的方法

public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。

public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FileFor {
public static void main(String[] args) {
File dir = new File("d:\\java_code");
//获取当前目录下的文件以及文件夹的名称。
String[] names = dir.list();
for(String name : names){
System.out.println(name);
}
//获取当前目录下的文件以及文件夹对象,只要拿到了文件对象,那么就可以获取更多信息
File[] files = dir.listFiles();
for (File file : files) {
System.out.println(file);
}
}
}

I/O 流

一切视角以内存为主。

文件就是数组的载体,是数据存放的地方。

文件编码是告诉你,字节如何翻译为字符,一个字符对应几个字节

image-20211205115816189

根据数据的流向分为:输入流和输出流。

  • 输入流 :把数据从 其他设备 上读取到 内存 中的流。
  • 输出流 :把数据从 内存 中写出到 其他设备 上的流。

格局数据的类型分为:字节流和字符流。

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流。

还可以分为:

  • 节点流:字节操作数据源的流,数据源:文件、数组、字符串、管道
  • 处理流:包装节点流,Buffered开头的流

这是个父类都是抽象类

image-20200429203709515

字节流

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,本质为字节,传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确:

​ 无论使用什么样的流对象,底层传输的始终为二进制数据。

字节输出流 OutputStream

java.io.OutputStream 是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

public void close():关闭此输出流并释放与此流相关联的任何系统资源。

public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。

public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。

public void write(byte[] b, int off, int len):从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。

public abstract void write(int b):将指定的当个字节输出流。

小贴士: close方法,当完成流的操作时,必须调用此方法,释放系统资源。

FileOutputStream类

OutputStream 有很多子类,我们从最简单的一个子类开始。

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件里面。

构造方法:

public FileOutputStream(File file) :创建文件输出流以写入由指定的 File对象表示的文件。

public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

Demo:

1
2
3
4
5
6
7
8
9
public class FileOutputStreamConstructor throws IOException {
public static void main(String[] args) {
// 使用File对象创建流对象
File file = new File("a.txt");
FileOutputStream fos = new FileOutputStream(file);
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("b.txt");
}
}

写出字节:

write(int b) 方法,每次可以写出一个字节数据,代码使用演示:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 写出数据
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源,一定记得
fos.close();
}
输出结果:
abc

写出字节数组:

write(byte[] b) 每次可以写出数组中的数据,代码使用演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "程序员".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();
}
}
输出结果:
程序员

写出指定长度字节数组:

write(byte[] b, int off, int len),每次写出从off索引开始,len个字节,代码使用演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b,2,2);
// 关闭资源
fos.close();
}
}
输出结果:
cd

image-20200429205536626

image-20200429205618717

image-20200429205702501

字节输入流 InputStream

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。

常常和循环搭配使用

它定义了字节输入流的基本共性功能方法。

public void close():关闭此输入流并释放与此流相关联的任何系统资源。 public abstract int read() : 从输入流读取数据的下一个字节。

public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

FileInputStream类

java.io.FileInputStream 类是文件输入流,从文件中读取字节。

构造方法:

FileInputStream(File file) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

FileInputStream(String name) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

1
2
3
4
5
6
7
8
9
public class FileInputStreamConstructor throws IOException{
public static void main(String[] args) {
// 使用File对象创建流对象
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file);
// 使用文件名称创建流对象
FileInputStream fos = new FileInputStream("b.txt");
}
}

读取字节:

read 方法,【每次】可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回 -1 ,代码使用演示:

循环改进读取方式,代码使用演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 定义int变量,保存数据
int abyte = 0;
// 循环读取
while ((abyte = fis.read())!=‐1) {
System.out.print((char)abyte);
}
// 关闭资源
fis.close();
}
}
输出结果:
abcde

使用字节数组读取:

read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读 取到末尾时,返回 -1 ,代码使用演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
// 定义变量,作为有效个数
int readLen = 0;
byte[] buffer = new byte[1024];
// 循环读取
while ((readLen= fis.read(buffer))!=‐1) {
// 每次读取后,把数组变成字符串打印
System.out.println(new String(buffer,0,readLen));
}
// 关闭资源
fis.close();
}
}

复制图片文件,代码使用演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Copy {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 指定数据源
FileInputStream fis = new FileInputStream("D:\\test.jpg");
// 1.2 指定目的地
FileOutputStream fos = new FileOutputStream("test_copy.jpg");
// 2.读写数据
// 2.1 定义数组
byte[] b = new byte[1024];
// 2.2 定义长度
int len;
// 2.3 循环读取
while ((len = fis.read(b))!=‐1) {
// 2.4 写出数据
fos.write(b, 0 , len);
}
// 3.关闭资源
fos.close();
fis.close();
}
}
image-20211205151613278

缓冲流(处理流,包装流)

image-20200429214541228

字节缓冲流 BufferedInputStream/OutputStream

构造方法

public BufferedInputStream(InputStream in) :创建缓冲输入流。

public BufferedOutputStream(OutputStream out) : 创建缓冲输出流。

1
2
3
4
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
){
// 读写数据
int len;
byte[] bytes = new byte[8*1024];
while ((len = bis.read(bytes)) != ‐1) {
bos.write(bytes, 0 , len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流使用数组复制时间:"+(end ‐ start)+" 毫秒");
}
}
缓冲流使用数组复制时间:666 毫秒

字符缓冲流 BufferedReader/BufferedWriter

特有方法:

字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。

BufferedReaderpublic String readLine() : 读一行文字。

BufferedWriterpublic void newLine() : 写入一个“行分隔符”,由系统属性定义符号。

readLine 方法演示,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
// 定义字符串,保存读取的一行文字
String line = null;
// 循环读取,读取到最后返回null
while ((line = br.readLine())!=null) {
System.out.print(line);
System.out.println("‐‐‐‐‐‐");
}
// 释放资源
br.close();
}
}

转换流

将字节流按照某种编码方法转换为字符流

将InputStream/OutputStream按照某种编码方式,包装为InputStreamReader/OutputStreamWriter

两种:

  • InputStreamReader
  • OutputStreamWriter
image-20211205183025845

转换流的本质是为了符合特殊的编码要求,转换流的构造方法就能看出来端倪

image-20211205183553379

骚操作

读取类的字节数组

getResource方法

Java为Class对象提供了getResource方法,可以获得该类字节码下的文件路径。

getResource方法接受一个参数,

如果该参数为/,就在classpath的根目录下寻找(不回递归寻找子目录);

如果是具体的文件名,那么就在Class对象所在的目录下来找(不回递归寻找子目录)。

classpath是什么?就是存放class字节码的文件的路径;

maven项目,classpath为“项目名/target/classes”;

普通项目,classpath为”项目名/bin“或者”项目名/build/classes“;

代码片段:

1
2
URI uri = Util.class.getResource("Evil.class").toURI();
byte[] bytes = Files.readAllBytes(Paths.get(uri));