1 IO流

  • Java如何操作电脑上的文件和文件夹?通过File类对它们进行操作
    因为Java是面向对象的,所以最擅长的就是操作对象,将盘符上的文件/目录的各种信息进行了封装,封装为一个对象,我们程序就可以直接操纵这个对象,通过这个对象获取文件的各种信息,还可以对文件进行创建 ,删除。

1.1 File类

  • 对电脑中的文件进行操作,代码实例:
public class Test {public static void main(String[] args) throws IOException {//将文件封装为一个File类的对象://找一个真实存在的文件即可,不存在那处理的就是空文件File f = new File("d:\\test.txt");File f1 = new File("d:\\test.txt");File f2 = new File("d:/test.txt");//File.separator属性帮我们获取当前操作系统的路径拼接符号//在windows,dos下,系统默认用“\”作为路径分隔符 ,在unix,url中,使用“/”作为路径分隔符。File f3 = new File("d:"+File.separator+"test.txt");//建议使用这种//常用方法:System.out.println("文件是否可读:"+f.canRead());System.out.println("文件是否可写:"+f.canWrite());System.out.println("文件的名字:"+f.getName());//test.txtSystem.out.println("上级目录:"+f.getParent());//d:\System.out.println("是否是一个目录:"+f.isDirectory());System.out.println("是否是一个文件:"+f.isFile());System.out.println("是否隐藏:"+f.isHidden());System.out.println("文件的大小:"+f.length());System.out.println("是否存在:"+f.exists());/*if(f.exists()){//如果文件存在,将文件删除操作f.delete();}else{//如果不存在,就创建这个文件f.createNewFile();}*/System.out.println(f == f1);//false 比较两个对象的地址System.out.println(f.equals(f1));//true 比较两个对象对应的文件的路径//跟路径相关的:System.out.println("绝对路径:"+f.getAbsolutePath());//d:\test.txtSystem.out.println("相对路径:"+f.getPath());//d:\test.txtSystem.out.println("toString:"+f.toString());//d:\test.txtSystem.out.println("----------------------");File f5 = new File("demo.txt");if(!f5.exists()){f5.createNewFile();}//绝对路径指的就是:真实的一个精准的,完整的路径//D:\Projects\JavaProjects\baseProject\demo.txtSystem.out.println("绝对路径:"+f5.getAbsolutePath());//相对路径:有一个参照物,相对这个参照物的路径。//在main方法中,相对位置指的就是:D:\IDEA_workspace\TestJavaSE//在junit的测试方法中,相对路径指的就是模块位置System.out.println("相对路径:"+f5.getPath());//demo.txt//toString的效果永远是  相对路径System.out.println("toString:"+f5.toString());//demo.txtFile f6 = new File("a/b/c/demo.txt");if(!f6.exists()){f6.createNewFile();//如果没有这个目录,会报错}System.out.println("绝对路径:"+f6.getAbsolutePath());System.out.println("相对路径:"+f6.getPath());}
}
  • 对电脑上的目录进行操作,代码实例:
public class Test {public static void main(String[] args) {//将目录封装为File类的对象:File f = new File("D:\\Projects\\JavaProjects");System.out.println("文件是否可读:"+f.canRead());System.out.println("文件是否可写:"+f.canWrite());System.out.println("文件的名字:"+f.getName());//JavaProjectsSystem.out.println("上级目录:"+f.getParent());//D:\ProjectsSystem.out.println("是否是一个目录:"+f.isDirectory());System.out.println("是否是一个文件:"+f.isFile());System.out.println("是否隐藏:"+f.isHidden());System.out.println("文件的大小:"+f.length());System.out.println("是否存在:"+f.exists());System.out.println("绝对路径:"+f.getAbsolutePath());//D:\Projects\JavaProjectsSystem.out.println("相对路径:"+f.getPath());//D:\Projects\JavaProjectsSystem.out.println("toString:"+f.toString());//D:\Projects\JavaProjects//跟目录相关的方法:File f2 = new File("D:\\a\\b\\c");//创建目录://f2.mkdir();//创建单层目录//f2.mkdirs();//创建多层目录//删除:如果是删除目录的话,只会删除一层,并且前提:这层目录是空的,里面没有内容,如果内容就不会被删除f2.delete();//查看:String[] list = f.list();//文件夹下目录/文件对应的名字的数组for(String s:list){System.out.println(s);}System.out.println("=========================");File[] files = f.listFiles();//作用更加广泛for(File file:files){System.out.println(file.getName()+","+file.getAbsolutePath());}}
}

1.2 IO流概述

  • IO流可以对具体文件的内容进行操作。File类的作用是封装文件/目录的各种信息,对目录/文件进行操作,但是我们不可以获取到文件/目录中的内容。
  • I/O : Input/Output的缩写,用于处理设备之间的数据的传输。(形象理解:IO流可以看作是一根 “管道”)
  • I/O流的体系结构

1.3 字符流FileReader和FileWriter

  • FileReaderFileWriter是对文本文件的内容进行操作的IO字符流(通俗点说就是一个操作文本文件的管道)。
  • FileReader负责从电脑上的文本文件读取数据。FileWriter负责向电脑上输出文本文件数据。
  • 注意字符流不能够处理除文本文件外(比如图片,视频等)的文件。
  • 使用代码实例:
//对电脑中某一个文件载入到内存中,然后复制到一个指定位置
public class Test {public static void main(String[] args) throws IOException {//1.指定一个源文件。创建一个File类的对象File f1 = new File("d:\\Test.txt");//2.设置一个目标文件:(这个文件电脑上可有可无)File f2 = new File("d:\\Demo.txt");//3.搞一个输入的管 怼到源文件上:FileReader fr = new FileReader(f1);//4.搞一个输出的管,怼到目标文件上:/* 如果目标文件不存在的话,那么会自动创建此文件。如果目标文件存在的话:new FileWriter(f)   相当于对原文件进行覆盖操作。new FileWriter(f,false)  相当于对源文件进行覆盖操作。不是追加。  new FileWriter(f,true)   对原来的文件进行追加,而不是覆盖。*/FileWriter fw = new FileWriter(f2);//5.开始动作://方式1:一个字符一个字符的复制:/*int n = fr.read();//如果到了文件的结尾处,那么读取的内容为-1,否则为某一个被读取到的字符的Unicode正数while(n!=-1){fw.write(n);n = fr.read();}*///方式2:利用缓冲字符数组:(引入一个“快递员的小车”,这个“小车”一次拉5个快递)/*char[] ch = new char[5];//一次读取五个:返回值是这个数组的有效长度.如果没有就返回-1int len = fr.read(ch);while(len!=-1){fw.write(ch,0,len);//将缓冲数组中有效长度写出len = fr.read(ch);}*///方式3:利用缓冲字符数组,将数组转为String写出。char[] ch = new char[5];int len = fr.read(ch);while(len!=-1){String s = new String(ch,0,len);fw.write(s);len = fr.read(ch);}//6.“管”不用了,就要关闭。关闭流:(关闭流的时候,倒着关闭,后用先关)//流,数据库,网络资源,靠jvm本身没有办法帮我们关闭,此时必须程序员手动关闭。fw.close();fr.close();}
}
  • 一般在真实的使用中,会用TryCatch语句来处理可能会发生的异常:
public class Test {public static void main(String[] args)  {File f1 = new File("d:\\Test.txt");File f2 = new File("d:\\Demo.txt");FileReader fr = null;FileWriter fw = null;try {fr = new FileReader(f1);fw = new FileWriter(f2);char[] ch = new char[5];int len = fr.read(ch);while(len!=-1){String s = new String(ch,0,len);fw.write(s);len = fr.read(ch);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {if(fw!=null){//防止空指针异常fw.close();}} catch (IOException e) {e.printStackTrace();}try {if(fr!=null){//防止空指针异常fr.close();}} catch (IOException e) {e.printStackTrace();}}}
}

1.4 字节流FileInputStream和FileOutputStream

