JAVA常用基础

目录

对线面试官系列

使用

一、字符串类String

二、StringBuilder 和StringBuffer 类

三、日期和时间类(jdk8之前)

四、日期和时间(jdk8)

LocalDateTime 转 Date

Date 转 LocalDateTime 

五、比较器

 六、System 类

七、Math类

八、BigDecimal 和 BigInteger类

 九、Collections工具类

1. 排序操作(主要针对List接口相关)

2. 查找和替换(主要针对Collection接口相关)

3. 同步控制

4. 设置不可变集合

5. 其它

十、Java中Arrays类的常用方法

 数组转List

原理

HashMap线程不安全

01、多线程下扩容会死循环

02、多线程下 put 会导致元素丢失

03、put 和 get 并发时会导致 get 到 null

自己画的HashMap 原理

Timer

引用  Reference源码解析


对线面试官系列

 【对线面试官】今天来聊聊Java注解https://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247483821&idx=1&sn=e9003410a8d3c8a092de0c4d2002bedd&chksm=fdf0e9f2ca8760e455ddf557ebb0bde3c7c295ecfb47e6759490fed36ee1bdaa54f6a46ae602&scene=178&cur_album_id=1657204970858872832#rd

Java泛型类型擦除以及类型擦除带来的问题(类型擦除与多态的冲突和解决方法)

【对线面试官】 Java NIOhttps://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247483854&idx=1&sn=aa450a03ac0d6e8cf12cf13d4719ede3&chksm=fdf0e991ca87608769b9aca208b9c8646c13434b880ac74a9fe335a3f77b28377d1598635dd7&scene=178&cur_album_id=1657204970858872832#rd

 【对线面试官】Java反射 && 动态代理https://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247483893&idx=1&sn=af51e626f2c2baec8cae4f4a15425957&chksm=fdf0e9aaca8760bcd384a290608ecb220adfb7d4615d8418e16c07d3e371970598d3d2f54c4a&scene=178&cur_album_id=1657204970858872832#rd

 【对线面试官】多线程基础https://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247483918&idx=1&sn=ab8550bb284edcf7cf0c6d0b41e0c2f6&chksm=fdf0ea51ca8763471470e9957eecfb33390b4efbcfd182429538c5b8c267d6e7e91b20a5a749&scene=178&cur_album_id=1657204970858872832#rd

 【对线面试官】 CAShttps://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247483977&idx=1&sn=1a3aa3aec27073aa3b422bc41d7fbe2d&chksm=fdf0ea16ca8763005aff64834eeb7bef08bf4ee2d8febb7e8d4d8e5d1542336e13fac71e2881&scene=178&cur_album_id=1657204970858872832#rd

 【对线面试官】synchronizedhttps://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247483980&idx=1&sn=c9b620834adb889ad8ccedb6afdcaed1&chksm=fdf0ea13ca8763058e59bde10f752264a5de6fb9dd087ca2a4290f90da4b2c2e78407613c5cd&scene=178&cur_album_id=1657204970858872832#rd【对线面试官】AQS&&ReentrantLockhttps://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247484035&idx=1&sn=ccaec352e192f1fd40020d9a984e9461&chksm=fdf0eadcca8763ca5c44bd19118fd00e843c163deb40cda444b3fc08430c57760db15eca1ea6&scene=178&cur_album_id=1657204970858872832#rd

【对线面试官】线程池https://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247484036&idx=1&sn=75e9e93a82a811e9c71b8127cf7ac677&chksm=fdf0eadbca8763cd7ab74757f9472d061c0244d2373a1ea85b1cbc833941441fdb1e91ead5b4&scene=178&cur_album_id=1657204970858872832#rd


【对线面试官】ThreadLocalhttps://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247484118&idx=1&sn=9526a1dc0d42926dd9bcccfc55e6abc2&chksm=fdf0ea89ca87639f3ecadc523c4d80970e05d0731fcf65c80439a71d553b44cfb767ae683af3&scene=178&cur_album_id=1657204970858872832#rd
【对线面试官】Listhttps://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247484253&idx=1&sn=532db3941f47502582295cbb003f753d&chksm=fdf0eb02ca8762145c66b33bbb429399f1f0f27b31c22f7cf6c693c235e9a7cffdafb6ce2fdc&scene=178&cur_album_id=1657204970858872832#rd

 【对线面试官】CountDownLatch和CyclicBarrierhttps://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247484363&idx=1&sn=743dcdfb84f83cfc38882407f87c7c6d&chksm=fdf0eb94ca87628296d86d16769f25e10acd052bcd78f4a4608f4218e4948aff610b04a41f60&scene=178&cur_album_id=1657204970858872832#rd


【对象面试官】Maphttps://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247484280&idx=1&sn=87cfede653dabc26c909823a1dafd615&chksm=fdf0eb27ca876231095ff99f0b3e30acd7b2ee4cdc7ddb16da0bb6a3b02f531e27324059cf58&scene=178&cur_album_id=1657204970858872832#rd

使用

一、字符串类String

String:字符串,使用一对""引起来表示,字符串常量池在方法区中

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
//
}
  • String声明为final的,不可被继承,代表不可变的字符序列
  • String实现了Serializable接口:表示字符串是支持序列化的。
  • String内部定义了final char[] value,用于存储字符串数据
  • 不可变的体现:
    • 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
    • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值 
    • 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
    • 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中
    • 常量与常量的拼接结果在常量池。 且常量池中不会存在相同内容的常量。
    • 只要其中有一个是变量, 结果就在堆中
    • 如果拼接的结果调用intern()方法, 返回值就在常量池中    
    @Test
    public void test3(){
        String s1 = "javaEE";
        String s2 = "hadoop";

        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";
        String s5 = s1 + "hadoop";
        String s6 = "javaEE" + s2;
        String s7 = s1 + s2;

        System.out.println(s3 == s4);//true
        System.out.println(s3 == s5);//false
        System.out.println(s3 == s6);//false
        System.out.println(s3 == s7);//false
        System.out.println(s5 == s6);//false
        System.out.println(s5 == s7);//false
        System.out.println(s6 == s7);//false

        String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
        System.out.println(s3 == s8);//true


    }

  • 常用的方法
