JavaSE 有这一篇就够(呕心狂敲41k字,只为博君一点赞!)(三)

简介: JavaSE 有这一篇就够(呕心狂敲41k字,只为博君一点赞!)(三)

JavaSE 有这一篇就够(呕心狂敲41k字,只为博君一点赞!)(二):https://developer.aliyun.com/article/1555487


五.IO流

File类  


○ 知识概要:

 一个File类的对象可以表示一个具体的文件或目录 .

•  File对象可以对文件或目录的属性进行操作,如:文件名、最后修改日期、文件大小等.


•  File对象无法操作文件的具体数据,即不能直接对文件进行读/写操作


○ File的构造方法:


   File( String pathname) 指明详细的文件或目录的路径


        //指明详细的路径以及文件名
        File file =new File("E:/demo1.txt");
        //指明详细的路径以及目录名
        File directory =new File("E:/temp");
        //注意使用/或\\来区分路径等级

○ File类的常用方法:

注意:delete删除一个文件夹时,文件夹中必须是空的

○ 使用File类创建文件及目录:

① 创建文件  

        //指明详细的路径以及文件名
        File file =new File("E:/demo3.txt");
        if (!file.exists()){//判断指向文件是否存在
            file.createNewFile();//通过createNewFile()方法创建文件
        }
        file.delete();//删除文件

② 创建文件夹(单级文件夹和多级文件夹)

   mkdir( ) 创建单级文件夹    mkdirs( )创建多级文件夹

        //指明详细的路径以及目录名
        File directory =new File("E:/demo");
        directory.mkdir();//mkdir用来创建单级文件夹
 
        File directorise =new File("E:/demo/demo1/demo2");
        directorise.mkdirs();//mkdirs用来创建多级文件夹

○ 如何得到一个目录中的所有文件:

  思路:对目录进行遍历, 遇到文件直接输出, 遇到子目录再对子目录遍历, 直到遇见文件

import java.io.File;
import java.io.IOException;
 
public class Review1 {
    public static void main(String[] args) throws IOException {
        //指明详细的路径以及目录名
        File directory =new File("E:/IO");
        search(directory);
    }
    //创建一个方法用来遍历目录里的文件
    public static void search(File file){
        File[] files =file.listFiles();获取到当前给定目录的子级文件/文件夹
        for (File f:files){
            if (f.isFile()){//若是文件,直接输出
                System.out.println(f);
            }else {
                  search(f);//若是目录,继续向下查找,直到遇到文件
            }
        }
    }
    
}

输入(I)与输出(O)  


   •  输入和输出是一个相对概念,输入和输出是相对于我们的程序 。

○ 输入---Input

  • 把电脑硬盘上的数据读到程序中,称为输入,即input,进行数据的read操作.

○ 输出---Output

  • 从程序往外部设备写数据,称为输出,即output,进行数据的write操作.

 ●  如图起连接作用的通道也就是我们通常所说的流 .

字节流与字符流  

体系图


○ 知识概要:

  字节流:读取时以字节为单位,可以读取任意文件.

 字符流:读取时以字符为单位,只能读取文本文件.

字节流中常用类:

  • 字节输入流   InputStream
  • 字节输出流  OutputStream

字符流中常用类:

  • 字符输入流   Reader
  • 字符输出流   Writer

常用类的基本方法

📌 InputStream和OutputStream的子类都是字节流, 可以读写二进制文件,主要处理音频、图片、歌曲、字节流处理单元为1个字节。


○ InputStream的基本方法:


  • 读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。
  • int read() throws IOException
  • 读取一系列字节并存储到一个数组buffer, 返回实际读取的字节数,如果读取前已到输入流的末尾返回-1
  • int read(byte[] buffer) throws IOException
  • 关闭流释放内存资源
  • void close() throws IOException

OutputStream的基本方法:

  • 向输出流中写入一个字节数据,该字节数据为参数b的低8位
  • void write(int b) throws IOException
  • 将一个字节类型的数组中的从指定位置(off)开始的 len个字节写入到输出流
  • void write(byte[] b, int off, int len) throws IOException

关闭流释放内存资源

  • void close() throws IOException
  • 📌 Reader和Writer的子类都是字符流, 主要处理字符或字符串, 字符流处理单元为1个字符。

○ Reader 的基本方法:

  • 读取一个字符并以整数的形式返回, 如果返回-1已到输入流的末尾。
  • int read() throws IOException
  • 读取一系列字符并存储到一个数组buffer, 返回实际读取的字符数, 如果读取前已到输入流的末尾返回-1。

