Java常用API(二)
Easul Lv6

相关链接

System类

特点

  • 包含很多有用的类字段和方法
  • 不能被实例化(类的构造函数未对外提供,私有化了)
  • 类中的方法和属性都是静态的

获取程序执行时间

先获取一个时间,程序然后执行,执行完在获取一个时间,相减就是程序运行时间。

折叠代码块JAVA 复制代码
1
2
3
4
long currentTime_1 = System.currentTimeMillis();
//code...
long currentTime_2 = System.currentTimeMillis();
System.out.println("code running uses " + (currentTime_2 - currentTime_1 + "ms"));

不同系统用不同换行符

通过系统信息,用键获取对应系统换行的符号值,常用,可以写成全局常量。

折叠代码块JAVA 复制代码
1
2
3
// 在任何系统上都可以获取该系统的回车符号,无需在不同系统写死固定的\n或\r\n。
// 这里可以将这个代码写成自定义常量
System.out.println("hello " + System.getProperty("line.separator") + " world");

常用字段(属性)

折叠代码块YAML 复制代码
1
2
3
4
5
err: 标准错误输出流(少用)
in: 标准输入流,使用键盘等设备输入
out: 标准输出流,将系统信息输出来
- 本身代表一个对象(public static final PrintStream out),拥有打印方法。
- 通常在显示器的控制台进行输出

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
currentTimeMillis(): 获取当前时间毫秒值。与1970年1月1日0点之间的差值,返回长整形
exit(): 终止当前运行的Java虚拟机
gc(): 运行垃圾回收器
getProperties(): 获取当前系统属性信息,存储到了Properties集合中。
- Properties是HashTable子类(即Map子类),用于属性集。
- 该类没有指定泛型,是因为键值都是String类型
- Map取的时候一般用的entrySet()和keySet()来取,但都有泛型,这里可以使用其自有方法进行存取。
- JVM是跨平台的,不同平台下获取的信息不同
getProperty(String key): 根据某键获取系统属性中该键的值
setProperty("myclasscpath", "c:\myclasspath"): 系统启动时存放自定义系统信息,这是全局信息,其他程序也可以使用

系统属性中的键的解释

键都是固定的,系统版本不同,获取的值不同

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
sun.boot.library.path: 系统启动类库路径
user.dir: 用户目录
os.name: 操作系统名称
path.separator: 路径分隔符(windows和linux不同)
line.separator: 行分隔符(windows和linux不同,windows是\r\n,linux是\n)
file.separator: 文件分隔符(windows和linux不同)
file.encoding: 系统默认字符集

常用方法

折叠代码块YAML 复制代码
1
2
Set<String> stringPropertyNames(): 获取Properties的键集
getProperty(String key): 通过key来获取Properties中的value

Runtime类

特点

  • 每个Java应用程序都有一个Runtime类实例,可以与运行环境相连接
  • 没有构造函数,但有非静态方法,那么至少有一个静态方法来返回本类对象。(单例模式,保证对象唯一性)
  • 使用getRuntime()方法获取当前对象
  • Runtime类就是单例设计模式的类

常用方法

折叠代码块YAML 复制代码
1
2
Runtime getRuntime(): 获取当前Runtime对象
Process exec(String cmd): 执行命令(可以用来执行本地程序),返回一个进程
折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
Runtime r = Runtime.getRuntime();
// 为防止命令不存在,需要抛IO异常(输入输出异常)。
try {
Process p = r.exec("/usr/bin/date");
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine())!= null) {
System.out.print(line);
}
} catch (IOException e) {
e.printStackTrace();
}

Process类

特点

  • 由Runtime类的exec()方法执行后返回的对象

常用方法

折叠代码块YAML 复制代码
1
destory(): 杀死Java程序开启的子进程

Math类

特点

  • 不能被继承
  • 都是静态的

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
Math.random(): 返回一个0到1的double伪随机数(次数多了有规律)
- 乘x返回大于等于0,小于x的数
- 也可以使用Random类,在util包中
Math.ceil(12.56): 返回大于该小数的最小整数。
Math.floor(12.56): 返回小于该小数的最大整数。
Math.round(12.56): 四舍五入
Math.pow(a, b): 返回a的b次方,a和b为double

Random(util包中)

常用方法

折叠代码块YAML 复制代码
1
2
3
nextInt(): 返回一个随机int值,有正有负
nextInt(num): 返回0到num(不包)的整数
nextDouble(): 返回0到1(不包)的小数

Date(util包中)

特点

  • 月:0-11表示1-12月
  • 格式化:将事物按照指定的格式进行转换

创建

折叠代码块YAML 复制代码
1
2
Date(): 将当前日期和时间封装为对象
Date(date): 将指定毫秒值封装为对象。long型需要加l

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
getTime(): 将当前日期和时间转为毫秒值,返回long型整数
- 方便日期运算,查看两个日期间隔
setTime(date): 为Date对象设置毫秒值,可用于日期和时间转换
- 毫秒转日期对象主要是为了对年月日时分秒方便操作
after(date): 比较此日期对象是否在指定日期对象之后,返回boolean
before(date): 比较此日期对象是否在指定日期对象之前,返回boolean
equals(date): 比较两个日期对象是否相等,返回boolean
compareTo(date): 比较此日期与指定日期对象。相等返回0,小于返回负数,大于返回正数
toString(): 显示的是当前日期字符串。Tue Aug 11 14:34:11 CST 2020

DateFormat(text包中)

特点

  • 可以用于日期对象和日期字符串之间的转换
  • 是一个抽象类,不能创建对象,但可以通过静态工厂返回实例。
  • 工厂:设计模式中的词汇,表示专门用于生产对象的地方。就像单例返回对象就是静态工厂。
  • windows中日期风格主要获取的是更改日期,时间或数字格式的信息
    image

常用字段

getDateInstance(style)getDateTimeInstance(dateStyle, timeStyle)的参数都可以放

折叠代码块YAML 复制代码
1
2
3
4
5
FULL: 2020年8月11日星期二
LONG: 2020年8月11日
MEDIUM: 2020年8月11日
SHORT: 2020/8/11
DEFAULT: (默认不用写)2020年8月11日

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
DateFormat getDateInstance(): 获取日期格式化实例。默认风格为年月日和当前语言。
DateFormat getDateInstance(style): 指定日期风格
DateFormat getDateTimeInstance(): 获取日期/时间格式化实例,默认风格为年月日时分秒和当前语言。
DateFormat getDateTimeInstance(dateStyle, timeStyle): 可以同时指定日期和时间风格
String format(date): 将日期对象格式化为日期字符串
Date parse(str): 将日期字符串转换为日期对象。
折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. format(date)的使用
Date date = new Date();
// 获取日期格式对象
// DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);// 指定风格
DateFormat df = DateFormat.getDateInstance();
String dateStr = df.format(date);

// 2. parse(str)的使用
// 静态工厂转换。只能转内置提供的格式
String str = "2012年4月19日";
DateFormat df = DateFormat.getDateInstance();
Date date = df.parse(str);
System.out.println(date);
// 自定义格式转换
String str = "2012--04--19";
DateFormat df = new SimpleDateFormat("yyyy--MM--dd");
Date date = df.parse(str);
System.out.println(date);

SimpleDateFormat(DateFormat子类)

特点

  • 用于生成自定义风格的日期格式化字符串

格式化字符

