JAVASE详解(下篇)

JAVASE详解(下篇)

1. 字符串

1.1. 字符串概述

字符串是引用数据类型,不是基本数据类型。

数据类型
基本数据类型
引用数据类型
数值型
字符型 (char)
布尔类型 (boolean)
整数类型 (byte, short, int, long)
浮点类型 (float, double)
类 (class)
接口 (interface)
数组 ([ ])

1.2. 字符串常量

1.2.1. 字符串常量之数据共享性(难点)

public class StringConstant {

    public static void main(String[] args) {
        String str = "hello";
        String str1 = "hello";
        System.out.println(str == str1);
    }
}

 字符串的常量是存储在数据共享区中(jdk1.7之前是在方法区中,jdk1.8之后是在堆中),如果创建一个字符串的常量,首先会先在常量池里面去查找是否存在这个常量,如果存在就不创建直接指向,如果不存在那么就创建。
 附:Java常量字符串String理解
   JDK1.8关于运行时常量池, 字符串常量池的要点

内存结构:(jdk1.7之前):
在这里插入图片描述
内存结构:(jdk1.7之后):
在这里插入图片描述

1.2.2. 字符串常量之不可变性

字符串的常量是永远不可变的,如果对字符串拼接,实际上是新产生的字符串常量,不会在原有的字符串常量上变化。
在这里插入图片描述

1.3. 字符串对象

1.3.1. 字符串的构造器

public class StringDemo {

    public static void main(String[] args) {
        //  "" 第一种了解即可
        String s = new String();
        String str = "";
        System.out.println(s);

        byte[] bs = {97, 98, 99};
        //把字节作为ascii转换成字符连接到一起
        String s1 = new String(bs);
        System.out.println(s1);

        byte[] bs1 = {97, 98, 99, 100, 101};
        //参数1 目标数组,参数2 开始索引,参数3 截取的长度
        String s2 = new String(bs1, 1, 3);
        System.out.println(s2);

        char [] cs = {'h','e','l','l','o'};
        String s3 = new String(cs);
        System.out.println(s3);

        String s4 = new String(cs, 2, 2);
        System.out.println(s4);

        String s5 = "world";
        String s6 = new String(s5);
        System.out.println(s6);

    }
}
面试题:String str1 = new String(“abc”);创建了几个对象?
 答:如果这行代码之前没有创建过abc的字符串的常量,就会创建两个,一个在堆中,一个在堆中的字符串常量池中。如果之前已经创建了字符串的常量abc,那么就只在堆中创建一个对象,字符串的常量对通过这个对象自动的查找而引用。

在这里插入图片描述

1.4. 字符串的方法

1.4.1. 字符串的判断方法

变量和类型方法描述
booleanendsWith​(String suffix)测试此字符串是否以指定的后缀结尾。
booleanequals​(Object anObject)将此字符串与指定的对象进行比较。
booleanequalsIgnoreCase​(String anotherString)将此 String与另一个 String比较,忽略了大小写。
booleancontains​(CharSequence s)当且仅当此字符串包含指定的char值序列时,才返回true。
booleanstartsWith​(String prefix)测试此字符串是否以指定的前缀开头。
booleanisEmpty()当且仅当 length() 为 0 ,返回 true 。
public class Demo {

    public static void main(String[] args) {
        String s = "helloworld";
        //判断字符串是否以某一个子字符串为结尾
        boolean ends = s.endsWith("world");
        System.out.println(ends);

        String s1 = "HelloWorld";
        //判断两个字符串的值是否相等,注意一下空指针
        boolean equals = s.equals(s1);
        System.out.println(equals);
        //判断两个字符串的值不考虑大小写是否相等,注意一下空指针
        boolean ignoreCase = s.equalsIgnoreCase(s1);
        System.out.println(ignoreCase);

        //判断字符串是否包含一个子字符串
        boolean contains = s.contains("owo");
        System.out.println(contains);

        boolean starts = s.startsWith("hello");
        System.out.println(starts);

        //判断字符串是否是空字符, 不推荐
        boolean empty = s.isEmpty();
        System.out.println(empty);

        //判断字符串是否是空串,推荐的用法
        boolean equals1 = "".equals(s);
        System.out.println(equals1);

    }
}	

1.4.2. 字符串的获取功能

变量和类型方法描述
intlength()返回此字符串的长度。
charcharAt​(int index)返回指定索引处的 char值。
intindexOf​(int ch)返回指定字符第一次出现的字符串中的索引。
intindexOf​(String str)返回指定子字符串第一次出现的字符串中的索引。
intindexOf​(String str, int fromIndex)从指定的索引处开始,返回指定子字符串第一次出现的字符串中的索引。
Stringsubstring​(int beginIndex)返回一个字符串,该字符串是此字符串的子字符串。
Stringsubstring​(int beginIndex, int endIndex)返回一个字符串,该字符串是此字符串的子字符串。
  • 作业一:计算一个字符串中大写字母和小写字母还有数字的数量
public class Demo {

    public static void main(String[] args) {
        String s = "OLO2343AJFLJSDF56opias32jdfadfga";

        int numCount = 0;
        int upperCount = 0;
        int lowerCount = 0;
        
        //遍历这个字符串
        for (int i = 0; i < s.length(); i++) {
            //根据索引获得对应的字符
            char c = s.charAt(i);
            //转换成ascii
            int ascii = c;
            if(ascii >= 48 && ascii <= 57){
                numCount++;
            }
            if(ascii >= 65 && ascii <= 90){
                upperCount++;
            }
            if(ascii >= 97 && ascii <= 122){
                lowerCount++;
            }
        }
        
        System.out.println("数字:"+numCount);
        System.out.println("大写:"+upperCount);
        System.out.println("小写:"+lowerCount);

    }
}
变量和类型方法描述
byte[]getBytes()使用平台的默认字符集将此 String编码为字节序列,将结果存储到新的字节数组中。
char[]toCharArray()将此字符串转换为新的字符数组。
static StringvalueOf​(boolean b)返回 boolean参数的字符串表示形式。
static StringvalueOf​(char c)返回 char参数的字符串表示形式。
static StringvalueOf​(char[] data)返回 char数组参数的字符串表示形式。
StringtoUpperCase()使用默认语言环境的规则将此 String所有字符转换为大写。
StringtoUpperCase​(Locale locale)使用给定 Locale的规则将此 String所有字符转换为大写。
Stringconcat​(String str)将指定的字符串连接到此字符串的末尾。
  • 作业2:从控制台输入一段字符串,然后把首字母变成大写,其余变成小写
