垃圾回收器(Garbage Collection,简称GC)是JVM自带的一个线程(自动运行着的程序),用于回收没有任何引用指向的对象。
Java程序员不用担心内存管理,因为垃圾回收器会自动进行内存回首。
Java程序的内存泄漏问题:
System.gc()方法:
package MyTest;
public class Test{
int a;
public static void main(String[] args) {
Test t = new Test();
t.show(2);
}
public void show(int b){
int c;
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
}
//error:Variable 'c' might not have been initialized
分析:一个变量在使用之前必须提前声明并且初始化,对于上面中的实例变量a,会自动初始化为0,所以不用担心;对于变量b,在调用相关函数show时,一定会传入一个参数用于变量b的初始化,所以也不用担心;但是对于变量c来说,仅仅对其进行了声明,并未对其初始化,所以会报错!
局部变量的生命周期:
成员变量与局部变量:
功能 | Eclipse | Idea |
---|---|---|
单步调试,会进入到方法中。 | F5 | F7 |
单步调试,不会进入到方法中。 | F6 | F8 |
结束方法的调试,返回。 | F7 | shift+F8 |
直接跳到下一个断点,若无断点则调试结束。 | F8 | shift+F8 |
Variables窗口:用于观看相关变量的值。
Watchers窗口:用于添加相关的逻辑表达式,用于判断真假。
什么是JDK API?
JDK包结构:
为了便于使用和维护,JDK类库按照包结构划分,不同功能的类划分在不同的包中。
经常使用的包有:
包 | 功能 |
---|---|
java.lang | Java程序的基础类,如字符串、多线程等,该包中的类的使用频率非常的高,不需要import,可以直接使用。 |
java.util | 常用工具类,如集合、随机数产生器、日历、时钟等 |
java.io | 文件操作、输入/输出操作 |
java.net | 网络操作 |
java.math | 数学运算相关操作 |
java.security | 安全相关操作 |
java.sql | 数据库访问 |
java.net | 处理文字、日期、数字、信息的格式 |
文档注释主要只在三个地方出现:类上面、方法上面、变量上面。
/**
* 这是用于测试功能的一个测试用例! //类功能说明
* @author YiWen Wan //作者
* @version 1.204,06/09/06 //版本
* @see java.lang.StringBuffer //参见
* @since JDK 1.0 //始于JDK版本
* ……
*/
package MyTest;
/**
* 这是用于测试功能的一个测试用例! //类功能说明
* @author YiWen Wan //作者
* @version 1.204,06/09/06 //版本
* @see java.lang.StringBuffer //参见
* @since JDK 1.0 //始于JDK版本
*/
public class Test {
/**
* 用于输出提示性话语的一个String变量!
*/
public static String string = "Hello World!";
/**
* 主函数
* @param args
*/
public static void main(String[] args) {
int a = 7;
int b = 8;
System.out.println(string);
Plus(a,b);
}
/**
* 用于计算两数加法运算,并限制其输出的一个函数。
* @param num1:参与加法运算的其中一个参数
* @param num2:参与加法运算的另一参数
*/
public static void Plus(int num1,int num2){
int num=-1;
num = num1 + num2;
if (num>=10){
System.out.println(num);
}
}
}
下一步
-encoding utf-8 -charset utf-8
-encoding UTF-8 -charset UTF-8 -windowtitle "你的文档在浏览器窗口标题栏显示的内容" -link http://docs.Oracle.com/javase/7/docs/api
两者的区别就在于,有没有和java官方的文档“联动起来”。
当你点击java语言中已存在的类时,他会跳转到官方文档!
- java.lang.String使用了final进行修饰,所以不能被继承。
- 字符串底层封装了字符数组以及针对字符数组的操作算法。
- 字符串一旦创建,对象就永运无法改变,但是字符串引用可以重新赋值。
- Java字符串在内存中采用了Unicode编码方式,任何一个字符对应着两个字节的定长编码。
/**
* String是不变对象,JVM对其做了一个优化,在内存中开辟了一段区域作为常量池,
* 凡是通过“字面量”(直接量)形式创建的字符串对象都会缓存并且重用。因为会重用
* 对象,所以该对象内容不可以改变!
* @author YiWen Wan
*/
public class StringDemo {
public static void main(String[] args) {
String str1 = "Hello World!";
String str2 = "Hello World!";
System.out.println("Java重用了这一个字符串直接量!true or false?");
//直接比较引用是否相等,来进一步说明是否指向了同一片内存区域!
System.out.println(str1==str2);
}
}
//Java重用了这一个字符串直接量!true or false?
//true
/**
* 一个有趣的现象!
* 因为对象的内容是不可变的,所以修改原指向的值本质上是重新创建了一个新的字符串,
* 原字符串还保留着,防止引起原同一引用的值发生改变!
* @author YiWen Wan
*/
public class StringDemo {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
//原本str1和str2是指向同一块内存区域的,现在改变str1的值!
str1 = str1+"!";
System.out.println(str1);
System.out.println(str2);
}
}
//Hello!
//Hello
/**
* 使用new创建新的String对象时,无论之前是否有一样内容的String对象,
* 都会强制创建一个新的对象!
* @author YiWen Wan
*/
public class StringDemo {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println("分割线:--------------");
System.out.println(str1.equals(str3));
}
}
//true
//false
//true
所以当比较两个String类型的对象是否相等时,利用equal函数进行判断更为准确,因为不是指向同一对象的String引用也可能内容相等!
对String对象进行拼接之后产生的新对象的特性:
/**
* 测试分割重组的String对象的一些特性!
* @author YiWen Wan
*/
public class StringDemo {
public static void main(String[] args) {
String str1 = "HelloWorld";
/*这里是一个编译器的一个优化措施,编译器在编译源代码时,发现计算表达式的所有参数都是“字面量”,在编译之后就直接会将这些“字面量”直接计算了,并将结果编译到class文件中,所以在执行该条语句的时候相当于:
String str2 = "HelloWorld";
所以后续输出才会是true,这已经不是String的特性了!
*/
String str2 = "Hello" + "World";
System.out.println(str1==str2);
//计算表达式有一方是变量时,就一定会创建一个新的对象,所以才会输出false!
String s = "Hello";
String str3 = s + "World";
System.out.println(str1==str3);
}
}
//true
//false
/**
* 用于测试String中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "a";
for (int i=0;i<100000000;i++)
str = str + "a";
System.out.println("Game Over!");
}
}
//结果出不来,一直卡着!
所以Java中String是不变对象,目的就是提高了重用性,但是对于频繁修改String对象就考虑的不是很完备,“一刀切”思想!
StringBuilder类:
/**
* 用于测试StringBuilder中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "好好学习 ";
//当使用无参数的构造方法时,默认生成的是一个空字符串,可以用来以后加东西!
StringBuilder sb = new StringBuilder();
StringBuilder stringBuilder = new StringBuilder(str);
//①增——append()函数,拼接字符串!
System.out.println(stringBuilder.append("天天向上!").toString());
//②改——replace()函数,用于替代指定范围内的字符串内容!表示范围时注意含头不含尾!
System.out.println(stringBuilder.replace(8,9,"后").toString());
//③删——delete()函数,用于删除指定位置的字符串内容!表示范围时注意含头不含尾!
System.out.println(stringBuilder.delete(0,5).toString());
//④插——insert()函数,用于将指定字符串插入到原字符串指定的范围内!
System.out.println(stringBuilder.insert(2,"向上、向下、向左、向右、向前、"));
System.out.println(stringBuilder.reverse());//反转
}
}
// 好好学习 天天向上!
// 好好学习 天天向后!
// 天天向后!
// 天天向上、向下、向左、向右、向前、向后!
/**
* 用于测试StringBuilder中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder("a");
for (int i=0;i<10000000;i++)
stringBuilder.append("a");
System.out.println("Game Over!");
}
}
// Game Over!
Java中字符串的连接过程就是利用StringBuilder实现的。
StringBuilder与StringBuffer:
/**
* 用于测试String中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "HelloWorld!";
System.out.println("Length:"+str.length());
System.out.println("位置是:"+str.indexOf("Hello"));
System.out.println("位置是:"+str.indexOf("WYW"));
//验证一下indexof的其他重载函数!
System.out.println("位置是:"+str.indexOf("o"));
//从指定位置开始查找:
System.out.println("位置是:"+str.indexOf("o",5));
//查找最后一次出现的位置:
System.out.println("位置是:"+str.lastIndexOf("o"));
}
}
// Length:11
// 位置是:0
// 位置是:-1
// 位置是:4
// 位置是:6
// 位置是:6
/**
* 用于测试String中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "HelloWorld";
System.out.println("截取的字符串为:"+str.substring(0,5));
//当参数变成一个的时候,就变成了一刀切了!
System.out.println("截取的字符串为:"+str.substring(5));
}
}
//截取的字符串为:Hello
//截取的字符串为:World
/**
* 用于测试String中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = " HelloWorld! ";
System.out.println("去掉之前的效果是:"+str);
System.out.println("去掉之后的效果是:"+str.trim());
}
}
// 去掉之前的效果是: HelloWorld!
// 去掉之后的效果是:HelloWorld!
/**
* 用于测试String中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "HelloWorld";
System.out.println("第6个字符是:"+str.charAt(5));
}
}
//第6个字符是:W
/**
* 用于测试String中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "HelloWorld";
if (str.startsWith("Hello"))
System.out.println("该字符串是以Hello开始的!");
if (str.endsWith("World"))
System.out.println("该字符串是以World结尾的!");
}
}
//该字符串是以Hello开始的!
//该字符串是以World结尾的!
/**
* 用于测试String中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "HelloWorld";
System.out.println(str.toUpperCase());
System.out.println(str.toLowerCase());
}
}
//HELLOWORLD
//helloworld
/**
* 用于测试String中函数特性!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
int a = 10;
double b = 3.14;
char c = 'c';
float d = 3.145f;
System.out.println(String.valueOf(a));
System.out.println(String.valueOf(b));
System.out.println(String .valueOf(c));
System.out.println(String.valueOf(d));
//更加简单的方法,但是效率不高
String str = 10+"";
System.out.println("更加简单的方法:"+str);
//一个小测试
System.out.print("测试结果为:");
System.out.println(1+2);
System.out.print("测试结果为:");
System.out.println(1+"2");
}
}
// 10
// 3.14
// c
// 3.145
// 更加简单的方法:10
// 测试结果为:3
// 测试结果为:12
在实际开发中,经常需要对字符串数据进行一些复杂的匹配、查找、替换等操作。通过正则表达式可以方便的实现字符串的复杂操作。
正则表达式是一串特定字符,组成的一个“规则字符串”,这个“规则字符串”是描绘文本规则的工具。正则表达式就是记录文本规则的代码。
字符集合:
正则表达式 | 说明 |
---|---|
[abc] | a、b、c中的任意一个字符 |
[^abc] | 除了a、b、c的任意一个字符 |
[a-z] | a、b、c、……、z中的任意一个字符 |
[a-zA-Z0-9] | a ~ z、A ~ Z、0 ~ 9中的任意一个字符 |
[a-z &&[ ^ bc]] | a ~ z中除了b和c以外的任意一个字符,其中&&表示“与”的关系 |
正则表达式 | 说明 |
---|---|
· | 任意一个字符 |
\d | 任意一个数字字符,相当于[0-9] |
\w | 单词字符,相当于[a-zA-Z0-9] |
\s | 空白字符,相当于[\t\n\x0B\f\r] |
\D | 非数字字符 |
\W | 非单词字符 |
\S | 非空白字符 |
正则表达式 | 说明 |
---|---|
X? | 表示0个或1个X |
X* | 表示0个或任意个X |
X+ | 表示1个到任意个X |
X{n} | 表示n个X |
X{n,} | 表示n个到任意个X |
X{n,m} | 表示n个到m个X |
分组“()”:
" ^ “和” $ ":
判断当前字符串是否符合提供的正则表达式!matches方法指定的正则表达式就算不指定边界匹配符,也是做全匹配验证的!
boolean matches(String regex)
/**
* 用于验证Java中正则表达式的使用!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String email = "[email protected]";//书写输入数据
/*
原本的正则表达式是:\w+@\w+(\.[a-zA-z]+)+但是因为Java中表示转义字符也是使用‘\’来表示,但是式子中的‘\’并非表示Java中的转义;
所以应该让Java看来是一个单纯的‘\’,所以在‘\’前面再加上一个‘\’用来表示!
*/
String regex = "\\w+@\\w+(\\.[a-zA-z]+)+";//书写正则表达式
//matches函数用于验证该字符串是否属于正则表达式!
boolean b = email.matches(regex);
if (b)
System.out.println("这是一个邮箱地址!");
else
System.out.println("这不是一个邮箱地址!");
}
}
//这是一个邮箱地址!
/**
* 用于验证Java中正则表达式的使用!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "WYW:007Hello123The456World78765867!";
//正则表达式(以数字进行分割):[0-9]+或者\d+
String regex = "\\d+";
String[] array = str.split(regex);
System.out.println(Arrays.toString(array));
}
}
//[WYW:, Hello, The, World, !]
/**
* 用于验证Java中正则表达式的使用!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "WYW:007Hello123The456World78765867";
String regex = "\\d";//此时遇到一次数字拆分一次!
String[] array = str.split(regex);
System.out.println(Arrays.toString(array));
}
}
//[WYW:, , , Hello, , , The, , , World]
/**
* 用于验证Java中正则表达式的使用!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String regex = "\\d";//此时遇到一次数字拆分一次!
String str = "534534WYW007Hello123The456World7876";
String[] array = str.split(regex);
System.out.println(Arrays.toString(array));
}
}
//[, , , , , , WYW, , , Hello, , , The, , , World]
/**
* 用于验证Java中正则表达式的使用!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
String str = "abc123def456ghi789jkl000mno";
String regex = "[0-9]+";
System.out.println("之前的模样:"+str);
System.out.println("现在的模样:"+str.replaceAll(regex,"¥"));
}
}
// 之前的模样:abc123def456ghi789jkl000mno
// 现在的模样:abc¥def¥ghi¥jkl¥mno
个人体会:这些知识只是对于正则表达式的基础知识,在了解这些基础知识之后,要是构建更加复杂的正则表达式,你可网上查询、或者借助一些工具进行构建;没有必要花太多的时间深度学习!
介绍一个我常用的工具正则表达式在线测试 | 菜鸟工具 (runoob.com)
有时候写稿子需要批量修改某个词,我也用它写正则表达式来批量替换,挺好用的!
Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。
Object 中比较常用的两个方法就是 toString方法和equal方法。
Java API提供的类,都对于toString方法和equal方法进行了妥善的重写,所以不用担心,直接使用就好了!(不信你看下面包装类Integer里面对于equal函数是直接使用的)所以只有你直接写的类才需要进行重写!
/**
* 测试object中的相关方法!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
Point p = new Point(1,2);
System.out.println(p.toString());
//当toString函数没有被重写时,输出:string.Point@568db2f2
//@后面的这部分是表示句柄,即该对象在堆中的地址!
// public String toString() {
// return getClass().getName() + "@" + Integer.toHexString(hashCode());
// }
System.out.println(p);
//toString函数我们一般不会直接使用它,但是有一些函数会在间接使用它,
//譬如println函数!
}
}
//输出:
//Point{x=1, y=2}
//Point{x=1, y=2}
public class Point {
private int x;
private int y;
//构造函数
public Point(int x,int y) {
this.x = x;
this.y = y;
}
//重写toString函数
@Override
public String toString() {
return "Point{" +"x=" + x + ", y=" + y + '}';
}
}
/**
* 测试object中的equal方法!
* @author YiWen Wan
*/
public class StringTest {
public static void main(String[] args) {
Point point = new Point(1,2);
Point point1 = new Point(1,2);
System.out.println(point == point1);
//equal函数要是不重写,就和上面的语句一样了,只会比较其引用值是否相等!
// public boolean equals(Object obj) {
// return (this == obj);
// }
System.out.println(point.equals(point1));
}
}
//输出:
//false
//true
public class Point {
private int x;
private int y;
//构造函数
public Point(int x,int y) {
this.x = x;
this.y = y;
}
//重写equal函数
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
}
包装类的出现是为了解决基本数据类型无法参与面向对象开发问题!
基本类型 | 包装类 | 父类 |
---|---|---|
int | Integer | java.lang.Number |
long | Long | java.lang.Number |
double | Double | java.lang.Number |
short | Short | java.lang.Number |
float | Float | java.lang.Number |
byte | Byte | java.lang.Number |
char | Character | java.lang.Object |
boolean | Boolean | java.lang.Object |
/**
* 这是用于测试功能的一个测试用例!
* @author YiWen Wan
*/
public class Test {
public static void main(String[] args) {
int data = 1;
Integer i1 = new Integer(data);
Integer i2 = new Integer(data);
System.out.println(i1==i2);
System.out.println(i1.equals(i2));
}
}
//output:
//false
//true
/**
* 这是用于测试功能的一个测试用例!
* @author YiWen Wan
*/
public class Test {
public static void main(String[] args) {
int data = 1;
Integer i1 = Integer.valueOf(data);
Integer i2 = Integer.valueOf(data);
System.out.println(i1==i2);
System.out.println(i1.equals(i2));
}
}
//output:
//true
//true
补充:
但是这种重用是有限制的,当数据的取值超过一定的范围时,就会新建一个对象。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
不是所有的包装类的valueof都有这种对象重用的优化,譬如Double包装类就没有!
public static Double valueOf(double d) {
return new Double(d);
}
Java还是推荐使用valueof方法来转化包装类,用得好可以重用优化,再不济和新建一个对象一样,怎么看都是稳赚不赔的买卖。
/**
* 这是用于测试功能的一个测试用例!
* @author YiWen Wan
*/
public class Test {
public static void main(String[] args) {
Integer data = Integer.valueOf(128);
int in = data.intValue();
//或者转换为其他的类型也可以,注意由小变大没问题,由大变小可能会溢出!
double dou = data.doubleValue();
float flo = data.floatValue();
byte by = data.byteValue();//溢出
System.out.println(by);
}
}
//output:
//-128
介绍两个常见的两个属性 Max_Value 和 Min_Value 用于表示该包装类对应的基本类型的取值范围。
/**
* 这是用于测试功能的一个测试用例!
* @author YiWen Wan
*/
public class Test {
public static void main(String[] args) {
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
System.out.println(Long.MAX_VALUE);
System.out.println(Long.MIN_VALUE);
System.out.println(Double.MAX_VALUE);
System.out.println(Double.MIN_VALUE);
}
}
//output:
// 2147483647
//-2147483648
//9223372036854775807
//-9223372036854775808
//1.7976931348623157E308
//4.9E-324
包装类都提供了一个静态方法parseXXX(String str)可以将字符串解析为对应的基本类型数据,但是该字符串必须能正确的描述出对应基本类型,否则将会抛出异常!
/**
* 这是用于测试功能的一个测试用例!
* @author YiWen Wan
*/
public class Test {
public static void main(String[] args) {
String string = "111.1";
int data = Integer.parseInt(string);
//类型不匹配时就会报错,抛出NumberFormatException异常!
System.out.println(data);
double data1 = Double.parseDouble(string);
System.out.println(data1);
}
}
JDK 1.5版本推出时推出了一个特性——自动拆装箱,该特性是编译器认可的(不是JVM),当我们在基本类型和其对应的引用类型之间相互赋值时,编译器会自动补全代码,在两者之间转换!
/**
* 这是用于测试功能的一个测试用例!
* @author YiWen Wan
*/
public class Test {
public static void main(String[] args) {
int data = new Integer(12);
// 自动补全为:int data1 = new Integer(12).intValue();
Integer integer = 4;
// 自动补全为:Integer integer1 = Integer.valueOf(4);
}
}
使用File可以:
访问其表示的文件或者目录的属性信息(名字,大小,访问权限等)。
操作文件或者目录(创建、删除)。
访问目录子项,但是不能访问文件数据。
import java.io.File;
public class TestForFile {
public static void main(String[] args) {
File file = new File("./demo.txt");
System.out.println("文件名:"+file.getName());//文件名
System.out.println("文件大小:"+file.length()+"bytes");//单位字节
System.out.println("是否可写:"+file.canWrite());//可写
System.out.println("是否可读:"+file.canRead());//可读
System.out.println("是否隐藏:"+file.isHidden());
}
}
//output:
//文件名:demo.txt
//文件大小:67bytes
//是否可写:true
//是否可读:true
//是否隐藏:false
创建文件:
createNewFile()
import java.io.File;
import java.io.IOException;
public class TestForFile {
public static void main(String[] args) throws IOException {
//使用File创建文件
File file = new File("./demo.txt");
System.out.println("原来是否已经存在该文件:"+file.exists());
if (!file.exists()){//如果不存在,则创建一个新的文件
file.createNewFile();
System.out.println("文件已经创建!");
}else{
System.out.println("文件已经存在!");
}
}
}
//output:
//原来是否已经存在该文件:true
//文件已经存在!
删除文件:
delete()
import java.io.File;
public class TestForFile {
public static void main(String[] args){
//使用File创建文件
File file = new File("./demo.txt");
System.out.println("是否存在该文件:"+file.exists());
if (file.exists()){
file.delete();
System.out.println("文件已经删除!");
}else{
System.out.println("文件不存在!");
}
System.out.println("是否存在该文件:"+file.exists());
}
}
//output:
//是否存在该文件:true
//文件已经删除!
//是否存在该文件:false
创建目录:
mkdir()
mkdir函数创建目录的前提就是该目录上级目录存在,不存在的时候就要使用后面的 mkdirs 创建多级目录了.
import java.io.File;
public class TestForFile {
public static void main(String[] args) {
//使用File创建目录
File dir = new File("./demo");
if (!dir.exists()) {//目录不存在
dir.mkdir();//创建目录
System.out.println("目录已创建!");
} else {
System.out.println("目录已存在!");
}
}
}
//output:
//目录已创建!
创建多级目录:
mkdirs()
import java.io.File;
public class TestForFile {
public static void main(String[] args){
//创建一个多级目录
File dir = new File("./A/B/test");
if (!dir.exists()){//当目录不存在时
dir.mkdirs();//创建多级目录时,使用mkdirs
System.out.println("目录创建完毕!");
}else{
System.out.println("目录已经存在!");
}
}
}
//output:
//目录已创建!
删除目录:
delete()
delete方法删除目录的前提条件就是待删除目录下没有东西(空目录);所以只能逐级删除各个目录。
import java.io.File;
public class TestForFile {
public static void main(String[] args){
//删除当前目录下A目录下的B目录
File dir = new File("./A/B");
if (dir.exists())//当前目录存在
{
dir.delete();
System.out.println("目录删除成功!");
}else{
System.out.println("当前目录不存在!");
}
}
}
//output:
//目录删除成功!
获取目录下的所有子项:
File[] listFiles()
import java.io.File;
public class TestForFile {
public static void main(String[] args){
//获取当前目录下的所有子项
File dir = new File(".");//表示当前目录
if (dir.isDirectory()){//判断是否是一个目录
File[] subs = dir.listFiles();//获取当前目录下的所有子项
if (subs != null) {
for (File file:subs
) {
System.out.println(file.getName());
}
}else{
System.out.println("当前目录下为空!");
}
}
}
}
//output:
//.idea
//A
//out
//src
//Test.iml
import java.io.File;
public class TestForFile {
public static void main(String[] args){
//构造一个Delete方法,使之可以删除文件或目录。
File file = new File("./A");
Delete(file);
}
//使用了递归的思想!
public static void Delete(File file){
if (file.isDirectory()){//表示是一个目录,所以需要清空里面的东西
File[] subs = file.listFiles();
for (int i=0;i<subs.length;i++){
File sub = subs[i];
Delete(sub);
}
}
file.delete();
}
}
ListFiles提供了一个重载的方法,可以指定一个文件过滤器(FileFilter),然后将满足所给过滤器要求的子项返回。
import java.io.File;
import java.io.FileFilter;
import java.util.Objects;
public class TestForFile {
public static void main(String[] args) {
File file = new File(".");//绝对路径
MyFilter filter = new MyFilter();
File[] subs = file.listFiles(filter);
for (int i = 0; i< Objects.requireNonNull(subs).length; i++)
System.out.println(subs[i]);
}
}
/**
* 判断文件名称是否是以 . 开始。
*/
class MyFilter implements FileFilter {
public boolean accept(File pathname) {
//该函数每次都会执行一次判断文件是否符合要求!
String name = pathname.getName();
System.out.println("正在过滤:"+name);
return name.startsWith(".");
}
}
//output:
//正在过滤:.idea
//正在过滤:out
//正在过滤:src
//正在过滤:Test.iml
//.\.idea
import java.io.File;
import java.io.FileFilter;
import java.util.Objects;
public class TestForFile {
public static void main(String[] args) {
File file = new File(".");//绝对路径
FileFilter filter = new FileFilter(){
@Override
public boolean accept(File pathname) {
return pathname.getName().startsWith(".");
}
};
File[] subs = file.listFiles(filter);
for (int i = 0; i< Objects.requireNonNull(subs).length; i++)
System.out.println(subs[i]);
}
}
//output:
// .\.idea
import java.io.File;
import java.io.FileFilter;
import java.util.Objects;
public class TestForFile {
public static void main(String[] args) {
File file = new File(".");//绝对路径
File[] subs = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().startsWith(".");
}
});
for (int i = 0; i< Objects.requireNonNull(subs).length; i++)
System.out.println(subs[i]);
}
}
//output:
// .\.idea
import java.io.File;
import java.io.FileFilter;
import java.util.Objects;
public class TestForFile {
public static void main(String[] args) {
File file = new File(".");//绝对路径
File[] subs = file.listFiles((pathname -> pathname.getName().startsWith(".")));
for (int i = 0; i< Objects.requireNonNull(subs).length; i++)
System.out.println(subs[i]);
}
}
//output:
// .\.idea
RandomAccessFile是专门用来读写文件数据的API。其基于指针对文件数据进行读写操作,可以灵活的编辑文件数据内容。
创建RandomAccessFile时可以指定对于该文件的权限,常见的有两种:①r-只读模式、②rw-读写模式。
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestForFile {
public static void main(String[] args) throws IOException {
//RandomAccessFile被赋予写权限时会检查该文件是否存在,不存在时将会创建一个,第二个参数表示了其权限!
RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt","rw");
//相对路径中的"./"是可以不写的,因为在默认情况下就是从当前目录下开始的!
randomAccessFile.write(66);
//向文件中写入一个字节,写的是给定int值对应二进制的低八位。
randomAccessFile.write('1');
System.out.println("写入完毕!");
randomAccessFile.close();//这是一定要写的!
}
}
//output:
//写入完毕!
int read()
/*
* int read()函数解释:
* 每次读取一个字节的数据,以int形式返回,所以接收到的数据的低八位有效,
* 返回值为-1时表示已经读到了文件的末尾!
*/
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestForFile {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt","r");
int data = -1;
do {
data = randomAccessFile.read();
System.out.println(data);
}while (data!=-1);
randomAccessFile.close();
}
}
//output:
//66
//49
//-1
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestForFile {
public static void main(String[] args) throws IOException {
//作为源文件
RandomAccessFile r1 = new RandomAccessFile("MyPicture.jpg","r");
//作为目的文件
RandomAccessFile r2 = new RandomAccessFile("MyPicture_copy.jpg","rw");
int data = -1;
while ((data = r1.read())!=-1){
r2.write(data);
}
System.out.println("over!");
r1.close();
r2.close();
}
}
//output:
// over!
如果希望提高读写效率,可以通过提高每一次实际读写的数据量,减少实际发生的读写操作来做到。(空间换时间)
随机读写——单字节读写。
块读写——一组字节读写。
RandomAccessFile提供的块读写操作的方法:
int read(byte[] data)
void write(byte[] data)
void write(byte[] data,int start,int len)
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestForFile {
public static void main(String[] args) throws IOException {
//作为源文件
RandomAccessFile r1 = new RandomAccessFile("Blueming.mkv","r");
//作为目的文件
RandomAccessFile r2 = new RandomAccessFile("Blueming_copy.mkv","rw");
//记录每次实际读取到的字节量
int len = -1;
//每次要去读取的字节量
byte[] data = new byte[1024*10];//10kb
while ((len = r1.read(data))!=-1){
r2.write(data,0,len);//文件大小不一定是10kb的整数倍
}
System.out.println("game over!");
r1.close();
r2.close();
}
}
//output:
// game over!
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
public class TestForFile {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt","rw");
String string = "Hello World!";
byte[] data = string.getBytes(StandardCharsets.UTF_8);
//将字符串转换为二进制,建议指定字符集,可移植性更好!
randomAccessFile.write(data);
System.out.println("写入完毕!");
randomAccessFile.close();
}
}
//output:
//写入完毕!
关键就在于使用getBytes方法将字符串转换为二进制形式的数据。
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
public class TestForFile {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt","r");
byte[] data = new byte[(int) randomAccessFile.length()];
randomAccessFile.read(data);//一步读取
String string = new String(data,StandardCharsets.UTF_8);
//利用String的构造函数转换为字符串,最好指定字符集(和写入保持一致),防止乱码
System.out.println(string);
randomAccessFile.close();
}
}
//output:
//Hello World!
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestForFile {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt","rw");
int max = Integer.MAX_VALUE;
//因为一次传一个字节,所以通过位运算将int数据拆解送过去!
randomAccessFile.write(max>>>24);
randomAccessFile.write(max>>>16);
randomAccessFile.write(max>>>8);
randomAccessFile.write(max);
System.out.println("写入完毕!");
randomAccessFile.close();
}
}
//output:
//写入完毕!
public final void writeInt(int v) throws IOException {
write((v >>> 24) & 0xFF);
write((v >>> 16) & 0xFF);
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
//written += 4;
}
其他基本类型数据也提供了对应的方法,writeLong、writeFloat……
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestForFile {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt","rw");
randomAccessFile.writeInt(Integer.MAX_VALUE);
randomAccessFile.writeLong(123L);
randomAccessFile.writeDouble(123.123);
System.out.println("写入完毕!");
int data = randomAccessFile.read();
System.out.println(data);
randomAccessFile.close();
}
}
//output:
//写入完毕!
//-1
按照前面的原理分析,我们可以预测输出的值,应当为127,但是结果却是-1?这是因为RandomAccessFile是基于指针进行操作的。当我们完成写数据操作时,指针已经位于文件的末尾,所以最终才会返回-1。
public long getFilePointer()
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestForFile {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt","rw");
System.out.println("指针位于:"+randomAccessFile.getFilePointer());
randomAccessFile.writeInt(Integer.MAX_VALUE);
System.out.println("指针位于:"+randomAccessFile.getFilePointer());
randomAccessFile.writeLong(123L);
System.out.println("指针位于:"+randomAccessFile.getFilePointer());
randomAccessFile.writeDouble(123.123);
System.out.println("指针位于:"+randomAccessFile.getFilePointer());
System.out.println("写入完毕!");
int data = randomAccessFile.read();
System.out.println(data);
randomAccessFile.close();
}
}
//output:
//指针位于:0
//指针位于:4
//指针位于:12
//指针位于:20
//写入完毕!
//-1
public void seek(long pos)//移动指针到指定位置
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestForFile {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt","rw");
randomAccessFile.writeInt(Integer.MAX_VALUE);
randomAccessFile.writeLong(123L);
randomAccessFile.writeDouble(123.123);
System.out.println("写入完毕!");
randomAccessFile.seek(0);//移动指针位置到文件开始的位置
System.out.println(randomAccessFile.readInt());
System.out.println(randomAccessFile.readLong());
System.out.println(randomAccessFile.readDouble());
randomAccessFile.close();
}
}
//output:
//写入完毕!
//2147483647
//123
//123.123
按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。
节点流与处理流:
IS和OS常用方法:
InputStream 是所有字节输入流的父类,其定义了基础的读取方法,常用的方法如下:
int read()
int read(byte[] d)
文件流是一对低级流,用于读写文件;就功能而言与RandomAccessFile一致,但是底层的读写方式有着本质区别。
文件输出流:
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException {
//使用文件流向文件中写入数据
FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
String string = "你好吗!我很好!";
fileOutputStream.write(string.getBytes(StandardCharsets.UTF_8));
System.out.println("写入完毕!");
fileOutputStream.close();
}
}
//output:
//写入完毕!
FileOutputStream常用构造方法:
FileOutputStream(String path)
FileOutputStream(File file)
另外两种构造方法:
FileOutputStream(String path,boolean append)
FileOutputStream(File file,boolean append)
文件输入流:
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("test.txt");
byte[] data = new byte[200];
int len = fileInputStream.read(data);
System.out.println("实际读取了"+len+"个字节!");
String str = new String(data,0,len,StandardCharsets.UTF_8);
System.out.println(str);
fileInputStream.close();
}
}
//output:
//实际读取了48个字节!
//你好吗!我很好!你好吗!我很好!
使用起来和RandomAccessFile类似的!
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("Travel.mp3");
FileOutputStream fileOutputStream = new FileOutputStream("Travel_copy.mp3");
int len = -1;
byte[] data = new byte[1024*10];//块读写一次10kb
while((len=fileInputStream.read(data))!=-1){
fileOutputStream.write(data,0,len);
}
System.out.println("复制完毕!");
fileInputStream.close();
fileOutputStream.close();
}
}
//output:
//复制完毕!
使用起来和RandomAccessFile类似的!
缓冲流是一对高级流,功能是提高读写效率;链接之后,无论我们进行随机读写还是块读写,当经过缓冲流时都会转换为块读写操作。
java.io.BufferedInputStream;
java.io.BufferedOutputStream;
import java.io.*;
public class TestForIO {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("Travel.mp3");
//链接缓冲流
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
FileOutputStream fileOutputStream = new FileOutputStream("Travel_copy2.mp3");
//链接缓冲流
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
int len = -1;
long start = System.currentTimeMillis();
while ((len = bufferedInputStream.read())!=-1){
bufferedOutputStream.write(len);
}
long end = System.currentTimeMillis();
System.out.println("复制完毕所花的时间为:"+(end-start)+"ms");
bufferedInputStream.close();
bufferedOutputStream.close();
}
}
//output:
//复制完毕所花的时间为:29535ms(未使用缓冲流所用时间)
//复制完毕所花的时间为:149ms
当使用缓冲流链接低级流之后,其余的操作就要围绕着缓冲流来进行了,当操作完毕之后,close方法也是针对着缓冲流进行的。
利用的块读写的原理,无论我们进行随机读写还是块读写,当经过缓冲流时都会转换为块读写操作。
protected volatile byte[] buf;
缓冲流缓冲区介绍:
缓冲流的 write 方法并不是立即将数据写出,而是先将数据存入其内部的数组中(上面的buf),当数组装满时才会做一次真实写操作。(转换为块写操作)
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
String string = "你好吗!我很好!";
fileOutputStream.write(string.getBytes(StandardCharsets.UTF_8));
System.out.println("game over!");
// fileOutputStream.close();
}
}
//output:game over!
当刻意把 close 方法注释掉,再执行代码;我们查看相关的文件可以发现,数据写进去了!
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
String string = "你好吗!我很好!";
bufferedOutputStream.write(string.getBytes(StandardCharsets.UTF_8));
System.out.println("game over!");
// bufferedOutputStream.close();
}
}
//output:game over!
当借助缓冲流(高级流)实现上述操作后,打开相关文件发现数据并没有写进去,此时数据还位于缓冲区中,没有写入到对应文件内。
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
String string = "你好吗!我很好!";
bufferedOutputStream.write(string.getBytes(StandardCharsets.UTF_8));
System.out.println("game over!");
bufferedOutputStream.flush();
// bufferedOutputStream.close();
}
}
//output:game over!
此时我们查看对应文件可以发现,数据已经写入了对应文件;我们打开 close 方法的源代码也可以发现其函数体中会调用 flush 方法。
try {
flush();
} catch (Throwable e) {
flushException = e;
throw e;
}
// 截取的代码片段
flush 方法的意义是强制将缓冲流已经缓存的数据一次性写出。这样做可以让写出的数据有即时性,但是频繁调用会降低写效率。
对象流也是一对高级流,提供的功能是读写 Java 中的任何对象。
对象输出流:
java.io.ObjectOutputStream
它可以将给定的 java 对象转换为一组字节,然后通过其他链接的流将这些字节写出。
import java.io.Serializable;
import java.io.Serial;
public class Test implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
……
}
当一个类实现了 Serializable 接口之后,要求应当定义一个常量 serialVersionUID ,即:序列化版本号。
序列化版本号影响反序列化是否成功。当对象输入流在进行对象反序列化时会检查该对象与当前类的版本是否一致,不一致则反序列化时会抛出异常导致反序列化失败。一致则可以进行反序列化,原则是对应的属性进行还原。
如果我们不定义该版本号,编译器会在编译当前类时根据结构生成一个版本号,一旦当前类发生改变,那么版本号一定会改变;这样以前的对象一定是不可以反序列化了。
实际举例:
import java.io.*;
public class TestForIO {
public static void main(String[] args) throws IOException {
Test test = new Test("Tom",18);
FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(test);//把一个对象写出去!
System.out.println("game over!");
objectOutputStream.close();
}
}
import java.io.Serializable;
import java.util.Objects;
public class Test implements Serializable {
private String name;
private int age;
Test(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Test{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Test test = (Test) o;
return age == test.age && Objects.equals(name, test.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
//output:game over!
通过对象流写出对象的这个方法经历了两个步骤:
对象的序列化——对象流先将给定的对象转换为了一组字节,这组字节包含对象本身保存的数据信息,还包含该对象的结构信息;然后将这组字节通过其链接的流写出。
数据持久化——经过文件流时,文件流将这组字节写入到文件中。
对象输入流:
可以进行对象的反序列化操作;使用对象流读取的字节必须是通过对象输出流序列化的一组字节。
java.io.ObjectInputStream
import java.io.*;
public class TestForIO {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("test.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Test t = (Test) objectInputStream.readObject();
System.out.println(t);
objectInputStream.close();
}
}
//output:
//Test{name='Tom', age=18}
transient
transient关键字修饰的属性在对象序列化时会被忽略;忽略不必要的属性可以达到对象“瘦身”的作用
public class Test implements Serializable {
private transient String name;
private int age;
@Serial
private static final long serialVersionUID = 1L;
……
}
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
……
}
java.io.Reader;
java.io.Writer;
这两个类也是抽象类,是所有字符输入流与字符输出流的父类,规定了所有读写字符的相关方法。
转换流:
java.io.InputStreamReader;
java.io.OuputStreamWriter;
他们是一对常用的字符流实现类,经常在我们做字符数据读写操作中使用;并且在流链接中是一个非常重要的一个环节;但我们很少直接对它做操作。
OuputStreamWriter使用举例:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);
outputStreamWriter.write("How are you?");
outputStreamWriter.write(" I'm fine!");
System.out.println("Game over!");
outputStreamWriter.close();
}
}
//output:
//Game over!
相比于前面的流,OutputStreamWriter 的好处就在于不用借助 String 的 getBytes方法转换为字节,更加方便快捷。
InputStreamReader使用举例:
//随机读写
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("test.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
int data = -1;
char c = ' ';
while((data=inputStreamReader.read())!=-1){
c = (char) data;
System.out.print(c);
}
inputStreamReader.close();
}
}
//output:
//How are you? I'm fine!
//read方法源代码
public int read() throws IOException {
return sd.read();
}
//块读写
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("test.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
char[] data = new char[100];
int length = inputStreamReader.read(data);//获取实际读取到的字符个数
String string = new String(data,0,length);//限定范围,去除空格
System.out.println(string);
inputStreamReader.close();
}
}
//output:
//How are you? I'm fine!
使用其他字符流(高级流)时,直接或间接地使用了 InputStreamReader 和 OuputStreamWriter 将字符流和字节流联系转换。这就是前面提到了为什么对这两个类很少直接对它们做操作。
PrintWriter缓冲字符输出流:
具有自动行刷新的缓冲字符输出流,开发中比较常用的字符高级流;可以按行写出字符串。
java.io.PrintWriter;
PrintWriter 提供了专门针对于写文件的构造方法。
PrintWriter(String path);
PrintWriter(File file);
查看 PrintWriter 构造方法的源代码:
public PrintWriter(String fileName) throws FileNotFoundException {
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),false);
}
通过观察,我们可以发现,构造方法中依次使用到了上图中的类,最终实现了 PrintWriter 作为自动行刷新的缓冲字符输出流的功能。
代码使用举例:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException, ClassNotFoundException {
PrintWriter printWriter = new PrintWriter("test.txt",StandardCharsets.UTF_8);
printWriter.println("这是一个用于测试PrintWriter写入数据功能的程序!");
printWriter.print("测试是否能换行显示!");
System.out.println("Game over!");
printWriter.close();
}
}
//output:
//Game over!
在创建了 printWriter 对象之后,我们可以使用两个方法(println 和 print)将数据写入到文件中,它们之间的区别在于前一个写入带换行,一个写入不带换行。
在流链接中使用PrintWriter:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,StandardCharsets.UTF_8);
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
PrintWriter printWriter = new PrintWriter(bufferedWriter);
printWriter.println("Hello World!");
printWriter.close();
}
}
使用 PrintWriter 实现简易的记事本:(输入文件名,然后写入句子直到输入exit退出)
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class TestForIO {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件名:");
String fileName = scanner.nextLine();
FileOutputStream fileOutputStream = new FileOutputStream(fileName);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,StandardCharsets.UTF_8);
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
PrintWriter printWriter = new PrintWriter(bufferedWriter);
while (!"exit".equals(fileName = scanner.nextLine())){
printWriter.println(fileName);
}
System.out.println("Game over!");
printWriter.close();
scanner.close();
}
}
//output:
//Game over!
//相关构造方法源代码
public PrintWriter(Writer out,boolean autoFlush) {
super(out);
this.out = out;
this.autoFlush = autoFlush;
}
//autoFlush取值为true时,PrintWriter对象就具有了自动行刷新功能!
该构造方法中的参数流必须是字符流,所以上面的代码才整了一堆的流进行链接,没有直接使用 PrintWriter 的构造方法一步完成。
PrintWriter printWriter = new PrintWriter(bufferedWriter,true);
注意:当调用 println 方法写一行字符串时就会自动行刷新;print 方法是不会自动行刷新的。
BufferedReader缓冲字符输入流:
可以按行读取字符串。
java.io.BufferedReader;
使用举例(输出该程序文件代码至控制台)
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TestForIO {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("src/MyTest/TestForIO.java");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String string = null;
while ((string = bufferedReader.readLine())!=null){
System.out.println(string);
}
bufferedReader.close();
}
}
//output:
//就是上面的代码!
String readLine()//读取一行字符串。
当程序中抛出一个异常后,程序从程序中导致异常的代码处跳出,java 虚拟机检测寻找和 try 关键字匹配的处理该异常的 catch 块,如果找到,将控制权交到 catch 块中的代码,然后继续往下执行程序,try块中发生异常的代码不会重新被执行。如果没有找到处理该异常的 catch 块,在所有的 finally 块代码被执行和当前线程所属的 ThreadGroup 的 uncaughtException 方法被调用后,遇到异常的当前线程被中止。
Throwable,Error和Exception:
Java 异常结构中定义有 Throwable 类,Exception 和 Error 是其派生的两个子类。其中 Exception 表示由于网络故障、文件损坏、设备错误、用户输入非法等情况导致的异常;而 Error 表示 Java 运行时环境出现的错误,例如: JVM 内存资源耗尽等。
基本介绍:
代码举例(未使用try-catch):
public class TestForTryCatch {
public static void main(String[] args) {
System.out.println("Start!");
String string = null;
System.out.println(string.length());
System.out.println("End!");
}
}
当 JVM 执行代码发现一个错误时,会根据错误实例化对应的异常实例,并将程序执行过程设置进去,然后将该异常在出错之后的语句位置抛出。
之后 JVM 会检查抛出异常的语句是否有被 try-catch 处理,若没有则认为出错的语句所在的方法没有解决异常的能力,随之将异常抛出到该方法外。
代码举例(使用try-catch):
public class TestForTryCatch {
public static void main(String[] args) {
System.out.println("Start!");
try{
// String string = null;
String string = "";
System.out.println(string.length());
System.out.println(string.charAt(0));
System.out.println("!!!!!!");//try语句块中出错代码以下内容都不执行!
}catch (NullPointerException e){
System.out.println("出现了空指针!");
}catch (StringIndexOutOfBoundsException e){
System.out.println("字符串下标越界了!");
}catch (Exception e){
System.out.println("出现了未知的错误!");
}
System.out.println("End!");
}
}
//output:
//Start!
//0
//字符串下标越界了!
//End!
注意事项:
finally 块是异常处理机制的最后一块,可以直接跟在 try 之后或者最后一个 catch 之后。finally 可以确保只要程序运行到 try 语句块中,那么无论是否抛出异常,finally 中的代码必定执行。
代码举例:
public class TestForTryCatch {
public static void main(String[] args) {
System.out.println("Start!");
try{
String string = "hhhh";
System.out.println(string.length());
}catch (NullPointerException e){
System.out.println("出现了空指针!");
}finally {
System.out.println("finally!");
}
System.out.println("End!");
}
}
//output:
//Start!
//4
//finally!
//End!
public class TestForTryCatch {
public static void main(String[] args) {
System.out.println("Start!");
try{
// String string = null;
String string = "hhhh";
System.out.println(string.length());
return;//会先执行完finally语句块再return。
}catch (NullPointerException e){
System.out.println("出现了空指针!");
}finally {
System.out.println("finally!");
}
System.out.println("End!");
}
}
//output:
//Start!
//4
//finally!
实际应用:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestForTryCatch {
public static void main(String[] args) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream("test.txt");
fileOutputStream.write('A');
// fileOutputStream.close();//原本的位置
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
if (fileOutputStream!=null)
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileOutputStream 对象的定义要放在 try 语句块的外面,在 try 语句块中定义的话,那么 finally 语句块中将因为变量作用权限的问题,无法识别该对象。
FileOutputStream fileOutputStream = null;
通过观察 FileNotFoundException 的源代码,我们可以发现 该类是继承于 IOException 的,所以对于异常的捕获没有更加精细的要求的话,对于 FileNotFoundException 异常的捕获是可以省去的。
public class FileNotFoundException extends IOException{
……
}
但是如果为了更加准确的捕获异常的信息,则派生类(子类)要在超类(父类)之前进行捕获判断。
catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
个人理解:就像下网捕鱼一样,一张大、小鱼都能捕获的网在前面,后面只能捕获小鱼的网怎么能抓到鱼。所以捕获的“层级”要逐步提升。
因为当 try 语句块中某一句程序报错被捕获之后,其后面的程序都不会被运行,所以为了保证文件流的关闭,需要借助 finally 语句块。
finally{
try {
if (fileOutputStream!=null)
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
之所以在 close 方法周围又来一个异常处理程序,是 java 为了 close 没有被正确执行所做的预防性策略。
针对上面代码的优化:
在 JDK1.7 之后推出了一个新的特性——autoclose;允许编译器在编译过程中自动处理诸如流的关闭工作。因此上面的代码可以改写为:
import java.io.FileOutputStream;
import java.io.IOException;
public class TestForTryCatch {
public static void main(String[] args) {
try(FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
){
fileOutputStream.write('B');
}catch (IOException e){
e.printStackTrace();
}
}
}
需要关闭的对象,皆定义在“()”中,编译器会帮我们自动处理关闭工作,最终会将代码改变,在 finally 中将其关闭。那么什么是需要关闭的对象呢?
凡是实现了 AutoCloseable接口的类的对象才是需要关闭的对象。(譬如上面的 FileOutputStream )
public class FileOutputStream extends OutputStream{
……
}
public abstract class OutputStream implements Closeable, Flushable {
……
}
public interface Closeable extends AutoCloseable {
……
}
常见的实现了 AutoCloseable接口的类有——JavaIO中的所有流、RandomAccessFile ……
final、finally、finalize之前的区别:
一个笔试题:
public class TestForTryCatch {
public static void main(String[] args) {
System.out.println(test("0")+","+test(null)+","+test(""));
}
public static int test(String str){
try{
return str.charAt(0)-'0';
}catch (NullPointerException e){
return 1;
}catch (Exception e){
return 2;
}finally {
return 3;
}
}
}
//output:
//3,3,3
throw关键字:
当程序发生错误无法处理时,会抛出对应的异常对象,除此之外,在某些时刻,可能会想要自行抛出异常。例如在异常处理结束之后,再将异常抛出,让下一层异常处理块来捕捉,若想要自行抛出异常,可以使用 throw 关键字,并生成指定的异常对象后抛出。
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if (age<0||age>100){
throw new RuntimeException("年龄数据不合理!");
}
this.age = age;
}
}
public class TestForTryCatch {
public static void main(String[] args) {
Person p = new Person();
//满足语法,但是不满足业务逻辑要求,这时setAge方法中可以当做异常抛出,要求被调用时处理。
p.setAge(10086);
System.out.println(p.getAge());
}
}
//output:
//Exception in thread "main" java.lang.RuntimeException: 年龄数据不合理!
//at MyTest.Person.setAge(Person.java:12)
//at MyTest.TestForTryCatch.main(TestForTryCatch.java:9)
throws关键字:
程序中会声明许多方法,这些方法中可能会因某些错误而引发异常,但不希望直接在这个方法中处理这些异常,而希望调用这个它的方法来统一处理,这个时候可以使用 throws 关键字来声明这个方法将会抛出异常。(换句话说就是通过 throws 来通知调用该方法的方法做好处理异常的准备,设置好 try-catch 程序。)
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) throws Exception {
if (age<0||age>100){
throw new Exception("年龄数据不合理!");
}
this.age = age;
}
}
public class TestForTryCatch {
public static void main(String[] args) {
Person p = new Person();
try {
p.setAge(10086);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(p.getAge());
}
}
//output:
// java.lang.Exception: 年龄数据不合理!
// at MyTest.Person.setAge(Person.java:10)
// at MyTest.TestForTryCatch.main(TestForTryCatch.java:8)
//0
throws 小结:
重写超类含有 throws 声明异常抛出的方法时对于 throws 的重写规则:
import java.awt.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLClientInfoException;
public class Person {
public void doSomething() throws IOException, AWTException{
}
}
class Son extends Person{
//可行方案
// public void doSomething() throws IOException, AWTException{
// //全部异常
// }
// public void doSomething() throws AWTException {
// //仅仅抛出部分异常
// }
// public void doSomething(){
// //允许不抛出异常
// }
// public void doSomething() throws FileNotFoundException {
// //抛出超类方法抛出异常的子类型异常
// }
//不可行方案:
// public void doSomething() throws SQLClientInfoException {
// //不允许抛出“额外”异常;和超类的异常类之间没有子类继承关系。
// }
// public void doSomething() throws Exception {
// //不允许抛出超类方法抛出异常的父类型异常。
// }
}
RuntimeException(运行时异常):
Java 异常可以分为可检测异常、非检测异常:
RuntimeException 类属于非检测异常,因为普通 JVM 操作引起的运行时异常随时可能发生,此类异常一般是由于特定操作引发的;但是这些操作在 java 应用程序中会频繁出现,因此它们不受编译器检查与处理或声明规则的限制。
通常一个方法中使用 throw 抛出一个异常时,就要在方法声明时使用 throws 声明该异常的抛出以通知调用者解决该异常。只有抛出 RuntimeException (运行时异常)及其子类型异常时可以不要求这样做。
常见的RuntimeException:
异常 | 说明 |
---|---|
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。 |
ArrayIndexOutOfBoundsException | 当使用的数组下标超过数组允许范围时,抛出该异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
printStackTrace 方法:输出错误信息。
public class TestForTryCatch {
public static void main(String[] args) {
try{
String string = "A";
System.out.println(Integer.parseInt(string));
}catch (Exception e){
e.printStackTrace();//输出错误信息
}
}
}
//output:
//java.lang.NumberFormatException: For input string: "A"
// at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)
// at java.base/java.lang.Integer.parseInt(Integer.java:652)
// at java.base/java.lang.Integer.parseInt(Integer.java:770)
// at MyTest.TestForTryCatch.main(TestForTryCatch.java:5)
getMessage 方法:获取错误消息。
public class TestForTryCatch {
public static void main(String[] args) {
try{
String string = "A";
System.out.println(Integer.parseInt(string));
}catch (Exception e){
System.out.println(e.getMessage());//获取错误消息
}
}
}
//output:
//For input string: "A"
通常是用来说明当前项目的某个业务逻辑错误。
首先定义自定义异常时,需要将其继承现有的一个异常类。
public class IllegalAgeException extends Exception{
……
}
然后自定义异常类中类体的声明可以借助编译器快捷实现。
最后使用时是和其他java给定的异常类一样使用的。
//异常类的定义:
public class IllegalAgeException extends Exception{
public IllegalAgeException() {
}
public IllegalAgeException(String message) {
super(message);
}
public IllegalAgeException(String message, Throwable cause) {
super(message, cause);
}
public IllegalAgeException(Throwable cause) {
super(cause);
}
public IllegalAgeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
//测试对象类的定义:
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) throws IllegalAgeException {
if (age<0||age>100){
throw new IllegalAgeException("年龄不符合!");
}
this.age = age;
}
}
//测试程序的设计:
public class TestForTryCatch {
public static void main(String[] args) {
Person p = new Person();
try {
p.setAge(10086);
} catch (IllegalAgeException e) {
e.printStackTrace();
}
System.out.println(p.getAge());
}
}
//output:
// MyTest.IllegalAgeException: 年龄不符合!
// at MyTest.Person.setAge(Person.java:14)
// at MyTest.TestForTryCatch.main(TestForTryCatch.java:7)