输入几个字母就有几位,yyyy是四位,yy是两位

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
y: 年,1996;96
M: 月,July; Jul;07
w: 年中的周数
W: 月中的周数
D: 年中的天
d: 月中的天
H: 24小时的小时数
h: 12小时的小时数
m: 小时中的分钟数
s: 分钟中的秒数
S: 毫秒数

将日期转为自定义格式化字符串

折叠代码块JAVA 复制代码
1
2
3
4
5
Date date = new Date();
// DateFormat的子类对象SimpleDateFormat支持日期时间自定义格式的书写
DateFormat df = new SimpleDateFormat("yyyy--MM--dd");
String dateStr = df.format(date);
System.out.println(dateStr);

Calendar(util包中)

特点

  • 日历对象,用于特定瞬间与日期或时间字段时间的转换
  • 是一个抽象类,

常用字段

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
DAY_OF_MONTH: 月中的第几天
DAY_OF_WEEK: 星期中的第几天。星期日是1,星期六是7
DAY_OF_YEAR: 年中的第几天
YEAR: 获取年份
MONTH: 获取月份,需要加1
DATE: 获取日期
HOUR: 获取12小时制小时
HOUR_OF_DAY: 获取24小时制小时
MINUTE: 获取分钟
SECOND: 获取秒
MILLISECOND: 获取毫秒

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
getInstance(): 获取日历类的实例,返回Calendar
toString(): 返回创建的类名和所有日期时间字段的键值
get(key): 根据常用字段中的键获取值
set(key, value): 设置某个字段的值
set(year, month, date): 设置年月日
add(field, amount): 进行某个日期字段的偏移。几年后(前)或几小时后(前)
- 获取2月有几天可以set该年3月一号,然后add回去一天,输出该天日期
setTime(date): 设置时间为Date对象,用于详细字段的操作
getTime(): 获取Calendar的Date对象
setTimeInMillis(num): 以毫秒值设置日历对象
getTimeInMillis(): 获取日历对象的毫秒值,返回长整型

IO流

作用

  • 处理设备之间的数据传输
  • Java对数据的操作都是通过流的方式
  • Java操作流的对象都在IO包
  • 流按照流向分为输入流(进行读操作)和输出流(进行写操作)。
    • 输入流和输出流是相对于内存而言
    • 外设的数据读取到内存就是输入
    • 将内存的数据写入到外设中就是输出
  • 流按操作数据分为字节流和字符流
    • Java使用unicode码表,每个字符使用两个字节表示
    • 因为码表不同,同一个汉字在不同的码表中对应的数字不同,所以会有乱码
    • 因为码表过多,所以读取字节流之后为了正确显示数据,需要和指定码表结合找到对应字符,从而形成了字符流
    • 字节流+编码表 = 字符流

IO流图解

  • 有一个数据源,创建一个与该源关联的读的流
  • 有一个目的源,创建一个与该源关联的写的流
  • 两个流并没有关联,所以通过中转站进行数据交接。
  • 读的流读到数据,把数据装载到中转站,写的流将中转站的数据写入到目的源,从而实现IO流操作
    image

IO流顶层基类

  • 字节流顶层基类
    • InputStream
    • OutputStream
  • 字符流的顶层基类
    • Reader(输入流,读入内存)
    • Writer(输出流,从内存写出)
  • 特点
    • 基类的子类名都以父类名做后缀
    • 子类名的前缀就是该对象的功能
  • 注意
    • 如果操作文字数据,优先考虑字符流
    • 硬盘数据的基本体现是文件
    • resolved:无法被识别
    • catch到异常,处理不了可以进行throw操作

IO小知识

  • 复制的原理就是边读边写
  • 移动剪切的原理就是读写完成后删掉原文件
  • 如果IO流操作一个文件,没有关流,该文件删除不掉。
  • 做代码有两个优化
    • 设计优化:代码重构(重新构建),让代码拥有更强的灵活性,扩展性,复用性
    • 性能优化:最常用手段之一就是缓冲区

Writer

Writer常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
write(String): 写入数据。调用后数据写入到了临时存储缓冲区,相当于写入到流中
write(String, offset, length): 写入一个字符串,从offset位置开始写length个
write(char[]): 写入字符数组
write(char[], offset, length): 写入字符数组,从offset位置开始写length个
write(int c): 写入一个字符
- write使用换行可以使用System.getProperty("line.separator")
- 可能写入失败,所以会抛IOException

flush(): 将临时缓冲区的数据写入到目标中
close(): 写入数据的时候调用了windows或linux的资源,如果不关闭会影响系统效率
- 可能关闭失败,所以会抛IOException
- 关闭之前会先调用flush()。关闭后再write()或flush()会有IOException

Reader

Reader常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
read(): 每次调用读取单个字符,返回int。如果没有字符返回-1
- 文件有结束标识,windows或linux底层读取到该标识,告诉JVM,JVM返回前台-1
read(char[]): 将读取到的字符存储到字符数组中,返回读取到的字符个数
- 不够字符数组个数返回字符数,读取完或没有字符返回-1。
- 用于每次读取指定数量的字符。
- 多次使用该字符数组存储,会从0下标开始覆盖
- 效率更高。chs取的时候一般用1024的整数倍。可以把该数值设置为一个常量。

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. read()使用
// 进行数据读取,不为-1就一直读
int ch ;
while ((ch = fr.read()) != -1) {
System.out.println((char)ch);
}

// 2. read(char[])使用
char[] chs = new char[3];
int num;
while ((num = fr.read(chs)) != -1) {
System.out.println(new String(chs, 0, num));
}

FileWriter

字符流操作对象,父类OutputStreamWriter,基类Writer

作用

  • 用于向文件中写入字符。

特点

  • 需要在写入的时候就指定好向哪个文件写入数据

构造方法演示

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
// 新创建demo.txt。文件不存在,自动创建,文件存在,覆盖原文件。
// 路径可能会错误,所以会抛IOException
// 可以直接写绝对路径
// 和new OutputStreamWriter(new FileOutputStream("demo.txt")效果一样。相当于在内部封装了该代码
FileWriter("demo.txt")
// 指定是否在原来的文件后附加写入字符。如果文件不存在,则仍会创建该文件
FileWriter("demo.txt"boolean)

常用方法

参考Writer常用方法

流操作流程

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 引用创建在外边,用于下边关闭流操作
FileWriter fw = null;
try {
// 路径不存在,可能会创建失败,因此会有异常发生
fw = new FileWriter("demo1.txt", true);
// 写入失败可能会有异常发生
fw.write("asdf" + System.getProperty("line.separator") + "asdfadf");
fw.flush();
} catch (IOException e) {
// do something
} finally {
try {
// 这里判断是否为空,主要是为了防止流对象没创建成功,这里关闭就会有空指针
if (fw != null)
fw.close();
} catch (IOException e) {
throw new RuntimeException("close fail");
}
}

FileReader

字符流操作对象,父类InputStreamReader,基类Reader

作用

  • 读取文件中的字符

特点

  • 需要在读出的时候就指定好从哪个文件读

构造方法演示

折叠代码块JAVA 复制代码
1
2
3
// 用读取流关联已存在文件。
// 可以直接写绝对路径
FileReader("demo.txt")

常用方法

参考Reader常用方法

BufferedWriter