public class Demo {

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        while (true){
            String line = s.nextLine();
            if("".equals(line)){
                System.out.println("输入的字符串不合法,请重新输入");
            }else{
                if ("-1".equals(line)){
                    break;
                }
                //   helloworld
                String firstLetter = line.substring(0, 1);
                //获得第一个字符后面的字符串
                String rest = line.substring(1);
                System.out.println("结果是:"+firstLetter.toUpperCase()+rest.toLowerCase());
            }
        }
        System.out.println("程序结束");
    }
}
变量和类型方法描述
String[]split​(String regex)将此字符串拆分为给定 regular expression的匹配项 。
Stringreplace​(char oldChar, char newChar)返回字符串 newChar 替换字符串 oldChar后形成的新字符串。
Stringreplace​(CharSequence target, CharSequence replacement)将此字符串中与文字目标序列匹配的每个子字符串替换为指定的文字替换序列。
Stringtrim()返回一个字符串,其值为此字符串,删除了所有前导和尾随空格,其中space被定义为其代码点小于或等于 ‘U+0020’ (空格字符)的任何字符。
  • 需求:找出下面字符串中li的数量
    "liasdflihsdhllihsdflihsdfiligsdfglikhsdfklilisdflio"
public class Demo {

    /**
     * 需求:找出下面字符串中li的数量
        "liasdflihsdhllihsdflihsdfiligsdfglikhsdfklilisdflio"
     */
    public static void main(String[] args) {
        String s = "liasdflihsdhllihsdflihsdfiligsdfglikhsdfklilisdflio";
        //定义一个计数器
        int count = 0;
        //获得li第一次出现的索引
        int index = s.indexOf("li");
        while (index != -1){
            //计数器累加
            count++;
            //找下一个li
            index = s.indexOf("li", index+2);
        }
        System.out.println("li的数量:"+count);
    }
}

2. 可变字符串

2.1. String和StringBuffer的比较

  • String:
    在这里插入图片描述
String str = “hello”;
str = str +”world”;
  • StringBuffer:
    在这里插入图片描述
StringBuffer s = new StringBuffer("hello");
StringBuffer s1 = s.append("world");

2.2. StringBuffer是线程安全的

线程是程序运行的最小的单元。

StringBuffer s = new StringBuffer(“hell”);

在这里插入图片描述
A线程和B线程完全同时来操作这个可变字符串
在这里插入图片描述
线程的安全保证了数据的安全性,但随之带来的是性能的低下。

2.3. SringBuffer的构造器

StringBuffer s = new StringBuffer();
//获得可变字符串的容量
int capacity = s.capacity();
System.out.println(capacity);

可变字符串;默认预留了16个位置(容量)。

helloworldliangg
public class StringBufferDemo {

    public static void main(String[] args) {
        StringBuffer s = new StringBuffer();
        int length = s.length();
        System.out.println("可变字符串的长度:"+length);
        //获得可变字符串的容量
        int capacity = s.capacity();
        System.out.println("可变字符串的容量"+capacity);

        s.append("helloworld");
        //获得可变字符串的容量
        capacity = s.capacity();
        System.out.println("可变字符串的容量"+capacity);
        s.append("liangge");
        //获得可变字符串的容量
        capacity = s.capacity();
        System.out.println("可变字符串的容量"+capacity);
        
        StringBuffer s1 = new StringBuffer("gege");
    }
}

2.4. StringBuffer的API

  • append:
    在这里插入图片描述
public class StringBufferDemo {

    public static void main(String[] args) {
        StringBuffer s = new StringBuffer();
        /*s.append("tx");
        s.append(2.1f);
        s.append(3.4);
        s.append(new char[]{'a','b','c'});
        s.append(new String("mystring"));*/

        s.append("tx")
                .append(2.1f)
                .append(3.4)
                .append(new char[]{'a','b','c'})
                .append(new String("mystring"));

        System.out.println(s);

    }
}
  • insert:
    在这里插入图片描述
public class StringBufferDemo {

    public static void main(String[] args) {
        StringBuffer s = new StringBuffer("helloworld");
        //插入的时候一定要注意索引不要越界
        s.insert(3, true);
        System.out.println(s);

    }
}
  • 删除:
public class StringBufferDemo {

    public static void main(String[] args) {
        StringBuffer s = new StringBuffer("helloworld");
        //删除指定索引处的字符串,不要越界啊
        s.deleteCharAt(5);
        System.out.println(s);
        StringBuffer s1 = new StringBuffer("helloworld");
        //删除开始索引到结束索引之间的字符串,包头不包尾,注意越界啊
        s1.delete(3,5);
        System.out.println(s1);
    }
}

3. Random随机数产生类

应用场景:

  1. 验证码;
  2. 彩票、抽奖;
  3. 文件服务器···
public class RandomTest {

    public static void main(String[] args) {

        //创建一个随机数的对象
        Random r = new Random();
        for (int i = 0; i < 10; i++) {
            //获得一个 0~n(不包括n)     随机的整数
            int nextInt = r.nextInt(1000);
            System.out.println(nextInt);
        }
    }
}

4. 包装类

8中基本数据类型不是类,是有局限性的。jdk5以后为每一种基本数据类型都提供了其包装类。包装类就是给基本数据类型做增强的。

基本数据类型byteshortintlongfloatdoublecharboolean
包装类ByteShortIntegerLongFloatDoubleCharacterBoolean

Integer的构造器:

public class Demo {

    public static void main(String[] args) {
        System.out.println(Integer.MAX_VALUE);
        System.out.println(Integer.MIN_VALUE);

        //创建一个Integer对象
        Integer integer = new Integer(10);
        System.out.println(integer);

        //创建一个以字符串为参数的构造器, 字符串不能是非数值类型或者越界int
        Integer integer1 = new Integer("100");
        System.out.println(integer1);

    }
}

4.1. 类型转换

  • int—>Integer
//创建一个Integer对象
Integer integer = new Integer(10);
System.out.println(integer);
Integer integer1 = Integer.valueOf(i);
System.out.println(integer);
  • Integer—>int
Integer integer = new Integer(10);
int i = integer.intValue();
System.out.println(i);
  • String—>Integer
Integer integer1 = new Integer("100");
System.out.println(integer1);
Integer integer2 = Integer.valueOf("200");
System.out.println(integer2);
  • Integer—>String
Integer integer = new Integer(200);
String s = integer.toString();
String s1 = integer+"";
String s2 = String.valueOf(integer);
  • String—>int
String s = "123";
//对字符串有要求,不能越界和非数值形式
int i = Integer.parseInt(s);
System.out.println(i+1);
  • int—>String
int j = 10;
String s1 = j+"";
String s2 = String.valueOf(j);
  • 常用方法
public class Demo {

    public static void main(String[] args) {
        String s = Integer.toBinaryString(100);
        String s1 = Integer.toHexString(100);
        String s2 = Integer.toOctalString(100);

        System.out.println(s);
        System.out.println(s1);
        System.out.println(s2);
    }
}
  • Integer判断
public class Demo {

