Java之常用API(中)
正则表达式
正则表达式是一串特定字符,组成一个“规则字符串”,这个“规则字符串”是描述文本规则的工具。正则表达式就是记录文本规则的代码。
1.1 字符集:
[abc] a、b、c中任意一个字符
[^abc] 除了a、b、c的任意字符
[a-z] a、b、c、……、z中的任意一个字符
[a-zA-Z0-9] az、AZ、0~9中任意一个字符
[a-z&&[^bc]] a~z中除了b和c以外的任意一个字符,其中&&表示“与”的
关系。
1.2 预定义字符集:
. 任意一个字符。
\d 任意一个数字字符,相当于[0-9]。
\w 单词字符,相当于 [ a-zA-Z0-9_ ]。
\s 空白字符,相当于[ \t\n\r ]。
\D 非数字字符。
\W 非单词字符。
\S 非空白字符。
注意:在使用预定义字符集时,需要注意,因为这些字符现在表示的不是它本来的意义了,所以在使用预定义字符集时,需要进行转义操作。
\d \w \s
. * ? + 如果想使用这些特殊字符的本意,此时也需要转义 \+ \.
1.3 数量词:
可以利用数量词约定字符出现的次数
X? 表示0个或1个X
X* 表示0个或任意多个X
X+ 表示1个到任意多个X(大于等于1个X)
X{n} 表示n个X
X{ n,} 表示n个到任意多个X(大于等于n个X)
X{ n , m } 表示n个到 m 个 X
1.4 分组:():
() 圆括号表示分组,可以将一系列正则表达式看做一个 整体,在正则表达式中()通常和|一起使用。
1.5 边界匹配:
^ 匹配开始,$ 匹配结束
String regex1 = “^\w{8,10}$”;
- 对一个字符串中的所有数字进行替换成”数字”
a) String str = “hello12world0 234ok5”;
b) replaceAll(“\d+”,”数字”)
public static void main(String[] args) {
String str = “hello12world0 234ok5”;
String newStr = str.replaceAll("\d+",“数字”);
System.out.println(newStr);
}
2.敏感字符过滤
正则表达式中中文如何表示?Unicode编码来表示中文[\u4e00-\u9fa5]
/*
- 验证用户输入的姓名是否是2-4位
*/
String regex = “[\u4e00-\u9fa5]{2,4}”;
3. 集合
集合其实就是容器,是用于保存数据的。
可以保存数量不定的数据,也可以保存具有映射关系的数据。
集合:数组是一个容器,集合也是容器
集合的结构:
Collection:Collection是一个接口,定义了集合相关的操作方法,其有两个子接口:
List:接口 -可重复集
Set:接口 - 不可重复集
Map:接口 用于保存具有映射关系的数据
集合的操作:
add(Object obj): 返回值类型 boolean
Collection定义了一个add方法用于向集合中添加新元素。
该方法会将给定的元素添加进集合,若添加成功则返回true,否则返回false
size():
clear():
isEmpty():boolean
contains(Object obj) – boolean 该方法会用于判断给定的元素是否被包含在集合中。
判断方式:
这里需要注意的是,集合在判断元素是否被包含在集合中是根据每个元素的equals()方法进行比较后的结果。通常有必要重写equals()保contains()方法的合理结果。
addAll(Collection)
containsAll(Collection):Boolean
注意点:
• 集合中存储的都是引用类型元素,并且集合只保存每个元素对象的引用,而并非将元素对象本身存入集合。
3.1 泛型机制:
• 问题:如果一个集合中保存的元素类型不统一,方便管理吗?
• 在实际开发中,一般一个集合中保存的是相同数据类型的元素,如何做到呢? • 通过泛型机制
集合
• 泛型是Java SE 5.0引入的特性,泛型的本质是参数化类型。在类、接口和方法的定义过程中,所操作的数据类型被传入的参数指定。
3.2 Iterator迭代器
• 迭代器用于遍历集合元素。获取迭代器可以使用Collection定义的方法: iterator()
• 迭代器Iterator是一个接口,集合在重写Collection的iterator()方法时利用内部类提供了迭代器的实现。
遍历方式:
hasNext()
next()
remove()
注意:使用迭代器遍历集合元素过程中,如果想删除满足条件的元素,必须是通过迭代器的remove方法删除,不可以通过集合的remove方法删除
使用迭代器遍历集合的步骤:
- 获得迭代器对象 Collection中的iterator()
- 写一个while循环,根据hasNext()的返回值决定是否继续循环
a) 调用next()获取元素并执行相对应的操作
public class IteratorDemo {
public static void main(String[] args) {
/*
- 创建集合对象用户保存学生对象信息
- 要求对此集合中的所有学生进行遍历并将学生信息打印输出
/
Collection col = new ArrayList<>();
for(int i=1;i<=5;i++){
Student student = new Student(i, “学生”+i, 20+i);
col.add(student);
}
//对集合进行遍历
Iterator it = col.iterator();
while(it.hasNext()){
Student student = it.next();
System.out.println(student);
}
}
forEach循环:增强for循环
结构:
for(元素数据类型 引用:集合/数组){
循环体
}
Java5.0之后推出了一个新的特性,增强for循环,也称为新循环。该循环不通用于传统循环的工作,其只用于遍历集合或数组。
• 在编译过程中,编译器会将新循环转换为迭代器模式。所以新循环本质上是迭代器。
/ - 使用foreach循环来遍历集合
*/
for (Student student : col) {
System.out.println(student);
}
集合操作 -线性表
1.1 List
List集合特点:可重复集,有索引的
List接口是Collection的子接口,用于定义线性表数据结构。可以将List理解为存放对象的数组,只不过其元素个数可以动态的增加或减少。
List接口的两个常见实现类为ArrayList和LinkedList,分别用动态数组和链表的方式实现了List接口。
可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别。ArrayList更适合于随机访问而LinkedList更适合于插入和删除。在性能要求不是特别苛刻的情形下可以忽略这个差别。
ArrayList和LinkedList
两者的数据结构
ArrayList:动态数组结构
优点:查询性能高
缺点:增删元素性能较低
LinkedList:双向循环链表
优点:增删元素性能更高
缺点:查询性能较低
List集合下和索引相关的方法操作:
get和set:
get(int index):获取集合中指定下标对应的元素,下标从0开始。
set(int index, E elment):将给定的元素存入给定位置,并将原位置的元素返回。
插入和删除:
void add(int index,E element):
将给定的元素插入到指定位置,原位置及后续元素都顺序向后移动。
remove(int index):
删除给定位置的元素,并将被删除的元素返回。
public static void main(String[] args) {
List list = new ArrayList<>();
//添加元素
list.add(“a”);
list.add(“b”);
list.add(“c”);
//取出第二个元素
// String ele = list.get(1);
// System.out.println(ele);
// //遍历
// for(int i=0;i<list.size();i++){
// String element = list.get(i);
// System.out.println(element);
// }
List转换为数组:List的toArray方法用于将集合转换为数组。
其有两个方法:
Object[] toArray()
T[] toArray(T[] a)
其中第二个方法是比较常用的,我们可以传入一个指定类型的数组,该数组的元素类型应与集合的元素类型一致。返回值则是转换后的数组,该数组会保存集合中所有的元素。
数组转换为List:
Arrays类中提供了一个静态方法asList,使用该方法我们可以将一个数组转换为对应的List集合。并且要注意的是,返回的集合我们不能对其增删元素,否则会抛出异常。并且对集合的元素进行修改会影响数组对应的元素。
注意点:
- ArrayàList不可以对List集合进行增删操作,否则抛出异常
- 可以对得到的list集合进行元素修改操作,但是原数组中对应的元素也会发生变化。
String[] strs = {“a”,“b”,“c”,“d”,“e”};
//Array–>List转换
List listStr = Arrays.asList(strs);
System.out.println(listStr);
List排序
• Collections是集合的工具类,它提供了很多便于我们操作集合的方法,其中就有用于集合排序的sort方法。
该方法定义为: void sort(List list)
该方法的作用是对给定的集合元素进行排序。
Comparable:接口
如果要使用Collections.sort(List)实现对一个List集合的排序,集合中的元素的排序规则必须提前定义好。
定义集合中元素的排序规则:
类 implements Comparable{
compareTo():定义好比较规则
}
Comparable接口叫做内比较器
Comparator接口叫做外比较器
经常用到的数据结构
队列和栈
Queue:接口 队列
Queue: java.util.Queue
特点:先进先出(FIFO,First Input First Output)
队列(Queue)是常用的数据结构,可以将队列看成特殊的线性表
方法:
Offer(E e):将一个对象添加至队尾,如果添加成功则返回true。
取元素:
poll(): 从队首删除并返回一个元素。
peek():返回队首的元素(但并不删除)。
注意点:调用peek和poll方法如果队列为空,返回null
- 创建队列
Queue queue = new LinkedList<>();
System.out.println(queue);
/* - 入队操作
/
queue.offer(“a”);
queue.offer(“b”);
queue.offer(“c”);
System.out.println(queue);
/ - 出队操作
- poll():E
- 如果队列中没有元素,调用此方法返回null
/
String str = queue.poll();
System.out.println(str);
System.out.println(queue);
queue.poll();
queue.poll();
str = queue.poll();
System.out.println(str);
System.out.println(queue);
/ - peek():E 获取但不出队元素
- 如果队列中没有元素,调用此方法返回null
/
str = queue.peek();
System.out.println(str);
System.out.println(queue);
}
Deque:接口
• Deque是Queue的子接口,定义了所谓“双端队列”即从队列的两端分别可以入队(offer)和出队(poll),LinkedList实现了该接口。
• LinkedList Deque Queue三者之间的关系:
• Queue <------Deque <---------LinkedList
双端队列对象的创建:
Deque deque = new LinkedList<>();
方法:
Offer(E e)
offerFirst(E e)
offerLast(E e)
poll()
pollFirst()
• 如果将Deque限制为只能从一端入队和出队,则可实现“栈”(Stack)的数据结构,对于栈而言,入栈称之为push,出栈称之为pop。
• 栈遵循先进后出(FILO First Input Last Output )的原则。
对象的创建
Deque stack = new LinkedList<>();
入栈:push(E)
出栈:pop():E
public static void main(String[] args) {
Deque stack = new LinkedList<>();
/ - 完成入栈操作
- push(E):void
/
stack.push(“a”);
stack.push(“b”);
stack.push(“c”);
System.out.println(stack);
/ - 出栈
- pop():E
*/
String str = stack.pop();
System.out.println(str);
System.out.println(stack);
}
常见的数据结构:
Queue、Deque、Stack
2 Set接口
元素不可重复
添加元素时,调用集合中元素的equals方法
Set接口下的大部分实现类所表示的set集合都是无序的,但是也有特殊的。所以不能说set集合是无序的。
实现类:
HashSet() TreeSet()…
查找表:
Map接口
Map集合用于保存具有映射关系的数据,如 语文-90.0
具有映射关系的数据又叫做键值对key-value
语文 – 90 key-value
数学 – 80
英文 – 90
Map集合保存的就是键值对的数据
强调点:
- Map集合中保存的k-v 其中k的值是唯一的,v的值可以重复,如果map集合中已经存在一个key值,那么继续向集合中添加和这个key相同的k-v对时,会让后添加的k-v中的value值覆盖掉key之前的value值。
- Map集合中的k和v的数据类型一定都是引用类型。
Map接口下常见的实现类:
hashMap() TreeMap()
Map接口中定义的方法:
put(K key,V value):向集合中存放元素。
get(K key) – V
containsKey(Object key):判读某个key是否在Map中存在、
public static void main(String[] args) {
Map集合的遍历:
keySet():得到map集合中所有key的set集合,可以实现对map集合中所有key值的遍历。
entrySet():
得到map集合中所有键值对的set集合
/*
- keySet():Set
/
for (String str:map.keySet()) {
System.out.println(str);
}
/ - entrySet():Set<Entry<K,V>>
/
for (Entry<String, Double> entry : map.entrySet()) {
//获取k-v中的key值
String key = entry.getKey();
//获取k-v中的value值
Double val = entry.getValue();
System.out.println(key+"-"+val);
}
HashCode()
Object类中的hashCode()方法:返回该对象所在内存地址的整数形式
如果两个对象equals相等,那么得到的hashCode值(返回的是一个int值)一定相等,但是如果两个对象的hashCode值相等,这两个对象不一定equals – 即hashCode值不是唯一的。
但是使用HashMap时应当尽量避免出现链表的情况.
HashMap中链表的产生原因:
HashMap是根据key元素的hashcode方法返回的数字经过散列计算得到该组键值对应当存放在数组的哪个位置,而根据key元素equals比较查看当前key是否已经存在于Map中,若存在则直接替换该位置这
个key所对应的value,但是若数组该位置已经存在键值对,并且key与当前准备存放的键值对中的key的equals比较不同时,则会在数组当前位置产生链表.这样在将来根据key检索时会出现遍历链表的情况,这样就会降低HashMap查询性能.
总结:当key元素hashcode值相同,但是equals比较不同时就会在HashMap中产生链表,影响查询性能.
如何尽可能的避免出现链表?
重写hashCode和equals方法
Map集合的实现类:
HashMap
TreeMap : 实现排序的map集合,采用红黑数算法
Set集合:
HashSet:的数据结构其实就是hashMap的数据结构
TreeSet:数据结构和TreeMap是一样的。
3.3 有序的Map – LinkedHashMap
• 使用Map接口的哈希表和链表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于:
LinkedHashMap维护着一个双向循环链表。此链表定义了迭代顺序,该迭代顺序通常就是存放元素的顺序。
HashMap中元素取出顺序和Put的顺序不一定一样。
LinkedHashMap中元素取出顺序和put的顺序一致。
文件操作 – File
java.io.File用于表示文件(目录),也就是说程序员可以通过File类在程
序中操作硬盘上的文件和目录。
1.1 创建File对象
构造方法:
1.File(String pathName)
路径名中用到目录的层级分隔符不要直接写”/”或”\”,应使用File.separator这个常量表示,以避免不同系统带来的差异。
2. File(File parent,String child)
3. File(String parent,String child)
注意点:
File对象可以指向已经存在的文件/目录,也可以指向不存在的文件/目录
createNewFile():创建空文件
注意点:如果file对象指向的文件已经存在,那么调用此方法返回false
mkdir():boolean
创建单级目录
mkdirs():boolean
创建多级目录
isFile():boolean
isDirectory():判断当前File对象表示的是否为一个目录
exists():boolean
:判断file对象指向的文件/目录是否存在,如果存在返回true,如果不存在,返回false
getName():String
delete():
需要注意的是,若此File对象所表示的是一个目录时,在删除时需要保证此为空目录才可以成功删除(目录中不能含有任何子项)
public static void main(String[] args) {
/
- isFile():boolean
- 判断file对象指向的是否是文件
/
File file = new File(“E:”+File.separator+“hello”);
System.out.println(file.isFile());
System.out.println(file.isDirectory());
/ - 要求在E盘hello目录下创建world目录,如果该目录
- 已经存在,那么不需要创建
*/
File dest = new
File(“E:”+File.separator+“hello”+File.separator+“world”);
if(!dest.exists()){
dest.mkdir();
System.out.println(“执行创建过程”);
}
String name = dest.getName();
System.out.println(name);
System.out.println(dest.delete());
}
案例:获取E盘下所有以.exe结尾的文件并打印输出文件名
思路:
- 对E盘下的所有目录/文件进行遍历
- 对遍历到的每个File对象进行筛选
a) 必须是文件
b) 以.exe结尾 - 得到目标文件获取其名称并打印
ListFiles()
/*
- 获取E盘下所有以.exe结尾的文件并打印输出文件名
/
File parent = new File(“E:”);
//获取此目录下的所有目录/文件
File[] ary = parent.listFiles();
//对目标数组进行遍历并筛选满足条件的文件
for (File file2 : ary) {
String fileName = file2.getName();
if(file2.isFile() && fileName.endsWith(".exe")){
System.out.println(fileName);
}
}
对某个父目录下的所有文件/目录进行筛选 – 文件过滤功能
FileFilter接口:文件过滤器
/ - 使用文件过滤器完成文件的过滤
*/
File[] dests = parent.listFiles(new FileFilter() {
//将过滤条件写入此方法中,此方法用于完成过滤功能
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(".exe");
}
});
for (File file2 : dests) {
System.out.println(file2.getName());
}
}
length():
File的length()方法用于返回由此抽象路径名表示的文件的长度(占用的字节量)
文件中的内容的长度是指文件中内容所占用的字节数
将字符写入文件过程中,其实是将字符转换成字节之后写入的,写入文件中的是字节数据。
3 编码:
互联网或者文件都是按照byte(8位数)进行数据传输的!
字符数据在互联网(文件)传输时候必须拆分为byte(8位)进行传输.
- 将字符数据拆分为byte数据的过程称为: 编码
- 将byte数据重新合并为字符数据过程称为: 解码
常见编码 - GBK 中国标准, 1~2字节变长编码, 支持字符2万+
规定:一个英文占用1个字节
一个中文占用2个字节
一个英文,一个中文 都对应一个unicode整数 - UTF-8 采用变长编码(1~4字节), 支持 100万+ 字符, 按照字符数值的大小进行编码, 英文采用1字节编码.
一个英文占用1个字节
一个中文占用3个字节
IO流
1.1 IO流分类:
按照流向分:
输入流:读数据
输出流:写数据
按照读写单位分:
字节流
字符流
Java中流还可以分为
节点流:
节点流又称为低级流,是实际连接程序与数据源的"管道",负责传输数据.读写一定是建立在节点流上进行的.
处理流:
处理流又称为高级流,用于处理字节流,不能独立存在(没有意义),使用高级流处理其他流的目的是通过高级流带来的功能简化我们对数据读写时的某些复杂处理.
1.2 字节流
抽象基类:
InputStream
OutPutStream
1.3 文件流:
FileOutputStream
FileInputStream
是一对低级流(节点流)
构造方法:
FileOutputStream(File file):
FileOutputStream(String filename):
FileOutputStream(File/String file,boolan append):
是否向文件实现追加写入功能
写数据方法:
write(int b):向文件中写入一个字节
write(byte[] bys):向文件中写入一个字节数组
思考:如何向文件中追加写入数据?
创建对象时调用2个参数的构造方法
注意点:
通过输出流对象向文件中写入数据,如果这个文件不存在,输出流对象会自动创建该文件
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
/*
- 向fos.txt文件中写入数据
/
// FileOutputStream fos = new
// FileOutputStream(“fos.txt”);
// //写操作
fos.write(97); //写入单个字节
//写入字节数组
fos.write(“hello”.getBytes());
//
// //关闭流
// fos.close();
/ - 实现向fos.txt文件追加写入信息
*/
FileOutputStream fos = new
FileOutputStream(“fos.txt”,true);
fos.write(“world”.getBytes());
fos.close();
FileInputStream:字节输入流
构造方法:
FileInputStream(File file)
FileInputStream(String name)
创建输入流对象从目标文件读取数据
注意点:
创建输入流对象时指向的目标文件必须是已经存在的,否则会抛出异常。
读取数据:
按单个字节读取,
按字节数组读取
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
//创建文件输入流对象用于读取数据
FileInputStream fis = new
FileInputStream(“fos.txt”);
//读取数据并打印到控制台
//按照单个字节读取
// int by = -1;
// while((by=fis.read())!=-1){
// System.out.print((char)by);
// }
//按照字节数组读取数据
byte[] bys = new byte[3];
int len = 0;
while((len=fis.read(bys))>0){
//将读取到的字节数组–>String
System.out.print(new String(bys, 0, len));
}
fis.close();
}
}
字节流中文件复制的实现:
案例:有一个目标文件,要求将此文件中的内容复制到另一个文件中去。
复制:先读后写
-2种方法:
- 按照单个字节复制
- 按照字节数组复制
注意点:
- 复制时只要文件格式是兼容的,就可以实现复制
- 创建的文件是在当前项目目录下,并不是在src目录下
- 字节流可以操作任何类型文件
- 按照字节数组读取,判断条件>0 ,也可以!=-1
/**
- 用文件流实现文件的复制
- 复制:先读后写
- 步骤:
- 1.创建输入/输出流对象
- 2.用输入流读取数据,用输出流写入数据
- 3.关闭流对象
/
public class CopyDemo01 {
public static void main(String[] args) throws IOException {
//创建输入流/输出流对象
FileInputStream fis = new
FileInputStream(“fis.java”);
FileOutputStream fos = new
FileOutputStream(“copy1.txt”);
//先读后写:复制 按照单个字节复制
按照单个字节复制
int by = -1;
while((by=fis.read())!=-1){
//将by写入目标文件中
fos.write(by);
}
按照字节数组复制
/
- 按照字节数组复制文件
*/
byte[] bys = new byte[1024];
int len = 0;
while((len=fis.read(bys))>0){
//将读取到的字节数组写入目标文件中
fos.write(bys, 0, len);
}
//关闭流对象
fis.close();
fos.close();
}
}
2.1.1 缓冲流
是一对处理流(高级流)
BufferedOutputStream
BufferedInputStream
BOS:BufferedOutputStream工作原理
在向硬件设备做写出操作时,增大写出次数无疑会降低写出效率,为此我们可以使用缓冲输出流来一次性批量写出若干数据减少写出次数来提高写出效率。
BufferedOutputStream缓冲输出流内部维护着一个缓冲区,每当我们向该流写数据时,都会先将数据存入缓冲区,当缓冲区已满时,缓冲流会将数据一次性全部写出。
字节缓冲输出流:
构造方法:
BufferedOutputStream(OutputStream)
写数据:
和之前文件流写数据的方法一致
字节缓冲输入流:
构造方法:
BufferedInputStream(InputStream)
读取数据方法:
和文件流读取数据的方法一致
/**
- 字节缓冲输出流写数据
- 特点:效率高
- @author adminitartor
*/
public class BufferedOutputStreamDemo {
public static void main(String[] args) throws IOException {
//创建对象
BufferedOutputStream bos = new
BufferedOutputStream(
new FileOutputStream(“bos.txt”,true));
//写数据
//按照单个字节写
// bos.write(104);
//按照字节数组写
bos.write(“helloworld中国”.getBytes());
//释放资源
bos.close();
}
}
/**
- 字节缓冲输入流读取数据
- 特点:当读取文件内容量大,读取效率高
- @author adminitartor
*/
public class BufferedInputStreamDemo {
public static void main(String[] args) throws IOException {
//创建输入流对象
BufferedInputStream bis = new
BufferedInputStream(
new FileInputStream(“fis.java”));
//调用方法读取数据
byte[] bys = new byte[1024];
int len = -1;
while((len=bis.read(bys))!=-1){
System.out.print(new String(bys, 0, len));
}
//释放资源
bis.close();
}
}
练习:分别用文件流和缓冲流复制文件,图片,比较效率
字节缓冲输出流:
flush方法: 清空缓冲区,将缓冲区中的数据强制写出。
注意:是缓冲输出流中的方法
比较close方法和flush方法
区别:
close方法是在流关闭之前强制刷出缓存
flush方法是在write方法之后刷出缓存
public class CopyDemo02 {
public static void main(String[] args) throws IOException {
//创建流对象
BufferedInputStream bis = new
BufferedInputStream(
new FileInputStream(“33.jpg”));
BufferedOutputStream bos = new
BufferedOutputStream(new
FileOutputStream(“pic2.jpg”));
//完成复制
byte[] bys = new byte[1024];
int len = -1;
while((len=bis.read(bys))!=-1){
bos.write(bys, 0, len);
bos.flush();
}
// bis.close();
// bos.close();
//逻辑实现,又用到以上两个流
}
BIS:BufferedInputStream工作原理
在读取数据时若以字节为单位读取数据,会导致读取次数过于频繁从而大大的降低读取效率。
为此我们可以通过提高一次读取的字节数量减少读取次数来提高读取的效率。
BufferedInputStream是缓冲字节输入流。其内部维护着一个缓冲区(字节数组),使用该流在读取一个字节时,该流会尽可能多的一次性读取若干字节并存入缓冲区,然后逐一的将字节返
回,直到缓冲区中的数据被全部读取完毕,会再次读取若干字节从而反复。这样就减少了读取的次数,从而提高了读取效率。
字符流(文本数据IO流)
2.1 Reader和Writer
Reader:字符输入流的父类
Writer:字符输出流的父类。
字符流是以字符(char)为单位读写数据的。一次处理一个字符。
字符流的底层仍然是基本的字节流。
字符流封装了字符的编码解码算法。
字符流输出流写数据:
write(char) char —>byte 编码
字符流输入流读数据:
byte—>char 解码
2.2 转换流:
Writer和Reader的子类:
OutputStreamWriter
使用该流可以设置字符集,并按照指定的字符集将字符转换为对应字节后通过该流写出。
OutputStreamWriter(OutputStream out,String charsetName)
基于给定的字节输出流以及字符编码创建OSW
OutputStreamWriter(OutputStream out)
该构造方法会根据系统默认字符集创建OSW
方法:
write(int c)
write(char[])
write(String)
InputStreamReader:
使用该流可以设置字符集,并按照指定的字符集从流中按照该编码将字节数据转换为字符并读取。
构造方法:
InputStreamReader(InputStream in,String charsetName)
基于给定的字节输入流以及字符编码创建ISR
InputStreamReader(InputStream in)
该构造方法会根据系统默认字符集创建ISR
注意:使用字符流对象向文件中写入字符数据时,写入时采用的是什么字符集,读取时也要采用相同的字符集。
/**
- 使用字符流向文件中写入数据
- @author adminitartor
/
public class OswDemo {
public static void main(String[] args) throws IOException {
//创建输出流对象
OutputStreamWriter osw = new
OutputStreamWriter(
new FileOutputStream(“osw.txt”),“UTF-8”);
//写数据
//写入单个字符
// osw.write(‘a’);
// osw.write(“helloworld中国”.toCharArray());
osw.write(“helloworld中国人”);
//关闭流
osw.close();
}
}
/*
- 字符输入流读取数据
- @author adminitartor
/
public class IsrDemo {
public static void main(String[] args) throws IOException {
/
- 从文件中读取数据并打印到控制台
*/
//创建输入流对象
InputStreamReader isr = new
InputStreamReader(
new FileInputStream(“osw.txt”),“UTF-8”);
//读取
//按照单个字符读取
// int ch = -1;
// while((ch=isr.read())!=-1){
// System.out.print((char)ch);
// }
//按照字符数组读取
char[] chs = new char[1024];
int len = -1;
while((len=isr.read(chs))!=-1){
//char[]–>String
System.out.print(new String(chs, 0, len));
}
//释放资源
isr.close();
}
}
字符缓冲流
缓冲字符流:缓冲字符流由于内部有缓冲区,读写字符的效率高.并且字符流的特点是可以按行读写字符串.
BufferedWriter
BufferedReader
PrintWriter也是缓冲字符输出流,它内部总是连接BufferedWriter,除此之外PW还提供了自动行刷新功能.所以更常用.
2.3.1 PrintWriter:缓冲字符输出流
PrintWriter提供了直接对文件进行写操作的构造方法:
分为2类:
参数是目标文件:
PrintWriter(File/String)
PrintWriter(File/String,String csn)
参数是流对象:
PrintWriter(OutputStream)
PrintWriter(Writer)
PrintWriter(OutputStream,boolean autuflush)
PrintWriter(Writer,boolean autoflush)
方法:
• PrintWriter提供了丰富的重载print与println方法。其中println方法在于输出目标数据后自动输出一个系统支持的换行符。
特点:行自动刷新
当PrintWriter的构造方法第一个参数为流(字节流,字符流均可)时,那么支持一个重载的构造方法可以传入一个boolean值,该值若为true,则当前PrintWriter具有自动行刷新功能,即:每当调用println方法写出一行字符串后会自动调用flush方法将其数据写出. 需要注意,调用print方法是不
会flush的。
PrintWriter pw = new PrintWriter(new
FileOutputStream(“autoflush.txt”),true); //此时具有自动行刷新功能
pw.println(“hello”);
2.3.2 BufferedReader:缓冲字符输入流
BufferedReader是缓冲字符输入流,其内部提供了缓冲区,可以提高读取效率。
BufferedReader的常用构造方法:
– BufferedReader(Reader reader)
BufferedReader提供了读取一行字符串的方法:
String readLine()
该方法会连续读取若干字符,直到读取了换行符为止,然后将换行符之间读取到的所有字符以一个字符串形式返回.需要注意,返回的字符串中不包含最后的换行符.当该方法返回null时,表示末尾(不会再读取到任何数据)
练习:实现文件的复制
public class BufferCopyDemo {
public static void main(String[] args) throws IOException {
/*
- 使用字符缓冲流实现按行复制文件
*/
//创建输入,输出流对象
BufferedReader br = new BufferedReader(new
InputStreamReader(new
FileInputStream(“fis.java”)));
PrintWriter pw = new PrintWriter(
new FileOutputStream(“buffer.txt”),true);
//复制
String str = null;
while((str=br.readLine())!=null){
pw.println(str);
}
//流关闭
br.close();
pw.close();
}
}
3.对象流
如果要将对象用于网络传输或持久化到磁盘上,此时需要使用对象流来完成操作。这个过程中数据必须转换为字节序列才可以完成的。
读写对象的流:
ObjectOutputStream
构造方法:
ObjectOutputStream(OutputStream)
方法:
writeObject()
写对象数据:
对象数据-----> 字节序列 : 序列化
读对象数据:
字节序列—> 对象 : 反序列化
Java中要想序列化一个对象,前提:这个对象一定是可序列化
Serializable接口:
ObjectOutputStream在对对象进行序列化时有一个要求,就是需要序列化的对象所属的类必须实现Serializable接口。
实现该接口不需要重写任何方法。其只是作为可序列化的标志。
序列化 : 对象 -->字节序列
应用场景:1.将对象持久化到磁盘 2.将对象用于网络传输
将对象序列化后写到文件中的内容包括:
对象的类型,对象中的数据,其他的信息
ObjectInputStream:
构造方法
ObjectInputStream(InputStream)
方法
readObject():Object
/**
- 向文件中写入对象信息-序列化
/
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException, IOException
{
Student student = new Student(1, “tom”, 23);
//创建流对象
ObjectOutputStream oos = new
ObjectOutputStream(new
FileOutputStream(“oos.txt”));
//写对象数据
oos.writeObject(student);
//释放资源
oos.close();
}
}
public static void main(String[] args) throws FileNotFoundException,
IOException, ClassNotFoundException {
/
读取数据将其还原成对象-反序列化
/
ObjectInputStream ois = new
ObjectInputStream(new
FileInputStream(“oos.txt”));
//读取对象
Student stu = (Student)ois.readObject();
System.out.println(stu);
ois.close();
}
如果对一个类添加属性或减少属性,对已经写好的对象进行反序列化,出现什么问题?如何解决?
抛出异常。
解决办法:手动生成序列版本号。
SeriaVersionID:
通常实现该接口的类需要提供一个常量serialVersionUID,表明该类的版本,如果声明的类的对象序列化存到硬盘上面,之后随着需求的变化更改了类的属性(增加或减少或改名),那么当反序列化时,就会出现InvalidClassException,这样就会造成不兼容性的问题。
当 一 个 类 实 现 了 Serializable 接 口 后 , 编 译 器 会 提 示 应 当 添 加 一 个 常量:serialVersionUID,这个常量标识当前类的序列化版本号.若不指定,编译器在编译当前
类时会根据当前类的结构生成,但是只要类的结构发生了变化,那么版本号就会改变.版本号决定着一个对象是否可以反序列化成功.
当对象输入流在将一个对象进行反序列化时会检查该对象的版本号与当前程序中这个类的版本号是否一致?
若一致:反序列化成功,若当前类发生了改变,那么则还原依然存在的属性.
若不一致,则反序列化方法readObject会抛出异常.
注意点:
当一个对象是可序列化时,如果内部的成员是别的对象,那么,作为成员的其他对象一定也是可序列化的。
transient关键字:
当一个属性被transient修饰后,该属性在进行对象序列化时其值会被忽略.
忽略不必要的属性值可以达到对象序列化"瘦身"的效果.
Person类
public class Person implements Serializable {
/*
*
*/
private static final long serialVersionUID = 7020175212548538187L;
private int id;
private String name;
private int age;
private transient String desc; //描述
public Person() {
super();
// TODO Auto-generated constructor stub
}
public Person(int id, String name, int age, String desc) {
super();
this.id = id;
this.name = name;
this.age = age;
this.desc = desc;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return “Person [id=” + id + “, name=” + name + “, age=” + age + “,
desc=” + desc + “]”;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (id != other.id)
return false;
return true;
}
}
测试类
public class TransientDemo {
public static void main(String[] args) throws IOException, IOException,
ClassNotFoundException {
/*
- 将Person对象序列化到文件中
- 要实现将Person对象中的desc数据不写入文件
*/
Person person = new Person(1, “amy”, 23, “好人”);
ObjectOutputStream oos = new
ObjectOutputStream(new FileOutputStream(“transient.txt”));
oos.writeObject(person);
oos.close();
//反序列化操作
ObjectInputStream ois = new
ObjectInputStream(new
FileInputStream(“transient.txt”));
Person per = (Person)ois.readObject();
System.out.println(per);
ois.close();
}
}
RandomAccessFile文件操作
Java提供了一个可以对文件随机访问的操作,访问包括读和写操作。该类名为RandomAccessFile。该类的读写是基于指针的操作。
RandomAccessFile也叫做随机访问流。
创建对象
• RandomAccessFile在对文件进行随机访问操作时有两个模式,分别为只读模式(只读取文件数据),和读写模式(对文件数据进行读写)。
• 构造方法:
• RandomAccessFile(File file,String mode)
• RandomAccessFile(String filename,String mode)
mode值: r :读 rw :可读可写
读写操作:
写数据:
write(int b)
wirte(byte[] bys)
write(byte[] by,int offset,int len)
读数据:
read() – int
read(byte[] b) - int
close() :
RandomAccessFile在对文件访问的操作全部结束后,要调用close()方法来释放与其关联的所有系统资源。
文件指针操作
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
/*
- 用RandomAccessFile对象实现向文件中写入数据
*/
//创建对象
RandomAccessFile raf = new
RandomAccessFile(“raf.txt”, “rw”);
//写入数据
// raf.write(“随机访问流对象”.getBytes());
//读取数据
byte[] bys = new byte[1024];
int len = raf.read(bys);
System.out.println(new String(bys,0,len));
//释放资源
raf.close();
}
}
如何实现文件的复制?如何实现“随机”访问?
随机:通过此流对象可以实现定位到文件内容的任意位置。
getFilePointer()
seek(long pos)
举个栗子:
向a.txt文件中偏移量3的位置写入“hello”,文件中原来的数据顺序后移
abchellodef
先将3后面的内容读取并保存到临时文件中,之后在原文件的指定位置写入数据,再次读取临时文件中的内容写入当前文件的最后。
public class InsertDemo {
public static void main(String[] args) throws IOException {
//创建RandomAccessFile对象
RandomAccessFile raf = new
RandomAccessFile(“raf.txt”, “rw”);
//移动指针偏移量并读取偏移量之后的内容到临时文件中
raf.seek(3);
//创建临时文件
File tmp = File.createTempFile(“copy”, “.tmp”);
//复制
RandomAccessFile raftmp = new
RandomAccessFile(tmp, “rw”);
byte[] bys = new byte[1024];
int len = -1;
while((len=raf.read(bys))!=-1){
tmp.write(bys, 0, len);
}
raf.seek(3);
//向raf.txt文件中插入数据
raf.write(“hello”.getBytes());
//将临时文件中的内容复制到本文件的最后
tmp.seek(0);
len = -1;
while((len=tmp.read(bys))!=-1){
raf.write(bys, 0, len);
}
tmp.deleteOnExit();
raf.close();
raftmp.close();
}
}
临时文件的创建:
File.createTempFile(String prefix,String suffix) :File
临时文件的删除:
在程序执行结束时删除临时文件:
deleteOnExit()
public class TmpFileDemo {
public static void main(String[] args) throws IOException {
/*
- 创建临时文件
*/
File tmpFile = File.createTempFile(“test”, “.tmp”);
System.out.println(tmpFile.getAbsolutePath());
tmpFile.deleteOnExit();
}
}
异常处理
Java中的异常结构:
Throwable
(1)Error (2)Exception
内存溢出:
OutOfMemoryError
• Java异常结构中定义有Throwable类,Exception和Error是其派生的两个子类。其中Exception表示由于程序员代码书写出现的异常;而Error表示Java运行时环境出现的错误,例如:JVM内存资源耗尽等。
异常的分类:
Exception分类:
非检测异常
可检测异常:
IOException
FileNotFoundException
ParseException
可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则,不处理这个异常,编译器就通不过,不允许编译。
非检测异常:非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常。
• RuntimeException 类属于非检测异常,因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。
常见的RuntimeException:
• IllegalArgumentException
抛出的异常表明向方法传递了一个不合法或不正确的参数。
NullPointerException
当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
ArrayIndexOutOfBoundsException
当使用的数组下标超出数组允许范围时,抛出该异常。
ClassCastException
当试图将对象强制转换为不是实例的子类时,抛出该异常。
NumberFormatException
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
String str = “123abc” Integer.parseInt(str)抛出NumberFormatException异常。
异常处理:2种方式
方式一:
抛出异常
throws和throw
throws用于在方法声明的位置抛出异常类型
throw用于在方法体内部抛出异常对象
用法:
定义方法
public void parse(String str) throws ParseException{
……
//判断参数是否合法
如果不合法{
throw new ParseException();
}
}
定义一个方法时,方法体内部采用throw抛出异常对象(可以是可检测异常,也可以是非检测异常),如果方法体内部抛出的是可检测异常,那么在方法声明的位置必须throws异常类型,如果方法体内部抛出的是非检
测异常,那么此时方法声明位置不需要抛出异常类型。
注意点:
- 异常处理采用抛出异常方式,当程序执行到异常后,程序执行结束,后面的逻辑不继续执行。
- 在重写中抛出异常的规则
- 可以在子类重写的方法声明位置不抛出异常
- 可以抛出和父类一样的异常
- 可以抛出父类异常的子类异常,不可以抛出父类异常的父类异常
- 可以抛出父类异常的一部分,但是不能抛出父类异常中没有的异常
方式二:
try…catch捕获异常:
java异常捕获机制
try{
… 可能出现异常的代码片段
}catch(XXXXException e){
… 当try中代码出错后在这里捕获并处理
}
注意点: - 捕获的异常可以是可检测异常,也可以是非检测异常
- 捕获异常之后,try…catch后续代码会继续执行
catch可以捕获多个异常,当针对不同的异常有不同的处理手段时,可以分别去捕获它们,写法:
try{
可能出现异常的代码
}catch(异常A e){
针对异常A的处理
}catch(异常B e){
针对异常B的处理
}
catch捕获多个异常的分类: - 当捕获的多个异常具有继承关系时,如果用多个catch捕获,一定是子类异常在前,父类异常在后;也可以直接捕获父类异常,不捕获子类异常
- 当捕获的多个异常不具有任何关系时,可以用多个catch分别捕获;也可以用一个catch捕获多个异常,写法:
a) catch(异常A|异常B|异常C e){}
通常和try…catch配合使用的关键字:finally
写法:
try{
}cath(捕获的异常){、
}catch…{
}finally{
用于释放资源
}
public void test(){
try{
创建流对象 – 流资源打开
读写操作 – 可能出现异常
}catch(….){
处理
}finally{
释放资源的代码
释放资源
}
finally块特点:不论以上代码是否出现异常,finally中的代码都会被执行。
其他逻辑代码
}
finally块:
finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序其它部分以前,能够对程序的状态作统一管理。
无论try所指定的程序块中是否抛出异常,finally所指定的代码都要被执行
通常在finally语句中可以进行资源的释放工作,如关闭打开的文件、删除临时文件等。
3.1 final,finally,finalize的区别
final:修饰符
可以修饰类,方法,变量
finally块和try…catch配合使用,用于做最后的资源释放。
finalize是一个方法,是在GC回收对象之前调用的。
作用:回收对象之前做最后的资源释放
是Object类中的方法
System.gc()
自定义异常
自定义异常通常用于定义业务异常
如何定义自定义异常类:
业务异常都是运行时异常(RuntimeException)
步骤: - 让自定义异常类继承RuntimeException/Exception
- 在自定义异常类中生成构造方法