  • FileInputStreamFileOutputStream是对字节文件的内容进行操作的IO字节流。(而FileReader和FileWriter是对文本文件,也就是对字符进行获取)。
  • 使用代码实例
//功能:完成图片的复制:
public class Test {//这是一个main方法,是程序的入口:public static void main(String[] args) throws IOException {//1.有一个源图片File f1 = new File("d:\\LOL.jpg");//2.有一个目标图片:File f2 = new File("d:\\LOL2.jpg");//3.有一个输入的管道 怼 到 源文件:FileInputStream fis = new FileInputStream(f1);//4.有一个输出的管道 怼到  目标文件上:FileOutputStream fos = new FileOutputStream(f2);//5.开始复制:(边读边写)//利用缓冲数组:byte[] b = new byte[1024*8];int len = fis.read(b);while(len!=-1){fos.write(b,0,len);len = fis.read(b);}//6.关闭流:(倒着关闭流,先用后关)fos.close();fis.close();/*细节1:文件是utf-8进行存储的,所以英文字符 底层实际占用1个字节但是中文字符,底层实际占用3个字节。细节2:如果文件是文本文件,那么就不要使用字节流读取了,建议使用字符流。(比如一个字就被分成了三份,没必要)细节3:read()读取一个字节,但是你有没有发现返回值是 int类型,而不是byte类型?read方法底层做了处理,让返回的数据都是“正数”就是为了避免如果字节返回的是-1的话,那到底是读入的字节,还是到文件结尾呢。*/}
}

1.5 处理流BufferedInputStream和BufferedOutputStream

  • 处理流的目的是为了提高程序对文件内容的读写速度,另外硬盘的读写次数是有限的,所以要尽量减少对硬盘的读写次数。(处理流的管道可以加强字符流和字节流的功能,核心思想是让文件一次性从硬盘读取到程序内存缓冲区中后,然后再对缓冲区进行读写操作即可)
  • 原理图解:
    (1)读入一个字节,写出一个字节:(速度最慢)

    (2)利用缓冲字节数组:(速度提升,但还可以更快)

    (3)利用缓冲区:(速度相比前两个更快,即使用处理流BufferedInputStream和BufferedOutputStream)
  • 使用代码实例:
//对电脑中的一个图片文件进行复制
public class Test {public static void main(String[] args) throws IOException {//1.有一个源图片File f1 = new File("d:\\LOL.jpg");//2.有一个目标图片:File f2 = new File("d:\\LOL2.jpg");//3.有一个输入的管道 怼 到 源文件:FileInputStream fis = new FileInputStream(f1);//4.有一个输出的管道 怼到  目标文件上:FileOutputStream fos = new FileOutputStream(f2);//5.功能加强,在FileInputStream外面套一个管:BufferedInputStream:BufferedInputStream bis = new BufferedInputStream(fis);//6.功能加强,在FileOutputStream外面套一个管:BufferedOutputStream:BufferedOutputStream bos = new BufferedOutputStream(fos);//7.开始动作 :byte[] b = new byte[1024*6];int len = bis.read(b);while(len!=-1){bos.write(b,0,len);/* bos.flush(); 底层已经帮我们做了刷新缓冲区的操作,不用我们手动完成:底层调用flushBuffer()*/len = bis.read(b);}//8.关闭流://倒着关://如果处理流包裹着节点流的话,那么其实只要关闭高级流(处理流),那么里面的字节流也会随之被关闭。bos.close();bis.close();}
}
//对文本文件进行复制,过程和图片复制基本一致,区别在于读取操作略有不同
public class Test {public static void main(String[] args) throws IOException {//1.有一个源文件:File f1 = new File("d:\\Test.txt");//2.有一个目标文件:File f2 = new File("d:\\Demo.txt");//3.需要一个管 怼到 源文件:FileReader fr = new FileReader(f1);//4.需要一根管怼到目标文件:FileWriter fw = new FileWriter(f2);//5.套一根管在输入字符流外面:BufferedReader br = new BufferedReader(fr);//6.套一根管在输出字符流外面:BufferedWriter bw = new BufferedWriter(fw);//7.开始动作://方式1:读取一个字符,输出一个字符:/*int n = br.read();while(n!=-1){bw.write(n);n = br.read();}*///方式2:利用缓冲数组:/*char[] ch = new char[30];int len = br.read(ch);while(len!=-1){bw.write(ch,0,len);len = br.read(ch);}*///方式3:读取String:String str = br.readLine();//每次读取文本文件中一行,返回字符串while(str!=null){bw.write(str);//在文本文件中应该再写出一个换行:bw.newLine();//新起一行str = br.readLine();}//8.关闭流bw.close();br.close();}
}

1.6 转换流InputStreamReader和OutputStreamWriter(使用很少)