方法含义返回值
int length() 返回字符串的长度 value.lengthreturn value.length
char charAt(int index)返回某索引处的字符return value[index]
boolean isEmpty()判断是否是空字符串 return value.length == 0
String toLowerCase()使用默认语言环境, 将 String 中的所有字符转换为小写本身的字符串不改变
String toUpperCase()使用默认语言环境, 将 String 中的所有字符转换为大写本身的字符串不改变
String trim()返回字符串的副本, 忽略前导空白和尾部空白本身的字符串不改变
boolean contains(CharSequence s)当且仅当此字符串包含指定的 char 值序列时,返回 true,比较的字符串
boolean equals(Object obj)比较字符串的内容是否相同重写了equals方法
boolean equalsIgnoreCase(String anotherString)与equals方法类似, 忽略大小写本身的字符串不改变
String concat(String str)将指定字符串连接到此字符串的结尾。 等价于用“+”本身的字符串不改变
 int compareTo(String anotherString)比较两个字符串的大小
String substring(int beginIndex)返回一个新的字符串, 它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex)返回一个新字符串, 它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean endsWith(String suffix)测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix, int toffset)测试此字符串从指定索引开始的子字符串是否以指定前缀开始 
boolean startsWith(String prefix) 测试此字符串是否以指定的前缀开始
 int indexOf(String str)返回指定子字符串在此字符串中第一次出现处的索引
 int indexOf(String str, int fromIndex)返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str) 返回指定子字符串在此字符串中最右边出现处的索引
 int lastIndexOf(String str, int fromIndex)返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索indexOf和lastIndexOf方法如果未找到都是返回-1 
String replace(char oldChar, char newChar)返回一个新的字符串, 它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement)使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) 使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) 使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 
boolean matches(String regex)告知此字符串是否匹配给定的正则表达式。
String[] split(String regex)根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit)根据匹配给定的正则表达式来拆分此字符串, 最多不超过limit个, 如果超过了, 剩下的全部都放到最后一个元素中。
char[] toCharArray()将字符串中的全部字符存放在一个字符数组中的方法。
byte[] getBytes(String charsetName) 使用平台的默认字符集将此 String 编码为byte 序列,并将结果存储到一个新的 byte 数组中
String(byte[])通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String
String(byte[], int offset, int length) 用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象
byte[] getBytes(String charsetName) 使用指定的字符集将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组
String(char[]) 字符数组中的全部字符创建字符串对象。
String(char[], int offset, int length)字符数组中的部分字符创建字符串对象。
    @Test
    public void test3() throws UnsupportedEncodingException {
        String str1 = "abc123中国";
        byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码,一个中文字符对应3个字符
        System.out.println(Arrays.toString(bytes));

        byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。一个中文字符对应2个字符
        System.out.println(Arrays.toString(gbks));

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

        String str2 = new String(bytes);//使用默认的字符集,进行解码。
        System.out.println(str2);

        String str3 = new String(gbks);
        System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!


        String str4 = new String(gbks, "gbk");
        System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!

    }

二、StringBuilder 和StringBuffer 类

  • String:不可变的字符序列;底层使用final char[]存储
  • StringBuffer:可变的字符序列;线程安全的 sychronized,效率低;底层使用char[]存储
  • StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
  • 常用方法:
    • StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
    • StringBuffer delete(int start,int end):删除指定位置的内容
    • StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
    • StringBuffer insert(int offset, xxx):在指定位置插入xxx
    • StringBuffer reverse() :把当前字符序列逆转
    • public int indexOf(String str)
    • public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
    • public int length()
    • public char charAt(int n )
    • public void setCharAt(int n ,char ch)
  • 总结:
    • 增:append(xxx)
    • 删:delete(int start,int end)
    • 改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
    • 查:charAt(int n )
    • 插:insert(int offset, xxx)
    • 长度:length();
    • 遍历:for() + charAt() / toString()
  • 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中

@HotSpotIntrinsicCandidate
    public StringBuffer() {
        super(16);  //默认初始容量为16
}

@HotSpotIntrinsicCandidate
    public StringBuilder() {
        super(16);
}
  •  面试题
String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb.length());//4
System.out.println(sb);//”null”
StringBuffer sb1 = new StringBuffer(str); //空指针异常
System.out.println(sb1);// 

private AbstractStringBuilder appendNull() {
    ensureCapacityInternal(count + 4);
    int count = this.count;
    byte[] val = this.value;
    if (isLatin1()) {
        val[count++] = 'n';
        val[count++] = 'u';
        val[count++] = 'l';
        val[count++] = 'l';
    } else {
        count = StringUTF16.putCharsAt(val, count, 'n', 'u', 'l', 'l');
    }
    this.count = count;
    return this;
}

