目录
📢:哈喽~☀️欢迎加入程序🐒大家庭,快点开始✒️自己的黑客帝国吧 ~🌊🌊
内容简述:正则表达式、包装类、文件操作、IO操作、异常处理、多线程基础、集合、泛型、增强for循环。
创建一个Maven项目:
新建→搜索Maven project
勾选Create a simple project.和Use default workspace location.
填:Group Id:com.acgkaka
Artifact Id:ACGkaka_SE
一、API、字符串基本操作
文档注释
-
文档注释值修饰三个地方—类,方法,常量
-
在类上声明是用来说明当前类的设计目的,以及当前类的功能。
什么是 JDK API
-
JDK中包含大量的AP类库,所谓API( Application Programming Interface,应用程序编程接口)就是些已写好、可供直接调用的功能(在Java语言中,这些功能以类的形式封装)
-
JDK API包含的类库功能强大,经常使用的有:字符串操作、集合操作、文件操作、输入输出操作、网络操作、多线程等等。
文档注释规范
String是不可变对象
-
java.lang.String使用了fna修饰,不能被继承;
-
字符串底层封装了字符数组及针对字符数组的操作算法;
-
字符串一旦创建,对象永远无法改变,但字符串引用可以重新赋值;
-
Java字符串中任何一个字符对应16位(两个字节)的定长 Unicode编码
String常量池
-
Java为了提高性能,静态字符串(字面量/常量/常量连接的结果)在常量池中创建,并尽量使用同一个对象,重用静态字符串;
-
对于重复出现的字符串直接量,NM会首先在常量池中查找,如果存在即返回该对象。
内存编码及长度
- String在内存中采用 Unicode编码,每个字符16位占用两个字节:任何一个字符(无论中文还是英文)都算1个char字符长度,占用两个字节。
使用 indexOf实现检索
-
indexOf 方法用于实现在字符串中检索另外一个字符串
-
String提供几个重载的 index法
方法 说明 int indexOf(String str) 在字符串中检索str,返回其第一次出现的位置,如果找不到则返回-1 int indexOf(String str, int fromIndex) 从字符串的 fromIndex 位置开始检索 -
String还定义有 lastIndexOf方法
方法 说明 int lastIndexOf( String str, int from) str在字符串中多次出现时,将返回最后一个出现的位置
使用 substring获取子串
-
substring方法用于返回一个字符串的子字符串。
-
substring常用重载方法定义如下
方法 说明 String substring(int beginIndex, int endIndex) 返回字符串中从下标 beginIndex(包括)开始到 endIndex(不包括)结束的子字符串 String substring(int beginIndex) 返回字符串中从下标 beginIndex(包括)开始到字符串结尾的子字符串
注意:java API有一个特点,通常用两个数字表示范围时,都是含头不含尾的
trim(查闻API)
- 去掉一个字符串的前导和后继空字符
char At(查阅API)
-
String中定义有 charAt()方法
方法 说明 char charAt(int index) 方法 charAt(index)用于返回字符串指定位置的字符。
参数 index表示指定的位置
starts With 和 endswith(查阅API)
- 检测一个字符串是否以指定字符串开头或结尾
static String valueOf(XXX xxx)
-
字符串提供了一组重载的valueOf方法,可以java中
-
不同类型的数据转换为字符串
-
常见的是将基本类型转换为字符串
(int a = 123 + “”; // 也可以但是性能差)
String Builder封装可变字符数组
-
String Builder封装可变的字符数组,对象创建后可以通过调用方法改变其封装的字符序列
-
String Builder有如下常用构造方法
- public String Builder()
- public String Builder(String str)
StringBuilder常用方法
String Builder类的常用方法 | 功能描述 |
---|---|
String Builder append(String str) | 追加字符串 |
String Builder insert(int dstoffset, String s) | 插入字符串 |
String Builder delete(int start, int end) | 删除字符串 |
String Builder replace(int start, int end, String str) | 替换字符串 |
String Builder reverse() | 字符串反转 |
String Builder
-
String Builder的很多方法的返回值均为 String builder类型。这些方法的返回语句均为: return this。
-
由于改变封装的字符序列后又返回了该对象的引用。可以按照如下简洁的方式书写代码
buf.append("ibm").append("java")
.insert(3, "oracle")
.replace(9, 13,"JAVA");
System.out.printIn(buf.toString());
StringBuilder总结
-
StringBuilder是可变字符串。字符串的内容计算,建议采用 String Builder实现,这样性能会好一些;
-
java的字符串连接的过程是利用 String Builder实现的
String s = "AB"; String s1 = s + "DE" + 1; // s1 相当于 s2 String s2 = new StringBuilder(s).append("DE").append(1).toString();
-
StringBuffer 和 StringBuilder
- StringBuffer是线程安全的,同步处理的,性能稍慢
- StringBuilder是非线程安全的,并发处理的,性能稍快
补充:
-
单线程的情况下使用StringBuilder,多线程情况下使用StringBuffer
-
几乎不在多线程情况下操作同一个字符串,一般是用StringBuilder
二、正则表达式、Object、包装类
正则表达式简介(1)
-
实际开发中,经常需要对字符串数据进行一些复杂的匹配、查找、替换等操作。
通过“正则表达式”,可以方便的实现字符串的复杂操作。
-
正则表达式是一串特定字符,组成一个“规则字符串”,这个“规则字符串”是描述文本规则的工具。
正则表达式就是记录文本规则的代码。
-
例如:
- 正则表达式:“[a-z]”表示a到z的任意一个字符
- 正则表达式"[a-z]+“表示由1个或多个a-z字符组成的字符串
正则表达式简介(2)
- 字符集合:
正则表达式 | 说明 |
---|---|
[abc] | a、b、c中任意一个字符 |
[^abd] | 除了a、b、c的任意字符 |
[a-z] | a、b、c、…z中的任意一个字符 |
[a-zA-Z0-9] | a-z、AZ、09中任意一个字符 |
[a-z&&[^bc]] | a~z中除了b和c以外的任意一个字符,其中&&表示与”的关系 |
正则表达式简介(3)
- 预定义字符集:
正则表达式 | 说明 |
---|---|
. | 任意一个字符 |
\d | 任意一个数字字符,相当于[0-9] |
\w | 单词字符,相当于[a-zA-Z0-9] |
\s | 空白字符,相当于[\t\n\x0B\f\r] |
\D | 非数字字符 |
\W | 非单词字符 |
\S | 非空白字符 |
正则表达式简介(4)
- 数量词:
正则表达式 | 说明 |
---|---|
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 |
正则表达式简介(5)
- 检索邮政编码:
- 规则为6位数字
- 第一种匹配规则 [0-9][0-9][0-9][0-9][0-9][0-9]
- 简化第一种规则 d\d\d\d\d\d
- 简化第二种规则 \d{6}
分组 "()"
-
分组 : () 圆括号表示分组,可以将一系列正则表达式看做一个整体,分组时可以使用""表示“或”关系,
例如 : 匹配手机号码前面的区号:
(+86|0086)?\s?\d{11}
上述例子中,圆括号表示这里需要出现"+86"或者"0086"
"^" 和 "$"
-
边界匹配
- 代表字符串开始
- 代表字符串结束
-
例如:匹配用户名规则 - 从头到尾连续8~10个单词字符
\w{8, 10}
^\w{8, 10}$
-
如果使用第一种写法,则"abcd1234_abcd"是可以验证通过的;
-
使用第二种写法由于有从头到尾整体的限定,则验证不能通过。
注意:
在java中,就算不写“^”和“$”也默认是全局认证;
即:[a-z]{3}.*[a-z]{3}和1[3].*[a-z]{3}$是一样的;
正则表达式就算不添加边界符(^…$)也是做全匹配验证。
字符串支持正则表达式的相关方法一:
-
boolean matches(String regex)
-
使用给定的正则表达式验证当前字符串是否满足
-
格式要求,满足则返回true
验证邮箱的正则表达式:
-
[a-zA-Z0-9]+@[a-zA-Z0-9_]+([.a-zA-Z]+)+
-
\\w+@\\w+(\\.\\w{2,3})+
字符串支持正则表达式的方法二:
-
String[] split(String regex)
-
按照当前字符串中满足正则表达式的部分进行拆分
-
字符串.将拆分出来的每段内容存入一个数组并返回该数组
-
若当前字符串中连续匹配了两次要拆分的部分,那么
-
中间会拆出一个空的字符串.
例如:
String str="abc123def456ghi789jkl";
/*
* 按照数字部分进行拆分,得到所有的字母部分
* abd def ghi jkl
*/
String[] data = str.split("[0-9]+");
System.out.println("len:"+data.length);
for (int i = 0; i < data.length; i++){
System.out.println("data["+i+"]:"+data[i]);
}
输出结果:
len:4
data[0]:abc
data[1]:def
data[2]:ghi
data[3]:jkl
注意:若第6行代码改成:
String[] data = str.split("[0-9]");
输出结果:
len:10
data[0]:abc
data[1]:
data[2]:
data[3]:def
data[4]:
data[5]:
data[6]:ghi
data[7]:
data[8]:
data[9]:jkl
字符串支持正则表达式三:
-
String replaceAll(String regex,String str)
-
将当前的字符串满足正则表达式的部分替换为
-
给定的字符串
java中所有的类都继承自Object,当一个类没有使用extends显示继承任何一个类时,编译器在编译该类后默认会让其继承自Object.
Object定义了toString方法:
-
该方法的意义是将当前字符串转为一个
-
字符串。
-
通常该字符串的内容包含该对象的属性信息
-
Object已经实现了toString方法,返回的
-
是当前对象的句柄(类名@地址)
当一个对象的toString被使用,那么就应当:
-
重写该方法,因为Object提供的方法后返回的
-
句柄对实际开发的价值不大
-
重写toString方法的原则是返回的字符串中
-
应当包含当前对象需要让外界了解的属性信息。
-
格式没有要求,将来按照实际工作中的需求定。
JAVA很多的API都会使用给定对象的 toString() 方法。
* System.out.println(Object obj)方法都使用了,
* 该方法会将给定对象的toString返回的字符串输出到控制台
JAVA API中提供的类大部分都已经重写过toString方法。
- 只有我们自己定义的类需要自行重写toString方法。
"=="是值比较
-
对于两个引用类型变量而言,值比较就是
-
比较地址,若"=="为true则表示两个引用
-
类型变量指向同一个对象
Object提供了equals方法,目的是比较两个对象的内容是否一样
-
Object实现的equals内部就是"==",所以若不重写该方法是没有实际意义的。
-
JAVA API中大部分类都重写了equals方法。
-
只有自己定义的 类我们在需要使用的时候
-
必须重写它。
包装类概述
-
在进行类型转换的范畴内,有一种特殊的转换,需要将 int 这样的基本数据类型转换为对象
-
所有基本类型都有一个与之对应的类,即包装类(wrapper)。
包装类概述(续1)
-
包装类是不可变类,在构造了包装类对象后,不允许更改包装在其中的值;
-
包装类是fina的,不能定义他们的子类
基本类型 | 包装类 | 父类 |
---|---|---|
int | java.lang.Integer | java.lang.Number |
long | java.lang.Long | java.lang.Number |
double | java.lang.Double | java.lang.Number |
short | java.lang.Short | java.lang.Number |
float | java.lang.Float | java.lang.Number |
byte | java.lang.Byte | java.lang.Number |
char | java.lang.Character | java.lang.Object |
boolean | java.lang.Boolean | java.lang.Object |
包装类(JDK1.5之后的新特性)
-
由于基本类型没有面向对象特征,在实际开发中不能
-
直接参与面向对象的开发环节。为此java为这8个基本
-
类型提供了对应的包装类。
-
其中6个数字类型的包装类都继承自Number,而char,
-
boolean的包装类直接继承自Object。
Number及其主要方法
-
抽象类 Number 是Byte、Double、Foat、Integer、Long和 Short类的父类;
-
Number的子类必须提供将表示的数值转换为byte,double,foat,int,long和 short的方法:
- doubleValue() 以double形式返回指定的数值
- intValue() 以int形式返回指定的数值
- floatValue() 以float形式返回指定的数值
Integer自带的valueOf()方法和Integer的构造
-
方法作用相同,但是valueOf()在某种程度上
-
可以帮你重用一些对象(就像String类一样,可以节省空间,
-
相同的内容重复实例化,地址是一样的)
-
但是只局限在-128~127之间,超出这个范围就不再重用,
-
而其他基本类型对应类中的valueOf()不会出现这样的问题,
-
java建议在一般情况下用valueOf()
包装类定义了相关的常量
-
其中数字类型的包装类有两个常用的常量:
-
MAX_VALUE,MIN_VALUE分别表示包装类对应的基本
-
数据类型的取值范围
包装类提供了一个静态方法:parseXXX(String str)
-
可以将字符串转换为对应的基本类型数据。前提是
-
该字符串的内容必须能正确表示基本类型可以保存的值。
-
注意:parseInteger(String str)方法在使用时,
-
如果字符串的格式不是整数形式,将无法转换
在JDK1.5的时候推出了一新的特性——自动拆装箱
-
自动拆装箱是编译器认可的特性,而非JVM
-
认可,当编译器在编译源代码时发现了基本
-
类型月对应的包装类之间互相赋值使用时,
-
会自动补充代码完成他们之间的转换工作。
手动拆箱:
int d=new Integer(123).intValue();
自动拆装箱:
// 触发了自动拆箱特性,编译器会补充代码,将引用类型转换为基本类型;
int d = new Integer(123);
// 触发自动装箱特性,编译器会补充代码,将基本类型转换为引用类型。
Integer i = Integer.valueOf(d);
三、二进制补课
// 将十进制int类型转换成二进制的字符串返回
Integer.toBinaryString(int i)
16进制
-
逢16进1的计数规则
-
用于缩写2进制:每4位2进制缩写为1位16进制数,
-
从使用角度:16进制就是2进制!
补码:(互补对称,取反加一)
- 将固定位数的二进制数分一半作为“负数”使用的算法称为补码。
1. 原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
[+1]原 = 0000 0001
[-1]原 = 1000 0001
第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:
[1111 1111 , 0111 1111]
即
[-127 , 127]
原码是人脑最容易理解和计算的表示方式.
2. 反码
反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.
3. 补码
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.
注意:计算中存储的都是补码,正数是直接存储,负数转换成补码再存储。
答案:D
负数=原数取反(1变0,0变1)+1
注意:最小值不满足上面的公式
总结:
1:一个数一旦溢出以后可能是负的也可能是正的
2:溢出结果是一个明确的值但是没有数学意义,我们不做考究
注意:JAVA中byte和short类型底层都是int,不适用于这个公式
UTF-8里中文是三个字节,是适用范围最广的
做网站一般就用UTF-8编码
(实际是在截取)
(实际是在拼接)
相当于str.getByte(“UTF-8”);
验证一下:new String(bytes,编码方案)
//将bytes数据进行解码
左移运算用于解码
左移的位数会自动对32取余
移位运算比普通乘法的效率更高
-97/2然后往小方向取整,得-49
-49/2然后往小方向取整,的-25
纯数字移动一般用三个箭头,接近数学运算一般用两个箭头
四、文件操作-RandomAccessFile(上)
创建File的时候书写路径尽量使用相对路径
-
路径。避免平台差异性。
-
目录层级分隔符应当使用File提供的一个
-
常量:File.separator
常见的相对路径:
-
"."表示当前目录,
- 当前目录视运行环境不同路径也不同;
- 在eclipse中运行java程序时的当前目录是当前类所在的项目的根目录
-
类加载路径
.mkdirs()是在创建当前File表示的目录
-
同时将该目录其上的所有不存在的父目录
-
一同创建出来。
boolean isFile()
-
判断当前File表示的是否为一个文件
-
boolean isDirectory()
-
判断当前File表示的是否为一个目录
File Filter接口
-
FileFilter用于抽象路径名的过滤器
-
此接口的实例可传递给File类的 listFiles( File Filter)方法。用于返回满足该过滤器要求的子项。
- File[] listFiles(FileFilter filter)
File[] list = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathName)(
return pathName.getName().startswith(".");
}
});
File[] listFiles()
-
获取当前目录中的所有子项,
-
如果该文件下没有文件或文件夹则返回null,记得处理,不然会报空指针异常!
File提供了一个重载的listFile方法
-
该方法允许我们传入一个文件过滤器FileFilter
-
该方法会将File表示的目录中所有满足过滤器要求的
-
字相返回,而不满足的则被忽略
java.io.RandomAccessFile
-
用于读写文件数据的类
-
RAF读写文件数据总是在指针当前位置进行读或写,
-
并且读写后之真会自动后移。
-
指针是指向文件数据位置的标记(底层实现)
第二个参数是读写模式,常用的有:
-
“r”:只读模式(要求文件必须存在)
-
“rw”:读写模式(文件不存在可以自动创建文件)
注意:提高每次读写的数据量减少读写次数,可以提高读写效率
RandomAccessFile提供了批量读写字节的方法:
-
int read(byte[] data)
-
一次性读取给定字节数组总长度的字节量
-
并将独到的字节存入到该数组中,返回值为
-
实际读取到的字节量,若返回值为-1表示
-
本次没有读取到任何字节(文件末尾读取)
write(byte[] data)
-
一次性将给定字节数组中的所有字节写出
-
write(byte[] data,int offset,int len)
-
将给定字节数组从下标为offset处的连续len个字节
-
一次性写出(一次读10k(1024*10)性价比就已经很好了)
五、文件操作-RandomAccessFile(下)、基本IO操作
String(byte[] data)
-
将给定的字节数组中所有字节按照系统默认的
-
字符集转换成字符串
String(byte[] data,int offset,int len)
-
将给定的字节数组中从下标为offset处开始的连
-
续len个字节按照系统默认的字符集转换成对应的字符
注意:不管往文件里面写什么都要转换成二进制再往文件里面写
注意:RandomAccessFile只在当前指针位置进行读和写
long getFilePointer()
- 获取文件指针位置
.seek()
- 操作指针位置
读取long
-
移动指针到long值的第一个字节的所在位置
raf.seek(8);;
-
连续读取8个字节还原为该long值
long l=raf.readLong();
IS和OS常用方法(续1)
-
Outputstream是所有字节输出流的父类,其定义了基础的写出方法,常用的方法如下:
-
void write(int d)
写出一个字节写的是给定的int的”低八位
-
void write(bytel d)
将给定的字节数组中的所有字节全部写出
-
java io java标准IO
-
IO流根据方向分为:
-
输入流:外界到我们编写的程序中的方向,所有输入流是利用从外界或取数据的流,读操作
-
输出流:将数据从我们编写的程序发送到外界的方向,写数据
java将流分为两类:
-
节点流:节点流又称为低级流,
- 是实际连接程序与数据源的"管道",
- 负责传输数据,读写一定是建立在节点流上进行的。
-
处理流:处理流又称为高级流,
- 用于处理其他流,不能独立存在(没有意义),
- 使用高级流处理其他流的目的是通过高级流带来的功能
- 简化我们对数据读写时的某些复杂操作。
文件流:
-
FileInputStream、FileOutputStream
-
它们是一对低级流,作用是对文件读写数据。
-
从功能上讲它们与RandomAccessFile一致,但是底层实现不同,
-
RandomAccessFile是专门设计用来读写文件数据的,基于指针操作,
-
而文件流符合JAVA标准IO操作
FileOutputStream(String fileName)
-
FileOutputStream(File file)
-
上面两种构造方法创建的文件输出流都是覆盖写操作。
(补充:覆盖写操作—如果文件存在,将文件内容清空后写入)
FileOutputStream(String fileName,boolean append)
-
FileOutputStream(File file,boolean append)
-
追加写模式,即:若制定的文件已经存在,
-
而且第二个参数值为true,会接着在文件末尾写入新数据
处理流,又称高级流
-
作用是使用它们可以简化我们的读写操作。
-
缓冲流:提高读写效率
-
BufferedInputStream:缓冲字节输入流,提高读取效率
-
BufferedOutputStream:缓冲字节输出流,提高写出效率
void flush()
-
将传冲去已经缓存的数据一次性写出,
-
频繁的调用flush会降低写出效率,
-
但是可以保证写出数据的即时性。
六、文本数据IO操作、异常处理(上)
PO–Persistant Object 持久对象
- 和数据库中表结构映射的Java实体类。
VO–Value Object 值对象
- 业务层和视图层、业务层之间的值传递
对象流是一组高级流
-
作用是通过这组流可以方便的读写java中的任何对象。
-
对象输出流:用于写出对象,由于底层读写都是字节读写,
- 所以无论什么样的数据都要转换为字节才能写出。
- 对象输出流可以自行将给定的对象转换为一组字节然后写出,
- 省去了我们将对象按照结构转化为字节的麻烦。
文件流作用:将给定的字节写入到指定文件
对象输出流作用:将给定的java对象转换为一组字节后写出
ObjectOutputStream提供了写对象的方法:
-
void writeObject(Object obj)
-
该方法会将给定的对象转换为一组字节然后
-
通过其处理的流写出
FileOutputStream fos=new FileOutputStream("person.obj");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(p);
System.out.println("写出完毕!");
oos.close();
这里的操作是:
- 先通过OOS将p对象转换为了一组字节,
- 然后再将该组字节通过FOS写入到了文件person .obj中
这里设计到两个操作:
-
对象序列化:将一个对象按照结构转换为一组字节的过程。
-
对象持久化:将该对象写入文件(硬盘中)的过程。
注意:若一个类的实例希望被对象流读写,那么该类必须实现
- 接口:java.io.Serializable,否则会报错
ObjectInputStream提供方法:
-
Object readObject()
-
该方法可以读取字节并还原为指定的对象
-
需要确保OIS读取的字节是通过对象输出流(OOS)
-
将一个对象写出的字节。否则会抛出异常。
当一个类实现了Serializable接口后
-
应当定义一个常量:serial.VersionUID
-
它是序列化版本号,若不指定,编译器
-
会在编译当前类时根据当前类结构生成一个版本号。
版本号
-
版本号决定反序列化当前类实例时是否可以成功。
-
当版本号一致时反序列化成功,
-
不一致时对象输入流在进行反序列化时会抛出异常。
-
若当前类结构发生改变,但是版本号没有改变的前提下,
-
原来的对象是可以反序列化的,
-
这是因为会采取兼容模式,即:还原原有属性。
transient关键字:
-
对象在序列化后得到的字节序列往往比较大,
-
有时我们在对一个对象进行序列化时可以忽略某些不必要的属性,
-
从而对序列化后得到的字节序列进行“瘦身”。
-
关键字—transient
-
被该关键字修饰的属性在序列化时其值将被忽略
Reader的常用方法:
-
int reado:读取一个字符,返回的int值“低16″位有
-
int read( char[] chs):从该流中读取一个字符数组的length个字符,
并存入该数组,返回值为实际读取到的字符量。
Writer的常用方法
-
void write(int c):写出一个字符,写出给定int值”低16′位表示的字符。
-
void write( char[] chs):将给定字符数组中所有字符写出。
-
void write( String str):将给定的字符串写出。
-
void write(char[] chs, int offset, int len):将给定的字符数组中从ofet处开始连续的len个字符写出。
java按照流读写数据的单位划分为:
-
字节流,字符流
-
字符流的读写单位是字符,但是实际底层还是以字节为单位读写。
-
字符流会负责将字符与字节之间进行转换。
-
由于字符流以字符为单位读写,所以它们只适合读写文本数据!
- Reader,Writer是所有字符输入,输出流的超类;
- 规定了字符流都应当具备的读写字符方法。
转换流(高级流)
- InputStreamReader,OutputStreamWriter
(注意:其他所有的字符流都不能直接接在字节流上,所以需要转换流在中间做一个转换的作用)
-
之所以叫转换流,
-
是因为java中其他的字符流都只能节在其他字符流上,不能直接处理字节流,
-
而转换流是可以处理字节流的,它们本身又是字符流,可以被其他字符流处理。
-
所以他们起到了“转换器”的作用,从而实现了其他字符流最终可以连接到字节流上的目的。
缓冲字符流
-
特点是可以按行读写字符串
-
java.io.BufferedWriter
-
java.io.BufferedReader
七、异常处理(下)、TCP通信
(异常详细参见--------https://www.cnblogs.com/cvst/p/5822373.html)
PrintWriter
- 是常用的缓冲字符输出流,
- 内部一定连接BufferedWriter作为缓冲功能。
- 而PrintWriter本身还提供了自动行刷新功能。
PrintWriter提供了直接写文件的相关构造方法
-
PrintWriter(String path)
-
PrintWriter(File file)
如果是直接操作文件,那么还支持重载的构造方法:
-
PrintWriter(String path,String csn)
-
PrintWriter(File file,Stirng csn)
-
第二个参数为字符集(charset name)
-
PrintWriter(Writer out, boolean autoFlush)
-
第二个参数为判断是否自动在每行结束的时候(println())自动调用
-
flush()方法将缓冲区剩余内容写入文件(boolean autoFlush)
当实例化PrintWriter在流连接中使用时,
-
即:构造方法第一个参数传入一个流,那么就支持另一个重载构造方法,
-
可以传入第二个参数,该参数为boolean值,默认false,
-
当该值为true时,当前PrintWriter具有自动行刷新的功能。
-
自动行刷新指的是每当我们使用println()方法,写出一行字符串后会自动flush();
BufferedReader提供了一个方法:
-
String readLine()
-
该方法会连续读取若干字符,直到读取换行符为止,
-
然后将换行符之前的字符以字符串形式返回。
-
需要注意,该字符串中不含有最后的换行符。
-
若返回值为null,表示所有数据都已读取完毕(读文件则为文件末尾)
异常:(责任制问题)
Throwable,Error和Exception:
- Java异常结构中定义有 Throwable类, Exception和Error是其派生的两个子类。
- 其中 Exception表示由于网络故障、文件损坏、设备错误、用户输入非法等情况导致的异常;
- 而Error表示Java运行时环境出现的错误,例如:JVM内存资源耗尽等。
结构图:
java异常捕获机制中的try-catch:
-
结构:
try{ // 可能出现错误的代码片段 }catch(要捕获的异常类型){ // 处理手段 }
(注意:try块中出错的代码以下的内容都不会再执行)
catch可以定义多个
-
针对不同异常有不同解决手段,应当针对这些异常进行捕获;
-
应当养成一个好习惯,在最后捕获Exception,
-
这样做可以避免因为try代码块中抛出一个未捕获异常导致程序中断。
-
当捕获的异常存在继承关系时,应当将子类型异常定义在上面现行捕获。
finally块
-
finally只能定义在异常捕获机制的最后,
-
可以直接跟在try块之后或者最后一个catch之后。
-
finally可以保证里面的代码一定执行(除非try语句块儿中有System.exit(0))。
-
所以通常会将无关异常而不需运行的代码,
-
定义在finally确保它们可以被运行。比如流操作中关闭流就应当放在finally中。
注意:
- 如果有try或catch中有return并且finally中有return的话,
- 不考虑try和catch中的return,只看finally中的return,
- try和catch中的return语句会执行,
- 但是java虚拟机会先把return的值存到缓存里,
- 每次有新的return值时会把前面覆盖,最终返回最后的值。
- 所以只需要看finally中的return值。
区分final,finally,finallize:
1.final的用法:
1)修时类的时候不能被继承
2)修时变量的时候变量不能被改变
3)修时方法的时候变量不能被重写
2.finally的用法:
finally 通常和 try-catch 搭配使用,保证不管有没有发生异常,资源都能够被释放(释放连接、关闭IO流)。(System.exit(0)可以直接结束程序而不执行finally里面的内容)
3.finalize的用法:
finalize是object类中的一个方法,子类可以重写finalize()方法实现对资源的回收。垃圾回收只负责回收内存,并不负责资源的回收,资源回收要由程序员完成,Java虚拟机在垃圾回收之前会先调用垃圾对象的finalize方法用于使对象释放资源(如关闭连接、关闭文件),之后才进行垃圾回收,这个方法一般不会显示的调用,在垃圾回收时垃圾回收器会主动调用。
小技巧:Alt+Shift+Z----对被选中的代码进行surround with
throw关键字
- 当程序发生错误而无法处理的时候,会拋出对应的异常对象,
- 除此之外,在某些时刻,您可能会想要自行拋出异常,
- 例如在异常处理结束后,再将异常拋出,让下层异常处理块来捕捉,
- 若想要自行拋出异常,您可以使用" throw关键词,并生成指定的异常对象后拋出。
例如:
- throw new ArithmeticException0:
throw用于主动抛出一个异常
- 除了RuntimeException及其子类型异常外,
- 抛出什么异常就必须在当前方法上使用throws声明该类型异常的抛出定义,
- 以通知调用者处理该异常。
throws关键字
- 程序中会声明许多方法( Method),这些方法中可能会因某些错误而引发异常,
- 但你不希望直接在这个方法中处理这些异常,而希望调用这个它的方法来统一处理
- 这时候你可以使用“ throws”关键词来声明这个方法将会抛出异常。
例如:
public static void stringToDate(String str) throws ParseException(
……
}
注意:
-
当调用一个含有throws声明异常抛出的方法时,
-
编译器要求必须处理这些异常。
-
处理方式有两种:
1:使用try-catch捕获并处理
2:继续在当前方法上使用throws将该异常声明抛出
RuntimeException
-
Java异常可以分为可检测异常,非检测异常:
-
可检测异常:可检测异常经编译器验证,对声明抛出异常的任何方法,编译器将强制执行处理或声明规则,不捕捉这个异常,编译器就通不过,不允许编译。
-
非检测异常:非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常。
-
RuntimeException类属于非检测异常,因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制
注意:永远不要在main方法中用throws抛出异常
子类在重写父类一个含有throws声明异常抛出的方法时对throws的重写规则:
-
1:父类抛啥我抛啥
-
2:可以仅抛出部分异常
-
3:可以不再抛出任何异常
-
4:可以抛父类方法抛出的异常的子类型异常
-
5:不能抛出额外异常
-
6:不能抛出父类方法抛出的异常的父类型异常
八、多线程基础(上)
java.net.Socket
-
封装了TCP通讯协议,中文翻译是:套接字
-
使用Socket的大致步骤:
-
1:实例化Socket,同时制定连接的服务端的IP和端口并与服务端建立连接
-
2:通过Socket创建两个流,一个输入流一个输出流,
- 通过输入流读取远程计算机发过来的数据
- 通过输出流将数据发送给对方计算机
实例化Socket时需要传入两个参数
-
IP地址,通过IP可以找到网络上的指定计算机
-
端口,用来连接该计算机上的对应应用程序
(实例化Socket的过程就是与服务端连接的过程)
java.net.ServerSocket
-
运行在Server端的ServerSocket
-
主要作用有两个:
-
1:向系统申请服务端口,客户端就是通过这个端口与之连接的。
-
2:监听该服务端口,一旦客户端通过这个端口请求连接,则创建一个Socket与该客户端进行通讯。
如何查看自己电脑的IP地址:
-
(在控制台输入)
-
Windows:ipconfig
-
Linux:/sbin/ifconfig
补充:
- 实例化ServerSocket的同时申请服务端口。
- 若该端口已经被其他程序占用,则会抛出异常
- address already in use
ServerSocket提供的方法:
-
Socket accept()
-
该方法是一个阻塞方法,调用到该方法后程序“阻塞”,
-
并等待客户端的连接,一旦一个客户端连接了,那么就会返回一个Socket实例,
-
通过该实例即可与连接的客户端进行通讯。
Socket提供的方法:
-
InputStream getInputSream()
-
通过该输入流读取的字节就是远程计算机发过来的字节,
-
对于服务端这边而言,远程指的就是客户端。
空指针异常:
- 我们调用一个对象的方法,如果这个对象是null就会报空指针异常。
(出错以后距离错误最近的你能看懂的那行就是错误的源头!!)
BindException
- BindException:端口被占用
- (8088端口一般是被自己写的程序占用了,找不出来就重启电脑)
- (不关闭服务器直接关闭eclipese就会出现端口被占的异常)
多线程:
-
线程是进程里面的一个任务,进程是操作系统里面的一个任务;
-
进程有自己独立的地址空间,线程没有,线程使用的是内存;
-
切换进程开销量大,切换线程开销量小,而且切换线程速度快
-
我们控制的时候只控制线程不控制进程。
并发原理
- 多个线程”同时”运行只是我们感官上的种表现。
- 事实上线程是井发运行的,OS将时间划分为很多时间片段(时间片),
- 尽可能均匀分配给每个线程,获取时间片段的线程被CPU运行,
- 而其他线程全部等待。所以微观上是走走停停的,宏观上都在运行。
- 这种现象叫并发,不是绝对意义上的“同时发生”。
线程使用的场合
-
线程通常用于在一个程序中需要同时完成多个任务的情况。
-
我们可以将每个任务定义为一个线程,使他们得以一同工作。
-
也可以用于在单一线程中可以完成,但是使用多线程可以更快的情况。比如下载文件。
时间片:CPU给线程的一次运行时间,是由CPU决定的,线程不能决定。每次时间片 基本是差不多长。
线程的五种状态:
-
新建状态(New)
- 当用new操作符创建一个线程时, 例如new Thread®,线程还没有开始运行,此时线程处在新建状态。
- 当一个线程处于新生状态时,程序还没有开始运行线程中的代码。
-
就绪状态(Runnable)
- 一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。
- 当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。
- 当start()方法返回后,线程就处于就绪状态。
注意:
- 处于就绪状态的线程并不一定立即运行run()方法,
- 线程还必须同其他线程竞争CPU时间,只有获得CPU时间片才可以运行线程。
- 因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。
- 因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
- 运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
- 阻塞状态(Blocked)
- 所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,
- 这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
线程运行过程中,可能由于各种原因进入阻塞状态:
1)线程通过调用sleep方法进入睡眠状态;
2)线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3)线程试图得到一个锁,而该锁正被其他线程持有;
4)线程在等待某个触发条件;
…
-
死亡状态(Dead)
有两个原因会导致线程死亡:
-
run方法正常退出而自然死亡,
-
一个未捕获的异常终止了run方法而使线程猝死。
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
-
多线程
-
线程可让我们"同时"运行多段代码。
-
线程运行代码是并发的,并不是真正的同时运行
线程有三种创建方式(面试题):
- 1:继承Thread并重写run方法,在run方法中定义线程要执行的任务(需要并发指定的代码);
class Test extends Thread {
@Override
public void run() {
System.out.println("Hello World");
}
public static void main(String[] args) {
// 方法一:继承Thread并重写run方法
Thread thread1 = new Test();
thread1.start();
}
}
- 2:实现Runnable接口;
public static void main(String[] args) {
// 方法二:实现 Runnable 接口
Thread thread2 = new Thread(() -> System.out.println("Hello World"));
thread2.start();
}
- 3:实现Callable接口,相比较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常。
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 方法三:实现 Callable 接口
FutureTask<Integer> futureTask = new FutureTask<>(() -> 1 + 1);
Thread thread3 = new Thread(futureTask);
thread3.start();
Integer sum = futureTask.get();
System.out.println("执行结果:" + sum);
}
注意:
1:让线程跑起来的时候千万不要调用run()方法,run()方法是线程要执行的任务。而是调用start()方法!!
2:线程运行的时候是在start()方法结束之后很快地被调用,而不是在调用start()方法的时候被调用!!
3:两个并发的线程不一定谁的run()方法先执行
九、多线程基础(下)、多线程TCP通信
第一种创建线程的方式有两种不足
-
1:继承冲突
- 由于java是单继承的,
- 这就导致若继承了Thread的类就无法继承其他类来复用方法,
- 这在实际开发中是非常不方便的。
-
2:由于在当前类内部重写run方法,
- 这就导致当前线程与该线程要执行的任务有一个必然的耦合关系,
- 不利于线程复用。
Thread t1=new Thread() {
public void run() {
System.out.println("自定义线程t1执行了!");
//获取线程ID id:唯一标示
long id=this.getId();
System.out.println("t1的ID:"+id);
//获取名字 Thread-xx
String name=this.getName();
System.out.println("t1的名字:"+name);
//获取线程优先级 1-10,默认优先级为5
int priority=this.getPriority();
System.out.println("t1的优先级:"+priority);
//查看当前线程是否为守护线程
boolean isDaemon=this.isDaemon();
System.out.println("t1是否为守护线程:"+isDaemon);
//查看线程是否处于活动状态
boolean isAlive=this.isAlive();
System.out.println("t1是否活着:"+isAlive);
//查看线程是否被中断,一个线程被中断后不能被唤醒,
//即不能再调用start()
boolean isInterrupted=this.isInterrupted();
System.out.println("t1是否被中断了:"+isInterrupted);
}
};
线程提供了一个静态方法
-
static Thread currentThread()
-
该方法可以获取运行该方法的线程
[线程名称、优先级和线程组]
线程优先级
-
线程不能干涉线程调度的工作,即:线程不能主动获取CPU时间片。
-
为了最大程度的改善某个线程获取CPU时间片的次数,
-
可以通过调整线程优先级来完成。
-
理论上线程优先级越高的线程获取CPU时间片的次数越多。
-
线程的优先级有10个等级,分别用整数1-10表示。
-
1最低,10最高,5为默认值。
Thread提供了一个静态方法:
-
static void sleep(long ms)
-
该方法可以将运行该方法的线程阻塞指定毫秒,当超时
-
以后该线程会自动回到RUNNABLE状态等待再次并发运行。
-
常使用该方法作间隔时间等操作使用。
try{
Thread.sleep(5000);
}catch(InterruptedException e) {
e.printStackTrace();
}
守护线程
- 守护线程又称后台线程。默认创建出来的线程都是前台线程。
- 使用上守护线程与前台线程没有区别。
- 而区别在于结束实际上有一点不同,即进程结束。
- 当一个进程中的所有前台线程都结束时,进程结束,
- 无论该进程中的守护线程是否还在运行都要强制将它们结束。
//设置为守护线程,必须在线程启动之前设置。
jack.setDaemon(true);
GC(垃圾回收器)-------守护线程
线程提供了一个方法:join
- 该方法可以协调多个线程之间同步运行。
同步与异步
- 同步执行:执行有先后顺序。(类似串联的电路)
- 异步执行:执行没有顺序,各干个的。(类似并联的电路)
- 多线程运行本身的设计是异步运行的,
- 但在某些业务逻辑中需要他们执行各自任务时要有先后,
- 这时就需要协调这些线程之间同步运行。
当show线程调用download的join方法时,
- show线程处于阻塞状态,直到download线程将任务全部执行完毕后,
- show才会结束阻塞继续执行join方法后续代码。
注意:如果一个方法的局部内部类当中想使用这个方法的其他变量,要求这个变量必 须是final修饰的,不然会报错。(JDK8以下)
若希望可以随时收到服务端转发过来的消息
-
那么就不能让读取服务端消息的代码与给服务端发送消息的代码同步操作,
-
否则互相会受影响。
-
解决办法:异步执行。
-
将循环读取服务端发送过来的消息并输出到控制台的代码放到另一个线程中运行。
十、集合框架、泛型、增强for循环
集合框架
HashSet和HashMap关系?
-
HashSet和HashMap都是根据哈希算法,将对象或键值对进行运算后存到相应的位置。
-
HashSet底层是基于HashMap实现的
-
都是散列存储
TreeSet和TreeMap关系?
-
TreeSet底层是基于TreeMap进行操作的
-
TreeSet和TreeMap都是按照红黑树存储的
-
TreeSet和TreeMap都是有序的
红黑树:
参考:https://zhuanlan.zhihu.com/p/24795143?refer=dreawer
-
每个节点要么是红色,要么是黑色。
-
根节点必须是黑色
-
红色节点不能连续(也即是,红色节点的孩子和父亲都不能是红色)。
-
对于每个节点,从该点至null(树尾端)的任何路径,都含有相同个数的黑色节点。
java提供了一套用于维护一组元素的数据结构解决方案–集合
-
java.util.Collection
-
Collection是所有集合的顶级接口,规定了所有集合都应
-
具备的功能。
Collection 下面派生了两个常用的子接口:
-
List:可重复集并且有序
-
Set:可不重复集
-
重复与否之的时元素是否可以重复,而重复的判断标准
-
时依靠元素自身equals比较的结果。比较为true则认为
-
两个元素是重复元素,Set集合不允许出现重复元素。
boolean add(E e)
- 向当前集合中添加元素,成功添加会返回true
int size()
- 返回当前集合的元素数量
boolean isEmpty();
- 判断当前集合是否为空集(不含有元素)
void clear()
- 清空集合
boolean contains(E e)
- 判断当前集合是否包含给定元素。
判断集合c是否包含p对象
-
集合contains方法判断包含的逻辑为:
-
当前集合元素只要有与给定对象equeals比较,为true的,则认为当前集合包含该元素;
-
作为集合的元素,它的toString,equals方法会影响集合很多方法的结果。
-
所以若集合存放的是我们自己写的类,那么就要妥善重写这两个方法。
(注意:集合存放的是元素的引用地址)
将c2集合中的所有元素存入c1
-
boolean addAll(Collection c)
-
将给定集合的所有元素添加到当前集合中,
-
添加后当前集合元素发生改变则返回true
boolean containsAll(Collection c)
- 判断当前集合是否包含给定集合中的所有元素
boolean removeAll(Collection c)
- 删除当前集合中与给定集合中的共有元素,即删交集。
集合的遍历
-
Collection提供了统一的遍历集合元素的方式:
-
迭代器模式(Iterator)
-
对应方法:Iterator iterator()
-
该方法会返回一个可以遍历当前集合元素的迭代器实例
java.util.Iterator接口
- 该接口规定了迭代器用来遍历集合的相关方法。
- 不同的集合都实现了一个可以用于遍历自身的迭代器实现类。
- 我们无需记住每种集合提供的迭代器实现类的名字,
- 只要将它们以Iterator接口看待,可以调用相关遍历方法达到遍历集合的目的就可以了。
迭代器遍历集合遵循步骤:
- 问,取,删(其中删除不是必须操作)
boolean hasNext()
- 通过迭代器判断集合是否还有元素可以遍历
E next()
- 获取集合中下一个元素
注意:判断String是否相等的时候把已知的字符串放在前面,不然如果str为null就会报空指针异常!
// 使用String的equals方法的时候,非空字符串在前
if ("#".equals(str)) {}
注意:我们在使用迭代器遍历集合的过程当中,不要通过集合的方法增删元素。
迭代器的remove方法可以将通过next方法取出的元素从集合中删除
泛型
JDK1.5后推出的一个新特性
-
泛型又称为参数化类型,允许使用方在实例化对象时,
-
传入具体类型来决定当前类中泛型的实际类型。
泛型是编译器认可
-
泛型可以不指定,若不指定则为原型Object。
-
泛型只是用来告知编译器,让编译器可以在编译时检查
-
实参是否符合泛型要求,当获取一个泛型值时,编译器
-
也会根据泛型的实际类型做自动转换操作。
1. 泛型里面的类型实际上还是Object类型,不过在你赋值的时候会有一个自动装箱的操作,把Object类型包装成相应的类型(比如Integer类型)
范型例子:
public class TestPoint2 {
public static void main(String[] args) {
Point<Integer> p1=new Point<Integer>(1,2);
System.out.println("p1:"+p1);
p1.setX(2);
System.out.println("p1:"+p1);
int x1=p1.getX();
System.out.println("x1:"+x1);
Point p2=p1;//不指定范型则为原型Object
System.out.println("p2:"+p2);
p2.setX("二");
System.out.println("p2:"+p2);
}
}
运行结果1:
注意:
从基本类型int变成Integer包装类的时候,是调用一个静态方法
valueOf(),Integer的valueOf()方法缓存了-128~127之间的数,
如果你使用的是同样的数,会重用同一个对象的。
在程序最后加上这两句:
int x2=p1.getX();
System.out.println("x2:"+x2);
运行结果2:
增强for循环:
-
JDK1.5之后推出的又一个新特征,
-
增强for循环,又称为新循环,forEach
-
新循环并非新的语法,它不是JVM认可的,
-
而是编译器认可的,作用是方便遍历集合或数组。
注意:新循环遍历集合就是用iterator,循环中不能用集合删除元素
编译器在编译源程序时会将新循环遍历集合
- 改为使用迭代器遍历方式。所以新循环在遍历
- 集合时不能通过集合方法增删元素。
ArrayList 和 LinkedList
-
List 接口是 Collection的子接口,用于定义线性表数据结构;
可以将List理解为存放对象的数组,只不过其元素个数可以动态的增加或减少。
-
List接口的两个常见实现类为 ArrayList() 和 LinkedList(),
分别用动态数组和链表的方式实现了List接口。
-
可以认为 ArrayList和 linkeDlist的方法在逻辑上完全一样,
只是在性能上有一定的差别。 ArrayList更适合于随机访问,
而 LinkedList 更适合于插入和删除。
在性能要求不是特别苛刻的情形下可以忽略这个差别。
java.util.List
-
List是Collection的一个常用子接口。
-
List集合的特点是:可重复性,并且有序。
-
List提供了一组独有的方法,特点都是可以通过下标
-
操作元素。(Set集合并不具备这些方法)
void add(int index,E e)
- 将给定元素插入到指定位置
E remove(int index)
-
从集合中删除指定位置的元素
-
返回值为被删除的元素。
E get(int index)
- 获取指定位置上的元素
(所以,List可以通过for循环遍历集合)
E set(int index,E e)
-
将给定元素设置到指定位置,返回值为
-
原位置对应的元素。所以set方法是替换
-
元素操作。
a-z ↩︎