洋蔥

贪婪,找不到比这更好的词了,是件好事。

Thread类

由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是,Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由 Java 去调用这样的东西,然后提供一些类(Thread 类)供我们使用。我们就可以实现多线程程序了。

阅读全文 »

调度模型 - 百度

调度模型

线程有两种调度模型:

  • 分时调度模型:让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片。
  • 抢占式调度模型:java虚拟机采用该方式,优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。
阅读全文 »

File

介绍

​ File:文件和目录(文件夹)路径名的抽象表示形式

​ IO流操作中大部分都是对文件的操作,所以Java就提供了File类供我们来操作文件

构造方法

1
2
3
  public File(String pathname):通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
  public File(String parent, String child):根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
  public File(File parent, String child):根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FileDemo {
public static void main(String[] args) {
// File(String pathname):根据一个路径得到File对象
// 把e:\\demo\\a.txt封装成一个File对象
File file = new File("G:\\demo\\a.txt");
System.out.println(file);// G:\demo\a.txt

// File(String parent, String child):根据一个目录和一个子文件/目录得到File对象
File file2 = new File("G:\\demo", "a.txt");
System.out.println(file2);// G:\demo\a.txt

// File(File parent, String child):根据一个父File对象和一个子文件/目录得到File对象
File file3 = new File("G:\\demo");
File file4 = new File(file3, "a.txt");
System.out.println(file4);// G:\demo\a.txt

// 以上三种方式其实效果一样
}
}

File类功能

更多功能详见 API。

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
  A:创建功能:
    public boolean createNewFile() throws IOException
    public boolean mkdir()
    public boolean mkdirs()

  B:删除功能:
    public boolean delete():删除此抽象路径名表示的文件或目录。

  C:重命名功能
    public boolean renameTo(File dest)

  D:判断功能
    public boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
    public boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
    public boolean exists():测试此抽象路径名表示的文件或目录是否存在。
    public boolean canRead():测试应用程序是否可以读取此抽象路径名表示的文件。 是否可读
    public boolean canWrite():测试应用程序是否可以修改此抽象路径名表示的文件。 是否可写
    public boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。

  E:获取功能
    public File getAbsoluteFile():返回此抽象路径名的绝对路径名形式。
    public String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
    public String getPath():将此抽象路径名转换为一个路径名字符串。获取相对路径。
    public String getName():返回由此抽象路径名表示的文件或目录的名称。
    public long length():返回由此抽象路径名表示的文件的长度(字节数)。如果此路径名表示一个目录,则返回值是不确定的。
    public long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间(毫秒值)。

  F:高级获取功能
    public String[] list():返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。 获取指定目录下的所有文件或者文件夹的名称数组
    public File[] listFiles():返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 获取指定目录下的所有文件或者文件夹的File数组

  G:过滤器功能
    public String[] list(FilenameFilter filter):返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。
    public File[] listFiles(FilenameFilter filter):返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
    public File[] listFiles(FileFilter filter):返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
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
/*
* 创建功能:
*/
public class FileDemo {
public static void main(String[] args) throws IOException {
// 需求:我要在G盘目录下创建一个文件夹demo
File file = new File("G:\\demo");
System.out.println("mkdir:" + file.mkdir());

// 需求:我要在G盘目录demo下创建一个文件a.txt
File file2 = new File("G:\\demo\\a.txt");
System.out.println("createNewFile:" + file2.createNewFile());

// 需求:我要在G盘目录test下创建一个文件b.txt
// Exception in thread "main" java.io.IOException: 系统找不到指定的路径。
// 注意:要想在某个目录下创建内容,该目录首先必须存在。
// File file3 = new File("G:\\test\\b.txt");
// System.out.println("createNewFile:" + file3.createNewFile());

// 需求:我要在G盘目录test下创建aaa目录
// File file4 = new File("G:\\test\\aaa");
// System.out.println("mkdir:" + file4.mkdir());

// File file5 = new File("G:\\test");
// File file6 = new File("G:\\test\\aaa");
// System.out.println("mkdir:" + file5.mkdir());
// System.out.println("mkdir:" + file6.mkdir());

// 其实我们有更简单的方法
// File file7 = new File("G:\\aaa\\bbb\\ccc\\ddd");
// System.out.println("mkdirs:" + file7.mkdirs());

// 看下面的这个东西:
// File file8 = new File("G:\\liuyi\\a.txt\\aaa");
// System.out.println("mkdirs:" + file8.mkdirs());// 只能创建文件夹
}
}
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
/*
* 删除功能:public boolean delete()
*
* 注意:
* A:如果你创建文件或者文件夹忘了写盘符路径,那么,默认在项目路径下。
* B:Java中的删除不走回收站。
* C:如果此路径名表示一个目录(文件夹),则该目录必须为空才能删除。
*/
public class FileDemo {
public static void main(String[] args) throws IOException {
// 创建文件
// File file = new File("e:\\a.txt");
// System.out.println("createNewFile:" + file.createNewFile());

// 不写盘符路径时,默认在项目路径下
File file = new File("a.txt");
System.out.println("createNewFile:" + file.createNewFile());
System.out.println("delete:" + file.delete());

File file2 = new File("aaa\\bbb\\ccc");
System.out.println("mkdirs:" + file2.mkdirs());

// 删除功能:我要删除aaa文件夹,必须先删ccc文件夹,再删bbb文件夹,最后才能删除aaa文件夹
// File file4 = new File("aaa\\bbb\\ccc");
// File file6 = new File("aaa\\bbb");
File file7 = new File("aaa");
// System.out.println("delete:" + file4.delete());
// System.out.println("delete:" + file6.delete());
System.out.println("delete:" + file7.delete());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* 重命名功能:public boolean renameTo(File dest)
* 如果路径名相同,就是改名。
* 如果路径名不同,就是改名并剪切。
*
* 路径以盘符开始:绝对路径 c:\\a.txt
* 路径不以盘符开始:相对路径 a.txt
*/
public class FileDemo {
public static void main(String[] args) throws IOException {
// 创建一个文件对象
// File file = new File("林青霞.jpg");
// System.out.println(file.createNewFile());
// 需求:我要修改这个文件的名称为"东方不败.jpg"
// File newFile = new File("东方不败.jpg");
// System.out.println("renameTo:" + file.renameTo(newFile));

File file2 = new File("东方不败.jpg");
File newFile2 = new File("G:\\林青霞.jpg");
System.out.println("renameTo:" + file2.renameTo(newFile2));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* 判断功能:
*/
public class FileDemo {
public static void main(String[] args) throws IOException {
// 创建文件对象
File file = new File("a.txt");
System.out.println(file.createNewFile());

System.out.println("isDirectory:" + file.isDirectory());// false
System.out.println("isFile:" + file.isFile());// true
System.out.println("exists:" + file.exists());// true
System.out.println("canRead:" + file.canRead());// true
System.out.println("canWrite:" + file.canWrite());// true
System.out.println("isHidden:" + file.isHidden());// false
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* 获取功能:
*/
public class FileDemo {
public static void main(String[] args) throws IOException {
// 创建文件对象
File file = new File("test.txt");
file.createNewFile();

System.out.println("getAbsolutePath:" + file.getAbsolutePath()); // E:\WorkspacesOxygen\DemosSpace\JavaProject\test.txt
System.out.println("getPath:" + file.getPath()); // test.txt
System.out.println("getName:" + file.getName()); // test.txt
System.out.println("length:" + file.length()); // 0
System.out.println("lastModified:" + file.lastModified()); // 1491468425566

// 1491468425566
Date d = new Date(1491468425566L);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(d);
System.out.println(s);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* 高级获取功能:
*/
public class FileDemo {
public static void main(String[] args) {
// 指定一个目录
File file = new File("G:\\");

// public String[] list():获取指定目录下的所有文件或者文件夹的名称数组
String[] strArray = file.list();
for (String s : strArray) {
System.out.println(s);
}
System.out.println("------------");

// public File[] listFiles():获取指定目录下的所有文件或者文件夹的File数组
File[] fileArray = file.listFiles();
for (File f : fileArray) {
System.out.println(f.getName());
}
}
}

案例

  A:输出指定目录下指定后缀名的文件名称

    a:先获取所有的,在遍历的时候判断,再输出

    b:先判断,再获取,最后直接遍历输出即可

  B:批量修改文件名称

案例A

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
/*
* 判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出此文件名称
*
* 分析:
* A:封装e判断目录
* B:获取该目录下所有文件或者文件夹的File数组
* C:遍历该File数组,得到每一个File对象,然后判断
* D:是否是文件
* 是:继续判断是否以.jpg结尾
* 是:就输出该文件名称
* 否:不搭理它
* 否:不搭理它
*/
public class FileDemo {
public static void main(String[] args) {
// 封装e判断目录
File file = new File("e:\\");

// 获取该目录下所有文件或者文件夹的File数组
File[] fileArray = file.listFiles();

// 遍历该File数组,得到每一个File对象,然后判断
for (File f : fileArray) {
// 是否是文件
if (f.isFile()) {
// 继续判断是否以.jpg结尾
if (f.getName().endsWith(".jpg")) {
// 就输出该文件名称
System.out.println(f.getName());
}
}
}
}
}
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
/*
* 判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出此文件名称
* A:先获取所有的,然后遍历的时候,依次判断,如果满足条件就输出。
* B:获取的时候就已经是满足条件的了,然后输出即可。要想实现这个效果,就必须学习一个接口:文件名称过滤器
*
* public String[] list(FilenameFilter filter)
* public File[] listFiles(FilenameFilter filter)
*/
public class FileDemo {
public static void main(String[] args) {
// 封装e判断目录
File file = new File("G:\\");

// 获取该目录下所有文件或者文件夹的String数组
// public String[] list(FilenameFilter filter)
String[] strArray = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// return false;
// return true;
// 通过这个测试,我们就知道了,到底把这个文件或者文件夹的名称加不加到数组中,取决于这里的返回值是true还是false
// 所以,这个的true或者false应该是我们通过某种判断得到的
// System.out.println(dir + "---" + name);
// File file = new File(dir, name);
// // System.ot.println(file);
// boolean flag = file.isFile();
// boolean flag2 = name.endsWith(".jpg");
// return flag && flag2;
return new File(dir, name).isFile() && name.endsWith(".jpg");
}
});

// 遍历
for (String s : strArray) {
System.out.println(s);
}
}
}

案例B:

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
/*
* 需求:把E:\评书\三国演义下面的视频名称修改为
* 00?_介绍.avi
*
* 思路:
* A:封装目录
* B:获取该目录下所有的文件的File数组
* C:遍历该File数组,得到每一个File对象
* D:拼接一个新的名称,然后重命名即可
*/
public class FileDemo {
public static void main(String[] args) {
// 封装目录
File srcFolder = new File("G:\\评书\\三国演义");

// 获取该目录下所有的文件的File数组
File[] fileArray = srcFolder.listFiles();

// 遍历该File数组,得到每一个File对象
for (File file : fileArray) {
// System.out.println(file);
// E:\评书\三国演义\三国演义_001_[评书网-今天很高兴,明天就IO了]_桃园三结义.avi
// 改后:E:\评书\三国演义\001_桃园三结义.avi
String name = file.getName(); // 三国演义_001_[评书网-今天很高兴,明天就IO了]_桃园三结义.avi

int index = name.indexOf("_");
String numberString = name.substring(index + 1, index + 4);
// System.out.println(numberString);

// int startIndex = name.lastIndexOf('_');
// int endIndex = name.lastIndexOf('.');
// String nameString = name.substring(startIndex + 1, endIndex);
// System.out.println(nameString);
int endIndex = name.lastIndexOf('_');
String nameString = name.substring(endIndex);

String newName = numberString.concat(nameString); // 001_桃园三结义.avi
// System.out.println(newName);

File newFile = new File(srcFolder, newName); // E:\\评书\\三国演义\\001_桃园三结义.avi

// 重命名即可
file.renameTo(newFile);
}
}
}

递归

​ 方法定义中调用方法本身的现象

注意事项

  A:要有出口,否则就是死递归

  B:次数不能过多,否则内存溢出

  C:构造方法不能递归使用

案例

  A:递归求阶乘

  B:兔子问题

  C:递归输出指定目录下所有指定后缀名的文件绝对路径

  D:递归删除带内容的目录(小心使用)

案例A:

img

img

案例B:

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
/**
* 有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问第二十个月的兔子对数为多少?
* 分析:我们要想办法找规律
* 兔子对数
* 第一个月: 1
* 第二个月: 1
* 第三个月: 2
* 第四个月: 3
* 第五个月: 5
* 第六个月: 8
* ...
*
* 由此可见兔子对象的数据是:
* 1,1,2,3,5,8...
* 规则:
* A:从第三项开始,每一项是前两项之和
* B:而且说明前两项是已知的
*
* 如何实现这个程序呢?
* A:数组实现
* B:变量的变化实现
* C:递归实现
*
* 假如相邻的两个月的兔子对数是a,b
* 第一个相邻的数据:a=1,b=1
* 第二个相邻的数据:a=1,b=2
* 第三个相邻的数据:a=2,b=3
* 第四个相邻的数据:a=3,b=5
* 看到了:下一次的a是以前的b,下一次是以前的a+b
*/
public class DiGuiDemo2 {
public static void main(String[] args) {
/************************ A:数组实现 ************************/
// 定义一个数组
int[] arr = new int[20];
arr[0] = 1;
arr[1] = 1;
// arr[2] = arr[0] + arr[1];
// arr[3] = arr[1] + arr[2];
// ...
for (int x = 2; x < arr.length; x++) {
arr[x] = arr[x - 2] + arr[x - 1];
}
System.out.println(arr[19]); // 6765
/************************ A:数组实现 ************************/

/************************ B:变量的变化实现 ************************/
int a = 1;
int b = 1;
for (int x = 0; x < 18; x++) {
// 临时变量存储上一次的a
int temp = a;
a = b;
b = temp + b;
}
System.out.println(b); // 6765
/************************ B:变量的变化实现 ************************/

/************************ C:递归实现 ************************/
System.out.println(fib(20, 1)); // 6765
/************************ C:递归实现 ************************/
}

/**
*
* @param mouth
* 月数
* @param num
* 初始兔子数量
* @return mouth 月后的兔子数量
*/
public static int fib(int mouth, int num) {
if (mouth == 1 || mouth == 2) {
return num;
} else {
return fib(mouth - 1, num) + fib(mouth - 2, num);
}
}
}

案例C:

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
/**
* 需求:请大家把G:\JavaSE目录下所有的java结尾的文件的绝对路径给输出在控制台。
*
* 分析:
* A:封装目录
* B:获取该目录下所有的文件或者文件夹的File数组
* C:遍历该File数组,得到每一个File对象
* D:判断该File对象是否是文件夹
* 是:回到B
* 否:继续判断是否以.java结尾
* 是:就输出该文件的绝对路径
* 否:不搭理它
*/
public class FilePathDemo {
public static void main(String[] args) {
// 封装目录
File srcFolder = new File("G:\\JavaSE");

// 递归功能实现
getAllJavaFilePaths(srcFolder);
}

private static void getAllJavaFilePaths(File srcFolder) {
// 获取该目录下所有的文件或者文件夹的File数组
File[] fileArray = srcFolder.listFiles();// 如果是根目录,有些有些受保护的文件调用该方法会返回null。自己可以加个判断,不是null再去遍历即可。

// 遍历该File数组,得到每一个File对象
for (File file : fileArray) {
// 判断该File对象是否是文件夹
if (file.isDirectory()) {
getAllJavaFilePaths(file);
} else {
// 继续判断是否以.java结尾
if (file.getName().endsWith(".java")) {
// 就输出该文件的绝对路径
System.out.println(file.getAbsolutePath());
}
}
}
}
}

案例D:

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
/**
* 需求:递归删除带内容的目录
*
* 目录我已经给定:demo
*
* 分析:
* A:封装目录
* B:获取该目录下的所有文件或者文件夹的File数组
* C:遍历该File数组,得到每一个File对象
* D:判断该File对象是否是文件夹
* 是:回到B
* 否:就删除
*/
public class FileDeleteDemo {
public static void main(String[] args) {
// 封装目录
File srcFolder = new File("G:\\JavaSE");
// 递归实现
deleteFolder(srcFolder);
}

private static void deleteFolder(File srcFolder) {
// 获取该目录下的所有文件或者文件夹的File数组
File[] fileArray = srcFolder.listFiles();

if (fileArray != null) {
// 遍历该File数组,得到每一个File对象
for (File file : fileArray) {
// 判断该File对象是否是文件夹
if (file.isDirectory()) {
deleteFolder(file);
} else {
System.out.println(file.getName() + "---" + file.delete());
}
}

System.out.println(srcFolder.getName() + "---" + srcFolder.delete());
} else {
System.out.println("该目录不存在!");
}
}
}

作用

​ IO流用来处理设备之间的数据传输(上传文件和下载文件)。

  Java对数据的操作的通过流的方式。

  Java用于操作流的对象都在IO包中。

流的分类

一般我们在讨论IO流的时候,如果没有明确说明按照什么分,默认按照数据类型分。

  A:数据流向(方向)

    输入流 取数据

    输出流 出数据

  B:数据类型(单位)

    字节流(操作二进制文件,用Windows记事本打开读不懂的文件,英文也可以用字节流)

      字节输入流 InputStream

        |– FileInputStream

        |– BufferedInputStream

      字节输出流 OutputStream

        |– FileOutputStream

        |– BufferedOutputStream

    字符流(操作文本文件,用Windows记事本打开能读懂的文件,包括中文、英文等各国语言)

      字符输入流 Reader

        |– InputStreamReader

          |– FileReader

        |– BufferedReader

      字符输出流 Writer

        |– OutputStreamWriter

          |– FileWriter

        |– BufferedWriter

img

  C:功能

    节点流,管道流(处理流)

输入流与输出流的区别

​ 1).无论文件是否存在,输出流会自动创建文件。而输入流不会自动创建文件。

​ 2).输出流有flush()方法,输入流没有此方法。

图解

img

字节流

FileOutputStream

构造方法

​ 没有无参构造,必须要知道往哪里写

1
2
3
4
5
6
public FileOutputStream(File file) :创建一个向指定 File 对象表示的文件中写入数据的文件输出流。参数:file - 为了进行写入而打开的文件。、
public FileOutputStream(String name) :创建一个向具有指定名称的文件中写入数据的输出文件流。参数:name - 与系统有关的文件名 。
public FileOutputStream(File file, boolean append):创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
                           如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
public FileOutputStream(String name, boolean append):创建一个向具有指定 name 的文件中写入数据的输出文件流。
                             如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。

成员方法

1
2
3
4
public void write(int b): 将指定字节写入此文件输出流。参数:b - 要写入的字节。
public void write(byte[] b): 将 b.length 个字节从指定 byte 数组写入此文件输出流中。
public void write(byte[] b,int off,int len): 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
public void close():关闭此文件输出流并释放与此流有关的所有系统资源。此文件输出流不能再用于写入字节。如果此流有一个与之关联的通道,则关闭该通道。

操作步骤

​ 1:创建字节输出流对象

​ 2:写数据,调用write()方法

​ 3:释放资源

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
/*
* 查看FileOutputStream的构造方法:
* FileOutputStream(File file)
* FileOutputStream(String name)// 看源码,其实本质是一样的。
*/
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
// FileOutputStream(File file)
// File file = new File("fos.txt");
// FileOutputStream fos = new FileOutputStream(file);

// FileOutputStream(String name)
FileOutputStream fos = new FileOutputStream("fos.txt");
// 实现数据的追加写入,用构造方法带第二个参数是true即可。
FileOutputStream fos2 = new FileOutputStream("fos.txt", true);
/*
创建字节输出流对象了做了几件事情:
A:调用系统功能去创建文件
B:创建fos对象
C:把fos对象指向这个文件
*/

//写数据
fos.write("hello,IO".getBytes());

fos.write(97); // 97 -- 底层二进制数据 -- 通过记事本打开 -- 找97对应的字符值 -- a
fos.write(57); // 57 -- 底层二进制数据 -- 通过记事本打开 -- 找57对应的字符值 -- 9
fos.write(55); // 55 -- 底层二进制数据 -- 通过记事本打开 -- 找55对应的字符值 -- 7

byte[] bys={97,98,99,100,101};
fos.write(bys);
fos.write(bys,1,3);

/*
实现数据的换行,不同的系统针对不同的换行符号识别是不一样的
windows:\r\n
linux:\n
Mac:\r
一些常见的高级记事本,是可以识别任意换行符号的。
*/
for (int x = 0; x < 10; x++) {
fos.write(("hello" + x).getBytes());
fos.write("\r\n".getBytes());
}

//释放资源
//关闭此文件输出流并释放与此流有关的所有系统资源。
fos.close();
/*
为什么一定要close()呢?
A:让流对象变成垃圾(fos),这样就可以被垃圾回收器回收了
B:通知系统去释放跟该文件相关的资源
*/
//java.io.IOException: Stream Closed
fos.write("java".getBytes());//因为流已关闭,所以写不进去。
}
}
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
/*
* 加入异常处理的字节输出流操作
*/
public class FileOutputStreamDemo4 {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("z:\\fos.txt");// 报两个异常
// fos = new FileOutputStream("fos.txt");
fos.write("java".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 如果fos不是null,才需要close()
// if (fos != null) {
try {
fos.close();// 为了保证close()一定会执行,就放到fianlly里。
} catch (IOException e) {
e.printStackTrace();
}
// }
}
}
}