int read( char[] cbuf) throws IOException

关闭  void close() throws IOException

○ Writer 的基本方法:

  • 向输出流中写入一个字符数据,该字节数据为参数b的16位
  • void write(int c) throws IOException
  • 将一个字符类型的数组中的从指定位置(off set)开始的 length个字符写入到输出流
  • void write( char[] cbuf, int off set, int length) throws IOException
  • 关闭  void close() throws IOException

字节流读写文件

① 首先们在D盘中创建一个ddd.docx文件(9.86KB),再在E盘中创建一个eee.docx文件(空)

代码实现 :                                                                                                                              

 public static void main(String[] args) throws IOException {
       
        FileInputStream inputStream1 =new FileInputStream("D:/ddd.docx");//输入流
        FileOutputStream outputStream1 =new FileOutputStream("E:/eee.docx");//输出流
        int a =0 ;
        while((a=inputStream1.read())!=-1){//只要a!=-1说明还未读完
            outputStream1.write(a);//写内容(以字节为单位)
        }
        //关闭通道,否则文件一直处于打开(占用)状态
        inputStream1.close();
        outputStream1.close();
    }

③ 运行程序,可以发现我们成功将ddd.docx文件内容写到了eee.docx文件中 ( 0字节➝9.86KB )

每次读写完后记得关闭通道,否则则文件一直处于打开(占用)状态.


○ 高效文件读写 :

  创建一个byte数组,一次读byte数组长度个字节,便于提高读写效率。

public static void main(String[] args) throws IOException {
        FileInputStream inputStream =new FileInputStream("D:/demo1.txt");
        FileOutputStream outputStream=new FileOutputStream("E:/IO.txt");
        byte[] bytes =new byte[10];
        //read(bytes) 一次读byte数组长度个字节,文件内容读取完后返回-1
        //size:每次实际往数组中装入的元素的个数
 
        int size =0;
        while((size=inputStream.read(bytes))!=-1){
        //一次向外写出一个byte数组长度个字节内容,从指定位置开始写,写size个
            outputStream.write(bytes,0,size);
        }
        inputStream.close();
        outputStream.close();
    }

字符流读写文件

字符流只能读取文本文件

① 我们先创建一个char1.txt文本, 并写入内容进行读写测试                  

② 通过代码实现将char1.txt文本的内容读写到char2.txt文本                

 public static void main(String[] args) throws IOException {
        FileReader reader =new FileReader("E:/char1.txt");
        FileWriter writer =new FileWriter("E:/char2.txt");
        int b = 0;
        while((b=reader.read())!=-1) {
            System.out.println(b);//打印字符编码
            writer.write(b);
        }
        reader.close();
        writer.close();
    }

字符流读取时以字符为单位,会将读到字节结合编码表转换为一个字符编码


③ 我们可以将每次读取到的字符对应的编码打印出来                          



④ 成功读写内容到char2.txt文本                                

📌 上述写法的弊端:

每次运行程序会将之前所读写的内容覆盖带掉,不能做到在原内容的基础上续写

解决方法:在字符输出流的对象路径后加上true, 表示可续写.

此时当我们多次运行程序时, 发现之前的所读写的内容依然存在


📌 那么我们怎样进行换行操作?

public static void main(String[] args) throws IOException {
        FileReader reader =new FileReader("E:/char1.txt");
        //保留原来的内容,在原内容基础上向后追加(续写)
        FileWriter writer =new FileWriter("E:/char2.txt",true);
        BufferedReader bufferedReader =new BufferedReader(reader);
        BufferedWriter bufferedWriter =new BufferedWriter(writer);
 
        String line = null ;
        while((line=bufferedReader.readLine())!=null){//只要每次读取不为空,则读取一行
            bufferedWriter.write(line);//写一行
            bufferedWriter.newLine();//插入换行符
        }
        bufferedReader.close();
        bufferedWriter.flush();
        bufferedWriter.close();//记得在读写完毕后关闭通道
 
    }

 注意:

由于读取一行的方法readLine( )BufferedReader中,换行方法newLine( )BufferedWriter类中, 所以需要用到缓冲字符输入输出流。

此时当我们多次运行程序时, 会将每次读取到的内容进行换行读写, 便于记录数据。

节点流与处理流    