    public static void main(String[] args) {
        Integer i1 = new Integer(100);
        Integer i2 = new Integer(100);
        //Integer是对象,不能==比较值
        System.out.println(i1 == i2);
        int i = 100;
        int j = 100;
        System.out.println(i == j);

        //比较Integer的值
        System.out.println(i1.equals(i2));
        System.out.println(i1.intValue() == i2.intValue());
    }
}
  • Integer的默认值
    在实战中默认值是有含义。
public class Person {

    private String name;

    /**
     int的默认值是0
     1.男,2.女
     Integer默认值是null
     1.男,2.女
     */
    private Integer gender;
}

项目中建议使用Integer

4.2. 自动装箱和拆箱

public class Demo {

    public static void main(String[] args) {
        //自动装箱,自动把int转换成一个Integer对象
        Integer i = 10;
        //自动拆箱把Integer对象自动的转换成int类型
        int i2 = new Integer(100);

        Long l1 = 200l;
        long l2 = new Long(300l);

        Double d = 3.1;
        double d1 = new Double(1.2);
    }
}

4.3. 自动装箱的原理

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
自动装箱在-128~127之间是走的缓存;
在缓存区间之外都是new Integer(int)。
0123······256
-128-127-126-125······127
public class Demo {

    public static void main(String[] args) {
        Integer i = 1000;
        Integer i1 = 1000;

        Integer i2 = -126;
        Integer i3 = -126;

        System.out.println(i == i1);
        System.out.println(i2 == i3);
    }
}

5. 日期

public class DateDemo {

    public static void main(String[] args) {
        Date d = new Date();
        System.out.println(d);

        Date d1 = new Date(System.currentTimeMillis() - 24*60*60*1000);
        System.out.println(d1);

        //获得一个指定的日期的毫秒数(1970年1月1日 0点)
        long time = d1.getTime();
        System.out.println(time);

        int year = d.getYear();
        System.out.println(year+1900);
        int month = d.getMonth();
        System.out.println(month+1);
        int day = d.getDate();
        System.out.println(day);
    }

    @Deprecated
    public static void method(){

    }
}

6. List

List是有序的集合,就像我们的数组一样。我们可以把list理解为是一个长度可变的数组,而且提供了丰富的api。List集合的底层就是数组。
附:Java集合List详解

01234
劳斯莱斯宾利迈巴赫奔驰宝马
public static void main(String[] args) {

    //创建一个list的集合
    List list = new ArrayList();
    //向末尾添加元素
    list.add("吴用"); //0
    list.add("刘唐"); //1
    list.add("宋江"); //2

    System.out.println(list);
    //我们在1处的索引位置来插入元素,我们插入一个元素的话,该索引后面的元素都会向后移动一位
    list.add(1, "晁盖");
    System.out.println(list);

    //创建一个集合
    List list1 = new ArrayList();
    list1.add("阮小五");
    list1.add("阮小二");
    list1.add("阮小⑦");

    list.addAll(list1);
    System.out.println(list);
    
}

6.1. List的迭代器ListIterator

在这里插入图片描述
通过ListIterator的方式遍历:

ListIterator listIterator = list.listIterator();
while(listIterator.hasNext()){
    //获得迭代的元素
    String str = (String) listIterator.next();
    if("刘唐".equals(str)){
        //我们不能在一边遍历的时候一边操作集合,这样会有多线程的并发问题
        //list.add("白胜");
        
        //迭代器给我们提供了一个add方法让我们避免并发问题,但是添加的时候本次遍历不生效
        listIterator.add("白胜");
    }
    System.out.println(str);

}
System.out.println(list);

可以使用for循环来动态的遍历List集合:

System.out.println("---------------分割线for循环遍历list-----------------------");
//int size = list.size();
//for循环对list的变量, 我们可以使用动态获得集合的长度的方式来遍历
for (int i = 0; i < list.size(); i++) {
    //根据索引来获得对应的元素
    String str  = (String) list.get(i);
    if("刘唐".equals(str)){
        list.add("阮小五");
    }
    System.out.println(str);
}
01234
劳斯莱斯宾利迈巴赫奔驰宝马

List中的元素是可以重复的。

6.2. ArrayList

ArrayList的底层是数组的原理。

特点:

  1. ArrayList中的元素可以重复;
  2. ArrayList是有序的集合,长度不固定;
  3. ArrayList不是线程安全的,但是效率高;
  4. ArrayList在添加数据的时候初始的长度是10,后续会以5个长度来扩充集合的长度。
    在这里插入图片描述

注意:

  1. ArrayList不是线程安全的集合,适合不要求线程安全的需求来实现;
  2. 好处是性能高;
  3. 缺点是线程不安全,可能带来数据不准确;
  4. 如果线程要是同步的话,性能就会变低。

6.3. LinkedList

LinkedList也不是线程安全的。LinkedList是链表为原理,添加修改删除的性能高。

  • 栈、队列和链表的对比:
    :先进后出
    在这里插入图片描述
    队列:先进先出
    在这里插入图片描述
    链表:储存了元素和前后的地址
    在这里插入图片描述

6.4. Vector

Vector底层也是数组的原理。线程安全的。

public static void main(String[] args) {
    Vector v = new Vector();
    v.add("宋江");
    v.add("晁盖");
    v.add("刘唐");
    System.out.println(v);


    Object o = v.get(1);
    //Object o1 = v.elementAt(1);
    System.out.println(o);
    
    System.out.println("--------------分割线-----------------");

    for (int i = 0; i < v.size(); i++) {
        Object o1 = v.get(i);
        System.out.println(o1);
    }
    
    System.out.println("--------------分割线-----------------");

    Enumeration elements = v.elements();
    while(elements.hasMoreElements()){
        Object o1 = elements.nextElement();
        System.out.println(o1);
    }

}

6.5. 泛型

泛型就是在集合中指定存储的数据类型,而且只能存储这种类型,在List<类型>必须要指定, ArrayList<>可以指定也可以不指定。基本数据类型不能作为泛型。

public static void main(String[] args) {
    //定义一个集合里面指定只能存储一种数据类型
    List<String> list = new ArrayList<>();
}

在这里插入图片描述
在这里插入图片描述

public static void main(String[] args) {
    //定义一个集合里面指定只能存储一种数据类型
    List<String> list = new ArrayList<>();
    
    //调用集合
    list.add("亮亮");
    list.add("腻腻");
    list.add("腻腻1");
    list.add("腻腻2");
    list.add("腻腻3");

    //创建一个迭代器对象
    Iterator<String> iterator = list.iterator();
    while(iterator.hasNext()){
        //获得到String类型
        String next = iterator.next();
        System.out.println(next);
    }

}

6.5.1. 自定义的泛型

语法:class/interface 类名/接口名 <T>{
	 }

T只是泛型的一个标准,使用什么字符都可以,但是都要大写,不要使用特殊字符,建议用T。

自定义泛型类:

public class GenericTest<T> {