FileInputStream

构造方法

​ 没有无参构造,必须要知道从哪里写

1
2
3
4
public FileInputStream(File file):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
                   参数: file - 为了进行读取而打开的文件。
public FileInputStream(String name):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
                     参数: name - 与系统有关的文件名。

成员方法

1
2
3
4
5
6
7
8
9
public int read():一次读一个字节,换行符号也能读到。读完后指针将指向下一个字节,有点类似迭代器的next()方法。
            从此输入流中读取一个数据字节。如果没有输入可用,则此方法将阻塞。
返回:下一个数据字节;如果已到达文件末尾,则返回 -1
public int read(byte[] b):一次读一个字节数组。
            从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。在某些输入可用之前,此方法将阻塞。
            返回:读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1
public int read(byte[] b, int off, int len):从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
            如果 len 不为 0,则在输入可用之前,该方法将阻塞;否则,不读取任何字节并返回 0
返回: 读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1

操作步骤

​ 1:创建字节输入流对象

​ 2:读数据,调用read()方法,并把数据显示到控制台

​ 3:释放资源

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
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
// 创建字节输入流对象
FileInputStream fis = new FileInputStream("fis.txt");

// 方式一:一次读一个字节
int by = 0;
// 读取,赋值,判断
while ((by = fis.read()) != -1) {
System.out.print((char) by); // 输出到控制台,中文会乱码。但是,和输出流一起使用,复制中文文件是没问题的。
}

// 方式二:一次读一个字节数组(每次可以读取多个数据,提高了操作效率)
// 数组的长度一般是1024或者1024的整数倍
byte[] bys = new byte[1024]; // 1K 理论上来说,比方式一的效率高1024倍。
int len = 0;
while ((len = fis.read(bys)) != -1) {
// System.out.print(len);
// System.out.print(new String(bys));
System.out.print(new String(bys, 0, len)); // 输出到控制台,中文有可能乱码(当第1024个字节刚好是一个中文的时候)。和输出流一起使用,复制中文文件是没问题的。
}

// 释放资源
fis.close();
}
}

案例

使用2种方式实现

  A:复制文本文件

  B:复制图片

  C:复制视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 一次读一个字节,太慢了
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.txt");// java.io.FileNotFoundException: a.txt (系统找不到指定的文件。)
// 封装目的地
FileOutputStream fos = new FileOutputStream("b.txt");// 输出流会自动创建文件

int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}

// 释放资源(先关谁都行)
fos.close();
fis.close();
}
}

这一次复制中文确没有乱码,为什么?

上一次我们出现问题的原因在于我们每次获取到一个字节数据,就把该字节数据转换为了字符数据,然后输出到控制台。而现在,通过IO流读取数据,写到文本文件,你读取一个字节,我就写入一个字节,你没有做任何的转换。它会自己做转换,两个字节拼接成一个字符。看下面代码。

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
/*
* 计算机是如何识别什么时候该把两个字节转换为一个中文呢?
* 在计算机中中文的存储分两个字节:
* 第一个字节肯定是负数。
* 第二个字节常见的是负数,可能有正数。但是没影响。
* 计算机遇到负数就会和后面的数(无论正负)拼接成一个字符
*/
public class StringDemo {
public static void main(String[] args) {
String s1 = "abcde";
byte[] by1 = s1.getBytes();
System.out.println(Arrays.toString(by1));// [97, 98, 99, 100, 101]

String s2 = "我爱你中国";
byte[] by2;

try {
by2 = s2.getBytes("GBK");
System.out.println(Arrays.toString(by2));// [-50, -46, -80, -82, -60, -29, -42, -48, -71, -6]
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

char[] ch = s2.toCharArray();
System.out.println(ch);// 我爱你中国
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 一次读一个字节数组,效率高
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.txt");
FileOutputStream fos = new FileOutputStream("b.txt");

// 复制数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}

// 释放资源
fos.close();
fis.close();
}
}

案例B:复制图片,只能用字节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 一次读一个字节,太慢了
public class CopyImageDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.jpg");
// 封装目的地
FileOutputStream fos = new FileOutputStream("b.jpg");

// 复制数据
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}

// 释放资源
fos.close();
fis.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 一次读一个字节数组,效率高
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.jpg");
FileOutputStream fos = new FileOutputStream("b.jpg");

// 复制数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}

// 释放资源
fos.close();
fis.close();
}
}

案例C:复制视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 一次读一个字节,太慢了
public class CopyMVDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.avi");
// 封装目的地
FileOutputStream fos = new FileOutputStream("b.avi");

// 复制数据
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}

// 释放资源
fos.close();
fis.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 一次读一个字节数组,效率高
public class CopyMp4Demo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.avi");
// 封装目的地
FileOutputStream fos = new FileOutputStream("b.avi");

// 复制数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}

// 释放资源
fos.close();
fis.close();
}
}

