Wt Blog


  • 首页

  • 归档

Java-Date-case

发表于 2019-06-01

两个日期常见案例

  • 程序启动要求用户输入自己的生日,格式:yyyy-MM-dd HH:mm:ss
  • 然后经过程序计算输出到今天位置一共活了多少天
  • 再经过计算输出其出生10000天的纪念日为哪天格式同上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package date;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Scanner;

    public class Test {
    public static void main(String[] args) throws ParseException {
    System.out.println("请输入你的生日:(如0000-00-00)");
    Scanner scan = new Scanner(System.in);
    String line = scan.nextLine();

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date birth = sdf.parse(line);

    Date now = new Date();
    long time = now.getTime()-birth.getTime();
    time = time/1000/60/60/24;
    System.out.println("到今天为止一共活了"+time+"天");

    // //1:第一种方法
    // time = 10000-time;
    // System.out.println("距离活够10000天还有"+time+"天");
    // time = time*1000*60*60*24;
    // now.setTime(now.getTime()+time);
    // System.out.println("生存10000天的纪念日为:"+sdf.format(now));

    //2:第二种方法
    time = birth.getTime()+1000L*60*60*24*10000;
    Date date = new Date(time);
    String str = sdf.format(date);
    System.out.println("10000天的纪念日为:"+str);
    }
    }
  • 编写程序计算商品促销日期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package date;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.Scanner;
    /**
    * 程序启动要求输入商品的生产日期:yyyy-MM-dd
    * 然后再输入保质期的天数。
    * 经过程序计算输出该商品促销日期,格式同上。
    *
    * 促销日期计算规则:商品过期日前2周的周3
    */
    public class Test2 {
    public static void main(String[] args) throws ParseException {
    Scanner scan = new Scanner(System.in);
    System.out.println("请输入商品生产日期:(如2019-12-01)");
    String data = scan.nextLine();
    System.out.println("请输入商品保质期:单位是(天)");
    int day = scan.nextInt();

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date date = sdf.parse(data);
    Calendar c = Calendar.getInstance();
    c.setTime(date);
    c.add(Calendar.DAY_OF_YEAR, day);
    c.add(Calendar.DAY_OF_YEAR, -14);
    c.set(Calendar.DAY_OF_WEEK, Calendar.WEDNESDAY);
    date = c.getTime();
    String input = sdf.format(date);
    System.out.println("促销日期为:" + input);
    }
    }

Java-Date2

发表于 2019-05-31