字符流缓冲区对象,装饰类,父类Writer

  • 反复读写耗费资源。缓冲区可以提高数据的读写效率,一次读写更多内容。
  • 可以自建缓冲区,也可以使用Java缓冲区对象
  • 缓冲区就是对要操作的内容进行临时缓存

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
构造方法:
BufferedWriter(Writer out): 创建一个默认大小的输出缓冲区,缓冲传入的输出对象。
- 本质就是封装了数组的对象,用数组缓存流的数据
- 默认缓冲区8K,满了就自动刷新写出。
常用方法:
write(String, offset, length): 写入字符串从offset位置的length个字符
write(char[], offset, length): 写入字符数组从offset位置的length个字符
write(int c): 写入一个字符,写到缓冲区,满了会自动flush
newLine(): 写入一个换行符
flush(): 刷新并写入流。最好写一次,刷新一次。防止断电后,缓存数据全部丢失。
close(): 刷新流,然后关闭流
- 输出缓冲区关闭流的同时会关闭掉输出对象的流
折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 用于字符的底层写入
FileWriter fw = null;
// 用于缓冲字符,提高效率
BufferedWriter bw = null;
try {
fw = new FileWriter("demo.txt");
bw = new BufferedWriter(fw);
// 缓冲区的写入是先写到缓冲区
bw.write("asdf");
// 缓冲区的刷新方法将数据写入到目的地。不写,满了会自动刷新,不满就需要手动刷新
bw.flush();
} catch (IOException e) {
} finally {
if (bw != null) {
try {
// 输出缓冲区关闭流在底层就是将输出流关闭
bw.close();
} catch (IOException e) {
}
}
}

BufferedReader

字符流缓冲区对象,装饰类,父类Reader

  • 反复读写耗费资源。缓冲区可以提高数据的读写效率,一次读写更多内容。
  • 可以自建缓冲区,也可以使用Java缓冲区对象
  • 缓冲区就是对要操作的内容进行临时缓存

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
构造方法:
BufferedReader(Reader in): 创建一个默认大小的输入缓冲区,缓冲传入的输入对象。
- 本质就是封装了数组的对象,用数组缓存流的数据
- 可以读取数组,字符,行
- 创建对象后会默认从输入流中读取一批字符到缓冲区,用完之后再从输入流中读取。
常用方法:
read(): 从缓冲区读取一个字符,返回字符int标识,没有返回-1
read(char[], offset, length): 从缓冲区读取字符到字符数组中,从offset位置存length个,返回读取的字符数。没有则返回-1
readLine(): 从缓冲区读取一行文本,不包含换行符。没有返回null
折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
//  readLine()的使用
FileReader fr = new FileReader("demo.txt");
br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}

BufferedReader模拟实现

流程:对象创建之后,从输入流中默认读取一批字符到缓冲区,可供读取。如果没有数据了。先从输入流中再读取一批数据到缓冲区,然后继续读取,直到获取到所有数据,则在缓冲区标记-1即可。
代码如下(以装饰类实现

折叠代码块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
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
public class MyBufferedReader extends Reader {
/**
* 可供操作的输入流,可以对Reader的子类操作
*/
private Reader fr;
/**
* 缓冲区长度
*/
private static final int BUFFER_LENGTH = 1024;
/**
* 缓冲区创建
*/
private char[] buf = new char[BUFFER_LENGTH];
/**
* 用于指向缓冲区未读取位置的指针。操作到最后一个元素,指针归零
*/
private int pos = 0;
/**
* 用于记录缓冲区中有多少个数据的计数器。计数器为0,从源中继续获取数据到缓冲区。没有数据,count为-1
*/
private int count = 0;

public MyBufferedReader(Reader fr) throws IOException {
this.fr = fr;
}

/**
* 从缓冲区中一次取一个字符
* @return 取出成功返回取出的字符的int值,数据已经取完,返回-1
*/
public int read() throws IOException {
if (count == 0) {
writeDataToBuffer();
}
if (count == -1) {
return -1;
}

char ch = buf[pos++];
count--;

return ch;
}

/**
* 从缓冲区中一次取一行字符
*
* @return 取出成功返回取出的字符串,没有数据返回null
* @throws IOException
*/
public String readLine() throws IOException {
StringBuilder sb = new StringBuilder();
int ch = 0;
// 调用自定义的read方法
while ((ch = read()) != -1) {
if (ch == '\r') {
continue;
}
if (ch == '\n') {
return sb.toString();
}
// 添加的时候不会自动把int看成char,需要强转一下
sb.append((char)ch);
}
if (sb.length() != 0) {
return sb.toString();
}

return null;
}

/**
* 关闭输入流
*
* @throws IOException
*/
public void close() throws IOException {
fr.close();
}

/**
* 用于为缓冲区填充数据
*/
private void writeDataToBuffer() throws IOException {
count = fr.read(buf);
pos = 0;
}
}

缓冲区评价

缓冲区对IO流中的很多类都进行了功能增强(也可以叫做装饰设计模式

装饰设计模式

对一组对象的功能进行增强时,就可以使用该模式进行问题的解决。并未改变其功能

折叠代码块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
public class PersonDemo {
public static void main(String[] args) {
Person p = new Person();
NewPerson np = new NewPerson(p);
np.chiFan();
}
}

class Person {
void chiFan() {
System.out.println("吃饭");
}
}

/**
* 装饰设计模式的实现:对Person进行增强
*/
class NewPerson {
private Person p;
NewPerson(Person p) {
this.p = p;
}

/**
* 在原来的功能上增强
*/
public void chiFan() {
System.out.println("开胃酒");
p.chiFan();
System.out.println("甜点");
}
}

装饰继承都可以实现功能的扩展增强(对于IO流就是缓冲实现),但

  • 通过继承来实现功能扩展增强,不断地产生子类,虽然扩展了功能,但继承体系会越来越臃肿,不够灵活
  • 通过将扩展功能单独封装(装饰),哪个对象需要该功能就和哪个对象关联,扩展了功能,也更灵活
  • 装饰类和被装饰类必须属于同一个接口或父类
    折叠代码块JAVA 复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 装饰操作
    * Writer
    * |--TextWriter:用于操作文本
    * |--MediarWriter:用于操作媒体
    * |--BufferWriter:用于提高效率
    * 继承操作
    * Writer
    * |--TextWriter:用于操作文本
    * |--BufferTextWriter:用于高效操作文本
    * |--MediarWrite:用于操作媒体
    * |--BufferMediarWrite:用于高效操作媒体
    * 需要对一个类中的很多子类进行功能扩展,如果每个子类再进行子类扩充太麻烦,实现接口也需要写很多代码
    * 使用装饰设计模式,传入父类对象,多态操作子类,并扩展子类的功能。
    * 该类的扩展功能还是父类的功能,所以继承父类即可
    */
    class BufferWriter extends Writer {
    private Writer w;
    BufferWriter(Writer w) {
    this.w = w;
    }
    }

不要随便修改原来文件中的代码,因为很可能会修改多处,造成问题。通过继承或装饰来扩展功能

LineNumberReader

BufferedReader子类。这个只有Reader,没有Writer,因为写入的时候就顺序写入

作用

  • 加入了行号的操作

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
构造方法:
LineNumberReader(fr): 创建一个行号阅读器。传入了输入流对象
常用方法:
readLine(): 读取缓冲区一行文字
read(): 从缓冲区读取一个字符,返回字符int标识,没有返回-1
read(char[], offset, length): 从缓冲区读取字符到字符数组中,从offset位置存length个,返回读取的字符数。没有则返回-1
getLineNumber(): 获取当前行号。例如读取代码的行号。
setLineNumber(int): 设置当前行号,下一个获取getLineNumber()从该行号下一个开始获取

FileOutputStream

字节流操作对象,与字符操作对象思想相同。OutputStream的子类
可以操作字符,也可以操作其他媒体文件

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
构造方法:
FileOutputStream("demo.txt"): 创建文件输出流对象(字节流对象)
- 可以直接写绝对路径
- 不需要编解码,数据不需要临时缓冲,会直接写到目的源中
- OutputStream中flush()和close()方法并没有写具体代码,该子类只重写了close()。所以它调用close()才有用
- 缓冲区需要flush()的重写
常用方法:
write(byte[]): 写入字节数组
write(byte[], offset, length): 从offset写入length长度的字节数组
write(int): 写入字节
- write后数据被直接写入目的源中,不需要flush()
close(): 关闭资源

FileInputStream

字节流操作对象,与字符操作对象思想相同。InputStream的子类
可以操作字符,也可以操作其他媒体文件

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
构造方法:
FileInputStream("demo.txt"): 创建文件输入流对象(字节流对象)
- 可以直接写绝对路径
常用方法:
read(): 读取一个字节的数据
read(byte[]): 读取一个字节数组
read(byte[], offset, length): 读取一个字节数组。从offset位置存length字节
close(): 关闭流
available(): 获取输入流还可以读取的剩余字节数。用于获取文件大小,也可以用于分段获取字节数据。
折叠代码块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
// 1. read(byte[])的操作
FileInputStream fis = null;
try {
fis = new FileInputStream("byteTest.txt");
byte[] buf = new byte[1024];
int length = 0;
while ((length = fis.read(buf)) != -1) {
System.out.println(new String(buf, 0, length));
}
} catch (IOException e) {
} finally {
try {
fis.close();
} catch (IOException e) {
}
}

// 2. available()的使用
FileInputStream fis = new FileInputStream("byteTest.txt");
// 创建一个能放文件中所有字节的字节数组
// 慎用,可能会内存溢出
byte[] buf = new byte[fis.available()];
// 一次将所有数据读出来
fis.read(buf);

BufferedOutputStream

字节流缓冲区对象,用于字节流的缓冲,父类FilterOutputStream,基类OutputStream

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
构造方法:
BufferedOutputStream(OutputStream out): 创建一个默认大小的字节输出缓冲区,缓冲传入字节输出对象。
- 本质就是封装了数组的对象,用数组缓存流的数据
常用数据:
write(byte[], offset, length): 从offset写入length长度的字节数组
write(int): 写入字节,缓冲区满了自动flush。不满想要刷新就手动flush
flush(): 刷新缓冲流,将数据写入目的源
close(): 关闭资源

BufferedInputStream

字节流缓冲区对象,用于字节流的缓冲,父类FilterInputStream,基类InputStream

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
构造方法:
BufferedInputStream(InputStream out): 创建一个默认大小的字节输入缓冲区,缓冲传入字节输入对象。
- 本质就是封装了数组的对象,用数组缓存流的数据
常用数据:
read(): 读取一个字节的数据。检测到流的末尾前或抛出异常前,一直阻塞
read(byte[], offset, length): 读取一个字节数组。从offset位置存length字节
close(): 关闭流
available(): 获取输入流还可以读取的剩余字节数。用于获取文件大小,也可以用于分段获取字节数据。

实现复制

这里使用字节流复制。用字符流来复制,得到一个字符会去查表。
如果查表找不到对应数据,就使用未知字符区的数据来标识,源数据和目的数据将不一致。很可能最后大小也不同。

使用自建缓冲区复制

折叠代码块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
// 使用字节流就可以操作媒体文件了。
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\刀马旦.mp3");
fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\刀马旦_1.mp3");
byte[] buf = new byte[BUFFER_SIZE];
int length = 0;
while ((length = fis.read(buf)) != -1) {
fos.write(buf, 0, length);
}
} catch (IOException e) {
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}

加了Java缓冲区再复制文件

折叠代码块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
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\刀马旦.mp3");
bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\刀马旦_1.mp3");
bos = new BufferedOutputStream(fos);
int ch = 0;
while ((ch = bis.read()) != -1) {
bos.write(ch);
// 不用加flush(),不然太慢了。
// 这里的写入是写入到缓冲区,会自动判断是否填满缓冲区,然后自动flush
}
} catch (IOException e) {
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
}
}
}

