目录

一. 基础语法

1. 数据类型

2. 基本数据类型转换

3. 运算符

3. 循环语句

5. 定义方法

6. 数组

 二. 面向对象

1. 类和对象

2. 构造方法

3. 方法的重载

4.  this关键字

5. static关键字

6. 代码块

7. 访问权限修饰符        

 8. 面向对象的三大特征

封装

继承

多态

 9.  方法的重写

10. final关键字

11. 抽象类

12. 接口

 三. API常用类

Object类

1.  toString方法

2.  equals方法

 Arrays类

1.  equals( )方法

2.  copyOf( ) 方法

3.  fill( ) 方法.

4.  sort( )方法

5.  binarySearch( )方法

6.  toString( )方法

String类

1. 获取功能的常用方法

2. 转换功能的常用方法

3. 替换功能的常用方法

4. 去除字符串两端空格

StringBuffer类

添加功能

删除功能

替换功能

反转功能

截取功能

Math类

Random类

Date类

四. 集合

体系图

单列集合

List 接口及实现类

● ArrayList      

● LinkedList  

● Vector    

○ 迭代器遍历 ( Iterator )

Set 接口及实现类

● HashSet

● TreeSet

双列集合

Map接口

● HashMap

● TreeMap

● Hashtable

 ○ Map集合遍历

Collections类 

 五.IO流

 File类   

输入(I)与输出(O)  

字节流与字符流   

体系图

常用类的基本方法

 字节流读写文件

字符流读写文件

节点流与处理流    

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

基础理论知识

 生成序列化ID教程

代码实践与测试

六. 异常处理机制

1. Java异常概述

2. Java异常体系结构

3. 常见的异常

4.异常处理

七 .网络编程

1. 网络编程概述

2. 网络模型

3. TCP协议   

4.UDP协议

5.TCP网络编程

八.线程

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

2. 创建线程

3. Thread类中的方法

4. 线程生命周期

5.多线程的概念

6. 线程同步与锁(Lock)


一. 基础语法


1. 数据类型

🎀分类:

基本数据类型:

○•●✰📌✍✎📖🎀

int  short  long   byte(整数型)

float  double  (浮点型)

char       (字符型)

boolean (布尔型)

注意:java中浮点数字面量默认是double类型,使用float类型需加 f / F 

引用数据类型:

String        修饰字符串

class          修饰类

Interface    修饰接口


2. 基本数据类型转换

分类:

• 默认转换 :  小容量类型转为大容量类型

• 强制转换 :  大容量类型转为小容量类型   (存在溢出或精度降低)

注意 容量不是所占字节大小;如4个字节的float类型,容量大于8个字节的long类型。

容量大小排行:  byte/short/char ----> int ----> long ----> float ----> double(小--->大)

3. 运算符

○ 算术运算符

注意 i++ 和 ++i 区别:

  •  i++ 先取值后运算

  •  ++i 先运算后取值


○ 赋值运算符

○ 比较运算符

    "=" 不是等于,是赋值


○ 逻辑运算符

注意 &&  和  & 区别:

•  &    无论左边是真是假,右边表达式都运算

•  &&  当左边表达式为真,右边表达式才运算;若左边为假,那么右边不参与运算

        ( ||  和 |  同理  ,|| 表示左边为真,右边不参与运算)

•  实际开发中推荐使用&&  ||


○ 位运算符

○ 条件运算符


3. 循环语句

 分类:

while循环     do/while循环     for循环

用法:

○ 不知道循环次数用while循环



○ 至少循环一次用do/while循环



○ 知道循环次数用for循环 



5. 定义方法

  • 所有方法都要定义到类里
  • java中的方法类似其他语言中的函数,对完成某个功能的代码进行封装并为其命名,最终可以重复调用

方法定义:
例如:public static void menu( ){ ... } 
• public          访问权限
• static           静态
• void             返回值类型
• menu          方法名(自定义)
• ( )                参数列表
• { ... }            方法体

6. 数组

 概述:

  • 数组是一组相同数据类型的集合,是一个容器.
  • 数组中可以存储基本数据类型,也可以存储引用数据类型.
  • 数组本身是引用数据类型,是一个对象.
  • 数组创建时必须指明长度,且长度不能改变.
  • 数组中每个元素空间是连续的.

 如何创建数组:

//方式1 
int[] a = new int[5];//new:创建一个数组,并指定数组长度
//方式2
 int[] b=new int[]{1,2,3,4,5,6,7};
//方式3
int[] c={1,2,3,4};
  • 访问数组中的元素通过下标访问,即索引 ;
  • 下标从0开始,是int类型;数组的最大索引=数组长度-1

打印数组中元素要用到Arrays类:

       int[] a = new int[5];//创建一个数组,并指定数组长度
       //Arrays类:java中提供用于操作数组的工具类,提供排序,二分查找,数组复制...
       System.out.println(Arrays.toString(a));

 ● 前言:面向过程和面向对象都是语言设计思想,面向过程POP是具体的步骤,是早期的编程语言设计思想,结构简单,扩展能力差,后期维护难度较大;面向对象OOP,面向对象设计程序时,从宏观上分析程序有哪些功能,然后对功能进行分类,把不同的功能封装在不同的类中,是一种宏观的设计,但到具体实现,仍然离不开面向过程。(二者的区别与关系)


 二. 面向对象

1. 类和对象


什么是类?

具有相同特征(同一类)事物的抽象描述,如人类,车类,学生类等。

类的结构:

○  变量:  事物属性的描述(名词)

○  方法:  事物的行为(可以做的事情 动词)

○  构造方法:  初始化对象

○  块:  一段没有名称的代码块

○  内部类:  即在类体中声明的类

如何写一个类?

  •  第一步----发现类

  •  第二步----定义类的成员变量

  •  第三步----定义类的成员方法

  •  第四步----使用类创建对象