    //定义一个泛型的属性
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

测试:

public static void main(String[] args) {
    GenericTest<String> gt = new GenericTest<>();

    gt.setT("哈哈");
    //获得对应的泛型的结果
    String t = gt.getT();

    //指定泛型是Integer类型
    GenericTest<Integer> gt1 = new GenericTest<>();
    gt1.setT(1);
    Integer t1 = gt1.getT();

    //指定泛型是Integer类型的数组
    GenericTest<Integer[]> gt2 = new GenericTest<>();
    gt2.setT(new Integer[]{1, 2, 4});
    Integer[] t2 = gt2.getT();

    //测试2个泛型
    GenericTest1<String, Integer> gtt1 = new GenericTest1<>("亮哥", 30);

    GenericTest1<String, Date> gtt2 = new GenericTest1<>("亮哥", new Date());

}

6.6. 增强的for循环

在jdk1.5以后出现了增强的for循环对数组、集合来做遍历。增强的for不支持并发,如果有并发的需求请使用普通的for循环。

语法:for(数据类型 变量:集合变量){
		///
	 }

数据类型是集合或者数组中的值的类型

public static void main(String[] args) {
    //定义一个数组
    String [] strs = {"晁盖","宋江","刘唐","吴用"};

    for(String str : strs){
        System.out.println(str);
    }

    System.out.println("-----------------------------");

    List<Integer> list = new ArrayList();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);

    for (Integer i : list){
        System.out.println(i);
    }
    
    System.out.println("-----------------------------");

    //这个增强的for不能支持并发
    /*for (Integer i : list){
        if(i.equals(2)){
            list.add(10);
        }
        System.out.println(i);
    }*/

    for (int i = 0; i < list.size(); i++) {
        //根据索引获得集合的元素
        Integer val = list.get(i);
        //如果某个元素是2就在这个集合里加入一个10
        if(val.equals(2)){
            list.add(10);
        }
        System.out.println(val);
    }

}

7. Set

7.1. HashSet

特点:

  1. 元素唯一性
  2. 无序性
  3. 允许null存在一个
  4. 不是线程安全的集合(效率高)
public static void main(String[] args) {

    /**
     * set是无序的
     * set的元素是不可重复的,如果重复了就会自动的去掉
     * set只能有一个null
     */
    Set<String> set = new HashSet<>();
    //给set添加元素
    set.add("董卓");
    set.add("张让");
    set.add("何进");
    set.add("李肃");
    set.add(null);
    System.out.println(set);
    
}

我们对set的唯一性深究一下(重点):
在这里插入图片描述
如果对象的hash值和equals都相等那么就是重复的对象。
在这里插入图片描述

public static void main(String[] args) {

   //创建一个存储Person对象的集合
    Set<Person> set = new HashSet<>();
    set.add(new Person("孔明", 26));
    set.add(new Person("刘备", 28));
    set.add(new Person("关羽", 27));
    set.add(new Person("张飞", 25));
    set.add(new Person("张飞", 25));

    System.out.println(set);
}

在这里插入图片描述

7.2. LinkedHashSet

特点:

  1. 元素唯一性
  2. 有序的
  3. 允许null存在一个
  4. 不是线程安全(效率高)

LinkedHashSet和HashSet来对比就是多了一个顺序。应用的不多。

public static void main(String[] args) {
    Set<String> set = new LinkedHashSet<>();
    set.add("董卓");
    set.add("张让");
    set.add("何进");
    set.add("李肃");
    set.add(null);
    System.out.println(set);
}

在这里插入图片描述

7.3. TreeSet

类如果要实现比较的规则都会实现Comparable接口。
TreeSet的特点:

  1. 元素唯一性
  2. 可自定义排序的
  3. 不允许null存在
  4. 不是线程安全

在这里插入图片描述
String对CompareTo的实现:
在这里插入图片描述

String str =  "ab";
String str1 = "ab";
//字符串的比较规则是先按着第一个字符来比较,如果说一个字符串的第一个字符比另一个字符串的首字符大,那么前者就大
//如果是整数说明str大, 如果是负数str1大, 如果是0相等
int i = str.compareTo(str1);
System.out.println(i);

创建学生对象实现comparable接口:

public class Student implements Comparable<Student>{

    private String name;

    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        //做年龄的差
        int flag = this.age - o.age;
        if(flag == 0){
            flag = this.name.compareTo(o.name);
        }
        return flag;
    }
}

测试:

Student s = new Student("abc", 20);
Student s1 = new Student("abc", 20);
int i1 = s.compareTo(s1);
System.out.println(i1);
public static void main(String[] args) {

    Set<Student> set = new TreeSet<>();
    Student s = new Student("abc", 20);
    Student s2 = new Student("abe", 20);
    Student s1 = new Student("abd", 19);

    set.add(s);
    set.add(s1);
    set.add(s2);
    
    System.out.println(set);
}

在这里插入图片描述

7.4. 可变参数

语法:修饰符 返回值类型 方法名(数据类型…变量){
	 }
public class DynamicParamDemo {

    public static void main(String[] args) {
        //int result = add(10,  10, 20, 50, 90);
        int result = add(new int[]{10, 20, 30});
        System.out.println(result);
    }
    
    //数据类型 ... 变量名 这是可变参数的定义方式
    //可以代表数组, 还可以代表单个数的传递
    //如果调用的时候我们发现了正好能匹配的方法就不会调用可变参数的方法,如果不存在这么一个正好调用的方法就会调用可变参数的方法
    public static int add(int... a){
        int total = 0;
        for (int i = 0; i < a.length ; i++) {
            total += a[i];
        }
        return total;
    }

    /*public static int add(int a, int b){
        return a + b;
    }*/
    
}

8. Map

  • List集合:
01234
鲁智深林冲白胜吴用公孙胜
  • Set集合:
鲁智深林冲白胜吴用公孙胜
  • Map集合: Map<Key, Value>是一个键值对的集合。
花和尚豹子头白日鼠智多星入云龙
鲁智深林冲白胜吴用公孙胜

我们如果想要从map中获得值,可以根据键来获得。
Map虽然是集合但是和Collection的集合体系不是同一个体系。
在这里插入图片描述
Map的映射的特点:

  1. 一个映射不能包含重复的键;
  2. 每个键最多只能映射到一个值;
  3. 键只允许有一个空值;
  4. 值可以有多个空值;
  5. Map也是无序的;
  6. Map<K,V> :K和V表示泛型,map的key和value可以是任何类型(不能是基本数据类型),实际项目中K一般都是字符串类型。

8.1. HashMap

如果大家能理解Set那么Map的key也就能理解了。
特点:

  1. 一个映射不能包含重复的键;
  2. 如果存在重复的key就会把之前的覆盖掉;
  3. 其实键就是Set,元素唯一,只能有一个null,元素是无序的;