字节缓冲区流

  字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式),所以提供了字节缓冲区流(带缓冲区的字节类)这种类被称为:缓冲区类(高效类)

  构造方法可以指定缓冲区的大小,但是我们一般用不上,因为默认缓冲区大小就足够了。

  为什么不传递一个具体的文件或者文件路径,而是传递一个OutputStream对象呢?

    原因很简单,字节缓冲区流仅仅提供缓冲区,为高效而设计的。但是呢,真正的读写操作还得靠基本的流对象实现。

  A:BufferedOutputStream字节缓冲输出流,写入数据。该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

    构造方法:

      public BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。

      public BufferedOutputStream(OutputStream out, int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。

  B:BufferedInputStream字节缓冲输入流,读取数据。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。(看API)

    构造方法:

      public BufferedInputStream(InputStream in):创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。创建一个内部缓冲区数组并将其存储在 buf 中。

      public BufferedInputStream(InputStream in, int size):创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。

                              创建一个长度为 size 的内部缓冲区数组并将其存储在 buf 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BufferedOutputStreamDemo {
public static void main(String[] args) throws IOException {
// FileOutputStream fos = new FileOutputStream("bos.txt");
// BufferedOutputStream bos = new BufferedOutputStream(fos);
// 简单写法
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

// 写数据
bos.write("hello".getBytes());

// 释放资源
bos.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* 注意:虽然我们有两种方式可以读取,但是,请注意,这两种方式针对同一个对象在一个代码中只能使用一个。因为read()方法类似迭代器的next()方法。读到末尾无法再读。
*/
public class BufferedInputStreamDemo {
public static void main(String[] args) throws IOException {
// BufferedInputStream(InputStream in)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bos.txt"));

// 读取数据
// int by = 0;
// while ((by = bis.read()) != -1) {
// System.out.print((char) by);
// }

byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}

// 释放资源
bis.close();
}
}

案例

4种实现

  A:复制文本文件

  B:复制图片

  C:复制视频

案例C:

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
/*
* 需求:把G:\\a.avi复制到G:\\b.avi中
*
* 字节流四种方式复制文件:
* 基本字节流一次读写一个字节: 共耗时24199毫秒
* 基本字节流一次读写一个字节数组: 共耗时40毫秒
* 高效字节流一次读写一个字节: 共耗时200毫秒
* 高效字节流一次读写一个字节数组: 共耗时17毫秒
*/
public class CopyAVIDemo {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
method4("G:\\a.avi", "G:\\b.avi");
long end = System.currentTimeMillis();
System.out.println("共耗时" + (end - start) + "毫秒");
}

// 高效字节流一次读写一个字节数组,共耗时17毫秒
public static void method4(String string, String string2) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(string));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(string2, true));

byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}

bos.close();
bis.close();
}

// 高效字节流一次读写一个字节,共耗时200毫秒
public static void method3(String string, String string2) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(string));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(string2));

int by = 0;
while ((by = bis.read()) != -1) {
bos.write(by);
}

bos.close();
bis.close();
}

// 基本字节流一次读写一个字节数组,共耗时40毫秒
public static void method2(String string, String string2) throws IOException {
FileInputStream fis = new FileInputStream(string);
FileOutputStream fos = new FileOutputStream(string2);

byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}

fos.close();
fis.close();
}

// 基本字节流一次读写一个字节,共耗时24199毫秒
public static void method1(String string, String string2) throws IOException {
FileInputStream fis = new FileInputStream(string);
FileOutputStream fos = new FileOutputStream(string2);

int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}

fos.close();
fis.close();
}
}

字符流

介绍

​ 字节流操作中文数据不是特别的方便,所以就出现了转换流。转换流的作用就是把字节流转换字符流来使用。

​ 转换流其实是一个字符流。

字符流 = 字节流 + 编码表

编码表

就是由字符和对应的数值组成的一张表。

1、计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一 一对应,形成一张表。

    ASCII:美国标准信息交换码。用一个字节的7位可以表示。

    ISO-8859-1:拉丁码表,欧洲码表。用一个字节的8位表示。

    GB2312:中国的中文编码表。

    GBK:中国的中文编码表升级,融合了更多的中文文字符号。

    GB18030:GBK的取代版本

    BIG-5码 :通行于台湾、香港地区的一个繁体字编码方案,俗称”大五码”。地区标准号为:CNS11643,这就是人们讲的BIG-5

    Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode

    UTF-8:最多用三个字节来表示一个字符。

    UTF-8不同,它定义了一种”区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:

      它将Unicode编码为00000000-0000007F的字符,用单个字节来表示

      它将Unicode编码为00000080-000007FF的字符用两个字节表示

      它将Unicode编码为00000800-0000FFFF的字符用3字节表示

2、字符串中的编码问题

    编码:把能看懂的变成看不懂的

      String – byte[]:使用String的构造方法

      public byte[] getBytes(Charset charset):使用给定的 charset (字符集)将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。

    解码:把看不懂的变成能看懂的

      byte[] – String:使用String的成员方法

      public String(byte[] bytes, String charsetName):通过使用指定的 charset (字符集)解码指定的 byte 数组,构造一个新的 String。

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
/*
* 发送端:今 -- 数值 -- 二进制 -- 发出去
* 接收端:接收 -- 二进制 -- 十进制 -- 数值 -- 字符 -- 今
* 编码问题简单,只要编码解码的格式是一致的。
*/
public class StringDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
String s = "你好";

// String -- byte[]
byte[] bys1 = s.getBytes(); //使用默认字符集编码 [-60, -29, -70, -61]
byte[] bys2 = s.getBytes("GBK"); // [-60, -29, -70, -61]
byte[] bys3 = s.getBytes("UTF-8"); // [-28, -67, -96, -27, -91, -67]
System.out.println(Arrays.toString(bys1));
System.out.println(Arrays.toString(bys2));
System.out.println(Arrays.toString(bys3));

// byte[] -- String
String ss1 = new String(bys1); // 你好
String ss2 = new String(bys2, "GBK"); // 你好
String ss3 = new String(bys1, "UTF-8"); // ???
System.out.println(ss1);
System.out.println(ss2);
System.out.println(ss3);
}
}

IO流中的编码问题

​ 编码问题其实很简单,编码只要一致即可

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 OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
// 创建对象
OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("osw.txt")); // 默认GBK
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("osw.txt"), "GBK"); // 指定GBK
OutputStreamWriter osw3 = new OutputStreamWriter(new FileOutputStream("osw.txt"), "UTF-8"); // 指定UTF-8

InputStreamReader isr1 = new InputStreamReader(new FileInputStream("osw.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("osw.txt"), "GBK");
InputStreamReader isr3 = new InputStreamReader(new FileInputStream("osw.txt"), "UTF-8");
// 写数据
osw2.write("中国");// 父类的方法,写入字符串

// 释放资源
osw1.close();
osw2.close();
osw3.close();

// 读取数据,一次读取一个字符
int ch = 0;
while ((ch = isr3.read()) != -1) {
System.out.println(ch);
}

isr1.close();
isr2.close();
isr3.close();
}
}

OutputStreamWriter

字符流通向字节流的桥梁。按指定字符集写入字符

  构造方法:

    public OutputStreamWriter(OutputStream out):创建使用默认字符编码的 OutputStreamWriter。

    public OutputStreamWriter(OutputStream out, String charsetName):创建使用指定字符集的 OutputStreamWriter。指定编码。

  成员方法:

    public void write(int c):写入单个字符

    public void write(char[] cbuf):写入字符数组。是父类中的方法

    public void write(char[] cbuf, int off, int len):写入字符数组的某一部分。

    public void write(String str):写入字符串。是父类中的方法

    public void write(String str, int off, int len):写入字符串的某一部分。

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
/*
* close()和flush()的区别?
* A:close():先刷新一次缓冲区, 再关闭流对象,流对象不可以继续再使用了。
* B:flush():仅仅刷新缓冲区,流对象还可以继续使用。
*/
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
// 创建对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt", true));

// 写数据
osw.write('a');
osw.write(97);

char[] chs = { 'a', 'b', 'c', 'd', 'e' };
osw.write(chs);
osw.write(chs, 1, 3);

osw.write("我爱林青霞\r\n");
osw.write("我爱林青霞", 2, 3);

// 刷新缓冲区
osw.flush();

osw.write("我爱林青霞", 2, 3);

// 释放资源
osw.close();
// osw.write("我爱林青霞", 2, 3);// java.io.IOException: Stream closed
}
}

InputStreamReader

字节流通向字符流的桥梁。按照指定字符集读取字符

  构造方法:

    public InputStreamReader(InputStream in):创建一个使用默认字符集的 InputStreamReader。

    public InputStreamReader(InputStream in, String charsetName):创建使用指定字符集的 InputStreamReader。

  成员方法:

    public int read():读取单个字符。返回:读取的字符,如果已到达流的末尾,则返回 -1 。一次读取一个字符

    public int read(char[] cbuf):将字符读入数组。返回:读取的字符数,如果已到达流的末尾,则返回 -1。是父类中的方法。一次读取一个字符数组

    public int read(char[] cbuf, int offset, int length):将字符读入数组中的某一部分。返回:读取的字符数,如果已到达流的末尾,则返回 -1 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
// 创建对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"));

// 一次读取一个字符
// int ch = 0;
// while ((ch = isr.read()) != -1) {
// System.out.print((char) ch);
// }

// 一次读取一个字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = isr.read(chs)) != -1) {
System.out.print(new String(chs, 0, len));
}

// 释放资源
isr.close();
}
}

FileWriter和FileReader

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
/*
* 由于我们常见的操作都是使用本地默认编码,所以,不用指定编码。
* 而转换流的名称有点长,所以,Java就提供了其子类供我们使用。
* OutputStreamWriter = FileOutputStream + 编码表(GBK)
* FileWriter = FileOutputStream + 编码表(GBK)
*
* InputStreamReader = FileInputStream + 编码表(GBK)
* FileReader = FileInputStream + 编码表(GBK)
*/
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileReader fr = new FileReader("a.txt");
// 封装目的地
FileWriter fw = new FileWriter("b.txt");

// 一次读取一个字符
// int ch = 0;
// while ((ch = fr.read()) != -1) {
// fw.write(ch);
// }

// 一次读取一个字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = fr.read(chs)) != -1) {
fw.write(chs, 0, len);
fw.flush();
}

// 释放资源
fw.close();
fr.close();
}
}

字符缓冲流

  A: BufferedWriter:字符缓冲输出流。将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。 可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。

    构造方法:

      public BufferedWriter(Writer out):创建一个使用默认大小输出缓冲区的缓冲字符输出流。

      public BufferedWriter(Writer out, int sz):创建一个使用给定大小输出缓冲区的新缓冲字符输出流。

    特殊方法:

      public void newLine():写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 (‘\n’) 符。

  B: BufferedReader:字符缓冲输入流。从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

    构造方法:

      public BufferedReader(Reader in):创建一个使用默认大小输入缓冲区的缓冲字符输入流。

      public BufferedReader(Reader in, int sz):创建一个使用指定大小输入缓冲区的缓冲字符输入流。

    特殊方法:

      public String readLine():读取一个文本行。通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行。

                  返回:包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
// BufferedWriter(Writer out)
// BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("bw.txt")));
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

bw.write("hello");
bw.write("world");
bw.write("java");
bw.flush();

bw.close();
}
}
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 BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// 创建字符缓冲输入流对象
// BufferedReader(Reader in)
BufferedReader br = new BufferedReader(new FileReader("bw.txt"));

// 方式1
int ch = 0;
while ((ch = br.read()) != -1) {
System.out.print((char) ch);
}

// 方式2
// char[] chs = new char[1024];
// int len = 0;
// while ((len = br.read(chs)) != -1) {
// System.out.print(new String(chs, 0, len));
// }

// 释放资源
br.close();
}
}
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
/*
* 字符缓冲流的特殊方法:
*/
public class BufferedDemo {
public static void main(String[] args) throws IOException {
// write();
read();
}

public static void read() throws IOException {
// 创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("bw.txt"));

// public String readLine():一次读取一行数据
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);// 不包含任何行终止符,所以用println。
}

//释放资源
br.close();
}

public static void write() throws IOException {
// 创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
for (int x = 0; x < 10; x++) {
bw.write("hello" + x);
// bw.write("\r\n");
bw.newLine();// 由系统属性决定换行符(行分隔符)
bw.flush();
}
bw.close();
}
}
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
/*
* 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
*
* 数据源:
* a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader -- BufferedReader
* 目的地:
* b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter -- BufferedWriter
*/
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));

// 两种方式其中的一种:一次读取一个字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = br.read(chs)) != -1) {
bw.write(chs, 0, len);
bw.flush();
}

// 释放资源
bw.close();
br.close();
}
}
/************************************************************************************/
public class CopyFileDemo2 {
public static void main(String[] args) throws IOException {
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));

// 读写数据
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}

// 释放资源
bw.close();
br.close();
}
}

案例

复制文本文件(5种方式)

数据操作流

数据操作流(操作基本类型数据的流)(理解)

一、可以操作基本类型的数据
二、流对象名称
    DataInputStream
    DataOutputStream

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
/*
* 可以读写基本数据类型的数据
* 数据输入流:DataInputStream
* DataInputStream(InputStream in)
* 数据输出流:DataOutputStream
* DataOutputStream(OutputStream out)
*/
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
// 写
// write();

// 读
read();
}

private static void read() throws IOException {
// DataInputStream(InputStream in)
// 创建数据输入流对象
DataInputStream dis = new DataInputStream(new FileInputStream("dos.txt"));

// 读数据
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
char c = dis.readChar();
boolean bb = dis.readBoolean();

// 释放资源
dis.close();

System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(c);
System.out.println(bb);
}