  • 转换流的作用是将字节流和字符流进行转换
    InputStreamReader :字节输入流转化为字符的输入流
    OutputStreamWriter : 字符输出流转化为字节的输出流
  • 转换流属于字符流
  • 原理图解:
  • 使用代码实例:
//对文本文件进行读取操作
public class Test {public static void main(String[] args) throws IOException {//1.有一个源文件File f1 = new File("d:\\Test.txt");//2.有一个目标文件:File f2 = new File("d:\\Demo.txt");//3.输入方向:需要一个输入的字节流接触文件:FileInputStream fis = new FileInputStream(f1);//3.加入一个转换流,将字节流转换为字符流:(转换流属于一个处理流)//将字节转换为字符的时候,需要指定一个编码,这个编码跟文件本身的编码格式统一//如果编码格式不统一的话,那么在控制台上展示的效果就会出现乱码InputStreamReader isr = new InputStreamReader(fis,"utf-8");//默认的话是获取程序本身的编码--》utf-8//InputStreamReader isr = new InputStreamReader(fis);//4.输出方向:FileOutputStream fos = new FileOutputStream(f2);OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");//5.开始动作:char[] ch = new char[20];int len = isr.read(ch);while(len!=-1){osw.write(ch,0,len);len = isr.read(ch);}//6.关闭流:osw.close();isr.close();}
}

1.7 System类对IO流的支持

  • System的属性:
    System.in : “标准”输入流。—》默认情况下 从键盘输入
    System.out :“标准”输出流。 —》默认情况下,输出到控制台。
  • System类对IO流操作的代码实例
//对System.in和System.out的使用和理解
public class Test {public static void main(String[] args) throws IOException {//System.in得到的是标准的输入流:--》从键盘输入://InputStream in = System.in;//调用方法://int n = in.read();//read方法等待键盘的录入,所以这个方法是一个阻塞方法。//System.out.println(n);//以前案例:从键盘录入一个int类型的数据://从上面的代码证明,键盘录入实际上是:System.in//形象的理解:System.in管,这个管怼到键盘上去了,所以你从键盘录入的话,就从这个管到程序中了//Scanner的作用:扫描器:起扫描作用的,扫键盘的从这根管出来的数据/*Scanner sc = new Scanner(System.in);int i = sc.nextInt();System.out.println(i);*///既然Scanner是扫描的作用,不一定非得扫 System.in进来的东西,还可以扫描其他管的内容:Scanner sc = new Scanner(new FileInputStream(new File("d:\\Test.txt")));while(sc.hasNext()){System.out.println(sc.next());}//System.out  : 返回的输出流 、 打印流(PrintStream)写到控制台:PrintStream out = System.out;//调用方法:out.print("你好1");//直接在控制台写出,但是不换行out.print("你好2");out.print("你好3");out.print("你好4");out.println("我是中国人1");//直接在控制台写出,并且换行操作out.println("我是中国人2");out.println("我是中国人3");out.println("我是中国人4");System.out.println("你是");System.out.print("中国人");}
}
//案例:把从控制台中输入的内容复制保存到电脑文件中去
public class Test {public static void main(String[] args) throws IOException {//1.先准备输入方向://键盘录入:InputStream in = System.in;//属于字节流//字节流--》字符流:InputStreamReader isr = new InputStreamReader(in);//在isr外面再套一个缓冲流:BufferedReader br = new BufferedReader(isr);//2.再准备输出方向://准备目标文件File f = new File("d:\\Demo1.txt");FileWriter fw = new FileWriter(f);BufferedWriter bw = new BufferedWriter(fw);//3.开始动作:String s = br.readLine();while(!s.equals("exit")){bw.write(s);bw.newLine();//文件中换行s = br.readLine();}//4.关闭流:bw.close();br.close();}
}

把从控制台中输入的内容复制保存到电脑文件中去案例的图解:

1.8 数据流DataInputStream和DataOutputStream

  • 数据流是专门用来操作基本数据类型(8种)和字符串的流。作用是把Java程序中的基本数据类型和字符串保存在电脑的磁盘中(这个文件人类读不懂,但是计算机读得懂)。
    DataInputStream:将文件中存储的基本数据类型和字符串写入内存的变量中。
    DataOutputStream:将内存中的基本数据类型和字符串的变量写出到文件中。
  • 使用代码实例:
public class Test {public static void main(String[] args) throws IOException {//DataOutputStream:  将内存中的基本数据类型和字符串的变量 写出  文件中/*File f = new File("d:\\Demo2.txt");FileOutputStream fos = new FileOutputStream(f);DataOutputStream dos = new DataOutputStream(fos);*/DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File("d:\\Demo2.txt")));//向外将变量写到文件中去:dos.writeUTF("你好");dos.writeBoolean(false);dos.writeDouble(6.9);dos.writeInt(82);//关闭流:dos.close();}
}

运行结果如下:发现看不懂。(因为是给程序看的,没打算给人看,只是找个地方把这个数据存着)

所以我们可以通过程序读取这个数据

public class Test {public static void main(String[] args) throws IOException {//DataInputStream:将文件中存储的基本数据类型和字符串  写入  内存的变量中DataInputStream dis = new DataInputStream(new FileInputStream(new File("d:\\Demo2.txt")));//将文件中内容读取到程序中来:System.out.println(dis.readUTF());System.out.println(dis.readBoolean());System.out.println(dis.readDouble());System.out.println(dis.readInt());//关闭流:dis.close();}
}

运行结果如下:发现成功读出本来的内容。(要求:写出的类型跟读入的类型必须要匹配!)

1.9 对象流ObjectInputStream和ObjectInputStream

  • 对象流就是将程序中的引用数据类型数据存储到电脑文件上,然后再从电脑文件上读取到程序中的一种技术。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
  • 在使用对象流之前,要对这个数据类型进行序列化和反序列化,否则无法使用。
  • 序列化和反序列化:
    序列化是使用ObjectOutputStream 类 : 把内存中的Java对象转换成平台无关的二进制数据,从而允许把这种二进制数据持久地保存在磁盘上,或通过网络将这种二进制数据传输到另一个网络节点。
    反序列化是使用ObjectInputStream类 : 当其它程序获取了这种二进制数据,就可以恢复成原来的Java对象。
  • 使用对象流的类必须实现序列化接口Serializable,这个接口没有内容,作为一个标识表示这个类是一个可以序列化的类。(字符串可以直接使用对象流,因为打开字符串源码发现已经实现了Serializable接口)
  • serialVersionUID:凡是实现Serializable接口(标识接口)的类都有一个表示序列化版本标识符的静态常量:
    (1)private static final long serialVersionUID;
    (2)serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序加化时是否兼容。
    (3)如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。
    简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
  • 使用代码实例:
class Person implements Serializable {String name;int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}
}
public class Test {public static void main(String[] args) throws IOException {//序列化:将内存中对象 ---》 文件://有一个对象:Person p = new Person("lili",19);//有对象流:ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:\\Demo4.txt")));//向外写:oos.writeObject(p);//关闭流:oos.close();}
}

测试:发现序列化成功,Person具备了序列化的能力。

同理,这个文件是给程序看的,不是给人看的,所以程序可以用对象流进行读取

public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:\\Demo4.txt")));//读入内存:Person p = (Person)(ois.readObject());System.out.println(p/*.toString()*/);//关闭流:ois.close();}
}

结果读取成功,证明反序列化成功:
发现这个Person没有重写ToString方法,我们对这个类进行toString方法的重写修改之后,再次运行读取电脑上的这个文件对象,发现报错。

这是因为程序对原来的Person进行序列化时会默认生成一个serialVersionUID号,在反序列化过程中,程序会找是否存在这个serialVersionUID号,如果有才可以反序列化成功。但是在重写toString方法后,这个serialVersionUID号会改变。所以反序列化找不到对应的Person,从而发生报错。

解决方法:显示的写入serialVersionUID号即可。

class Person implements Serializable {private static final long serialVersionUID = 7311953400778740171L;String name;int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}
}

测试发现运行成功:

Person{name=‘lili’, age=19}