三、日期和时间类(jdk8之前)

  • java.lang.System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
  • java.util.Date类表示特定的瞬间,精确到毫秒
    • 构造器:
      • Date(): 使用无参构造器创建的对象可以获取本地当前时间
      • Date(long date)
    • 常用方法
      • getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
      • toString():把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue,Wed, Thu, Fri, Sat), zzz是时间标准。
      • 其它很多方法都过时了
  • java.sql.Date对应着数据库中的日期类型的变量

    • 构造器:

      • Date(): 使用无参构造器创建的对象可以获取本地当前时间
      • Date(long date)
    • 如何将java.util.Date对象转换为java.sql.Date对象: 父类强转子类有风险
      // 方式一:
      Date date4 = new java.sql.Date(2343243242323L);
      java.sql.Date date5 = (java.sql.Date) date4;
      //方式二:
      Date date6 = new Date();
      java.sql.Date date7 = new java.sql.Date(date6.getTime());
      //此种方式编译通过,运行发生异常
      Date date1 = newDate(2343243242323L);
      java.sql.Date date2 = (java.sql.Date) date1;
  • .java.text.SimpleDateFormat类 :一个不与语言环境有关的方式来格式化和解析日期的具体类。
    • 格式化:
      • SimpleDateFormat() :默认的模式和语言环境创建对象 "19-12-18 上午11:43"
      • public SimpleDateFormat(String pattern): 该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:
      • public String format(Date date): 方法格式化时间对象date
    • 解析:
      • 解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现)

      • public Date parse(String source): 从给定字符串的开始解析文本,以生成一个日期    

    @Test
    public void testSimpleDateFormat() throws ParseException {
        //实例化SimpleDateFormat:使用默认的构造器
        SimpleDateFormat sdf = new SimpleDateFormat();

        //格式化:日期 --->字符串
        Date date = new Date();
        System.out.println(date);

        String format = sdf.format(date);
        System.out.println(format);

        //解析:格式化的逆过程,字符串 ---> 日期
        String str = "19-12-18 上午11:43";
        Date date1 = sdf.parse(str);
        System.out.println(date1);

        //*************按照指定的方式格式化和解析:调用带参的构造器*****************
        //        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        //格式化
        String format1 = sdf1.format(date);
        System.out.println(format1);//2019-02-18 11:48:27
        //解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
        //否则,抛异常
        Date date2 = sdf1.parse("2020-02-18 11:48:27");
        System.out.println(date2);
    }
  • java.util.Calendar(日历)类:Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。
  • 获取Calendar实例的方法
    • 使用Calendar.getInstance()方法
    • 调用它的子类GregorianCalendar的构造器。
  • 一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、 MONTH、 DAY_OF_WEEK、 HOUR_OF_DAY 、
    MINUTE、 SECOND
    • public void set(int field,int value)  设置
    • public void add(int field,int amount)  加
    • public final Date getTime() 获取对应Date的毫秒数
    • public final void setTime(Date date) 将date转成calender
  • 获取月份时: 一月是0,二月是1,以此类推, 12月是11
  • 获取星期时: 周日是1,周二是2 , 。 。。。周六是7

  • 缺点 

    • 可变性:像日期和时间这样的类应该是不可变的。
    • 偏移性: Date中的年份是从1900开始的,而月份都从0开始。
    • 格式化:格式化只对Date有用, Calendar则不行。
    • 它们也不是线程安全的;不能处理闰秒等

四、日期和时间(jdk8)

JDK8的LocalDateTime用法

1.LocalDate、LocalTime、LocalDateTime 

  • now():获取当前的日期、时间、日期+时间
  • of():设置指定的年、月、日、时、分、秒。没有偏移量
  • getXxx():获取相关的属性
  • withXxx():设置相关的属性,会返回一个新的日期对象,原来的日期不会改变

2. Instant

  • Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。 在UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位。
  •  java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。 Instant表示时间线上的一点,表示自1970年1月1日0时0分0秒(UTC)开始的毫秒数。 
    @Test
    public void test2(){
        //now():获取本初子午线对应的标准时间
        Instant instant = Instant.now();
        System.out.println(instant);//2019-02-18T07:29:41.719Z

        //添加时间的偏移量
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8)); //添加时区之间的时间差
        System.out.println(offsetDateTime);//2019-02-18T15:32:50.611+08:00

        //toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数  ---> Date类的getTime()
        long milli = instant.toEpochMilli();
        System.out.println(milli);

        //ofEpochMilli():通过给定的毫秒数,获取Instant实例  -->Date(long millis)
        Instant instant1 = Instant.ofEpochMilli(1550475314878L);
        System.out.println(instant1);
    }
 

3. DateTimeFormatter

  • 用于格式化或解析日期、时间
    • 预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
    • 本地化相关的格式。如: ofLocalizedDateTime(FormatStyle.LONG)
    • 自定义的格式。如: ofPattern(“yyyy-MM-dd hh:mm:ss”)
方法含义
ofPattern(String pattern)静态方法,返回一个指定字符串格式的DateTimeFormatter
format(TemporalAccessor t)格式化一个日期、 时间, 返回字符串
parse(CharSequence text)将指定格式的字符序列解析为一个日期、 时间

public void test3(){
//        方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
        DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        //格式化:日期-->字符串
        LocalDateTime localDateTime = LocalDateTime.now();
        String str1 = formatter.format(localDateTime);
        System.out.println(localDateTime);
        System.out.println(str1);//2019-02-18T15:42:18.797

        //解析:字符串 -->日期
        TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
        System.out.println(parse);

//        方式二:
//        本地化相关的格式。如:ofLocalizedDateTime()
//        FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
        DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
        //格式化
        String str2 = formatter1.format(localDateTime);
        System.out.println(str2);//2019年2月18日 下午03时47分16秒


//      本地化相关的格式。如:ofLocalizedDate()
//      FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
        DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
        //格式化
        String str3 = formatter2.format(LocalDate.now());
        System.out.println(str3);//2019-2-18


//       重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
        DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
        //格式化
        String str4 = formatter3.format(LocalDateTime.now());
        System.out.println(str4);//2019-02-18 03:52:09

        //解析
        TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
        System.out.println(accessor);

    }

LocalDateTime 转 Date

LocalDateTime localDateTime=LocalDateTime.now()
Date date = Date.from(localDateTime.atZone( ZoneId.systemDefault()).toInstant());

Date 转 LocalDateTime 

Date startDate=new Date();
LocalDateTime localDateTime = startDate.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime()

五、比较器

Java实现对象排序的方式有两种:自然排序: java.lang.Comparable 和 定制排序: java.util.Comparator 

  • java.lang.Comparable
    • Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。默认从小到大
    • 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。
    • 如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj, 则返回负整数,如果当前对象this等于形参对象obj, 则返回零。
    • 实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
    • 对于类 C 的每一个 e1 和 e2 来说,当且仅当 e1.compareTo(e2) == 0 与1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals一致。 建议(虽然不是必需的) 最好使自然排序与 equals 一致。
  • Comparable 的典型实现: (默认都是从小到大排列的)
    • String:按照字符串中字符的Unicode值进行比较
    • Character:按照字符的Unicode值来进行比较
    • 数值类型对应的包装类以及BigInteger、 BigDecimal:按照它们对应的数值大小进行比较
    • Boolean: true 对应的包装类实例大于 false 对应的包装类实例
    • Date、 Time等:后面的日期时间比前面的日期时间大
  • java.util.Comparator
    • 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序, 强行对多个对象进行整体排序的比较。
    • 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
      • 如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
      • 可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。
      • 还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
  • Comparable接口与Comparator的使用的对比:
    • Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小
    • Comparator接口属于临时性的比较。

 六、System 类