private static void write() throws IOException {
// DataOutputStream(OutputStream out)
// 创建数据输出流对象
DataOutputStream dos = new DataOutputStream(new FileOutputStream("dos.txt"));

// 写数据了
dos.writeByte(10);
dos.writeShort(100);
dos.writeInt(1000);
dos.writeLong(10000);
dos.writeFloat(12.34F);
dos.writeDouble(12.56);
dos.writeChar('a');
dos.writeBoolean(true);

// 释放资源
dos.close();
}
}

内存操作流

内存操作流

一、有些时候我们操作完毕后,未必需要产生一个文件,就可以使用内存操作流。例如:从数据库中取出二进制的文件,会用到 ByteArrayInputStream 。

二、三种

  A:ByteArrayInputStream,ByteArrayOutputStream

    ByteArrayInputStream:包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。

               关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。

  B:CharArrayReader,CharArrayWriter

  C:StringReader,StringWriter

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
/*
* 内存操作流:用于处理临时存储信息的,程序结束,数据就从内存中消失。
* 字节数组:
* ByteArrayInputStream
* ByteArrayOutputStream
* 字符数组:
* CharArrayReader
* CharArrayWriter
* 字符串:
* StringReader
* StringWriter
*/
public class ByteArrayStreamDemo {
public static void main(String[] args) throws IOException {
// 写数据
// ByteArrayOutputStream()
ByteArrayOutputStream baos = new ByteArrayOutputStream();

// 写数据
for (int x = 0; x < 10; x++) {
baos.write(("hello" + x).getBytes());
}

// 释放资源
// 通过查看源码我们知道这里什么都没做,所以根本不需要close()
// baos.close();

// public byte[] toByteArray()
byte[] bys = baos.toByteArray();

// 读数据
// ByteArrayInputStream(byte[] buf)
ByteArrayInputStream bais = new ByteArrayInputStream(bys);

int by = 0;
while ((by = bais.read()) != -1) {
System.out.print((char) by);
}

// bais.close();
}
}

打印流(掌握)

一、字节打印流,字符打印流

二、特点:

  A:只操作目的地,不操作数据源

  B:可以操作任意类型的数据

  C:如果启用了自动刷新,在调用println()方法的时候,能够换行并刷新

  D:可以直接操作文件

    问题:哪些流可以直接操作文件呢?

    看API,如果其构造方法能够同时接收File和String类型的参数,一般都是可以直接操作文件的

三、复制文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PrintWriterDemo {
public static void main(String args[]) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
PrintWriter pw = new PrintWriter(new FileWriter("b.txt"), true);

String line = null;
while ((line = br.readLine()) != null) {
pw.println(line);
}

pw.close();
br.close();
}
}
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
/*
* 打印流
* 字节流打印流 PrintStream
* 字符打印流 PrintWriter
*
* 打印流的特点:
* A:只有写数据的,没有读取数据。只能操作目的地,不能操作数据源。
* B:可以操作任意类型的数据。
* C:如果启动了自动刷新,能够自动刷新。
* D:该流是可以直接操作文本文件的。
* 哪些流对象是可以直接操作文本文件的呢?
* FileInputStream
* FileOutputStream
* FileReader
* FileWriter
* PrintStream
* PrintWriter
* 看API,查流对象的构造方法,如果同时有File类型和String类型的参数,一般来说就是可以直接操作文件的。
*
* 流:
* 基本流:就是能够直接读写文件的
* 高级流:在基本流基础上提供了一些其他的功能
*/
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
// 作为Writer的子类使用
PrintWriter pw = new PrintWriter("test.txt");

pw.write("hello");
pw.write("world");
pw.write("java");

pw.close();
}
}
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
/*
* 1:可以操作任意类型的数据。
* print()
* println()
* 2:启动自动刷新
* PrintWriter pw = new PrintWriter(new FileWriter("pw2.txt"), true);
* 还是应该调用println()的方法才可以
* 这个时候不仅仅自动刷新了,还实现了数据的换行。
*
* println()
* 其实等价于于:
* bw.write();
* bw.newLine();
* bw.flush();
*/
public class PrintWriterDemo2 {
public static void main(String[] args) throws IOException {
// 创建打印流对象
// PrintWriter pw = new PrintWriter("pw2.txt");
PrintWriter pw = new PrintWriter(new FileWriter("test.txt"), true);

// write()是搞不定的,怎么办呢?
// 我们就应该看看它的新方法
// pw.print(true);
// pw.print(100);
// pw.print("hello");

pw.println("hello");
pw.println(true);
pw.println(100);

pw.close();
}
}
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
/*
* 需求:DataStreamDemo.java复制到Copy.java中
* 数据源:
* DataStreamDemo.java -- 读取数据 -- FileReader -- BufferedReader
* 目的地:
* Copy.java -- 写出数据 -- FileWriter -- BufferedWriter -- PrintWriter
*/
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 以前的版本
// 封装数据源
// BufferedReader br = new BufferedReader(new FileReader("DataStreamDemo.java"));
// // 封装目的地
// BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
//
// String line = null;
// while ((line = br.readLine()) != null) {
// bw.write(line);
// bw.newLine();
// bw.flush();
// }
//
// bw.close();
// br.close();

// 打印流的改进版
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("DataStreamDemo.java"));
// 封装目的地
PrintWriter pw = new PrintWriter(new FileWriter("Copy.java"), true);

String line = null;
while ((line = br.readLine()) != null) {
pw.println(line);
}

pw.close();
br.close();
}
}

标准输入输出流

一、System类下面有这样的两个字段

​ in 标准输入流

​ out 标准输出流

二、三种键盘录入方式

  A:main方法的args接收参数

  B:System.in通过BufferedReader进行包装

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

  C:Scanner

    Scanner sc = new Scanner(System.in);

三、输出语句的原理和如何使用字符流输出数据

  A:原理

    System.out.println(“helloworld”);

    PrintStream ps = System.out;

    ps.println(“helloworld”);

  B:把System.out用字符缓冲流包装一下使用

    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

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
/*
* 标准输入输出流
* System类中的两个成员变量:
* public static final InputStream in “标准”输入流。
* public static final PrintStream out “标准”输出流。
*
* InputStream is = System.in;
* PrintStream ps = System.out;
*/
public class SystemOutDemo {
public static void main(String[] args) {
// 有这里的讲解我们就知道了,这个输出语句其本质是IO流操作,把数据输出到控制台。
System.out.println("helloworld");

// 获取标准输出流对象
PrintStream ps = System.out;
ps.println("helloworld");

ps.println();
// ps.print();//这个方法不存在

// System.out.println();
// System.out.print();
}
}
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
/*
* System.in 标准输入流。是从键盘获取数据的
*
* 键盘录入数据:
* A:main方法的args接收参数。
* java HelloWorld hello world java
* B:Scanner(JDK5以后的)
* Scanner sc = new Scanner(System.in);
* String s = sc.nextLine();
* int x = sc.nextInt()
* C:通过字符缓冲流包装标准输入流实现
* BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
*/
public class SystemInDemo {
public static void main(String[] args) throws IOException {
// //获取标准输入流
// InputStream is = System.in;// 一次读一个字节或一个字节数组
// //我要一次获取一行行不行呢?
// //行。
// //怎么实现呢?
// //要想实现,首先你得知道一次读取一行数据的方法是哪个呢?
// //readLine()
// //而这个方法在哪个类中呢?
// //BufferedReader
// //所以,你这次应该创建BufferedReader的对象,但是底层还是的使用标准输入流
// // BufferedReader br = new BufferedReader(is);
// //按照我们的推想,现在应该可以了,但是却报错了
// //原因是:字符缓冲流只能针对字符流操作,而你现在是字节流,所以不能是用?
// //那么,我还就想使用了,请大家给我一个解决方案?
// //把字节流转换为字符流,然后在通过字符缓冲流操作
// InputStreamReader isr = new InputStreamReader(is);
// BufferedReader br= new BufferedReader(isr);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

System.out.println("请输入一个字符串:");
String line = br.readLine();
System.out.println("你输入的字符串是:" + line);

System.out.println("请输入一个整数:");
// int i = Integer.parseInt(br.readLine());
line = br.readLine();
int i = Integer.parseInt(line);
System.out.println("你输入的整数是:" + i);

br.close();
}
}
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
/*
* 转换流的应用。
*/
public class SystemOutDemo2 {
public static void main(String[] args) throws IOException {
// 获取标准输入流
// // PrintStream ps = System.out;
// // OutputStream os = ps;
// OutputStream os = System.out; // 多态
// // 我能不能按照刚才使用标准输入流的方式一样把数据输出到控制台呢?
// OutputStreamWriter osw = new OutputStreamWriter(os);
// BufferedWriter bw = new BufferedWriter(osw);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

bw.write("hello");
bw.newLine();
// bw.flush();
bw.write("world");
bw.newLine();
// bw.flush();
bw.write("java");
bw.newLine();
bw.flush();

bw.close();
}
}

随机访问流

一、RandomAccessFile类,可以按照文件指针的位置读写数据。

  RandomAccessFile类既不是InputStream类的子类,也不是OutputStream类的子类。习惯上,仍然呈RandomAccessFile类创建的对象为一个流。RandomAccessFile流的指向既可以作为源也可以作为目的地。换句话说,当想对一个文件进行读写操作时,可以创建一个指向该文件的RandomAccessFile流,这样既可以从这个流读取文件的数据,也可以通过这个流向文件写入数据。

  RandomAccessFile类的两个构造方法:

    RandomAccessFile(String name, String mode):参数name用来确定一个文件名,给出创建的流的源(也是流的目的地)。参数mode取”r”(只读)或”rw”(可读写),决定创建的流对文件的操作权限。

    RandomAccessFile(File file, String mode):参数file是一个File对象,给出创建的流的源(也是流的目的地)。参数mode取”r”(只读)或”rw”(可读写),决定创建的流对文件的操作权限。创建对象时应捕获IOException异常。

  RandomAccessFile流对文件的读写方式更为灵活。因为以下两个方法:

    public void seek(long pos):设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。偏移量的设置可能会超出文件末尾。偏移量的设置超出文件末尾不会改变文件的长度。只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。 参数: pos - 从文件开头以字节为单位测量的偏移量位置,在该位置设置文件指针。

    public long getFilePointer():返回此文件中的当前偏移量。返回:到此文件开头的偏移量(以字节为单位),在该位置发生下一个读取或写入操作。

  RandomAccessFile类的常用方法有:

    public native long getFilePointer():获取当前流在文件中的读写位置。

    public native long length():获取文件的长度。

    public final byte readByte():从文件中读取一个字节。

    public final double readDouble():从文件中读取一个双精度浮点值(8个字节)。

    public final int readInt():从文件中读取一个int值(4个字节)。

    public final String readLine():从文件中读取一个文本行。

    public final String readUTF():从文件中读取一个UTF字符串。

    public void seek(long pos):定位当前流在文件中的读写的位置。

    public void write(byte b[]):写b.length个字节到文件。

    public final void writeDouble(double v):向文件写入一个双精度浮点值。

    public final void writeInt(int v):向文件写入一个int值。

    public final void writeUTF(String str):写入一个UTF字符串。

二、案例:

  A:写数据

  B:读数据

  C:获取和改变文件指针的位置

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
/*
* 随机访问流:
* RandomAccessFile类不属于流,是Object类的子类。
* 但它融合了InputStream和OutputStream的功能。
* 支持对文件的随机访问读取和写入。
*
* public RandomAccessFile(String name,String mode):第一个参数是文件路径,第二个参数是操作文件的模式。
* 模式有四种,我们最常用的一种叫"rw",这种方式表示我既可以写数据,也可以读取数据
*/
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
// write();
read();
}

private static void read() throws IOException {
// 创建随机访问流对象
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");

int i = raf.readInt();
System.out.println(i);
// 该文件指针可以通过 getFilePointer方法读取,并通过 seek 方法设置。
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());

char ch = raf.readChar();
System.out.println(ch);
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());

String s = raf.readUTF();
System.out.println(s);
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());

// 我不想重头开始了,我就要读取a,怎么办呢?
raf.seek(4);
ch = raf.readChar();
System.out.println(ch);
}

private static void write() throws IOException {
// 创建随机访问流对象
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");

// 怎么玩呢?
raf.writeInt(100);
raf.writeChar('a');
raf.writeUTF("中国");

raf.close();
}
}

合并流

一、把多个输入流的数据写到一个输出流中。