直接读取所有字节,然后写入另一个文件

不建议使用,可能内存溢出

折叠代码块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
// 适用于不太大的文件,不建议用
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\刀马旦.mp3");
fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\刀马旦_3.mp3");
byte[] buf = new byte[fis.available()];
fis.read(buf);
fos.write(buf);
} catch (IOException e) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
}
}
}

读一个字节,写一个字节

很慢,千万不要用

折叠代码块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
// 直接一个字节一个字节的复制
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\刀马旦.mp3");
fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\刀马旦_3.mp3");
int ch = 0;
while ((ch = fis.read()) != -1) {
fos.write(ch);
}
} catch (IOException e) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
}
}
}

读取键盘数据并打印

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
// 简单读取一个键盘录入的数据,打印到控制台。
// 用来获取标准输入流,可以获取输入的数据
InputStream is = System.in;
// read()方法是阻塞式方法。没有数据就会一直等。读到末尾或抛异常前都会阻塞。
// 输入a,在windows回车,相当于输入了a\r\n,可以读取到3个字符
int ch = is.read();
System.out.println(ch);

// System.in和System.out的流是静态对象,关了就不能再用了,所以不需要关流

InputStreamReader

字节流通向字符流的桥梁,叫做转换流解码

特点

  • 本身是字符流
  • 使用指定charset读取字节,并将其解析为字符
    • 直接使用FileReader读使用系统默认码表,转换之后使用转换后的码表读,然后使用FileInputStream
  • 字节流+编码表组成的对象就是InputStreamReader

构造函数

折叠代码块YAML 复制代码
1
2
InputStreamReader(InputStream out): 创建一个字节流转字符流的对象。传入的是字节输入流对象
InputStreamReader(InputStream out, String charset): 以某种编码将字节输入流解码为字符,大小写均可以(gbk或GBK)

读取标准输入流转为字符流输出

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);

String line = null;
try {
// 通过字符缓冲区的readLine()进行输入数据读取。
while ((line = br.readLine()) != null) {
if ("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
} catch (IOException e) {
}

字节流通过InputStreamReader转为字符流之后,每次调用read()读取中文会读取一个字符,也就是两个字节

OutputStreamWriter

字符流通向字节流的桥梁,叫做转换流编码

特点

  • 本身是字符流,但可以输出字节流
  • 使用指定charset将写入流的字符编码为字节。默认使用操作系统的编码格式(windows简体中文默认GBK)
    • 直接使用FileWriter写使用系统默认码表,转换之后使用转换后的码表写,然后使用FileOutputStream
  • 字节到字符是解码(看得懂),字符到字节是编码(看不懂)

构造函数

折叠代码块YAML 复制代码
1
2
3
OutputStreamWriter(OutputStream out): 创建一个字符流转字节流的对象。传入的是字节输出流对象
OutputStreamWriter(OutputStream out, String charset): 以某种编码将字符输出流编码为字节,大小写均可以(gbk或GBK)
- 不同编码保存后,文件大小不同。

标准输入流获取数据,标准输出流输出数据

折叠代码块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
// 获取标准输入的字节流
InputStream in = System.in;
// 将获取的字节流转为字符流
InputStreamReader isr = new InputStreamReader(in);
// 对字符流进行高效装饰
BufferedReader br = new BufferedReader(isr);

// 标准输出的字节流。out是PrintStream类型,基类是OutputStream
OutputStream os = System.out;
// 将字符流转为字节流,以供输出到os
OutputStreamWriter osw = new OutputStreamWriter(os);
// 对输出的字符流进行高效装饰
BufferedWriter bw = new BufferedWriter(osw);
String line = null;
while ((line = br.readLine()) != null) {
if ("over".equals(line)) {
break;
}
// 先将获取到的字符流写到输出缓冲区
bw.write(line.toUpperCase());
// 写入一个换行符
bw.newLine();
// 刷新之后,字符流被转为字节流,并输出到标准输出中
bw.flush();
}

输入流与输出流的简化

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 对字符流进行高效装饰。
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 对输出的字符流进行高效装饰
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while ((line = br.readLine()) != null) {
if ("over".equals(line)) {
break;
}
// 先将获取到的字符流写到输出缓冲区
bw.write(line.toUpperCase());
// 写入一个换行符
bw.newLine();
// 刷新之后,字符流被转为字节流,并输出到标准输出中
bw.flush();
}

将控制台录入字符保存到文件中

折叠代码块JAVA 复制代码
1
2
3
// 将输出源改成文件输出流就可以了
// 剩下的参考 输入流与输出流的简化
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("demo.txt")));