  • 序列化细节:
    (1)被序列化的类的内部的所有属性,必须是可序列化的 (基本数据类型都是可序列化的)


    (2)static,transient修饰的属性 不可以被序列化。(所以比如什么密码等敏感信息就使用这些修饰符修饰,这样就不会存储在电脑中防止安全事故。)
    EG:
public class Person implements Serializable {private static final long serialVersionUID = 8027651838638826533L;private transient String name;private static int age;private Famaily f = new Famaily();public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Person() {}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", f=" + f + ",age=" + age +'}';}
}

2 多线程

2.1 进程及其相关概念

  • 程序,进程,线程的概念
    (1)程序(program):是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的)
    (2)进程(process):是程序的一次执行过程(进程是动态的 )。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 另外进程有它的生命周期(有自身的产生、存在和消亡的过程 )
    (3)线程(thread):进程可进一步细化为线程, 是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。
  • 单核CPU与多核CPU的任务执行
    (1)单核CPU是指电脑只有一个CPU。CPU在运行的时候,是按照时间片来执行的,一个时间片只能执行一个线程因为时间片特别短,所以从用户宏观感受到的就是“同一个时间”执行了多个线程,实际这个多线程是一种假象。这种运行方式我们称之为“并发”。
    (2)多核CPU是指有多个CPU了同时运行。这个时候才能真正做到同一个时间执行了多个线程。这种运行方式我们也称之为“并行”。

2.2 创建线程的三种方式

  • 创建线程的三种方式中,第二种方法最常用。
  • 首先先注意:在没有使用线程类之前,也是有三个线程同时执行的。

2.2.1 通过继承Thread类来创建线程:

(1)代码使用实例:

//如果一个类想要具备抢夺资源,也就是多线程的能力,就继承一个类:Thread
public class TestThread extends Thread{/*一会线程对象就要开始争抢资源了,这个线程要执行的任务到底是啥?这个任务你要放在方法中但是这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法然后线程的任务/逻辑写在run方法中*/@Overridepublic void run() {for(int i=0;i<20;i++){System.out.println(this.getName()+":  "+i);}}
}
class Demo{public static void main(String[] args) {//给main方法这个主线程设置名字://Thread.currentThread()作用获取当前正在执行的线程Thread.currentThread().setName("主线程");for(int i=0;i<20;i++){System.out.println(Thread.currentThread().getName()+":  "+i);}//制造其他线程,要跟主线程争抢资源://具体的线程对象:子线程threadThread thread = new Thread();thread.setName("子线程1");//给子线程thread设置线程//thread.run();//这个run方法不能直接调用,直接调用就会被当做一个普通方法//想要thread子线程真正起作用是调用start()方法启动线程:thread.start();//start()是Thread类中的方法//主线程中也要输出十个数:for(int i=0;i<20;i++){System.out.println(Thread.currentThread().getName()+":  "+i);}}
}

(2)火车票买票案例:

public class BuyTicketThread extends Thread {public BuyTicketThread(String name){super(name);}//一共10张票:static int ticketNum = 10;//多个对象共享10张票//每个窗口都是一个线程对象:每个对象执行的代码放入run方法中@Overridepublic void run() {//每个窗口后面有100个人在抢票:for (int i = 1; i <= 100 ; i++) {if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票System.out.println("我在"+this.getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");}}}
}
class Demo{public static void main(String[] args) {//多个窗口抢票:三个窗口三个线程对象:BuyTicketThread t1 = new BuyTicketThread("窗口1");t1.start();BuyTicketThread t2 = new BuyTicketThread("窗口2");t2.start();BuyTicketThread t3 = new BuyTicketThread("窗口3");t3.start();}
}

2.2.2 通过实现Runnable接口创建线程

(1)代码使用实例:

public class TestThread implements Runnable{@Overridepublic void run() {//输出1-10数字:for (int i = 1; i <= 10 ; i++) {System.out.println(Thread.currentThread().getName()+"----"+i);}}
}
class Test {public static void main(String[] args) {//创建子线程对象:TestThread tt = new TestThread();Thread t = new Thread(tt, "子线程");t.start();//主线程里面也是打印1-10数字:for (int i = 1; i <= 10 ; i++) {System.out.println(Thread.currentThread().getName()+"---"+i);}}
}

(2)火车票买票案例:

class BuyTicketThread implements Runnable {int ticketNum = 10;@Overridepublic void run() {for (int i = 1; i <= 100 ; i++) {if(ticketNum > 0){System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");}}}
}
public class Test {public static void main(String[] args) {//定义一个线程对象:BuyTicketThread t = new BuyTicketThread();//窗口1买票:Thread t1 = new Thread(t,"窗口1");t1.start();//窗口2买票:Thread t2 = new Thread(t,"窗口2");t2.start();//窗口3买票:Thread t3 = new Thread(t,"窗口3");t3.start();}
}
  • 实际开发中,方式1 继承Thread类 还是 方式2 实现Runnable接口这种方式多呢?方式2使用多,理由如下:
    (1)方式1的话有 Java单继承的局限性,因为继承了Thread类,就不能再继承其它的类了。
    (2)方式2的共享资源的能力也会强一些,不需要非得加个static来修饰。
  • Thread类和Runnable接口的联系:

2.2.3 通过实现Callable接口创建线程:

  • 引入原因:对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法,但是这个run有些问题:没有返回值,也不能抛出异常。所以JDK1.5后新增实现Callable接口方法,但缺点是创建比较麻烦
  • 使用代码实例:
public class TestRandomNum implements Callable<Integer> {/*1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型2.如果带泛型,那么call的返回值就是泛型对应的类型3.从call方法看到:方法有返回值,可以跑出异常*/@Overridepublic Integer call() throws Exception {return new Random().nextInt(10);//返回10以内的随机数}
}
class Test{public static void main(String[] args) throws ExecutionException, InterruptedException {//定义一个线程对象:TestRandomNum trn = new TestRandomNum();FutureTask ft = new FutureTask(trn);Thread t = new Thread(ft);t.start();//获取线程得到的返回值:Object obj = ft.get();System.out.println(obj);}
}

2.3 线程的生命周期

2.4 线程的常见方法

2.4.1 基本方法

(1)start() : 启动当前线程,表面上调用start方法,实际在调用线程里面的run方法
(2)run() : 线程类 继承 Thread类 或者 实现Runnable接口的时候,都要重新实现这个run方法,run方法里面是线程要执行的内容
(3)currentThread :Thread类中一个静态方法:获取当前正在执行的线程
(4)setName 设置线程名字
(5)getName 读取线程名字
(6)stop 停止线程

2.4.2 设置优先级

在有很多个线程的情况下,会采取以下情形选择执行线程:
(1)同优先级别的线程,采取的策略就是先到先服务,使用时间片策略
(2)如果优先级别高,被CPU调度的概率就高
(3)级别:1-10 默认的级别为5
使用代码实例:

public class TestThread01 extends Thread {@Overridepublic void run() {for (int i = 1; i <= 10; i++) {System.out.println(i);}}
}
class TestThread02 extends Thread{@Overridepublic void run() {for (int i = 20; i <= 30 ; i++) {System.out.println(i);}}
}
class Test{public static void main(String[] args) {//创建两个子线程,让这两个子线程争抢资源:TestThread01 t1 = new TestThread01();t1.setPriority(10);//优先级别高t1.start();TestThread02 t2 = new TestThread02();t2.setPriority(1);//优先级别低t2.start();}
}

2.4.3 join方法:

当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。(注意:必须先start,再join才有效。)

public class TestThread extends Thread {public TestThread(String name){super(name);}@Overridepublic void run() {for (int i = 1; i <= 10 ; i++) {System.out.println(this.getName()+"----"+i);}}
}
class Test{public static void main(String[] args) throws InterruptedException {for (int i = 1; i <= 100 ; i++) {System.out.println("main-----"+i);if(i == 6){//创建子线程:TestThread tt = new TestThread("子线程");tt.start();tt.join();//“半路杀出个程咬金”}}}
}

2.4.4 sleep方法 : 人为的制造阻塞事件

  • 使用代码实例:
public class Test {public static void main(String[] args) {System.out.println("程序等待中。。。。");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("程序启动了");}
}
  • 案例:完成秒表功能
public class Test {public static void main(String[] args) {//2.定义一个时间格式:DateFormat df = new SimpleDateFormat("HH:mm:ss");while(true){//1.获取当前时间:Date d = new Date();//3.按照上面定义的格式将Date类型转为指定格式的字符串:System.out.println(df.format(d));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

2.4.5 setDamon方法:设置为伴随线程

  • 作用:将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也不要继续执行了
  • 使用代码实例:
public class TestThread extends Thread {@Overridepublic void run() {for (int i = 1; i <= 1000 ; i++) {System.out.println("子线程----"+i);}}
}
class Test{public static void main(String[] args) {//创建并启动子线程:TestThread tt = new TestThread();tt.setDaemon(true);//设置伴随线程  注意:先设置,再启动tt.start();//主线程中还要输出1-10的数字:for (int i = 1; i <= 10 ; i++) {System.out.println("main---"+i);}}
}

2.5 线程的安全问题

2.5.1 使用线程类可能出现的问题

  • 在前面的例子中会出现线程安全的问题。原因是多个线程,在争抢资源的过程中,导致共享的资源出现问题。一个线程还没执行完,另一个线程就参与进来了,开始争抢。
    (1)出现了两个10张票或者3个10张票:

    (2)票出现0,-1,-2的可能:

2.5.2 安全问题解决方法:锁

  • 解决方法:就是在可能出现问题的地方加一个(也叫同步或者同步监视器),在锁中加入原子性的代码(也就是只能一个线程全部执行完之后才能让其他线程执行)
  • 加入锁一共有三中方法:使用synchronized同步代码块、使用synchronized同步方法和使用lock锁

(1)使用synchronized同步代码块(引用买火车票的例子)

public class BuyTicketThread extends Thread {public BuyTicketThread(String name){super(name);}//一共10张票:static int ticketNum = 10;//多个对象共享10张票//每个窗口都是一个线程对象:每个对象执行的代码放入run方法中@Overridepublic void run() {//每个窗口后面有100个人在抢票:for (int i = 1; i <= 100 ; i++) {//括号中就是锁,锁在使用时必须多个线程用的是同一把锁!!!synchronized (BuyTicketThread.class) {//字节码文件只有一个if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票System.out.println("我在" + this.getName() + "买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");}}}}
}
class Demo{public static void main(String[] args) {//多个窗口抢票:三个窗口三个线程对象:BuyTicketThread t1 = new BuyTicketThread("窗口1");t1.start();BuyTicketThread t2 = new BuyTicketThread("窗口2");t2.start();BuyTicketThread t3 = new BuyTicketThread("窗口3");t3.start();}
}

(2)使用synchronized同步方法

class BuyTicketThread extends Thread {public BuyTicketThread(String name){super(name);}//一共10张票:static int ticketNum = 10;//多个对象共享10张票//每个窗口都是一个线程对象:每个对象执行的代码放入run方法中@Overridepublic void run() {//每个窗口后面有100个人在抢票:for (int i = 1; i <= 100 ; i++) {buyTicket();}}//同步方法。锁住的同步监视器是本类的字节码: BuyTicketThread.classpublic synchronized void buyTicket(){if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票System.out.println("我在" + this.getName() + "买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");}}
}
public class Test{public static void main(String[] args) {//多个窗口抢票:三个窗口三个线程对象:BuyTicketThread t1 = new BuyTicketThread("窗口1");t1.start();BuyTicketThread t2 = new BuyTicketThread("窗口2");t2.start();BuyTicketThread t3 = new BuyTicketThread("窗口3");t3.start();}
}

(3)使用lock锁

  • Lock锁,是JDK1.5后新增新一代的线程同步方式。与采用synchronized相比,lock可提供多种锁方案,更灵活。synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。
class BuyTicketThread implements Runnable {int ticketNum = 10;//拿来一把锁:Lock lock = new ReentrantLock();//多态  接口=实现类  可以使用不同的实现类@Overridepublic void run() {//此处有1000行代码for (int i = 1; i <= 100 ; i++) {//打开锁:lock.lock();try{if(ticketNum > 0){System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");}}catch (Exception ex){ex.printStackTrace();}finally {//关闭锁:--->即使有异常,这个锁也可以得到释放lock.unlock();}}//此处有1000行代码}
}
public class Test {public static void main(String[] args) {//定义一个线程对象:BuyTicketThread t = new BuyTicketThread();//窗口1买票:Thread t1 = new Thread(t,"窗口1");t1.start();//窗口2买票:Thread t2 = new Thread(t,"窗口2");t2.start();//窗口3买票:Thread t3 = new Thread(t,"窗口3");t3.start();}
}

2.5.3 锁使用的注意和总结

(1)关于synchronized关键字