二、构造方法:

  A:SequenceInputStream(InputStream s1, InputStream s2)

  B:SequenceInputStream(Enumeration<? extends InputStream> e)

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
/*
* 以前的操作:
* a.txt -- b.txt
* c.txt -- d.txt
*
* 现在想要:
* a.txt+b.txt -- c.txt
*/
public class SequenceInputStreamDemo {
public static void main(String[] args) throws IOException {
// SequenceInputStream(InputStream s1, InputStream s2)
// 需求:把ByteArrayStreamDemo.java和DataStreamDemo.java的内容复制到Copy.java中
InputStream s1 = new FileInputStream("a.txt");
InputStream s2 = new FileInputStream("b.txt");
SequenceInputStream sis = new SequenceInputStream(s1, s2);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("c.txt"));

// 如何写读写呢,其实很简单,你就按照以前怎么读写,现在还是怎么读写
byte[] bys = new byte[1024];
int len = 0;
while ((len = sis.read(bys)) != -1) {
bos.write(bys, 0, len);
}

bos.close();
sis.close();
}
}
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
/*
* 以前的操作:
* a.txt -- b.txt
* c.txt -- d.txt
* e.txt -- f.txt
*
* 现在想要:
* a.txt+b.txt+c.txt -- d.txt
*/
public class SequenceInputStreamDemo2 {
public static void main(String[] args) throws IOException {
// 需求:把下面的三个文件的内容复制到Copy.java中
// ByteArrayStreamDemo.java,CopyFileDemo.java,DataStreamDemo.java

// SequenceInputStream(Enumeration e)
// 通过简单的回顾我们知道了Enumeration是Vector中的一个方法的返回值类型。
// Enumeration<E> elements()
Vector<InputStream> v = new Vector<InputStream>();
InputStream s1 = new FileInputStream("1.txt");
InputStream s2 = new FileInputStream("2.txt");
InputStream s3 = new FileInputStream("3.txt");
v.add(s1);
v.add(s2);
v.add(s3);
Enumeration<InputStream> en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("4.txt"));

// 如何写读写呢,其实很简单,你就按照以前怎么读写,现在还是怎么读写
byte[] bys = new byte[1024];
int len = 0;
while ((len = sis.read(bys)) != -1) {
bos.write(bys, 0, len);
}

bos.close();
sis.close();
}
}

序列化流(理解)

一、可以把对象写入文本文件或者在网络中传输

二、如何实现序列化呢?

  让被序列化的对象所属类实现序列化接口。

  该接口是一个标记接口。没有功能需要实现。

三、注意问题:

  把数据写到文件后,在去修改类会产生一个问题。

  如何解决该问题呢?

    在类文件中,给出一个固定的序列化id值。

    而且,这样也可以解决黄色警告线问题

四、

​ 什么时候序列化?

​ 如何实现序列化?

​ 什么是反序列化?

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
* 序列化流:把对象按照流一样的方式存入文本文件或者在网络中传输。对象 -- 流数据(ObjectOutputStream)
* 反序列化流:把文本文件中的流对象数据或者网络中的流对象数据还原成对象。流数据 -- 对象(ObjectInputStream)
*/
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 由于我们要对对象进行序列化,所以我们先自定义一个类
// 序列化数据其实就是把对象写到文本文件
// write();

read();
}

private static void read() throws IOException, ClassNotFoundException {
// 创建反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("oos.txt"));

// 还原对象
Object obj = ois.readObject();

// 释放资源
ois.close();

// 输出对象
System.out.println(obj);
}

private static void write() throws IOException {
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("oos.txt"));

// 创建对象
Person p = new Person("林青霞", 27);

// public final void writeObject(Object obj)
oos.writeObject(p);

// 释放资源
oos.close();
}
}
/*
* NotSerializableException:未序列化异常
*
* 类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。
* 该接口居然没有任何方法,类似于这种没有方法的接口被称为标记接口。
*
* 修改java文件后报错如下:
* java.io.InvalidClassException: Person; local class incompatible:stream classdesc serialVersionUID = -2071565876962058344, local class serialVersionUID = -8345153069362641443
* java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = -2071565876962058344
*
* 为什么会有问题呢?
* Person类实现了序列化接口,那么它本身也应该有一个标记值。
* 这个标记值假设是100。
* 开始的时候:
* Person.class -- id=100
* wirte数据: oos.txt -- id=100
* read数据: oos.txt -- id=100
*
* 现在:
* Person.class -- id=200
* wirte数据: oos.txt -- id=100
* read数据: oos.txt -- id=100
* 我们在实际开发中,可能还需要使用以前写过的数据,不能重新写入。怎么办呢?
* 回想一下原因是因为它们的id值不匹配。
* 每次修改java文件的内容的时候,class文件的id值都会发生改变。
* 而读取文件的时候,会和class文件中的id值进行匹配。所以,就会出问题。
* 但是呢,如果我有办法,让这个id值在java文件中是一个固定的值,这样,你修改文件的时候,这个id值还会发生改变吗?
* 不会。现在的关键是我如何能够知道这个id值如何表示的呢?
* 不用担心,你不用记住,也没关系,点击鼠标即可。
* 你难道没有看到黄色警告线吗?
*
* 我们要知道的是:
* 看到类实现了序列化接口的时候,要想解决黄色警告线问题,就可以自动产生一个序列化id值。
* 而且产生这个值以后,我们对类进行任何改动,它读取以前的数据是没有问题的。
*
* 注意:
* 我一个类中可能有很多的成员变量,有些我不想进行序列化。请问该怎么办呢?
* 使用transient关键字声明不需要序列化的成员变量
*/
class Person implements Serializable {

private static final long serialVersionUID = -2071565876962058344L;

private String name;

// private int age;

private transient int age;

// int age;

public Person() {
}

public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}

Properties

一、Properties是一个集合类,Hashtable的子类

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
/*
* Properties:属性集合类。是一个可以和IO流相结合使用的集合类。
* Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
*
* 是Hashtable的子类,说明是一个Map集合。
*/
public class PropertiesDemo {
public static void main(String[] args) {
// 作为Map集合的使用
// 下面这种用法是错误的,一定要看API,如果没有<>,就说明该类不是一个泛型类,在使用的时候就不能加泛型
// Properties<String, String> prop = new Properties<String, String>();

Properties prop = new Properties();

// 添加元素
prop.put("it002", "hello");
prop.put("it001", "world");
prop.put("it003", "java");

// System.out.println("prop:" + prop);

// 遍历集合
Set<Object> set = prop.keySet();
for (Object key : set) {
Object value = prop.get(key);
System.out.println(key + "---" + value);
}
}
}

二、特有功能

  A:public Object setProperty(String key,String value):添加元素

  B:public String getProperty(String key):获取元素

  C:public Set<String> stringPropertyNames():获取所有键的集合

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
public class PropertiesDemo2 {
public static void main(String[] args) {
// 创建集合对象
Properties prop = new Properties();

// 添加元素
// prop.put("11", "11");
prop.setProperty("张三", "30");
prop.setProperty("李四", "40");
prop.setProperty("王五", "50");

// public Set<String> stringPropertyNames():获取所有的键的集合
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
// String value = prop.getProperty(key);
String value = (String)prop.get(key);
System.out.println(key + "---" + value);
}
}
}


/*
class Hashtalbe<K,V> {
public V put(K key,V value) { ... }
}
class Properties extends Hashtable {
public V setProperty(String key, Stringvalue) {
return put(key,value);
}
}
*/

三、和IO流结合的方法

  把键值对形式的文本文件内容加载到集合中,(把文件中的数据读取到集合中)

  public void load(Reader reader)

  public void load(InputStream inStream)

  把集合中的数据存储到文本文件中

  public void store(Writer writer,String comments)

  public void store(OutputStream out,String comments)

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
/*
* 这里的集合必须是Properties集合:
* public void load(Reader reader):把文件中的数据读取到集合中
* public void store(Writer writer,String comments):把集合中的数据存储到文件
*
* 单机版游戏:
* 进度保存和加载。
* 三国群英传,三国志,仙剑奇侠传...
*
* 吕布=1
* 方天画戟=1
*/
public class PropertiesDemo3 {
public static void main(String[] args) throws IOException {
// myLoad();

myStore();
}

private static void myStore() throws IOException {
// 创建集合对象
Properties prop = new Properties();

prop.setProperty("林青霞", "27");
prop.setProperty("武鑫", "30");
prop.setProperty("刘晓曲", "18");

// public void store(Writer writer,String comments):把集合中的数据存储到文件
Writer w = new FileWriter("name.txt");
prop.store(w, "comments – 属性列表的描述");
w.close();
}

private static void myLoad() throws IOException {
Properties prop = new Properties();

// public void load(Reader reader):把文件中的数据读取到集合中
// 注意:这个文件的数据必须是键值对形式
Reader r = new FileReader("name.txt");
prop.load(r);
r.close();

System.out.println("prop:" + prop);
}
}

四、案例:

  A:根据给定的文件判断是否有键为”lisi”的,如果有就修改其值为100

  B:写一个程序实现控制猜数字小游戏程序不能玩超过5次

案例A:

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
/*
* 我有一个文本文件(user.txt),我知道数据是键值对形式的,但是不知道内容是什么。
* 请写一个程序判断是否有“lisi”这样的键存在,如果有就改变其实为”100”
*
* 分析:
* A:把文件中的数据加载到集合中
* B:遍历集合,获取得到每一个键
* C:判断键是否有为"lisi"的,如果有就修改其值为"100"
* D:把集合中的数据重新存储到文件中
*/
public class PropertiesTest {
public static void main(String[] args) throws IOException {
// 把文件中的数据加载到集合中
Properties prop = new Properties();
Reader r = new FileReader("user.txt");
prop.load(r);
r.close();

// 遍历集合,获取得到每一个键
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
// 判断键是否有为"lisi"的,如果有就修改其值为"100"
if ("lisi".equals(key)) {
prop.setProperty(key, "100");
break;
}
}

// 把集合中的数据重新存储到文件中
Writer w = new FileWriter("user.txt");
prop.store(w, null);
w.close();
}
}

案例B:

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
/*
* 我有一个猜数字小游戏的程序,请写一个程序实现在测试类中只能用5次,超过5次提示:游戏试玩已结束,请付费。
*/
public class PropertiesTest2 {
public static void main(String[] args) throws IOException {
// 读取某个地方的数据,如果次数不大于5,可以继续玩。否则就提示"游戏试玩已结束,请付费。"
// 创建一个文件
// File file = new File("count.txt");
// if (!file.exists()) {
// file.createNewFile();
// }

// 把数据加载到集合中
Properties prop = new Properties();
Reader r = new FileReader("count.txt");
prop.load(r);
r.close();

// 我自己的程序,我当然知道里面的键是谁
String value = prop.getProperty("count");
int number = Integer.parseInt(value);

if (number > 5) {
System.out.println("游戏试玩已结束,请付费。");
System.exit(0);
} else {
number++;
prop.setProperty("count", String.valueOf(number));
Writer w = new FileWriter("count.txt");
prop.store(w, null);
w.close();

GuessNumber.start();
}
}
}

/**
* 这是猜数字小游戏
*/
class GuessNumber {
private GuessNumber() {
}

public static void start() {
// 产生一个随机数
int number = (int) (Math.random() * 100) + 1;

// 定义一个统计变量
int count = 0;

while (true) {
// 键盘录入一个数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入数据(1-100):");
int guessNumber = sc.nextInt();

count++;

// 判断
if (guessNumber > number) {
System.out.println("你猜的数据" + guessNumber + "大了");
} else if (guessNumber < number) {
System.out.println("你猜的数据" + guessNumber + "小了");
} else {
System.out.println("恭喜你," + count + "次就猜中了");
break;
}
}
}
}

NIO

一、JDK4出现NIO。

  新IO和传统的IO有相同的目的,都是用于进行输入输出的,但新IO使用了不同的方式来处理输入输出,采用内存映射文件的方式,将文件或者文件的一段区域映射到内存中,就可以像访问内存一样的来访问文件了,这种方式效率比旧IO要高很多,但是目前好多地方我们看到的还是旧IO的引用。

  JDK4新IO要了解的类(看API)

  Buffer(缓冲),Channer(通道)

二、JDK7的NIO的使用

  Path:与平台无关的路径。

  Paths:包含了返回Path的静态方法。

    public static Path get(URI uri):根据给定的URI来确定文件路径。

  Files:操作文件的工具类。提供了大量的方法,简单了解如下方法

    public static long copy(Path source, OutputStream out) :复制文件

    public static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption… options):把集合的数据写到文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NIODemo {
public static void main(String[] args) throws IOException {
//复制文件
// public static long copy(Path source,OutputStream out)
Files.copy(Paths.get("a.txt"), new FileOutputStream("Copy.txt"));

//把集合中的数据写到文件
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("world");
array.add("java");
Files.write(Paths.get("array.txt"), array, Charset.forName("GBK"));
}
}

推荐:

正则表达式 - 维基百科

正则表达式 - 菜鸟

Java正则表达式 - 菜鸟

重用正则总结

1
([a-zA-Z -]*)([0-9]*)    # 利用捕获组匹配,可匹配字符串示例:"abc90"、"90",第一组为字符串(空或abc),第二组为数字(90)。

正则表达式

定义

  • 正则表达式定义了字符串的模式。
  • 正则表达式可以用来搜索、编辑或处理文本。
  • 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。

转义

  • 在 Java 中,\\ 表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊意义。
  • 在其他语言中(如Perl),一个反斜杠 \ 就足以具有转义的作用,而在 Java 中则需要有两个反斜杠才能被解析为其他语言中的转义作用。
  • Java 的正则表达式中,两个 \\ 代表其他语言中的一个 \,这也就是为什么表示一位数字的正则表达式是 \\d,而表示一个普通的反斜杠是 \\\\

常用元字符

^$*+?[a-z]\w\W{n, m}