java.text.SimpleDateFormat(DateFormat的直接子类)

  • 这个SimpleDateFormat类是我们日常转换字符串和Date对象最常用的类。
  • 我们如果用DateFormat进行转换格式,只能用它规定的几种格式进行转换,非常的不直观,从字面我们看不出来从Date转出来的日期是怎样的格式。
  • 但是如果用SimpleDateFormat就不一样了,在构造SimpleDateFormat我们可以直接传一个pattern,只要pattern符合要求且是合理的,就可以对Date和字符串进行转换了。
  • 日期和时间格式由日期和时间模式 字符串指定。在日期和时间模式字符串中,未加引号的字母 ‘A’ 到 ‘Z’ 和 ‘a’ 到 ‘z’ 被解释为模式字母,用来表示日期或时间字符串元素。文本可以使用单引号 (‘) 引起来,以免进行解释。’’ 表示单引号。所有其他字符均不解释;只是在格式化时将它们简单复制到输出字符串,或者在分析时与输入字符串进行匹配。

  • 定义了以下模式字母(所有其他字符 ‘A’ 到 ‘Z’ 和 ‘a’ 到 ‘z’ 都被保留):

    • G 年 代标志符
    • y 年
    • M 月
    • d 日
    • h 时 在上午或下午 (1~12)
    • H 时 在一天中 (0~23)
    • m 分
    • s 秒
    • S 毫秒
    • E 星期
    • D 一年中的第几天
    • F 一月中第几个星期几
    • w 一年中第几个星期
    • W 一月中第几个星期
    • a 上午 / 下午 标记符
    • k 时 在一天中 (1~24)
    • K 时 在上午或下午 (0~11)
    • z 时区
  • SimpleDateFormat类方法使用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package date;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    /**
    * java.text.SimpleDateFormat
    * 可以按照指定的日期格式在Date与String之间相互转换
    */
    public class SimpleDateFormatDemo {
    public static void main(String[] args) {
    Date now = new Date();
    System.out.println(now);
    /*
    * 2019-05-29 10:26:24
    * yyyy-MM-dd HH:mm:ss
    */
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /*
    * String format(Date date)
    * 将给定的日期按照SDF指定的日期格式转换为
    * 一个字符串并返回。
    */
    String str = sdf.format(now);
    System.out.println(str);
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 将一个字符串解析为Date
*/
public class SimpleDateFormatDemo2 {
public static void main(String[] args) throws ParseException {
String line = "2008-12-07 00:00:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/*
* Date parse(String str)
* 将给定的字符串按照SDF指定的日期格式解析为一个Date对象
*/
Date date = sdf.parse(line);
System.out.println(date);
}
}

Java-Date(一)

发表于 2019-05-30

Java 日期时间

Java日期涉及几大类

1. java.util.Date

  • Date类负责时间的表示,在计算机中,时间的表示是一个较大的概念,现有的系统基本都是利用从1970.1.1 00:00:00 到当前时间的毫秒数进行计时,这个时间称为epoch。在后文中如果没有明确说明,毫秒数就是指从1970年到对应时间的毫秒数。在Java 的Date类内部其实也是一个毫秒数,对外表现为一个Date对象。
    Date类方法
  • Date()
    • 分配一个 Date对象,并初始化它,以便它代表它被分配的时间,测量到最近的毫秒。
  • Date(long date)
    • 分配一个 Date对象,并将其初始化为表示自称为“时代”的标准基准时间以后的指定毫秒数,即1970年1月1日00:00:00 GMT。
  • void setTime(long date)
    • 使用给定的毫秒时间值设置现有的 Date对象。
  • void setTime(long time)
    • 设置此 Date对象以表示1970年1月1日00:00:00 GMT后的 time毫秒的时间点。
  • Instant toInstant()
    • 此方法总是引发UnsupportedOperationException,因为SQL Date值没有时间组件,所以不应该使用。
  • LocalDate toLocalDate()
    • 将此 Date对象转换为 LocalDate
  • String toString()
    • 格式化日期转义格式yyyy-mm-dd。
  • static Date valueOf(LocalDate date)
    • 从一个LocalDate对象获取一个Date的实例,具有与给定的LocalDate相同的年,月和日的月值。
  • static Date valueOf(String s)
    • 将JDBC日期转义格式的字符串转换为 Date值。
  • boolean after(Date when)
    • 测试此日期是否在指定日期之后。
  • boolean before(Date when)
    • 测试此日期是否在指定日期之前。
  • Object clone()
    • 返回此对象的副本。
  • int compareTo(Date anotherDate)
    • 比较两个日期进行订购。
  • boolean equals(Object obj)
    • 比较两个日期来平等。
  • static Date from(Instant instant)
    • 从 Instant对象获取一个 Date的实例。
  • long getTime()
    • 返回自1970年1月1日以来,由此 Date对象表示的00:00:00 GMT的毫秒 数 。
  • int hashCode()
    • 返回此对象的哈希码值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//常用几个方法使用
public static void main(String[] args) {
//当前系统时间
Date date = new Date();
System.out.println(date);
/*
* 获取Date内部维护的毫秒
*/
long time = date.getTime();
System.out.println(time);
/*
* 设置一个毫秒值,使得Date表示该日期
*/
date.setTime(10);
System.out.println(date);
/*
* Date大部分方法被声明为过时的,新开发项目不要再使用这些方法。
*/
date.getYear();
}

2.java.util.Calendar(抽象类)

  • Calendar类说是一个工具类,其实它比工具类还是更深一步的,它更像是一个加强版的Date类。一般的工具类会提供一堆static方法,工具类本身并不存储对象。但是Calendar类不是像一般的工具类只提供一堆static方法,而是在其内部本身就有一个毫秒数,所以它不是对外部Date对象操作,而是对内部的毫秒数进行转化,所以说Calendar本身就包含了日期时间信息,像一个装饰者模式。
  • Calendar本身是一个抽象类,不能直接实例化,但是Calendar类提供一个工厂方法,即getInstance来创建一个Calendar实例,通过setTime()设定一个Calendar内部的毫秒数,之后就可以对这个毫秒数进行分析,进而得到它的年月日信息。同时,我们也可以对Calendar直接设定年月日属性,从而获取对应的Date对象。
  • GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家使用的标准日历系统。结合Calendar抽象类使用。
Calendar类对象字段类型
常量 描述
Calendar.YEAR 年份
Calendar.MONTH 月份
Calendar.DATE 日期
Calendar.DAY_OF_MONTH 日期,和上面的字段意义完全相同
Calendar.HOUR 12小时制的小时
Calendar.HOUR_OF_DAY 24小时制的小时
Calendar.MINUTE 分钟
Calendar.SECOND 秒
Calendar.DAY_OF_WEEK 星期几
  • Calendar中需要注意的:
  1. Calendar的星期是从周日开始的,常量值为0。
  2. Calendar的月份是从一月开始的,常量值为0。
  3. Calendar的每个月的第一天值为1。
  • 常用方法示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public static void main(String[] args) {
    /*
    * static Calendar getInstance()
    * 根据当前系统所在地区获取一个适用的实现类
    * 通常返回的就是GregorianCalendar
    */
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar);
    /*
    * Date getTime()
    * 将当前Calendar的表示的时间一个Date实例形式返回。
    */
    Date date = calendar.getTime();
    System.out.println(date);
    /*
    * void setTime()
    * 调整当前calendar的时间为给定的Date所表示的时间。
    */
    calendar.setTime(date);

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package date;
import java.util.Calendar;
/**
* int get(int field)
* 该方法可以获取指定的时间分量所对应的值。
* 时间分量为一个int值,无需记住每个值得含义,
* Calendar提供了大量的常量与之对应。
*/
public class CalendarDemo2 {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();
/*
* 1:获取年
* 2:获取月
* 3:获取日
* 与"天"相关的时间分量
* DAY_OF_MONTH:月中的天(DATE)
* DAY_OF_WEEK:周中的天,星期几
* DAY_OF_YEAR:年中的天
* HOUR 12小时制的小时
* HOUR_OF_DAY 24小时制的小时
* MINUTE 分钟
* SECOND 秒
*/
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
int h = c.get(Calendar.HOUR_OF_DAY);
int m = c.get(Calendar.MINUTE);
int s = c.get(Calendar.SECOND);
System.out.println(year + "-" + month + "-" + day);
System.out.println(h + ":" + m + ":" + s);

int days = c.get(Calendar.DAY_OF_YEAR);
System.out.println("今年的第" + days + "天");
days = c.get(Calendar.DAY_OF_WEEK);
String[] data = { "日", "一", "二", "三", "四", "五", "六" };
System.out.println("今天是周" + data[days - 1]);
// 获取指定时间分量所允许的最大值
days = c.getActualMaximum(Calendar.DAY_OF_YEAR);
System.out.println("今年一共" + days + "天");
days = c.getActualMaximum(Calendar.DAY_OF_MONTH);
System.out.println("当月一共" + days + "天");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package date;
import java.util.Calendar;
/**
* void set(int field , int value)
* 调整指定的时间分量为指定的值
*/
public class CalendarDemo3 {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();
System.out.println(c.getTime());

// 调整年
c.set(Calendar.YEAR, 2000);
System.out.println(c.getTime());
// 调整月
c.set(Calendar.MONTH, Calendar.DECEMBER);
System.out.println(c.getTime());

c.set(Calendar.HOUR_OF_DAY, 20);
c.set(Calendar.MINUTE, 20);
c.set(Calendar.SECOND, 20);
System.out.println(c.getTime());

c.set(Calendar.DAY_OF_WEEK, 4);
System.out.println(c.getTime());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package date;
import java.util.Calendar;
/**
* void add(int field,int amount)
* 对指定的时间分量加上给定的值,若给定的值为负数则是减去。
*/
public class CalendarDemo4 {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();
/*
* 查看三年二月零25天以后所在周的周三是哪天?
*/
//加年
c.add(Calendar.YEAR, 3);
//加月
c.add(Calendar.MONTH, 2);
//加天
c.add(Calendar.DAY_OF_YEAR, 25);
System.out.println(c.getTime());
c.set(Calendar.DAY_OF_WEEK, 4);
System.out.println(c.getTime());
}
}

Java-Lambda

发表于 2019-05-29

Lambda表达式

  • Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。
  • 使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多。
  • 语法:

    1
    2
    3
    (参数列表)->{
    方法体
    }
  • lambda表达式的重要特征:

    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
  • 没有使用Lambda的老方法:

    1
    2
    3
    4
    5
    Runnable runnable1=new Runnable(){
    public void run(){
    System.out.println("Hello!");
    }
    };
  • 使用Lambda:

    1
    2
    3
    4
    5
    Runnable r2 = () -> {
    System.out.println("Hi!");
    };
    // 只有一句代码 {}可以省略
    Runnable r3 = () -> System.out.println("Hi!");
  • 方法中含有参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("one");
    list.add("three");
    list.add("four");

    Collections.sort(list,new Comparator<String>(){
    public int compare(String o1, String o2) {
    return o2.length()-o1.length();
    }
    });
    System.out.println(list);
    /*
    * 参数类型无需指定,编译器会根据程序长下文分析出参数类型,
    * 若不能确定时需要指定(否则编译不通过)
    */
    Collections.sort(list,(o1,o2)->{
    return o1.length()-o2.length();
    });
    System.out.println(list);
    /*
    * 如果可以忽略{} 那么方法中的return关键字也要省略
    */
    Collections.sort(list,(o1,o2)->o1.length()-o2.length());
    }

Java-reflect

发表于 2019-05-28

Java 反射

什么是反射?

  • 反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
  • 简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
  • 反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
  • Java 反射主要提供以下功能:

    • 在运行时判断任意一个对象所属的类;
    • 在运行时构造任意一个类的对象;
    • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
    • 在运行时调用任意一个对象的方法
      重点:是运行时而不是编译时
  • 注意事项:

  • 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
  • 另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

Class类

  • 类对象,它的每一个实例用于表示JVM加载的一个类。并且在JVM内部,每个被加载的类有且只有一个类对象。通过类对象可以得知其表示的类的一切信息(类名,有哪些方法,属性,构造器等),并且可以快速实例化该类的实例。

Class.forName() 动态加载类

  • 动态加载类到内存方法区:
    1
    Class cls = Class.forName(类名)
  1. 类名是运行期间动态输入的类名,可以任何类名。
  2. 返回值是一个引用,利用这个引用指向的对象可以访问方法区中的类信息。
  3. 如果类名是错的将会出现”类没有找到”异常,(ClassNotFoundException)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package reflect;
    import java.util.Scanner;
    public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
    Scanner scan = new Scanner(System.in);
    System.out.println("请输入要加载的类名:");
    String className = scan.nextLine();
    // Class cls = Class.forName(className);
    Class cls = String.class;
    String name = cls.getName();
    System.out.println(name);
    }
    }

