Java基础(一):编译和解释、数据类型、变量作用域、String常用方法、数组、面向对象、异常
Java基础(二):集合、IO流(Zip压缩输入/输出流等)、File文件类、反射、枚举
Java异常、继承结构、处理异常、自定义异常、SpringBoot中全局捕获处理异常
Java–JUC之CountDownLatch、Semaphore以及CyclicBarrier
Java多线程基本概念、线程的创建方式(有、无返回值)、常用方法、synchronized锁、线程安全问题、死锁以及如何避免
Java基础(二)
集合
java.util包中提供了一些集合类,也被称为是容器。数组其实也是容器,那么集合和数组的是有区别的:
- 数组的长度是固定的,集合长度可变
- 数组可存放基本数据类型和引用数据类型,而集合是存储引用数据类型,且集合可以有泛型
Collection接口是层次结构中的根接口,构成Collection的单位称为元素。Collection中提供了一些基本方法,需要子类实现并重写。
List
list集合中的元素允许重复,是有序的(插入顺序)。
子类有ArrayList
和LinkedList
实现类,区别?
- ArrayList底层是动态数组实现,在堆内存中开辟一块连续的空间存储元素,并且默认存储空间为10,扩容每次1.5倍。每个元素存储着对象的引用地址和一个索引值,查询元素时根据索引下标即可获取元素,时间复杂度为O(1),但是随机插入和删除元素时,需要移动元素的位置,影响效率,比LinkedList效率慢。
- LinkedList底层是双向链表,内存分布散列,是一种无界限的数据机构;元素除保存着对象的引用地址外,还保留着前后节点的指针地址,随机访问元素时,需要从头到尾将链表查询一遍,直到找到元素为止,时间复杂度是O(n),比ArrayList要慢,但是随机插入删除元素时只需要改变目标节点的前后节点指针即可,效率很高,比ArrayList效率高。
一般项目中ArrayList完全够用了,除非涉及到过多的插入删除集合元素时使用LinkedList
,有一种情况,当数据量很大的情况下,若了解数据的预估量,那么在创建集合时直接声明元素数量即可,因为过多的扩容会很影响效率
。
Set
set集合中的元素不允许重复,且无序的(有些子类维护了顺序)
子类有HashSet
、TreeSet
、LinkedHashSet
等
-
HashSet底层就是HashMap(也就是哈希表),是一个无序集合,允许存入一个null值,感觉可以将HashSet看做是只有key的一个HashMap。
-
TreeSet:底层是TreeMap来实现(红黑树),维护了一个排序,但是需要注意:
- 元素要实现
Comparable
,重写compareTo()
- 或者创建TreeSet集合对象时,构造函数中传入一个比较器
- 元素要实现
-
LinkedHashSet:就是在HashSet的基础上维护了一个链表,来保证元素的插入顺序。
Map
map是比较特殊的一种集合,它存储的不是单一的元素,而是一个k-v键值对的集合,其中key是不能重复的,通过hashCode和equals方法比较是否是重复元素,若重复则value会把先前的覆盖掉。
key决定了存储对象在映射中的存储位置,不能由key本身决定,而是通过散列技术
进行处理,产生一个hash值,通常是作为一个偏移量。
子类有:HashMap
、TreeMap
、LinkedHashMap
-
HashMap:是通过数组+链表/红黑树来实现,允许使用null键和null值,但是只能有一个null键(重复的会被覆盖),是无序的。存储的元素需要计算key的hash值确定其存储位置,当遇到hash冲突时(key的hash值相同但却是不同的对象),它们的value通过链表相连接,当hash冲突的数量大于一定阈值时,链表转为红黑树来保证查询效率。
-
TreeMap:通过红黑树来实现,插入元素时会自动排序,同样的需要注意:
- 元素实现
Comparable
接口,重写compareTo()
- 创建
TreeMap
集合时,构造器传入一个比较器 - 且key不能是null,因为要比较排序
- 元素实现
-
LinkedHashMap:在HashMap的基础上维护一个链表,保证元素的插入顺序。
IO
流
流是一组有序的数据序列,根据操作的类型,可以分为输入流
和输出流
两种,从其他维度也可细粒度划分:如数据类型上分字节流
和字符流
,与数据源的关系上划分为节点流
和处理流
等等。
图解
在java中定义了很多专门负责各种方式的输入/输出,这些类被放入到java.io包中。所有的输入流类都是抽象类InputStream(字节输入流)
或抽象类Reader(字符输入流)
的子类;输出流类都是抽象类OutputStream(字节输出流)
或抽象类Writer(字符输出流)
的子类。
注意:所有的流资源都会在内存中创建对象占用系统资源,一定要在流资源使用完毕后关闭,否则会造成java程序中占用着系统资源且java中的流资源对象一直在堆内存中存在,导致内存泄漏
详解看Java IO流
File类
File类是java包中唯一代表磁盘文件本身的对象,可以调用File类的一些方法,实现创建、删除、重命名文件等操作,可以获取文件的一些基本信息,文件所在目录、文件长度、文件读写权限等。数据流可以将数据写入到文件中,文件也是数据流最常用的数据媒体。
构造函数
-
File(String pathname) : 该构造通过给定路径名称字符串转换为抽象路径创建一个File实例
// 创建a.txt文件 File file = new File("D:\\file\\a.txt");
-
File(String parent, String child) : 该构造根据定义的父子路径拼接创建一个File实例
// 创建a.txt文件 File file = new File("D:\\file","a.txt");
-
File(File f, String child) : 该构造根据parent抽象路径名和子路径名称字符串创建一个新的File实例
// 创建a.txt文件 File file = new File(new File("D:\\file"), "a.txt");
常用方法
方法 | 返回值 | 说明 |
---|---|---|
getName() | String | 文件名称 |
canRead() | boolean | 是否可读 |
canWrite() | boolean | 是否可写 |
getAbsoletePath() | String | 文件绝对路径 |
getParent() | String | 获取文件父路径 |
isFile() | boolean | 是否是文件 |
isDirectory() | boolean | 是否是目录 |
缓冲流
缓冲流主要是在内存里开了一块缓冲区,以提高数据的读写速度,遇到一些大文件读写速度较慢时可以考虑将数据搞到内存里进行读写,写数据的话然后记得flush()
一下,将数据全部写入到目的文件中去。
注意的是:缓冲流其实只是在内存开了块缓冲区,实际上还是调用的InputStream
或OutputStream
、Reader
、Writer
的方法
具体详解看Java IO流
Zip压缩输入/输出流
在java.util.zip
包中的ZipOutputStream
和ZipInputStream
类来实现文件的压缩/解压缩。如要从Zip压缩管理文件内读取某个文件,那么要先找到对应文件的"目录进入点"(从它可知该文件在Zip文件内的位置),才可以读取到这个文件的内容。若要将文件内容写至ZIP文件内,要先写入对应文件的"目录进入点",且要把写入文件内容的位置移入到此进入点所指的位置,然后写入文件内容。
在Java中实现了I/O数据流与网络数据流的单一接口,因此数据的压缩、网络传输和压缩的实现比较容易。ZipEntry类产生的对象,是用来代表一个ZIP压缩文件内的进入点。
ZipInputStream
类用来读取压缩格式的文件,所支持的包括已压缩及未压缩的进入点(entry)。ZipOutputStream
用来写出Zip压缩格式的文件,而且所支持的包括已压缩及未压缩的进入点(entry)。
ZipEntry、ZipInputStream、ZipOutputStream三个Java类实现Zip数据压缩/解压。
ZipOutputStream类
构函
可将文件压缩为.zip文件。构造函数如下:
- ZipOutputStream(OutputStream out):接收一个输出流
方法
方法 | 返回值 | 说明 |
---|---|---|
putNextEntry(ZipEntry e) | void | 开始写一个新的ZipEntry,将流内的位置移至此entry所指数据的开头 |
write(byte[] b, int off, int len) | void | 字节数组写入当前Zip条目数据 |
finish() | void | 完成写入ZIP输出流的内容,无需关闭它所配合的OutputStream |
setComment(String comment) | void | 可设置此ZIP文件的注释文字 |
例子
如压缩E盘的hello文件件,在该文件夹中有个hello1.txt和hello2.txt文件,将压缩后的hello.zip
文件保存在E盘的根目录下。
实现:
public class ZipTest {public static void main(String[] args) throws IOException {ZipTest zi = new ZipTest();// 参数: 1.压缩后的文件 2.要压缩的文件/目录zi.zip("E:\\hello.zip", new File("E:\\hello"));System.out.println("压缩完成");}private void zip(String zipFileName, File inputFile) throws IOException {ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));zip(out, inputFile, "");System.out.println("压缩中...");out.close();}private void zip(ZipOutputStream out, File f, String base) throws IOException {// 如果是目录,递归方式继续找下层,直到是文件if (f.isDirectory()) {File[] fl = f.listFiles();// 每执行一次,都要将目录进入点更新下,否则可能会写入到别的目录中out.putNextEntry(new ZipEntry(base + "/"));base = base.length() == 0 ? "" : base + "/";for (File aFl : fl) {zip(out, aFl, base + aFl);}} else { // 是文件,将文件拷入到 压缩文件内// 每执行一次,都要将目录进入点更新下out.putNextEntry(new ZipEntry(base));FileInputStream in = new FileInputStream(f);int b;System.out.println(base);while ((b = in.read()) != -1) {out.write(b);}in.close();}}}
ZipInputStream
Zip类可以读取ZIP压缩格式的文件,包括已压缩和未压缩的条目.
构造函数
- ZipInputStream(InputStream in) : 接收一个字节输入流
方法
方法 | 返回值 | 说明 |
---|---|---|
read(byte[] b, int off, int len) | int | 读取目标b数组内off偏移量的位置,长度是len字节 |
available() | int | 判断是否已经读完目前entry所指的数据。读完返回0,否则返回1 |
closeEntry() | void | 关闭当前ZIP条目并且定位流以读取下一个条目 |
getNextEntry() | ZipEntry | 读取下一个ZipEntry,并将流的位置移至该entry所指的数据开头 |
createZipEntry() | ZipEntry | 以指定的name参数新建一个ZipEntry对象 |
解压缩示例
public class DeZipTest {public static void main(String[] args) {try (ZipInputStream zin = new ZipInputStream(new FileInputStream("E:\\hello.zip"))){ZipEntry entry;while (((entry = zin.getNextEntry()) != null) && !entry.isDirectory()) {File file = new File(entry.getName());System.out.println(file);// 文件不存在时,创建目录和文件if (!file.exists()) {file.mkdirs();file.createNewFile();}zin.closeEntry();System.out.println(entry.getName() + "解压成功");}} catch (IOException e) {e.printStackTrace();}}}
hutool工具
hutool开源工具非常好用,有兴趣可以去官网看下,有各种各样的工具类,方便使用,简化开发,提高效率。
官方网站
简单介绍下hutool工具有关压缩/解压的工具类ZipUtil
,有兴趣可以去官网学习下。
ZipUtil就是针对java.util.zip
做工具化封装,使用时一个方法全都搞定。
- 压缩
//将aaa目录下的所有文件目录打包到d:/bbb/目录下的ccc.zip文件中
ZipUtil.zip("d:/aaa", "d:/bbb/ccc.zip");
- 解压
//将test.zip解压到e:\\aaa目录下,返回解压到的目录
File unzip = ZipUtil.unzip("E:\\aaa\\test.zip", "e:\\aaa");
反射
通过反射机制,可以在程序中访问已经装载到JVM中的Java对象的描述,实现访问、监测和修改描述Java对象信息的功能。java.lang.reflect
包中提供了该功能的支持。
Object类中定义了一个getClass()方法,返回一个Class对象。
Class textClasss = a.getClass();
获取Class类对象的几种方式:
对象.getClass();
类名.class
Class.forName("com....")
,从类的全限定类名加载Class类对象
常用方法
组成 | 方法 | 返回值 | 说明 |
---|---|---|---|
包路径 | getPackage() | Package对象 | 获得该类的存放路径 |
类名 | getName() | String对象 | 获得该类的名称 |
继承类 | getSuperclass() | Class对象 | 获得该类继承的类 |
实现接口 | getInterfaces() | Class型数组 | 获取该类实现的所有接口 |
构造 | getConstructors() | Constructor型数组 | 获取所有权限为public的构造函数 |
getConstructor(Class<?> paramType) | Constructor对象 | 获取权限为public的指定构造 | |
getDeclaredConstructors() | Constructor型数组 | 暴力获取所有的构造方法,按照声明顺序返回 | |
方法 | getMethods() | Method型数组 | 获取所有public的方法 |
getDeclaredMethods() | Methods型数组 | 获取所有的方法(含private) | |
成员变量 | getFields() | Field型数组 | 获取成员变量(public) |
getDeclaredFields() | Field型数组 | 暴力获取所有 |
注意:
getFields()
和getMethods()
获取public
级别的成员变量和方法时,将会获取到父类的所有public
级别的成员变量和方法;而通过getDeclaredFields()
和getDeclaredMethods()
时,只能暴力获取所有本类的的成员变量和方法。
每个Class对象中存在的组件:
- Method
- Constructor
- Field
这几个组件中的方法不再展示,有用到直接查Java8API文档或其他资料即可。
这里贴几块代码展示下基本使用,首先有个Student
的类
@Data
public class Student {@Deprecated // 标个注解String name;Integer age;public Student() {}@Deprecated // 标个注解public Student(String name, Integer age) {this.name = name;this.age = age;}// 随便写个注解(bean销货时调用的注解)@PreDestroypublic void show() {System.out.println("show 1");}private void show2() {System.out.println("show 2");}}
-
Constructor
public static void main(String[] args) throws ClassNotFoundException {// 获取 Class类对象Class<?> aClass = Class.forName("com.xw.test.Student");Constructor<?>[] constructors = aClass.getConstructors();for (Constructor<?> constructor : constructors) {// 构造名System.out.println(constructor.getName());// 查看注解Annotation[] annotations = constructor.getAnnotations();if (annotations.length > 0) {System.out.println(Arrays.toString(annotations));}} }
com.xw.test.Student com.xw.test.Student [@java.lang.Deprecated()]
-
Method
public static void main(String[] args) throws ClassNotFoundException {// 获取 Class类对象Class<?> aClass = Class.forName("com.xw.test.Student");Method[] methods = aClass.getMethods();for (Method method : methods) {// 获取方法名System.out.println(method.getName());// 查看注解Annotation[] annotations = constructor.getAnnotations();if (annotations.length > 0) {System.out.println(Arrays.toString(annotations));}} }
... show [@javax.annotation.PreDestroy()] 获取到show方法,并且和他的@PreDestory注解 ...
-
Field
public static void main(String[] args) throws ClassNotFoundException {// 获取 Class类对象Class<?> aClass = Class.forName("com.xw.test.Student");// 这里如果用 getFields()是获取不到属性的,因为在Student类中的所有属性都是private级别的// 只能通过暴力返回Field[] fields = aClass.getDeclaredFields();for (Field field : fields) {// 获取属性名System.out.println(field.getName());// 查看注解Annotation[] annotations = field.getAnnotations();if (annotations.length > 0) {System.out.println(Arrays.toString(annotations));}} }
name [@java.lang.Deprecated()] age
注解@Annotation
使用@interface
关键字可以声明一个注解,这个关键字的隐含意思是继承了java.lang.annotation.Annotation
接口。
@interface MyAnnotation {String value();
}
其中 String 是成员的类型,可指定的类型:
- String
- Class
- primitive
value是成员的名称。如果在定义的Annotation类型中只有一个成员,通常会将名称命名为value;
在定义Annotation类型时
- 可以通过Annotation类型@Target来设置那些地方上可以声明注解,如果未设置
@Target
,那么适用于所有的程序元素。枚举类ElementType
中的常量用来设置@Target
// 表示此注解声明在 属性和方法上
@Target({ElementType.FIELD, ElementType.METHOD})
@interface MyAnnotation {String value();
}
- 可通过Annotation的@Retention设置注解的有效范围,枚举类
RetentionPolicy
中的枚举常量用来设置@Retention
@Target({ElementType.FIELD, ElementType.METHOD})// 使用时,一般使用 RUNTIME即可
@Retention(RetentionPolicy.SOURCE) // 表示不编译注解到类文件中,有效范围最小
@Retention(RetentionPolicy.CLASS) // 表示编译注解到类文件中,但是运行时不加载注解到JVM中
@Retention(RetentionPolicy.RUNTIME) // 运行时加载注解到JVM中,有效范围最大
@interface MyAnnotation {String value();
}
使用注解,获取注解信息
例如创建了一个注解,那么后面如何使用和获取注解的信息呢
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) // 运行时加载注解到JVM中,有效范围最大
@interface MyAnnotation {String value() default "";
}// 使用注解
@MyAnnotation(value = "用户类")
class User {@MyAnnotation(value = "用户姓名")private String name;}
获取注解信息
如果定义Annotation类型时将@Retention
设置为RUNTIME类型,那么程序运行期间通过反射可以获取相关的注解上的信息。
在Constructor、Field、Method类中均有访问注解的方法,如getAnnotations()
方法获取所有注解,或getAnnotation(Class<T> clazz)
获取指定类型的注解
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {Class<?> aClass = Class.forName("com.xw.test.User");// 获取User类上的注解MyAnnotation annotation = aClass.getAnnotation(MyAnnotation.class);// 强制获取 User 类的name属性Field name = aClass.getDeclaredField("name");// 获取 name属性上的 注解MyAnnotation nameAnnotation = name.getAnnotation(MyAnnotation.class);// User类上注解的value()System.out.println(annotation.value());// User类的 name 属性上的注解 的 value()System.out.println(nameAnnotation.value());}
用户类
用户姓名
枚举
可以将枚举看做是一个类,它继承自java.lang.Enum
类,当定义一个枚举类型时,每一个枚举类型成员可以看做是枚举类型的一个实例,这些枚举类型成员默认都被final、public、static
修饰。并且枚举类型都可以调用Enum类中的一些常用方法。
方法名 | 说明 | 使用方法 |
---|---|---|
values() | 将枚举成员以数组形式返回 | 枚举类名.values() |
valueOf() | 将普通字符串转换为枚举实例 | 枚举类型.valueOf(“abc”) |
compareTo() | 比较两个枚举对象在定义时的顺序 | 枚举对象.compareTo() |
ordinal() | 得到枚举成员的索引位置 | 枚举对象.ordinal() |
普通使用,有参构造方式定义,且提供一个get方法
@Getter
enum Constant {SEASON_1(1, "季节1"),SEASON_2(2, "季节2"),SEASON_3(3, "季节3");private int code;private String msg;Constant(int code, String msg) {this.code = code;this.msg = msg;}
}
获取某个实例,且获取code值
public static void main(String[] args) {Constant season1 = Constant.SEASON_1;System.out.println(season1.getCode());
}
高级使用
通常情况下,我们可能会把相关的code值,存入到其他的数据库表里面,查询时需要根据code值来到枚举类中
寻找相关的msg值。改造下枚举
对外提供一个根据code查询msg的方法,内部实现下即可。
@Getter
enum Constant {SEASON_1(1, "季节1"),SEASON_2(2, "季节2"),SEASON_3(3, "季节3");private int code;private String msg;Constant(int code, String msg) {this.code = code;this.msg = msg;}public static String getByCode(int code) {Constant[] values = values();for (Constant value : values) {if (value.getCode() == code) {return value.getMsg();}}return null;}
}
使用
public static void main(String[] args) {int code = 2;String byCode = Constant.getByCode(code);System.out.println(byCode);
}
季节2