常见规则

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
A:字符
x 字符 x。 举例:'a'表示字符a
\\ 反斜线字符。 两个杠才能表示一个杠,杠具有转义作用。
\n 新行(换行)符 ('\u000A')
\r 回车符 ('\u000D')
B:字符类
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a到 z 或 A到 Z,两头的字母包括在内(范围)
[0-9] 0到9的字符都包括
C:预定义字符类
. 任何字符(与行结束符可能匹配也可能不匹配)。'.' 字符本身,怎么表示呢? \.
\d 数字:[0-9]
\w 单词字符:[a-zA-Z_0-9]
D:边界匹配器
^ 行的开头
$ 行的结尾
\b 单词边界
不是单词字符的地方就是单词边界。
举例:hello world?haha;xixi 有三个单词边界
E:Greedy 数量词
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次

String类

String 类中有几个可以使用正则的方法,实际都是通过调用 Pattern、Matcher 类实现的

1
2
3
4
5
6
7
8
9
10
// 判断功能
public boolean matches(String regex)

// 替换功能
public String replaceFirst(String regex, String replacement)
public String replaceAll(String regex, String replacement)

// 分割功能
public String[] split(String regex)
public String[] split(String regex, int limit)

Pattern和Matcher类

Pattern 类:正则表达式的编译表示,没有公共构造方法。

使用方法:正则表达式字符串先被编译为此类的实例,然后用得到的 Pattern 对象创建 Matcher 对象。执行 matcher 方法后的所有匹配都驻留在匹配器 Matcher 中,所以多个匹配器可以共享同一模式。

1
2
3
4
public static Pattern compile(String regex) // 将给定的正则表达式编译成一个模式。
public static Pattern compile(String regex, int flags) // 将给定的正则表达式编译成具有给定标志的模式。
public Matcher matcher(CharSequence input) // 创建一个匹配器,将给定输入与此模式匹配。
public static boolean matches(String regex, CharSequence input) // 编译给定的正则表达式并尝试匹配给定的输入。

Matcher 类:没有公共构造方法。

通过解释 Pattern 对字符序列执行匹配操作的引擎

匹配器通过调用 Pattern 的 matcher 方法创建一个 Matcher 对象。创建后,Matcher 可用于执行三种不同类型的匹配操作:

  • matches 方法尝试将整个输入序列与模式匹配。
  • lookingAt 方法尝试将输入序列与模式匹配,从头开始。
  • find 方法扫描输入序列以查找与模式匹配的下一个子序列。

每个方法都返回一个表示成功或失败的布尔值。通过查询匹配器的状态可以获取关于成功匹配的更多信息。

如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。

1
2
3
public boolean matches():尝试将整个区域与模式匹配。
public boolean find():尝试查找与该模式匹配的输入序列的下一个子序列。 如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。
public String group():返回由以前匹配操作所匹配的输入子序列。以前匹配操作所匹配的字符串形式的子序列(可能为空)。

PatternSyntaxException 类:一个非强制异常类,它表示一个正则表达式模式中的语法错误。

具体使用说明及更多方法见 API。

典型调用顺序

1
2
3
4
5
6
7
8
Pattern p = Pattern.compile("a*b"); // 正则表达式字符串先被编译为Pattern实例。
Matcher m = p.matcher("aaaaab"); // 用得到的Pattern对象创建Matcher对象,所有状态都驻留在匹配器Matcher中。多个匹配器可以共享同一模式。
boolean b = m.matches();

Pattern.compile(regex).matcher(input).matches()

// 等同于
boolean b = Pattern.matches("a*b", "aaaaab")

练习

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
/*
* 获取功能
* Pattern和Matcher类的使用
* 模式和匹配器的基本使用顺序
*/
public class RegexDemo {
public static void main(String[] args) {
// 模式和匹配器的典型调用顺序
Pattern p = Pattern.compile("a*b");// 先吧正则表达式字符串编译为Pattern实例。
Matcher m = p.matcher("aaaaab");// 用得到的Pattern对象创建Matcher对象,所有状态都驻留在匹配器Matcher中。
boolean b = m.matches(); // 调用匹配器对象的功能
// boolean b = Pattern.compile("a*b").matcher("aaaaab").matches(); // 链式调用 Pattern.compile(regex).matcher(input).matches()
// boolean b = Pattern.matches("a*b", "aaaaab"); // 简化调用 Pattern.matches(regex, input)
System.out.println(b); // true

boolean bb = p.matcher("aaaaa").matches();// 多个匹配器可以共享同一模式。
System.out.println(bb); // false

// 用String类
String str = "aaaaab";
String regex = "a*b";
boolean cc = str.matches(regex); // 与上面的"链式调用"和"简化调用"行为相同
System.out.println(cc);// true
}
}

捕获组

  • 捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号内的字符分组来创建。
  • 捕获组是通过从左至右计算其开括号来编号。例如,在表达式 ((A)(B(C))),有四个这样的组:((A)(B(C)))(A)(B(C))(C)
  • Matcher 类的 groupCount 方法返回一个 int 值,表示 Matcher 对象有多个捕获组。
  • group(0) 是一个特殊的组,代表整个表达式。该组不包括在 groupCount 的返回值中。

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RegexDemo {
public static void main(String[] args) {
String line = "This order was placed for QT3000! OK?";
Pattern pattern = Pattern.compile("(\\D*)(\\d+)(.*)");
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
System.out.println("Found value: " + matcher.group(0));
System.out.println("Found value: " + matcher.group(1));
System.out.println("Found value: " + matcher.group(2));
System.out.println("Found value: " + matcher.group(3));
} else {
System.out.println("NO MATCH");
}
}
}

输出:

1
2
3
4
Found value: This order was placed for QT3000! OK?
Found value: This order was placed for QT
Found value: 3000
Found value: ! OK?

正则注入(regex injection)

定义

攻击者可能会通过恶意构造的输入对初始化的正则表达式进行修改,比如导致正则表达式不符合程序规定要求;可能会影响控制流,导致信息泄露,或导致ReDos攻击。

避免使用不可信数据构造正则表达式。

利用方式

  • 匹配标志:不可信的输入可能覆盖匹配选项,然后有可能会被传给 Pattern.compile() 方法。
  • 贪赞:一个非受信的输入可能试图注入一个正则表达式,通过它来改变初始的那个正则表达式,从而匹配尽可能多的字符串,从而暴露敏感信息。
  • 分组:程序员会用括号包括一部分的正则表达式以完成一组动作中某些共同的部分。攻击者可能通过提供非受信的输入来改变这种分组。

输入校验

  • 非受信的输入应该在使用前净化,从而防止发生正则表达式注入。
  • 当用户必须指定正则表达式作为输入时,必须注意需要保证初始的正则表达式没有被无限制修改。
  • 在用户输入字符串提交给正则解析之前,进行白名单字符处理 (比如字母和数字)。
  • 开发人员必须仅仅提供有限的正则表达式功能给用户,从而减少被误用的可能。

ReDos攻击

正则表达式拒绝服务( ReDoS ) 是一种算法复杂性攻击,它通过提供 正则表达式需要很长时间评估的输入 来产生拒绝服务(即:通过提供特制的正则表达式或输入来使程序花费大量时间,消耗系统资源,然后程序将变慢或变得无响应)。

ReDos攻击概述

  • JDK 中提供的正则匹配使用的是 NFA 引擎。
  • NFA 引擎具有回溯机制(一个字符可能尝试多次匹配),匹配失败时花费时间很大。 正则表达式回溯法原理
  • 当使用简单的非分组正则表达式时,是不会导致ReDos攻击的。

潜在危险

  • 包含具有自我重复的重复性分组的正则
    举例:^(\d+)+$^(\d*)*$^(\d+)*$^(\d+|\s+)*$
  • 包含替换的重复性分组
    举例:^(\d|\d|\d)+$^(\d|\d?)+$

当输入字符串为 1111111111111111111111x1 时,正则表达式 ^(\d+)+$ 就会不断进行失败重试,从而耗死CPU计算。

解析: \d+ 表示匹配一个或多个数字; ()+ 表示分组本身也匹配一个或多个;那么匹配字符串 1111111111111111111111x1 就会进行非常多的尝试,从而导致CPU资源枯竭。

规避猎施

  • 进行正则匹配前,先对匹配的文本的长度进行校验
  • 在编写正则时,尽量不要使用过于复杂的正则,越复杂越容易有缺陷。
  • 在编写正则时,尽量减少分组的使用。
  • 避免动态构建正则(因为难以判断是否有性能问题),当使用不可信数据构造正则时,要使用白名单进行严格校验。

案例

校验qq号码

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
/*
* 校验qq号码.
* 1:要求必须是5-15位数字
* 2:0不能开头
* 分析:
* A:键盘录入一个QQ号码
* B:写一个功能实现校验
* C:调用功能,输出结果。
*/
public class RegexDemo {
public static void main(String[] args) {
// 创建键盘录入对象
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的QQ号码:");
String qq = sc.nextLine();
System.out.println("checkQQ:" + checkQQ(qq));
}

/*
* 写一个功能实现校验两个明确:明确返回值类型:boolean 明确参数列表:String qq
*/
public static boolean checkQQ(String qq) {
boolean flag = true;
// 校验长度
if (qq.length() >= 5 && qq.length() <= 15) {
// 0不能开头
if (!qq.startsWith("0")) {
// 必须是数字
char[] chs = qq.toCharArray();
for (int x = 0; x < chs.length; x++) {
char ch = chs[x];
if (!Character.isDigit(ch)) {
flag = false;
break;
}
}
} else {
flag = false;
}
} else {
flag = false;
}
return flag;
}
}

用正则表达式改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RegexDemo {
public static void main(String[] args) {
// 创建键盘录入对象
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的QQ号码:");
String qq = sc.nextLine();
System.out.println("checkQQ:" + checkQQ(qq));
}

public static boolean checkQQ(String qq) {
// String类的 public boolean matches(String regex)告知此字符串是否匹配给定的正则表达式
// return qq.matches("[1-9][0-9]{4,14}");
return qq.matches("[1-9]\\d{4,14}");
}
}

校验电话号码和邮箱

img

按照不同的规则分割数据

一:

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
/*
* 分割功能
* String类的public String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
* 举例:
* 百合网,世纪佳缘,珍爱网,QQ
* 搜索好友
* 性别:女
* 范围:"18-24"
* age>=18 && age<=24
*/
public class RegexDemo {
public static void main(String[] args) {
// 定义一个年龄搜索范围
String ages = "18-24";
// 定义规则
String regex = "-";
// 调用方法
String[] strArray = ages.split(regex);
// //遍历
for (int x = 0; x < strArray.length; x++) {
System.out.println(strArray[x]);
}
// 如何得到int类型的呢?
int startAge = Integer.parseInt(strArray[0]);
int endAge = Integer.parseInt(strArray[1]);
// 键盘录入年龄
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的年龄:");
int age = sc.nextInt();
if (age >= startAge && age <= endAge) {
System.out.println("你就是我想找的");
} else {
System.out.println("不符合我的要求,gun");
}
}
}

二:

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
/*
* 分割功能练习
*/
public class RegexDemo {
public static void main(String[] args) {
// 定义一个字符串
String s1 = "aa,bb,cc";
// 直接分割
String[] str1Array = s1.split(",");
for (int x = 0; x < str1Array.length; x++) {
System.out.println(str1Array[x]);
}
String s2 = "aa.bb.cc";
String[] str2Array = s2.split("\\.");
for (int x = 0; x < str2Array.length; x++) {
System.out.println(str2Array[x]);
}
String s3 = "aa bb cc";
String[] str3Array = s3.split(" +");
for (int x = 0; x < str3Array.length; x++) {
System.out.println(str3Array[x]);
}
// 硬盘上的路径,我们应该用\\替代\
String s4 = "E:\\JavaSE\\day14\\avi";
String[] str4Array = s4.split("\\\\");// 两个杠代表一个杠
for (int x = 0; x < str4Array.length; x++) {
System.out.println(str4Array[x]);
}
}
}

三:

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
/*
* 我有如下一个字符串:"91 27 46 38 50"
* 请写代码实现最终输出结果是:"27 38 46 50 91"
* 分析:
* A:定义一个字符串
* B:把字符串进行分割,得到一个字符串数组
* C:把字符串数组变换成int数组
* D:对int数组排序
* E:把排序后的int数组在组装成一个字符串
* F:输出字符串
*/
public class RegexDemo {
public static void main(String[] args) {
String s = "91 27 46 38 50";
String[] strArray = s.split(" ");
int[] arr = new int[strArray.length];
for (int x = 0; x < arr.length; x++) {
arr[x] = Integer.parseInt(strArray[x]);
}
Arrays.sort(arr);
// String result1 = Arrays.toString(arr);
// System.out.println("result1:"+result1);// result1:[27, 38, 46, 50, 91]
StringBuilder sb = new StringBuilder();
for (int x = 0; x < arr.length; x++) {
sb.append(arr[x]).append(" ");
}
String result2 = sb.toString().trim();
System.out.println("result2:" + result2);// result2:27 38 46 50 91
}
}

把论坛中的数字替换为*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* 替换功能
* String类的public String replaceAll(String regex,String replacement)
* 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
*/
public class RegexDemo {
public static void main(String[] args) {
String str = "hello!qq:12345;world!kh:622112345678;java!";
// 我要去除所有的数字,用*给替换掉
String regex1 = "\\d+";
String ss1 = "*";
String result1 = str.replaceAll(regex1, ss1);
System.out.println(result1); // hello!qq:*;world!kh:*;java!
String regex2 = "\\d";
String ss2 = "*";
String result2 = str.replaceAll(regex2, ss2);
System.out.println(result2); // hello!qq:*****;world!kh:************;java!
// 直接把数字干掉
String regex3 = "\\d+";
String ss3 = "";
String result3 = str.replaceAll(regex3, ss3);
System.out.println(result3); // hello!qq:;world!kh:;java!
}
}