/*
   定义一个学生类(发现类)
 */
public class Student {
    //定义类的成员变量
    String name ;
    int age;
    String gender;
    //定义类的成员方法
    public  void study(){
        System.out.println("学生要好好学习");
    }
    public void excise(){
        System.out.println("学生要多运动");
    }

    public static void main(String[] args) {
        //使用类创建对象
        Student student = new Student();
        //调用类中的成员方法,成员变量
        student.study();
        student.name = "小王";
    }
}

什么是对象?

对象是类的实例,以类为模版,在内存中创建出一个实际存在的实例。

我们使用new关键字来创建一个新的对象。

//类的类型(Student类型)  对象名 = new 类名(Student); 
Student student = new Student();//创建一个学生对象

2. 构造方法


分类:构造方法分为 无参构造方法 和 有参构造方法。

特点:

  在一个类中可以有多个构造方法 ( 构造方法可以重载 ).

●  方法名与类名相同,没有返回值,且不需要void修饰。

●  如果一个类没有定义构造方法,Java会提供一个默认的无参构造方法 ; 如果一个类定义了构造        方法,Java将不会提供默认构造方法。

●  构造方法在使用new关键字实例化对象时自动调用。

作用:

●  构造方法的作用是初始化对象的状态,可以在构造方法中为对象的实例变量赋初值。

public class Student {

    String name ;
    int age;
    String gender;
    //无参构成方法
    public Student(){
        name = "小张";
        age = 18;
        gender = "男";
        System.out.println(name+age+gender);
    }
    //有参构成方法
    public Student(String name,int age,String gender){
        this.name=name;
        this.age=age;
        this.gender=gender;
        System.out.println(name+age+gender);
    }


    public static void main(String[] args) {
    // 使用构造方法创建对象,并初始化
        Student student = new Student();//调用无参构造方法
        Student student1 = new Student("小魏",20,"女");//调用有参构造方法
    }
}

3. 方法的重载


○ 在一个类中有多个名称相同的方法,如何在调用时区分同名的方法?

   通过方法的参数的个数,类型,顺序;

•  方法的重载与返回值类型无关

public class Student {
    
    String name ;
    int age;
    String gender;
    //有参构成方法的重载
    public Student(String name,int age,String gender){
        
    }
    public Student(int age,String name,String gender){//通过参数顺序区分

    }
    public Student(String name,int age){//通过参数个数区分

    }
}

4.  this关键字


○ 在一个类的内部可以使用this关键字来引用成员变量名,区分成员变量和局部变量.(作用)

○ this在类中表示当前正在访问的对象

//this在类中用来表示当前正在访问的对象, this.成员变量名--显示的访问当前对象的成员变量
     public Person(String name,int age,String gender){
           this.name = name;
           this.age = age;
           this.gender  = gender;
     }

5. static关键字

介绍:

● static---静态,可以修饰类中的成员变量,成员方法,代码块,内部类(不能修饰构造方法)

○  修饰成员变量

    静态成员变量也称类变量,在内存中只有一份,所有对象可以共享,一般情况下,将类中所有        对象都相同的属性设置为静态的。

○  修饰成员方法

    修饰的成员方法也称为类方法,可以直接使用类名访问,在静态的方法中只能访问静态的成员        变量,非静态的方法中可以使用静态的成员变量。

static关键字修饰的属性特点:

•  随着类的加载而加载

•  优先于对象存在

•  静态成员被所有对象共享

•  可以直接使用类名访问

注意:

●  静态的方法中,只能使用静态的成员变量,因为他们都是随着类的加载而加载的;

●  一旦方法中使用了非静态的成员变量,那么此方法就不能定义为静态的;但非静态的方法中可以       使用静态的成员变量。

6. 代码块

概念:在类中声明的一个没有名字的代码块。

分类:实例代码块:在每次创建对象时执行                          {  实例代码块内容  }

           静态代码块:在类被加载时自动执行                static {   静态代码块内容   }

类什么时候会被加载?

○ 运行类中的main方法

○ 访问类中的静态成员变量,静态成员方法

○ 创建类的对象

 
public class Demo {
 
    //实例代码块
    {
        System.out.println("1-创建对象时,不需要显示的调用,会自动执行");
    }
    //静态代码块
    static{
        System.out.println("2-在类被加载时,会自动执行");
    }
    //运行类中的main方法,此时类被加载,静态代码块执行
    public static void main(String[] args) {
    //创建对象,实例代码块执行
        new Demo();
        new Demo();
    }
}


 

7. 访问权限修饰符        

● public        公共权限 --------修饰的成员,在任何地方都可以访问到.  

● protected  受保护权限------在本类,同包的其他类中可以访问到,及不同包的子类.

● default       默认权限 --------在本类,同包的其他类中可以访问到.

● private       私有权限  -------只能在本类访问到.

( protected和默认(default)的区别就是,protected可以在不同包的子类中访问 )

 8. 面向对象的三大特征

封装

● 概念:将类中的某些信息,使用不同的访问权限修饰符隐藏起来,不让外界直接访问操作,而是通过     类中向外提供的特定方法访问,方便加入控制语句,主动权在类手中。

● 封装案例1:将类中成员变量私有化