  • 标识同步监视器的语法是synchronized(同步监视器){ }。其中同步监视器的类型必须是引用数据类型,不能是基本数据类型。
  • 一般使用共享资源做同步监视器即可。
  • 一般建议使用final修饰同步监视器 。
  • 火车站案例同步代码块的执行过程:
    1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码。
    2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open。
    3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态。
    4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open。
    5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)。
    强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close)

(2)关于同步方法

  • 非静态同步方法的同步监视器是this,静态同步方法的同步监视器是 类名.class 字节码信息对象。
  • 同步代码块的效率要高于同步方法。因为同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部。另一个是同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法,而同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块。

(3)关于Lock锁和synchronized的区别
1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

(4)使用锁后可能发生的死锁问题:

  • 原因:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
  • 表现:出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
  • 解决方法是:减少同步资源的定义,避免嵌套同步。
  • 死锁代码实例:
public class TestDeadLock implements Runnable {public int flag = 1;static Object o1 = new Object(),o2 = new Object();   public void run(){System.out.println("flag=" + flag);// 当flag==1锁住o1if (flag == 1) {synchronized (o1) {try {Thread.sleep(500);} catch (Exception e) {e.printStackTrace();}// 只要锁住o2就完成synchronized (o2) {System.out.println("2");}}}// 如果flag==0锁住o2if (flag == 0) {synchronized (o2) {try {Thread.sleep(500);} catch (Exception e) {e.printStackTrace();}// 只要锁住o1就完成synchronized (o1) {System.out.println("3");}}}}   public static void main(String[] args) {// 实例2个线程类TestDeadLock td1 = new TestDeadLock();TestDeadLock td2 = new TestDeadLock();td1.flag = 1;td2.flag = 0;// 开启2个线程Thread t1 = new Thread(td1);Thread t2 = new Thread(td2);t1.start();t2.start();}
}

2.6 线程的通信问题

  • 线程的通信问题就是指线程之间可能因为某种条件或者业务需要有一个先后执行的顺序。
  • 方法是在锁的基础上增加一个等待锁wait()方法和notify(),notifyAll()方法。
  • 在Java对象中,有两种池:锁池 synchronized() 和 等待池 wait(),notify(),notifyAll()。
    如果一个线程调用了某个对象的wait方法,那么线程进入到该对象的等待池中(并且已经将锁释放),如果未来的某一个时刻,另一个线程调用了相同对象的notify方法或者notifyAll方法,那么该等待池中的线程就会被唤起,然后进入到对象的锁池里面去获得该对象的锁,如果获得锁成功后,那么该线程就会沿着wait方法之后的路径继续执行。注意是沿着wait方法之后。
  • 注意
    (1)wait方法和notify方法 是必须放在同步方法或者同步代码块中才生效的 (因为在同步的基础上进行线程的通信才是有效的)
    (2)sleep和wait的区别:sleep进入阻塞状态没有释放锁,wait进入阻塞状态但是同时释放了锁
  • JDK1.5之后通信有更强大的方法,Condition是在Java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。
    它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition(一个Condition包含一个等待队列。一个Lock可以产生多个Condition,所以可以有多个等待队列。)
    Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。
    调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
    · Conditon中的await()对应Object的wait();
    · Condition中的signal()对应Object的notify();
    · Condition中的signalAll()对应Object的notifyAll()。
    与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:
    · 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者
    · 其他某个线程调用此 Condition 的 signalAll() 方法;或者
    · 其他某个线程中断当前线程,且支持中断线程的挂起;或者
    · 发生“虚假唤醒”
    在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证它保持此锁。
    void signal():唤醒一个等待线程。
    如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。
    void signalAll():唤醒所有等待线程。
    如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。
  • 使用代码实例:
    应用场景:生产者和消费者问题
    假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
    如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
    如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
    结果实例:

    原理:
class Product {//商品类//品牌private String brand;//名字private String name;//声明一个Lock锁:Lock lock = new ReentrantLock();//搞一个生产者的等待队列:Condition produceCondition = lock.newCondition();//搞一个消费者的等待队列:Condition consumeCondition = lock.newCondition();//引入一个灯:true:红色  false 绿色boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费//setter,getter方法;public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public String getName() {return name;}public void setName(String name) {this.name = name;}//生产商品public void setProduct(String brand,String name){lock.lock();try{if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费try {//wait();//生产者阻塞,生产者进入等待队列中produceCondition.await();} catch (InterruptedException e) {e.printStackTrace();}}//灯是绿色的,就生产:this.setBrand(brand);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.setName(name);//将生产信息做一个打印:System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());//生产完以后,灯变色:变成红色:flag = true;//告诉消费者赶紧来消费://notify();consumeCondition.signal();}finally {lock.unlock();}}//消费商品:public void getProduct(){lock.lock();try{if(!flag){//flag == false没有商品,等待生产者生产:try {// wait();//消费者等待,消费者线程进入等待队列:consumeCondition.await();} catch (InterruptedException e) {e.printStackTrace();}}//有商品,消费:System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());//消费完:灯变色:flag = false;//通知生产者生产://notify();produceCondition.signal();}finally {lock.unlock();}}
}
class CustomerThread extends Thread{//消费者线程//共享商品:private Product p;public CustomerThread(Product p) {this.p = p;}@Overridepublic void run() {for (int i = 1; i <= 10 ; i++) {//i:消费次数p.getProduct();;}}
}
class ProducerThread extends Thread{//生产者线程//共享商品:private Product p;public ProducerThread(Product p) {this.p = p;}@Overridepublic void run() {for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数if(i % 2 == 0){p.setProduct("费列罗","巧克力");}else{p.setProduct("哈尔滨","啤酒");}}}
}
public class Test {public static void main(String[] args) {//共享的商品:Product p = new Product();//创建生产者和消费者线程:ProducerThread pt = new ProducerThread(p);CustomerThread ct = new CustomerThread(p);pt.start();ct.start();}
}

3 网络编程

3.1 引入和基本概念

  • 网络编程就是把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源
  • IP地址和PORT端口号是网络编程中寻找某一台计算机其中线程的定位方法。IP地址确定是在哪一个电脑上(每一台机器只要连接了网络都有IP地址),PORT确定是这一台电脑的哪一个进程(每一个程序都有自己的端口号)。
  • 网络编程有两种,一种是基于传输安全可信的TCP网络编程,一种是基于效率优先,但是不安全的UDP网络编程。

3.2 InetAddress和InetSocketAddress