● 按 封装类型 流又分为:

  • 节点流:直接封装的是文件, 数据.
  • 处理流:封装的是其他节点流对象; 可以提供缓冲功能, 提高读写效率.

● 节点流中常用类:

  • 字节输入流 FileInputStream
  • 字节输出流 FileOutputStream
  • 字符输入流 FileReader
  • 字符输出流 FileWriter

处理流中常用类:

  • 缓冲字节输出流 BufferedOutputStream
  • 缓冲字节输入流 BufferedInputStream
  • 缓冲字符输入流 BufferedReader
  • 缓冲字符输出流 BufferedWriter

代码演示:

 public static void main(String[] args) throws IOException {
        FileInputStream inputStream = new FileInputStream("D:/demo1.txt");
        //封装的是一个节点流对象,可以提供缓冲功能,称为处理流/包装流
        BufferedInputStream bufferedInputStream =new BufferedInputStream(inputStream,20);
        FileOutputStream outputStream =new FileOutputStream("E:/IO.txt");
        BufferedOutputStream bufferedOutputStream =new BufferedOutputStream(outputStream,20);
 
        int size =0;
        byte[] bytes =new byte[10];
        while ((size=bufferedInputStream.read(bytes))!=-1){
            bufferedOutputStream.write(bytes,0,size);
        }
        bufferedInputStream.close();
        bufferedOutputStream.close();
    }

缓存区流底层代码:


对象输入输出流 ( 序列化 )  

基础理论知识

📌怎么理解对象输入输出流 ?

○ 把java中的对象输出到文件中,从文件中把对象输入到程序中.


📌为什么要这样做(目的) ?


当我们创建一个对象时, 如new Student( "小张",20 );  数据存储在对象中, 对象是在内存中存储的,一旦程序运行结束, 对象就会销毁, 有时需要将对象的信息长久保存,就需要将对象输入到文件中。


( 例如系统升级,关闭服务器时将对象保存起来,升级完毕后再重新把数据还原回来.)


📌对象的序列化和反序列化:

○ 把对象输入到文件的过程也称为对象的序列化.

○ 把对象从文件输入到程序的过程称为对象的反序列化, 反序列化时会生成一个新的对象, 所以反序列化也是创建对象的一种方式.

📌注意:

当一个类的对象需要被序列化到文件时, 这个类必须要生成一个序列化编号。

○ 如果一个类需要被序列化到文件中, 那么这个类就需要实现Serializable接口, 实现后, 会自动的为该类生成一个序列化编号.


📌关于序列化编号的意义:


○ 编号是类的唯一标识,但是自动生成的编号在类信息改变后会重新为类生成一个编号。


○ 可以在类中显示的生成一个编号,这样类信息修改后,编号也不会改变。

📌常用类及基本方法:


  • 对象的输出流: ObjectOutputStream
  • 对象的输入流: ObjectInputStream
  • 在ObjectInputStream中用readObject()方法可以直接读取一个对象。
  • 在ObjectOutputStream中用writeObject()方法可以直接将对象保存到输出流中。

生成序列化ID教程

📌 如何在IDEA中设置, 使可以在类中生成序列化ID ? ( 教程 )

📌 设置成功后, 当我们把鼠标移至类名处, 点击serialVersionUID即可生成编号

代码实践与测试

我们首先创建一个学生类,需要将学生信息序列化到文件中,切记需要实现Serializable接口.

import java.io.Serializable;
 
//如果一个类需要被序列化到文件中,那么这个类就需要实现Serializable接口
public class Student implements Serializable {
    private String name;
    private int age;
 
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

进行序列化操作,将对象数据输出到文件中,使对象的信息可以做到持久化。

       //对象输出 (对象的序列化)
        Student student =new Student("小魏",20);//创建对象
        FileOutputStream fileOutputStream  = new FileOutputStream("E:/obj.txt");
        ObjectOutput output = new ObjectOutputStream(fileOutputStream);
        output.writeObject(student);
        output.flush();
        output.close();

运行程序后发现, 对象信息保存在了文件obj.txt中(格式为乱码,但不影响最后反序列化输入结果)

进行反序列化操作, 将之前保存在文件obj.txt的对象信息输入到程序中。