public class Student {
    private String name;
    private int age ;
}

 ● 此时在其他类中不能任意访问成员变量,只能通过类中提供的特殊的方法进行访问.

 public String getName() {
        return name;
    }

    public void setName(String name) {
        //加入控制语句
        if(name.length()>1&&name.length()<5);{
            this.name = name;
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        //加入控制语句
        if(age<100&&age>0){
            this.age = age;
        }
    }
public static void main(String[] args) {
        Student s1 = new Student();
        s1.setName("小王"); //符合setName方法中的条件
        s1.setAge(-1);     //不符合setAge方法中的条件
        System.out.println(s1.getName());
        System.out.println(s1.getAge());
    }

  封装案例2:将类中的方法私有化

 ○ 单例模式让一个类在一个程序中只能创建一个对象,将类的构造方法私有化,外界不能随便用.

public class Window {
    //在类加载时,只创建了一个唯一对象
    static Window window = new Window();

    //将构造方法私有化,在外界不能随意调用
    private Window(){

    }
    //向外界提供可获得此对象的方法
    public  static  Window getWindow(){
        return window;
    }

    public static void main(String[] args) {
        System.out.println(Window.getWindow());
        System.out.println(Window.getWindow());
        System.out.println(Window.getWindow());
    }

}

   可以看到,此例中创建的每个window的哈希值都相同于Window类中第一次创建的window

● 作用:可以有效的避免创建多个对象,达到在一个程序中只创建一个对象的作用。


继承

● 概念:将同一类事物中共性的属性和行为进行抽取,定义为一个类中(基类),其他类可以继       承基类,就可以拥有基类中的功能,但不能使用基类中私有的内容.

● 作用:实现代码的复用性,以及可以在子类扩展自己特有的功能,而不影响其他类.(优点)

● 使用条件:只要符合is-a关系(什么是什么关系,比如苹果是水果),就可以使用继承,一个类     只能直接继承一个类;而间接的可以继承多个类,称为继承的传递性,例如b继承a,然后c继承      b,则c也继承了a.

● 继承的基本语法:

   在创建类的时候在类名后使用extends关键字继承别的类,子类继承父类后,拥有了父类的成员     变量和成员方法,但不能访问私有成员。

public class Cat extends Animal{
   //Cat继承Animal的成员变量和成员方法,但不能访问私有成员
}

● 注意:当一个类没有显示继承其他类的时候默认继承object类,Object类是java类体系中最大的       类,Object类之上再也没有别的类。


多态

 前提:二者存在直接或间接的继承关系。

 概念:用父类的引用变量指向子类对象,多态也称向上转型,将子类类型转为父类类型。

 作用:用父类类型表示任意的子类类型对象,利于程序扩展。

Animal dog = new Dog();

两个时间段:

○  编译期 --- 类型是父类类型

○  运行期 --- 类型是具体的子类类型

 口诀:编译看左边,运行看右边 ( 若是静态方法则都看左边(父类))

● 多态的存在意味着可以使用父类类型的引用来调用子类对象中重写的方法。

案例:

public class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound");
    }
}
public class Dog extends Animal{
    @Override//方法的重写
    public void makeSound() {
        System.out.println("Dog is barking");
    }

    public static void main(String[] args) {
        Animal dog = new Dog();//现在我们可以使用Animal类的引用来引用Dog对象

        dog.makeSound();  //输出Dog is barking
    }
}

○ 在上面的例子中,animal引用的是一个Dog对象,但是调用的是Dog类中重写的makeSound()方     法。这就是多态的效果!


 9.  方法的重写

概念:当父类中方法的实现不能满足子类需求时,可以在子类中对父类的方法进行重写 ( 覆盖) ,这             样调用时,就会调用子类中重写的方法。

重写时需要注意:
● 子类重写的方法结构与父类方法的结果必须一致(方法名,参数列表,返回值类型必须一致)

● 子类重写的方法使用的访问权限不能小于父类被重写方法的访问权。

● 构造方法,静态方法不能重写,成员变量不存在重写。

○ 使用 @Override 注解标签

了解:

        @Override是Java中的一个注解标签,定义在重写的方法上面,表示此方法是从父类重写而来,也可以不用添加,不过建议保留,因为编译器可以进行语法验证 ,并且阅读代码时可以明确的知道此方法是重写的。

10. final关键字

○ final 可以修饰类,方法,参数,成员变量(常量)

 final 修饰的类不能被继承,所以不能修饰抽象类,例如Java中String类就是final修饰

○ final 修饰的方法不能被重写

○ final 修饰方法的参数,参数值在方法中不能被改变

 final 修饰的成员变量值不能改变,因此称为常量

//1.在类定义时,值就确定,直接赋值,赋值后不能改变,建议用static修饰
    final static int A = 10;
//2.在类定义时,值不明确,必须在创建对象后在构造方法中赋值,每个对象中都有一个常量
    final int COUNT ;
​
    public demo1(){
        COUNT = 10;
    }
    public demo1(int COUNT){
        this.COUNT=COUNT;
    }

  (常量一般建议全部使用大写字母 多个单词间用下划线连接)

11. 抽象类

抽象方法:

● 概念:抽象方法是一种特殊的方法,只有声明,没有具体的实现,必须用abstract关键字修饰,     没有方法体。

● 作用:在一些体系结构的顶端,只需要某些定义功能而没有必要实现,因为不同子类中的实现       都不同,这个时候就可以将这个方法声明为抽象方法。

   例如定义一个eat的抽象方法:

    //抽象类也需要用abstract来修饰
public abstract  class Animal {
    //抽象方法,没有方法体
    public abstract void eat();
  
}

● 注意:一个类如果包含抽象方法,这个类为抽象类;一个类为抽象类,不一定有抽象方法。

              抽象类也需要用abstract关键字修饰。             

● 抽象类的特点:

○ 抽象类不能用来创建对象,其他功能与正常类相同,可以有成员变量,成员方法,构造方法。

○ 主要在上层定义功能,让子类继承实现。

● 代码实现:

abstract class Animal {
   abstract void sound(); // 抽象方法
   
   void sleep() {
      System.out.println("睡觉"); // 具体方法
   }
}

 ○ 子类继承抽象类时,必须实现所有的抽象方法,否则子类也必须声明为抽象类

class Dog extends Animal {
   void sound() {
      System.out.println("汪汪"); // 实现抽象方法
   }
}

 ○ 抽象类可以有构造方法,但是不能被实例化。子类可以通过使用"super"关键字来调用抽象类的      构造方法和方法。

