目录
API
#File类
###File可以做到
1.访问其表示的文件或目录的属性信息,例如 : 名字,大小,修改时间等等
2.创建和删除文件或目录
3.访问一个目录中的子项
但是不能访问文件数据
//使用File访问当前项目目录下的demo.txt文件
/*
创建File时要指定路径,而路径通常使用相对路径。
相对路径的好处在于有良好的跨平台性。
"./"是相对路径中使用最多的,表示"当前目录",而当前目录是哪里
取决于程序运行环境而定,在idea中运行java程序时,这里指定的
当前目录就是当前程序所在的项目目录。
*/
// File file = new File("c:/xxx/xxx/xx/xxx.txt");
File file = new File("./demo.txt"); //相当于创建
//获取名字
String name = file.getName();
System.out.println(name);
//获取文件大小(单位是字节)
long len = file.length();
System.out.println(len+"字节");
//是否可读可写
boolean cr = file.canRead();
boolean cw = file.canWrite();
System.out.println("是否可读:"+cr);
System.out.println("是否可写:"+cw);
//是否隐藏
boolean ih = file.isHidden();
System.out.println("是否隐藏:"+ih);
##File方法
createNewFile() :方法,可以创建一个新文件
创建一个新文件
//在当前目录下新建一个文件:test.txt
File file = new File("./test.txt");
//boolean exists()判断当前File表示的位置是否已经实际存在该文件或目录
if(file.exists()){
System.out.println("该文件已存在!");
}else{
file.createNewFile();//将File表示的文件创建出来
System.out.println("文件已创建!");
}
delete() :方法,可以将File表示的文件删除
删除一个文件
//将当前目录下的test.txt文件删除
/*
相对路径中"./"可以忽略不写,默认就是从当前目录开始的。
*/
File file = new File("test.txt");
if(file.exists()){
file.delete();
System.out.println("文件已删除!");
}else{
System.out.println("文件不存在!");
}
mkDir() :创建当前File表示的目录
mkDirs() :创建当前File表示的目录,同时将所有不存在的父目录一同创建
创建一个新目录
//在当前目录下新建一个目录:demo
//File dir = new File("demo"); //只能创建一个目录
File dir = new File("./a/b/c/d/e/f"); //可以连带父级目录一起创建出来
if(dir.exists()){
System.out.println("该目录已存在!");
}else{
//dir.mkdir();//创建目录时要求所在的目录必须存在
dir.mkdirs();//创建目录时会将路径上所有不存在的目录一同创建
System.out.println("目录已创建!");
}
delete() :方法可以删除一个目录,但是只能删除空目录
删除一个目录
//将当前目录下的demo目录删除
File dir = new File("demo");
//File dir = new File("a");
if(dir.exists()){
dir.delete();//delete方法删除目录时只能删除空目录
System.out.println("目录已删除!");
}else{
System.out.println("目录不存在!");
}
listFiles :方法可以访问一个目录中的所有子项
访问目录中的所有子项
//获取当前目录中的所有子项
File dir = new File(".");
/*
boolean isFile()
判断当前File表示的是否为一个文件
boolean isDirectory()
判断当前File表示的是否为一个目录
*/
if(dir.isDirectory()){
/*
File[] listFiles()
将当前目录中的所有子项返回。返回的数组中每个File实例表示其中的一个子项
*/
File[] subs = dir.listFiles();
System.out.println("当前目录包含"+subs.length+"个子项");
for(int i=0;i<subs.length;i++){
File sub = subs[i];
System.out.println(sub.getName());
}
}
重载的listFiles方法 :File[] listFiles(FileFilter)
该方法要求传入一个文件过滤器,并将满足该过滤器要求的子项返回
筛选符合条件的子项
/*
需求:获取当前目录中所有名字以"."开始的子项
*/
File dir = new File(".");
if(dir.isDirectory()){
// FileFilter filter = new FileFilter(){//匿名内部类创建过滤器
// public boolean accept(File file) {
// String name = file.getName();
// boolean starts = name.startsWith(".");//名字是否以"."开始
// System.out.println("过滤器过滤:"+name+",是否符合求:"+starts);
// return starts;
// }
// };
// File[] subs = dir.listFiles(filter);//方法内部会调用accept方法
File[] subs = dir.listFiles(new FileFilter(){
public boolean accept(File file) {
return file.getName().startsWith(".");
}
});
System.out.println(subs.length);
}
/**
* JDK8之后java支持了lambda表达式这个特性
* lambda表达式可以用更精简的语法创建匿名内部类,但是实现的接口只能有一个抽象
* 方法,否则无法使用。
* lambda表达式是编译器认可的,最终会被改为内部类形式编译到class文件中。
*
* 语法:
* (参数列表)->{
* 方法体
* }
*/
//匿名内部类形式创建FileFilter
FileFilter filter = new FileFilter() {
public boolean accept(File file) {
return file.getName().startsWith(".");
}
};
FileFilter filter2 = (File file)->{
return file.getName().startsWith(".");
};
//lambda表达式中参数的类型可以忽略不写
FileFilter filter3 = (file)->{
return file.getName().startsWith(".");
};
/*
lambda表达式方法体中若只有一句代码,则{}可以省略
如果这句话有return关键字,那么return也要一并省略!
*/
FileFilter filter4 = (file)->file.getName().startsWith(".");
File[] subs = dir.listFiles(filter4 ); //返回当前目录下的所有子项
System.out.println("共:"+subs.length+"个子项"); //输出子项
for (int i = 0; i < subs.length; i++) { //普通for循环遍历输出
File sub = subs[i];
System.out.println(sub.getName());
}
for (File a : subs){ //增强for循环遍历输出
System.out.println(a.getName());
}
IO流
介绍:
java io可以让我们用标准的读写操作来完成对不同设备的读写数据工作
java将IO按照方向划分为输入与输出,参照点是我们写的程序
输入: 用来读取数据的,是从外界到程序的方向,用于获取数据 Input
输出: 用来写出数据,是从程序到外界的方向,用于发送数据 Output
java将IO比作"流",即: stream.以同一个方向顺序移动的过程,流动的是字节(2进制数据)
###### Java定义了超类(抽象类)
java将流按照读写单位划分为字节流与字符流
java.io.IntputStream 和 java.io.OutputStream 是所有字节流的超类
java.io.Reader 和 java.io.Writer 则是所有字符流的超类,它们是平级关系
###字节流:
java.io.IntputStream : 所有字节输入流的超类,其中定义了读取数据的方法
java.io.OutputStream : 所有字节输出流的超类,其中定义了写出数据的方法(往文件中写入数据的过程,从自己的方向 向 外面输出数据.叫写出)
属于字节流的
文件流 : FileInputStream/OutputStream (低级流)
缓冲流 : BufferedInputStream/OutputStream (高级流)加快读写
对象流 : ObjectInputStream/OutputSteam (高级流) 反/序 列化
###字符流:
java.io.Reader : 所有字符输入流的超类
java.io.Writer : 所有字符输出流的超类
字符流没有低级流
属于字符流的
转换流 : InputStreamReader/OutputStreamWriter (高级流字符字节转换输入/输出流)
缓冲流 : BufferedReader/PrintWriter (高级流缓冲输入/输出流)
###### Java将流分为两类: 节点流与处理流
节点流 : 也称为低级流,节点流的另一端是明确的,是实际读写数据的流,读写一定是建立在节点流基础上进行的.
处理流 : 也称高级流. 处理流不能单独存在,必须连接在其他流上,目的是当数据流经当前流时对数据进行加工处理来简化我们对数据的该操作
#######################
在实际应用中,我们可以通过串联一组高级流到某个低级流上以流水线式的加工处理对某设备的数据进行读写,这个过程也称为流的连接,这也是IO的精髓所在
#######################
# 文件流(字节流)
void write(int d) : 向文件中写入一个字节,写入的内容是给定的int值对应的2进制的"低/后八位"
void close() : 在向文件中写入数据结束后,调用关闭流
int read() : 读取一个字节,并以int型返回,返回的整数中读取的字节部分在该2进制的最后8位上.如果返回的是-1,则表示读取到了文件末尾
文件的复制
void write(byte[] data) 一次性将给定的字节数组所有字节写入到文件中,比源文件大一点
void write(byte[] data,int offset,int len) 一次性将给定的字节数组从下标offset处开始的连续len个字节写入文件
文件的写入
String提供方法: byte[] getBytes(String charsetName) 将当前字符串转换为一组字节
常用的是UTF-8
##文件输入输出流
文件流是一对低级流,用于读写文件数据的流.用于连接程序与文件(硬盘)的"管道",负责读写文件数据
/** * JAVA IO Input & Output 输入和输出 * java程序与外界交换数据是基于IO完成的,这里输入与输出一个负责读一个负责写 * * 输入:是从外界到我们写的程序的方向,是用来从外界获取信息的。因此是"读"操作 * 输出:是从我们写的程序到外界的方向,是用来向外界发送信息的。因此是"写"操作 * * java将IO比喻为"流",可以理解为是程序与外界连接的"管道",内部流动的是字节,并且 * 字节是顺着同一侧方向顺序移动的。 * * java.io.InputStream 输入流,这个类是所有字节输入流的超类,规定了所有字节输入 * 流读取数据的相关方法。 * java.io.OutputStream 输出流,这个类是所有字节输出流的超类,规定了所有字节输出 * 流写出数据的相关方法。 * * 实际应用中,我们连接不同的设备,java都专门提供了用于连接它的输入流与输出流,而 * 这些负责实际连接设备的流称为节点流,也叫低级流。是真实负责读写数据的流。 * 与之对应的还有高级流,高级流可以连接其他的流,目的是当数据流经它们时,对数据做某 * 种加工处理,用来简化我们的操作。 * * * 文件流 * java.io.FileInputStream和FileOutputStream * 这是一对低级流,继承自InputStream和OutputStream。用于读写硬盘上文件的流 * */
###文件输出流 : java.io.FileOutputStream
//向当前目录下的demo.dat文件中写入数据
/*
FileOutputStream提供的常用构造器
FileOutputStream(String path)
FileOutputStream(File file)
*/
//文件流创建时,如果该文件不存在会自动将其创建(前提是该文件所在目录必须存在!)
FileOutputStream fos = new FileOutputStream("./demo.dat");
/*
void write(int d)
向文件中写入1个字节,写入的内容是给定的int值对应的2进制的"低八位"
int值 1: vvvvvvvv
二进制:00000000 00000000 00000000 00000001
demo.dat文件内容:
00000000
*/
fos.write(1);
/*
vvvvvvvv
00000000 00000000 00000000 0000001
demo.dat文件内容
00000001 00000010
*/
fos.write(2);
fos.close();
System.out.println("执行完了!");
###文件输入流 : java.io.FileInputStream
文件字节输入流,用于从文件中读取字节
/*
fos.dat文件内容
00000001 00000011
*/
FileInputStream fis = new FileInputStream("fos.dat");
/*
int read()
读取一个字节,并以int型返回。返回的整数中读取的字节部分在该整数2进制的最后8位上
如果返回值为整数-1,则表示流读取到了末尾。对于读取文件而言就是EOF(end of file文件末尾)
第一次调用read():
int d = fis.read();
fos.dat文件内容
00000001 00000011
^^^^^^^^
读取该字节
返回int值时,2进制样子:
00000000 00000000 00000000 00000001
^^^^^^^^
|-----补充24个0(3字节)-----| 读取的字节
返回的int值d就是上述内容
*/
int d = fis.read();
System.out.println(d); //输出第一读取内容
/*
第二次调用read()
d = fis.read();
fos.dat文件内容
00000001 00000011
^^^^^^^^
读取该字节
返回int值时,2进制样子:
00000000 00000000 00000000 00000011
^^^^^^^^
|-----补充24个0(3字节)-----| 读取的字节
返回的int值d就是上述内容
*/
d = fis.read();
System.out.println(d);
/*
第三次调用read()
d = fis.read();
fos.dat文件内容
00000001 00000011
^^^^^^^^
文件末尾了
返回int值时,2进制样子:
11111111 11111111 11111111 11111111
^^^^^^^^
|-----补充32个1(4字节,来表示-1)-----|
返回的int值d就是上述内容
*/
d = fis.read();
System.out.println(d); //
fis.close(); //关闭流
##单字节文件复制操作
//创建文件输入流读取原文件
FileInputStream fis = new FileInputStream("image.jpg");
//创建文件输出流写入复制文件
FileOutputStream fos = new FileOutputStream("image_cp.jpg");
int d;//保存每次读取到的字节
/*
原文件数据:
11000011 10101010 00001111 11001100 00110011 ...
^^^^^^^^
d = fis.read();
d:00000000 00000000 00000000 10101010
fos.write(d);
复制文件的数据:
11000011 10101010
*/
long start = System.currentTimeMillis();//获取当前系统时间的毫秒值(UTC时间)
while((d = fis.read()) != -1) {
fos.write(d);
}
long end = System.currentTimeMillis();//获取当前系统时间的毫秒值(UTC时间)
System.out.println("复制完毕!耗时:"+(end-start)+"ms"); //用于计算使用了多长时间
fis.close();
fos.close(); //用完关流
文件的复制有可能会比源文件大一点
##块读写文件复制操作
int read(byte[] data)
一次性从文件中读取给定的字节数组总长度的字节量,并存入到该数组中.
返回值为实际读取到的字节量,若返回值为-1则表示读取到了文件末尾
###块写操作
void write(byte[] data)
一次性将给定的字节数组所有字节写入到文件中
void write(byte[] data,int offset.int len)
一次性将给定的字节数组从下标offset处开始的连续len字节写入文件
/** * 通过提高每次读写的数据量,减少实际读写的次数,可以提高读写效率。 * 单字节读写是一种随机读写形式。而一组一组字节的读写是块读写形式。 */
###单字节复制
FileInputStream fis = new FileInputStream("fos.txt");
FileOutputStream fos = new FileOutputStream("fos_cp.txt");
byte[] data = new byte[1024*10];
//会比源文件大,超出范围大小不会超过 10KB
fos.write(data);
fos.close();
fis.close();
System.out.println("写出完毕");
//与下面的块读写复制形成比较
//使用块读写形式完成文件复制
//创建文件输入流读取原文件
FileInputStream fis = new FileInputStream("wnwb.exe");
//创建文件输出流写复制文件
FileOutputStream fos = new FileOutputStream("wnwb_cp.exe");
/*
流提供了块读写的方法
int read(byte[] data)
一次性从文件中读取给定的字节数组总长度的字节量,并存入到该数组中。
返回值为实际读取到的字节量。若返回值为-1则表示读取到了文件末尾。
文件数据
11001100 11110000 10101010 00001111 00110011
^^^^^^^^ ^^^^^^^^ ^^^^^^^^
int d;
byte[] data = new byte[3];
[00000000 00000000 00000000]
第一次调用
d = fis.read(data);
[11001100 11110000 10101010]
d = 3 本次读取到了3个字节
文件数据
11001100 11110000 10101010 00001111 00110011
^^^^^^^^ ^^^^^^^^
第二次调用
d = fis.read(data);//仅读取了最后两个字节
[00001111 00110011 10101010]//前两个字节为本次读取的内容
^^^^^^^^ ^^^^^^^^
d = 2 本次读取到了2个字节
文件数据
11001100 11110000 10101010 00001111 00110011 文件末尾!
^^^^^^^^
第三次调用
d = fis.read(data);//一个字节都没有读取到
[00001111 00110011 10101010]数组没变化
d = -1 文件末尾
块写操作
void write(byte[] data)
一次性将给定的字节数组所有字节写入到文件中
void write(byte[] data,int offset,int len)
一次性将给定的字节数组从下标offset处开始的连续len个字节写入文件
*/
int len;//记录每次实际读取的字节量
/*
00000000 1byte 8位2进制称为1字节
1024byte 1kb
1024kb 1mb
1024mb 1gb
*/
byte[] data = new byte[1024*10];//10kb
long start = System.currentTimeMillis();//开始时间
while((len = fis.read(data))!=-1){
fos.write(data,0,len);//读取多少就写多少
}
long end = System.currentTimeMillis(); //结束时间
System.out.println("复制完毕!耗时:"+(end-start)+"ms");
fis.close();
fos.close();
文件的块读写操作会和源文件一样大小
##写出文件数据
String提供方法:
byte[] getBytes(String charsetName)
将当前字符串替换为一组字节
参数为字符集的名字,常用的是UTF-8 .其中 中文字3字节表示一个汉字,英文1字节表示一个字母
/** * 向文件中写入文本数据 */
###文件输出流
FileOutputStream fos = new FileOutputStream("demo.txt");
String str = "super idol的笑容都没你的甜,";
/*
支持中文的常见字符集有:
GBK:国标编码。英文每个字符占1个字节,中文每个字符占2个字节
UTF-8:内部是unicode编码,在这个基础上不同了少部分2进制信息作为长度描述
英文每个字符占1字节
中文每个字符占3字节
String提供了将字符串转换为一组字节的方法
byte[] getBytes(String charsetName)
参数为字符集的名字,名字不缺分大小写,但是拼写错误会引发异常:
UnsupportedEncodingException
不支持 字符集 异常
*/
byte[] data = str.getBytes("UTF-8");
fos.write(data);
fos.write("八月正午的阳光,都没你耀眼。".getBytes("UTF-8"));//以UTF-8转换为字节写入文件中
System.out.println("写出完毕!");
fos.close();
###文件输出流-追加模式
重载的构造方法可以将文件输出流创建为追加模式
当第二个参数传入true时,文件流为追加模式,即:指定的文件若存在,则原有数据保留,新写入的数据会被顺序的追加到文件中
- FileOutputStream(String path,boolean append)
- FileOutputStream(File file,boolean append)
/*
FileOutputStream默认创建方式为覆盖模式,即:如果连接的文件存在,
则会将该文件原有数据全部删除。然后将通过当前流写出的内容保存到文件中。
重载的构造方法允许我们再传入一个boolean型参数,如果这个值为true,则
文件流为追加模式,即:若连接文件时该文件存在,原有数据全部保留,通过当前
流写出的数据会顺序的追加到文件中。
*/
FileOutputStream fos = new FileOutputStream("demo.txt",true);
fos.write("热爱105°的你,".getBytes("UTF-8"));
fos.write("滴滴清纯的蒸馏水。".getBytes("UTF-8"));
System.out.println("写出完毕!");
fos.close();
//这样写可以实现将 "" 中数据连续输入文件中,
//不会将上一次的数据摸除,再进行下一次写出
##读取文本数据
/** * 从文件中读取文本数据 */
FileInputStream fis = new FileInputStream("fos.txt");
byte[] data = new byte[1024];
int len = fis.read(data);//块读操作
System.out.println("实际读取到了"+len+"个字节");
/*
String提供了将字节数组转换为字符串的构造方法:
String(byte[]data,String charsetName)
将给定的字节数组中所有字节按照指定的字符集转换为字符串
String(byte[]data,int offset,int len,String charsetName)
将给定的字节数组从下标offset处开始的连续len个字节按照指定的字符集转换为字符串
*/
String line = new String(data,0,len,"UTF-8");
System.out.println(line);
System.out.println(line.length());
fis.close();
读取和写入时需要加上编码格式 ,常用的是"UTF-8"
复制情况不需要加
#高级流/处理流
缓冲流
# 使用缓冲流完成文件复制操作 Buffered :缓冲输入(Input)/输出(Output)
# 缓冲输出流写出数据时的缓冲区问题 : fiush()//冲刷
对象流 :
必须实现可序列化接口Serializable ,被transient修饰的属性在序列化时忽略
#使用对象输出流完成对象的序列化 : ObjectInput , writeObject()
#使用对象输入流完成对象的反序列化 : ObjectOutput , readObject()
介绍:
缓冲流:java.io.BufferedOutputStream 和 java.io.BufferredInputStream
缓冲流是一对高级流,作用是提高读写数据的效率
缓冲流内部有一个字节数组,默认长度是8K,缓冲流读写数据时一定是将数据的读写方式转换为块读写来保证读写效率
##缓冲流
### 使用缓冲流完成文件复制操作
/** * java将流分为节点流与处理流两类 * 节点流:也称为低级流,是真实连接程序与另一端的"管道",负责实际读写数据的流。 * 读写一定是建立在节点流的基础上进行的。 * 节点流好比家里的"自来水管"。连接我们的家庭与自来水厂,负责搬运水。 * 处理流:也称为高级流,不能独立存在,必须连接在其他流上,目的是当数据经过当前流时 * 对其进行某种加工处理,简化我们对数据的同等操作。 * 高级流好比家里常见的对水做加工的设备,比如"净水器","热水器"。 * 有了它们我们就不必再自己对水进行加工了。 * 实际开发中我们经常会串联一组高级流最终连接到低级流上,在读写操作时以流水线式的加工 * 完成复杂IO操作。这个过程也称为"流的连接"。 * * 缓冲流,是一对高级流,作用是加快读写效率。 * java.io.BufferedInputStream和java.io.BufferedOutputStream * */
FileInputStream fis = new FileInputStream("ppt.pptx"); //字节低级流,读取
BufferedInputStream bis = new BufferedInputStream(fis); //字节缓冲流
FileOutputStream fos = new FileOutputStream("ppt_cp.pptx"); //字节低级流,写出
BufferedOutputStream bos = new BufferedOutputStream(fos); //字节高级流,代替了块读写的操作
int d;
long start = System.currentTimeMillis();
while((d = bis.read())!=-1){//使用缓冲流读取字节
bos.write(d);//使用缓冲流写出字节
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)+"ms");
bis.close();//关闭流时只需要关闭高级流即可,它会自动关闭它连接的流
bos.close();
### 缓冲输出流写出数据时的缓冲区问题
/** * 缓冲输出流写出数据的缓冲区问题 */
FileOutputStream fos = new FileOutputStream("bos.txt"); //创建一个名为bos的txt文件
BufferedOutputStream bos = new BufferedOutputStream(fos); //字节缓冲输出流
String line = "奥里给!";
byte[] data = line.getBytes(StandardCharsets.UTF_8); //缓冲块写入
bos.write(data);
System.out.println("写出完毕!");
/*
缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。
注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲
输出流有这个方法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现
该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。
*/
bos.flush();//冲刷缓冲区内的数据
bos.close();//关闭流
##对象流/序列/反序列化
需要进行序列化的类必须实现接口:java.io.Serializable 实现序列化接口后最好主动定义序列化版本号这个常量。 这样一来对象序列化时就不会根据类的结构生成一个版本号,而是使用该固定值。 那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。 transient关键字可以修饰属性,用于在进行对象序列化时忽略不必要的属性,达到对象瘦身的目的
java.io.ObjectOutputStream和ObjectInputSteam 对象流是一对高级流,在流连接中的作用是进行对象的序列化与反序列化。 对象序列化:将一个java对象按照其结构转换为一组字节的过程 对象反序列化:将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节) 对于反/序列化简而言之: 序列化 :将一个java对象按照其结构转换为一组字节的过程,并永久存入磁盘 反序列化 :将序列化后的字节还原为java对象
### 对象序列化
/** * 使用对象输出流完成对象的序列化 */
/*
将一个Person对象写入文件,
1.先将Person对象转换为一组字节
2.将字节写入文件
流连接:
序列化 持久化
V V
对象------>对象流------>文件流------>文件
*/
//将一个Person对象写入文件person.obj
String name = "苍老师";
int age = 18;
String gender = "女";
String[] otherInfo = {"是一名的演员","爱好写大字","老师"};
Person p = new Person(name,age,gender,otherInfo); //Person实现了可序列化接口
//Serializable
System.out.println(p);
FileOutputStream fos = new FileOutputStream("person.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
/*
对象输出流提供了序列化方法
void writeObject(Object obj)
将给定的对象转换为一组字节并写出,但是需要注意:写出对象所属的类必须
实现接口import java.io.Serializable;
否则该方法会抛出异常java.io.NotSerializableException
*/
oos.writeObject(p);
System.out.println("写出完毕!");
oos.close();
### 对象反序列化
/** * 使用对象输入流完成对象的反序列化 */
//从person.obj文件中将对象反序列化回来
FileInputStream fis = new FileInputStream("person.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
/*
Object readObject()
该方法会进行对象的反序列化,如果对象流通过其连接的流读取的字节分析并非
是一个java对象时,会抛出异常:ClassNotFoundException
*/
Person p = (Person)ois.readObject();
System.out.println(p);
#字符流
### 字符流
- java将流按照读写单位划分为字节流与字符流. - java.io.InputStream和OutputStream是所有字节流的超类 - 而java.io.Reader和Writer则是所有字符流的超类,它们和字节流的超类是平级关系. - Reader和Writer是两个抽象类,里面规定了所有字符流都必须具备的读写字符的相关方法. - 字符流最小读写单位为字符(char),但是底层实际还是读写字节,只是字符与字节的转换工作由字符流完成.
##字符转换流
#### 转换流 java.io.InputStreamReader和OutputStreamWriter 它们是字符流非常常用的一对实现类同时也是一对高级流,实际开发中我们不直接操作它们,但是它们在流连接中是非常重要的一环.
###转换输出流向文件中写入文本数据
/** * 转换字符输出流 * 可以将读取的字符按照指定的字符集转换为字节 */
//向文件osw.txt中写入文字
FileOutputStream fos = new FileOutputStream("osw.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
//创建转换流时,可以通过第二个参数来指定字符集
//字符流的write方法可以直接写出字符串,无需再手动转换为字节
osw.write("我可以接受你的所有,所有小脾气.");
osw.write("我可以带你去吃很多,很多好东西.");
/*
String name = "张张张张张张三三三三三";
osw.write(name);
也可以
*/
System.out.println("写出完毕!");
osw.close();
###转换输入流读取文本文件
/** * 转换字符输入流 * 可以将读取的字节按照指定的字符集转换为字符 */
//将osw.txt文件中的所有文字读取回来.
FileInputStream fis = new FileInputStream("osw.txt"); //找到文件
InputStreamReader isr = new InputStreamReader(fis,"UTF-8"); //以UTF-8转换为字符
/*
字符流读一个字符的read方法定义:
int read()
读取一个字符,返回的int值实际上表示的是一个char(低16位有效).如果返回的
int值表示的是-1则说明EOF
*/
//测试读取文件中第一个字
// int d = isr.read();
// char c = (char)d;
// System.out.println(c);//只能拿到的第一个字节,并输出
//循环将文件所有字符读取回来
int d;
while((d = isr.read()) != -1){
System.out.print((char)d);
}
isr.close();
##字符缓冲流
##### 转换流的意义: 实际开发中我们还有功能更好用的字符高级流.但是其他的字符高级流都有一个共通点:不能直接连接在字节流上.而实际操作设备的流都是低级流同时也都是字节流.因此不能直接在流连接中串联起来.转换流是一对可以连接在字节流上的字符流,其他的高级字符流可以连接在转换流上.在流连接中起到"转换器"的作用(负责字符与字节的实际转换)
###缓冲字符输出流向文件中写入文本文件
#### 缓冲字符流 ##### 缓冲字符输出流:java.io.PrintWriter java.io.BufferedWriter和BufferedReader 缓冲字符流内部也有一个缓冲区,读写文本数据以块读写形式加快效率.并且缓冲流有一个特别的功能:可以按行读写文本数据. java.io.PrintWriter具有自动行刷新的缓冲字符输出流,实际开发中更常用.它内部总是会自动连接BufferedWriter作为块写加速使用.
/*
PrintWriter提供了对文件操作的构造方法:
PrintWriter(String path)
PrintWriter(File file)
*/
//向文件中写入字符串
PrintWriter pw = new PrintWriter("pw.txt","UTF-8");
pw.println("我看过沙漠下暴雨");
pw.println("看过大海亲吻鲨鱼");
pw.println("看过黄昏追逐黎明");
pw.println("没看过你");
System.out.println("写出完毕!");
pw.close();
//无法实时向文件中写入文本文件
###缓冲字符输出流自动行刷新功能
/** * 在流连接中使用PW */
##### PrintWriter的自动行刷新功能 如果实例化PW时第一个参数传入的是一个流,则此时可以再传入一个boolean型的参数,此值为true时就打开了自动行刷新功能。 即: 每当我们用PW的println方法写出一行字符串后会自动flush.或者不加true,在调用println后调用filsh ,没有直接加true效果好
//支持追加内容,但是不支持实时将数据写入到文件中
// FileOutputStream fos = new FileOutputStream("pw2.txt");
// //创建转换流时指定字符集
// OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
// BufferedWriter bw = new BufferedWriter(osw);
// PrintWriter pw = new PrintWriter(bw);
//
// int a = 0;
// while (a<10){
// pw.println("张三");
// a++;
//
// }
// System.out.println("输出完毕");
//
// pw.close();
//如下,支持追加,也支持实时将数据写入到文件中
//文件字节输出流(是一个低级流),向文件中写入字节数据
FileOutputStream fos = new FileOutputStream("pw2.txt",true);
//转换输出流(是一个高级流,且是一个字符流)。1:衔接字符与字节流 2:将写出的字符转换为字节
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
//缓冲输出流(是一个高级流,且是一个字符流)。块写文本数据加速
BufferedWriter bw = new BufferedWriter(osw);
/*
PrintWriter提供的构造器中,当第一个参数为一个流时,就支持再传入一个boolean
型的参数表示是否自动行刷新。当该值为true时则打开了自动行刷新功能。这意味着
每当我们调用println方法后会自动flush一次。
*/
PrintWriter pw = new PrintWriter(bw,true);//加true支持追加内容
//完成简易记事本。控制台输入的每行字符串都按行写入文件。单独输入exit时退出。
Scanner scan = new Scanner(System.in);
while (true){
String q = scan.nextLine();
// int p = scan.nextInt();
if (("exit".equalsIgnoreCase(q))){ //忽略大小写
// if (("exit".equals(q))){
break;
}
pw.println(q);
//pw.flush(); //实时存入每一行输入的内容 .和加true作用是一样的
}
System.out.println("输出完毕");
pw.close();
###缓冲字符输入流按行读取字符串
/** * 使用java.io.BufferedReader按行读取文本数据 */
//将当前源程序读取出来并输出到控制台上
FileInputStream fis = new FileInputStream("./src/io/BRDemo.java");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line;
/*
BufferedReader提供了一个读取一行字符串的方法:
String readLine()
该方法会返回一行字符串,返回的字符串不含有最后的换行符。
当某一行是空行时(该行内容只有一个换行符)则返回值为空字符串。
如果流读取到了末尾,则返回值为null。
*/
while((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
#IO总结
异常处理
##java异常处理机制
- java中所有错误的超类为:Throwable。其下有两个子类:Error和Exception - Error的子类描述的都是系统错误,比如虚拟机内存溢出等。 - Exception的子类描述的都是程序错误,比如空指针,下表越界等。 - 通常我们程序中处理的异常都是Exception。
### 异常处理机制中的try-catch
/** * 异常处理机制中的try-catch * 语法: * try{ * 可能出现异常的代码片段 * }catch(XXXException e){ * try中出现XXXException后的处理代码 * } * * try语句块不能独立存在,后面必须跟catch语句块或finally语句块 */
System.out.println("程序开始了");
try {
//String line = null;
//String line = "";
String line = "abc";
//当JVM执行程序出现了某个异常时就会实例化这个异常并将其抛出
//如果该异常没有被异常处理机制控制,则JVM会将异常隐式抛出当方法外(这里是main方法外)
System.out.println(line.length());
System.out.println(line.charAt(0));
System.out.println(Integer.parseInt(line));
//若try语句块中某句话出错了,则剩下的代码都不会执行!
System.out.println("!!!!!!!!!!!!!!!!");
//}catch(NullPointerException e){
// System.out.println("出现了空指针!");
//catch可以定义多个,当try中不同的异常有不同处理办法时可分开捕获并处理
//}catch(StringIndexOutOfBoundsException e){
//System.out.println("出现了下标越界!");
//若某些异常的处理方式相同时,可以合并在一个catch来处理
}catch(NullPointerException|StringIndexOutOfBoundsException e){
System.out.println("出现了空指针或下标越界并处理了!");
//可以在下面catch超类异常来捕获并处理这一类异常。
}catch(Exception e){
System.out.println("反正就是出了个错");
}
System.out.println("程序结束了");
### 异常处理机制中的finally
- finally块定义在异常处理机制中的最后一块。它可以直接跟在try之后,或者最后一个catch之后。 - finally可以保证只要程序执行到了try语句块中,无论try语句块中的代码是否出现异常,最终finally都必定执行。 - finally通常用来做释放资源这类操作。关比输入输出流等操作
/** * 异常处理机制中的finally块 * finally块定义在异常处理机制中的最后一块。它可以直接跟在try之后,或者最后一个catch之后。 * * finally可以保证只要程序执行到了try语句块中,无论try语句块中的代码是否出现异常,最终 * finally都必定执行。 * * finally通常用来做释放资源这类操作。 */
System.out.println("程序开始了...");
try{
String line = "abc";
//String line = null;
System.out.println(line.length());
return;
}catch(Exception e){
System.out.println("出错了!");
}finally{
System.out.println("finally中的代码执行了!");
}
System.out.println("程序结束了!");
### IO操作时的异常处理机制应用
/** * IO操作时的异常处理机制应用 */
FileOutputStream fos = null;
try {
fos = new FileOutputStream("fos.dat");
fos.write(1);
} catch (IOException e) {
e.printStackTrace();//向控制台输出当前异常的错误信息
} finally {
try {
if (fos!=null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
### 自动关闭特性
JDK7之后,java提供了一个新的特性:自动关闭。旨在IO操作中可以更简洁的使用异常处理机制完成最后的close操作。语法: try( 定义需要在finally中调用close()方法关闭的对象. ){ IO操作 }catch(XXXException e){ ... }public class AutocloseableDemo { public static void main(String[] args) { try( FileOutputStream fos = new FileOutputStream("fos.dat"); ){ fos.write(1); } catch (IOException e) { e.printStackTrace();//向控制台输出当前异常的错误信息 } } }
上述语法中可在try的"()"中定义的并初始化的对象必须实现了java.io.AutoCloseable接口,否则编译不通过.
上述代码是编译器认可的,而不是虚拟机。编译器在编译上述代码后会在编译后的class文件中改回成IO操作时的异常处理机制应用案例的代码样子(上次课最后的案例)。
## throw、throws关键字
###throw关键字
throw用来对外主动抛出一个异常,通常下面两种情况我们主动对外抛出异常: - 1:当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。 - 2:程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者。
/** * 测试异常的抛出 */
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) throws Exception {
if(age<0||age>100){
//使用throw对外抛出一个异常
throw new RuntimeException("年龄不合法!");
}
this.age = age;
}
### throws关键字
当一个方法中使用throw抛出一个非RuntimeException的异常时,就要在该方法上使用throws声明这个异常的抛出。此时调用该方法的代码就必须处理这个异常,否则编译不通过。
/** * 测试异常的抛出 */
public class Person {
private int age;
public int getAge() {
return age;
}
/**
* 当一个方法使用throws声明异常抛出时,调用此方法的代码片段就必须处理这个异常
*/
public void setAge(int age) throws Exception {
if(age<0||age>100){
//使用throw对外抛出一个异常
// throw new RuntimeException("年龄不合法!");
//除了RuntimeException之外,抛出什么异常就要在方法上声明throws什么异常
throw new Exception("年龄不合法!");
}
this.age = age;
}
}
当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须处理这个异常,否则编译不通过。 处理手段有两种: - 使用try-catch捕获并处理这个异常 - 在当前方法(本案例就是main方法)上继续使用throws声明该异常的抛出给调用者解决。 具体选取那种取决于异常处理的责任问题。
/** * throw关键字,用于主动对外抛出一个异常 */
public class ThrowDemo {
public static void main(String[] args){
System.out.println("程序开始了...");
try {
Person p = new Person();
/*
当我们调用一个含有throws声明异常抛出的方法时,编译器要求
我们必须添加处理异常的手段,否则编译不通过.而处理手段有两种
1:使用try-catch捕获并处理异常
2:在当前方法上继续使用throws声明该异常的抛出
具体用哪种取决于异常处理的责任问题
*/
p.setAge(100000);//典型的符合语法,但是不符合业务逻辑要求
System.out.println("此人年龄:"+p.getAge()+"岁");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序结束了...");
}
}
注 意 :永远不应当在main方法上使用throws!!
### 含有throws的方法被子类重写时的规则
/** * 子类重写超类含有throws声明异常抛出的方法时对throws的几种特殊的重写规则 */
//声明一个抛出异常的类
public class ThrowsDemo {
public void dosome()throws IOException, AWTException {}
}
class SubClass extends ThrowsDemo{
//抛出完全相同的异常是可以的
// public void dosome()throws IOException, AWTException {}
//可以不再抛出任何异常
// public void dosome(){}
//可以仅抛出部分异常
// public void dosome()throws IOException {}
//可以抛出超类方法抛出异常的子类型异常
// public void dosome()throws FileNotFoundException {}
//不允许抛出额外异常(超类方法中没有的,并且没有继承关系的异常)
// public void dosome()throws SQLException {}
//不可以抛出超类方法抛出异常的超类型异常
// public void dosome()throws Exception {}
}
##异常分类
#### Java异常可以分为可检测异常,非检测异常: - 可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则,不捕捉这个异常,编译器就通不过,不允许编译 - 非检测异常:非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常 - RuntimeException 类属于非检测异常,因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。 #### 常见的RuntimeException子类 - IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数 - NullPointerException:当应用程序试图在需要对象的地方使用 null 时,抛出该异常 - ArrayIndexOutOfBoundsException:当使用的数组下标超出数组允许范围时,抛出该异常 - ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常 - NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
### 异常中常用的方法
/** * 异常常见的方法 */
System.out.println("程序开始了");
try {
String str = "abc";
System.out.println(Integer.parseInt(str));
} catch (NumberFormatException e) {
//异常最常用的方法,用于将当前错误信息输出到控制台
e.printStackTrace();
//获取错误消息.记录日志的时候或提示给用户可以使用它
String message = e.getMessage();
System.out.println(message);
}
System.out.println("程序结束了");
###自定义异常
自定义异常通常用来定义那些业务上的异常问题。 定义自定义异常需要注意以下问题: - 异常的类名要做到见名知义 - 需要是Exception的子类 - 提供超类异常提供的所有种类构造器
/** * 非法的年龄异常 * * 自定义异常通常用来说明业务上的错误. * 自定义异常要注意以下问题: * 1:定义的类名要做到见名知义 * 2:必须是Exception的子类 * 3:提供Exception所定义的所有构造方法 */
/**
* 非法的年龄异常
*
* 自定义异常通常用来说明业务上的错误.
* 自定义异常要注意以下问题:
* 1:定义的类名要做到见名知义
* 2:必须是Exception的子类
* 3:提供Exception所定义的所有构造方法
*/
public class IllegalAgeException extends Exception{
//必须是所有构造器,按Ait+Ins键自动生成,如果出现异常,会自动调用其中一个
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);
}
}
```
```java
package exception;
/**
* 测试异常的抛出
*/
public class Person {
private int age;
public int getAge() {
return age;
}
/**
* 当一个方法使用throws声明异常抛出时,调用此方法的代码片段就必须处理这个异常
*/
public void setAge(int age) throws IllegalAgeException {
if(age<0||age>100){
//使用throw对外抛出一个异常
// throw new RuntimeException("年龄不合法!");
//除了RuntimeException之外,抛出什么异常就要在方法上声明throws什么异常
// throw new Exception("年龄不合法!");
//抛出自定义异常
throw new IllegalAgeException("年龄超范围:"+age);
}
this.age = age;
}
}
```JAVA
package exception;
/**
* throw关键字,用于主动对外抛出一个异常
*/
public class ThrowDemo {
public static void main(String[] args){
System.out.println("程序开始了...");
try {
Person p = new Person();
/*
当我们调用一个含有throws声明异常抛出的方法时,编译器要求
我们必须添加处理异常的手段,否则编译不通过.而处理手段有两种
1:使用try-catch捕获并处理异常
2:在当前方法上继续使用throws声明该异常的抛出
具体用哪种取决于异常处理的责任问题
*/
p.setAge(100000);//典型的符合语法,但是不符合业务逻辑要求
System.out.println("此人年龄:"+p.getAge()+"岁");
} catch (IllegalAgeException e) {
e.printStackTrace();
}
System.out.println("程序结束了...");
}
}
#### 总结:
异常处理机制是用来处理那些可能存在的异常,但是无法通过修改逻辑完全规避的场景。
而如果通过修改逻辑可以规避的异常是bug,不应当用异常处理机制在运行期间解决!应当在编码时及时修正
=+=+=+=+=+=+=+=+=+=+=+==+=+=+=+=+=+=
写入项目(聊天室)
项目CSDN链接
项目Git链接
src/socket01 · 敲代码/JAVA_CGB2202SE - 码云 - 开源中国 (gitee.com)
=+=+=+=+=+=+=+=+=+=+=+==+=+=+=+=+=+=
多线程
介绍
### 多线程 ##### 线程:一个顺序的单一的程序执行流程就是一个线程。代码一句一句的有先后顺序的执行。 ##### 多线程:多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。 ##### 并发: 多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间片段并 尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度 程序会再次分配一个时间片段给一个线程使得CPU执行它。如此反复。由于CPU执行时间在 纳秒级别,我们感觉不到切换线程运行的过程。所以微观上走走停停,宏观上感觉一起运行 的现象成为并发运行! ##### 用途: - 当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运行 - 一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行
线程的生命周期 :
新建:new创建线程对象时
就绪:调用 start()方法时
运行:调用 run()方法时
阻塞: 多种原因可导致阻塞
死亡:多种原因
用完就"死" ,,
#创建线程的两种方式
###方式一 : 继承Thread并重写run方法
定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。 注:启动该线程要调用该线程的start方法,而不是run方法!!!
/** * 多线程 * 线程:程序中一个单一的顺序执行流程 * 多线程:多个单一顺序执行流程"同时"执行 * * 多线程改变了代码的执行方式,从原来的单一顺序执行流程变为多个执行流程"同时"执行。 * 可以让多个代码片段的执行互不打扰。 * * 线程之间是并发执行的,并非真正意义上的同时运行。 * 常见线程有两种方式: * 1:继承Thread并重写run方法 * */
public class ThreadDemo1 {
public static void main(String[] args) {
//创建两个线程
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/*
启动线程,注意:不要调用run方法!!
线程调用完start方法后会纳入到系统的线程调度器程序中被统一管理。
线程调度器会分配时间片段给线程,使得CPU执行该线程这段时间,用完后
线程调度器会再分配一个时间片段给一个线程,如此反复,使得多个线程
都有机会执行一会,做到走走停停,并发运行。
线程第一次被分配到时间后会执行它的run方法开始工作。
*/
t1.start();
t2.start();
}
}
/**
* 第一种创建线程的优点:
* 结构简单,利于匿名内部类形式创建。
*
* 缺点:
* 1:由于java是单继承的,这会导致继承了Thread就无法再继承其他类去复用方法
* 2:定义线程的同时重写了run方法,这等于将线程的任务定义在了这个线程中导致
* 线程只能干这件事。重(chong)用性很低。
*/
class MyThread1 extends Thread{
public void run(){
for (int i=0;i<1000;i++){
System.out.println("hello姐~");
}
}
}
class MyThread2 extends Thread{
public void run(){
for (int i=0;i<1000;i++){
System.out.println("来了~老弟!");
}
}
}
第一种创建线程的方式 优点: 在于结构简单,便于匿名内部类形式创建。 缺点: - 1:直接继承线程,会导致不能在继承其他类去复用方法,这在实际开发中是非常不便的。 - 2:定义线程的同时重写了run方法,会导致线程与线程任务绑定在了一起,不利于线程的重用。
###方式二 : 实现Runnable接口单独定义线程任务
/** * 第二种创建线程的方式 * 实现Runnable接口单独定义线程任务 */
public class ThreadDemo2 {
public static void main(String[] args) {
//实例化任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//创建线程并指派任务
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
class MyRunnable1 implements Runnable{
public void run() {
for (int i=0;i<1000;i++){
System.out.println("你是谁啊?");
}
}
}
class MyRunnable2 implements Runnable{
public void run() {
for (int i=0;i<1000;i++){
System.out.println("开门!查水表的!");
}
}
}
java中的代码都是靠线程运行的,执行main方法的线程称为"主线程"
线程提供了一个方法:
* static Thread currentThread() 该方法可以获取运行这个方法的线程
/** * java中所有的代码都是靠线程执行的,main方法也不例外。JVM启动后会创建一条线程来执行main * 方法,该线程的名字叫做"main",所以通常称它为"主线程"。 * 我们自己定义的线程在不指定名字的情况下系统会分配一个名字,格式为"thread-x"(x是一个数)。 * * Thread提供了一个静态方法: * static Thread currentThread() * 获取执行该方法的线程。 * */
public static void main(String[] args) {
/*
后期会学习到一个很重要的API:ThreadLocal (线程本地) ,它可以使得我们在一个线程上跨越多个方法时共享数据使用,其内部要用到currentThread (当前线程) 方法来辨别线程。
如spring的事物控制就是靠ThreadLocal实现的。
*/
Thread main = Thread.currentThread();//获取执行main方法的线程(主线程)
System.out.println("线程:"+main);
dosome();//主线程执行dosome方法
}
public static void dosome(){
Thread t = Thread.currentThread();//获取执行dosome方法的线程
System.out.println("执行dosome方法的线程是:"+t);
}
###使用多线程实现多客户端连接服务端
流程图
=+=+=+=+=+=+=+=+=+=+=+==+=+=+=+=+=+=
加入线程后 ,只有服务端代码改造,客户端暂时照旧
写入项目(聊天室)
项目CSDN链接
项目Git链接
src/socket01 · 敲代码/JAVA_CGB2202SE - 码云 - 开源中国 (gitee.com)
=+=+=+=+=+=+=+=+=+=+=+==+=+=+=+=+=+=
#线程API
##获取线程相关信息的方法
/** * 获取线程相关信息的一组方法 */
Thread main = Thread.currentThread();//获取主线程
String name = main.getName();//获取线程的名字
System.out.println("名字:"+name);
long id = main.getId();//获取该线程的唯一标识
System.out.println("id:"+id);
int priority = main.getPriority();//获取该线程的优先级
System.out.println("优先级:"+priority);
boolean isAlive = main.isAlive();//该线程是否活着
System.out.println("是否活着:"+isAlive);
boolean isDaemon = main.isDaemon();//是否为守护线程
System.out.println("是否为守护线程:"+isDaemon);
boolean isInterrupted = main.isInterrupted();//是否被中断了
System.out.println("是否被中断了:"+isInterrupted);
##线程优先级
线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程. 线程有10个优先级,使用整数1-10表示 - 1为最小优先级,10为最高优先级.5为默认值 - 调整线程的优先级可以最大程度的干涉获取时间片的几率.优先级越高的线程获取时间片的次数越多,反之则越少.
Thread max = new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("max");
}
}
};
Thread min = new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("min");
}
}
};
Thread norm = new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("nor");
}
}
};
min.setPriority(Thread.MIN_PRIORITY);//设置优先级为1,最低级
max.setPriority(Thread.MAX_PRIORITY);//设置优先级为10,最高级
min.start(); //最后执行,第三
norm.start(); //未设置,默认为5 第二
max.start(); //先执行,第一
##sleep阻塞(翻译 : 睡眠)
线程提供了一个静态方法: - static void sleep(long ms) - 使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE(可运行)状态等待再次获取时间片并发运行.
System.out.println("程序开始了!");
try {
Thread.sleep(5000);//主线程阻塞5秒钟 可以直接调用,不用new一个线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束了!");
sleep方法出现异常时处理方法。 异常 : InterruptedException
当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞.
/** * sleep方法要求必须处理中断异常:InterruptedException * 当一个线程调用sleep方法处于睡眠阻塞的过程中,它的interrupt()方法被调用时 * 会中断该阻塞,此时sleep方法会抛出该异常。 */
Thread lin = new Thread(){
public void run(){
System.out.println("林:刚美完容,睡一会吧~");
try {
Thread.sleep(9999999);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了像了!");
}
System.out.println("林:醒了");
}
};
Thread huang = new Thread(){
public void run(){
System.out.println("黄:大锤80!小锤40!开始砸墙!");
for(int i=0;i<5;i++){
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("咣当!");
System.out.println("黄:大哥,搞定!");
lin.interrupt();//中断林的睡眠阻塞
}
};
lin.start();
huang.start();
##守护线程
守护线程也称为:后台线程 - 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异. - 守护线程的结束时机上有一点与普通线程不同,即:进程的结束. - 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.
/** * 守护线程 * 守护线程是通过普通线程调用setDaemon(true)设置而转变的。因此守护线程创建上 * 与普通线程无异。 * 但是结束时机上有一点不同:进程结束。 * 当一个java进程中的所有普通线程都结束时,该进程就会结束,此时会强制杀死所有正在 * 运行的守护线程。 */
Thread rose = new Thread(){
public void run(){
for(int i=0;i<5;i++){
System.out.println("rose:let me go!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("rose:啊啊啊啊啊啊AAAAAAAaaaaa....");
System.out.println("噗通");
}
};
Thread jack = new Thread(){ //设置为守护线程
public void run(){
while(true){
System.out.println("jack:you jump!i jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
};
rose.start();
jack.setDaemon(true);//设置守护线程必须在线程启动前进行
jack.start();
通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.
补 充 :
当有三个或三个以上线程时,其中有一个设置为守护线程时。
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
#多线程并发安全问题
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪. 临界资源:操作该资源的全过程同时只能被单个线程完成.(方法或者被调用的对象称为临界资源) 是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。
/** * 多线程并发安全问题 * 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现 * 混乱,严重时可能导致系统瘫痪。 * 临界资源:同时只能被单一线程访问操作过程的资源。 */
public class SyncDemo1 {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
/*
static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。
*/
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans = 20;//桌子上有20个豆子
public int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了!");
}
Thread.yield();//让线程主动放弃本次时间片,用来模拟执行到这里时CPU没有时间了
return beans--;
}
}
以上代码,在两个线程同时运行时,每次调用getBean()方法时,豆子都会减一,当其中一个线程拿到最后一个豆子后,第二个线程没有豆子可拿时(beans == 0)则会抛出运行时异常:然后停下.
如果不抛出异常,则会一直执行下去,会造成造作顺序混乱
##同步块
/** * 有效的缩小同步范围可以在保证并发安全的前提下尽可能提高并发效率。 * * 同步块 * 语法: * synchronized(同步监视器对象){ * 需要多个线程同步执行的代码片段 * } * 同步块可以更准确的锁定需要多个线程同步执行的代码片段来有效缩小排队范围。 */
同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个.
public class SyncDemo2 {
public static void main(String[] args) {
Shop shop = new Shop(); //实例化,方便调用方法
Thread t1 = new Thread(){
public void run(){
shop.buy();
}
};
Thread t2 = new Thread(){
public void run(){
shop.buy();
}
};
t1.start();
t2.start();
}
}
class Shop{
public void buy(){
/*
在方法上使用synchronized,那么同步监视器对象就是this。
*/
// public synchronized void buy(){ //在这里加同步监视器和在具体的一段代码上加,
// 效果是一样的,但是极少数情况下会出现试衣服时1线程先进去后过一会,1线程还没离开,2线程
// 就进去了,这里写同步监视器具体的代码上就不用写同步监视器
Thread t = Thread.currentThread();//获取运行该方法的线程
try {
System.out.println(t.getName()+":正在挑衣服...");
Thread.sleep(5000);
/*
使用同步块需要指定同步监视器对象,即:上锁的对象
这个对象可以是java中任何引用类型的实例,只要保证多个需要排队
执行该同步块中代码的线程看到的该对象是"同一个"即可
*/
synchronized (this) {
// synchronized (new Object()) {//没有效果!
System.out.println(t.getName() + ":正在试衣服...");
Thread.sleep(5000);
}
System.out.println(t.getName()+":结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
##在静态方法中使用同步监视器
当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果. 静态方法使用的同步监视器对象为当前类的类对象(Class的实例). 注:类对象会在后期反射知识点介绍.静态方法上使用同步监视器,不需要在()内加任何东西,只要在静态关键词前加synchronized即可
/**
* 静态方法上如果使用synchronized,则该方法一定具有同步效果。
*/
public class SyncDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
Boo.dosome();
}
};
Thread t2 = new Thread(){
public void run(){
Boo.dosome();
}
};
t1.start();
t2.start();
}
}
class Boo{
/**
* synchronized在静态方法上使用时,指定的同步监视器对象为当前类的类对象。
* 即:Class实例。
* 在JVM中,每个被加载的类都有且只有一个Class的实例与之对应,后面讲反射
* 知识点的时候会介绍类对象。
* 在静态方法上使用同步监视器,不需要在()内加任何东西,只需在静态关键词前加synchronized即可
*/
public synchronized static void dosome(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#### 静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象
class Boo{
public static void dosome(){
/*
静态方法中使用同步块时,指定同步监视器对象通常还是用当前类的类对象
获取方式为:类名.class
*/
synchronized (Boo.class) {
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
##互斥锁
当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的. 使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.
/**
* 互斥锁
* 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,
* 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。
*/
public class SyncDemo4 {
public static void main(String[] args) {
Foo foo = new Foo();
Thread t1 = new Thread(){
public void run(){
foo.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
foo.methodB();
}
};
t1.start();
t2.start();
}
}
class Foo{
public synchronized void methodA(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在执行A方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行A方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在执行B方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行B方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在非静态方法上加synchronized时,当有多个线程,同时执行该的方法时,不能同时执行,
称为互斥: "我在用,我在调方法. 你不能用. 我用完,你再用"
##死锁
死锁的产生: 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。这个现象就是死锁。
/**
* 以下仅作为举例,无任何其它行为
*/
/**
* 死锁
* 死锁的产生:
* 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。
* 这个现象就是死锁。
*/
public class DeadLockDemo {
//定义两个锁对象,"筷子"和"勺"
public static Object chopsticks = new Object();
public static Object spoon = new Object();
public static void main(String[] args) {
Thread np = new Thread(){
public void run(){
System.out.println("北方人开始吃饭.");
System.out.println("北方人去拿筷子...");
synchronized (chopsticks){
System.out.println("北方人拿起了筷子开始吃饭...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("北方人吃完了饭,去拿勺...");
synchronized (spoon){
System.out.println("北方人拿起了勺子开始喝汤...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("北方人喝完了汤");
}
System.out.println("北方人放下了勺");
}
System.out.println("北方人放下了筷子,吃饭完毕!");
}
};
Thread sp = new Thread(){
public void run(){
System.out.println("南方人开始吃饭.");
System.out.println("南方人去拿勺...");
synchronized (spoon){
System.out.println("南方人拿起了勺开始喝汤...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("南方人喝完了汤,去拿筷子...");
synchronized (chopsticks){
System.out.println("南方人拿起了筷子开始吃饭...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("南方人吃完了饭");
}
System.out.println("南方人放下了筷子");
}
System.out.println("南方人放下了勺,吃饭完毕!");
}
};
np.start();
sp.start();
}
}
以上代码运行情况就称其为"死锁"
运行结果为 :
北方人:开始吃饭
北方人去拿筷子...
北方人拿起了筷子,开始吃饭...
南方人:开始吃饭
南方人去拿勺...
南方人拿起了勺,开始喝汤...
南方人喝完了汤,去拿筷子...
北方人吃完了饭,去拿勺...
然后.....然后就没有下文了,北方人拿着筷子去要勺子,南方人拿着勺子去拿筷子
##解决死锁
/**
* 以下仅作为举例,无任何其它行为
*/
/**
* 解决死锁:
* 1:尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套)
* 2:当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。
* 即:A线程在持有锁1的过程中去持有锁2时,B线程也要以这样的持有顺序进行。
*/
public class DeadLockDemo2 {
//筷子
private static Object chopsticks = new Object();
//勺
private static Object spoon = new Object();
public static void main(String[] args) {
//北方人
Thread np = new Thread(){
public void run(){
try {
System.out.println("北方人:开始吃饭");
System.out.println("北方人去拿筷子...");
synchronized (chopsticks) {
System.out.println("北方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
}
System.out.println("北方人吃完了饭,放下了筷子");
System.out.println("北方人去拿勺子...");
synchronized (spoon){
System.out.println("北方人拿起了勺子,开始喝汤...");
Thread.sleep(5000);
}
System.out.println("北方人喝完了汤,北方人放下了勺子");
System.out.println("吃饭完毕。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//南方人
Thread sp = new Thread(){
public void run(){
try {
System.out.println("南方人:开始吃饭");
System.out.println("南方人去拿勺...");
synchronized (spoon) {
System.out.println("南方人拿起了勺,开始喝汤...");
Thread.sleep(5000);
}
System.out.println("南方人喝完了汤,放下勺子...");
System.out.println("南方人去拿筷子...");
synchronized (chopsticks){
System.out.println("南方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
}
System.out.println("南方人吃完了饭,南方人放下了筷子");
System.out.println("吃饭完毕。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
np.start();
sp.start();
}
}
以上代码就较好的避免了出现死锁的情况
运行结果为 :
北方人:开始吃饭
北方人去拿筷子...
南方人:开始吃饭
南方人去拿勺...
北方人拿起了筷子,开始吃饭...
南方人拿起了勺,开始喝汤...
北方人吃完了饭,放下了筷子
北方人去拿勺子...
北方人拿起了勺子,开始喝汤...
南方人喝完了汤,放下勺子...
南方人去拿筷子...
南方人拿起了筷子,开始吃饭...
北方人喝完了汤,北方人放下了勺子
吃饭完毕。
南方人吃完了饭,南方人放下了筷子
吃饭完毕。
=+=+=+=+=+=+=+=+=+=+=+==+=+=+=+=+=+=
实现客户端发送消息至服务端,服务端再将消息发送给客户端
实现多个客户端连接,并将单个客户端发送的消息,群发给其它客户端
写入项目(聊天室)
项目CSDN链接
项目Git链接
src/socket01 · 敲代码/JAVA_CGB2202SE - 码云 - 开源中国 (gitee.com)
=+=+=+=+=+=+=+=+=+=+=+==+=+=+=+=+=+=
集合框架
介绍
#### 什么是集合 集合与数组一样,可以保存一组元素,并且提供了操作元素的相关方法,使用更方便.
#java集合框架中相关接口
#### java.util.Collection接口: java.util.Collection是所有集合的顶级接口.Collection下面有多种实现类,因此我们有更多的数据结构可供选择.
##collection常见接口
#### Collection下面有两个常见的子接口: - java.util.List: 线性表.是可重复集合,并且有序. - java.util.Set : 不可重复的集合,大部分实现类是无序的. 这里可重复指的是集合中的元素是否可以重复,而判定重复元素的标准是依靠元素自身equals比较 的结果.为true就认为是重复元素.
##集合
###List集合中几个方法
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemo {
public static void main(String[] args) {
Collection c = new ArrayList(); //如果想要使用List集合,就要先实例化,new一个
//List集合,才可以调用集合中的方法
/*
boolean add(E e)
向当前集合中添加一个元素.当元素成功添加后返回true
*/
c.add("one");
c.add("two");
c.add("three");
c.add("four");
c.add("five");
System.out.println(c);
/*
int size()
返回当前集合的元素个数
*/
int size = c.size();
System.out.println("size:"+size);
/*
boolean isEmpty()
判断当前集合是否为空集(不含有任何元素)
*/
boolean isEmpty = c.isEmpty();
System.out.println("是否为空集:"+isEmpty);
/*
clear()
清空集合
*/
c.clear();
System.out.println(c);
System.out.println("size:"+c.size());//0
System.out.println("是否为空集:"+c.isEmpty());
}
}
###集合与元素equals方法相关的方法
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
/**
* 集合的很多操作有与元素的equals方法相关。
*/
public class CollectionDemo2 {
public static void main(String[] args) {
// Collection c = new ArrayList();
Collection c = new HashSet();
c.add(new Point(1,2));
c.add(new Point(3,4));
c.add(new Point(5,6));
c.add(new Point(7,8));
c.add(new Point(1,2));
/*
上述代码中new Point(),是单独定义/创建的一个类.是自己写的,便于调用,便于后面的方法使用
并非IDAE自己提供的类
*/
/*
集合重写了Object的toString方法,输出的格式为:
[元素1.toString(), 元素2.toString(), ....]
*/
System.out.println(c);
Point p = new Point(1,2);
/*
boolean contains(Object o)
判断当前集合是否包含给定元素,这里判断的依据是给定元素是否与集合
现有元素存在equals比较为true的情况。
*/
boolean contains = c.contains(p);
System.out.println("包含:"+contains);
/*
remove用来从集合中删除给定元素,删除的也是与集合中equals比较
为true的元素。注意,对于可以存放重复元素的集合而言,只删除一次。
*/
c.remove(p);
System.out.println(c);
}
}
###集合存放的是元素的引用
集合只能存放引用类型元素,并且存放的是元素的引用(地址)
import java.util.ArrayList;
import java.util.Collection;
/**
* 集合只能存放引用类型元素,并且存放的是元素的引用(地址)
*/
public class CollectionDemo3 {
public static void main(String[] args) {
Collection c = new ArrayList();
Point p = new Point(1,2);
c.add(p);
System.out.println("p:"+p);//p:(1,2)
System.out.println("c:"+c);//c:[(1,2)]
p.setX(2);
System.out.println("p:"+p);//p:(2,2)
System.out.println("c:"+c);//c:[(2,2)]
}
}
#集合的操作
#### 集合间的操作 集合提供了如取并集,删交集,判断包含子集等操作
##集合间的操作
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
/**
* 集合间的操作
*/
public class CollectionDemo4 {
public static void main(String[] args) {
// Collection c1 = new ArrayList();
Collection c1 = new HashSet();//不可重复元素
c1.add("java");
c1.add("c");
c1.add("c++");
System.out.println("c1:"+c1);
Collection c2 = new ArrayList();
c2.add("android");
c2.add("ios");
c2.add("java");
System.out.println("c2:"+c2);
/*
boolean addAll(Collection c)
将给定集合中的所有元素添加到当前集合中。当前集合若发生了改变则返回true
*/
boolean tf = c1.addAll(c2);
System.out.println("是否发生改变 :"+tf);
System.out.println("c1:"+c1);
System.out.println("c2:"+c2);
Collection c3 = new ArrayList();
c3.add("ios");
c3.add("c++");
c3.add("php");
System.out.println("c3:"+c3);
/*
boolean containsAll(Collection c)
判断当前集合是否包含给定集合中的所有元素
*/
boolean contains = c1.containsAll(c3);
System.out.println("包含所有元素 :"+contains);
/*
取交集,仅保留当前集合中给定集合的共有元素
在c2中如果有c3有的元素时,保留,其余删除,最后输出c2
*/
// c2.retainAll(c3);
/*
boolean removeAll(Collection c)
删除当前集合中与给定集合中的共有元素
在c1中如果有c3的元素时删除,其余保留,最后输出c1
*/
c1.removeAll(c3);
System.out.println("c1:"+c1);
System.out.println("c3:"+c3);
}
}
## 集合的遍历
/** * Collection接口没有定义单独获取某一个元素的操作,因为不通用。 * 但是Collection提供了遍历集合元素的操作。该操作是一个通用操作,无论什么类型的 * 集合都支持此种遍历方式:迭代器模式。 * * Iterator iterator() die(二声) * 该方法会获取一个用于遍历当前集合元素的迭代器 * * java.util.Iterator接口,是迭代器接口,规定了迭代器遍历集合的相关操作,不同的 * 集合都提供了一个用于遍历自身元素的迭代器实现类,不过我们不需要记住它们的名字,以 * 多态的方式当成Iterator使用即可。 * 迭代器遍历集合遵循的步骤为:问->取->删 * 其中删除不是必须操作。 * */
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("one");
c.add("two");
c.add("three");
c.add("four");
c.add("five");
System.out.println(c);
//获取迭代器
Iterator it = c.iterator();
/*
迭代器提供的相关方法:
boolean hasNext()
判断集合是否还有元素可以遍历
E next()
获取集合下一个元素(第一次调用时就是获取第一个元素,以此类推)
*/
while(it.hasNext()){
String str = (String)it.next();
System.out.println(str);
}
System.out.println(c);
}
}
#### 迭代器遍历过程中不得通过集合的方法增删元素
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("one");
c.add("#");
c.add("two");
c.add("#");
c.add("three");
c.add("#");
c.add("four");
c.add("#");
c.add("five");
System.out.println(c);
//获取迭代器
Iterator it = c.iterator();
/*
迭代器提供的相关方法:
boolean hasNext()
判断集合是否还有元素可以遍历
E next()
获取集合下一个元素(第一次调用时就是获取第一个元素,以此类推)
*/
while(it.hasNext()){
String str = (String)it.next();
System.out.println(str);
if("#".equals(str)){
/*
迭代器要求遍历的过程中不得通过集合的方法增删元素
否则会抛出异常:ConcurrentModificationException
*/
// c.remove(str);
/*
迭代器的remove方法可以将通过next方法获取的元素从集合
中删除。
*/
it.remove();
}
}
System.out.println(c);
}
}
##增强for循环
JDK5之后推出了一个特性:增强型for循环 - 也称为新循环,使得我们可以使用相同的语法遍历集合或数组. 语法: for(元素类型 变量名 : 集合或数组){ 循环体 }
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* JDK5推出时,推出了一个新的特性:增强型for循环
* 也称为新循环,它可以用相同的语法遍历集合或数组。
*
* 新循环是java编译器认可的,并非虚拟机。
*/
public class NewForDemo {
public static void main(String[] args) {
String[] array = {"one","two","three","four","five"};
for(int i=0;i<array.length;i++){
String str = array[i];
System.out.println(str);
}
for(String str : array){
System.out.println(str);
}
Collection c = new ArrayList();
c.add("one");
c.add("two");
c.add("three");
c.add("four");
c.add("five");
//迭代器遍历
Iterator it = c.iterator();
while(it.hasNext()){
String str = (String)it.next();
System.out.println(str);
}
//新循环遍历
for(Object o : c){
String str = (String)o;
System.out.println(str);
}
}
}
##泛型
JDK5之后推出的另一个特性:泛型 泛型也称为参数化类型,允许我们在使用一个类时指定它当中属性,方法参数或返回值的类型. - 泛型在集合中被广泛使用,用来指定集合中的元素类型. - 有泛型支持的类在使用时若不指定泛型的具体类型则默认为原型Object
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/*
* 泛型 JDK5之后推出的另一个特性。
* 泛型也称为参数化类型,允许我们在使用一个类时指定它里面属性的类型,
* 方法参数或返回值的类型,使得我们使用一个类时可以更灵活。
* 泛型被广泛应用于集合中,用来指定集合中的元素类型。
* 支持泛型的类在使用时如果未指定泛型,那么默认就是原型Object
*
* Collection接口的定义
* public interface Collection<E> ... {
*
* Collection<E> 这里的<E>就是泛型
*
* Collection中add方法的定义,参数为E
* boolean add(E e)
*/
public class NewForDemo {
public static void main(String[] args) {
String[] array = {"one","two","three","four","five"};
for(int i=0;i<array.length;i++){
String str = array[i];
System.out.println(str);
}
for(String str : array){ //可以遍历String数组
System.out.println(str);
}
/*
Collection<E>定义时,指定了一个泛型E。我们在实际使用集合时可以指定
E的实际类型。这样一来,编译器会检查我们使用泛型时的类型是否匹配。例如
集合的方法:
boolean add(E e)
编译器会检查我们调用add方法向集合中添加元素时,元素的类型是否为E指定的
类型,不符合编译不通过。
*/
Collection<String> c = new ArrayList<>();
c.add("one");//编译器会检查add方法的实参是否为String类型
c.add("two");
c.add("three");
c.add("four");
c.add("five");
// c.add(123);//不符合E在这里的实际类型String,因此编译不通过。
System.out.println(c);//输出c集合中的元素
for (String a : c){ //使用增强for循环输出集合中的元素
System.out.println(a);
}
//新循环遍历(只遍历,删除等操作需要重写迭代器)集合会改回成迭代器遍历
for (String s : c){
System.out.println(s);
}
//迭代器也支持泛型,指定时要与其遍历的集合指定的泛型一致
Iterator<String> e = c.iterator();
while (e.hasNext()){
String str = e.next();
System.out.println(str);
//可以在迭代器中进行删除等操作,新循环/增强for循环只能进行遍历集合或者数组
}
}
}
## List集
List集对子集进行操作就是对原集合对应的操作
java.util.List接口,继承自Collection. List集合是可重复集,并且有序,提供了一套可以通过下标操作元素的方法 常用实现类: - java.util.ArrayList:内部使用数组实现,查询性能更好. - java.util.LinkedList:内部使用链表实现,首尾增删元素性能更好.
### List集合常见方法
#### get()与set()
import java.util.ArrayList;
import java.util.List;
/**
* List集合
* List是Collection下面常见的一类集合。
* java.util.List接口是所有List的接口,它继承自Collection。
* 常见的实现类:
* java.util.ArrayList:内部由数组实现,查询性能更好。
* java.util.LinkedList:内部由链表实现,增删性能更好。
*
* List集合的特点是:可以存放重复元素,并且有序。其提供了一套可以通过下标
* 操作元素的方法。
*/
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//对性能没有特别苛刻的情况下通常使用ArrayList
// List<String> list = new LinkedList<>(); //增删性能更好
list.add("one");
list.add("two");
list.add("three");
list.add("four");
list.add("five");
/*
E get(int index)
获取指定下标对应的元素
*/
//获取第三个元素
String e = list.get(2);
System.out.println(e);
for(int i=0;i<list.size();i++){ //遍历集合中的元素
e = list.get(i); //get 拿到
System.out.println(e);
}
/*
E set(int index,E e)
将给定元素设置到指定位置,返回值为该位置原有的元素。
替换元素操作
*/
String old = list.set(1,"six"); //将在集合中下标为1的元素更改为"six",并返回
System.out.println(list); //[one,six,three,four,five]
System.out.println("被替换的元素是:"+old);
}
}
##一个练习
// List<String> list = new ArrayList<>();
List<String> list = new LinkedList<>();
list.add("one");
list.add("two");
list.add("three");
list.add("four");
list.add("five");
System.out.println(list);
//将集合list翻转
/*
//自己写的
int a = list.size();
String old;
for (int i = 0; i < list.size()/2; i++) {//遍历集合中的所有元素
old = list.set(i, list.get(a-1)); //拿到最后一个元素,换到第一位上,返回第一
//个元素,存到old中
list.set(list.size()-i-1, old); //第一个元素(old)存到最后一个位上 这一
//行可以删
a--;
}
System.out.println(list);
*/
// 更方便的方法
// for (int i = 0; i < list.size()/2; i++) {
// //获取正数位置上的元素
// String e = list.get(i);
// //将正数位置上的元素放到倒数位置上并接收被替换的倒数位置元素
// e = list.set(list.size()-1-i,e);
// //将倒数位置的元素放到正数位置上
// list.set(i,e);
// }
// System.out.println(list);
Collections.reverse(list);//调用方法
System.out.println(list);
#### 重载的add()和remove()
import java.util.ArrayList;
import java.util.List;
/**
* List集合提供了一对重载的add,remove方法
*/
public class ListDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("one"); //写法未重载的方法
list.add("two");
list.add("three");
list.add("four");
list.add("five");
System.out.println(list);
/*
void add(int index,E e)
将给定元素插入到指定位置
*/
//[one,two,six,three,four,five]
list.add(2,"six"); //重载的方法.将下标为2的元素更换为指定的字符串
System.out.println(list);
/*
E remove(int index)
删除并返回指定位置上的元素
*/
//[one,six,three,four,five]
String e = list.remove(1); //重载,删除下标为1的元素删除并返回
System.out.println(list);
System.out.println("被删除的元素:"+e);
}
}
#### subList()方法
import java.util.ArrayList;
import java.util.List;
/**
* List subList(int start,int end)
* 获取当前集合中指定范围内的子集。两个参数为开始与结束的下标(含头不含尾)
*/
public class ListDemo3 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
System.out.println(list);
//获取3-7这部分
List<Integer> subList = list.subList(3,8); //都是含头不含尾
System.out.println(subList);
//将子集每个元素扩大10倍
for(int i=0;i<subList.size();i++){
subList.set(i,subList.get(i) * 10);
}
//[30,40,50,60,70]
System.out.println(subList);
/*
对子集元素的操作就是对原集合对应元素的操作
*/
System.out.println(list);
//删除list集合中的2-8
list.subList(2,9).clear();
System.out.println(list);
}
}
## 集合与数组的转换
### 集合转换为数组
Collection提供了一个方法:**toArray**,可以将当前集合转换为一个数组
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 集合转换为数组
* Collection提供了方法toArray可以将当前集合转换为一个数组
*/
public class CollectionToArrayDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
list.add("four");
list.add("five");
System.out.println(list);
// Object[] array = list.toArray();
/*
重载的toArray方法要求传入一个数组,内部会将集合所有元素存入该数组
后将其返回(前提是该数组长度>=集合的size)。如果给定的数组长度不足,
则方法内部会自行根据给定数组类型创建一个与集合size一致长度的数组并
将集合元素存入后返回。
*/
String[] array = list.toArray(new String[list.size()]); //想省事直接填0
System.out.println(array.length);
System.out.println(Arrays.toString(array));
}
}
### 数组转换为List集合
数组的工具类Arrays提供了一个静态方法**asList()**,可以将一个数组转换为一个List集合
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 数组转换为List集合
* 数组的工具类Arrays提供了一个静态方法asList,可以将数组转换为一个List集合。
*/
public class ArrayToListDemo {
public static void main(String[] args) {
String[] array = {"one","two","three","four","five"};
System.out.println(Arrays.toString(array));
List<String> list = Arrays.asList(array);
System.out.println(list);
list.set(1,"six");
System.out.println(list);
//数组跟着改变了。注意:对数组转换的集合进行元素操作就是对原数组对应的操作
System.out.println(Arrays.toString(array));
/*
由于数组是定长的,因此对该集合进行增删元素的操作是不支持的,会抛出
异常:java.lang.UnsupportedOperationException
*/
// list.add("seven");
/*
若希望对集合进行增删操作,则需要自行创建一个集合,然后将该集合元素
导入。
*/
// List<String> list2 = new ArrayList<>();
// list2.addAll(list);
/*
所有的集合都支持一个参数为Collection的构造方法,作用是在创建当前
集合的同时包含给定集合中的所有元素
*/
List<String> list2 = new ArrayList<>(list);
System.out.println("list2:"+list2);
list2.add("seven"); //另外添加元素需要再建一个集合存,由数组转换后那个集合
System.out.println("list2:"+list2);
}
}
## 集合的排序
Collections是集合的工具类,里面定义了很多静态方法用于操作集合. #### Collections.sort(List list)方法 可以对List集合进行自然排序(从小到大)
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
/**
* 集合的排序
* 集合的工具类:java.util.Collections提供了一个静态方法sort,可以对List集合
* 进行自然排序
*/
public class SortListDemo1 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Random random = new Random();
for(int i=0;i<10;i++){
list.add(random.nextInt(100));
}
System.out.println(list);
Collections.sort(list); //排序
/**
* 正序 内部是这样写的
* i1-i2
* 结果>0时,表示i1>i2
* 结果<0时,表示i1<i2
* 当返回值=0时,表示i1=i2
*/
System.out.println("从小到大:"+list);
Collections.sort(list,(i1,i2)->i2-i1);
/**
* 倒序 内部是这样写的
* i2-i1
* 结果>0时,表示i2>i1
* 结果<0时,表示i2<i1
* 当返回值=0时,表示i2=i1
*/
System.out.println("从大到小"+list);
Collections.shuffle(list); //乱序
System.out.println("打乱顺序:"+list);
}
}
### 排序自定义类型元素
在排序自定义元素时,需要自己写一个用匿名内部类创建的比较器,但是匿名内部类,会出现侵入性,需要简化为lambda表达式的写法,才没有侵入性,是为最终代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* 集合排序自定义类型元素
*/
public class SortListDemo2 {
public static void main(String[] args) {
List<Point> list = new ArrayList<>();
list.add(new Point(1,2));
list.add(new Point(15,8));
list.add(new Point(9,7));
list.add(new Point(5,3));
list.add(new Point(4,6));
System.out.println(list);
/*
编译不通过。原因:集合元素没有实现接口Comparable。只有实现了该接口的类
才可以进行比较(实现接口后就必须重写方法compareTo()用于定义两个元素间的
比较大小规则)。
该操作对我们的代码具有侵入性,不建议这样做。
侵入性:当我们调用一个功能时,其要求我们为其修改其他额外的代码,这就是侵入性
它不利于程序后期的维护与扩展,应当尽量避免。
*/
// Collections.sort(list);
/*
建一个类,实现
*/
// MyComparator c = new MyComparator();
// Collections.sort(list,c);
/*
匿名内部类写法
*/
// Comparator c = new Comparator<Point>() {
// @Override
// public int compare(Point o1, Point o2) {//compare:比较
// int len1 = o1.getX()*o1.getX()+o1.getY()* o1.getY();
// int len2 = o2.getX()*o2.getX()+o2.getY()* o2.getY();
// return len1-len2;
// }
// };
// Collections.sort(list,c);
/*
没有侵入性的写法
*/
// Collections.sort(list, new Comparator<Point>() {
// @Override
// public int compare(Point o1, Point o2) {
// int len1 = o1.getX()*o1.getX()+o1.getY()* o1.getY();
// int len2 = o2.getX()*o2.getX()+o2.getY()* o2.getY();
// return len1-len2;
// }
// });
/*
最简写法 lambda
*/
Collections.sort(list,
(o1,o2)->
o1.getX()*o1.getX()+o1.getY()* o1.getY()
-
o2.getX()*o2.getX()-o2.getY()* o2.getY()
);
System.out.println(list);
}
}
/**
* 以后不会这么干
*/
//class MyComparator implements Comparator<Point>{
//
// @Override
// /**
// * 用来定义两个元素o1与o2的大小关系,返回值为:
// * 当返回值>0时:表示o1>o2的
// * 当返回值<0时:表示o1<o2的
// * 当返回值=0时:表示o1=o2
// */
// public int compare(Point o1, Point o2) {//compare:比较
// int len1 = o1.getX()*o1.getX()+o1.getY()* o1.getY();
// int len2 = o2.getX()*o2.getX()+o2.getY()* o2.getY();
// return len1-len2;
// }
//}
### 排序字符串
java中提供的类,如:String,包装类都实现了Comparable接口,但有时候这些比较规则不能满足我们的排序需求时,同样可以临时提供一种比较规则来进行排序.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class SortListDemo3 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// list.add("Tom");
// list.add("jackson");
// list.add("rose");
// list.add("jill");
// list.add("ada");
// list.add("hanmeimei");
// list.add("lilei");
// list.add("hongtaoliu");
// list.add("Jerry");
list.add("传奇");
list.add("小泽老师");
list.add("苍老师");
System.out.println(list);
//按照字符多少排序
// Collections.sort(list);
// Collections.sort(list, new Comparator<String>() {
// public int compare(String o1, String o2) {
return o1.length()-o2.length();
// return o2.length()-o1.length();//反过来减就是降序
// }
// });
Collections.sort(list,(o1,o2)->o2.length()-o1.length()); //最终写法
System.out.println(list);
}
}
#Map查找表
### Map 查找表 Map体现的结构是一个多行两列的表格,其中左列称为key,右列称为value. - Map总是成对保存数据,并且总是根据key获取对应的value.因此我们可以将查询的条件作为key查询对应的结果作为value保存到Map中. - Map有一个要求:key不允许重复(equals比较的结果) 重复就替换掉原来的value值 java.util.Map接口,是所有Map的顶级接口,规定了Map的相关功能. 常用实现类: - java.util.HashMap:称为散列表,使用散列算法实现的Map,当今查询速度最快的数据结构. - java.util.TreeMap:使用二叉树实现的Map
/** * java.util.Map接口 查找表 * Map体现的结构像是一个多行两列的表格,其中左列称为key,右列称为value * Map总是成对儿(key-value键值对)保存数据,并且总是根据key获取其对应的value * * 常用实现类: * java.util.HashMap:称为散列表,使用散列算法实现的Map,当今查询速度最快的 * 数据结构。 */
import java.util.HashMap;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
/*
V put(K k,V v)
将给定的键值对儿存入Map
Map有一个要求,即:Key不允许重复(Key的equals比较)
因此如果使用重复的key存入value,则是替换value操作,此时put方法
的返回值就是被替换的value。否则返回值为null。
*/
/*
注意,如果value的类型是包装类,切记不要用基本类型接收返回值,
避免因为自动拆箱导致的空指针
*/
Integer value = map.put("语文",99);
System.out.println(value);//null
map.put("数学",98);
map.put("英语",97);
map.put("物理",96);
map.put("化学",98);
System.out.println(map);
value = map.put("物理",66);
System.out.println(value);//96,物理被替换的值
System.out.println(map);
/*
V get(Object key)
根据给定的key获取对应的value。若给定的key不存在则返回值为null
*/
value = map.get("语文");
System.out.println("语文:"+value);
value = map.get("体育");
System.out.println("体育:"+value);//null
int size = map.size();
System.out.println("size:"+size);
/*
V remove(Object key)
删除给定的key所对应的键值对,返回值为这个key对应的value
*/
value = map.remove("语文");
System.out.println(map);
System.out.println(value);
/*
boolean containsKey(Object key)
判断当前Map是否包含给定的key
boolean containsValue(Object value)
判断当前Map是否包含给定的value
*/
boolean ck = map.containsKey("英语");
System.out.println("包含key:"+ck);
boolean cv = map.containsValue(97);
System.out.println("包含value:"+cv);
}
}
##Map的遍历
Map支持三种遍历方式: - 遍历所有的key - 遍历所有的键值对 - 遍历所有的value(相对不常用)
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo2 {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("语文",99);
map.put("数学",98);
map.put("英语",97);
map.put("物理",96);
map.put("化学",98);
System.out.println(map);
/*
遍历所有的key
Set keySet()
将当前Map中所有的key以一个Set集合形式返回。遍历该集合就等同于
遍历了所有的key
*/
Set<String> keySet = map.keySet(); //Map中存入后输出都是无序的元素
// List<String> keySet = map.keySet(); //List中存入的都是有序的元素,
//不适合Map遍历
for(String key : keySet){
System.out.println("key:"+key);
}
/*
遍历每一组键值对
Set<Entry> entrySet()
将当前Map中每一组键值对以一个Entry实例形式存入Set集合后返回。
java.util.Map.Entry
Entry的每一个实例用于表示Map中的一组键值对,其中有两个常用方法:
getKey()和getValue()
*/
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
for(Map.Entry<String,Integer> e : entrySet){
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key+":"+value);
}
}
}
###使用forEach遍历Map
/* JDK8之后集合框架支持了使用lambda表达式遍历。因此Map和Collection都 提供了foreach方法,通过lambda表达式遍历元素。 */
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
*JDK8之后,集合和Map都提供了支持使用lambda表达式遍历的操作
*/
public class ForeachDemo {
public static void main(String[] args) {
//Map的lambda表达式
Map<String,Integer> map = new HashMap<>();
map.put("语文",95);
map.put("数学",94);
map.put("英语",93);
map.put("物理",92);
map.put("化学",91);
System.out.println(map);
//lambda表达式
// map.forEach((String k,Integer v)-> System.out.println(k+":"+v));
map.forEach((k,v)-> System.out.println(k+":"+v));
//该lambda表达式源码底层调用的是MapDemo2中的entrySet
Collection<Integer> values = map.values();
/*
遍历所有的value
Collection values()
将当前Map中所有的value以一个集合形式返回
*/
// for(Integer i : values){
// System.out.println("value:"+i);
// }
/*
集合在使用foreach遍历时并不要求过程中不能通过集合的方法增删元素。
而之前的迭代器则有此要求,否则可能在遍历过程中抛出异常。
*/
values.forEach(
i -> System.out.println("value:"+i)
);
}
}
###lambda表达式简写步骤
//lambda表达式
// map.forEach((String k,Integer v)-> System.out.println(k+":"+v));
map.forEach((k,v)-> System.out.println(k+":"+v));
//该lambda表达式源码底层调用的是MapDemo2中的entrySet
/**
* 1.完整代码
* BiConsumer<String,Integer> action = new BiConsumer<String, Integer>() {
* public void accept(String k, Integer v) {
* System.out.println(k+":"+v);
* }
* };
* Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
* for(Map.Entry<String,Integer> e : entrySet) {
* String k = e.getKey();
* Integer v = e.getValue();
* action.accept(k,v);
* }
*
* 2.Map的forEache方法的回调写法
* BiConsumer<String,Integer> action = new BiConsumer<String, Integer>() {
* public void accept(String k, Integer v) {
* System.out.println(k+":"+v);
* }
* };
* map.forEach(action);//这个等效上面41-46行(可参考forEach源代码)
*
* 3.使用lambda表达式形式创建
* BiConsumer<String,Integer> action =(k,v)->System.out.println(k+":"+v);
* map.forEach(action);
*
* 4.最终写法
* map.forEach((k,v)->System.out.println(k+":"+v));
*/