第 1 章
目录
第 1 章:目录
第 2 章:Object
第 3 章:基本数据类型的包装类
第 4 章:字符串的常用API
第 5 章:异常处理
第 6 章:系统类和日期类
第 7 章:Collection单列集合
第 8 章:Map双列集合
第 9 章:JDK8新特性
第 10 章:File文件操作
第 11 章:常用的算法
第 12 章:IO流
第 13 章:多线程
第 14 章:Java高级技术
第 2 章
Object
API,Application Program Interface 中文翻译过来叫“应用程序接口”,是别人写好的一些程序,给我们直接进行调用。
关注公众号,回复“jdk文档”即可获取JDK9汉化版下载链接。
第 3 章
基本数据类型的包装类
Java的包装类型是为了解决基本数据类型(也称为原始类型)在面向对象编程环境中的局限性而设计的一组类。
基本数据类型的局限性:不支持泛型,Java泛型要求类型参数必须是对象类型,因此基本类型不能直接用作泛型参数。例如,你不能创建一个List<int>,但可以创建一个List<Integer>。
valueOf()静态方法可以把基本类型转为包装类,Java中8种基本数据类型和包装类的对应关系如下:
代码应用:将用户id保存到ThreadLocal线程变量池中。
字符串转为数值可以用parseInt和parseDouble等静态方法进行转换。
Long和Integer,对于数不在 [-128,127] 之外的值不能使用==进行比较,而应该使用equals进行比较。
Long是一个封装类型,它是对应一个 long 类型的包装类。在 Java 里面之所以要提供 Long 这种基本类型的封装类,是因为 Java 是一个面向对象的语言,而基本类型不具备对象的特征,所以在基本类型上做了一层对象的包装并且提供了相关的属性和访问方法来完善基本类型的操作。
在 Long 这个封装类里面,除了基本的 long类型的操作之外,还引入了享元模式的设计,对-128 到 127 之间的数据做了一层缓存,也就是说,如果 Long 类型的目标值在-128 到 127 之间,就直接从缓存里面获取 Long 这个对象实例并返回,地址值是一样的,不在 -128 到 127范围 之间创建一个新的 Long 对象,地址值不一样,所以不能用==进行比较。
享元设计模式的好处是减少频繁创建 Long 对象带来的内存消耗从而提升性能。因此在这样一个前提下,如果定义两个 Long 对象,并且这两个 Long 的取值范围正好在-128 到 127 之间。如果直接用==号来判断,返回的结果必然是 true,因为这两个 Long对象 指向的内存地址是同一个。对于不在这个范围的数据,Long会创建一个新的对象,==是比较内存地址,所以相同的值返回的结果是 false。
之所以在测试环境上没有把这个问题暴露出来,是因为测试环境上验证的数据量有限,使得取值的范围正好在 Long 的缓存区间,从而通过了测试。但是在实际的应用里面,数据量远远超过LongCashe的取值范围,所以会导致校验失败的问题。
工具类就是类中的方法API都用static修饰,调用时通过类名.静态方法名即可调用。如果需要接收对象,则使用对应的类名声明一个对象变量接收即可。
使用“+,-,*,/”四则运行符号进行运算时会导致精度丢失。
doubleValue() 方法可以将BigDecimal类型转换为double类型。
第 4 章
字符串常用的API
String内部的value值是final修饰的,每一次对字符串进行操作时都会创建一个新的对象放到堆内存中,性能比较低,而需要占用内存,所以在开发中很少直接使用String类对字符串进行频繁操作。
String类常用的API有:
截取字符串内容 (包前不包后)。
判断字符串中是否包含某个关键字。
把字符串中的某个内容替换成新内容,并返回新的字符串对象给我们。
判断字符串是否以某个字符开头。
把字符串按照某个指定内容分割成多个字符串,放到一个字符串数组中返回给我们。
4.2、StringBuilder
指定的位置插入指定的字符串。
删除指定位置的字符串。
反转StringBuilder中的字符顺序。
获取StringBuilder中的字符总数。
将StringBuilder对象转换为String对象。
append():在 StringBuffer 的末尾追加指定的数据。
insert():在指定的位置插入指定的字符串。
注意:在 insert() 方法中,指定的位置是索引,从0开始计数。如果索引等于字符串的长度,则新字符将追加到字符串的末尾。
delete():删除指定位置的字符串。
注意:在 delete() 方法中,指定的是起始索引和结束索引,结束索引处的字符不包括在内。
reverse():反转StringBuffer 中的字符顺序。
获取StringBuffer 中的字符总数。
substring():提取字符串的子串。
将StringBuffer 转换为String对象。
以上这些方法都是 StringBuffer 类中非常常用的。与 StringBuilder 不同的是,StringBuffer 的大部分主要方法(如 append、insert、delete、reverse)都是同步的,这意味着在多线程环境中,它们可以安全地被多个线程同时调用,而不会导致数据不一致的问题。然而,这种同步机制也带来了额外的性能开销,所以在单线程环境中,如果不需要考虑线程安全问题,通常推荐使用 StringBuilder 以获得更好的性能。
String 内部的 value 值是 final 修饰的,所以它是不可变类。所以每次修改 String 的值,都会产生一个新的对象。
StringBuffer 和 StringBuilder 是可变类,字符串的变更不会产生新的对象。
String 是不可变类,所以它是线程安全的。
StringBuffer 是线程安全的,因为它每个操作方法都加了 synchronized 同步关键字。
StringBuilder 不是线程安全的。所以在多线程环境下对字符串进行操作,应该使用 StringBuffer,否则使用StringBuilder。
String 的性能是最的低的,因为不可变意味着在做字符串拼接和修改的时候,需要重新
创建新的对象以及分配堆内存。
其次是 StringBuffer 要比 String 性能高,因为它的可变性使得字符串可以直接被修改;
最后是 StringBuilder,它比 StringBuffer 的性能高,因为 StringBuffer 加了同步锁。
String 存储在字符串常量池里面;
StringBuffer 和 StringBuilder 存储在堆内存空间。
最后再补充一下, StringBuilder 和 StringBuffer 都是派生自 AbstractStringBuilder这个抽象类。
检查字符串是否为空或只包含空格。
StringUtils.isNotBlank 是 Apache Commons Lang 库中的一个方法,用于检查一个字符串是否不为 null、长度大于0并且包含至少一个非空白字符。
检查字符串是否为 null 或空。
比较两个字符串,考虑 null 值。
StringJoiner 是 Java 中的一个类,用于将多个字符串连接起来。它的主要特点是可以指定分隔符,并可以通过链式编程来设置多个属性。
正则表达式的作用1:用来校验字符串数据是否合法。
正则表达式的作用2:可以从一段文本中查找满足要求的内容。
正则表达式的作用3:批量替换文本中的不想展示的字符。
需求:请把下面文本中的电话,邮箱,座机号码,热线都爬取出来。
步骤1:使用Pattern类的静态方法compile来编译正则表达式,生成一个Pattern对象。
步骤2:使用Pattern对象的matcher方法创建一个Matcher对象,用于在给定的文本中进行匹配。
步骤3:用Matcher对象的replaceAll方法来替换所有匹配的内容。使该方法接收一个字符串作为参数,这里使用replacement来替换。
第 5 章
异常的处理
可查异常:编译器要求必须处置的异常,不然不给运行。
不可查异常:编译器不要求强制处置的异常,运行过后才报错, 包括运行时异常(RuntimeException与其子类)和错误(Error),如ArrayIndexOutOfBoundsException(数组的越界)。
异常处理快捷键:Alt+enter
使用try-catch语句提前对可能出现异常的代码进行捕获处理。
注意:捕获异常时,不要使用e.printStackTrace()打印太多的堆栈信息,这样会占用系统的内存,影响软件的性能。
在Java中,自定义异常是指为了满足特定应用需求而创建的、继承自现有异常类的用户自定义异常类。Java提供了一套丰富的预定义异常体系,但有时系统提供的异常类型可能无法精确表达业务逻辑中的错误情况或特殊条件,这时就需要开发者自行创建异常类来表示这些特殊的异常状态。
第 6 章
系统类和日期类
ZoneId是一个类,表示时区ID,例如"Asia/Shanghai"是获取亚洲上海的时区ID,"America/New_York"是获取美洲纽约的时区ID。它包含时区信息,可以用于获取特定时区的偏移量。
ZonedDateTime表示带有时区的日期和时间。它结合了LocalDateTime和ZoneId,可以用于处理具有时区信息的时间。ZonedDateTime代表了一个特定的时间点,可以用于计算时间差、比较时间等操作。
DateTimeFormatter是Java 8中新增的日期、时间格式器,主要用于LocalDateTime、LocalDate、LocalTime等Java 8新增的时间类进行格式化和解析。
第 7 章
Collection单列集合
ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet集合都可以调用下面的方法。
Collection集合常用方法:
1.add添加元素。
2.clear清空集合中的元素。
3.remove删除元素。
4.contains判断当前集合是否包含某个元素。
5.isEmpty判断当前集合是否为空。
6.size获取集合的长度。
List系列集合:添加的元素是有序、可重复、有索引的。
List集合有两个实现类:ArrayList和LinekdList。
1.add插入方法,没有指定索引则是往集合尾部插入元素。
2.remove删除指定索引处的元素。
3.set修改指定索引处的元素。
4.获取指定索引位置的元素。
List集合相比于前面的Collection多了一种可以通过索引遍历的方式,所以List集合遍历方式一共有四种:
1.普通for循环(只因为List有索引)。
2.迭代器。
3.增强for。
4.Lambda表达式。
[1] 使用迭代器遍历集合时,又同时通过集合对象删除集合中的数据,程序就会出现并发修改异常的错误。
[2] 增强for循环底层依赖迭代器,因此,增强for循环遍历集合,并通过集合删除元素时,会出现并发修改异常。
[3]使用普通for循环从头开始遍历删除相同元素会出现漏删的现象。解决方法就是从后往前倒着遍历删除,从前往后遍历,每删除一个元素要执行i--操作。
1.利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组。
2.当往空集合添加第一个元素时,底层会创一个新的长度为10的数组,添加到索引为0的位置,再添加存放元素的指针就会往后移动。存满时,会自动进行扩容1.5倍,创建一个新的数组,长度为原来数组的1.5倍,然后将原来数组的值复制到新数组中。
3.如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准。
使用LinekdList设计栈结构:使用push方法往链表头部添加元素,使用pop方法移除链表头部的元素。
1.数据结构:ArrayList是基于数组的数据结构,而LinkedList是基于双向链表的数据结构。
2.动态扩容:当你在ArrayList中添加元素并且超出当前数组容量时,ArrayList会自动进行扩容。而LinkedList则不需要扩容,只需将添加的元素增加一个指针链接到链表头部或者尾部的元素即可。
3.插入和删除操作:在ArrayList中,插入和删除操作(特别是删除第一个或最后一个元素)可能会涉及到大量的元素移动,因为需要移动元素来填补空位。相反,LinkedList的在插入和删除操作通常更快,因为它只需要改变头部或者尾部元素指针即可。
4.访问元素:访问ArrayList的元素通常更快,因为它是通过索引直接访问的。而访问LinkedList的元素则需要从头或尾部遍历到指定的位置。
5.线程安全:ArrayList和LinkedList都不是线程安全的。
6.使用场景:一般来说,如果你需要频繁地访问元素,那么ArrayList可能是一个更好的选择。如果你需要频繁地插入和删除元素,那么LinkedList可能更适合。
可变参数是一种特殊的形式参数,定义在方法、构造器的形参列表处,它可以让方法接收多个同类型的实际参数。
可变参数在方法内部,本质上是一个数组。
1.一个形参列表中,只能有一个可变参数;否则会报错。
2.一个形参列表中如果多个参数,可变参数需要写在最后;否则会报错。
红黑树能实现元素的快速查找:
比如要查找22这个元素的值,比较4次就可以找到了。第1次和13判断,大于13,走右边,大于17,再走右边,小于25,就走左边。比较4次就能找到22这个元素了。
二叉树的遍历方式有三种,分别是前序遍历(VLR)、中序遍历(LDR)和后序遍历(LRD)。
前序遍历的顺序是“根左右”,即首先访问根节点,然后遍历左子树,最后遍历右子树。
中序遍历的顺序是“左根右”,即首先遍历左子树,然后访问根节点,最后遍历右子树。
后序遍历的顺序是“左右根”,即首先遍历左子树,然后遍历右子树,最后访问根节点。
红黑树的默认遍历方式是中序遍历:根—>左—>右。
HashSet集合底层是基于哈希表实现的,哈希表根据JDK版本的不同,也是有点区别的。
JDK8以前:哈希表 = 数组+链表。
JDK8开始:哈希表 = 数组+链表+红黑树。
jdk8版本的HashSet扩容原理:
当创建一个集合时,哈希表会创建一个默认长度为16的数组来存储元素。默认加载因子是0.75,当存储容量达到3/4的时候就会进行自动扩容两倍,它会自动扩容至原来的两倍(乘以2),首次扩容后,数组的容量会从16变为32。
当向集合中添加元素时,Java会根据hashCode对数组长度进行取余确定元素存储位置。具体来说,它对元素的hashCode值进行取余操作,以确定元素在数组中的索引位置,例如第一次添加元素时,将元素的hashCode除以16,如果余数为8,元素就放在数组索引为8的位置。第二次添加元素时,还是对16进行取余,如果余数还是8,元素会被挂在原来元素的下面。
在数组容量扩容至两倍(即乘以2)后,对新的数组长度(即32)进行取余操作,并根据余数确定新元素的位置。这种机制有助于提高集合的存储效率和查找速度。
当数组长度大于等于64时,有链表长度超过8,该链表就会自动转换成红黑树,当该红黑树的元素小于等于6时,又会将红黑树转为链表。
TreeSet集合的特点是可以对元素进行排序,如果往集合中存储String类型的元素,或者Integer类型的元素,它们本身就具备排序规则,所以直接就可以排序。
但是如果添加集合中的元素是自己对象,则必须指定元素的排序规则,否则会抛出异常。
排序方式1:让元素的类实现Comparable接口,重写compareTo方法。
实现步骤:
1.让集合的元素类实现Comparable接口。
2.重写comparaTo方法,this-o是升序排序;o-this是降序排序。
Double.compare(double x, double y) 是用于比较两个 double 类型的值。
Integer.compare(int x, int y) 是用于比较两个 int 类型的值。
第 8 章
Map双列集合