获取字符串中由3个字符组成的单词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 获取功能:
* 获取下面这个字符串中由三个字符组成的单词
* da jia ting wo shuo,jin tian yao xia yu,bu shang wan zi xi,gao xing bu?
*/
public class RegexDemo {
public static void main(String[] args) {
String s = "da jia ting wo shuo,jin tian yao xia yu,bu shang wan zi xi,gao xing bu?";
String regex = "\\b\\w{3}\\b";// 正则表达式
Pattern p = Pattern.compile(regex);// 把正则表达式编译成模式对象
Matcher m = p.matcher(s);// 通过模式对象得到匹配器对象
// 调用匹配器对象的功能,通过find方法就是查找有没有满足条件的子串:public boolean find()
while (m.find()) {
// 如何得到值呢? public String group()
System.out.println(m.group());
}
// 注意:一定要先find(),然后才能group()
}
}

常用集合结构

线性结构

  • List:ArrayList、CopyOnWriteArrayListVector
  • Queue: LinkedList、PriorityQueueBlockingQueueConcurrentLinkedQueue
  • Stack:extends from Vector

非线性结构

  • Set:HashSet、LinkedHashSet、TreeSet、CopyOnWriteArraySetConcurrentSkipListSet
  • Map:HashMap、TreeMap、WeakHashMap、IdentityHashMap、 LinkedHashMap 、 HashTableConcurrentHashMapConcurrentSkipListMap

集合包装类

集合包装类可以对上述的几种结构进行包装,而它在其中的返回会加入它的语义理解

  • 不可变更集合类:Collections.unmodifiableCollection,对上述结构的限定,返回不可修改的集合
  • 同步集合类Collections.synchronizedCollection,可以将任意List转换成线程安全的List
  • 可检查集合类:Collections.checkedCollection

注:其中 斜体 标注的类为线程安全类。

Java集合框架图

线性存储结构

重点选讲以 List 展开的类

ArrayList

  • 可设置初始的空间大小,在底层数组不够用时在原来的基础上扩容1.5倍

Vector

  • 可设置初始的空间大小,在底层数组不够用时在原来的基础上默认扩容2倍 (亦可设置增长的空间大小)
  • 读写方法都加了synchronized关键字,是线程安全的

CopyOnWriteArrayList

  • 利用写时复制的思想,一旦碰到写操作都会对所有元素进行复制
  • 在新的数组上修改后,重新对数组赋值引用,保证线程安全
  • 适合读多写少的应用场景

ConcurrentLinkedQueue

  • 非阻塞式队列,利用CAS算法保证入队出队线程安全
  • 进行元素遍历时是线程不安全
  • 不允许添加null元素

映射关系

WeakHashMap

  • 适用于需要缓存的场景,对entry进行弱引用管理
  • GC时会自动释放弱引用的entry项
  • 相对HashMap只增加弱引用管理,并不保证线程安全

HashTable

  • 读写方法都加了synchronized关键字
  • key和value都不允许出现null值
  • 与HashMap不同的是HashTable直接使用对象的hashCode,不会重新计算hash值

ConcurrentHashMap

  • 利用CAS+ Synchronized来确保线程安全
  • 底层数据结构依然是数组+链表+红黑树
  • 对哈希项进行分段上锁,效率上较之HashTable要高
  • key和value都不允许出现null值

问题:

为什么ConcurrentHashMap不允许插入Null值?

小测

ArrayList list = new ArrayList(20); 中的 List 扩充了几次?

初始化时直接分配大小,不存在扩充情况,所以是0次;add等操作时才可能会发生扩充。

Math类

​ 包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。

1、针对数学运算进行操作的类

2、常见方法(更多方法见API)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  成员变量:
    public static final double PI
    public static final double E

  成员方法:
    public static int abs(int a):绝对值
    public static double ceil(double a):向上取整
    public static double floor(double a):向下取整
    public static int max(int a,int b):最大值
    public static int min(int a, int b):最小值
    public static double pow(double a,double b):a的b次幂
    public static double random():随机数 [0.0,1.0)
    public static int round(float a):四舍五入。
      返回最接近参数的 int。结果将舍入为整数:加上 1/2,对结果调用 floor 并将所得结果强制转换为 int 类型。换句话说,结果等于以下表达式的值:
        (int)Math.floor(a + 0.5f)
    public static long round(double a):四舍五入。
      返回最接近参数的 long。结果将舍入为整数:加上 1/2,对结果调用 floor 并将所得结果强制转换为 long 类型。换句话说,结果等于以下表达式的值:
        (long)Math.floor(a + 0.5d)
    public static double sqrt(double a):正平方根,返回正确舍入的 double 值的正平方根

  System.out.println("max:" + Math.max(Math.max(12, 23), 18)); // 方法的嵌套调用

案例:

  A:猜数字小游戏

  B:获取任意范围的随机数

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
/*
* 需求:请设计一个方法,可以实现获取任意范围内的随机数。
* 分析:
* A:键盘录入两个数据。
* int strat;
* int end;
* B:想办法获取在start到end之间的随机数
* 我写一个功能实现这个效果,得到一个随机数。(int)
* C:输出这个随机数
*/
public class MathDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入开始数:");
int start = sc.nextInt();
System.out.println("请输入结束数:");
int end = sc.nextInt();
for (int x = 0; x < 100; x++) {
// 调用功能
int num = getRandom(start, end);
// 输出结果
System.out.println(num);
}
}

// 写一个功能两个明确:返回值类型:int 参数列表:int start,int end
public static int getRandom(int start, int end) {
// 1-100之间的随机数:int number = (int) (Math.random() * 100) + 1;
int number1 = (int) (Math.random() * end) + start;// 错误,获得的是 [start, end + start)范围的随机数
int number2 = (int) (Math.random() * (end - start + 1)) + start;// 有问题, 当 start > end 时, 如 start=100,end=95,获取的是[97, 100]范围的随机数
int number3 = (int) (Math.random() * (Math.abs(end - start) + 1)) + Math.min(start, end); // 正确
return number3;
}
}

Random类

1.用于产生随机数的类

  如果用相同的种子创建两个 Random 实例,则对每个实例进行相同的方法调用序列,它们将生成并返回相同的数字序列。

2.构造方法:

  A:Random():创建一个新的随机数生成器。此构造方法将随机数生成器的种子设置为某个值,该值与此构造方法的所有其他调用所用的值完全不同。

        没有给种子,用的是默认种子,是当前时间的毫秒值,每次产生的随机数不同

  B:Random(long seed): 使用单个 long 种子创建一个新的随机数生成器。该种子是伪随机数生成器的内部状态的初始值,该生成器可通过方法 protected int next(int bits) 维护。

              指定种子,每次种子相同,随机数就相同

3.成员方法:

A:int nextInt() 返回int范围内的随机数

1
2
3
4
// nextInt方法源码
public int nextInt() {
return next(32);
}

B:int nextInt(int n) 返回[0,n)范围内的随机数

1
2
3
4
5
6
7
8
9
10
11
12
public class RandomDemo {
public static void main(String[] args) {
// 创建对象
// Random r = new Random();// 未给定种子,每次产生的随机数不同
Random r = new Random(1111);// 给定种子,每次产生的随机数相同
for (int x = 0; x < 10; x++) {
// int num = r.nextInt();// int范围内的随机数
int num = r.nextInt(100) + 1;// [1,100]内的随机数
System.out.println(num);
}
}
}

System类

1.System 类包含一些有用的 字段和方法。System 类不能被实例化。

  在 System 类提供的设施中,有标准输入、标准输出和错误输出流;对外部定义的属性和环境变量的访问;加载文件和库的方法;还有快速复制数组的一部分的实用方法。

  解析:说明字段和方法都是跟类相关的,跟类相关不就是静态的么,说明没有构造方法

2.成员方法(更多方法见API)

1
2
3
4
  A: public static void gc():运行垃圾回收器
  B: public static void exit(int status):终止当前正在运行的 Java 虚拟机。参数用作状态码;根据惯例,非 0 的状态码表示异常终止。
  C: public static long currentTimeMillis():获取当前时间的毫秒值
  D: public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length):从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。

案例:

  A. 运行垃圾回收器

    System.gc()可用于垃圾回收。当使用System.gc()回收某个对象所占用的内存之前,通过要求程序调用适当的方法来清理资源。在没有明确指定资源清理的情况下,Java提高了默认机制来清理该对象的资源,也就是调用Object类的finalize()方法。finalize()方法的作用是释放一个对象占用的内存空间时,会被JVM调用。而子类重写该方法,就可以清理对象占用的资源,该方法没有链式调用,所以必须手动实现。

    从程序的运行结果可以发现,执行System.gc()前,系统会自动调用finalize()方法清除对象占有的资源,通过super.finalize()方式可以实现从下到上的finalize()方法的调用,即先释放自己的资源,再去释放父类的资源。

    但是,不要在程序中频繁的调用垃圾回收,因为每一次执行垃圾回收,jvm都会强制启动垃圾回收器运行,这会耗费更多的系统资源,会与正常的Java程序运行争抢资源,只有在执行大量的对象的释放,才调用垃圾回收最好

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
public class RandomDemo {
public static void main(String[] args) {
Person p = new Person("林俊杰", 60);
System.out.println(p);
p = null; // 让p不再指定堆内存
System.gc();
}
}

class Person {
@Override
protected void finalize() throws Throwable {
System.out.println("当前的对象被回收了" + this);
super.finalize();
}

private String name;
private int age;

public Person() {
super();
}

public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}

  B. 退出jvm

    public static void exit(int status)

      终止当前正在运行的 Java 虚拟机。参数用作状态码;根据惯例,非 0 的状态码表示异常终止。

      该方法调用 Runtime 类中的 exit 方法。该方法永远不会正常返回。

      调用 System.exit(n) 实际上等效于调用:

        Runtime.getRuntime().exit(n)

  C. 获取当前时间的毫秒值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SystemDemo {
public static void main(String[] args) {
System.out.println("我们喜欢林青霞(东方不败)");
// System.exit(0); // 后面代码将终止执行
System.out.println("我们也喜欢赵雅芝(白娘子)");
// System.out.println(System.currentTimeMillis());// 单独得到这样的实际目前对我们来说意义不大,那么,它到底有什么作用呢?

// 要求:请大家给我统计这段程序的运行时间
long start = System.currentTimeMillis();
for (int x = 0; x < 100000; x++) {
System.out.println("hello" + x);
}
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - start) + "毫秒");
}
}

  D. 数组复制

1
2
3
4
5
6
7
8
9
public class SystemDemo {
public static void main(String[] args) {
int[] arr1 = { 11, 22, 33, 44, 55 };
int[] arr2 = { 6, 7, 8, 9, 10 };
System.arraycopy(arr1, 1, arr2, 2, 2);
System.out.println(Arrays.toString(arr1));// [11, 22, 33, 44, 55]
System.out.println(Arrays.toString(arr2));// [6, 7, 22, 33, 10]
}
}

BigInteger类

大数字

  a. java.math.BigInteger:整数

  b. java.math.BigDecimal:浮点数

1、针对大整数的运算(BigInteger:可以让超过Integer范围内的数据进行运算)