12. 接口

介绍:

● 接口可以看做是一种特殊的抽象类,里面可以包含抽象方法,但不能被创建对象; 接口可以被       类实现,实现类必须实现接口中定义的所有方法;接口和抽象类十分的类似,接口内部的方法也     是默认抽象的,不可在内部实例化的,只能由接口的调用者去实现。

● 接口通过关键字 interface定义;类通过implements关键字来实现接口。

如何定义接口?

 //interface关键字修饰接口
public interface MyInterface {
​
    int num = 10; // public static final int num = 10;
    void eat();  //public abstract void eat();
​
    //静态方法,可以直接通过接口名调用
    public static void test(){
​
    }
    //默认方法,被子类继承后调用
    default void test1(){
​
    }
}

注意:类实现接口后,要么重写接口中所有抽象方法,要么声明为抽象类。

○  一个类可以实现多个接口: 

public class MyClass implements A,B{
    //Myclass类实现A,B接口
}

○  一个接口可以继承多个接口:

public interface A extends B,C{  
    //A继承B,C接口
}

 三. API常用类

Object类

介绍:

○ Object类是所有Java类的祖先(根基类),每个类都使用 Object 作为超类(父类),所有对象(包括数组)都继承实现这个类的方法。

  new int[10].hashCode();   //数组也继承Object类

○ 如果在类的声明中未使用extends关键字指明其基类,则默认基类为Object类

○ public class Person {...}    等价于: public class Person extends Object {...}

常用方法:

1.  toString方法

  • Object类中定义有public String toString()方法,其返回值是 String 类型,描述当前对象的有关息;当需要通过输出语句输出一个对象时,如System.out.println(person),将自动调用该对象类的 toString()方法,如果该类中没有重写toString(),那么默认调用Object类中toString(),默认输出对象hashCode值。我们可以根据需要在用户自定义类型中重写toString()方法。