系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包

  • 构造器是私有的,无法实例化该类。其内部的成员变量和成员方法都是static的
  • 成员变量
    • System类内部包含in、 out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
  • 成员方法
    • native long currentTimeMillis():该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数
    • void exit(int status):该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。 使用该方法可以在图形界面编程中实现程序的退出功能等
    • void gc():该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
    • String getProperty(String key):该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:


       

七、Math类

  • java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型
  • 常用函数
    函数 含义
    abs 绝对值
    acos,asin,atan,cos,sin,tan三角函数
    sqrt 平方根
    pow(double a,doble b) a的b次幂
    log 自然对数
    exp e为底指数
    max(double a,double b)最大值
    min(double a,double b)最小值
    random() 返回0.0到1.0的随机数
    long round(double a) double型数据a转换为long型(四舍五入)
    toDegrees(double angrad) 弧度—>角度
    toRadians(double angdeg) 角度—>弧度

八、BigDecimal 和 BigInteger类

  • java.math包的BigInteger可以表示不可变的任意精度的整数。 BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。
  • 商业计算中,要求数字精度比较高,用java.math.BigDecimal类, 它支持不可变的、任意精度的有符号十进制定点数。  
  • BigInteger 还提供以下运算:模算术、 GCD 计算、质数测试、素数生成、位操作以及一些其他操作。
  • 构造器
    • public BigInteger(String val)  --- 根据字符串构建BigInteger对象
    • public BigDecimal(double val)
    • public BigDecimal(String val)  
  • 常用方法
方法含义
BigInteger
public BigInteger abs()返回此 BigInteger 的绝对值的 BigInteger。
BigInteger add(BigInteger val) 返回其值为 (this + val) 的 BigInteger
BigInteger subtract(BigInteger val)返回其值为 (this - val) 的 BigInteger
BigInteger multiply(BigInteger val) 返回其值为 (this * val) 的 BigInteger
BigInteger divide(BigInteger val)返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。
BigInteger remainder(BigInteger val) 返回其值为 (this % val) 的 BigInteger。
BigInteger[] divideAndRemainder(BigInteger val)返回包含 (this / val) 后跟(this % val) 的两个 BigInteger 的数组。
BigInteger pow(int exponent) 返回其值为 (thisexponent) 的 BigInteger。
BigDecimal
public BigDecimal add(BigDecimal augend)+
public BigDecimal subtract(BigDecimal subtrahend)-
 public BigDecimal multiply(BigDecimal multiplicand)*
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) /

 九、Collections工具类

1、sort(Collection)方法的使用(含义:对集合进行排序)。

例:对已知集合c进行排序?
public class Practice {
  public static void main(String[] args){
    List c = new ArrayList();
    c.add("l");
    c.add("o");
    c.add("v");
    c.add("e");
    System.out.println(c);
    Collections.sort(c);
    System.out.println(c);
  }
}
运行结果为:[l, o, v, e]
           [e, l, o, v]   

2.reverse()方法的使用(含义:反转集合中元素的顺序)。例:

public class Practice {
  public static void main(String[] args){
    List list = Arrays.asList("one two three four five six siven".split(" "));
    System.out.println(list);
    Collections.reverse(list);
    System.out.println(list);
  }
}
   运行结果为:
      [one, two, three, four, five, six, siven]
      [siven, six, five, four, three, two, one]

3.shuffle(Collection)方法的使用(含义:对集合进行随机排序)。

例:shuffle(Collection)的简单示例?
public class Practice {
  public static void main(String[] args){
    List c = new ArrayList();
    c.add("l");
    c.add("o");
    c.add("v");
    c.add("e");
    System.out.println(c);
    Collections.shuffle(c);
    System.out.println(c);
    Collections.shuffle(c);
    System.out.println(c);
  }
}
运行结果为:[l, o, v, e]
           [l, v, e, o]
           [o, v, e, l]

4.fill(List list,Object o)方法的使用(含义:用对象o替换集合list中的所有元素) 。例:

public class Practice {
    public static void main(String[] args){
       List m = Arrays.asList("one two three four five six siven".split(" "));
       System.out.println(m);
       Collections.fill(m, "青鸟52T25小龙");
       System.out.println(m);
    }
}
运行结果为:
    [one, two, three, four, five, six, siven]
    [青鸟52T25小龙, 青鸟52T25小龙, 青鸟52T25小龙, 青鸟52T25小龙, 青鸟52T25小龙, 青鸟52T25小龙, 青鸟52T25小龙]

5.copy(List m,List n)方法的使用(含义:将集合n中的元素全部复制到m中,并且覆盖相应索引的元素)。 例:

public class Practice {
     public static void main(String[] args){
           List m = Arrays.asList("one two three four five six siven".split(" "));
           System.out.println(m);
           List n = Arrays.asList("我 是 复制过来的哈".split(" "));
           System.out.println(n);
           Collections.copy(m,n);
           System.out.println(m);
       }
}
运行结果为:[one, two, three, four, five, six, siven]
           [我, 是, 复制过来的哈]
           [我, 是, 复制过来的哈, four, five, six, siven]      

6.min(Collection),min(Collection,Comparator)方法的使用(前者采用Collection内含自然比较法,后者采用Comparator进行比较)。

public static void main(String[] args){
    List c = new ArrayList();
    c.add("l");
    c.add("o");
    c.add("v");
    c.add("e");
    System.out.println(c);
    System.out.println(Collections.min(c));
}
运行结果:[l, o, v, e]
          e

7.max(Collection),max(Collection,Comparator)方法的使用(前者采用Collection内含自然比较法,后者采用Comparator进行比较)。