2、构造方法

  A:BigInteger(String s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BigIntegerDemo {
public static void main(String[] args) {
// 超过int范围内,Integer就不能再表示,所以就更谈不上计算了。
Integer i = new Integer(100);
System.out.println(i); // 100
System.out.println(Integer.MAX_VALUE); // 2147483647
Integer ii = new Integer("2147483647");
System.out.println(ii); // 2147483647
// Integer iii = new Integer("2147483648"); // NumberFormatException
// System.out.println(iii);
// 通过大整数来创建对象
BigInteger bi = new BigInteger("2147483648");
System.out.println("bi:" + bi); // bi:2147483648
}
}

3、成员方法(更多方法见API)

  A: public BigInteger add(BigInteger val):加

  B: public BigInteger subtract(BigInteger val):减

  C: public BigInteger multiply(BigInteger val):乘

  D: public BigInteger divide(BigInteger val):除

  E: public BigInteger[] divideAndRemainder(BigInteger val):返回商和余数的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BigIntegerDemo {
public static void main(String[] args) {
BigInteger bi1 = new BigInteger("100");
BigInteger bi2 = new BigInteger("50");
System.out.println("add:" + bi1.add(bi2)); // add:150
System.out.println("subtract:" + bi1.subtract(bi2)); // subtract:50
System.out.println("multiply:" + bi1.multiply(bi2)); // multiply:5000
System.out.println("divide:" + bi1.divide(bi2)); // divide:2
BigInteger[] bis = bi1.divideAndRemainder(bi2);
System.out.println("商:" + bis[0]); // 商:2
System.out.println("余数:" + bis[1]); // 余数:0
}
}

BigDecimal类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* 看程序写结果:结果和我们想的有一点点不一样,这是因为float类型的数据存储和整数不一样导致的。它们大部分的时候,都是带有有效数字位。
* 由于在运算的时候,float类型和double很容易丢失精度,演示案例。所以,为了能精确的表示、计算浮点数,Java提供了BigDecimal
* BigDecimal类:不可变的、任意精度的有符号十进制数,可以解决数据丢失问题。
*/
public class BigDecimalDemo {
public static void main(String[] args) {
System.out.println(0.09 + 0.01); // 0.09999999999999999
System.out.println(1.0 - 0.32); // 0.6799999999999999
System.out.println(1.015 * 100); // 101.49999999999999
System.out.println(1.301 / 100); // 0.013009999999999999
System.out.println(1.0 - 0.12); // 0.88
}
}

1、浮点数据做运算,会丢失精度。所以,针对浮点数据的操作建议采用BigDecimal。(金融相关的项目)

2、构造方法

  A:BigDecimal(String s)

3、成员方法:

  A: public BigDecimal add(BigDecimal augend):加

  B: public BigDecimal subtract(BigDecimal subtrahend):减

  C: public BigDecimal multiply(BigDecimal multiplicand):乘

  D: public BigDecimal divide(BigDecimal divisor):除

  E: public BigDecimal divide(BigDecimal divisor,int scale,int roundingMode):商,几位小数,如何舍取

    divisor - 此 BigDecimal 要除以的值。

    scale - 要返回的 BigDecimal 商的标度。

    roundingMode - 要应用的舍入模式。

  F: public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode):商,几位小数,如何舍取

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 BigDecimalDemo {
public static void main(String[] args) {
System.out.println(0.09 + 0.01); // 0.09999999999999999
System.out.println(1.0 - 0.32); // 0.6799999999999999
System.out.println(1.015 * 100); // 101.49999999999999
System.out.println(1.301 / 100); // 0.013009999999999999

BigDecimal bd1 = new BigDecimal("0.09");
BigDecimal bd2 = new BigDecimal("0.01");
System.out.println("add:" + bd1.add(bd2)); // add:0.10

BigDecimal bd3 = new BigDecimal("1.0");
BigDecimal bd4 = new BigDecimal("0.32");
System.out.println("subtract:" + bd3.subtract(bd4)); // subtract:0.68

BigDecimal bd5 = new BigDecimal("1.015");
BigDecimal bd6 = new BigDecimal("100");
System.out.println("multiply:" + bd5.multiply(bd6)); // multiply:101.500

BigDecimal bd7 = new BigDecimal("1.301");
BigDecimal bd8 = new BigDecimal("100");
System.out.println("divide:" + bd7.divide(bd8)); // divide:0.01301
System.out.println("divide:" + bd7.divide(bd8, 3, BigDecimal.ROUND_HALF_UP)); // divide:0.013
System.out.println("divide:" + bd7.divide(bd8, 3, 4)); // divide:0.013
System.out.println("divide:" + bd7.divide(bd8, 8, BigDecimal.ROUND_HALF_UP)); // divide:0.01301000

System.out.println("divide:" + bd7.divide(bd8, 3, RoundingMode.HALF_UP)); // divide:0.013
}
}

日期相关类

  A. java.util.Date类:最常用的功能为获得系统的当前时间。

  B. java.util.Calendar类:与日历相关的类。可以获得日期相关的信息,还可以进行日期的计算。

  C. java.text.DateFormat及其子类:可以对String与Date进行相互的转换。

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
/*********************************************************************/
Date date = new Date();
System.out.println(date); // Sat Apr 01 11:32:05 CST 2017
// 将Date转换为String(格式化)
DateFormat df = DateFormat.getDateInstance();
String s = df.format(date);
System.out.println(s); // 2017-4-1
// 将String转换为Date(解析)
s = "2008-8-8";
try {
date = df.parse(s);
System.out.println(date); // Fri Aug 08 00:00:00 CST 2008
} catch (ParseException e) {
System.out.println("日期不合法!!!");
}
/*********************************************************************/
Date date = new Date();
System.out.println(date);// Sat Apr 01 11:38:09 CST 2017
// 将Date转换为String(格式化)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(date);
System.out.println(s);// 2017-04-01 11:38:09
// 将String转换为Date(解析)
s = "2008-8-8 8:8:8";
try {
date = sdf.parse(s);
System.out.println(date);// Fri Aug 08 08:08:08 CST 2008
} catch (ParseException e) {
System.out.println("日期不合法!!!");
}
/*********************************************************************/

Date

类 Date 表示特定的瞬间,精确到毫秒。

  A:构造方法

    public Date():创建自 1970 年 1 月 1 日 00:00:00 GMT 到当前的默认毫秒值的日期对象

    public Date(long time):创建自 1970 年 1 月 1 日 00:00:00 GMT 到给定的毫秒值的日期对象

1
2
3
4
5
6
7
8
9
10
11
public class DateDemo {
public static void main(String[] args) {
Date d1 = new Date();
System.out.println("d1:" + d1); // d:Sat Apr 01 09:10:13 CST 2017
// long time = System.currentTimeMillis();
long time = 1000 * 60 * 60; // 1小时
Date d2 = new Date(time);
System.out.println("d2:" + d2); // d2:Thu Jan 01 09:00:00 CST 1970
// 不是 01:00:00 是因为我们使用的是北京时间,北京时间是中国采用北京东八时区的区时作为标准时间
}
}

  B:成员方法

    public long getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。

    public void setTime(long time):设置此 Date 对象,以表示 1970 年 1 月 1 日 00:00:00 GMT 以后 time 毫秒的时间点。

1
2
3
4
5
6
7
8
9
10
11
public class DateDemo {
public static void main(String[] args) {
Date d = new Date();
long time = d.getTime();
System.out.println(time); // 1491009637592
System.out.println(System.currentTimeMillis()); // 1491009637592
System.out.println("d:" + d); // d:Sat Apr 01 09:20:37 CST 2017
d.setTime(1000);
System.out.println("d:" + d); // d:Thu Jan 01 08:00:01 CST 1970
}
}

  C:日期和毫秒值的相互转换

    案例:你来到这个世界多少天了?

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
/*
* 算一下你来到这个世界多少年?
* 分析:
* A:键盘录入你的出生的年月日
* B:把该字符串转换为一个日期
* C:通过该日期得到一个毫秒值
* D:获取当前时间的毫秒值
* E:用D-C得到一个毫秒值
* F:把E的毫秒值转换为年
* /1000/60/60/24/365
*/
public class MyYearOldDemo {
public static void main(String[] args) throws ParseException {
// 键盘录入你的出生的年月日
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的出生年月日(格式为yyyy-MM-dd):");
String line = sc.nextLine();
// 把该字符串转换为一个日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d = sdf.parse(line);
// 通过该日期得到一个毫秒值
long myTime = d.getTime();
// 获取当前时间的毫秒值
long nowTime = System.currentTimeMillis();
// 用D-C得到一个毫秒值
long time = nowTime - myTime;
// 把E的毫秒值转换为年
long day = time / 1000 / 60 / 60 / 24 / 365;
System.out.println("你来到这个世界:" + day + "年");
}
}

DateFormat

针对日期进行格式化和针对字符串进行解析的类,但是是抽象类,所以使用其子类SimpleDateFormat

  A: SimpleDateFormat的构造方法:

    public SimpleDateFormat():用默认的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。

    public SimpleDateFormat(String pattern): 用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。

      这个模式字符串该如何写呢? 通过查看API,我们就找到了对应的模式

        y 年

        M 年中的月份

        d 月份中的天数

        H 一天中的小时数(0-23)

        m 小时中的分钟数

        s 分钟中的秒数

      yyyy-MM-dd HH:mm:ss 2017-05-01 12:12:12

      yyyy年MM月dd日 HH:mm:ss 2017年05月01日 12:12:12

  B:日期和字符串的转换

    a:Date – String(格式化)

      public final String format(Date date)

    b:String – Date(解析)

      public Date parse(String source)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DateFormatDemo {
public static void main(String[] args) throws ParseException {
Date d = new Date();
// 创建格式化对象
SimpleDateFormat sdf1 = new SimpleDateFormat();
String s1 = sdf1.format(d);
System.out.println(s1); // 17-5-1 上午9:50

// 给定模式
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
// DateFormat类的 public final String format(Date date)
String s2 = sdf2.format(d);
System.out.println(s2); // 2017年05月01日 09:52:15

// String -- Date
String str = "2008-08-08 12:12:12";
// 在把一个字符串解析为日期的时候,请注意格式必须和给定的字符串格式匹配
SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dd = sdf3.parse(str);
System.out.println(dd); // Fri Aug 08 12:12:12 CST 2008
}
}

  C:制作了一个针对日期操作的工具类。

img

日期类的时间从为什么是从1970年1月1日

I suspect that Java was born and raised on a UNIX system.
UNIX considers the epoch (when did time begin) to be midnight, January 1, 1970.
是说java起源于UNIX系统,而UNIX认为1970年1月1日0点是时间纪元.

但这依然没很好的解释”为什么”,出于好奇,继续Google,总算找到了答案:

http://en.wikipedia.org/wiki/Unix_time

这里的解释是:

最初计算机操作系统是32位,而时间也是用32位表示。
System.out.println(Integer.MAX_VALUE);
2147483647
Integer在JAVA内用32位表 示,因此32位能表示的最大值是2147483647。
另外1年365天的总秒数是31536000,
2147483647/31536000 = 68.1

也就是说32位能表示的最长时间是68年,而实际上到2038年01月19日03时14分07
秒,便会到达最大时间,过了这个时间点,所有32位操作系统时间便会变为
10000000 00000000 00000000 00000000

也就是1901年12月13日20时45分52秒,这样便会出现时间回归的现象,很多软件便会运行异常了。

到这里,我想问题的答案已经出来了:

因为用32位来表示时间的最大间隔是68年,而最早出现的UNIX操作系统考虑到计算
机产生的年代和应用的时限综合取了1970年1月1日作为UNIX TIME的纪元时间(开始
时间),而java自然也遵循了这一约束。

至于时间回归的现象相信随着64为操作系统的产生逐渐得到解决,因为用64位操作
系统可以表示到292,277,026,596年12月4日15时30分08秒,相信我们的N代子孙,哪
怕地球毁灭那天都不用愁不够用了,因为这个时间已经是千亿年以后了。

最后一个问题:上面System.out.println(new Date(0)),打印出来的时间是8点而非0点,
原因是存在系统时间和本地时间的问题,其实系统时间依然是0点,只不过我的电脑时区
设置为东8区,故打印的结果是8点。

Calendar类

它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段 之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。

1、日历类,封装了所有的日历字段值,通过统一的方法根据传入不同的日历字段可以获取值。

2、如何得到一个日历对象呢?

  Calendar rightNow = Calendar.getInstance();// 使用默认时区和语言环境获得一个日历。返回的 Calendar 基于当前时间,使用了默认时区和默认语言环境。

  本质返回的是子类对象

3、成员方法

  A: public int get(int field):返回给定日历字段的值。日历类中的每个日历字段都是静态的成员变量,并且是int类型

  B: public void add(int field,int amount): 根据日历的规则,为给定的日历字段添加或减去指定的时间量。

  C: public final void set(int year,int month,int date): 设置日历字段 YEAR、MONTH 和 DAY_OF_MONTH 的值。

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
public class CalendarDemo {
public static void main(String[] args) {
// 其日历字段已由当前日期和时间初始化:
Calendar c = Calendar.getInstance();
// 获取年
int year = c.get(Calendar.YEAR);
// 获取月
int month = c.get(Calendar.MONTH);
// 获取日
int date = c.get(Calendar.DATE);
System.out.println(year + "年" + (month + 1) + "月" + date + "日"); // 2017年4月1日
// Month 值是基于 0 的。例如,0 表示 January。
/*********************************************************************************/
// 三年前的今天
c.add(Calendar.YEAR, -3);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH);
date = c.get(Calendar.DATE);
System.out.println(year + "年" + (month + 1) + "月" + date + "日"); // 2014年4月1日
/*********************************************************************************/
// 5年后的10天前
c.add(Calendar.YEAR, 5);
c.add(Calendar.DATE, -10);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH);
date = c.get(Calendar.DATE);
System.out.println(year + "年" + (month + 1) + "月" + date + "日"); // 2019年3月22日
/*********************************************************************************/
c.set(2011, 11, 11);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH);
date = c.get(Calendar.DATE);
System.out.println(year + "年" + (month + 1) + "月" + date + "日"); // 2011年12月11日
}
}
  1. 案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 计算任意一年的二月有多少天
* 分析:
* A:键盘录入任意的年份
* B:设置日历对象的年月日
* 年就是A输入的数据
* 月是2
* 日是1
* C:把时间往前推一天,就是2月的最后一天
* D:获取这一天输出即可
*/
public class CalendarDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入年份:");
int year = sc.nextInt();
// 设置日历对象的年月日
Calendar c = Calendar.getInstance();
c.set(year, 2, 1); // 其实是这一年的3月1日
c.add(Calendar.DATE, -1); // 把时间往前推一天,就是2月的最后一天
System.out.println(c.get(Calendar.DATE));
}
}

二分查找(二分法)

查找:

基本查找:数组元素无序(从头找到尾)

二分查找(折半查找):数组元素有序

数组高级二分查找原理图解:

img

代码:

img

注意:下面这种做法是有问题的。

因为数组本身是无序的,所以这种情况下的查找不能使用二分查找。

所以你先排序了,但是你排序的时候已经改变了我最原始的元素索引。

必须使用基本查找,除非需求不在意。

img

十大排序算法

0%