newInstance() 动态创建对象

  • Class提供了动态创建对象的方法:
    1
    Object newInstance()
  1. newInstance方法将调用类信息中的无参数构造器创建对象,如果没有无参数构造器,将抛出没有方法的异常。
  2. 如果需要调用有参数构造器,可以利用Constructor API实现。
  3. 返回值引用动态创建的对象,因为可以是任何类型的对象,所以其类型为Object。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package reflect;
    import java.util.Scanner;
    public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
    Scanner scan = new Scanner(System.in);
    System.out.println("请输入要实例化的类名:");
    String className = scan.nextLine();
    Class cls = Class.forName(className);
    Object o = cls.newInstance();
    System.out.println(o);
    }
    }

动态调用方法

动态发现方法

  • Class提供了方法可以动态获取类的全部方法信息:
    1
    Method[] getMethods()
  1. Method代表方法信息,可以利用Method API获取方法对详细信息,如:方法名,返回值类型列表等。
  2. 这个方法返回对数组代表当前类中对全部方法信息,每个元素代表一个方法信息。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package reflect;
    import java.util.Scanner;
    import java.lang.reflect.Method;
    public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
    Scanner scan = new Scanner(System.in);
    System.out.println("请输入要加载的类名:");
    String className = scan.nextLine();
    // Class cls = Class.forName(className);
    Class cls = String.class;
    String name = cls.getName();
    System.out.println(name);
    //Method类的每一个实例用于表示一个具体的方法
    Method[] methods = cls.getMethods();
    for(Method m : methods) {
    System.out.println(m.getName());
    }
    }
    }