public static void main(String[] args){
     List c = new ArrayList();
     c.add("l");
     c.add("o");
     c.add("v");
     c.add("e");
     System.out.println(c);
     System.out.println(Collections.max(c));
}
运行结果:[l, o, v, e]
         v

8.indexOfSubList(List list,List subList)方法的使用(含义:查找subList在list中首次出现位置的索引)。

public static void main(String[] args){
        ArrayList<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 6, 6, 7, 3));
        ArrayList<Integer> targetList = new ArrayList<>(Arrays.asList(6));
        System.out.println(Collections.indexOfSubList(intList, targetList));
}
运行结果:5

9.lastIndexOfSubList(List source,List target)方法的使用与上例方法的使用相同

public static void main(String[] args){
    ArrayList<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 6, 6, 7, 3));
    ArrayList<Integer> targetList = new ArrayList<>(Arrays.asList(6));
    System.out.println(Collections.lastIndexOfSubList(intList, targetList));
}
运行结果:7

10.rotate(List list,int m)方法的使用(含义:集合中的元素向后移m个位置,在后面被遮盖的元素循环到前面来)。移动列表中的元素,负数向左移动,正数向右移动

public static void main(String[] args){
        ArrayList<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        System.out.println(intList);
        Collections.rotate(intList, 1);
        System.out.println(intList);
}
运行结果:[1, 2, 3, 4, 5]
             [5, 1, 2, 3, 4]
public static void main(String[] args){
        ArrayList<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        System.out.println(intList);
        Collections.rotate(intList, -1);
        System.out.println(intList);
}
运行结果:[1, 2, 3, 4, 5]
               [2, 3, 4, 5, 1]

11.swap(List list,int i,int j)方法的使用(含义:交换集合中指定元素索引的位置)。例:

public class Practice {
      public static void main(String[] args){
      List m = Arrays.asList("one two three four five six siven".split(" "));
      System.out.println(m);
      Collections.swap(m, 2, 3);
      System.out.println(m);
   }
}
运行结果为:
           [one, two, three, four, five, six, siven]
           [one, two, four, three, five, six, siven]

12.binarySearch(Collection,Object)方法的使用(含义:查找指定集合中的元素,返回所查找元素的索引)。例:

binarySearch(Collection,Object)的简单示例?
public class Practice {
      public static void main(String[] args){
            List c = new ArrayList();
            c.add("l");
            c.add("o");
            c.add("v");
            c.add("e");
            System.out.println(c);
            int m = Collections.binarySearch(c, "o");
            System.out.println(m);
       }
 }

运行结果为:

[l, o, v, e]

1

13.replaceAll(List list,Object old,Object new)方法的使用(含义:替换批定元素为某元素,若要替换的值存在刚返回true,反之返回false)。 例:

public class Practice {
      public static void main(String[] args){
         List list = Arrays.asList("one two three four five six siven".split(" "));
         System.out.println(list);
         List subList = Arrays.asList("three four five six".split(" "));
         System.out.println(Collections.replaceAll(list, "siven", "siven eight"));
         System.out.println(list);
      }
 }
   运行结果为:
            [one, two, three, four, five, six, siven]
            true
            [one, two, three, four, five, six, siven eight]

 总结:!!!!!