输入流与输出流的简化

将文件中的数据显示到控制台

折叠代码块JAVA 复制代码
1
2
3
// 将输入源改成文件输入流就可以了
// 剩下的参考 输入流与输出流的简化
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("demo.txt")));")));

输入流与输出流的简化

文本复制

折叠代码块JAVA 复制代码
1
2
3
4
5
// 将输入源改成文件输入流
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("demo.txt")));
// 将输出源改成文件输出流
// 剩下的参考 输入流与输出流的简化
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("demoCopy.txt")));

输入流与输出流的简化

转换流的使用场景

  • 源或目的对应设备是字节流,但操作的是文本数据,可以作为转换桥梁,提高文本操作便捷性
  • 操作文本涉及到编码表,需要使用转换流。

常识

  • Unicode所有字符都用两个字节表示。a也是两个字节。所以比较浪费空间。后期改进为UTF-8,一个字节装不下用两个,两个字节装不下用三个。UTF-8有编码头,所以可能两个字节不够。
  • 中文UTF-8是三个字节,GBK是两个字节。
  • 流写入默认使用系统默认编码表写入
  • 如果读入读出需要指定明确编码,需要使用转换流

流的操作规律

可以更好的进行流对象的创建

  • 流太多,开发时不知道用哪个对象合适。所以需要总结一下
  • 使用四个明确来获得使用对象。
    1. 明确是否需要源和目的(源和目的统称为汇)
      折叠代码块YAML 复制代码
      1
      2
      源: InputStream或Reader两个体系
      目的: OutputStream或Writer两个体系
    2. 明确数据是否纯文本数据
      折叠代码块YAML 复制代码
      1
      2
      源: 是纯文本,使用Reader;不是纯文本,使用InputStream
      目的: 是纯文本,使用Writer;不是纯文本,使用OutputStream
    3. 明确具体设备
      折叠代码块YAML 复制代码
      1
      2
      源设备相关对象: 硬盘,File;键盘,System.in;内存,数组;网络,Socket流;
      目的设备相关对象: 硬盘,File;控制台,System.out;内存,数组;网络,Socket流;
    4. 是否需要其他额外功能(装饰)
      折叠代码块YAML 复制代码
      1
      2
      3
      是否需要高效(缓冲区),是就加上buffer。
      是否需要操作基本数据类型
      是否需要对象存储化
  • 流只能操作设备数据,文件夹,文件属性(创建属性,只读等),文件名等需要使用File对象操作

常用流汇总

折叠代码块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
/**
* 字节流
* InputStream
* |--FileInputStream
* |--FilterInputStream
* |--BufferedInputStream
* OutputStream
* |--FileOutputStream
* |--FilterOutputStream
* |--BufferedOutputStream
* 字符流
* Reader
* |--FileReader
* |--BufferedReader
* |--InputStreamReader
* Writer
* |--FileWriter
* |--BufferedWriter
* |--OutputStreamWriter
* 文件对象
* File
* |--FilenameFilter
* |--FileFilter
*/

File

特点

  • 将文件或文件夹封装成对象
  • 方便操作文件或文件夹的属性信息
  • 可以作为参数传递给流的构造函数
  • 最好先将所有数据存储,最后一起进行IO操作,降低资源使用。

构造函数

折叠代码块YAML 复制代码
1
2
3
File("a.txt"): 可以将一个已存在的或不存在的文件或目录封装为对象。传入文件或目录路径。
File("c:\\", "a.txt"): 创建一个文件对象,传入路径和文件名,表示在使用该路径下的该文件或在该路径下创建该文件
File(File, "a.txt"): 创建一个文件对象,传入File对象和文件名
折叠代码块JAVA 复制代码
1
2
// File(File, "a.txt")的演示
File file_3 = new File(new File("c:\\"), "a.txt");

常用字段

折叠代码块YAML 复制代码
1
2
3
4
5
pathSeparatorChar: 多个路径分隔符。windows(;) linux(:)
- 就是环境变量中路径之间的分隔符号
pathSeparator: 多个路径分隔符。windows(;) linux(:)
separator: 路径分隔符。windows(\\) linux(/)
separatorChar: 路径分隔符。windows(\\) linux(/)

常用方法

canis开头的都是用来判断的方法

折叠代码块YAML 复制代码
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
获取:
getName(): 获取文件名称(demo.txt)
getAbsolutePath(): 获取文件绝对路径
getPath(): 获取File对象传入的路径
length(): 获取文件大小,返回长整型
lastModified(): 获取文件修改时间。用于自动更新已修改的文件。可以开一个线程来操作。
getParent(): 获取文件父目录。如果传入相对路径则返回null,绝对路径可以获取父目录
File getAbsoluteFile() 将获取到的文件绝对路径封装为File对象
创建与删除:
createNewFile(): 创建一个新文件。返回创建结果。路径已被封装到File对象中,不需要传路径。
- 文件存在就创建,文件不存在就不创建
createTempFile(prefix, suffix): 创建临时文件到默认临时文件夹中,需要指定前缀和后缀。是静态方法
createTempFile(prefix, suffix, directory): 创建临时文件到指定目录中
delete(): 删除File封装好的文件或文件夹。如果文件夹内有文件或文件夹或该文件或文件夹正在使用,则不会被删除
- 慎用,删除就没了。
- 删除创建的多级文件夹,只会删除最内层的文件夹。
deleteOnExit(): 在虚拟机结束的时候删除该文件。如果文件出现IOException,则请求虚拟机在退出前删除文件
mkdir(): 创建一个单级目录
mkdirs(): 创建多级目录
判断:
exists(): 判断文件(夹)是否存在。在操作文件前判断一下
isAbsolute(): 判断是否是绝对路径名
isFile(): 判断是否是文件。不存在返回false。最好先判断存在性。文件也可以没有后缀
isDirectory(): 判断是否是目录。不存在返回false。最好先判断存在性。文件夹也可以是demo.txt
isHidden(): 判断是否被隐藏。不存在返回false。最好先判断存在性
重命名:
renameTo(File): 将一个File对象重命名为另一个File对象。也可以将文件重命名到另一个目录,相当于剪切。
获取所有分区及空间信息:
listRoots(): 列出所有文件系统根目录,返回File数组
getFreeSpace(): 获取某个根目录的空闲空间大小
getTotalSpace(): 获取某个根目录的总空间大小
getUsableSpace(): 获取某个根目录的可用于虚拟机的空间大小
获取某个目录下所有文件和文件夹名称:
list(): 获取某个目录下所有文件和文件夹名称,包括系统隐藏文件名称。返回字符串数组
- System Volume Information系统卷标信息,无法访问,有权限。
- 如果传入的是文件路径,则数组创建失败,空指针异常
- 系统级目录访问也是返回空。
- 目录存在,但没有内容,返回数组长度为0
list(new FilterByJava): 获取某个目录下符合过滤器的所有文件和文件夹名称,返回String数组
listFiles(): 获取某个目录下所有文件或文件夹的对象,返回File数组
listFiles(FileFilter): 获取某个目录下符合过滤器所有文件或文件夹的对象,返回File数组
listFiles(FilenameFilter): 获取某个目录下符合过滤文件名的所有文件或文件夹的对象,返回File数组
折叠代码块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
// 创建多及目录
File f = new File("/home/easul/test/test2/test23");
f.mkdirs();