public static void main(String[] args) {
    //创建一个Map集合
    Map<String, String> map = new HashMap<>();
    //向集合总加元素
    map.put("白日鼠","白胜");
    map.put("豹子头","林冲");
    map.put("小诸葛","富安");

    //key只能有一个是null,值任意
    /*map.put(null, null);
    map.put("aaaa", null);*/
    //值是可以重复的
    //map.put("大诸葛","富安");

    //如果存在重复的key就会把之前的覆盖掉
    //map.put("小诸葛","高衙内");
    System.out.println(map);
    
}

8.2. TreeMap

该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法;
特点:

  1. 可以按着key来做排序;
  2. Key不能null,key不能重复,值可以有多个null;
  3. 不是线程安全的。
public static void main(String[] args) {
    //创建一个Map集合
    Map<String, String> map = new TreeMap<>();
    //向集合总加元素
    map.put("b","白胜");
    map.put("a","林冲");
    map.put("e","富安");
    map.put("c","富安");

    System.out.println(map);

}

Person实现了comparable接口,我们的treemap可以根据key来做排序:

public static void main(String[] args) {
    //创建一个Map集合
    Map<Person, String> map = new TreeMap<>();
    //向集合总加元素
    map.put(new Person("白日鼠", 30),"白胜");
    map.put(new Person("豹子头", 28),"林冲");
    map.put(new Person("及时雨", 35),"宋江");
    
    System.out.println(map);
    
}

8.3. HashTable

特点:

  1. HashTable是map的实现类;
  2. 不允许任何null值和null键;
  3. HashTable中的元素没有顺序(跟添加的顺序无关);
  4. HashTable是线程安全的。
public static void main(String[] args) {
    //创建一个Map集合
    Map<String, String> map = new Hashtable<>();
    //向集合总加元素
    map.put("b","白胜");
    map.put("a","林冲");
    map.put("e","富安");
    map.put("c",null);
    
    System.out.println(map);

}

8.4. LinkedHashMap

特点:

  1. LinkedHashMap是map的实现类;
  2. 允许多个null值和一个null键;
  3. LinkedHashMap有顺序(添加的顺序);
  4. LinkedHashMap不是线程安全的。
public static void main(String[] args) {
    //创建一个Map集合
    Map<String, String> map = new LinkedHashMap<>();
    //向集合总加元素
    map.put("b","白胜");
    map.put("a","林冲");
    map.put("e","富安");
    map.put("c",null);
    
    System.out.println(map);
    
}

9. 文件

9.1. 文件的构造器

public class FileDemo {

    public static void main(String[] args) {
        //文件的构造器是用来创建文件对象的, 我们创建文件对象的时候要保证文件路径正确
        File file = new File("D:\\tx.txt");
        System.out.println(file.getName());


        File file1 = new File("D:\\aaa", "拓薪教育.txt");
        System.out.println(file1.getName());


        //java中的File类的对象可以代表目录,也可以代表文件
        File file2 = new File("D:\\aaa");
        System.out.println(file2.getName());

        //根据目录的文件对象创建一个文件
        File file3 = new File(file2, "拓薪教育.txt");
        System.out.println(file3.getName());
        
    }
}

9.2. 创建文件

public class FileDemo1 {

    public static void main(String[] args) throws IOException {
        File file = new File("D:\\aaa\\hello.txt");
        //创建一个文件
        boolean newFile = file.createNewFile();
        System.out.println(newFile);

    }
}
  • 创建目录:
public class FileDemo2 {

    public static void main(String[] args) throws IOException {
        //创建一个目录的对象,创建的目录上一层的路径必须正确
        File file = new File("D:\\aaa\\bbb");
        boolean mkdir = file.mkdir();
        System.out.println(mkdir);

    }
}
  • 多及目录创建:
public class FileDemo3 {

    public static void main(String[] args) throws IOException {
        //创建一个目录的对象,创建的目录上一层的路径必须正确
        File file = new File("D:\\aaa\\ccc\\hhh\\ggg");
        boolean mkdir = file.mkdirs();
        System.out.println(mkdir);

    }
}

9.3. 文件的删除

public class FileDemo4 {

    public static void main(String[] args) throws IOException {
        /*
        File file = new File("D:\\tx.txt");
        //程序删除不走回收站
        file.delete();
		*/
		
        //删除路径的最后一层, 只有空目录才能被删除
        File file1 = new File("D:\\aaa\\bbb");
        boolean delete = file1.delete();
        System.out.println(delete);

    }
}

9.4. 文件的判断

绝对路径是指目录下的绝对位置,直接到的目标位置。

相对路径就是指由这个文件所在的路径引起的跟其它文件(或文件夹)的路径关系。

public class FileDemo5 {

    public static void main(String[] args) throws IOException {

        File file = new File("D:\\tx.txt");
        //判断文件是否存在
        boolean exists = file.exists();
        System.out.println(exists);

        //判断这个文件对象是否是文件
        boolean isFile = file.isFile();
        System.out.println("判断一个文件对象是否是文件:"+isFile);

        //判断这个文件对象是否是目录
        boolean isDir = file.isDirectory();
        System.out.println("判断一个文件对象是否是目录:"+isDir);

        boolean absolute = file.isAbsolute();
        System.out.println("判断一个文件对象是否是绝对路径:"+absolute);

        File file1 = new File("D:\\aaa\\hello.txt");
        //判断文件是否可读
        boolean b = file1.canRead();
        System.out.println("文件是否可读:"+b);

        //判断文件是否可写
        boolean w = file1.canWrite();
        System.out.println("文件是否可写:"+w);

        //判断文件是否是隐藏的
        boolean h = file1.isHidden();
        System.out.println("文件是否隐藏:"+h);

    }
}

9.5. 文件的获取功能

public class FileDemo6 {

    public static void main(String[] args) throws IOException {

        File file = new File("D:\\tx.txt");
        //获得文件的绝对路径
        String absolutePath = file.getAbsolutePath();
        System.out.println("文件的绝对路径是:"+absolutePath);

        //创建一个文件对象,用相对路径, 相对路径就是当前的项目的跟路径
        File file1 = new File("a.txt");
        //file1.createNewFile();
        String absolutePath1 = file1.getAbsolutePath();
        System.out.println("文件的绝对路径是:"+absolutePath1);

        //获得的就是当前文件对象的路径
        String path = file1.getPath();
        System.out.println("文件的路径是:"+path);

        //获得文件名
        String name = file1.getName();
        System.out.println("获得文件名:"+name);

        //获得a.txt的大小
        long length = file1.length();
        System.out.println("文件的大小是:"+length);

        //获得最后的修改时间
        long l = file1.lastModified();
        System.out.println("文件的最后修改时间:"+l);
    }
}

9.6. 目录的子文件获取

public class FileDemo7 {