1. 排序操作(主要针对List接口相关)

  • reverse(List list):反转指定List集合中元素的顺序
  • shuffle(List list):对List中的元素进行随机排序(洗牌)
  • sort(List list):对List里的元素根据自然升序排序
  • sort(List list, Comparator c):自定义比较器进行排序
  • swap(List list, int i, int j):将指定List集合中i处元素和j出元素进行交换
  • rotate(List list, int distance):将所有元素向右移位指定长度,如果distance等于size那么结果不变
  •  public void testSort() {
            System.out.println("原始顺序:" + list);
            
            Collections.reverse(list);
            System.out.println("reverse后顺序:" + list);
    
            Collections.shuffle(list);
            System.out.println("shuffle后顺序:" + list);
            
            Collections.swap(list, 1, 3);
            System.out.println("swap后顺序:" + list);
    
            Collections.sort(list);
            System.out.println("sort后顺序:" + list);
    
            Collections.rotate(list, 1);
            System.out.println("rotate后顺序:" + list);
        }

    输出

    原始顺序:[b张三, d孙六, a李四, e钱七, c赵五]
    reverse后顺序:[c赵五, e钱七, a李四, d孙六, b张三]
    shuffle后顺序:[b张三, c赵五, d孙六, e钱七, a李四]
    swap后顺序:[b张三, e钱七, d孙六, c赵五, a李四]
    sort后顺序:[a李四, b张三, c赵五, d孙六, e钱七]
    rotate后顺序:[e钱七, a李四, b张三, c赵五, d孙六]

    2. 查找和替换(主要针对Collection接口相关)

    • binarySearch(List list, Object key):使用二分搜索法,以获得指定对象在List中的索引,前提是集合已经排序
    • max(Collection coll):返回最大元素
    • max(Collection coll, Comparator comp):根据自定义比较器,返回最大元素
    • min(Collection coll):返回最小元素
    • min(Collection coll, Comparator comp):根据自定义比较器,返回最小元素
    • fill(List list, Object obj):使用指定对象填充
    • frequency(Collection Object o):返回指定集合中指定对象出现的次数
    • replaceAll(List list, Object old, Object new):替换
      public void testSearch() {
              System.out.println("给定的list:" + list);
              System.out.println("max:" + Collections.max(list));
              System.out.println("min:" + Collections.min(list));
              System.out.println("frequency:" + Collections.frequency(list, "a李四"));
              Collections.replaceAll(list, "a李四", "aa李四");
              System.out.println("replaceAll之后:" + list);
              
              // 如果binarySearch的对象没有排序的话,搜索结果是不确定的
              System.out.println("binarySearch在sort之前:" + Collections.binarySearch(list, "c赵五"));
              Collections.sort(list);
              // sort之后,结果出来了
              System.out.println("binarySearch在sort之后:" + Collections.binarySearch(list, "c赵五"));
      
              Collections.fill(list, "A");
              System.out.println("fill:" + list);
      }

      输出

      给定的list:[b张三, d孙六, a李四, e钱七, c赵五]
      max:e钱七
      min:a李四
      frequency:1
      replaceAll之后:[b张三, d孙六, aa李四, e钱七, c赵五]
      binarySearch在sort之前:-4
      binarySearch在sort之后:2
      fill:[A, A, A, A, A]

    • 3. 同步控制

      Collections工具类中提供了多个synchronizedXxx方法,该方法返回指定集合对象对应的同步对象,从而解决多线程并发访问集合时线程的安全问题。HashSet、ArrayList、HashMap都是线程不安全的,如果需要考虑同步,则使用这些方法。这些方法主要有:synchronizedSet、synchronizedSortedSet、synchronizedList、synchronizedMap、synchronizedSortedMap。

      特别需要指出的是,在使用迭代方法遍历集合时需要手工同步返回的集合。

        Map m = Collections.synchronizedMap(new HashMap());
            ...
        Set s = m.keySet();  // Needn't be in synchronized block
            ...
        synchronized (m) {  // Synchronizing on m, not s!
            Iterator i = s.iterator(); // Must be in synchronized block
            while (i.hasNext())
                foo(i.next());
        }

      4. 设置不可变集合

      Collections有三类方法可返回一个不可变集合:

    • emptyXxx():返回一个空的不可变的集合对象
    • singletonXxx():返回一个只包含指定对象的,不可变的集合对象。
    • unmodifiableXxx():返回指定集合对象的不可变视图
          public void testUnmodifiable() {
              System.out.println("给定的list:" + list);
              List<String> unmodList = Collections.unmodifiableList(list);
              
              unmodList.add("再加个试试!"); // 抛出:java.lang.UnsupportedOperationException
              
              // 这一行不会执行了
              System.out.println("新的unmodList:" + unmodList);
          }
    • 5. 其它

    • disjoint(Collection<?> c1, Collection<?> c2) - 如果两个指定 collection 中没有相同的元素,则返回 true。
    • addAll(Collection<? super T> c, T... a) - 一种方便的方式,将所有指定元素添加到指定 collection 中。示范: 
      Collections.addAll(flavors, "Peaches 'n Plutonium", "Rocky Racoon");
    • Comparator<T> reverseOrder(Comparator<T> cmp) - 返回一个比较器,它强行反转指定比较器的顺序。如果指定比较器为 null,则此方法等同于 reverseOrder()(换句话说,它返回一个比较器,该比较器将强行反转实现 Comparable 接口那些对象 collection 上的自然顺序)。
          public void testOther() {
              List<String> list1 = new ArrayList<String>();
              List<String> list2 = new ArrayList<String>();
              
              // addAll增加变长参数
              Collections.addAll(list1, "大家好", "你好","我也好");
              Collections.addAll(list2, "大家好", "a李四","我也好");
              
              // disjoint检查两个Collection是否的交集
              boolean b1 = Collections.disjoint(list, list1);
              boolean b2 = Collections.disjoint(list, list2);
              System.out.println(b1 + "\t" + b2);
              
              // 利用reverseOrder倒序
              Collections.sort(list1, Collections.reverseOrder());
              System.out.println(list1);
          }

      输出

      true false
      [我也好, 大家好, 你好]

十、Java中Arrays类的常用方法

Arrays类位于 java.util 包中,主要包含了操作数组的各种方法。

import java.util.Arrays;

Arrays.fill(); //填充数组

    int[] arr = new int[5];//新建一个大小为5的数组
    Arrays.fill(arr,4);//给所有值赋值4
    String str = Arrays.toString(arr); // Arrays类的toString()方法能将数组中的内容全部打印出来
    System.out.print(str);
    //输出:[4, 4, 4, 4, 4]

    int[] arr = new int[5];//新建一个大小为5的数组
    Arrays.fill(arr, 2,4,6);//给第2位(0开始)到第4位(不包括)赋值6
    String str = Arrays.toString(arr); // Arrays类的toString()方法能将数组中的内容全部打印出来
    System.out.print(str);
    //输出:[0, 0, 6, 6, 0]

Arrays.sort(); //数组排序

Arrays类有一个静态方法sort,利用这个方法可传入要排序的数组进去排序,因为传入的是一个数组的引用,所以排序完成的结果也通过这个引用来更改数组。

1.数字排序
    int[] intArray = new int[] { 4, 1, 3, -23 };
    Arrays.sort(intArray);
    //输出: [-23, 1, 3, 4]
2.字符串排序,先大写后小写
    String[] strArray = new String[] { “z”, “a”, “C” };
    Arrays.sort(strArray);
    //输出: [C, a, z]
3.严格按字母表顺序排序,也就是忽略大小写排序 Case-insensitive sort
    Arrays.sort(strArray, String.CASE_INSENSITIVE_ORDER);
    //输出: [a, C, z]
4.反向排序, Reverse-order sort
    Arrays.sort(strArray, Collections.reverseOrder());
    //输出:[z, a, C]
5.忽略大小写反向排序 Case-insensitive reverse-order sort
    Arrays.sort(strArray, String.CASE_INSENSITIVE_ORDER);
    Collections.reverse(Arrays.asList(strArray));
    //输出: [z, C, a]
6.选择数组指定位置进行排序
    int[] arr = {3,2,1,5,4};
    Arrays.sort(arr,0,3);//给第0位(0开始)到第3位(不包括)排序
    String str = Arrays.toString(arr); // Arrays类的toString()方法能将数组中的内容全部打印出来
    System.out.print(str);
    //输出:[1, 2, 3, 5, 4]

 Arrays.toString(); //将数组中的内容全部打印出来

 int[] arr = {3,2,1,5,4};
 System.out.print(arr);//直接将数组打印输出
 //输出:[I@7852e922 (数组的地址)

 String str = Arrays.toString(arr); // Arrays类的toString()方法能将数组中的内容全部打印出来
 //System.out.print(str);
 //输出:[3, 2, 1, 5, 4]

Arrays.equals(); //比较数组元素是否相等