public class Person {
    private String name ;
    private int age;
    public Person(){

    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    /*
    * 当输出一个对象时会默认调用此对象的toString().
    * 如果类中没有定义toString(),会调用Object类中toString(),
    * Object类中toString()是吧对象在内存的哈希值返回(以16进制返回)
    * 把对象信息通过字符串形式输出
     */

//重写toString()
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public static void main(String[] args) {
  
        Person p1=new Person("小魏",18);
        System.out.println(p1);
    }

○ 当我们重写toString方法后就可以把对象信息通过字符串形式输出了 。

(重写后的结果)

○ 如果不重写的话会默认调用Object类中toString(),把对象在内存的哈希值返回(以16进制返回)。

(没重写的结果)

快速生成toString重写方法:

○ 右键选择Generate,点击toString()即可。

 


2.  equals方法

○ boolean   equals(Object obj)  判断两个对象是否相等

注意:
○ Object类中的equals默认比较的是两个对象的地址是否相等, 我们就可以使用 == 代替它,在其他类中,一般都重写了equals(), 把它改造为比较对象中的内容是否相等

public class Test_2 {
    public static void main(String[] args) {
        Person p1 = new Person("小魏",16);
        Person p2 = new Person("小魏",16);
        System.out.println(p1==p2);   //比较的是地址
        System.out.println(p1.equals(p2)); //已经对equals方法进行了重写,使其比较的是两对象内容是否相等
    }
}

    (Object 中的 equals 方法默认使用==比较,比较的是对象地址,这点需要注意 )

 ○ 在Person类中对equals方法进行重写,使其比较的是对象中的内容是否相等

 @Override
    public boolean equals(Object obj) {
        if(obj instanceof Person){
            Person other =(Person)obj;
            return name.equals(other.name) && age ==other.age;
        }
        return false;
    }

○ 关于 " == " :

  • "=="  比较等号两边是否相等

○ 当==用于基本类型比较时,比较的是变量值是否相等。
○ 当==用于引用类型比较时,比较的是对象的地址是否相等。

( JDK提供的一些类,如String,Date等,重写了Object的equals方法,调用这些类的equals方法, x.equals (y) ,当x和y所引用的对象是同一类对象且属性内容相等返回 true 否则返回 false )


 Arrays类

○ 常用方法: 

1.  equals( )方法

•  比较两个数组内容是否相等,返回值为boolean类型.

public class Null {
    public static void main(String[] args) {
       /*
        比较两个数组内容是否相等
         */
        int[] a={1,2,3,4};
        int[] b={1,2,3,4};
        System.out.println(Arrays.equals(a,b));

    }
}

2.  copyOf( ) 方法

•  数组复制,将原数组内容复制到一个指定长度新数组中.

public class Null {
    public static void main(String[] args) {
        int[] c=new int[3];
        c[0]=0;
        c[1]=1;
        c[2]=2;
        int[]d=Arrays.copyOf(c,6);//将原数组c复制到长度为6的新数组d中
                      //(原数组,新数组长度)
        System.out.println(Arrays.toString(d));
    }
}

3.  fill( ) 方法.

•  用指定的值 ,将指定数组中的值进行填充.

public class Null {
    public static void main(String[] args) {
        int[]e ={1,2,3,4,5};
        Arrays.fill(e,0);
        System.out.println(Arrays.toString(e));
        int[]f = new int[10];
        Arrays.fill(f,6);
        System.out.println(Arrays.toString(f));
    }
}

4.  sort( )方法

•  排序,且可通过索引局部排序

public class Null { 
public static void main(String[] args) {
        //全部排序
        int[] a = {5,4,3,2,1};
         Arrays.sort(a);
         System.out.println(Arrays.toString(a));
         //通过索引指定区间排序,tolndex索引对应的值不参与排序
         int[] b ={6,5,4,3,2,1};
        Arrays.sort(b,1,4);  //对数组b索引1~4元素排序,所有4不参与
        System.out.println(Arrays.toString(b));
    }
}

5.  binarySearch( )方法

•  二分查找,查找前需要先排序

public class Null {
    public static void main(String[] args) {
        int[] b ={5,4,6,8,2,1,7};
        Arrays.sort(b);  //排序后 b={1,2,4,5,6,7,8}
        int index =Arrays.binarySearch(b,6); //需要找6
        System.out.println(index);  //输出索引,排序后6对应的索引为4
    }
}

6.  toString( )方法

 •  将数组中的元素内容拼接成一个字符串输出

public class Null {
    public static void main(String[] args) {
        int[] a={1,2,3,4};
        System.out.println(a);  //输出数组首元素地址,不是数组的元素内容
        System.out.println(Arrays.toString(a));;//通过toString()输出元素内容
    }
}

 


                                    String类


1. 获取功能的常用方法

•  int length( )           获取字符串长度

•  char charAt( )       获取指定位置上的字符

•  int indexOf( )         获取字符首次出现的位置,也可以从指定位置开始查找

•  lastIndexOf( )        从后往前找

•  String substring( )    从指定位置开始截取一个字符串副本

 public static void main(String[] args) {
        //获取功能
        String s1 = "abcdabcd";

        //int length() 获取字符串长度
        System.out.println(s1.length());   // 8
        
        //char charAt() 获取指定位置上的字符 
        System.out.println(s1.charAt(4));  //索引4对应的字符 a
        
        //int indexOf() 获取字符首次出现的位置
        System.out.println(s1.indexOf("a"));  //注意是首次出现的位置
        
        //int indexOf(str:  ,fromindex: )   从指定位置开始查找
        System.out.println(s1.indexOf("a",4));
        
        //lastIndexOf() 从后往前找
        System.out.println(s1.lastIndexOf("a"));
        
        //String substring() 从指定位置开始截取一个字符串副本
        String s2=s1.substring(2,6);   //截取索引2~6的字符串(不包含索引6对应的字符)
        System.out.println(s2);     //   2 <= res < 6

    }


2. 转换功能的常用方法

•  String toUpperCase( )         将字符串内容全部转换为大写

•  String toLowerCase( )         将字符串内容全部转换为小写

•  String concat(String str)      拼接指定字符串内容到原字符串末尾

•  String [ ] split(分割符)           通过分割符将字符串分割并以数组形式返回

public static void main(String[] args) {

        String s1="WoShiChinese";
        System.out.println(s1.toUpperCase()); //转大写
        System.out.println(s1.toLowerCase()); //转小写

        String s2 ="我爱中国";
        System.out.println(s2.concat(s1));  //拼接s1字符串内容到s2字符串末尾

        //String[] split(分割符)
        String s3 = "张三:中国人;"+"Tom:英国人;"+"梅西:阿根廷人;";
        String[] strings = s3.split(";");  //通过" ; "来截取s3字符串
        System.out.println(Arrays.toString(strings));
    }


3. 替换功能的常用方法

•  String replace(char old,char new)           单个字符替换

•  String replace(String old,String new)      整个字符串替换

•  replaceAll(String regex, String replacement)      替换字符串中所有数字

•  replaceFirst(String regex, String replacement)   替换字符串中第一个数字

 public static void main(String[] args) {
        String s1 = " ab5cd7bda";
        //用"A"替换字符串中所有的"a"
        System.out.println(s1.replace("a","A"));
        //用"G"替换字符串中所有数字       \\d表示所有字符串中所有的数字
        System.out.println(s1.replaceAll("\\d","G"));
        //用"R"替换字符串中第一个数字
        System.out.println(s1.replaceFirst("\\d","R"));
    }


4. 去除字符串两端空格

•  String trim( )     去除字符串两端空格(字符串中间的空格不能去除)

public static void main(String[] args) {
        
        //String trim() 去除字符串两端空格

        String s2 =" abcEF 13aD1 ";
        System.out.println(s2);
        System.out.println(s2.length());         //原字符串长度
        System.out.println(s2.trim());//去除两端空格,字符串中间的空格不能去除
        System.out.println(s2.trim().length());  //去除空格后字符串的长度

    }


                               StringBuffer类

注意:

  • String可以直接创建字符串对象,而StringBuffer不能,需要新创建一个字符串对象。
          String s1 = "abc";
          StringBuffer s2 = new StringBuffer("abc");

添加功能

○ public StringBuffer append(String str)         在原字符串末尾添加字符串

public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer("abc");
        s1.append("def");         // 不能使用+=
        System.out.println(s1);
    }

输出:abcdef

○ public StringBuffer insert(int offset,String str)    向指定位置插入字符串

public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer("abc");
        //向字符串s1末尾添加"def"
        s1.append("def");         // 不能使用+=
        System.out.println(s1);

        //向指定位置插入字符串
        s1.insert(1,"ABC");  //向索引为1的位置插入字符串"ABC"
        System.out.println(s1);
    }

输出:aABCbcdef


删除功能

○ public StringBuffer deleteCharAt(int index)      删除单个字符

○ public StringBuffer delete(int start,int end)       删除指定区间的元素,不包含结尾

public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer("abcdef");
        s1.deleteCharAt(2);  //删除索引为2的字符
        System.out.println(s1);

        //删除指定区间的元素,不包含结尾
        StringBuffer s2 = new StringBuffer("ABCDEF");
        s2.delete(1,3);
        System.out.println(s2);
    }


替换功能

○ public StringBuffer replace(int start,int end,String str)

StringBuffer s1 = new StringBuffer("abcdef");
        s1.replace(0,3,"ABC");
        System.out.println(s1);

输出:ABCdef


反转功能

○ public StringBuffer reverse( )

StringBuffer s1 = new StringBuffer("abcdef");
        s1.reverse();   //反转
        System.out.println(s1);

输出:fedcba


截取功能

○ 从StringBuffer中截取一个副本,返回给一个新的String对象,StringBuffer对象不变