  • 如同File类封装一个文件或者文件夹一样,InetAddress 封装了IP ,InetSocketAddress封装了IP和端口号
  • 使用代码实例
public class Test {public static void main(String[] args) throws UnknownHostException {//封装IP://InetAddress ia = new InetAddress();不能直接创建对象,因为InetAddress()被default修饰了。InetAddress ia = InetAddress.getByName("192.168.199.217");System.out.println(ia);InetAddress ia2 = InetAddress.getByName("localhost");//localhost指代的是本机的ip地址System.out.println(ia2);InetAddress ia3 = InetAddress.getByName("127.0.0.1");//127.0.0.1指代的是本机的ip地址System.out.println(ia3);InetAddress ia4 = InetAddress.getByName("DESKTOP-G3QETFP");//封装计算机名System.out.println(ia4);InetAddress ia5 = InetAddress.getByName("www.mashibing.com");//封装域名System.out.println(ia5);System.out.println(ia5.getHostName());//获取域名System.out.println(ia5.getHostAddress());//获取ip地址InetSocketAddress isa = new InetSocketAddress("192.168.199.217",8080);System.out.println(isa);System.out.println(isa.getHostName());System.out.println(isa.getPort());InetAddress ia6 = isa.getAddress();System.out.println(ia6.getHostName());System.out.println(ia6.getHostAddress());}
}

3.3 基于TCP的网络编程

  • 代码实例:模拟网站的登录,客户端录入账号密码,然后服务器端进行验证。
    用户封装对象
public class User implements Serializable {private static final long serialVersionUID = 9050691344308365540L;private String name;private String pwd;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;}public User(String name, String pwd) {this.name = name;this.pwd = pwd;}
}

客户端

public class TestClient {//客户端//这是一个main方法,是程序的入口:public static void main(String[] args) throws IOException {//1.创建套接字:指定服务器的ip和端口号:Socket s = new Socket("192.168.199.217",8888);//录入用户的账号和密码:Scanner sc = new Scanner(System.in);System.out.println("请录入您的账号:");String name = sc.next();System.out.println("请录入您的密码:");String pwd = sc.next();//将账号和密码封装为一个User的对象:User user = new User(name,pwd);//2.对于程序员来说,向外发送数据 感受 --》利用输出流:OutputStream os = s.getOutputStream();ObjectOutputStream oos = new ObjectOutputStream(os);oos.writeObject(user);//接收服务器端的回话--》利用输入流:InputStream is = s.getInputStream();DataInputStream dis = new DataInputStream(is);boolean b = dis.readBoolean();if(b){System.out.println("恭喜,登录成功");}else{System.out.println("对不起,登录失败");}//3.关闭流  +  关闭网络资源:dis.close();is.close();oos.close();os.close();s.close();}
}

服务器

public class TestServer {//服务器//这是一个main方法,是程序的入口:public static void main(String[] args) throws IOException, ClassNotFoundException {//1.创建套接字: 指定服务器的端口号ServerSocket ss = new ServerSocket(8888);//2.等着客户端发来的信息:Socket s = ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。//accept()返回值为一个Socket,这个Socket其实就是客户端的Socket//接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了//3.感受到的操作流:InputStream is = s.getInputStream();ObjectInputStream ois = new ObjectInputStream(is);//4.读取客户端发来的数据:User user = (User)(ois.readObject());//对对象进行验证:boolean flag = false;if(user.getName().equals("娜娜")&&user.getPwd().equals("123123")){flag = true;}//向客户端输出结果:---》操作流---》输出流OutputStream os = s.getOutputStream();DataOutputStream dos = new DataOutputStream(os);dos.writeBoolean(flag);//5.关闭流+关闭网络资源:dos.close();os.close();ois.close();is.close();s.close();ss.close();}
}

注意要先开服务端,然后再开客户端。

  • 遗留问题:服务器针对一个请求服务,之后服务器就关闭了(程序自然结束了)现在需要解决:服务器必须一直在监听 ,一直开着,等待客户端的请求(在以上代码中,客户端不用动了,只需要修改服务器代码即可)
public class ServerThread extends Thread {//线程:专门处理客户端的请求InputStream is = null;ObjectInputStream ois = null;OutputStream os = null;DataOutputStream dos = null;Socket s = null;public ServerThread(Socket s){this.s = s;}@Overridepublic void run() {try{//2.等着客户端发来的信息:is = s.getInputStream();ois = new ObjectInputStream(is);//4.读取客户端发来的数据:User user = (User)(ois.readObject());//对对象进行验证:boolean flag = false;if(user.getName().equals("娜娜")&&user.getPwd().equals("123123")){flag = true;}//向客户端输出结果:---》操作流---》输出流os = s.getOutputStream();dos = new DataOutputStream(os);dos.writeBoolean(flag);}catch (IOException | ClassNotFoundException e) {e.printStackTrace();}finally {try {if(dos!=null){dos.close();}} catch (IOException e) {e.printStackTrace();}try {if(os!=null){os.close();}} catch (IOException e) {e.printStackTrace();}try {if(ois!=null){ois.close();}} catch (IOException e) {e.printStackTrace();}try {if(is!=null){is.close();}} catch (IOException e) {e.printStackTrace();}}}
}
public class TestServer {//服务器//这是一个main方法,是程序的入口:public static void main(String[] args) {System.out.println("服务器启动了");//1.创建套接字: 指定服务器的端口号ServerSocket ss = null;Socket s = null;int count = 0;//定义一个计数器,用来计数  客户端的请求try {ss = new ServerSocket(8888);while(true){//加入死循环,服务器一直监听客户端是否发送数据s = ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。//每次过来的客户端的请求 靠 线程处理:new ServerThread(s).start();count++;//输入请求的客户端的信息:System.out.println("当前是第"+count+"个用户访问我们的服务器,对应的用户是:"+s.getInetAddress());}} catch (IOException  e) {e.printStackTrace();}}
}

3.4 基于UDP的网络编程