  public static void main(String[] args) throws IOException, ClassNotFoundException {
        //对象输出 (对象的序列化)
        /*
        Student student =new Student("小魏",20);//创建对象
        FileOutputStream fileOutputStream  = new FileOutputStream("E:/obj.txt");
        ObjectOutput output = new ObjectOutputStream(fileOutputStream);
        output.writeObject(student);
        output.flush();
        output.close();
        */
        //对象输入 (对象的反序列化)
        FileInputStream inputStream  = new FileInputStream("E:/obj.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Student student = (Student) objectInputStream.readObject();//强制类型转换
        System.out.println(student);//打印对象信息
 
        objectInputStream.close();//记得关闭流通道
    }

运行结果:


六. 异常处理机制

本章学习内容使用异常处理机制,对程序运行过程中出现的异常情况进行捕捉并处理.

         📌 Java异常概述

○ 异常的概念:

•  程序在运行过程中出现的不正常情况;

  例如用户输入数据有问题,读写文件时文件被强制删除了,网络传输过程中突然断网...

•  出现异常后,会导致jvm(虚拟机)停止运行,后续程序无法执行.


○ 注意:


•  异常指的并不是语法错误.  (语法错误,编译不通过,不会产生字节码文件,根本不能运行)


○ 异常的抛出机制 :


java中把不同的异常用不同的类表示, 一旦发生某种异常, 就创建该异常类型的对象, 并且抛出;

然后程序员可以捕获到这个异常对象, 并处理; 如果没有捕获这个异常对象, 那么这个异常将会


导致程序终止。


○ java中默认的异常处理机制:

  • 将出现的异常,按不同的类型分类,为每种异常封装了一个类来进行标识.
  • 当出现某种类型的异常情况时,会抛出此类的对象,然后终止虚拟机的运行.

异常信息:

•  异常的类型 (在哪种情况下出现,结合API定位)         • 异常原因           • 异常位置


                       📌 Java异常体系结构

java.lang.Throwable  : 异常体系的超类

 Error 错误:

  • 是虚拟机和Java代码无法解决的问题,例如虚拟机内部异常,内存不够用了.
  • 堆溢出: OutOfMemoryError
  • 栈溢出: StackOverflowError

○ Exception 异常:


这类异常时可以通过异常处理机制进行处理的一般性问题.

                                                   


                              📌 常见的异常

ArithmeticException                                算术异常

ArrayIndexOutOfBoundsException         数组索引越界

StringIndexOutOfBoundsException        字符串索引越界

ClassCastException                                  类型转换异常

NumberFormatException                          数字格式化异常

NullPointerException                                空指针异常

代码演示:

public static void main(String[] args) {
        /* 
        int a = 10;
        int b =0 ;
        System.out.println(a/b);
        System.out.println("*****"); 
        */  //ArithmeticException 算术异常
 
       /* 
        int[] c=new int[2];
        c[3] = 0; 
        */   //ArrayIndexOutOfBoundsException 数组索引越界
 
       /* 
       String s = "abc";
        s.charAt(4);
        */   //StringIndexOutOfBoundsException 字符串索引越界
 
        /* 
        Object o = new Integer(10);
        String so = (String) o;
        */  //ClassCastException 类型转换异常
 
        /* 
        Integer.parseInt("abc"); 
        */  //NumberFormatException 数字格式化异常
 
        /*
        String ss = null;
        ss.length(); 
        */  //NullPointerException 使用null中的方法称为空指针异常      
    }

                     


                              📌 异常处理

⦁  Java中使用异常处理机制为程序提供了错误处理的能力

⦁  在编码时,就针对可能出现问题的代码,预先编写一些处理机制,当程序运行出现异常时执行处理机制,之后继续执行后续的程序。


  Java的异常处理是通过5个关键字来实现的:trycatchfinallythrowthrows

try{...}catch(){...}

try{

 编写可能出现异常的代码

}catch(异常类型){

 处理机制

}

代码演示:

 public static void main(String[] args) {
        int a =10;
        int b = 0;
        try{
            int c = a/b;
        }catch (ArithmeticException aindex){
            aindex.printStackTrace();  //打印异常信息到控制台
            /* 一般在开发调试阶段使用,供开发人员定位异常问题 */
            System.out.println("算术异常");
        }
    }

运行结果:


一个try代码块可以对应多个catch

public static void main(String[] args) {
        try {
            String s =null;
            s.length();
 
            int num =Integer.parseInt("1a");
 
            int[] b =new int[5];
            for (int i = 0; i <=b.length ; i++) {
                System.out.println(b[i]);
            }
        } catch (ArrayIndexOutOfBoundsException aindex) {
            aindex.printStackTrace();//打印异常信息
            System.out.println("数组索引越界,越界索引:"+aindex.getMessage());
            //getMessage()得到异常原因
        }catch (NumberFormatException nex){
            nex.printStackTrace();
            System.out.println("数字格式化异常:"+nex.getMessage());
        }catch (Exception e){//在具体捕获类型后catch Exception,可以捕获任意类型,但是必须放在最后面
            e.printStackTrace();
            System.out.println("系统繁忙~请稍后再试!");
        }
        System.out.println("继续执行后续程序");
 
    }

当我们不确定捕获类型时可以使用Exception; Exception可以捕获任意类型, 但是需要放在具体捕获类型后catch, 否则放到首部会覆盖其他具体的捕获类型。


○ finally{...}


   finally块中的内容总是会执行的, 且只能有一个finally语句.

try{

编写可能出现异常的代码

}catch(异常类型){

处理机制

}finally{

代码总能执行

}

使用finally的两种情形:

  • 当catch错误, 异常没有被捕获到, 后面的代码无法执行, 但finally中的代码是可以执行的.
public static void main(String[] args) {
        int[] a = new int[5];
        try {
            for (int i = 0; i <= a.length; i++) {
                System.out.println(a[i]);
            }
        }catch (NumberFormatException nex){
            nex.printStackTrace();
            System.out.println("数字格式化异常:"+nex.getMessage());
        }finally {
            System.out.println("继续执行后续程序");
        }
    }
  • 确保在出现异常的情况下, 依然最终把流对象关闭掉 .
public static void main(String[] args) throws IOException {
       
        FileInputStream inputStream =null;
        try{
            inputStream =new FileInputStream("F:/demo.txt");
                    //文件若找不到,就会出现异常
        }catch (FileNotFoundException e){
            e.printStackTrace();
            System.out.println("文件找不到异常");
        }finally {
            //确保在出现异常时,仍然可以把流对象关闭掉
            if (inputStream!=null){
                inputStream.close();
            }
        }
    }

七 .网络编程

1. 网络编程概述


什么是计算机网络?

把分布在不同地理区域的计算机设备, 通过物理线路连接起来,最终实现数据传输, 资源共享.

📌 网络编程概述


什么是计算机网络?

把分布在不同地理区域的计算机设备, 通过物理线路连接起来,最终实现数据传输, 资源共享.

什么是网络编程?

在网络的基础上,开发的程序能够进行数据传输.

java语言是支持网络的, 并且将网络连接的细节都封装起来了,对外提供一套网络库,就可以进行统一环境的网络编程

要进行网络数据传输的核心问题?

如何找到网络世界中的目标主机和程序?(IP和端口)

找到后如何高效安全的进行数据传输?(协议)

OK那就让我们带着这两个问题来一探究竟吧!


📌 网络模型

✎ " 理论有七层网络模型-----现实中是四层模型 "


                      应用层 —— 内容                           传输层 —— 加入协议控制  


                      网络层 —— IP                               物理层 —— 网线 光纤        


📖  网络通信的三要素: IP    端口号    通信协议

IP:

  • IP地址是指互联网协议地址, 可以理解为计算机的地址 ; 如 ipconfig 局域网IP 192.168 , 本机回环地址127.0.0.1 (访问自己电脑)

端口号:

  • 计算机运行的每个程序都对应分配一个整数的编号, 不能重复 ; 范围: 0~65535 ;  一般0~1024已经被系统使用或保留,  例如MySQl的端口号是3306.

📖  端口对应程序   IP对应设备

通信协议:

  • 是一种规范和约定,计算机网络中实现通信必须有一些约定,即通信协议;对速率,传输代码,代码结构,出错后如何应对...等制定规则.

📌 TCP协议  

Transmission Control Protocol  传输控制协议  

安全可靠,但传输效率低

① 客户端向服务器端发送数据前,首先要建立连接(测试网络是否通畅)

三次握手

1. 客户端向服务器发送连接请求

    2. 当服务器接收到客户端连接请求后,给客户端做出一个回应

    3. 客户端为服务器的回应再次做出确认回应    

② 正式传输数据

③ 断开时需相互确认

四次挥手

    1. 客户端向服务器发送一个断开请求

    2. 服务器向客户端做出一个回应

 3. 服务器端把没有传完的数据传输完毕,再向客户端做出回应

    4. 客户端向服务器端的回应做出回应

   断开...


📌 UDP协议


User Datagram Protocol 用户数据协议

• 把要发送的数据封装成一个数据包数据包包含数据、对方的IP、 对方端口。

只管发送即可,是否发送成功是不知道的 ( 由于没有建立连接,故发送信息成功与否不晓得 )

优点:不需要建立双方连接,传输速度快.

缺陷:由于没有建立连接,它是不安全的.


📌 TCP网络编程



搭建服务器

new ServerSocket(端口)      

ServerSocket中的accept()方法来监听客户端的连接(此时程序阻塞)

        try {
            //创建服务器对象
            ServerSocket serverSocket =new ServerSocket(8888);//(端口)
            System.out.println("服务器启动成功!");
            //监听客户端是否发送链接请求服务器
            Socket socket= serverSocket.accept();//等待监听客户端的连接
            System.out.println("有客户连接到了服务器!");
        } catch (IOException e) {
            e.printStackTrace();
        }

搭建客户端

new Socket(服务器IP,端口

        try {
            //创建客户端
            Socket socket = new Socket("127.0.0.1",8888);// ("服务器的IP",端口)
        } catch (IOException e) {
            e.printStackTrace();
        }

✎后续的操作就需要用到IO流

   在客户端输出一个字符串, 并转换为字节的形式写入程序

    public static void main(String[] args) {
        //创建客户端
        try {
            Socket socket=new Socket("127.0.0.1",8888);//服务器的IP  端口
            String s="你好世界";
            OutputStream outputStream=socket.getOutputStream();
            outputStream.write(s.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 在服务器输入客户端输出的字节,并将其转为字符串进行打印

     public static void main(String[] args) {
        //创建服务器对象
        try {
            ServerSocket serverSocket =new ServerSocket(8888);
            System.out.println("服务器启动成功!");
            //监听客户端是否发送链接请求服务器
            Socket socket = serverSocket.accept();//监听客户端的连接
            System.out.println("有客户端连接到了服务器");
            //后续操作...
            InputStream inputStream=socket.getInputStream();
            byte[] bytes =new byte[100];
            int size =inputStream.read(bytes);
            String s =new String(bytes,0,size);
            System.out.println(s);
 
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器启动失败,端口已被占用");
        }
 
 
    }

进阶:实现单线程聊天

我们通过while循环和Scanner控制台输入实现服务器与客户端交替聊天

我们使用包装流DateInputStreamDateOutputStream来输入输出,可以直接读到一个字符串, 无需进行字符串与字节的转换.

 /*
    服务器
     */
    static Scanner scanner =new Scanner(System.in);
    public static void main(String[] args) {
        try {
            //创建服务器
            ServerSocket server = new ServerSocket(20049);
            System.out.println("服务器启动成功!");
            //监听客户端的连接
            Socket socket = server.accept();
            System.out.println("有客户端连接到了服务器!");
            //服务器接收客户端的消息
            while (true) {
                DataInputStream inputStream = new DataInputStream(socket.getInputStream());
                String string = inputStream.readUTF();
                System.out.println("服务器:" + string);
                //向客户端发送消息
                System.out.println("---->客户端:");
                String str = scanner.next();
                DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
                dataOutputStream.writeUTF(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
    /*
    客户端
     */
    static Scanner scanner =new Scanner(System.in);
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 20049);
            while (true) {
                System.out.println("--->服务器:");
                String s = scanner.next();
                DataOutputStream output = new DataOutputStream(socket.getOutputStream());
                output.writeUTF(s);
 
                DataInputStream inputStream = new DataInputStream(socket.getInputStream());
                String string = inputStream.readUTF();
                System.out.println("服务器:" + string);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }

📖 补充:

本机回环地址:127.0.0.1

获取服务器的IP  ( ipconfig 局域网IP 以 192.168 开头 )

八.线程


1. 区分程序、进程、线程



程序:为实现某种功能,使用计算机语言编写的一系列指令的集合。

          指的是静态的代码(安装在电脑上的那些文件)

进程:是运行中的程序(如运行中的原神)进程是操作系统进行资源分配的最小单位。

线程:进程可以进一步细化为线程,是进程中一个最小的执行单元,是cpu进行调度的最小单元

          例如:QQ中的一个聊天窗口

进程和线程的关系:

⑴ 一个进程中可以包含多个线程 (一个QQ程序可以有多个聊天窗口)


⑵ 一个线程只能隶属于一个进程 (QQ的聊天窗口只能属于QQ进程)


⑶ 每一个进程至少包含一个线程,也就是我们的主线程(像java中的main方法就是来启动主线程的)在主线程中可以创建并启动其他线程.


⑷ 一个进程的线程共享该进程的内存资源.

🪅 创建线程


通过继承Thread来创建线程

• 写一个类继承java.lang.Thread

• 重写run( )

线程中要执行的任务都要写在run( )中,或在run( )中进行调用.

public class Demo1 extends Thread{//继承Thread类
    @Override
    public void run() {//重写run方法
        for (int i = 1; i <= 200; i++) {
            System.out.println("run"+i);
        }
    }
}
 public static void main(String[] args) {
        //创建线程
        Demo1 demo1 = new Demo1();
        //启动线程
        demo1.start();
        for (int i = 1; i <= 200; i++) {
            System.out.println("main"+i);
        }
    }

📖 启动线程调用的是start() ; 不是run()

     run()这不是启动线程,只是一个方法调用,没有启动线程,还是单线程模式的。

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

• 创建任务,只先创建线程要执行的任务,创建一个类,实现Runnable接口.

• 重写任务执行的Run()

• 创建线程,并为线程指定执行任务.

public class Demo2 implements Runnable {//实现Runnable接口
    @Override
    public void run() {//重写run方法
        for (int i = 0; i < 200; i++) {
            System.out.println("自定义线程");
        }
    }
}
public static void main(String[] args) {
        //创建任务
        Demo2 demo2 = new Demo2();
        //创建线程,并指定执行任务
        Thread thread = new Thread(demo2);
        thread.start();
    }

实现Runnable接口创建的优点:

1. 因为java是单继承,一旦继承一个类就不能在继承其他类,避免单继承的局限。

2. 适合多线程来处理同一份资源时使用

🪅 Thread类中的方法

run()    用来定义线程要执行的任务代码.

start()  启动线程

currentThread() 获取到当前线程(.得到具体信息)

setName()   为线程设置名字

getState()  获取状态

getPriority() setPriority 获取/设置 优先级

sleep() 让当前线程休眠指定时间.

join()  等待当前线程执行完毕,其他线程再执行.

yield() 主动礼让,退出cpu重新回到等待序列.

📖 关于优先级:

【java中默认优先级为5, 设置优先级范围为1~10】 (作用:为操作系统调度算法提供的)

🪅 线程生命周期


线程状态:

新建:刚刚创建了一个线程对象,并没有启动


就绪:调用start() 后线程就进入到了就绪状态(可运行状态),进入到了操作系统的调度队列


运行状态:获得了cpu执行权,进入到cpu执行


阻塞状态:例如调用sleep() ,有线程调用了join(),线程中进行Scanner输入...


死亡/销毁:run()方法中的任务执行完毕了

状态关系图:

🪅 多线程的概念


• 顾名思义指:在一个程序中可以创建多个线程执行.

优点】提高程序执行效率(多个任务可以在不同的线程中同时执行)

             提高了cpu的利用率

             改善程序结构,将复杂任务拆分成若干个小任务

缺点】线程也是程序,线程越多占用内存也越多,cpu开销变大(扩充内存或升级cpu)

             线程之间同时对共享资源的访问会相互影响,若不加以控制会导致数据出错.

那么如何解决多线程操作共享数据的问题?

🪅 线程同步与锁(Lock)



       多个线程同时访问操作同一个共享的数据( 例如买票、抢购等 )时,可能会引起冲突,所以引入线程 “同步” 机制,即各线程间要有先来后到。

即通过【 排队+锁 】 在关键的步骤处,多个线程只能一个一个的执行.

synchronized(同步锁)  

      同步锁对象作用:用来记录有没有线程进入到同步代码块,如果有线程进入同步代码块,那么其他线程就不能进入同步代码块,直到上一个线程执行完同步代码块的内容,释放锁之后,其他线程才能进入。


      同步锁对象要求:同步锁对象必须是唯一的.


  synchronized(同步锁对象) {

        同步代码块      

  }  

● synchronized修饰方法时,同步锁对象不需要我们指定,同步锁对象会默认提供:


  1. 非静态方法 ------  默认是this


  2.  静态方法   ------  锁对象是当前类的class对象(一个类的对象只有一个)


ReentrantLock与synchronized区别?

  • synchronized是一个关键字,控制依靠底层编译后的指令去实现.
  • synchronized可以修饰一个方法或一个代码块.
  • synchronized是隐式的加锁和释放锁,一旦方法或代码块出现异常,会自动释放锁.


  • ReentrantLock是一个,依靠java底层代码去控制 (底层有一个同步队列)
  • ReentrantLock只能修饰代码块.
  • ReentrantLock需要手动的加锁和释放锁, 所以释放锁最好写在finally中, 一旦出现异常, 保证锁能释放.


误区:不是只要有线程就需要加锁,只有多个线程对同一资源共享时才加锁


模拟卖票

两个窗口分别售票,票数为10张

public class MyThread extends Thread{//我们使用了继承Thread的方法
    static int num =10;   //票总数10,且为共享资源,要用static修饰
    static String obj = new String();//可以是任意类对象,但必须唯一。
/*  synchronized(同步锁对象) {
         同步代码块
    }                         */
 
    @Override
    public void run() {//线程要执行的代码块要写在run()中
        while (true){
            synchronized (obj){//加锁,一次只能执行一个线程
                if(num>0){
                    try {
                        Thread.sleep(800);//此处加入休眠为了让运行结果更明显,也可不加
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
                    num--;  //每抢一张票,总数num(10)就减1
                }else{
                    break;
                }
            }
        }
    }
}

在Main方法中创建线程并启动:

 
public static void main(String[] args) {
        //创建两个线程,分别对应两个窗口
        MyThread myThread1 = new MyThread();
        myThread1.setName("窗口1");//线程1
        myThread1.start();
 
        MyThread myThread2 = new MyThread();
        myThread2.setName("窗口2");//线程2
        myThread2.start();
    }

运行结果:

相关文章
|
6月前
|
算法 程序员 C#
程序员也可以很浪漫!这样的一款表白代码收藏馆项目,即使小白也可以使用,发给你的对象试试!
程序员也可以很浪漫!这样的一款表白代码收藏馆项目,即使小白也可以使用,发给你的对象试试!
105 0
|
4月前
|
存储 Java 编译器
JavaSE 有这一篇就够(呕心狂敲41k字,只为博君一点赞!)(一)
JavaSE 有这一篇就够(呕心狂敲41k字,只为博君一点赞!)(一)
|
4月前
|
存储 安全 Java
JavaSE 有这一篇就够(呕心狂敲41k字,只为博君一点赞!)(二)
JavaSE 有这一篇就够(呕心狂敲41k字,只为博君一点赞!)(二)
|
6月前
|
算法 搜索推荐 Java
|
NoSQL Java 数据库连接
12W字大厂横跳神技!阿里P8熬夜三个月肝出Java面试必备真题手册
招聘市场开始回暖了?! 很多大厂和央企国企已经陆续开放 hc!是时候开始好好准备一下挑战高薪、冲击大厂了! 从事 Java 开发的小伙伴都会发现,面试一定逃不过 Spring 的关卡。 在 Spring 源代码能力相对较弱的情况下参加面试,很容易被面试官问住,只能和心仪的 offer 失之交臂~
|
存储 编译器 C语言
开心档之开发入门网-C++ 字符串
开心档之开发入门网-C++ 字符串
开心档之开发入门网-C++ 字符串
|
存储 机器学习/深度学习 算法
大逆不道,从天界偷下来的算法修仙秘籍竟然传到你手上~~(结尾有彩蛋)
这有可能是你见过最离谱的标题 这有可能是你没见过的技术文章模式 我不知道我的选择是否正确,但是我的想法就是: 不再让技术冷冰冰,让所有人学习中获得快乐!
268 1
|
算法 NoSQL API
到底该不该看源码(懂这三点儿就够了)
1、不要为了看源码而看源码 2、代码积累到一定程度,遇到问题自然就去查源码了,然后你就看懂了 3、两年内不要刻意去看源码,可以点开简单了解一下就行,前两年疯狂做项目就行了,后期项目做的多了,你自己就会有疑问,每次写代码就会问自己为什么要这样写?底层的原理是什么?很自觉的带着问题就去看源码了,如果你没有这样的疑问,那说明你也不适合去看源码了,写写业务代码,了了一生
191 0
|
存储 机器学习/深度学习 算法
只要你认真看完一万字☀️Linux操作系统基础知识☀️分分钟钟都吊打面试官《❤️记得收藏❤️》
只要你认真看完一万字☀️Linux操作系统基础知识☀️分分钟钟都吊打面试官《❤️记得收藏❤️》
234 0
只要你认真看完一万字☀️Linux操作系统基础知识☀️分分钟钟都吊打面试官《❤️记得收藏❤️》
下一篇
无影云桌面