    public static void main(String[] args) throws IOException {

        //获得电脑磁盘的跟目录
        File[] files = File.listRoots();
        System.out.println(Arrays.toString(files));

        //获得一个目录下的子文件对象
        File file1 = new File("D:\\aaa");
        //获得D:\aaa下的子文件对象
        File[] files1 = file1.listFiles();
        for (File f : files1){
            System.out.println(f);
        }

        //获得子文件的名字
        String[] list = file1.list();
        System.out.println(Arrays.toString(list));

    }
}

9.7. 递归

求一个数的阶乘:

public class FileDemo8 {

    public static void main(String[] args) throws IOException {

        int result = fn(5);
        System.out.println(result);
    }

    /**
     * 5! = 5*4!
     * 4! = 4*3!
     * 3! = 3*2!
     * 2! = 2*1!
     * 1! = 1
     *
     * fn(num) = num * fn(num - 1);
     *
     */
     
    public static int fn(int num){
        int result = 1;
        if(num > 1)
            //方法的自身调用自身就是递归
            result = num * fn(num - 1);
        return result;
    }
}

9.7.1.练习:递归查询文件夹的内部所有文件?

public class FileDemo9 {

    //定义一个文件目录的层级
    static int level;

    public static void main(String[] args) throws IOException {
        //创建一个目录
        File file = new File("D:\\Program Files\\eclipse-jee-mars-1-win32-x86_64\\eclipse\\workspace\\demo1");
        parseFile(file);
    }

    public static void parseFile(File file){

        if(file == null || !file.exists()){
            return;
        }
        //给层级来加一
        level++;

        //获得文件对象的子文件对象列表
        File[] files = file.listFiles();
        //遍历这些子文件
        for(File f : files){
            //打印层级的缩进
            for (int i = 0; i < level; i++)
                System.out.print("\t");


            //打印文件
            System.out.println(f.getName());
            //判断这些子文件是否是目录
            if(f.isDirectory()){
                //递归的方式来遍历
                parseFile(f);
            }
        }
        //本层次遍历完毕把层级减回来
        level--;

    }
}

9.7.2. 练习:如何删除一个文件夹下的所有文件(不包括目录)?

public class FileDemo11 {

    //定义一个文件目录的层级
    static int level;

    public static void main(String[] args) throws IOException {
        //创建一个目录
        File file = new File("D:\\aaa");
        parseFile(file);
    }

    public static void parseFile(File file){

        if(file == null || !file.exists()){
            return;
        }

        //获得文件对象的子文件对象列表
        File[] files = file.listFiles();
        //遍历这些子文件
        for(File f : files){
            //判断这些子文件是否是目录
            if(f.isDirectory()){
                //递归的方式来遍历
                parseFile(f);
            }else {
                f.delete();
            }
        }
        //删除空文件夹
        file.delete();

    }
}

10. IO流

  • IO概述:
     IO(Input/Output):输入和输出,指的是某个设备或环境进行数据的输入或者输出。例如:键盘的输入,再比如显示器就是输出设备,输出图像。
     对于java来说输入输出问题,java将它抽象成流对象来解决。
     以游戏程序为中心读取文件就是输入,写入文件是输出。
    在这里插入图片描述
  • 流的分类:
    • IO流在java中从输入输出角度分类:

      • 输入流
      • 输出流
    • IO流在java中从数据的角度来分类:

      • 字符流
         文本,我们能读的懂的都可以认为是字符流。比如:文章,java文件等等
      • 字节流
         二进制的数据,这种数据一般用文本打开我们读不懂。比如,图片文件,mp3文件,等等。

10.1. 字符流

文本,我们能读的懂的都可以认为是字符流。比如:文章,java文件等等

  • 输出流:
    在这里插入图片描述
  • 输入流:
    在这里插入图片描述

字符流的类的命名的规则:

  1. 如果是输出流就以Writer结尾;
  2. 如果是输入流就以Reader结尾。

10.2. 案例

使用字符流向一个文件输入helloworld。

步骤:

  1. 创建文件
  2. 创建输出流对象
  3. 把流指向指定的文件
  4. 释放资源
    在这里插入图片描述
public class IOTest {