// list(new FilterByJava)中添加过滤器
public class FilterByJava implements FilenameFilter {
// dir 文件所在目录 name 文件名称
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}

}
// 用于过滤自定义字段名
public class SuffixFilter implements FilenameFilter {
private String suffix;
@Override
public boolean accept(File dir, String name) {
return name.endsWith(suffix);
}

public SuffixFilter(String suffix) {
this.suffix = suffix;
}
}

// listFiles(FileFilter)的使用
public class FilterByHidden implements FileFilter {
// dir 文件所在目录 name 文件名称
@Override
public boolean accept(File pathname) {
return pathname.isHidden();
}
}

递归遍历文件夹(深度遍历)

递归太多会栈溢出

折叠代码块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
public void showAllFile() {
File dir = new File("/home/easul/test");
getAllFile(dir, 0);
}

public void getAllFile(File dir, int count) {
System.out.println(getSpace(count) + "" + dir.getName());
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
getAllFile(file, count + 1);
} else {
System.out.println(getSpace(count + 1) + "" + file.getName());
}
}
}

public String getSpace(int count) {
StringBuilder sb = new StringBuilder();
sb.append("|--");
for (int i = 0; i < count; i++) {
sb.insert(0, "| ");
}
return sb.toString();
}

递归删除目录下所有文件和文件夹

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void doDelete() {
File dir = new File("C:\\src");
deleteAllFile(dir);
System.out.println("success");
}

public void deleteAllFile(File dir) {
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
deleteAllFile(file);
} else {
file.delete();
}
}
dir.delete();
}

Properties

父类Hashtable,基类Map

特点

  • 键值都是String类型,所以不用加泛型
  • 最好使用自身的存储和取出方法来进行元素操作。
  • 该集合的数据可以保存到流,或者从流获取
  • 通常用于操作以键值对存在的配置文件,xml也可以
  • 持久化:把数据存到硬盘,数据可以一直存在。

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
setProperty(key, value): 设置键值
getProperty(key): 获取键的值
stringPropertyNames(): 获取某个Properties的所有键值对的Set集
list(PrintStream): 将所有属性信息输出。用于调试。
store(OutputStream, comments): 将属性写到输出流,同时需要进行属性描述。如果写中文,会输出成\u523D,转为了Unicode码
store(Writer, comments): 将属性写入字符流。如果写中文,会输出成\u523D,转为了Unicode码
load(InputStream): 将输入流的属性读入
load(Reader): 将输入字符流的属性读入

简单演示

折叠代码块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
// 创建Properties集合
Properties prop = new Properties();
// 存储元素
prop.setProperty("name", "easul");
prop.setProperty("age", "12");

// 修改元素
prop.setProperty("age", "24");


// 取出所有元素
Set<String> names = prop.stringPropertyNames();
for (String name : names) {
String value = prop.getProperty(name);
System.out.println(name + ":" + value);
}

// 将prop用标准输出流输出
prop.list(System.out);

// 从输入流读入属性
prop.load(new FileReader("test.txt"));
// 将属性导出到输出流
prop.store(new FileWriter("test.txt"), "comment");

修改已有配置信息

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
File file = new File("prop.ini");
if (!file.exists()) {
file.createNewFile();
}
Properties prop = new Properties();
FileReader fr = new FileReader(file);
prop.load(fr);
prop.list(System.out);

prop.setProperty("name", "aboive");

// 如果这里放到FileReader下边,相当于重新创建了一个文件,覆盖了源文件。需要改完再创建
FileWriter fw = new FileWriter(file);
prop.store(fw, "");

fw.close();
fr.close();

IO流常用对象

折叠代码块YAML 复制代码
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
打印流(用于目的):
- PrintWriter
- PrintStream
序列流(用于源):
- SequenceInputStream
操作对象的流:
- ObjectInputStream
- ObjectOutputStream
管道流: 输入流和输出流可以直接连接
- PipedInputStream
- PipedOutputStream
操作基本数据类型流:
- DataInputStream
- DataOutputStream
操作字节数组: 源和目的都是内存,都是字节数组
- ByteArrayInputStream
- ByteArrayOutputStream
操作字符数组: 源和目的都是内存,都是字符数组
- CharArrayInputStream
- CharArrayOutputStream
操作字符串: 源和目的都是内存,都是字符串
- StringReader
- StringWriter
其他:
- RandomAccessFile

PrintStream

特点

  • 不会抛出IOException
  • 可以打印多种数据类型值,并保持数据表示形式

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
构造方法:
PrintStream(path): 可以直接将流打印到路径文件中
PrintStream(OutputStream): 将数据打印到另一个输出流中
PrintStream(File): 将数据打印到File对象指定的文件中
常用方法:
write(): 将数据写出。如果写出int,只写出int的低8位,高24位被忽略
print(): 参数是什么样,打印出的是什么样。底层就是将数据转为字符串打印出去。省得打印字节数组太麻烦。
println(): 参数是什么样,打印出的是什么样。打印完顺便换行。

PrintWriter

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
构造方法:
PrintWriter(path): 可以直接将字符流打印到路径文件中
PrintWriter(OutputStream): 将字符流打印到另一个输出流中
PrintWriter(OutputStream, true): 自动刷新,不需要加flush()
PrintWriter(File): 将字符流打印到File对象指定的文件中
PrintWriter(Writer): 将字符流打印到另一个Writer对象中
PrintWriter(Writer, true): 自动刷新,不需要加flush()
常用方法:
write(): 将数据写出。如果写出int,只写出int的低8位,高24位被忽略
print(): 参数是什么样,打印出的是什么样。底层就是将数据转为字符串打印出去。省得打印字节数组太麻烦。
println(): 参数是什么样,打印出的是什么样。打印完顺便换行。
flush(): 打印之后会打印到缓冲区,需要刷新才能显示。一般缓冲区满了会自动写出,缓冲区不满想写出需要flush

SequenceInputStream

特点

  • 可以依次读取多个流,直到最后一个流
  • 相当于多个流合并为一个
  • 可用于文件合并(例如文件分割后再进行合并)

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
构造方法:
SequenceInputStream(Enumeration<? extends InputStream> e): 合并多个流
SequenceInputStream(InputStream s1, InputStream s2): 合并两个流
常用方法:
read(): 读取一个字节的数据。检测到流的末尾前或抛出异常前,一直阻塞
read(byte[], offset, length): 读取一个字节数组。从offset位置存length字节
close(): 关闭流。因为可能合并多个流,所以会关闭所有被合并的流
available(): 获取输入流还可以读取的剩余字节数。用于获取文件大小,也可以用于分段获取字节数据。

简单操作