动态执行方法

  • Method提供了动态执行一个方法的方法:
    1
    Object invoke(obj, args)
  1. obj代表一个对象,该对象上一定包含当前方法!否则将会出现调用异常,如果obj为null则抛出空指针异常。
  2. args代表调用方法时候传递的实际参数,如果没有参数可以不用或者传递null,但是要注意参数的个数和类型必须和要调用的方法匹配,否则会出现参数错误异常。
  3. 返回值表示方法执行的结果,因为可能是任何类型,则其类型为Object,调用没有返回值的方法则返回值为null;
  4. 当被调用方法执行出现异常时候抛出InvocationTargetException。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package reflect;
    import java.lang.reflect.Method;
    public class ReflectDemo4 {
    public static void main(String[] args) throws Exception {
    /*
    * 1:加载类对象
    * 2:获取对应方法
    * 3:执行该方法
    */
    Class cls = Class.forName("reflect.Person");
    Object o = cls.newInstance();
    // Method m = cls.getMethod("sayHello");
    // m.invoke(o);

    Method me = cls.getMethod("say", String.class);
    me.invoke(o, "wangtao");
    }
    }

执行不可访问方法

  • 如果利用反射API调用了没有可访问权限时候会抛出异常:IllegalAccessException,表示没有访问权限。
  • 但是在Method方法上提供了解除访问限制的方法:

    1
    setAccessible(boolean flag)
  • 在invoke之前使用这个方法可以解除访问限制,实现访问没有权限的方法。

  • 注意:这个功能破坏了面向对象原有的封装性,但是利用它却能写出一些特殊功能代码。

变长参数

  • 在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用。
  • 演示案例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package reflect;
    public class ReflectDemo5 {
    public static void main(String[] args) {
    dosome();
    dosome("one","two");
    dosome("one","two","three");
    dosome("one","two","three","one","two","three");
    }
    /**
    * 变长参数在一个方法中只能定义一个,
    * 并且只能作为方法的最后一个参数使用。
    * 变长参数本质就是一个数组。
    * @param s
    */
    public static void dosome(String... s) {
    System.out.println(s.length);
    }
    }

Java-Map

发表于 2019-05-27

Map接口

  • Map接口中键和值一一映射. 可以通过键来获取值。
    • 给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值。
    • 当访问的值不存在的时候,方法就会抛出一个NoSuchElementException异常.
    • 当对象的类型和Map里元素类型不兼容的时候,就会抛出一个 ClassCastException异常。
    • 当在不允许使用Null对象的Map中使用Null对象,会抛出一个NullPointerException 异常。
    • 当尝试修改一个只读的Map时,会抛出一个UnsupportedOperationException异常。