public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer("abcdef");
        //从StringBuffer中截取一个副本,返回给一个新的String对象,StringBuffer对象不变
        String s2=s1.substring(1,4);
        System.out.println(s1);
        System.out.println(s2);
    }

 


StringBuffer和String的区别

  • String修饰的字符串是一个值不能改变的字符串,用String声明的字符串对象值一旦给定就不能改变了,每次拼接都会创建新的字符串对象,耗时且占用空间。
  • StringBuffer是内容可以改变的字符串,值可以改变且不需要创建新对象,在多任务执行时是安全的,适合单线程。

                                    Math类

  可以直接调用

介绍

java.lang.Math 提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为double型.

常用方法有:

 ○   abs         绝对值 
 
  sqrt         平方根
 ○   pow(double a, double b)      a的b次幂
 
  max(double a, double b)      min(double a, double b)     比较两数的大小
 
   random( )                               返回 0.0 到 1.0 的随机数 
 
   long  round(double a)           double型的数据a转换为long型(四舍五入)

 public static void main(String[] args) {
        //Math类
        System.out.println(Math.abs(-3));  //绝对值
        System.out.println(Math.sqrt(25)); //平方根
        System.out.println(Math.pow(2,5)); //次方
        System.out.println(Math.round(9.35));//四舍五入
        System.out.println(Math.floor(4.9)); //向下舍去
        System.out.println(Math.ceil(1.1));  //向上补加
        System.out.println(Math.random());   //0~1随机生成 (不等于1)
}


                                 Random类

○ 不能直接调用,使用Random类之前需要创建新的对象。

○ 介绍:此类用于产生随机数

○ 常用方法有:

import java.util.Random;
public static void main(String[] args) {

        Random random = new Random();
        System.out.println(random.nextBoolean()); //true 或 false
        System.out.println(random.nextInt());//在int的取值范围内随机返回一个整数
        System.out.println(random.nextLong());
        System.out.println(random.nextInt(35)+1); //在给定范围内随机获取一个数  0=<res<给定数

        byte[] bytes = new byte[5];
        random.nextBytes(bytes);  //随机取出数组长度个byte类型的随机数
        System.out.println(Arrays.toString(bytes)); 
}


                                     Date类

○ 不能直接调用,使用Date类之前需要创建新的对象。

1. 获取程序运行时刻的时间

        Date date = new Date();//获取程序运行时刻的时间
        System.out.println(date);

2. 获取自1970 1.1 0:0:0 到程序运行时刻的毫秒值

       Date date = new Date();
        //获取的是自1970 1.1 0:0:0 到程序运行时刻的毫秒值
        System.out.println(date.getTime());

3. 测试效率    

        //测试效率
        Date date1 = new Date();
        System.out.println(date1.getTime()-date.getTime());

 测试效率

○ 测试String字符串和StringBuffer字符串循环拼接10万次,程序运行耗时。

○ 测试String字符串拼接速度.

public static void main(String[] args) {

        //获取程序运行时刻的时间
        Date date = new Date();
        // System.out.println(date.getTime());

        String s1 = "w";
       // StringBuffer s2 = new StringBuffer("w");
        String s3 = "";
       //  StringBuffer s4 =new StringBuffer("");
        for (int i = 0; i < 100000; i++) {
            s3 = s3.concat(s1);
        }

        //获取下一个程序运行时刻的时间,即上一个程序运行结束时刻的时间
        Date date1 = new Date();
        // System.out.println(date1.getTime());

        System.out.println("用时"+(date1.getTime()-date.getTime())+"毫秒");//最终运行时间
    }

○ 测试StringBuffer字符串拼接速度。

public static void main(String[] args) {

        //获取程序运行时刻的时间
        Date date = new Date();
        // System.out.println(date.getTime());

        //String s1 = "w";
       StringBuffer s2 = new StringBuffer("w");
        //String s3 = "";
         StringBuffer s4 =new StringBuffer("");
        for (int i = 0; i < 100000; i++) {
            s4 = s4.append(s2);
        }

        //获取下一个程序运行时刻的时间,即上一个程序运行结束时刻的时间
        Date date1 = new Date();
        // System.out.println(date1.getTime());

        System.out.println("用时"+(date1.getTime()-date.getTime())+"毫秒");//最终运行时间
    }

  • 在前文中我们了解了二者的区别,通过这次测试我们也验证了之前的结论:用String声明的字符串对象值一旦给定就不能改变了,每次拼接都会创建新的字符串对象,耗时且占用空间;而StringBuffer是内容可以改变的字符串,值改变的同时不需要创建新对象,所有拼接起来速度更快。

四. 集合

前言:

  • 在开发实践中,我们需要一些能够动态增长长度的容器来保存我们的数据,java中为了解决数据存储单一的情况,java中就提供了不同结构的集合类,可以让我们根据不同的场景进行数据存储的选择,如Java中提供了 数组实现的集合,链表实现的集合,哈希结构,树结构等。

体系图

Java中的集合体系如图:

4eb99290537048db9fd3dbe83f6c0ffc.png○ 分类:集合可以分为单列集合和双列集合.

单列集合

Collection接口定义了单列集合共有的方法,其子接口SetList分别定义了存储方式。

List接口继承了Collection接口,有三个实现的类,分别是:ArrayList (数组列表) |  LinkedList    

  (链表列表) | Vector 数组列表 (且线程安全).

Set接口继承了Collection接口,有两个实现的类,分别是:HashSet | TreeSet .

○ 区别:

•  List:可以有重复元素       •  Set:不可以有重复元素


List 接口及实现类

● ArrayList      

•  底层有一个数组,可以动态扩展数组长度,并提供了一系列方法操作 ; 查询快,在中间增加 / 删除慢.

○ 常用方法:

ArrayList<String> s=new ArrayList<>();
        s.add("a");
        s.add("b");
        s.add("c");
        s.add("d");
        System.out.println(s.get(2));//根据索引得到指定位置的元素
        s.remove(0);//删除并返回指定位置的元素
        System.out.println(s);
        s.set(0,"X");//替换指定元素并返回数组
        System.out.println(s);
        s.size();
        System.out.println(s.size());//返回实际元素个数
        s.addFirst("X");
        s.addLast("X");
        
● LinkedList  

•  底层是一个链表结构 ; 查询慢,但增加 / 删除元素快。(特点)

○ 我们发现ArrayList和LinkedList的特点正好相反,原因如图:

6a738614c6464488aeff04a6254f67c0.png

● Vector    

○ 和ArrayList一样,底层也是数组实现,不同的是Vector的方法默认加了锁,线程是安全的。      

 a64c29356968405aa5a767ff67fb0eef.png 【常用方法】

○ 迭代器遍历 ( Iterator )

List集合遍历有三种方式:for循环,增强for循环和迭代器。

○ 增强for循环 遍历元素时,不允许修改集合元素(删除,添加)

○ for循环 是允许操作(删除)元素的,但要注意索引的变化与元素位置的移动。

○ 而使用迭代器即可以修改集合元素,且不用担心操作元素时索引的变化与元素位置的移动(优点)

● Iterator

public static void main(String[] args) {

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");
        //获得集合对象的迭代器对象
          Iterator<String> it =  arrayList.iterator();
         while (it.hasNext()){
                String s = it.next();//获取到下一个元素
                if(s.equals("a")){
                    it.remove();//使用迭代器对象删除元素           
                }
}

● ListIterator

•  ListIterator迭代器  只能对List接口下的实现类遍历.(条件)

○ 从指定的位置开始向前或者向后遍历

public static void main(String[] args) {

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");   
        //ListIterator 迭代器 只能对List接口下的实现类遍历,
        //listIterator(index);  可以从指定的位置开始向前或者向后遍历
            ListIterator<String>  listIterator =  arrayList.listIterator(2);
            while (listIterator.hasNext()){
                 System.out.println(listIterator.next());
            }     
    }

○ 逆序遍历  

 public static void main(String[] args) {

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");

        ListIterator<String>  listIterator =  arrayList.listIterator(arrayList.size()); //4   3 2 1 0
        while (listIterator.hasPrevious()){ //获取上一个元素
            System.out.println(listIterator.previous());
        }
    }


Set 接口及实现类

Set中所储存的元素是不重复的,无序的,且Set中的元素没有索引

○ 由于Set中元素无索引,所有其实现类中没有get() [通过索引获取指定位置元素] 且不能通过for循     环进行遍历

● HashSet

HashSet 是一个不允许有重复元素的集合,是无序的,不是线程安全的。

 public static void main(String[] args) {
        HashSet set =new HashSet<>();
        set.add("a");
        set.add("a");
        set.add("b");
        set.add("c");    //元素是不重复的
        System.out.println(set);//输出:[a,b,c]

        HashSet set1 =new HashSet<>();
        set1.add("c");
        set1.add("s");
        set1.add("x");
        set1.add("d");    //元素是无序的
        System.out.println(set1);//输出:[c,s,d,x]

    }

★ HashSet在添加元素时,是如何判断元素重复的?     (面试高频题)

○ 在底层会先调用hashCode(),注意,Object中的hashCode()返回的是对象的地址,此时并不会调用;此时调用的是类中重写的hashCode(),返回的是根据内容计算的哈希值,遍历时,会用哈希值先比较是否相等,会提高比较的效率;但哈希值会存在问题:内容不同,哈希值相同;这种情况下再调equals比较内容,这样既保证效率又确保安全。

● TreeSet

○ TreeSet可以根据值进行排序,底层使用了树形结构,树结构本身就是有序的。

 TreeSet<Integer> treeSet =new TreeSet<>();
        treeSet.add(2);
        treeSet.add(1);
        treeSet.add(4);
        treeSet.add(4);
        treeSet.add(3);
        System.out.println(treeSet);//输出[1,2,3,4]

● 向树形结构中添加元素时,如何判断元素大小以及元素是否重复?

○ 向TreeSet中添加的元素类型必须实现Comparable接口,重写compareTo() ; 每次添加元素时,调     用compareTo()进行元素大小判断  (小于0放左子结点,等于0表示重复,大于0放右子节点)

● TreeSet集合的遍历只能通过 增强for循环 和 迭代器(Iterator) 遍历.  (元素没有索引)


双列集合

Map接口

Map接口共性:

○ 数据存储是以 ( 键,值 ) 形式存储     

○ 键不能重复,值可以重复。

○ 通过键找到值,一个键只能映射到一个值。

● HashMap

○ HashMap中的键是无序的

       //可以存储两组值(键K,值V)
        HashMap<String,String> map =new HashMap<>();
        map.put("a","aa");     //put() 向map中添加一组键 值对
        map.put("w","ww");
        map.put("c","cc");
        map.put("s","ss");
        map.put("a","aaa");    /* 替代之前的键a */
        System.out.println(map); //键是无序的

  输出:{a=aaa, c=cc, s=ss, w=ww} 

○ HashMap中的常用方法

        //常用方法
        HashMap<String,String> map =new HashMap<>();
        map.put("a","aa");     //put() 向map中添加一组键 值对
        map.remove("a");  //删除指的的键,返回对应的值
        map.clear();     //清空键值对
        map.isEmpty();   //判断键值对的个数是否为空
        map.containsKey("a");    //是否含对应键
        map.containsValue("aaa");//是否含对应值
        map.get("s");    //传键返值
        map.size();      //有几组键值对

★ HashMap底层存储数据的结构 :(面试高频题)

○ 底层使用了一个长度默认为16的哈希数组,用来确定元素的位置,每次用key计算出哈希值,再     用哈希值%数组长度确定元素位置,将元素放在哈希表中指定的位置。

○ 后来继续添加元素,如果出现位置相同且不重复的元素,那么将后来元素添加到之前元素的next     节点。

○ 当链表长度等于8且哈希数组的长度大于64时链表会自动转为红黑树。

补充: 哈希表负载因子为0.75 , 当哈希表使用数组的0.75倍时会自动扩容为原来数组长的2倍。

● TreeMap

○ 底层使用树形结构存储键值

○ 键可以排序

○ 键元素类型必须实现Comparable接口,重写compareTo()

● Hashtable

 ○ 底层实现也是用到key的哈希值计算位置判断元素是否重复

 ○ 方法上都添加了synchronized

HashMap和Hashtable的区别:

● Hashtable中不能存储为null的键和为null值,但HashMap中可以。

        HashMap<Integer,String> map =new HashMap<>();
        map.put(1,"a");
        map.put(2,null);
        map.put(null,null);
        System.out.println(map); //输出:{null=null, 1=a, 2=null}

        Hashtable<String,String> table =new Hashtable<>();
        table.put(null,"a");     //报错      
        System.out.println(table);

 ○ Map集合遍历
       //可以存储两组值(键K,值V)
        HashMap<String,String> map =new HashMap<>();
        map.put("a","aa");     //put() 向map中添加一组键 值对
        map.put("w","ww");
        map.put("c","cc");
        map.put("s","ss");
        map.put("a","aaa");     /* 替代之前的键a */
        System.out.println(map); //键是无序的

        //遍历方式2:(推荐)
        Set<Map.Entry<String,String>> entries =map.entrySet();
        for (Map.Entry entry:entries){
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

        //遍历方式1:拿到所有的键
        Set<String> keyset =map.keySet();
        for (String key:keyset){
            System.out.println(key+":"+map.get(key));
        }

Collections类 

概述:Collections是集合类的工具类,与数组的工具类Arrays类似.

常用方法:

1. sort(Comparator<? super E>):void List

对集合中的元素排序。

2.reverse(List<?>):void

反转集合中的元素。

3.shuffle(List<?>):void

打乱元素中的元素。

4.fill(List<? super T>,T):void

用T元素替换掉集合中的所有的元素。

5.copy(List<? super T>,List<? extend T>):void

复制并覆盖相应索引的元素

6.swap(List<?>,int,int):void

交换集合中指定元素索引的位置.

7.replaceAll(List,T,T):boolean

替换成指定的元素。

代码演示: 

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class collections {
    public static void main(String[] args) {
        ArrayList<Integer> list =new ArrayList<>();
        list.add(1);
        list.add(2);
        /* addAll(Collection<? super T> c, T... elements); */
        Collections.addAll(list,3,4,5,6);//将指定的可变长度参数添加到指定集合中
        System.out.println(list);

        Collections.sort(list);        //排序(默认升序)
        System.out.println("升序:"+list);

        //Collections.binarySearch() 二分查找
        System.out.println(Collections.binarySearch(list,5));//二分查找

        //创建了一个实现Comparator接口的匿名内部类对象,省去了创建一个类简化语法
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.intValue()- o1.intValue();  //降序
            }
        });
        System.out.println("降序:"+list);

        ArrayList<Integer> list1 =new ArrayList<>();
        Collections.addAll(list1,1,2,3,4);
        Collections.fill(list1,5);
        System.out.println(list1);
        Collections.replaceAll(list1,5,6);
        // Collections.swap(list,0,1);//交换指定位置上元素
        //System.out.println(list);  // 2134
        //copy(list2,list1) 集合复制    目标集合size > 原集合size
        //fill(list,v)用指定的值填充集合
        //max / min
        //replaceAll
        //reverse    逆序
        //Collections.shuffle(list);  随机排序

    }
}

 五.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