  • 代码实例(老师答疑对话框)
public class TestSend {//发送方://这是一个main方法,是程序的入口:public static void main(String[] args)  {System.out.println("学生上线。。。");//1.准备套接字: 指定发送方的端口号DatagramSocket ds = null;try {ds = new DatagramSocket(8888);while(true){//2.准备数据包Scanner sc = new Scanner(System.in);System.out.print("学生:");String str = sc.next();byte[] bytes = str.getBytes();/*需要四个参数:1.指的是传送数据转为Z字节数组2.字节数组的长度3.封装接收方的ip4.指定接收方的端口号*/DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),9999);//发送:ds.send(dp);if(str.equals("byebye")){System.out.println("学生下线。。");break;}//接收老师发送回来的信息:byte[] b = new byte[1024];DatagramPacket dp2 = new DatagramPacket(b,b.length);ds.receive(dp2);//接收完以后 dp2里面就填充好内容了//取出数据:byte[] data = dp2.getData();String s = new String(data,0,dp2.getLength());//dp.getLength()数组包中的有效长度System.out.println("老师对我说:"+s);}} catch (SocketException e) {e.printStackTrace();} catch (UnknownHostException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {//关闭资源ds.close();}}
}
public class TestReceive {//接收方//这是一个main方法,是程序的入口:public static void main(String[] args){System.out.println("老师上线了。。");//1.创建套接字:指定接收方的端口DatagramSocket ds = null;try {ds = new DatagramSocket(9999);while(true){//2.有一个空的数据包,打算用来接收  对方传过来的数据包:byte[] b = new byte[1024];DatagramPacket dp = new DatagramPacket(b,b.length);//3.接收对方的数据包,然后放入我们的dp数据包中填充ds.receive(dp);//接收完以后 dp里面就填充好内容了//4.取出数据:byte[] data = dp.getData();String s = new String(data,0,dp.getLength());//dp.getLength()数组包中的有效长度System.out.println("学生对我说:"+s);if(s.equals("byebye")){System.out.println("学生已经下线了,老师也下线。。。");break;}//老师进行回复:Scanner sc = new Scanner(System.in);System.out.print("老师:");String str = sc.next();byte[] bytes = str.getBytes();//封装数据,并且指定学生的ip和端口号DatagramPacket dp2 = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),8888);//发送:ds.send(dp2);}} catch (SocketException e) {e.printStackTrace();} catch (UnknownHostException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {//5.关闭资源:ds.close();}}
}

IO流,多线程,网络编程(4)JavaSE相关推荐

  1. Java的IO流与网络编程

    目录 一.概述 二.文件类(File) 1. File类的构造.获取属性 2. File类获取子文件或目录 3. File类文件重命名 4. File类的判断功能 5. File类创建.删除功能 三. ...

  2. JAVA的IO流 和网络编程 超详细每行代码都有备注

    IO流: {     文件的操作: f1.getAbsolutePath();//获取绝对路径               f1.getPath();//获取相对路径               f1 ...

  3. java基础知识总结:基础知识、面向对象、集合框架、多线程、jdk1.5新特性、IO流、网络编程

    目录 一.基础知识: 二.面向对象 三.集合框架 四.多线程: 五.jdk1.5的新特性 六.IO流 七.网络编程: 一.基础知识: 1.JVM.JRE和JDK的区别: JVM(Java Virtua ...

  4. 泛型、IO流 和 网络编程

    文章目录

  5. day16多线程网络编程日志枚举

    多线程&网络编程 一.实现多线程 1.1 相关概念 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的一条执行路径.实际运作单位.简单理解:应用软件中互相独立,可以同时运 ...

  6. Linux多线程网络编程要义丨epoll与reactor原理

    linux多线程网络编程要义 1. epoll原理剖析 2. 单reactor原理以及应用 3. 多reactor原理以及应用 [Linux服务器系列]Linux多线程网络编程要义丨epoll与rea ...

  7. 【Linux服务器开发系列】详解多线程网络编程丨百分百干货分享丨学到就是赚到

    90分钟搞懂多线程网络编程模型 1. 网络编程关注的问题 2. 网络编程的几种模型reactor,one loop per thread及其变种 3. skynet,redis,nginx,memca ...

  8. Java的IO模型基于网络编程利弊分析

    JAVA的IO模型基于网络编程利弊分析 一.IO通俗理解 IO的过程 思考①答案:文件句柄 思考②答案: 阻塞/非阻塞 IO(程序对内核空间数据拷贝到用户空间阶段的耗时操作的等待方式) 同步/异步IO ...

  9. 5_异常_多线程_设计模式_IO流_网络编程_反射

    JavaSE_第五周 异常 异常的概念 什么是异常 概念 概念:程序在运行过程中出现的特殊情况异常-----通过Jvm将异常的信息打印在控制台---告诉开发者(当前程序在某个环节出现了哪些问题!) 异 ...

  10. 缓冲流,转换流,序列化流,网络编程

    一,缓冲流 能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等等.这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,相当于是对基本流对象的一种增强.1.1 概述 缓冲 ...

最新文章

  1. raise IOError('The file is not exist!')
  2. php 编译 线程安全,PHP7(zts 线程安全版)编译安装(支持多线程pthreads)
  3. 几何画板度量三角形的步骤
  4. 第13篇: Flowable-BPMN操作流程之流程进展查看之流程图
  5. c语言定义函数后引用,求助,函数在其他函数中使用时要先声明后调用,这个没声明就用了...
  6. 如何格式化搭载 Apple 芯片的 Mac?
  7. 基于python+django框架+Mysql数据库的电影院售票选座系统设计与实现
  8. 浙江理工大学计算机考研试卷,2016年浙江理工大学计算机程序设计考研试题.pdf...
  9. 图片标注工具Labelme使用
  10. instead of 的用法
  11. 作为面试官准备提问的问题及其答案
  12. 现代几何学在计算机科学中的应用,CNCC2017
  13. IDEA 关于两个分支代码合并的操作
  14. 联想微型计算机开机密码忘记了,lenovo台式电脑忘了开机密码简单解决的方法,小孩子就能搞定的...
  15. 手机传感器的意义:未来生活将被彻底改变
  16. 有编程基础的人学python_有哪些适合零编程基础的人学习Python的书?
  17. python和java哪个好找工作-短期找工作,编程语言是学习Python还是Java好呢?
  18. 企业考勤管理系统python_用Python编写一个电子考勤系统!谁还敢迟到?
  19. mysql 5.7.20 免安装版配置
  20. mabatis报错:Result type not match for select id=XXX

热门文章

  1. aop - spring如何选择、创建代理bean
  2. 股票自动交易软件的网格交易的优势?
  3. 【老码农的技术理想】
  4. Hyperf Casbin适配于Hyperf 的开源访问控制框架Casbin
  5. 001、JDK环境配置
  6. 微信小程序-申领福利老客邀请新客之间的逻辑判断
  7. Python入门到实战(五)自动化办公、pandas操作Excel、数据可视化、绘制柱状图、操作Word、数据报表生成、pip install国内镜像下载
  8. matlab 画阻尼振荡信号及其包络线
  9. JS删除对象中的某一属性
  10. 快递对接京东,菜鸟,顺丰过程备忘录