方法

  • void clear()
    • 从该地图中删除所有的映射(可选操作)。
  • default V compute(K key, BiFunction< ? super K,? super V,? extends V> remappingFunction)
    • 尝试计算指定键的映射及其当前映射的值(如果没有当前映射, null )。
  • default V computeIfAbsent(K key, Function< ? super K,? extends V> mappingFunction)
    • 如果指定的键尚未与值相关联(或映射到 null ),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非 null 。
  • default V computeIfPresent(K key, BiFunction< ? super K,? super V,? extends V> remappingFunction)
    • 如果指定的密钥的值存在且非空,则尝试计算给定密钥及其当前映射值的新映射。
  • boolean containsKey(Object key)
    • 如果此映射包含指定键的映射,则返回 true 。
  • boolean containsValue(Object value)
    • 如果此地图将一个或多个键映射到指定的值,则返回 true 。
  • Set<Map.Entry<K,V>> entrySet()
    • 返回此地图中包含的映射的Set视图。
  • boolean equals(Object o)
    • 将指定的对象与此映射进行比较以获得相等性。
  • default void forEach(BiConsumer< ? super K,? super V> action)
    • 对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常。
  • V get(Object key)
    • 返回到指定键所映射的值,或 null如果此映射包含该键的映射。
  • default V getOrDefault(Object key, V defaultValue)
    • 返回到指定键所映射的值,或 defaultValue如果此映射包含该键的映射。
  • int hashCode()
    • 返回此地图的哈希码值。
  • boolean isEmpty()
    • 如果此地图不包含键值映射,则返回 true 。
  • Set< K > keySet()
    • 返回此地图中包含的键的Set视图。
  • default V merge(K key, V value, BiFunction< ? super V,? super V,? extends V> remappingFunction)
    • 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。
  • V put(K key, V value)
    • 将指定的值与该映射中的指定键相关联(可选操作)。
  • void putAll(Map< ? extends K,? extends V> m)
    • 将指定地图的所有映射复制到此映射(可选操作)。
  • default V putIfAbsent(K key, V value)
    • 如果指定的键尚未与某个值相关联(或映射到 null )将其与给定值相关联并返回 null ,否则返回当前值。
  • V remove(Object key)
    • 如果存在(从可选的操作),从该地图中删除一个键的映射。
  • default boolean remove(Object key, Object value)
    • 仅当指定的密钥当前映射到指定的值时删除该条目。
  • default V replace(K key, V value)
    • 只有当目标映射到某个值时,才能替换指定键的条目。
  • default boolean replace(K key, V oldValue, V newValue)
    • 仅当当前映射到指定的值时,才能替换指定键的条目。
  • default void replaceAll(BiFunction< ? super K,? super V,? extends V> function)
    • 将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。
  • int size()
    • 返回此地图中键值映射的数量。
  • Collection< V > values()
    • 返回此地图中包含的值的Collection视图。

HashMap

  • hashMap是一个用于存储key-value键值对的集合,每一个键值对也被叫做Entry。这些个键值对分散存储在一个数组当中,这个数组就是HaspMap的主干。
  • 总体上看,Hashmap分为很多个数组,每一个数组里面存放着一个链表。

hashCode方法

  • key的hashCode()方法的返回值对HashMap存储元素时起着很重要的作用。而hashCode()方法实际上是在Object中定义的。那么应当妥善重写该方法:
    • 对于重写equals方法的对象,一般要妥善的重写继承自Object类的hashCode方法,Object提供的hashCode方法将返回该对象所在内存地址的整数形式。
    • 重写hashCode方法时需注意两点:
      • 1:与equals方法的一致性,即equals比较返回true的两个对象其hashCode方法返回值应该相同。
      • 2:hashCode返回的数值应符合hash算法的要求,如果有很多对象的hashCode方法返回值都相同,则会大大降低hash表的效率。
  • Capacity:容量,hash表里bucket(桶)的数量,也就是散列数组大小。
  • Initial capacity:初始容量,创建hash表示,初始bucket的数量,默认构建容量是16,也可以使用特定容量。
  • Size:大小,当前散列表中储存数据的数量。
  • Load factor:加载因子,默认值0.75(75%),当向散列表增加数据时如果size/capacity的值大于Load factor则发生扩充并且重新散列(rehash)。
  • 性能优化:加载银子较小时,散列查找性能会提高,同时也浪费了散列桶空间容量。0.75是性能和空间相对平衡结果。在创建散列表时指定合理容量,减少rehash提高性能。

Java-XML

发表于 2019-05-25

XML

  • 可扩展标记语言(英语:Extensible Markup Language,简称:XML),是一种标记语言。标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。如何定义这些标记,既可以选择国际通用的标记语言,比如HTML,也可以使用像XML这样由相关人士自由决定的标记语言,这就是语言的可扩展性。XML是从标准通用标记语言(SGML)中简化修改出来的。它主要用到的有可扩展标记语言、可扩展样式语言(XSL)、XBRL和XPath等。

用途

XML设计用来传送及携带数据信息,不用来表现或展示数据,HTML则用来表现数据,所以XML用途的焦点是它说明数据是什么,以及携带数据信息。

  • 丰富文件(Rich Documents)- 自定文件描述并使其更丰富
    • 属于文件为主的XML技术应用
    • 标记是用来定义一份资料应该如何呈现
  • 元数据(Metadata)- 描述其它文件或网络资讯
    • 属于资料为主的XML技术应用
    • 标记是用来说明一份资料的意义
  • 配置文档(Configuration Files)- 描述软件设置的参数

处理节点

xml 推荐在第一行,必须写在第一行,可以省略。

1
<?xml version="1.0" encoding="UTF-8"?>

标记(标签) tag

语法:

1
2
开始标记 <标记名>  
结束标记 </标记名>

标记规则:

  1. 中文英文都可以作为标记名,建议使用英文
  2. 区分大小写
  3. 开始标记和结束标记要成对使用
  4. 不能交叉嵌套
  5. 一个XML中只有唯一的根标记
    标记名可以扩展 标记的嵌套关系可以扩展

注释 Comment

语法:

1
<!-- 注释 -->

注释不能嵌套使用!!

内容 Content

语法: 开始标记和结束标记之间的信息称为内容。

1
2
3
4
5
6
<name>檀香刑</name>
<book><name>檀香刑</name></book>
<book>
<name>檀香刑</name>
<author>莫言</author>
</book>

内容:

  1. 文本内容
  2. 标记
  3. 多个标记和文本的混合
  4. 内容是可扩展的!

元素 Element

语法:

1
标记 + 内容 = 元素

  1. XML 只有一个根元素
  2. 元素中的元素称为子元素,外层元素称为父元素

属性

语法:

1
2
<book id="b1" lang="cn">
<book lang="cn" id="b1">

  1. 在开始标记上定义元素的属性
  2. 可以定义多个属性
  3. 多个属性不分先后次序
  4. 属性之间需要有空格
  5. 属性名=”属性值” 属性和值之间等号前后不用写空格,属性值必须用 引号

实体 Entity

实体: 类似于 Java 的字符串中的转义字符,一些用于替换表示XML语法的字符。 用来解决XML文字解析错误
常用实体:

1
2
3
< &lt;
> &gt;
& &amp;

CDATA

使用CDATA包裹的文本,可以写任何字符,无需进行实体替换

1
<![CDATA[ 文本内容 ]]>

XML可扩展性

  1. 元素(标记)名可以扩展
  2. 元素的嵌套关系可以扩展
  3. 元素的属性可以扩展
  4. 属性名可以扩展
  5. 内容可以扩展

在Java中使用XML

Java 业界有API可以读取XML文件。读取以后可以按照元素、属性进行结构分解。
Dom4J 读作 Dom for J
Dom4J 的底层

1
IO 流 -> W3C Dom -> Dom4J

利用Maven导入dom4j API

得到Dom4j 的maven组件坐标

1
2
3
4
5
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>

将组件坐标添加到 pom.xml 文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tedu</groupId>
<artifactId>XML01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</project>

Dom4j API 的使用

读取XML

Dom4j提供了API将XML文件读取为Dom对象(Document)

1
2
3
4
5
6
7
8
9
10
/*
* 使用dom4j读取xml文件
*/
SAXReader reader = new SAXReader();

//doc=reader.read(文件、文件流)
Document doc=
reader.read(new File("books.xml"));

System.out.println(doc.asXML());

利用Dom API(Document)可以访问Dom树中的数据

  1. 访问根元素

    1
    2
    3
    4
    5
    //读取根元素, Root 根, Element元素
    Element root = doc.getRootElement();
    //输出根元素root中的内容
    System.out.println("根元素:");
    System.out.println(root.asXML());
  2. 可以获取元素中全部的子元素

    1
    List<Element> list=root.elements();
  3. 可以获取一批名字一样的子元素

    1
    List<Element> list= root.elements("book");
  4. 获取一个指定名字的子元素, 适合元素的子元素名字都不同,获取其中的一个子元素时候使用。

    1
    Element e = book.element("name");
  5. 获取元素中文字内容

    1
    2
    String s1 = name.getText()
    String s2 = name.getTextTrim() //去除前后空白,常用!
  6. 获取元素的属性值

    1
    2
    <book id="b1" >
    String s = book.attributeValue("id");
  7. 直接获取子元素的文本内容

    1
    String name = book.elementTextTrim("name");

Java-Collection

发表于 2019-05-22

Java 集合框架

  • 从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
  • 集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:
    • 接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
    • 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
    • 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
      集合与数组一样,都是用来保存一组数据的,但是集合提供了操作元素的相关方法,使用更方便,并且集合有多种不同的数据结构实现。

Java集合和数组的区别:

  • 数组长度在初始化时指定,意味着只能保存定长的数据。而集合可以保存数量不确定的数据。同时可以保存具有映射关系的数据(即关联数组,键值对 key-value)。
  • 数组元素即可以是基本类型的值,也可以是对象。集合里只能保存对象(实际上只是保存对象的引用变量),基本数据类型的变量要转换成对应的包装类才能放入集合类中。

集合接口

  • Collection 接口
    Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。
    Collection 接口存储一组不唯一,无序的对象。

  • List 接口
    List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。
    List 接口存储一组不唯一,有序(插入顺序)的对象。

  • Set
    Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。
    Set 接口存储一组唯一,无序的对象。

  • SortedSet
    继承于Set保存有序的集合。

  • Map
    Map 接口存储一组键值对象,提供key(键)到value(值)的映射。

  • Map.Entry
    描述在一个Map中的一个元素(键/值对)。是一个Map的内部类。

  • SortedMap
    继承于 Map,使 Key 保持在升序排列。

  • Enumeration
    这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。

  • Set和List的区别:
    • Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
    • Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
    • List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。

集合实现类(集合类)

  • Java提供了一套实现了Collection接口的标准集合类。其中一些是具体类,这些类可以直接拿来使用,而另外一些是抽象类,提供了接口的部分实现。
  • AbstractCollection
    实现了大部分的集合接口。
  • AbstractList
    继承于AbstractCollection 并且实现了大部分List接口。
  • AbstractSequentialList
    继承于 AbstractList ,提供了对数据元素的链式访问而不是随机访问。
  • LinkedList
    该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如:

    1
    2
    Listlist=Collections.synchronizedList(newLinkedList(...));
    LinkedList 查找效率低。
  • ArrayList
    该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低。

  • AbstractSet
    继承于AbstractCollection 并且实现了大部分Set接口。
  • HashSet
    该类实现了Set接口,不允许出现重复元素,不保证集合中元素的顺序,允许包含值为null的元素,但最多只能一个。
  • LinkedHashSet
    具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。
  • TreeSet
    该类实现了Set接口,可以实现排序等功能。
  • AbstractMap
    实现了大部分的Map接口。
  • HashMap
    HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
    该类实现了Map接口,根据键的HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。
  • TreeMap
    继承了AbstractMap,并且使用一颗树。
  • WeakHashMap
    继承AbstractMap类,使用弱密钥的哈希表。
  • LinkedHashMap
    继承于HashMap,使用元素的自然顺序对元素进行排序.
  • IdentityHashMap
    继承AbstractMap类,比较文档时使用引用相等。