    public static void main(String[] args) {

        //创建一个文件
        File file = new File("test.txt");
        Writer writer = null;

        try {
            //IO流是需要关闭的,如果不这样设计就会不能关闭资源
            writer = new FileWriter(file);
            writer.write("HelloWorld");


        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //判断writer不是空防止空指针异常
            if(writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

10.2.1. 文件的追加

public class IOTest4 {

    public static void main(String[] args) {

        Writer writer = null;

        try {
            //IO流是需要关闭的,如果不这样设计就会不能关闭资源
            //writer = new FileWriter("test1.txt", true);
            writer = new FileWriter(new File("test1.txt"), true);
            writer.write("liangliang");

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //判断writer不是空防止空指针异常
            if(writer != null) {
                try {
                    //在关闭前会做flush的事情
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

10.2.2. 输出换行

把文本写入文件中,\n代表换行,问题是不同的环境下换行的方式也不一样:

  • Windows: \r\n
  • Linux:\n
  • Mac:\r
public class IOTest5 {

    public static void main(String[] args) {

        //创建一个文件
        File file = new File("test.txt");
        Writer writer = null;

        try {
            //IO流是需要关闭的,如果不这样设计就会不能关闭资源
            writer = new FileWriter(file);
            for (int i = 0; i < 100; i++) {
                writer.write("HelloWorld\r\n");
                //每次写入10个helloworld的时候做一次flush
                if(i % 10 == 0){
                    writer.flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //判断writer不是空防止空指针异常
            if(writer != null) {
                try {
                    //在关闭前会做flush的事情
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

10.2.3. Writer的五种写入的方法

public class IOTest6 {

    public static void main(String[] args) {

        //创建一个文件
        File file = new File("test.txt");
        Writer writer = null;

        try {
            writer = new FileWriter(file);
            //定义一个数组
            char[] c = {'a','b','p','b','p'};
            /*writer.write(c);

            //把数组中的一部分写入文件
            writer.write(c, 2, 2);

            writer.write(97);
            */

            writer.write("helloworld", 2, 2);

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

10.3. 字节流

10.3.1. 字符流转向字节流的桥梁

在这里插入图片描述

public class ConverterDemo {

    public static void main(String[] args) {
        //创建字符流转换字节流的桥梁的对象
        OutputStreamWriter ow = null;

        try {
            //ow = new OutputStreamWriter(new FileOutputStream("b.txt"));
        	//字符流通向字节流的桥梁可以指定存储的编码
            ow = new OutputStreamWriter(new FileOutputStream("b.txt"),"GBK");
            ow.write("中");
            ow.flush();

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(ow != null){
                    ow.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

10.4. Properties

Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。

特点:

  1. 继承于Hashtable,是线程安全的键值对存储结构
  2. Properties 可保存在流中或从流中加载
  3. 只能保存字符串的键值对。
public class PropTest6 {


    public static void main(String[] args) {

        //创建一个Properties对象
        Properties prop = new Properties();


        InputStream in = null;
        try {
            //in = new FileReader("src/names.properties");
             in = PropTest6.class.getClassLoader().getResourceAsStream("names.properties");
             //从字节流中来加载数据到属性对象中
            prop.load(in);
            System.out.println(prop);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

10.5. 序列化流

对象输出流: ObjectOutputStream 将 Java 对象写入 OutputStream。

对象输入流: ObjectInputStream 对以前使用 ObjectOutputStream 写入的对象进行反序列化。

10.5.1. ObjectOutputStream

我们要序列化的对象需要实现序列化接口。
在这里插入图片描述
实现:
在这里插入图片描述
我们一般会提供一个 serialVersionUID

某个类序列化之后,如果类发生了 ,那么依然可以反序列化。

如果要是对多个对象来做序列化,一定要放在集合中。

10.6. 字符编码

  • 存储:
    在计算机中存储字符都是存储的字符所对应的数值以二进制的形式表示。

  • 展示:
    去相关的编码表中去查找该值(存储的值)所对应的字符。
    在这里插入图片描述
    用什么编码,就要用什么解码。

11. 线程

11.1. 线程的创建方式

11.1.1. 继承Thread类来创建线程

子类要重写run方法:

public class MyThread extends Thread {

    private String name;

    public MyThread(String name){
        this.name = name;
    }

    /**
     * 这就是线程执行的逻辑体
     */
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(name+"下载了"+i+"%");
        }
    }
}

测试代码:

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个线程的对象
        MyThread mt = new MyThread("肖申克的救赎");
        //启动一个线程
        mt.start();

        //创建一个线程的对象
        MyThread mt1 = new MyThread("当幸福来敲门");
        //启动一个线程
        mt1.start();

        //System.out.println("方法结束");

    }
}

11.1.2. 实现Runnable接口来创建线程

示例代码:

public class DownLoad implements Runnable {

    private String name;

    public DownLoad(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(name+"下载了"+i+"%");
        }
    }
}

测试代码:

public class ThreadTest {

    public static void main(String[] args) {
        //创建线程对象
        Thread t = new Thread(new DownLoad("肖申克的救赎"));
        Thread t1 = new Thread(new DownLoad("当幸福来敲门"));
        t.start();
        t1.start();
    }
}

11.2. 线程的执行原理

线程的并发执行通过多个线程不断的切换CPU的资源,这个速度非常快,我们感知不到,我们能感知到的就是三个线程在并发的执行。
在这里插入图片描述

11.3. 线程生命周期

  1. 新建: 线程被new出来;
  2. 准备就绪:线程具有执行的资格,即线程调用了start(),没有执行的权利;
  3. 运行:具备执行的资格和具备执行的权利;
  4. 阻塞:没有执行的资格和执行权利;
  5. 销毁: 线程的对象变成垃圾,释放资源。

在这里插入图片描述

11.4. 并发

互联网的项目中存在着大量的并发的案例,如卖火车票,电商网站。

范例: 火车站有100张票,4个窗口同时买票。
分析: 4个窗口是4个线程同时在运行,100票是4个线程的共享资源。

在这里插入图片描述

在这里插入图片描述

语法:synchronized(锁对象){
		//操作共享资源的代码
	 }

同步代码加在什么地方?

  1. 代码被多个线程访问
  2. 代码中有共享的数据
  3. 共享数据被多条语句操作。

11.4.1. 第一种同步处理

public class SaleTicketThread extends Thread {

    private String name;

    /**
     * 定义共享的数据100张票
     */
    static int tickets = 100;

    //创建一个锁对象,这个对象是多个线程对象共享的数据
    static Object obj = new Object();

    public SaleTicketThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {

        //卖票是持续的
        while (true){

            synchronized (obj){
                if(tickets > 0){
                    System.out.println(name+"卖出座位是"+(tickets--)+"号");
                }else{
                    break;
                }
            }

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println(name+"卖票结束");
    }
}

测试代码:

public class ThreadTest {

    public static void main(String[] args) {
        SaleTicketThread t1 = new SaleTicketThread("窗口1");
        SaleTicketThread t2 = new SaleTicketThread("窗口2");
        SaleTicketThread t3 = new SaleTicketThread("窗口3");
        SaleTicketThread t4 = new SaleTicketThread("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

11.4.2. 第二种同步处理

public class SaleTicket implements Runnable {
    /**
     * 多个线程共享的100张票
     */
    int tickets = 100;

    //创建一个锁对象,这个对象是多个线程对象共享的数据
    Object obj = new Object();

    @Override
    public void run() {
        //卖票是持续的
        while (true){
            synchronized (obj){
                if(tickets > 0){
                    System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");
                }else{
                    break;
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println(Thread.currentThread().getName()+"卖票结束");
    }

}

测试代码:

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个卖票的对象
        SaleTicket st = new SaleTicket();

        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");
        Thread t4 = new Thread(st, "窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

11.5. Synchronized

11.5.1. Synchronized如果放在对象方法上

public class SaleTicket implements Runnable {
    /**
     * 多个线程共享的100张票
     */
    int tickets = 100;

    //创建一个锁对象,这个对象是多个线程对象共享的数据
    Object obj = new Object();

    @Override
    public void run() {
        //卖票是持续的
        while (true){
            if(saleTickets()){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println(Thread.currentThread().getName()+"卖票结束");
    }


    /*public boolean saleTickets(){
        synchronized (obj){
            boolean isFinish = false;
            if(tickets > 0){
                System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");
            }else{
                isFinish = true;
            }
            return isFinish;
        }
    }*/

    /**
     *
     * @return     如果一个对象方法上有synchronized的话那么锁的对象就是this
     */
    public synchronized boolean saleTickets(){
        //synchronized (obj){
        boolean isFinish = false;
        if(tickets > 0){
            System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");
        }else{
            isFinish = true;
        }
        return isFinish;
        //}
    }
}

11.5.2. Synchronized如果在类方法上那么锁对象就是类的类对象

public class SaleTicketThread extends Thread {

    private String name;

    /**
     * 定义共享的数据100张票
     */
    static int tickets = 100;

    //创建一个锁对象,这个对象是多个线程对象共享的数据
    static Object obj = new Object();

    public SaleTicketThread(String name) {
        super(name);
        this.name = name;
    }

    @Override
    public void run() {

        //卖票是持续的
        while (true){
            if(saleTickets()){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println(name+"卖票结束");
    }

    public static synchronized boolean saleTickets(){

        boolean isFinish = false;
        if(tickets > 0){
            System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");
        }else{
            isFinish = true;
        }
        return isFinish;

    }
}

测试代码:

public class ThreadTest {

    public static void main(String[] args) {
        SaleTicketThread t1 = new SaleTicketThread("窗口1");
        SaleTicketThread t2 = new SaleTicketThread("窗口2");
        SaleTicketThread t3 = new SaleTicketThread("窗口3");
        SaleTicketThread t4 = new SaleTicketThread("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

11.6. 休眠

在做服务器端的程序的时候都需要给一个休眠的时间,在没有 synchronized 代码块里面会让出cpu的资源。

public static void main(String[] args) {
    while(true){
        System.out.println(new Date());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

休眠在同步代码块内不会让出cpu:

synchronized (ojb){
    try {

        //我们休眠如果在synchronized内部就不会让出cpu的资源
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

11.7. 线程间的通信

在这里插入图片描述
生产者生成水果,如果水果没有被买走那么就不生产处于等待状态,如果水果被消费者买走这时候消费者会通知生产者告诉他我们已经把水果买走了请生产,消费者同理,如果水果已经生产出来那么就买走,买走之后再通知生产者水果已经没了请生产。

注意:

  1. 线程间的通信共享数据一定要有同步代码块synchronized;
  2. 一定要有wait和notify,而且二者一定是成对出现;
  3. 生产者和消费者的线程实现一定是在while(true)里面。

11.7.1. 第一种实现方式:

水果篮子:

public class Basket {

    private boolean isEmpty;

    public boolean isEmpty() {
        return isEmpty;
    }

    public void setEmpty(boolean empty) {
        isEmpty = empty;
    }
    
}

生产者:

public class Producer extends Thread {

    private Basket basket;

    public Producer(Basket basket){
        super();
        this.basket = basket;
    }

    @Override
    public void run() {

        while(true){
            //定义一个同步代码块
            synchronized (basket){
                try {
                    if(!basket.isEmpty()){
                        //线程等待的状态
                        basket.wait();
                    }
                    System.out.println("生成水果");
                    basket.setEmpty(false);
                    //通知在这个共享对象上等待的线程
                    basket.notify();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {

            }

        }
    }
}

消费者:

package cn.tx.demo8;

public class Consumer extends Thread{

    private Basket basket;

    public Consumer(Basket basket){
        super();
        this.basket = basket;
    }

    @Override
    public void run() {

        while(true){
            //定义一个同步代码块
            synchronized (basket){
                try {
                    if(basket.isEmpty()){
                        //线程等待的状态
                        basket.wait();
                    }
                    System.out.println("消费水果");
                    basket.setEmpty(true);
                        //通知在这个共享对象上等待的线程
                    basket.notify();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }
    }
}

11.7.2. 线程的第二种实现方式实现runnable

水果篮子:

public class Basket {
    private boolean isEmpty;

    public boolean isEmpty() {
        return isEmpty;
    }

    public void setEmpty(boolean empty) {
        isEmpty = empty;
    }
}

生产者:

package cn.tx.demo9;

public class Producer implements Runnable {


    private Basket basket;

    public Producer(Basket basket){
        super();
        this.basket = basket;
    }

    @Override
    public void run() {

        while(true){
            //定义一个同步代码块
            synchronized (basket){
                try {
                    if(!basket.isEmpty()){
                        //线程等待的状态
                        basket.wait();
                    }
                    System.out.println("生成水果");
                    basket.setEmpty(false);
                    //通知在这个共享对象上等待的线程
                    basket.notify();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {

            }

        }
    }
}

消费者:

package cn.tx.demo9;

public class Consumer implements Runnable{

    private Basket basket;

    public Consumer(Basket basket){
        super();
        this.basket = basket;
    }

    @Override
    public void run() {

        while(true){
            //定义一个同步代码块
            synchronized (basket){
                try {
                    if(basket.isEmpty()){
                        //线程等待的状态
                        basket.wait();
                    }
                    System.out.println("消费水果");
                    basket.setEmpty(true);
                        //通知在这个共享对象上等待的线程
                    basket.notify();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }
    }

}

测试:

public class Test {

    public static void main(String[] args) {
        Basket basket = new Basket();
        Producer producer = new Producer(basket);
        Consumer consumer = new Consumer(basket);

        Thread t = new Thread(producer);
        Thread t1 = new Thread(consumer);

        t.start();
        t1.start();
    }
}

11.8. 线程的优先级

我们可以通过public final void setPriority(int newPriority)来设置线程的优先级,但是优先级并不是绝对的,只是先对来说比其他的线程得到CPU的资源机会多一些。

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个线程的对象
        MyThread mt = new MyThread("肖申克的救赎");
        //创建一个线程的对象
        MyThread mt1 = new MyThread("当幸福来敲门");

        mt.setPriority(Thread.MAX_PRIORITY);
        //启动一个线程
        mt.start();
        //启动一个线程
        mt1.start();

        //System.out.println("方法结束");

    }
}

11.9. 加入线程

join线程会抢先拿到CPU来执行线程,然后其他的线程再来执行。
加入线程必须要在先执行的线程的start下面来执行。

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个线程的对象
        MyThread mt = new MyThread("肖申克的救赎");
        //创建一个线程的对象
        MyThread mt1 = new MyThread("当幸福来敲门");

        MyThread mt2 = new MyThread("魔戒1");

        mt.start();
        try {
            //加入线程必须要在先执行的线程的start下面来执行
            mt.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mt1.start();

        mt2.start();
    }
}

11.10. 让出线程

当前的线程从运行阶段回到就绪阶段,目的是把CPU的资源让给其他的线程。

public class MyThread extends Thread {

    private String name;

    public MyThread(String name){
        this.name = name;
    }
    /**
     * 这就是线程执行的逻辑体
     */
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(name+"下载了"+i+"%");
            //让出线程
            Thread.yield();
        }
    }
}

11.11. 守护线程

守护线程会随着主线程的结束而结束。

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个线程的对象
        MyThread mt = new MyThread("肖申克的救赎");
        //设置守护线程
        mt.setDaemon(true);
        mt.start();
        System.out.println("主线程结束");
    }
}

11.12. 死锁

在这里插入图片描述
在做线程开发的时候要避免出现死锁。
死锁程序:

public class DeadLockThread extends Thread {

    int flag;

    @Override
    public void run() {
        if(flag == 1){
            synchronized (Lock.lock1){
                System.out.println("进入锁1");
                synchronized (Lock.lock2){
                    System.out.println("进入锁1中的锁2");
                }
            }
        }else{
            synchronized (Lock.lock2){
                System.out.println("进入锁2");
                synchronized (Lock.lock1){
                    System.out.println("进入锁2中的锁1");
                }
            }
        }
    }
}

测试:

public class DeadLockTest {

    public static void main(String[] args) {
        DeadLockThread dt = new DeadLockThread();
        dt.flag = 1;
        DeadLockThread dt1 = new DeadLockThread();

        dt.start();
        dt1.start();

    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值