折叠代码块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
48
// 用来合并多个流
Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("1.txt"));
v.add(new FileInputStream("2.txt"));
v.add(new FileInputStream("3.txt"));
Enumeration<FileInputStream> en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("4.txt"));
int len = 0;
while ((len = sis.read()) != -1) {
bos.write(len);
}
bos.close();
sis.close();

// 因为Vector效率更低,下边使用ArrayList进行一下操作
// ArrayList没有枚举对象,所以需要自己创建。
// 枚举也被迭代器给替代了。这里展示一下迭代到枚举的转换
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for (int i = 1; i <= 3; i++) {
al.add(new FileInputStream(i + ".txt"));
}
// 使用集合工具类来获取集合的枚举形式
Enumeration<FileInputStream> en = Collections.enumeration(al);
// 该集合工具类获取枚举的内部实现原理
/*
final Iterator<FileInputStream> it = al.iterator();
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>(){
// 判断是否还有下一个对象
public boolean hasMoreElements() {
return it.hasNext();
}
// 获取下一个对象
public FileInputStream nextElement() {
return it.next();
}

};
*/
SequenceInputStream sis = new SequenceInputStream(en);

BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("4.txt"));
int len = 0;
while ((len = sis.read()) != -1) {
bos.write(len);
}
bos.close();
sis.close();

文件切割器

  • 将文件切割为文件碎片(固定每份大小或固定总份数)
  • 文件碎片的扩展名可以自己进行定义,例如:part
  • 文件相关信息需要保存到配置文件中
  • 流要关联文件,所以有几个文件,就需要几个流
    折叠代码块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
    // 读取流关联文件
    FileInputStream fis = new FileInputStream(file);
    byte[] buf = new byte[BUFFER_SIZE * BUFFER_SIZE];
    // 创建目的
    FileOutputStream fos = null;
    // 获取读取的字节长度
    int len = 0;
    // 定义切割文件名称
    int count = 0;
    // 创建相关配置对象
    Properties prop = new Properties();
    // 存放切割文件的目录
    File dir = new File("C:\\partfiles");
    if (!dir.exists()) {
    dir.mkdirs();
    }
    while ((len = fis.read(buf)) != -1) {
    fos = new FileOutputStream(new File(dir, (count++) + ".part"));
    fos.write(buf, 0, len);
    fos.close();
    }

    // 创建配置文件。通过指定输出字符集,可以将中文原样输出
    OutputStreamWriter propWriter = new OutputStreamWriter(new FileOutputStream(new File(dir, "part.properties")), "UTF-8");
    prop.setProperty("partcount", count + "");
    prop.setProperty("filename", file.getName());
    prop.store(propWriter, "file info");

    propWriter.close();
    fis.close();

文件合并器

折叠代码块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
Properties prop = new Properties();
// 也可以使用过滤器获取File[] files = dir.listFiles(new SuffixFilter(".properties"));
File partProp = new File(dir, "part.properties");
if (!partProp.exists()) {
throw new RuntimeException(dir + "目录下未找到配置文件");
} else {
// 以存入配置文件的字符集读入配置数据
prop.load(new InputStreamReader(new FileInputStream(partProp), "UTF-8"));
}
String filename = prop.getProperty("filename");
int partCount = Integer.parseInt(prop.getProperty("partcount"));
File[] partFiles = dir.listFiles(new MyJavaFilter(".part"));
if (partCount != partFiles.length) {
throw new RuntimeException("碎片文件数目不符合要求,应为" + partCount +"个");
}
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for (int i = 0; i < partCount; i++) {
al.add(new FileInputStream(new File(dir, i + ".part")));
}
Enumeration<FileInputStream> en = Collections.enumeration(al);
SequenceInputStream sis = new SequenceInputStream(en);