int[] arr1 = {1,2,3};
int[] arr2 = {1,2,3};
System.out.println(Arrays.equals(arr1,arr2));
//输出:true
//如果是arr1.equals(arr2),则返回false,因为equals比较的是两个对象的地址,不是里面的数,而Arrays.equals重写了equals,所以,这里能比较元素是否相等。

Arrays.binarySearch(); //二分查找法找指定元素的索引值(下标)

数组一定是排好序的,否则会出错。找到元素,只会返回最后一个位置  

    int[] arr = {10,20,30,40,50};
    System.out.println(Arrays.binarySearch(arr, 30));
    //输出:2 (下标索引值从0开始)


    int[] arr = {10,20,30,40,50};
    System.out.println(Arrays.binarySearch(arr, 36));
    //输出:-4 (找不到元素,返回-x,从-1开始数,如题,返回-4)

    int []arr = {10,20,30,40,50};
    System.out.println(Arrays.binarySearch(arr, 0,3,30));
    //输出:2 (从0到3位(不包括)找30,找到了,在第2位,返回2)


    int []arr = {10,20,30,40,50};
    System.out.println(Arrays.binarySearch(arr, 0,3,40));
    //输出:-4 (从0到3位(不包括)找40,找不到,从-1开始数,返回-4)

Arrays.copeOf() 和Arrays.copeOfRange(); //截取数组

    

    int[] arr = {10,20,30,40,50};
    int[] arr1 = Arrays.copyOf(arr, 3);
    String str = Arrays.toString(arr1); // Arrays类的toString()方法能将数组中的内容全部打印出来
    System.out.print(str);
    //输出:[10, 20, 30] (截取arr数组的3个元素赋值给新数组arr1)


    int []arr = {10,20,30,40,50};
    int []arr1 = Arrays.copyOfRange(arr,1,3);
    String str = Arrays.toString(arr1); // Arrays类的toString()方法能将数组中的内容全部打印出来
    System.out.print(str);
    //输出:[20, 30] (从第1位(0开始)截取到第3位(不包括)

 数组转List

Java数组转List的三种方式及对比_五道口-CSDN博客_数组转list本文介绍Java中数组转为List三种情况的优劣对比,以及应用场景的对比,以及程序员常犯的类型转换错误原因解析。https://blog.csdn.net/x541211190/article/details/79597236

十一、类加载

JVM层面

背了100次,也忘了100次。醉了。。。

【Java基础】类加载过程 - 简书要点:1、类加载机制的原理2、程序初始化的顺序3、类加载的代理模式(双亲委托机制) 一、类加载机制 JVM把class文件加载到内存,并对数据进行校验、准备、解析、初始化,最...https://www.jianshu.com/p/dd39654231e0

JAVA程序运行层面 - JVM的启动过程

1、JVM的装入环境和配置

        在学习这个之前,我们需要了解一件事情,就是JDK和JRE的区别。

        JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境,JDK中包含了JRE。

        JRE是Java的运行环境,是面向所有Java程序的使用者,包括开发者。

        JRE = 运行环境 = JVM。

        如果安装了JDK,会发现电脑中有两套JRE,一套位于/Java/jre.../下,一套位于/Java/jdk.../jre下。那么问题来了,一台机器上有两套以上JRE,谁来决定运行那一套呢?这个任务就落到java.exe身上,java.exe的任务就是找到合适的JRE来运行java程序。

        java.exe按照以下的顺序来选择JRE:

                1、自己目录下有没有JRE

                2、父目录下有没有JRE

                3、查询注册表: HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\"当前JRE版本号"\JavaHome

这几步的主要核心是为了找到JVM的绝对路径。

jvm.cfg的路径为:JRE路径\lib\"CPU架构"\jvm.fig

jvm.cfg的内容大致如下:

        -client KNOWN 
        -server KNOWN 
        -hotspot ALIASED_TO -client 
        -classic WARN 
        -native ERROR 
        -green ERROR 

        KNOWN 表示存在 、IGNORE 表示不存在 、ALIASED_TO 表示给别的JVM去一个别名
WARN 表示不存在时找一个替代 、ERROR 表示不存在抛出异常

2、装载JVM

        通过第一步找到JVM的路径后,Java.exe通过LoadJavaVM来装入JVM文件。
LoadLibrary装载JVM动态连接库,然后把JVM中的到处函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMIntArgs 挂接到InvocationFunction 变量的CreateJavaVM和GetDafaultJavaVMInitArgs 函数指针变量上。JVM的装载工作完成。

3、初始化JVM,获得本地调用接口

        调用InvocationFunction -> CreateJavaVM也就是JVM中JNI_CreateJavaVM方法获得JNIEnv结构的实例。

4、运行Java程序

        JVM运行Java程序的方式有两种:jar包 与 Class运行jar 的时候,Java.exe调用GetMainClassName函数,该函数先获得JNIEnv实例然后调用JarFileJNIEnv类中getManifest(),从其返回的Manifest对象中取getAttrebutes("Main-Class")的值,即jar 包中文件:META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。之后main函数会调用Java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。
        运行Class的时候,main函数直接调用Java.c中的LoadClass方法装载该类。

以上引自:Java JVM 运行机制及基本原理_AlbenXie的博客-CSDN博客_java jvm 运行机制

十二、反射

Reflection类

 Reflection的getCallerClass的使用:可以得到调用者的类。这个方法是很好用。

 不推荐使用:http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html

       /**
         0 和小于0  -   返回 Reflection类
         1  -   返回自己的类
         2  -    返回调用者的类
         3. 4. ....层层上传。
         result:
         class sun.reflect.Reflection
         class sun.reflect.Reflection
         class fileTest
         class com.intellij.rt.execution.application.AppMain
         null
         null
         */
        System.out.println(Reflection.getCallerClass(-1));
        System.out.println(Reflection.getCallerClass(0));
        System.out.println(Reflection.getCallerClass(1));
        System.out.println(Reflection.getCallerClass(2));
        System.out.println(Reflection.getCallerClass(3));
        System.out.println(Reflection.getCallerClass(4));

原理

HashMap线程不安全

