集合
一方面, 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用 Array 存储对象方面具有一些弊端,而 Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中。
Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
Java 集合可分为 Collection 和 Map 两种体系。
Collection接口:Set:元素无序、不可重复的集合 —类似高中的“集合”List:元素有序,可重复的集合 —“动态”数组
Map接口:具有映射关系 key-value 对的集合 —类似于高中的“函数” y = f(x) (x1,y1) (x2,y2)
框架
1 | |---Collection(接口) |
Collection 接口
Collection 接口是 List Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
JDK 不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。
Collection 接口中的方法:
- add(Object obj) # boolean,添加元素 obj 至集合中
- addAll(Collection coll) # boolean,添加coll中所有元素至该集合
- size() # int,返回元素个数
- clear() # void,移除集合中的所有元素
- isEmpty() # boolean,判断集合是否为空
- remove(Object obj) # boolean,从此集合中移除指定元素,如果存在的话
- removeAll(Collection coll) # boolean,移除coll中包含在指定集合中的所有元素
- retainAll(Collection coll) # boolean,求集合与coll的交集
- equals(Object obj) # boolean,比较集合与指定对象是否相等
- contains(Object obj) # boolean,判断集合是否包含指定元素
- containsAll(Collection coll),hashCode() # boolean,判断集合是否包含coll中所有元素
- iterator() # 迭代器
- toArray() # 与数组相互转化
List 接口与 HashSet LinkedHashSet TreeSet
List 集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。List 容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
List 接口存储有序的,可以重复的元素,相当于“动态”数组。
在 Collection 的基础上,List 新增的方法有:
- 删除 remove(int index)
- 修改 set(int index, Object obj)
- 获取 get(int index)
- 插入 add(int index, Object obj)
添加进 List 集合中的元素(或对象)所在的类一定要重写 equals() 方法
ArrayList 是 List 主要的实现类; LinkedList 更适用于频繁的插入、删除操作; Vector 是古老的实现类、线程安全的,但效率要低于 ArrayList 。
Set 接口与 HashSet LinkedHashSet TreeSet
Set 接口存储无序的,不可重复的元素。相当于高中的“集合”概念。
Set 有如下特点:
Set使用的方法基本上都是Collection接口下定义的。- 添加进
Set集合中的元素所在的类一定要重写equals()和hashCode()。要求重写equals()和hashCode()方法保持一致。 - 无序性:无序性!= 随机性。真正的无序性,指的是元素在底层存储的位置是无序的。
- 不可重复性:当向Set中添加进相同的元素的时候,后面的这个不能添加进去。
HashSet 是 Set 主要的实现类; LinkedHashSet 是 HashSet 的子类,当我们遍历集合元素时,是按照添加进去的顺序实现的,频繁的遍历,较少的添加、插入操作建议选择此类; TreeSet 可以按照添加进集合中的元素的指定属性进行排序。
两种排序方式:
- 自然排序:
① 要求添加进 TreeSet 中的元素所在的类 implements Comparable 接口
② 重写 compareTo(Object obj),在此方法内指明按照元素的哪个属性进行排序
③ 向 TreeSet 中添加元素即可。若不实现此接口,会报运行时异常 - 定制排序:
① 创建一个实现 Comparator 接口的实现类的对象。在实现类中重写 Comparator 的 compare(Object o1,Object o2) 方法
② 在此 compare() 方法中指明按照元素所在类的哪个属性进行排序
③ 将此实现 Comparator 接口的实现类的对象作为形参传递给 TreeSet 的构造器中
④ 向 TreeSet 中添加元素即可。若不实现此接口,会报运行时异常 - 注:要求重写的compareTo()或者compare()方法与equals()和hashCode()方法保持一致。
Map 接口
Map 接口用于存储“键-值”对的数据,相当于高中的“函数 y = f(x)” (x1,y1) (x2,y2)。 Map 中的 key 和 value 都可以是任何引用类型的数据。
Map 中的 key 用 Set 来存放,不允许重复,即同一个 Map 对象所对应的类,须重写 hashCode() 和 equals() 方法。value 可以重复的,使用 Collection 来存放的。一个 key-value 对构成一个 entry(Map.Entry), entry 使用 Set 来存放。
Map 接口中的方法:
- 添加、删除操作:
Object put(Object key, Object value)
Object remove(Object key)
void putAll(Map t)
void clear() - 元视图操作的方法:
Set keySet()
Collection values()
Set entrySet() - 元素查询的操作:
Object get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
boolean equals(Object obj)
HashMap 与 LinkedHashMap
HashMap 是 Map 接口使用频率最高的实现类。允许使用 null 键和 null 值,与 HashSet 一样,不保证映射的顺序。
HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。 HashMap 判断两个 value 相等的标准是:两个 value 通过 equals() 方法返回 true。
LinkedHashMap 是 HashMap 的子类。与 LinkedHashSet 类似, LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致。所以 LinkedHashMap 可以按照添加进 Map 的顺序实现遍历。
TreeMap
TreeMap 存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
TreeMap 的 Key 的排序:
- 自然排序:
TreeMap的所有的 Key 必须实现Comparable接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出ClasssCastException - 定制排序:创建
TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有 key 进行排序。此时不需要Map的 Key 实现Comparable接口
TreeMap 判断两个 key 相等的标准:两个 key 通过 compareTo() 方法或者 compare() 方法返回 0 。若使用自定义类作为 TreeMap 的 key ,所属类需要重写 equals() 和 hashCode() 方法,且 equals() 方法返回 true 时, compareTo() 方法应返回 0 。
Hashtable 与 Properties
Hashtable 是个古老的 Map 实现类,线程安全。与 HashMap 不同, Hashtable 不允许使用 null 作为 key 和 value 。 与 HashMap 一样, Hashtable 也不能保证其中 Key-Value 对的顺序,且判断两个 key 或 value 相等的标准与 hashMap 一致。
Properties 类是 Hashtable 的子类,该对象用于处理属性文件。由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型。存取数据时,建议使用 setProperty(String key,String value) 方法和 getProperty(String key) 方法。
集合的遍历与 Iterator 接口
主要作用是用来遍历集合中的元素。
List 遍历的代码示例
1 | Collection coll = new ArrayList(); |
Map 遍历的代码示例
1 | Map map = new HashMap(); |
Collections 工具类
操作 Collection 及 Map 的工具类,大部分为 static 的方法。
排序操作
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List,int,int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
查找、替换
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection,Comparator)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copy(List dest,List src):将src中的内容复制到dest中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
同步控制
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
- static
Collection synchronizedCollection(Collection c)
返回指定 collection 支持的同步(线程安全的)collection。 - static
List synchronizedList(List list)
返回指定列表支持的同步(线程安全的)列表。 - static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
返回由指定映射支持的同步(线程安全的)映射。 - static
Set synchronizedSet(Set s)
返回指定 set 支持的同步(线程安全的)set。 - static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
返回指定有序映射支持的同步(线程安全的)有序映射。 - static
SortedSet synchronizedSortedSet(SortedSet s)
返回指定有序 set 支持的同步(线程安全的)有序 set。
Java 代码示例
1 |
|
Properties 的使用
占位
泛型
枚举和注解
枚举
枚举类:类的对象是有限个的,确定的。
自定义枚举类
步骤:
- 私有化类的构造器,保证不能在类的外部创建其对象
- 在类的内部创建枚举类的实例。声明为:public static final
- 若类有属性,那么属性声明为:private final 。此属性在构造器中赋值。
代码示例: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
39public class TestSeason {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
spring.show();
System.out.println(spring.getSeasonName());
}
}
//枚举类
class Season{
//1. 提供类的属性,什么为private final
private final String seasonName;
private final String seasonDesc;
//2. 声明为final的属性,在构造器中初始化
private Season(String seasonName, String seasonDesc) {
super();
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3. 通过公共的方法来调用属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//4. 创建枚举类的对象
public static final Season SPRING = new Season("spring", "春暖花开");
public static final Season SUMMER = new Season("summer", "夏日炎炎");
public static final Season AUTUMN = new Season("autumn", "秋高气爽");
public static final Season WINTER = new Season("winter", "白雪皑皑");
public String toString() {
return "Season [seasonName=" + seasonName + ", seasonDesc=" + seasonDesc + "]";
}
public void show() {
System.out.println("这是一个季节");
}
}
枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰。上述代码中类的属性和构造器都命名为 private 说明,枚举类的属性和构造器不能在外部访问,只能通过在类内部创建对象。
关键字 enum 定义枚举类
其中常用的方法:
- values()
- valueOf(String name);
枚举类如何实现接口:
- 让类实现此接口,类的对象共享同一套接口的抽象方法的实现。
- 让类的每一个对象都去实现接口的抽象方法,进而通过类的对象调用被重写的抽象方法时,执行的效果不同
代码示例: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
67public class TestSeason {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
spring.show();
System.out.println(spring.getSeasonName());
System.out.println();
//1. values()
Season[] seasons = Season.values();
for(int i=0; i < seasons.length; i++)
System.out.println(seasons[i]);
//2. valueOf(String name):要求传入的形参name是枚举类对象的名字
String str = "SPRING";
Season sea = Season.valueOf(str);
System.out.println(sea);
}
}
//接口
interface Info{
void show();
}
//枚举类
enum Season implements Info{
//1. 必须在开头声明,每个枚举类中show方法不同
SPRING("spring", "春暖花开"){
public void show(){
System.out.println("春天在哪里?");
}
},
SUMMER("summer", "夏日炎炎"){
public void show(){
System.out.println("生如夏花");
}
},
AUTUMN("autumn", "秋高气爽"){
public void show(){
System.out.println("秋天是用来分手的季节");
}
},
WINTER("winter", "白雪皑皑"){
public void show(){
System.out.println("冬天里的一把火");
}
};
//2. 定义枚举类的属性和构造器
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3. 通过公共的方法来调用属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
public String toString() {
return "Season [seasonName=" + seasonName + ", seasonDesc=" + seasonDesc + "]";
}
public void show() {
System.out.println("这是一个季节");
}
}
注解
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素。
JDK提供的常用的三个注解
@Override: 限定重写父类方法, 该注释只能用于方法@Deprecated: 用于表示某个程序元素(类, 方法等)已过时@SuppressWarnings: 抑制编译器警告
自定义注解
以 SuppressWarnings 为例进行创建即可
元注解
JDK 的元 Annotation 用于修饰其他 Annotation 定义,即元注解是修饰注解的注解。
JDK5.0提供了专门在注解上的注解类型,分别是:
- Retention
- Target
- Documented
- Inherited
@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留多长时间, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
RetentionPolicy.SOURCE:编译器直接丢弃这种策略的注释RetentionPolicy.CLASS:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解。 这是默认值RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注释. 程序可以通过反射获取该注释
@Target :用于修饰 Annotation 定义,用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。
@Documented :用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。定义为 Documented 的注解必须设置 Retention 值为 RUNTIME。
@Inherited :被它修饰的 Annotation 将具有继承性。如果某个类使用了被 @Inherited 修饰的 Annotation,则其子类将自动具有该注解实际应用中,使用较少。
IO 流
File 类
java.io.File 类:文件和目录路径名的抽象表示形式,与平台无关。File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。File 对象可以作为参数传递给流的构造函数。
java.io.File 类的特点:
- File 既可以表示一个文件(.doc .xls .mp3 .avi .jpg .dat),也可以表示一个文件目录!
- File 类的对象是与平台无关的。
- File 类针对于文件或文件目录,只能进行新建、删除、重命名、上层目录等等的操作。如果涉及到访问文件的内容, File 是无能为力的,只能使用IO流下提供的相应的输入输出流来实现。
- 常把 File 类的对象作为形参传递给相应的输入输出流的构造器中!
File 类的构造
public File(String pathname)
以 pathname 为路径创建 File 对象,可以是绝对路径或者相对路径,如果 pathname 是相对路径,则默认的当前路径在系统属性 user.dir 中存储。public File(String parent,String child)
以 parent 为父路径,child 为子路径创建 File 对象。
File 的静态属性 String separator 存储了当前系统的路径分隔符。在 UNIX 中,此字段为‘/’,在 Windows 中,为‘\\’。
代码示例:1
2
3
4
5
6// 绝对路径方式,包括盘符在内的完整文件路径
File file1 = new File("d:/io/helloworld.txt");
// 相对路径方式,在当前文件目录下的文件路径
File file1 = new File("hello.txt");
// 文件夹路径,不包含文件名
File file1 = new File("d:\\io\\il1");
Flie 类常用方法
访问文件名:
- getName()
- getPath()
- getAbsoluteFile()
- getAbsolutePath()
- getParent()
- renameTo(File newName)
文件检测:
- exists()
- canWrite()
- canRead()
- isFile()
- isDirectory()
获取常规文件信息:
- lastModified()
- length()
文件操作相关:
- createNewFile()
- delete()
目录操作相关:
- mkDir()
- mkDirs()
- list()
- listFiles()
IO 流原理
IO 流用来处理设备之间的数据传输。Java 程序中,对于数据的输入/输出操作以”流(stream)” 的方式进行。java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中
IO 流的划分:
- 按照流的流向的不同:输入流、输出流 (站位于程序的角度)
- 按照流中的数据单位的不同:字节流、字符流(纯文本文件使用字符流 ,除此之外使用字节流)
- 按照流的角色的不同:节点流、处理流(流直接作用于文件上是节点流(4个),除此之外都是处理流)
IO 流的体系
| 分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
|---|---|---|---|---|
| 抽象类型 | InputStream | OutputStream | Reader | Writer |
| 访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
| 访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
| 访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
| 访问字符串 | StringReader | StringWriter | ||
| 缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
| 对换流 | InputStreamReader | OutputStreamWriter | ||
| 对象流 | ObjectInputStream | ObjectOutputStream | ||
| FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | |
| 打印流 | PrintStream | PrintWriter | ||
| 推回输入流 | PushbackInputStream | PushbackReader | ||
| 特殊流 | DataInputStream | DataOutputStream |
注:
- 从硬盘中读入一个文件,要求此文件一定得存在。若不存在,报 FileNotFoundException的异常;
- 从程序中输出一个文件到硬盘,此文件可以不存在。若不存在,就创建一个实现输出。若存在,则将已存在的文件覆盖;
- 真正开发时,就使用缓冲流来代替节点流;
- 主要最后要关闭相应的流。先关闭输出流,再关闭输入流。将此操作放入 finally;
IO 流的应用
FileInputStream
1 | //从硬盘存在的一个文件中,读取内容到程序中使用FileInputStream |
FileOutputStream
1 |
|
FileReader 与 FileWriter
1 |
|
缓冲流
为了提高数据读写的速度,Java API 提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组。
缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。
对于输出的缓冲流,写出的数据会先在内存中缓存,使用 flush() 将会使内存中的数据立刻写出。
对应于节点流,缓冲流有如下4种:
- BufferedInputStream
- BufferedOutputStream
- BufferedReader
- Buffered]Writer
示例代码: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
51import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.junit.Test;
public class TestBuffered {
// 使用 BufferedInputStream 和 BufferedOutputStream 实现非文本文件的复制
@ Test
public void testBufferedInputOutputStream() {
// 3. 将创建的节点流的对象作为形参传递给缓冲流的构造器中
BufferedInputStream bis = null;
BufferedOutputStream bos= null;
try {
// 1. 提供读入、写出的文件
File file_in = new File("angel.png");
File file_out = new File("angel_copy.png");
// 2. 创建相应的节点流,FileInputStream、FileOutputStream
FileInputStream fis = new FileInputStream(file_in);
FileOutputStream fos = new FileOutputStream(file_out);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
// 4. 具体的实现文件复制的操作
byte[] b = new byte[1024];
int len;
while((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
bos.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭相应的流
if (bos != null)
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
if (bis != null)
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓冲流实现文件的复制操作,较节点流速度要快。
转换流
转换流提供了在字节流和字符流之间的转换,Java API 提供了两个转换流:
- InputStreamReader
输入时,实现字节流到字符流的转换,提高操作的效率(前提是,数据是文本文件)===>解码:字节数组—>字符串 - OutputStreamWriter
输出时,实现字符流到字节流的转换。 ===>编码:字符串—->字节数组
字节流中的数据都是字符时,转成字符流操作更高效。
示例代码: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
40public class TestOtherStream {
@ Test
public void test() {
BufferedReader br = null;
BufferedWriter bw = null;
try {
// 解码
File file = new File("dbcp.txt");
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis, "GBK");
br = new BufferedReader(isr);
// 编码
File file1 = new File("dbcp2.txt");
FileOutputStream fos = new FileOutputStream(file1);
OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");
bw = new BufferedWriter(osw);
String str;
while ((str = br.readLine()) != null) {
bw.write(str);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bw != null)
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
if (br != null)
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
标准的输入输出流
System.in 和 System.out 分别代表了系统标准的输入和输出设备。默认输入设备是键盘,输出设备是显示器。System.in 的类型是 InputStream;System.out 的类型是 PrintStream,其是 OutputStream 的子类 FilterOutputStream 的子类。
示例代码: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
26public void systemIn() {
BufferedReader br = null;
try {
InputStream is = System.in;
InputStreamReader isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String str;
while (true) {
System.out.println("请输入字符串: ");
str = br.readLine();
if (str.equals("e") || str.equals("exit")) {
break;
}
String res = str.toUpperCase();
System.out.println(res);
}
} catch (IOException e) {
e.printStackTrace();
}
if (br != null)
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
打印流
分类:
- PrintStream(字节打印流)
- PrintWriter(字符打印流)
特点:
- 提供了一系列重载的 print 和 println 方法,用于多种数据类型的输出
- PrintStream 和 PrintWriter 的输出不会抛出异常
- PrintStream 和 PrintWriter 有自动 flush 功能
- System.out 返回的是 PrintStream 的实例
- 可以使用 System.setOut(PrintStream p)重新设置一下输出的位置。
声明方式:
PrintStream p = new PrintStream(new FileOutputStream(“hello.txt”),true);
代码示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public void printStreamWriter() {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File("print.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
PrintStream ps = new PrintStream(fos, true);
if (ps != null) { // 把标准输出流(控制台输出)改成文件
System.setOut(ps);}
for (int i = 0; i <= 255; i++) { //输出ASCII字符
System.out.print((char)i);
if (i % 50 == 0) { //每50个数据一行
System.out.println(); // 换行
} }
ps.close();
}
数据流
为了方便地操作 Java 语言的基本数据类型的数据,可以使用数据流。
数据流有两个类:(用于读取和写出基本数据类型的数据)
- DataInputStream
- DataOutputStream
分别“套接”在 InputStream 和 OutputStream 节点流上
DataInputStream 中的方法
- boolean readBoolean()
- char readChar()
- double readDouble()
- long readLong()
- String readUTF()
- byte readByte()
- float readFloat()
- short readShort()
- int readInt()
- void readFully(byte[] b)
DataOutputStream 中的方法
- 将上述的方法的 read 改为相应的 write 即可。
代码示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public void dataStream() {
DataOutputStream dos = null;
try {
FileOutputStream fos = new FileOutputStream("data.txt");
dos = new DataOutputStream(fos);
dos.writeUTF("我爱你,而你却不知道!"); //写UTF字符串
dos.writeBoolean(true); //写入布尔值
dos.writeLong(1432522344); //写入长整数
System.out.println("写文件成功!");
} catch (IOException e) {
e.printStackTrace();
} finally { //关闭流对象
if (dos != null) {
// 关闭过滤流时,会自动关闭它包装的底层节点流
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象流
用于存储和读取对象的处理流。它的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原回来。
序列化 (Serialize) :用 ObjectOutputStream 类将一个 Java 对象写入 IO 流中
反序列化 (Deserialize) :用 ObjectInputStream 类从 IO 流中恢复该 Java 对象
对象的序列化
对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象。
序列化的好处在于可将任何实现了 Serializable 接口的对象转化为字节数据,使其在保存和传输时可被还原。
序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。
如果需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
- Serializable
- Externalizable
凡是实现 Serializable 接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID;serialVersionUID用来表明类的不同版本间的兼容性- 如果类没有显示定义这个静态变量,它的值是 Java 运行时环境根据类的内部细节自动生成的。若类的源代码作了修改,
serialVersionUID可能发生变化。故建议,显示声明
显示定义 serialVersionUID 的用途
- 希望类的不同版本对序列化兼容,因此需确保类的不同版本具有相同的
serialVersionUID - 不希望类的不同版本对序列化兼容,因此需确保类的不同版本具有不同的
serialVersionUID
实现序列化机制的对象对应的类的要求:
- 要求类要实现 Serializable 接口
- 同样要求类的所有属性也必须实现 Serializable 接口
- 要求给类提供一个序列版本号:
private static final long serialVersionUID; - 属性声明为
static或transient的,不可以实现序列化
示例代码
1 | package TreeSet; |
多线程
基本概念 - 程序 - 进程 - 线程
程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process) 是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
- 如:运行中的 QQ,运行中的 MP3 播放器
- 程序是静态的,进程是动态的
线程(thread) 是由进程进一步细化而来,是一个程序内部的一条执行路径。
- 若一个程序可同一时间执行多个线程,就是支持多线程的
何时需要多线程:
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
多线程的创建和启动
Java 语言的 JVM 允许程序运行多个线程,它通过 java.lang.Thread 类来实现。
Thread 类的特性
- 每个线程都是通过某个特定 Thread 对象的
run()方法来完成操作的,经常把run()方法的主体称为线程体 - 通过该 Thread 对象的
start()方法来调用这个线程
方法一 声明继承 Thread 子类
- 定义子类继承 Thread 类。
- 子类中重写 Thread 类中的
run方法。 - 创建 Thread 子类对象,即创建了线程对象。
- 调用线程对象
start方法:启动线程,调用run方法
方法二 声明实现 Runnable 接口
- 定义子类,实现 Runnable 接口。
- 子类中重写 Runnable 接口中的
run方法。 - 通过 Thread 类含参构造器创建线程对象。
- 将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造方法中。
- 调用 Thread 类的
start方法:开启线程,调用 Runnable 子类接口的run方法。
Thread 常用方法
start():启动线程并执行相应的run()方法run():子线程要执行的代码放入run()方法中currentThread():静态的,调取当前的线程getName():获取此线程的名字setName():设置此线程的名字yield():调用此方法的线程释放当前 CPU 的执行权join():在 A 线程中调用B 线程的join()方法,表示,当执行到此方法,A 线程停止执行,直到 B 线程执行完毕,A 线程再接着 join() 之后的代码执行isAlive():判断当前线程是否还存活sleep(long l):显示的让当前线程睡眠 l 毫秒- 线程通信:
wait()notify()notifyAll()
线程的分类与生命周期
分类
Java 中的线程分为两类:一种是守护线程,一种是用户线程。它们在几乎每个方面都是相同的,唯一的区别是判断 JVM 何时离开。
守护线程是用来服务用户线程的,通过在 start() 方法前调用 thread.setDaemon(true) 可以把一个用户线程变成一个守护线程。
Java 垃圾回收就是一个典型的守护线程。若 JVM 中都是守护线程,当前 JVM 将退出。
生命周期
要想实现多线程,必须在主线程中创建新的线程对象。Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建: 当一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被
start()后,将进入线程队列等待 CPU 时间片,此时它已具备了运行的条件 - 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,
run()方法定义了线程的操作和功能 - 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止

线程的同步
前提:如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题。
解决方式:要求一个线程操作共享数据时,只有当其完成操作共享数据,其它线程才有机会执行共享数据。
方式一 同步代码块:1
2
3synchronized(同步监视器){
//操作共享数据的代码
}
注:
- 同步监视器:俗称锁,任何一个类的对象都可以才充当锁。要想保证线程的安全,必须要求所有的线程共用同一把锁!
- 使用实现 Runnable 接口的方式创建多线程的话,同步代码块中的锁,可以考虑是 this。如果使用继承 Thread 类的方式,慎用 this!
- 共享数据:多个线程需要共同操作的变量。明确哪部分是操作共享数据的代码。
方式二 同步方法:
将操作共享数据的方法声明为 synchronized。比如1
2
3public synchronized void show(){
//操作共享数据的代码
}
注:
- 对于非静态的方法而言,使用同步的话,默认锁为:this。如果使用在继承的方式实现多线程的话,慎用!
- 对于静态的方法,如果使用同步,默认的锁为:当前类本身。以单例的懒汉式为例。
Class clazz = Singleton.class
互斥锁与死锁
互斥锁
在 Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
同步的局限性:导致程序的执行效率要降低。同步方法(非静态的)的锁为 this。同步方法(静态的)的锁为当前类本身。
死锁
死锁是指不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
解决方法:
- 专门的算法、原则
- 尽量减少同步资源的定义
释放锁操作
- 当前线程的同步方法、同步代码块执行结束;
- 当前线程在同步代码块、同步方法中遇到 break、return 终止了该代码块、该方法的继续执行;
- 当前线程在同步代码块、同步方法中出现了未处理的 Error 或 Exception,导致异常结束;
- 当前线程在同步代码块、同步方法中执行了线程对象的
wait()方法,当前线程暂停,并释放锁。
不释放锁的操作
- 线程执行同步代码块或同步方法时,程序调用
Thread.sleep()、Thread.yield()方法暂停当前线程的执行; - 线程执行同步代码块时,其他线程调用了该线程的
suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程。
死锁代码示例: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
32public class TestDeadLock {
static StringBuffer sb1 = new StringBuffer();
static StringBuffer sb2 = new StringBuffer();
public static void main(String[] args) {
new Thread() {
public void run() {
synchronized(sb1) {
sb1.append("A");
synchronized(sb2) {
sb2.append("B");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
new Thread() {
public void run() {
synchronized(sb2) {
sb1.append("C");
synchronized(sb1) {
sb2.append("D");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
}
}
线程通信
wait() 与 notify() 和 notifyAll()
wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待notifyAll():唤醒正在排队等待资源的所有线程结束等待
Java.lang.Object 提供的这三个方法只有在synchronized 方法或 synchronized 代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException 异常。
1 | class PrintNum implements Runnable { |
字符串类
String 类
字符串广泛应用在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。
构造方法
1 | String s1 = new String(); |
字符串的特性
String 是一个 final 类,代表不可变的字符序列。
字符串是不可变的。一个字符串对象一旦被配置,其内容是不可变的。
所以你一旦创建了 String 对象,那它的值就无法改变了。 如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类。
字符串对象的操作
返回字符串长度
1
public int length()
返回在指定
index位置的字符,index从零开始1
public char charAt(int index)
比较两个字符串是否相等,相等返回
true,否则返回false1
public boolean equals(Object anObject)
比较两个字符串是否相等,用于排序
1
public int compareTo(String anotherString)
返回
s字符串在当前字符串首次出现的位置。若没有,返回-1,startpoint表示从哪个位置开始匹配。1
2public int indexOf(String s)
public int indexOf(String s ,int startpoint)返回
s字符串在当前字符串最后一次出现的位置。若没有,返回-1,startpoint表示从哪个位置开始匹配。1
2public int lastIndexOf(String s)
public int lastIndexOf(String s ,int startpoint)判断当前字符串是否以
prefix开始1
public boolean startsWith(String prefix)
判断当前字符串是否以
prefix结束1
public boolean endsWith(String suffix)
判断当前字符串从
firstStart开始的子串与另一个字符串other从otherStart开始,length长度的字符串是否相等1
public boolean regionMatches(int firstStart,String other,int otherStart ,int length)
字符串对象的修改
返回从
startpoint开始到原字符串末尾的子字符串1
public String substring(int startpoint)
返回从
start开始到end结束的一个左闭右开的子字符串1
public String substring(int start,int end)
将
oldChar替换为newChar1
pubic String replace(char oldChar,char newChar)
将字符串字符串
old替换为new1
public String replaceAll(String old,String new)
去除当前字符串中首尾出现的空格
1
public String trim()
连接当前字符串与 str
1
public String concat(String str)
按照 regex 将当前字符串拆分拆分为多个字符串,整体返回一个字符串数组
1
public String[] split(String regex)
字符串与基本数据类型转换
字符串与基本数据类型、包装类的转换
1
2
3
4String str = "123";
int i = Integer.parseInt(str); // 字符串--->整型
String str1 = i + ""; // 整型--->字符串
String str2 = String.valueOf(i); // 整型--->字符串字符串与字节数组的转换
1
2
3String str = "abc123";
byte[] b = str.getBytes(); // 字符串--->字节数组
String str3 = new String(b) // 字节数组--->字符串字符串与字符数组的转换
1
2
3String str = "abc123中国人"
char[] c = str.toCharArray(); // 字符串--->字符数组
String str5 = new String(c); // 字符数组--->字符串
StringBuffer 类
java.lang.StringBuffer 代表可变的字符序列,可以对字符串内容进行增删。很多方法与 String 相同,但 StingBuffer 是可变长度的。
StingBuffer 是一个容器。
构造方法
1.StringBuffer() 初始容量为 16 的字符串缓冲区
2.StringBuffer(int size) 构造指定容量的字符串缓冲区
3.StringBuffer(String str) 将内容初始化为指定字符串内容
常用方法
1 | StringBuffer append(String s), StringBuffer append(int n) , |
总结:添加 append(); 修改 setCharAt(); 查询 charAt(int index); 插入 insert(); 反转 reverse(); 删除 delete()
StringBuilder 类
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且方法也一样
- String:不可变字符序列
- StringBuffer:可变字符序列、效率低、线程安全
- StringBuilder(JDK1.5):可变字符序列、效率高、线程不安全
日期类
System 类
System 类提供的 public static long currentTimeMillis() 用来返回当前时间与 1970 年 1 月 1 日 0 时 0 分 0 秒之间以毫秒为单位的时间差。此方法适于计算时间差。
计算世界时间的主要标准有:
- UTC(Universal Time Coordinated)
- GMT(Greenwich Mean Time)
- CST(Central Standard Time)
Date 类
java.util.Date 类,表示特定的瞬间,精确到毫秒。java.sql.Date 类为其的一个子类。
构造方法
- Date():使用 Date 类的无参数构造方法创建的对象可以获取本地当前时间。
- Date(long date):创建指定毫秒数的 Date 对象
常用方法
- getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
- toString():把此 Date 对象转换为以下形式的 String:dow mon dd hh:mm:ss zzz yyyy 其中:dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。
代码示例
1 | Data date1 = new Date(); |
SimpleDateFormat 类
`java.text.SimpleDateFormat 类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
它允许进行格式化 和 解析 操作。格式化:日期—>文本(字符串),解析:文本(字符串)—>日期
格式化
SimpleDateFormat():默认的模式和语言环境创建对象public SimpleDateFormat(String pattern):该构造方法可以用参数 pattern 指定的格式创建一个对象,该对象调用:public String format(Date date):方法格式化时间对象 date
解析
- public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期。
代码示例
1 | // 实例化 SimpleDateFormat:使用默认构造器 |
Calendar 类
java.util.Calendar (日历)类是一个抽象基类,主用用于完成日期字段之间相互操作的功能。
获取Calendar实例的方法
- 使用Calendar.getInstance()方法
- 调用它的子类GregorianCalendar的构造器。
一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND
- public void set(int field,int value)
- public void add(int field,int amount)
- public final Date getTime()
- public final void setTime(Date date)
Math BigInteger BigDecimal
Math 类
java.lang.Math 提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为 double 型。
- abs 绝对值
- acos,asin,atan,cos,sin,tan 三角函数
- sqrt 平方根
- pow(double a,doble b) a的b次幂
- log 自然对数
- exp e为底指数
- max(double a,double b)
- min(double a,double b)
- random() 返回0.0到1.0的随机数
- long round(double a) double型数据a转换为long型(四舍五入)
- toDegrees(double angrad) 弧度—>角度
- toRadians(double angdeg) 角度—>弧度
BigInteger 类
Integer 类作为 int 的包装类,能存储的最大整型值为 2^31−1,BigInteger 类的数字范围较 Integer 类的数字范围要大得多,可以支持任意精度的整数。
构造器
- BigInteger(String val)
常用方法 - public BigInteger abs()
- public BigInteger add(BigInteger val)
- public BigInteger subtract(BigInteger val)
- public BigInteger multiply(BigInteger val)
- public BigInteger divide(BigInteger val)
- public BigInteger remainder(BigInteger val)
- public BigInteger pow(int exponent)
- public BigInteger[] divideAndRemainder(BigInteger val)
BigDecimal 类
一般的 Float 类和 Double 类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到 java.math.BigDecimal 类。BigDecimal 类支持任何精度的定点数。
构造器
- public BigDecimal(double val)
- public BigDecimal(String val)
常用方法 - public BigDecimal add(BigDecimal augend)
- public BigDecimal subtract(BigDecimal subtrahend)
- public BigDecimal multiply(BigDecimal multiplicand)
- public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)