java.util包中定义的类:

  • Vector
    该类和ArrayList非常相似,但是该类是同步的,可以用在多线程的情况,该类允许设置默认的增长长度,默认扩容方式为原来的2倍。
  • Stack
    栈是Vector的一个子类,它实现了一个标准的后进先出的栈。
  • Dictionary
    Dictionary 类是一个抽象类,用来存储键/值对,作用和Map类相似。
  • Hashtable
    Hashtable 是 Dictionary(字典) 类的子类,位于 java.util 包中。
  • Properties
    Properties 继承于 Hashtable,表示一个持久的属性集,属性列表中每个键及其对应值都是一个字符串。
  • BitSet
    一个Bitset类创建一种特殊类型的数组来保存位值。BitSet中数组大小会随需要增加。

如何使用迭代器

  • 通常情况下,你会希望遍历一个集合中的元素。例如,显示集合中的每个元素。
    一般遍历数组都是采用for循环或者增强for,这两个方法也可以用在集合框架,但是还有一种方法是采用迭代器遍历集合框架,它是一个对象,实现了Iterator 接口或ListIterator接口。
    迭代器,使你能够通过循环来得到或删除集合的元素。ListIterator 继承了Iterator,以允许双向遍历列表和修改元素。

集合的遍历

  • Iterator iterator()
    该方法可以获取一个用于遍历当前集合的迭代器实现类通过Iterator就可以遍历集合元素。
  • java.util.Iterator接口
    该接口规定了所有迭代器实现类遍历和的通用操作想法 而遍历集合遵循的步骤为:问,取,删,其中删除元素不是必要操作。 不同的集合是提供了一个Iterator的实现类,我们无需记住这些实现类的名字,以Iterator接口接收并调用方法遍历即可。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public static void main(String[] args) {
    Collection c = new ArrayList();
    c.add("one");
    c.add("#");
    c.add("two");
    c.add("#");
    c.add("three");
    c.add("#");
    c.add("four");
    c.add("#");
    c.add("five");
    System.out.println(c);

    Iterator it = c.iterator();
    /*
    * boolean hasNext()
    * 通过迭代器判断集合是否含有元素可以遍历
    */
    while(it.hasNext()) {
    /*
    * E next()
    * 获取集合下一个元素
    */
    String s = (String)it.next();
    System.out.println(s);
    if("#".equals(s)) {
    /*
    * 迭代器遍历集合有一个要求,遍历的过程中不能
    * 通过集合的方法增删元素,否则会抛出异常
    */
    //c.remove(s);
    /*
    * 迭代器的remove方法不需要传参,
    * 删除的就是本次遍历时next()得到的元素。
    */
    it.remove();
    }
    }
    System.out.println(c);
    }

增强for 循环

  • For-Each循环也叫增强型的for循环,或者叫foreach循环。
  • For-Each循环是JDK5.0的新特性(其他新特性比如泛型、自动装箱等)。
  • For-Each循环的加入简化了集合的遍历。

    1
    2
    3
    for(type element : array){
    System.out.println(element);
    }
  • For-Each循环的缺点:丢掉了索引信息。

    • 当遍历集合或数组时,如果需要访问集合或数组的下标,那么最好使用旧式的方式来实现循环或遍历,而不要使用增强的for循环,因为它丢失了下标信息。

Java 泛型

  • Java 泛型是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
  • Java泛型机制广泛的应用在集合框架中。所有的集合类型都带有泛型参数,这样在创建集合时可以指定放入集合中元素的类型。Java编译器可以根据此类型进行检查,这样可以减少代码在运行时出现错误的可能性。

泛型在集合中的应用

  • ArrayList类的定义中,< E >中的E为泛型参数,在创建对象时可以将类型作为参数传递,此时,类定义所有的E将替换成传入的参数。
    1
    2
    3
    4
    5
    6
    7
    8
    public class ArrayList<E>{
    ... ... ...
    public boolean add(E e){...};
    public E get(int index){...};
    }
    ArrayList<String> list = new ArrayList<String>();
    list.add("one");
    //list.add(100); Java编译器类型检查错误,此时add方法应传入的参数类型是String

Thread-2

发表于 2019-05-21

多线程 - 2

使用内部类创建线程

  • 通常我们可以通过匿名内部类的方式创建线程,使用该方式可以简化编写代码的复杂度,当一个线程仅需要一个实例时我们通常使用这种方式来创建。
    1
    2
    3
    4
    5
    6
    7
    创建方式:
    Thread t = new Thread(){
    public void run() {
    //线程体
    }
    };
    t.start();//启动线程

synchronized关键字

  • synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
  • 实现原理:
    JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。
    具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。