三方面原因:多线程下扩容会死循环、多线程下 put 会导致元素丢失、put 和 get 并发时会导致 get 到 null,我们来一一分析。

01、多线程下扩容会死循环

众所周知,HashMap 是通过拉链法来解决哈希冲突的,也就是当哈希冲突时,会将相同哈希值的键值对通过链表的形式存放起来。

JDK 7 时,采用的是头部插入的方式来存放链表的,也就是下一个冲突的键值对会放在上一个键值对的前面(同一位置上的新元素被放在链表的头部)。扩容的时候就有可能导致出现环形链表,造成死循环。

resize 方法的源码:

// newCapacity为新的容量
void resize(int newCapacity) {
    // 小数组,临时过度下
    Entry[] oldTable = table;
    // 扩容前的容量
    int oldCapacity = oldTable.length;
    // MAXIMUM_CAPACITY 为最大容量,2 的 30 次方 = 1<<30
    if (oldCapacity == MAXIMUM_CAPACITY) {
        // 容量调整为 Integer 的最大值 0x7fffffff(十六进制)=2 的 31 次方-1
        threshold = Integer.MAX_VALUE;
        return;
    }

    // 初始化一个新的数组(大容量)
    Entry[] newTable = new Entry[newCapacity];
    // 把小数组的元素转移到大数组中
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    // 引用新的大数组
    table = newTable;
    // 重新计算阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

transfer 方法用来转移,将小数组的元素拷贝到新的数组中。

void transfer(Entry[] newTable, boolean rehash) {
    // 新的容量
    int newCapacity = newTable.length;
    // 遍历小数组
    for (Entry<K,V> e : table) {
        while(null != e) {
            // 拉链法,相同 key 上的不同值
            Entry<K,V> next = e.next;
            // 是否需要重新计算 hash
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            // 根据大数组的容量,和键的 hash 计算元素在数组中的下标
            int i = indexFor(e.hash, newCapacity);

            // 同一位置上的新元素被放在链表的头部
            e.next = newTable[i];

            // 放在新的数组上
            newTable[i] = e;

            // 链表上的下一个元素
            e = next;
        }
    }
}

注意 e.next = newTable[i] 和 newTable[i] = e 这两行代码,就会将同一位置上的新元素被放在链表的头部。

扩容前的样子假如是下面这样子。

那么正常扩容后就是下面这样子。

假设现在有两个线程同时进行扩容,线程 A 在执行到 newTable[i] = e; 被挂起,此时线程 A 中:e=3、next=7、e.next=null

线程 B 开始执行,并且完成了数据转移。

此时,7 的 next 为 3,3 的 next 为 null。

随后线程A获得CPU时间片继续执行 newTable[i] = e,将3放入新数组对应的位置,执行完此轮循环后线程A的情况如下:

执行下一轮循环,此时 e=7,原本线程 A 中 7 的 next 为 5,但由于 table 是线程 A 和线程 B 共享的,而线程 B 顺利执行完后,7 的 next 变成了 3,那么此时线程 A 中,7 的 next 也为 3 了。

采用头部插入的方式,变成了下面这样子:

好像也没什么问题,此时 next = 3,e = 3。

进行下一轮循环,但此时,由于线程 B 将 3 的 next 变为了 null,所以此轮循环应该是最后一轮了。

接下来当执行完 e.next=newTable[i] 即 3.next=7 后,3 和 7 之间就相互链接了,执行完 newTable[i]=e 后,3 被头插法重新插入到链表中,执行结果如下图所示:

套娃开始,元素 5 也就成了弃婴,惨~~~

不过,JDK 8 时已经修复了这个问题,扩容时会保持链表原来的顺序,参照HashMap 扩容机制的这一篇。

02、多线程下 put 会导致元素丢失

正常情况下,当发生哈希冲突时,HashMap 是这样的:

但多线程同时执行 put 操作时,如果计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致元素的丢失。

put 的源码:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;

    // 步骤①:tab为空则创建
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;

    // 步骤②:计算index,并对null做处理 
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;

        // 步骤③:节点key存在,直接覆盖value
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;

        // 步骤④:判断该链为红黑树
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

        // 步骤⑤:该链为链表
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);

                    //链表长度大于8转换为红黑树进行处理
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }

                // key已经存在直接覆盖value
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }

        // 步骤⑥、直接覆盖
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;

    // 步骤⑦:超过最大容量 就扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

问题发生在步骤 ② 这里:

if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);

两个线程都执行了 if 语句,假设线程 A 先执行了 tab[i] = newNode(hash, key, value, null),那 table 是这样的:

接着,线程 B 执行了 tab[i] = newNode(hash, key, value, null),那 table 是这样的:

3 被干掉了。

03、put 和 get 并发时会导致 get 到 null

线程 A 执行put时,因为元素个数超出阈值而出现扩容,线程B 此时执行get,有可能导致这个问题。

注意来看 resize 源码:

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        // 超过最大值就不再扩充了,就只好随你碰撞去吧
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 没超过最大值,就扩充为原来的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 计算新的resize上限
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
}

线程 A 执行完 table = newTab 之后,线程 B 中的 table 此时也发生了变化,此时去 get 的时候当然会 get 到 null 了,因为元素还没有转移。

自己画的HashMap 原理
 

Timer

[资料1](https://github.com/kangjianwei/LearningJDK/blob/master/src/java/util/TaskQueue.java)
[资料2](https://my.oschina.net/u/2474629/blog/691453)

基本结构原理了解了,但是TaskQueue里的fixDown、fixUp方法比较迷惑。
特此,做笔记记录,后续研究。

引用  Reference源码解析

https://cloud.tencent.com/developer/article/1366147

https://blog.csdn.net/p13132312312/article/details/103227415

 【对线面试官】今天来聊聊Java注解https://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247483821&idx=1&sn=e9003410a8d3c8a092de0c4d2002bedd&chksm=fdf0e9f2ca8760e455ddf557ebb0bde3c7c295ecfb47e6759490fed36ee1bdaa54f6a46ae602&scene=178&cur_album_id=1657204970858872832#rd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值