FileOutputStream fos = new FileOutputStream(new File(dir, filename));
byte[] buf = new byte[1024];
int len = 0;
while ((len = sis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();

ObjectInputStream

特点

  • 直接把对象读入
  • 使用普通流对象,可以读入输入,但无法解析为对象。
  • 只获得对象持久化文件,不能够将对象还原。因为对象在堆内存创建都需要依赖该对象所属类文件(字节码文件),没有无法创建。因此还需要获得该类文件
  • 会对ObjectOutputStream写入的基本数据和对象进行反序列化(读出来,序列化是写进去)

读入持久化对象(反序列化)

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
// 存对象默认使用object后缀
File file = new File("obj.object");
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
// 只获得对象持久化文件,不能够将对象还原。
// 因为对象在堆内存创建都需要依赖该对象所属类文件(字节码文件),没有无法创建。因此还需要获得该类文件
// 没有该类文件会抛出ClassNotFoundException
Worker p = (Worker)ois.readObject();
System.out.println(p.getName());
System.out.println(p.getAge());
ois.close();

ObjectOutputStream

特点

  • 直接把对象保存出去。实现对象的持久存储
  • 写出多个对象需要序列化。
    折叠代码块 复制代码
    1
    2
    3
    4
    Serializable是标记接口。没有实现该接口,类不能被序列化
    序列化后会写出对象的类,类签名(底层实现),非瞬态和非静态字段的值。
    对象修改了静态字段,因为其不被持久化存储,所以再反序列化的时候还是静态属性默认值。
    可以写多个
  • 可以将各种类型的数据包括对象写到文件中
  • 用于数据库链接对象,存储特定数据的对象

对象持久化(序列化)

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
// 存对象默认使用object后缀
File file = new File("obj.object");
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 对象持久化需要存储的对象实现Serializable接口
oos.writeObject(new Worker("easul", 24));
oos.close();

Serializable

  • 实现了该类,就有ID号,相当于序列化了。
  • 默认ID号是根据类的特征和类的签名算出来的
  • 用于验证持久化的对象文件和类文件的ID版本是否一致
  • 如果持久化了老对象的类,后边对该类进行了更新,反序列化会抛出异常InvalidClassException(无效类文件异常)
  • 网络传输需要加上该ID
    • 网络传输有些对象需要序列化。服务器如果崩了,会话会消失,所以对象会存在服务器。重启服务器,会恢复会话。数据还在。

声明方式

折叠代码块JAVA 复制代码
1
2
3
class Test implements Serializable {
private static final long serialVersionUID = 8823927758172226839L;
}

关键字transient

这是短暂的,瞬态的
如果对象的某个字段不想存储到硬盘,也不想用静态,加transient关键字即可(ObjectOutputStream不存储瞬态字段的值)。该对象中的值还在。

折叠代码块JAVA 复制代码
1
private transient String name;

RandomAccessFile

特点

  • 不是IO体系(字符流或字节流)中的子类
  • 支持对随机访问文件的读取或写入
  • 对象内部维护了一个byte数组,可以通过指针操作数组中的元素。
  • 数组可以延长
  • getFilePointer()可以获取指针位置,seek()可以设置指针位置。
  • 该对象将字节输入流和输出流进行了封装
  • 该对象的源或目的只能是文件(构造函数的特点)
  • random就是指针可以移动,想从哪里读都可以

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
构造方法:
RandomAccessFile(File file, String mode): 创建一个随机访问流。mode有r,rw,rws,rwd。
- 文件不存在,创建,文件存在,不创建
RandomAccessFile(String file, String mode): 文件使用字符串指定路径
常用方法:
close(): 用于关资源
seek(long): 设置指针偏移量。移动一次走8位。用于实现随机位置读取数据或写入数据。
- 可用于追加数据或修改数据
getFilePointer(): 获取指针位置,返回长整型
write(): 按照字节写入。int只写入低八位
writeInt(int): 按照32位写入。
read(byte[]): 将数据读入字节缓冲区
readInt(): 一次读32位,并转为整数返回

注意

  • 一般要求数据有规律。这样指针才能每次获取固定长的数据。
  • 可以默认创建16字节,多出来的用0补。每次读取16字节的数据即可。
  • 可以用于多线程写入,同时向一个文件写数据。指定好指针位置即可。不同的数据段属于不同线程,但是属于同一个文件。
  • 可以用于文本文件,也可以用于媒体文件。

PipedInputStream

特点

  • 输入输出流可以直接连接,通过结合线程使用
  • 可以单线程写完就读,也可以读完就写。但是读的时候没有数据,读阻塞,写也无法执行。造成死锁
  • 可以多线程同时读写(要使用多线程避免死锁)
  • 管道输出流提供写到管道输入流的所有数据

常用方法

折叠代码块YAML 复制代码
1
2
3
4
构造方法:
PipedInputStream(PipedOutputStream src): 创建管道输入流的同时就连接上一个管道输出流。
常用方法:
connect(PipedOutputStream src): 连接一个管道输出流

PipedOutputStream

特点

  • 输入输出流可以直接连接,通过结合线程使用
  • 可以单线程写完就读,也可以读完就写。但是读的时候没有数据,读阻塞,写也无法执行。造成死锁
  • 可以多线程同时读写(要使用多线程避免死锁)
  • 管道输出流提供写到管道输入流的所有数据

管道流的使用

折叠代码块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
public void pipedDemo() throws IOException {
PipedInputStream input = new PipedInputStream();
PipedOutputStream output = new PipedOutputStream();
input.connect(output);
new Thread(new Input(input)).start();
new Thread(new Output(output)).start();
}

class Input implements Runnable {
private PipedInputStream in;

@Override
public void run() {
try {
byte[] buf = new byte[1024];
int len = in.read(buf);

String s = new String(buf, 0, len);
System.out.println("s=" + s);
in.close();
} catch (Exception e) {

}
}

public Input(PipedInputStream in) {
this.in = in;
}
}

class Output implements Runnable {
private PipedOutputStream out;

@Override
public void run() {
try {
out.write("管道来了".getBytes());
} catch (Exception e) {

}
}

public Output(PipedOutputStream out) {
this.out = out;
}
}

DataInputStream

可以将基本Java数据类型的数据读入

常用方法

折叠代码块YAML 复制代码
1
readUTF(): 读入使用修改版UTF-8写出的数据。每一个修改版UTF-8标头读一次,返回String

DataOutputStream

可以将基本Java数据类型写入输出流

常用方法

折叠代码块YAML 复制代码
1
2
writeUTF(String): 使用修改版UTF-8将数据写入输出流。存储的时候会带着编码的标头。
- 其和普通UTF-8不同。转换流无法指定修改版UTF-8,所以转换流无法读该文件

ByteArrayInputStream

特点

  • 实现了输入流
  • 在内存操作
  • 有内部缓冲区
  • 该流关闭无效
  • 构造的源是数组

构造方法

折叠代码块YAML 复制代码
1
ByteArrayInputStream("abcde".getBytes()): 构造的时候传一个字符数组

ByteArrayOutputStream

特点

  • 实现了输出流
  • 在内存操作
  • 缓冲区可以自增长
  • 该流关闭无效。因为没有调用底层资源,只在内存操作数据。所以不用关闭。关闭了也还能使用。
  • 不抛出IOException

常用方法

折叠代码块YAML 复制代码
1
2
3
4
构造方法:
ByteArrayOutputStream(): 构造的时候传已经有了数组。不够会自动增长数组长度
常用方法:
writeTo(OutputStream): 将数据写到另一个流

简单操作

用流的思想操作数组,这两个对象操作数据量一般不大

折叠代码块JAVA 复制代码
1
2
3
4
5
6
ByteArrayInputStream bais = new ByteArrayInputStream("abcde".getBytes());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int ch = 0;
while((ch = bais.read()) != -1) {
baos.write(ch);
}

编码表

  • 二进制和生活中文字的关系
  • 用于编码解码。
  • 常用码表
    • ASCII:美国标准信息交换码。7位即可表示完。一个字节的开头位0
    • ISO8859-1:拉丁码表。欧洲码表。一个字节的8位都用上了。兼容ASCII
    • GB2312:中国的中文编码表。
    • GBK:中文编码表的升级版,有了更多的文字
    • Unicode:国际标准码,融合了多种文字。所有文字都是两个字节,Java默认使用。
    • UTF-8:Unicode Transfer Format,最低8位,一个字节。最多3个字节
  • 字符串->字节数组:编码,字节数组->字符串:解码

编码表与String

String关于编码解码的方法

折叠代码块YAML 复制代码
1
2
String(buf, "UTF-8"): 构造函数中指定编码来解析字节数组
getBytes(charset): 指定字符串转为字节数组的时候的编码表。GBK,UTF-8等

字符串编码的使用

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
String str = "你好";
byte[] buf = str.getBytes("UTF-8");
for (byte b : buf) {
System.out.println(b);
}
String s1 = new String(buf, "UTF-8");
System.out.println(s1);

编码问题

  • 编码编错了,无法解码(例如使用不识别中文的码表)。
  • 编对了,使用码表不对,也可能解错(例如tomcat默认使用iso8859-1,就会解错)
    • 可以以该码表将字符串编码,然后再使用正确的码表解码
      折叠代码块JAVA 复制代码
      1
      2
      3
      4
      5
      6
      7
      String str = "你好";
      byte[] buf = str.getBytes("GBK");
      String s1 = new String(buf, "iso8859-1");
      System.out.println(s1);
      byte[] buf2 = s1.getBytes("iso8859-1");
      String s2 = new String(buf2, "GBK");
      System.out.println(s2);
  • 编码正确,解码不对,可能无法再挽回
    • 使用GBK编码,UTF-8解码,再UTF-8编码,GBK解码
    • 测试使用 你好 ,哈哈 ,谢谢。前两个都不能找到对应编码,第三个可以,所以第三个可以进行重新编码,前两个不可以。
    • GBK编码的数据,如果在UTF-8解码的时候找不到对应的字符,就使用未知字符代替。未知字符可能使用同一个码代替,所以再使用UTF-8编码可能无法再找回源编写的编码
  • UTF-8编码解析
    • 一个字节,最高位为0,剩下的7位表示数据
    • 两个字节,第一个字节开头是110,剩下5位数据位,第二个字节开头是10,剩下6位数据位
    • 三个字节,第一个字节开头是1110,剩下四位数据位,第二个和第三个字节开头是10,都剩下6位数据位
    • 读完符合的字节,然后直接用读到的字节查表

记事本写入联通两个字乱码

折叠代码块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
String s1 = "移动";
String s2 = "联通";
byte[] b1 = s1.getBytes("GBK");
byte[] b2 = s2.getBytes("GBK");
for (byte b : b1) {
/**
* 11010010
* 11000110
* 10110110
* 10101111
*/
System.out.println(Integer.toBinaryString(b & 255));
}
System.out.println();
for (byte b : b2) {
/**
* 进行联通GBK编码之后,结果刚好与UTF-8编码格式相符,但用UTF-8解析会有问题,所以乱码了
* 11000001
* 10101010
* 11001101
* 10101000
*/
System.out.println(Integer.toBinaryString(b & 255));
}
 评论
来发评论吧~
Powered By Valine
v1.5.2