锁机制

  • Java提供了一种内置的锁机制来支持原子性:
  • 同步代码块(synchronized),同步代码快包含两部分:一个作为锁的对象的引用,一个作为由这个。
  • 若方法所有代码都需要同步也可以给方法直接加锁。
  • 每个Java对象都可以用做一个实现同步的锁,线程进入同步代码快之前会自动获得锁,并且在退出同步代码时自动释放锁,而且无论是通过正常途径还是通过抛异常退出都一样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
  • 选择合适锁对象:
    • 使用synchroinzed需要对一个对象上锁以保证线程同步。
    • 多个需要同步的线程在访问该同步块时,看到的应该是同一个所对象引用。否则达不到同步效果。
    • 通常我们会使用this来作为锁对象。
  • 选择合适锁范围:
    • 在使用同步块时,应当尽量在允许的情况下减少同步范围,以提高并发的执行效率。

静态方法锁

  • 当我们对一个静态方法加锁,那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式: 类名.class
  • 静态方法与非静态方法同时声明了synchronized,他们之间是非互斥关系的。原因在于,静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。

使用ExecutorService实现线程池

  • ExecutorService是java提供的用于管理线程池的类。
  • 线程池有两个主要作用:
    • 控制线程数量
    • 重用线程
  • 当一个程序中创建大量线程,并在任务结束后销毁,会给系统带来过度消耗资源,以及过度切换线程的危险,从而可能导致系统崩溃。
  • 概念:
    • 首先创建一些线程,他们的集合称为线程池,当服务器接收到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完成后不关闭该线程,而是将该线程还回到线程池中。
    • 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,他就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程。一个线程同时只能执行一个任务,但是可以同时向一个线程池提交多个任务。
  • 线程池的几种实现策略:
    • Executors.newCachedThreadPool():
      • 创建一个可以根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
    • Executors.newFixedThreadPoll(int nThreads):
      • 创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。
    • Executors.newScheduledThreadPool(int corePoolSize):
      • 创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行。
    • Executors.newSingleThreadExecutor():
      • 创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。

BlockingQueue

  • BlockingQueue从字面意思就知道它是阻塞队列,它在以下两种情况会造成阻塞:
  1. 当队列满了的时候进行入队操作。
  2. 当队列空了的时候进行出队操作。
    也就是说,当一个线程对已经满了的队列进行入队操作时,会被阻塞,除非另外一个线程进行了出队操作。或者当一个线程对一个空的队列进行出队操作的时候,会被阻塞,除非另外一个线程进行了入队的操作。
  • 阻塞队列是线程安全的,主要用于生产者/消费者的场景。比如一个线程在队尾进行put操作,另外一个线程在队头进行take操作。需要注意的是BlockingQueue不能插入Null值,否则会报NullPointerException异常。
方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用

Thread-1

发表于 2019-05-20

JAVA 多线程

简介

  • Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
  • 这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
  • 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

线程的几个主要概念

  1. 线程同步
  2. 线程间通信
  3. 线程死锁
  4. 线程控制:挂起、停止和恢复

进程与线程的区别

  • 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
  • 线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  • 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
  • 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
  • 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行
  • 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

多线程的使用

  • 有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!

并发原理

  • 多个线程“同时”运行只是我们感观上的一种表现。事实上线程是并发运行的,操作系统将时间划分为很多时间段,尽可能的均匀分配给每一个线程,获取到时间片的线程被CPU执行,其他则一直在等待。所以微观上是走走停停,宏观上都在运行。这种现象叫并发,但不是绝对意义上的同时发生。实则操作系统里面“同一时刻”只有一个线程在执行,但是处理速率快,效果上是并发运行。

线程的优先级

  • 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
  • Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
    默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
  • 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

创建线程的三种方式

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。
  1. 通过实现Runnable接口创建线程
    • 实现Runnable接口并重写run方法来定义线程体,然后在创建线程的时候将Runnable的实例传入并启动线程。这样做的好处在于将线程和线程执行的任务分离开解耦合。同时Java是单继承,实现接口可以更好的让该类去继承其他类。
  2. 通过继承Thread来创建线程
    • 创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
Thread类的一些重要方法:
  • 测试线程是否处于活动状态。 被Thread对象调用的。

    • public void start()
      • 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
    • public void run()
      • 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
    • public final void setName(String name)
      • 改变线程名称,使之与参数 name 相同。
    • public final void setPriority(int priority)
      • 更改线程的优先级。
    • public final void setDaemon(boolean on)
      • 将该线程标记为守护线程或用户线程。
    • public final void join(long millisec)
      • 等待该线程终止的时间最长为 millis 毫秒。
    • public void interrupt()
      • 中断线程。
    • public final boolean isAlive()
      • 测试线程是否处于活动状态。
  • Thread类的静态方法:

    • public static void yield()
      • 暂停当前正在执行的线程对象,并执行其他线程。
    • public static void sleep(long millisec)
      • 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
    • public static boolean holdsLock(Object x)
      • 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
    • public static Thread currentThread()
      • 返回对当前正在执行的线程对象的引用。
    • public static void dumpStack()
      • 将当前线程的堆栈跟踪打印至标准错误流。
  1. 通过 Callable 和 Future 创建线程
    • 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
    • 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
    • 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
    • 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

创建线程的三种方式的对比

  • 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  • 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
1…4567

Alone5

70 日志
1 标签
© 2020 Alone5
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4