常用类的基本方法

📌 InputStreamOutputStream的子类都是字节流, 可以读写二进制文件,主要处理音频、图片、歌曲、字节流处理单元为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

📌 ReaderWriter的子类都是字符流, 主要处理字符或字符串, 字符流处理单元为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();//记得关闭流通道
    }

 ☞ 运行结果: 


六. 异常处理机制

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


1. Java异常概述

○ 异常的概念:

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

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

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

○ 注意:

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

○ 异常的抛出机制 :

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

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

导致程序终止。

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

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

异常信息:

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


2. Java异常体系结构


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

 Error 错误:

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

 Exception 异常:

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

3. 常见的异常

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中的方法称为空指针异常      
    }

4.异常处理

⦁  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那就让我们带着这两个问题来一探究竟吧!


2. 网络模型

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

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

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


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

○ IP:

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

○ 端口号:

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

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

○ 通信协议:

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

3. TCP协议   


Transmission Control Protocol  传输控制协议  

 安全可靠,但传输效率低

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

三次握手

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

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

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

② 正式传输数据

③ 断开时需相互确认

四次挥手

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

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

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

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

     断开...


4.UDP协议


User Datagram Protocol 用户数据协议

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

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

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

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


5.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方法就是来启动主线程的)在主线程中可以创建并启动其他线程.

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

2. 创建线程


通过继承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. 适合多线程来处理同一份资源时使用

 

3. Thread类中的方法


run()    用来定义线程要执行的任务代码.
start()  启动线程
currentThread() 获取到当前线程(.得到具体信息)
setName()   为线程设置名字
getState()  获取状态
getPriority() setPriority 获取/设置 优先级
sleep() 让当前线程休眠指定时间.
join()  等待当前线程执行完毕,其他线程再执行.
yield() 主动礼让,退出cpu重新回到等待序列.

 📖 关于优先级:

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

4. 线程生命周期


线程状态:

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

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

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

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

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

状态关系图:

5.多线程的概念


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

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

              提高了cpu的利用率

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

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

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

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

6. 线程同步与锁(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();
    }

运行结果: 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