JDK-JRE-JVM概述
JRE (Java Runtime Environment): Java运行环境,如果要运行Java程序,就需要JRE的支持,JRE里包含JVM。
JDK (Java Development Kit): Java开发工具,包含java程序的所有工具,javac和java,JDK里包含JRE
JVM (Java Virtual Machine) 简称JVM,它是运行所有Java程序的虚拟计算机,好比模拟器,JNM是Java语言的运行环境。JVM用于读取并处理编译过的与平台无关的字节码,从而实现Java的可移植性。选择不同平台的JDK。
JVM是Java程序的解释和执行器。
Java JDK的使用
bin 存放Java的操作工具,比如编译工具javac等
include:存储c++的头文件
jre:java的运行环境,内有JVM
lib:java运行和核心库
src.zip:java的源代码
1 2 3 4 5 $ javac 123 .java $ java hello
java基本语法
java语言严格区分大小写,好比main和Main是完全不同的概念
一个java源文件里可以有多个java类,其中最多有一个类被定义为pulic,若源文件中包括了pulic类,源文件必须和pulic类同名
一个源文件中包涵N个java类时,编译后会生成N份字节码文件,即每个类都会生成一份单独的class文件,且字节码文件名和对应的类名相同
一个类必须有main方法才能运行,main方法是程序的入口。
建议: 一个java源文件之定义一个类,不同的类使用不同的源文件定义: 将每个源文件中单独定义的类都定义成pulic 保持java源文件的主文件名与源文件中的类名一致
JVM内存模型
JVM内存划分,认为的根据不同内存空间的存储特点以及存储的数据。
程序计数器:当前线程所执行的字节码的行号指示器。
本地方法栈:为虚拟机使用的native方法服务。
方法区:线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量即时编译器编译后的代码数据等(这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载)
java虚拟机栈:简称栈,每个方法被执行的时候都会同时创建一个栈帧用于存储改方法的局部变量、操作栈、动态链接、方法出口灯信息。
每当调用一个方法时,创建一个栈帧,存放了当前方法的局部变量,当方法调用完毕,改方法的栈帧就被销毁了。
java堆:被所有线程共享的一块内存区域,在虚拟机启动时创建,所有的对象实例以及数组都要在堆上分配。
每次使用new关键字,就表示在堆内存中开辟一块新的内存空间。
GC(Garbage Collection),垃圾回收器。 java的自动垃圾回收机制可以简单的理解为,不需要程序员手动的去控制内存的释放,当JVM内存资源不够用的时候,就会自动地去清理堆中无用对象(没有内引用到的对象)所占用的内存空间。
javaBean
栈帧:C语言中,每个栈帧对应一个未运行完的函数,栈帧中保存了该函数的返回地址和局部变量。
从逻辑上讲,栈帧就是一个函数执行的环境,函数参数,函数的局部变量,函数执行完后会返回到哪里等。
类
同一个包
子类
任何地方
private
✅
无
✅
✅
protected
✅
✅
✅
public
✅
✅
✅
✅
JavaBean规范:
类必须使用public修饰
必须保证有公共无参数的构造器,即使手动提供带参数的构造器,也得手动提供无参数构造器
字段使用private修饰,每个字段提供一堆getter和setter方法
每个字段都得使用private修饰,并提供一堆getter/setter方法
继承思想
Object类是java语言的根类,任何类都是Object的子类,要么是直接子类,要么是间接子类。
子类可以继承父类的全部成员(成员变量和方法),但private和无修饰的字段没有访问权限
方法覆盖
方法签名相同(方法签名 = 方法名 + 方法参数)
方法返回类型必须相同
子类方法中抛出的异常类型应该小于等于父类方法中抛出的异常类型
子类方法的访问权限必须大于父类方法的访问权限
只有实例方法(无Static修饰)才称之为方法覆盖 @override标签表示验证方法覆盖是否正确
方法重载
同一个类中,方法名相同
方法参数列表不同
注意:
方法覆盖和重载的区别: 方法覆盖和方法重载没有关系,override overload, 方法覆盖的判断规则 方法重载规则。
抽象
使用abstract修饰的方法,称为抽象方法 抽象类用于描述诸如生物,这样没有实例,但是在人们描述事物的概念中存在的。
1 public abstract 返回类型 方法名(参数);
特点
使用abstract修饰,没有方法体,留给子类去覆盖
抽象方法必须定义在抽象类或者接口中
使用abstract修饰的类称为抽象类。
1 public abstract class 类名 {}
抽象类不能创建对象,调用没有方法体的抽象方法没有意义
抽象类中可以同时拥有抽象方法和普通方法
抽象类要有子类才有意义,子类必须覆盖父类的抽象方法,否则子类也得作为抽象类。
Object
== 和 equals的区别
== 比较基本数据类型,比较值是否相等,比较应用类型时,比较内存空间是否相等
equals只能比较对象
覆盖equals 比较方式就看equals的方法体,一般比较内容是否相等
不覆盖equals 使用Object的equals方法比较,实际上就是用==比较两个对象是否相同,比较内存空间
接口
接口是一种规范,只定义应该有哪些功能,本身不实现功能。 作用:降低耦合性
接口定义
1 2 3 4 5 public interface 接口名 { }
接口表示具有某些功能的事物,接口名使用名词,有人习惯I打头,IWalkable.java
1 2 3 4 5 public interface IWalkable { void walk () ; public abstract void walk () ; }
接口实现类
类和接口之间的关系是implements,实现关系
1 2 3 public class 类名 implements 接口名 { }
如果实现类没有全部实现接口中的方法,要么报错,要么把实现类设置为抽象类。
多态
编译类型和运行类型不一致 Animal a = new Cat();
编译类型必须是运行类型的父类或接口
父类引用子类变量指向子类对象,调用方法时,实际调用的是子类方法
多态时方法调用问题
1 2 Animal a = new Cat(); a.shout();
上述代码的逻辑图
类型转换和instanceof运算符
instanceof
语法格式:boolean b = 对象A instanceof 类B; // 判断A对象是否是B类的实例。如果是返回true
面向接口编程,体现的就是多态,好处是:把实现类对象赋给接口类型变量,屏蔽了不同实现类之间的实现差异,从而可以做到通用编程。
this and super and static
super用法同this
在方法中,哪一个对象调用this所在的方法,此时this就表示哪一个对象。
this在构造器中的用法需要注意的是:
一般少参构造器调用多参构造器,this调用构造器必须为当前构造器方法中的第一行代码
static
static 修饰的成员变量,随着类被加载进JVM,内存储存在方法区中。
使用对象访问static修饰的成员变量,底层依然使用类名访问。
static方法访问的成员变量必须使用static修饰,必须依托类,而不是对象。
在类中,代码块是由上到下依次运行的,但是由static修饰的代码块,会在类加载到内存中就运行依次,之后如果不调用则不再运行。
final
final可以修饰类、方法、变量
final修饰类时,表示该类不能再有子类
final修饰方法时,表示该方法不能被子类所覆盖
final修饰变量,表示常量,改变量只能赋值一次,不能再重新赋值。
基本数据类型:表示值不能被改变
引用数据类型,所引用的地址不能被改变(即该变量指针不能重新指向其他地址);
内部类和匿名内部类
内部类可以看做和字段、方法一样,是外部类的成员变量,也可以被static修饰。
静态内部类
静态内部类:使用static修饰的内部类,访问内部类直接用类名.静态类构造器创建静态类
静态的内部类中,不能访问外部类中非静态的变量和方法
使用场景,一般针对于,静态内部类不需要外部类的资源,而外部类需要使用内部类的时候。
静态类的调用
1 2 Cat.Dog d = new Cat.Dog(); d.fun();
实例内部类
实例内部类:访问内部类,使用外部类实例的对象来访问。
匿名内部类
针对类,定义匿名内部类来继承父类(使用较少)
1 2 3 new [父类构造器]([实参列表]) { }
针对接口,使用较多
1 2 3 4 5 6 7 8 9 10 new 接口名称() { } board.plugin(new IUSB() { public void swapData () { System.out.println("打印" ); } });
匿名类的底层,依然还是创建了一份字节码文件,USBDemo$1,其反编译为:
1 2 3 4 5 6 class USBDemo $1 implements IUSB { public void swapData () { System.out.println("打印" ); } }
枚举
枚举类的由来:
1 2 3 4 5 6 7 public class Type { public static final Type MAN = new Type(); public static final Type WOMAN = new Type(); public static final Type NETURAL = new Type(); private Type () ; }
枚举类的定义和使用
public enum 枚举类名 { 常量对象A,常量对象B,常量对象C。 }
我们自定义的枚举类在底层都是直接继承了java.lang.Enum类的。
1 2 3 4 5 6 7 public enum Type { MEN, WOMEN, NEUTRAL; } Type t = new Type.MEN; t.name(); t.ordinal();
单例模式
目的:保证在整个应用中某一个类有且只有一个实例,(一个类在堆内存中只有一个对象)。
步骤:
必须在该类中,自己先创建一个对象。
私有化自身构造器,防止外界通过构造器创建新的工具类对象
向外暴露一个公共的静态方法用于返回自身的对象。
1 2 3 4 5 6 7 8 public class ArrayUtil { private static ArrayUtil instance = new ArrayUtil(); private ArrayUtil () {} public static ArrayUtil getInstance () { return instance; } }
基本类型的包装类
除了Integer和Character外,其他都是基本类型的首字母大写
装箱和拆箱
把基本类型数据转成对应的包装类对象。反之是拆箱
1 2 3 4 5 Integer num2 = Integer.valueOf(17 ); Integer num2 = 17 ; int val = num2.intValue();int val = num2; Object obj = 17
把字符串转换为int,int num = Integer.parseInt(“123”);
如果传入的不是纯数字字符串会抛出异常”NumberFormatException”
六种转换操作:
Integer -> int int val = num2.intValue(); int val = num2;
int -> Integer Integer num2 = Integer.valueOf(17); Integer num2 = 17;
Integer -> String num2 + ""
num2.toString()
String -> Integer Integer.valueOf(String s)
int -> String num3 + ""
String -> int int num = Integer.parseInt("123");
缓存设计
Byte、Short、Integer、Long缓存范围[-128, 127]; Character缓存范围为[0, 127];
1 2 3 4 5 6 7 8 9 Integer i1 = new Integer(123 ); Integer i2 = new Integer(123 ); System.out.println(i1 == i2); Integer i1 = Integer.valueOf(123 ); Integer i2 = Integer.valueOf(123 ); System.out.println(i1 == i2);
部分源码如下:
1 2 3 4 5 public static Integer valueOf (int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
BigDecimal
处理类似精确2位这样的需求
1 2 3 System.out.println(0.01 + 0.09 ); System.out.println((new BigDecimal(0.01 )).add(new BigDecimal(0.09 ))); System.out.println(new BigDecimal("0.01" ).add(new BigDecimal("0.09" )));
随机数 Random
Math.random(); // 返回一个[0,1)的随机小数 int intNum1 = (int)(num * 100); [0, 100)之间的随机数;
Random类用于产生一个伪随机数(通过相同的种子,产生的随机数是相同的),Math类的random方法底层使用的就是Random类的方式。
UUID
UUID表示通用唯一标识符(Universally Unique Identifier),其算法通过电脑网卡、当地时间、随机数等组合而成,优点是真实的唯一性、确定是字符串太长。
1 2 3 4 5 String uuid = UUID.randomUUID().toString(); String code = uuid.substring(0 , 5 ); code.toUpperCase();
字符串String类
字符串=字符序列,
不可变字符串:String str = “ABCD”; 等价于 char[] cs = new char[]{‘A’, ‘B’, ‘C’, ‘D’};对象内容改变,则变成了一个新的对象。
可变字符串 StringBuilder/StringBuffer:当StringBuilder对象创建完毕后,对象的内容可以发生改变,当内容发成改变的时候,对象保持不变。
String 对象创建的两种方式,有什么区别:
1 2 1 . String str1 = "ABCD" ;2 . String str2 = new String("ABCD" );
在内存中的分布:
Stirng对象的空值
表示引用为空(null),没有初始化,没有分配内存空间 空字符串,已经初始化,分配了内存空间。
判断字符串非空:字符串不为null,且字符内容不能为空字符串(“”)
1 2 3 public static boolean hasLength (String str) { return str != null && !"" .equals(str.trim()); }
比较字符串操作: “==” 比较两个字符串引用内存地址是否相同。 equals,比较两个字符串的内容是否相同。
1 2 3 4 "ABCD" == "ABCD" "ABCD" == new String("ABCD" ) "ABCD" .equals("ABCD" ) "ABCD" .equals(new String("ABCD" ))
String类型常用方法
int length()
返回次字符串的字符个数
char charAt(int index)
返回指定索引位置的字符
int indexOf(String str)
返回指定子字符串在此字符串中第一次出现处的索引位置
boolean equals(Object anObject)
比较内容是否相同
boolean equalsIgnoreCase(String anotherString)
忽略大小写,比较内容是否相同
String toUpperCase()
把当前字符串转换为大写
String toLowerCase()
把当前字符串转换为小写
String substring(int beginIndex)
葱绿指定位置开始截取字符串
String substring(int beginIndex, int endIndex)
截取指定区域的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 String str = "HelloWorld" ; System.out.println(str.length()); char ch = str.charAt(3 );System.out.println(ch); int index = str.indexOf("lo" );System.out.println(index); System.out.println("helloworld" .equals(str)); System.out.println("helloworld" .equalsIgnoreCase(str)); System.out.println(str.toLowerCase()); System.out.println(str.toUpperCase()); String str1 = str.substring(3 ); System.out.println(str1); String str2 = str.substring(3 , 6 ); System.out.println(str2);
可变字符串 StringBuilder(拼接多个字符串的时候才会使用)
性能比String高2500倍
StringBuffer和 StringBuilder的区别:
StringBuffer StringBuffer中的方法都使用了 sunchronized 修饰,保证线程安全但性能较低
StringBuilder StringBuilder中的方法没有使用 synchronized修饰,线程不安全但是性能较高,开发中建议使用StringBuilder
StringBuilder sb = new StringBuilder(40);
创建指定容量的字符数组,缺省为16
// 添加各种数据类型sb.append(123)
详见手册
// 删除下标start开始到end前一位的字符sb.delete(start, end)
// 删除指定位置的字符sb.deleteCharAt(index)
// 在指定位置插入各种数据类型sb.insert(index, "l")
// 反转字符串 sb.reverse();
Date日期
1 2 3 4 java.util.Date d = new java.util.Date(); d.toString(); d.toLocaleString(); long time = d.getTime();
格式化(Format):Date类型转换为String类型, String format(Date date)
解析(parse): String类型转换为Date类型 Date parse(String source)
日期模式列举
日期格式
日期
yyyy-MM-dd
2020-12-12
HH:mm:ss
20:12:12
yyyy-MM-dd HH:mm:ss
2020-12-12 20:12:12
yyyy/MM/dd HH:mm:ss
2020/12/12 20:12:12
yyyy年MM月dd日 HH时mm分ss秒
2020年12月12日 20时12分12秒
1 2 3 4 5 6 7 8 9 10 11 public static void main (String[] args) throws ParseException { Date d = new Date(); SimpleDateFormat sdf = new SimpleDateFormat(); sdf.applyPattern("yyyy-MM-dd HH:mm:ss" ); String dateString = sdf.format(d); System.out.println(dateString); Date dd = sdf.parse(dateString); System.out.println(dd); }
Calendar
Calendar是日历类,主要用来对日期做加减法,重新设置日期时间的功能,Calendar本身是抽象类,通过getInstance方法获取对象,底层创建的是Calendar的子类对象。
Calendar类中的MONTH的一月是从0开始计算的。
The first month of the year in the Gregorian and Julian calendars is JANUARY which is 0; the last depends on the number of months in a year.
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) throws ParseException { Calendar c = Calendar.getInstance(); System.out.println(c.get(Calendar.MONTH)); String time = "2019-09-30" ; SimpleDateFormat sdf = new SimpleDateFormat(); sdf.applyPattern("yyyy-MM-dd" ); Date date = sdf.parse(time); c.setTime(date); System.out.println(c.get(Calendar.YEAR)); System.out.println(c.get(Calendar.MONTH)); System.out.println(c.get(Calendar.DAY_OF_MONTH)); System.out.println(c.get(Calendar.DAY_OF_YEAR)); System.out.println(c.get(Calendar.DAY_OF_WEEK)); }
正则表达式
匹配规则一:
No.
规范
描述
1
\
表示反斜线(\)字符
2
\t
表示制表符
3
\n
表示换行
4
[abc]
字符a、b或c
5
[^abc]
除了a、b、c之外的任意字符
6
[a-zA-Z0-9]
表示由字母、数字组成
7
\d
表示数字
8
\D
表示非数字
9
\w
表示字母、数字、下划线
10
\W
表示非字母、数字、下划线
11
\s
表示所有空白字符(换行、空格等)
12
\S
表示所有非空白字符
13
^
行的开头
14
$
行的结尾
15
.
匹配除换行符之外的任意字符
匹配规则二:
数量表示(X表示一组规范)
No.
规范
描述
1
X
必须出现一次
2
X?
可以出现0次或者1次
3
X*
可以出现0次、1次或多次
4
X+
可以出现1次或者多次
5
X{n}
必须出现n次
6
X{n,}
必须出现n次以上
7
X{n,m}
必须出现n~m次
逻辑运算符(X,Y表示一组规范)
No.
规范
描述
1
XY
X规范后跟着Y规范
2
X
Y
3
(X)
作为一个捕获组规范
异常
Throwable类有两个子类 Error和 Exception,分别表示错误和异常。 Exception 和 Error子类都是以其名字作为类名后缀
Error表示代码运行时, JVM出现的问题,如系统崩溃或内存溢出等,不需要处理Error,
StackOverflowError 当应用程序递归太深而发生堆栈溢出,抛出该错误。比如死循环或者没有出口的递归调用。
OutOfMemoryError 因为内存溢出或者没有可用的内存提供给垃圾回收器时,Java虚拟机无法分配一个对象,这时候抛出该错误,比如new了一个非常庞大数量的对象而没释放。
抛出异常之后的代码不会执行。
常见的Exception
ArrayIndexOutOfBoundsException:数组越界
ArithmeticException:异常运算条件,比如除数为0
NullPointException:需要对象的地方使用了null,空指针异常。比如调用null对象的实例方法
捕获单个异常
异常一旦产生,首先会实例化一个该类型异常对象,并把该对象赋值给对应catch语句块里的异常类变量。
1 2 3 4 5 6 try { a / b } catch (ArithmeticException e) { }
也可以使用Exception接受所有的异常对象(多态),捕获异常的时候不建议使用Throwable,使用Throwable没有问题,但是Throwable分为Error和Exception,Error是没必要处理的。所以用Exception
方法
方法说明
String getMessage()
返回异常信息
void printStackTrace()
打印异常类名和异常信息,以及程序中出现异常的位置
getMessage
方法获取异常的错误信息,一般获取后,把错误信息打印给用户查看
printStackTrace
方法,用于打印异常具体信息,包含了异常信息,错误类型,错误位置,方便开发阶段的调试,也是JVM默认的异常处理机制
1 2 3 4 5 6 7 8 public static void divide (int a, int b) { try { a/b } catch (ArithmeticException e) { e.printStackTrace(); System.out.println("异常信息:" +e.getMessage()); } }
目前直接使用e.printStackTrace()即可
捕获多个异常
1 2 3 4 5 6 7 8 9 try { } catch (A异常) { } catch (B异常) { } catch (C异常) { }
finally语句块
try-catch-finally 格式
1 2 3 4 5 6 7 try {} catch (异常类型 变量) { } finally { }
try catch或者try finally 必须成对出现 finally块总会执行,无论是否有错误出现,如果try或者catch 中语句块存在JVM退出代码 System.exit(0);,则finally块就不会执行。
一般,把关闭资源的代码放在finally中,保证资源总是能关闭。
throws
异常可以抛出,使用throws关键字
1 修饰符 返回值类型 方法名(参数列表...) throws 异常类A,异常类B{}
throw
方法内,需要返回一个错误结果给调用者时,使用throw关键字在方法内手动抛出一个具体的异常对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) { try { isExist("will" ); } catch (Exception e) { System.out.println(e.getMessage()); } } public static boolean isExist (String username) throws Exception { String[] data = {"will" , "lucy" , "lily" }; if (username != null && !username.isEmpty()) { for (String string : data) { if (string.equals(username)) { throw new Exception("对不起,用户名已存在" ); } } } return false ; }
throws: 用于提醒isExist方法的调用者,需要处理异常,用于方法声明 throw: 当条件不满足时,主动抛出一个异常,返回一个具体的错误结果
异常体系
checked(编译)异常,runtime(运行)异常
RuntimeException和其子类属于运行异常,除了运行异常,其他都是编译异常。
运行异常在编译时期无法检查出来,如空指针异常、除数为0异常。可以避免。可以不用try-catch和throws处理运行异常。
程序员必须处理编译异常,使用try-catch或者throws处理异常
自定义异常类
两种方式:集成Exception或者RuntimeException类,一般推荐集成RuntimeException
需要提供一个无参构造器和一个带String类型参数的构造器
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 public class CustomerException extends RuntimeException { private Customer customer; public CustomerException () { } public CustomerException (String message, Customer customer) { super (message); this .customer = customer; } public Customer getCustomer () { return customer; } public void setCustomer (Customer customer) { this .customer = customer; } } public class Test { public static void someMistakes (String name) { throw new CustomerException("用户名已存在" , new Customer(name)); } public static void main (String[] args) { try { someMistakes("will" ); } catch (Exception e) { if (e instanceof CustomerException) { CustomerException exception = (CustomerException)e; System.out.println(exception.getMessage()); System.out.println(exception.getCustomer().name); } } } }
异常链
1 2 3 public HighAgeException (String message, Throwable cause) { super (message, cause); }
在catch到异常后,由于可能会不清楚该异常,则使用另一个异常描述类,重新编辑message,并将原有异常作为第二个参数构造新异常对象,并抛出。如此做,会将异常更加清晰的抛出。让调用者更加直观。
此种方式称为异常链。
多线程
运行一个简单的java程序的时候,存在2个线程:主线程和垃圾回收线程。
线程的创建和启动:
方式一:
自定义类继承Thread
覆写run方法
创建自定义类对象
自定义类对象调用start方法
1 2 3 4 5 6 7 8 9 10 11 12 class MyThread extends Thread { public void run () { } } public class ExceptionDemo { public static void main (String[] args) { MyThread t = new MyThread(); t.start(); } }
方式二:
通过实现Runnable接口
覆写run方法
创建自定义类对象
把自定类对象作为Thread类构造器参数,调用Thread对象start方法
1 2 3 4 5 6 7 8 9 10 11 12 13 class MyRunnable implements Runnable { public void run () { } } public class ThreadDemo2 { public static void main (String[] args) { MyRunnable target = new MyRunnable(); Thread t = new Thread(target); t.start(); } }
启动线程必须调用线程类Thread中的start方法,改方法由Thread类的一个实例来调用,底层会调用该线程的run方法 只有调用了start方法才会开启线程,单独调用run方法并不会开启线程
线程生命周期和状态
新建状态:new一个线程对象后,该线程对象处于新建状态,此时他和其他java对象并无不同。
可运行状态:RUNNABLE状态分为READY和RUNNING。分别表示就绪和可运行状态。
就绪状态:当线程对象调用start()方法后,该线程处于就绪状态,进入线程队列排队。何时运行取决于CPU调度器的调度
运行状态:线程对象被CPU调度器调度,执行线程体,就绪和运行状态会相互切换。
阻塞状态:正在运行的线程遇到特殊情况,同步、等待I/O操作完成等,进入阻塞状态,让出CPU资源,暂时停止自己的执行。
等待状态:一个可运行状态线程变成等待状态,它会等待另一个线程通知它转到可运行状态,才继续执行。
计时等待状态:进入等待状态的线程如果没有其他线程唤醒,此时考虑设计一个提示器,会在特定时间后唤醒该线程对象。
终止状态:死亡状态,表示线程终止,当线程成功执行完成或者抛出未捕获的Exception或者Error或者调用线程的stop方法(易导致死锁、不推荐)
操作线程的方法:
join方法,同步,可以使得线程之间的并行执行变成串行执行
A线程中调用了B线程的join()方法时,只有当B线程执行完毕时,A线程才能继续执行。
让正在执行的线程暂停一段时间,进入阻塞状态,常用来模拟网络延迟sleep(long milllis) throws InterruptedException; // 毫秒为单位
调用sleep()后,在指定时间段内,改线程不会获得执行机会
setPriority(int x)和getPriority()
优先级和线程的执行机会的次数多少有关,并不是优先级高的就一定先执行。调用方法设置优先级。
后台线程,一本用于为其他线程提供服务,也叫守护线程。JVM的垃圾回收就是典型的后台线程。 特点:若所有的前台线程都死亡,后台线程自动死亡。 Thread对象setDaemon(true)
来设置后台线程。 setDaemon(true)必须在start()调用前,否则抛出IllegalThreadStateException异常
多线程安全安全问题
继承方式:
Java类是单继承,如果继承了Thread类,就不能再有其他直接父类了
继承方式无法让多线程共享同一个资源
实现方式:
Java类可以实现多个接口,此时该类还可以继承其他类,还可以实现其他接口,设计上更优雅
多线程可以共享同一个资源
什么时候线程不安全:
多线程
共享同一个资源
非原子性操作
线程同步
在多线程访问同一资源的时候,由于一些复杂的操作,导致可能A未访问完全,B就可以拿到资源访问,使得A的一些后续操作作废,因此需要上锁,作用:A开始访问后上锁,B、C只能等待A完全访问后,才能获得资源访问权。
线程同步的两种方式:
同步代码块
同步锁,又称同步监听对象,同步监听器
资源在哪里就锁哪里,如果访问的资源在方法区就锁类,如果访问的资源在堆就锁对象
任何时候,最多允许一个线程拥有同步锁,谁拿到锁就执行,其他的线程只能在代码块外等着。
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 public class Person implements Runnable { private Integer appleNum = 50 ; public void run () { try { for (int i = 0 ; i < 50 ; i++) { synchronized (this ) { if (appleNum > 0 ) { Thread.sleep(10 ); System.out.println(Thread.currentThread().getName() + "吃了编号为:" + appleNum-- + "的苹果" ); } } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test { public static void main (String[] args) { Person p1 = new Person(); Thread t1 = new Thread(p1, "小A" ); Thread t2 = new Thread(p1, "小B" ); Thread t3 = new Thread(p1, "小C" ); t1.start(); t2.start(); t3.start(); } }
同步方法
使用synchronized修饰的方法,叫做同步方法,
1 2 3 synchronized public void doWork () { }
此时同步锁是谁:
对于非static方法,同步锁就是this
对于static方法,同步锁就是当前方法所在的类的字节码对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Person implements Runnable { private Integer appleNum = 50 ; synchronized public void run () { try { for (int i = 0 ; i < 50 ; i++) { if (appleNum > 0 ) { Thread.sleep(10 ); System.out.println(Thread.currentThread().getName() + "吃了编号为:" + appleNum-- + "的苹果" ); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
synchronized的优劣
好处:线程安全 缺点:使用sunchronized方法的性能要低一些
建议:尽量减小synchronized的作用域
集合
泛型
泛型方法:
1 public <T> List<T> parse (String url, Class<T> type)
public 与 返回值中间的 非常重要,可以理解为声明此方法为泛型方法。
只有声明了 的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
表明该方法将使用泛型类型 T ,此时才可以在方法中使用泛型类型 T
与泛型类一样, T 类型 E 元素 K 键 V 值
数组、可变数组
掌握ArrayList的源代码,动态缩容优化
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 package com.lizhaoloveit._集合._ArrayList2;@SuppressWarnings ("unchecked" )public class ArrayList <E > { private E[] elements; private static int size; private static final int DEFAULT_CAPACITY = 10 ; public static final int ELEMENT_NOT_FOUND = -1 ; public ArrayList () { elements = (E[]) new Object[DEFAULT_CAPACITY]; } public ArrayList (int capacity) { if (capacity < DEFAULT_CAPACITY) { capacity = DEFAULT_CAPACITY; } elements = (E[]) new Object[capacity]; } public void clear () { size = 0 ; } public boolean isEmpty () { return size == 0 ; } public boolean contains (E element) { return indexOf(element) != ELEMENT_NOT_FOUND; } public int indexOf (E element) { if (element == null ) { for (int i = 0 ; i < size; i++) { if (elements[i] == null ) return i; } } for (int i = 0 ; i < size; i++) { if (elements[i].equals(element)) return i; } return ELEMENT_NOT_FOUND; } public void add (E element) { add(size, element); } public void add (int index, E element) { checkRangeForAdd(index); ensureCapacity(size + 1 ); for (int i = size; i >= index; i--) { elements[i] = elements[i - 1 ]; } elements[index] = element; } public E get (int index) { checkRange(index); return elements[index]; } public E set (int index, E element) { checkRange(index); E old = elements[index]; elements[index] = element; return old; } public E remove (int index) { checkRange(index); E old = elements[index]; for (int i = index; i < size - 1 ; i++) { elements[i] = elements[i + 1 ]; } elements[size - 1 ] = null ; size--; return old; } public E remove (int index) { checkRange(index); E oldValue = elementData(index); int numMoved = size - index - 1 ; if (numMoved > 0 ) System.arraycopy(elementData, index+1 , elementData, index, numMoved); elementData[--size] = null ; return oldValue; } public int remove (E element) { int index = indexOf(element); remove(index); return index; } private void ensureCapacity (int capacity) { int oldCapacity = elements.length; if (oldCapacity > capacity) return ; int newCapacity = oldCapacity + oldCapacity >> 1 ; E[] newElements = (E[])new Object[newCapacity]; for (int i = 0 ; i < oldCapacity; i++) { newElements[i] = elements[i]; } elements = newElements; System.out.println("旧数组容量:" + oldCapacity + ",变为新数组容量:" + newCapacity); } private void checkRange (int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", size:" + size); } } private void checkRangeForAdd (int index) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException("Index: " + index + ", size:" + size); } } public String toString () { StringBuilder sb = new StringBuilder(); sb.append("size=" ).append(size).append(", [" ); for (int i = 0 ; i < size; i++) { if (i != 0 ) { sb.append(", " ); } sb.append(elements[i]); } sb.append("]" ); return sb.toString(); } }
栈
栈结构仅允许在表的一端进行插入和删除操作,这一端被称为栈顶。
栈底、入栈、出栈、
哈希表
数组中的元素在数组中的索引位置是随机的,元素取值和元素的位置之间不存在确定的对应关系,在数组中查找特定的值时,需要把查找值和一系列元素比较。
查询效率取决于查找过程中的比较次数,比较次数越多,效率越低。
如果元素和索引之间存在对应关系,则查找效率会非常高。index = hash(value);
通过给定元素值,调用hash(value)方法就能找到数组中value的位置 index
这种关系叫做hash,往哈希表中存储对象的时候,该hash算法就是对象的hashCode方法。
树和二叉树(还需学习)
!还需学习!
集合框架体系
Set 集合 不允许重复元素 继承Collection
List 列表 允许重复元素 继承Collection
Map 映射 value可以重复,Key不行 接口
List接口
List接口规范:要求该容器允许记录元素的添加顺序,也允许元素重复
ArrayList:数组列表,
LinkedList:链表,表示双向列表和双向队列结构,采用链表实现,使用不多
Stack类:栈,栈结构,数组实现
Vector类:向量,采用数组实现,使用不多
List常用API:
添加操作:
boolean add(Object e)
将元素添加到列表末尾
void add(int index, Object element)
在列表的index位置插入指定元素
boolean addAll(Collection c)
把C列表中的所有元素添加到当前列表中
删除操作:
Object remove(int index)
从列表中删除指定索引位置的元素,并返回被删除的元素
boolean removeAll(Collection c)
从列表中移除C列表中的所有元素
修改操作:
Object set(int index, Object element)
修改列表中指定索引位置的元素,返回被替换的旧元素
查询操作:
int size()
返回当前列表中的元素个数boolean is Empty()
判断当前列表中元素个数是否为0Object get(int index)
查询列表中指定索引位置对应的元素Object[] toArray()
把列表对象转换为Object数组boolean contains(Object o)
判断列表是否存在指定对象
集合元素迭代
集合元素遍历。
并发修改异常(ConcurrentModificationException)
当遍历集合时,删除某集合元素会触发该异常。如果要遍历时删除集合元素,必须使用迭代器。
实现Interface Iterator<E>
接口的类可以调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void main (String[] args) { List<String> list = new ArrayList<String>(); list.add("1" ); list.add("2" ); list.add("3" ); list.add("4" ); list.add("5" ); Iterator<String> it = list.iterator(); while (it.hasNext()) { String ele = it.next(); if (ele.equals("1" )) { list.remove(ele); } } }
Set接口
Set是 Collection 子接口,Set接口定义了一种规范,该容器不记录元素的添加顺序,不允许元素重复。
常用实现类:
HashSet类:底层采用哈希表实现,开发中使用最多的实现类。
TreeSet类:底层采用红黑树实现,可以对集合中的元素排序,使用不多。
HashSet
详见HashMap实现原理
底层采用哈希表实现,元素对象的 hashCode值决定了在哈希表中的存储位置。
当,往 HashSet 中添加新元素时,会先判断该对象的 hashCode 和 集合对象中的 hashCode 值进行比较。
相等:再继续判断新对象和集合对象中的 equals 比较
true 视为同一个对象,则不保存。
false 存储之前对象同槽位的链表上。
不等:直接把该对象存储到 hashCode 指定的位置。
哈希表中元素对象的 hashCode 和 equals 方法很重要
每一个存储到哈希表中的对象,都得覆盖 hashCode
和 equals
方法来判断是否是同一对象,Eclipse可以根据对象哪些字段做比较而自动生成 hashCode
和 equals
方法。
一般的,equals 为 true 的时候 hashCode 也应该相等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Set<String> set = new HashSet<String>(); set.add("1" ); set.add("2" ); set.add("3" ); set.add("4" ); set.add("5" ); set.size(); set.contains("3" ); set.remove("1" ); for (String string : set) { System.out.println(string); } Iterator<String> it = set.iterator(); while (it.hasNext()) { String ele = it.next(); System.out.println(ele); } Integer[] i = new Integer[] {1 , 2 , 3 }; List<Integer> list1 = Arrays.asList(i);
TreeSet
TreeSet 除了支持实现接口Comparable
的类排序以外,还可以自定义排序器
排序器类需要实现接口 Comparator<T>
1 2 3 public interface Comparator <T > { int compare (T o1, T o2) ; }
compare返回0表示两个对象为同一个对象,返回正数排前面, 返回负数排后面
1 2 3 4 5 6 7 class NameLengthComparator implements java .util .Comparator <User > {} public static void main (String[] args) { Set<User> set = new TreeSet<>(new NameLengthComparator()); }
HashSet 等值查询效率高, TreeSet 做范围查询效率高。
Map 映射
Map常用的API方法:
添加操作:
boolean put(Object key, Object value)
存储一个键值对
boolean putAll(Map m)
把 m 中的所有键值对添加到当前Map中
删除操作:
Object remove(Object key)
从Map中删除指定key的键值对,并返回被删除的key对应的value
修改操作:
同添加操作,使用相同的key 不同的value即可
查询操作:
int size()
返回当前Map中键值对个数
boolean isEmpty()
判断当前Map中键值对个数是否为0
Object get(Object key)
返回Map中指定key对应的value值,如果不存在该key,返回null
boolean containsKey(Object key)
判断Map中是否包含该Key
boolean containsValue(Object value)
判断Map中是否包含指定value
Set keySet()
返回Map中所有key所组成的 Set集合
Collection values()
返回Map中所有value所组成的Collection集合
Set<Entry>entrySet()
返回Map中所有键值对所组成的Set集合
HashMap
HashMap底层基于哈希表算法,Map中存储的key对象的hashCode值决定了哈希表中的存储位置,因为Map中的key是Set,
map迭代
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 46 47 48 49 50 import java.util.*;import java.util.Map.Entry;public class Test { public static void main (String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("g1" , "王昭君" ); map.put("g2" , "西施" ); map.put("g3" , "貂蝉" ); map.put("g4" , "杨玉环" ); System.out.println("map中有多少键值对:" + map.size()); System.out.println("是否包含调查哦:" + map.containsValue("貂蝉" )); map.put("g4" , "小乔" ); map.remove("g4" ); Set<String> keys = map.keySet(); Collection<String> values = map.values(); Set<Entry<String, String>> entrys = map.entrySet(); for (Entry<String, String> entry : entrys) { String key = entry.getKey(); String value = entry.getValue(); System.out.println("key:" + key + "--->value:" + value); } String str = "ASWQSASDAWQEAASGVDZGDFGFDBCBVCNGHFIUYTGFBFDGDDFS" ; Map<Character, Integer> map2 = new HashMap<Character, Integer>(); for (int i = 0 ; i < str.length(); i++) { Character key = str.charAt(i); if (map2.containsKey(key)) { map2.put(key, map2.get(key) + 1 ); } else { map2.put(key, 1 ); } } Set<Entry<Character, Integer>> entrys2 = map2.entrySet(); for (Entry<Character, Integer> entry : entrys2) { Character key = entry.getKey(); Integer value = entry.getValue(); System.out.println("key:" + key + "--->value:" + value); } } }
TreeMap
TreeMap底层基于红黑树算法,key是Set,会对存储的key进行排序,和TreeSet一样
1 2 3 4 5 6 7 8 9 10 11 12 public class App { public static void main (String[] args) { Map<String, Stirng> map = new HashMap<>(); map.put("girl4" , "杨玉环" ); map.put("girl3" , "王昭君" ); map.put("girl2" , "西施" ); map.put("girl1" , "貂蝉" ); map = new TreeMap<>(map); System.out.println(map); } }
集合框架工具类
Arrays类是数据工具类,有一个方法比较常用
public static <T> List <T> asList(T... a)
该方法可以把一个Object数组转换为List集合
1 2 3 4 5 6 public ArraysDemo { public static void main (String[] args) { List<Integer> list1 = Arrays.asList(1 , 2 , 3 ); } }
通过Arrays.asList 方法得到的List对象的长度是固定的。
Collections 是集合的工具类,封装了 Set、List、Map操作的工具方法,比如拷贝、排序、搜索、比较大小等。
小结
I/O
File类
文件和文件夹目录,表示磁盘中某个文件和文件夹的路径,用于文件的创建、删除、重命名、判断是否存在等方法。
Unix:严格区分大小写,使用/
来表示路径分隔符
Windows:默认情况下 不区分大小写,使用\
分割目录路径,java中一个\
表示转义,所以Windows系统中需要使用两个\\
。
常用方法:
String getName()
获取文件名称
String getPath()
获取文件路径
String getAbsolutePath()
获取文件的绝对路径
File getParentFile()
获取上级目录文件
boolean exists()
判断文件是否存在
boolean isFile()
是否是文件
boolean isDirectory()
判断是否是目录
boolean delete()
删除文件
boolean mkdirs()
创建当前目录和上级目录
File[] listFiles()
列出所有文件对象
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 import java.io.File;public class FileDemo { public static void main (String[] args) { File f = new File("/Users/Ammar/Desktop/sddd/123.txt" ); System.out.println(f.getName()); System.out.println(f.getPath()); System.out.println(f.getAbsolutePath()); System.out.println(f.getParentFile().getName()); System.out.println(f.exists()); System.out.println(f.isFile()); System.out.println(f.isDirectory()); if (!f.getParentFile().exists()) { f.getParentFile().mkdirs(); } File[] fs = f.getParentFile().getParentFile().listFiles(); for (File file : fs) { System.out.println(file); } list(f.getParentFile().getParentFile()); } public static void list (File file) { if (file.isDirectory()) { File[] files = file.listFiles(); if (files != null ) { for (File file2 : files) { list(file2); } } } System.out.println(file); } }
字符编码
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 import java.io.UnsupportedEncodingException;import java.util.Arrays;public class CodeDemo { public static void main (String[] args) throws UnsupportedEncodingException { String ISO = "ISO-8859-1" ; String utf = "UTF-8" ; String name = "李朝" ; byte [] data = name.getBytes(utf); System.out.println(Arrays.toString(data)); String result = new String(data, ISO); System.out.println(result); data = result.getBytes(ISO); System.out.println(Arrays.toString(data)); result = new String(data, utf); System.out.println(result); } }
注意:GBK 和 utf-8 编码,本身都有对第一个字节的头几个位有要求,必须是1,中文是3个字节,因此必须是 111xxxxx ,因此,有规范的不同,导致 GBK 和 utf-8 无法互相无损转换。
IO流操作
读取数据:文件——->程序, 输入操作
保存数据:程序——->文件, 输出操作
按流动方向:输入流和输出流
按数据传输单位:字节流和字符流,每次传递一个字节 byte 或 一个字符 char
按功能上划分:分为节点流和处理流,节点流功能单一,处理流功能更强
输入操作:read 输出操作:write
流向
字节流(单位是字节)
字符流(单位是字符)
输入流
InputStream
Reader
输出流
OutputStream
Writer
操作UI流的模板:
创建源或者目标对象(挖井)
输入操作:把文件中的数据流向到程序中,此文件是源,程序是目标
输出操作:把程序中的数据流向到文件中,此文件是目标,程序是源
创建IO流对象(水管)
输入操作:创建输入流对象
输出操作:创建输出流对象
具体的IO操作
输入操作:输入流对象的read方法
输出操作:输出流对象的write方法
关闭资源(勿忘),一旦资源关闭之后,就不能使用流对象了。否则报错
输入操作:输入流对象.close()
输出操作:输出流对象.close()
四大抽象流是不能创建对象的,根据不同的需求创建他们不同的子类对象。比如操作文件时,使用文件流 不管什么流,操作完毕必须调用 close
方法,释放资源
FileOutputStream
继承 OutputStream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class WriteDemo { public static void main (String[] args) throws IOException { File dest = new File("/Users/Ammar/Desktop/sddd/123.txt" ); FileOutputStream out = new FileOutputStream(dest); out.write(65 ); out.write(66 ); out.write(67 ); String str = "李朝" ; out.write(str.getBytes("UTF-8" )); out.close(); } }
继承 InputStream
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 class ReadDemo { public static void main (String[] args) throws Exception { File src = new File("/Users/Ammar/Desktop/sddd/123.txt" ); FileInputStream input = new FileInputStream(src); System.out.println((char )input.read()); System.out.println((char )input.read()); System.out.println((char )input.read()); while (input.read() != -1 ) { System.out.print(input.read()); } byte [] buff = new byte [5 ]; int len = input.read(buff); System.out.println(Arrays.toString(buff)); System.out.println(len); input.close(); } }
FileWriter
继承 OutputStreamWriter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class FileWriterDemo { public static void main (String[] args) throws Exception { File file = new File("/Users/Ammar/Desktop/sddd/123.txt" ); FileWriter fw = new FileWriter(file); fw.write('辛' ); fw.write('弃' ); fw.write('疾' ); String str = "众里寻他千百度,蓦然回首,那人却在灯火阑珊处" ; fw.write(str); fw.close(); } }
FileReader
继承 InputStreamReader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class FileReaderDemo { public static void main (String[] args) throws Exception { File file = new File("/Users/Ammar/Desktop/sddd/123.txt" ); FileReader fr = new FileReader(file); while (fr.read() != -1 ) { System.out.println((char )fr.read()); } fr.close(); } }
记事本打开某个文件,如果可以看到内容就是文本文件,否则可以暂时认为是二进制文件
操作二进制文件(图片、音频、视频) 必须使用字节流,操作文本文件使用字符流,尤其是操作带有中文的文件,使用字符流不容易导致乱码,如果不清楚属于哪一类型文件,都可以使用字节流。
需求:文件拷贝
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 46 47 public class Test { public static void copy () { File src = new File("/Users/Ammar/Desktop/sddd/copy_before.txt" ); File des = new File("/Users/Ammar/Desktop/sddd/copy_after.txt" ); FileReader in = null ; FileWriter out = null ; try { in = new FileReader(src); out = new FileWriter(des); int length = -1 ; char [] buff = new char [1024 ]; length = in.read(buff); while (length > 0 ) { out.write(buff, 0 , length); length = in.read(buff); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null ) { out.close(); } } catch (Exception e2) { e2.printStackTrace(); } finally { try { if (in != null ) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } } } public static void main (String[] args) { copy(); } }
缓冲流
new 流类A(new 流类B)
四大基流都有各自的包装流
BufferedInputStream,BufferedOutputStream,BufferedReader, BufferedWriter
非常重要的包装流——缓冲流
节点流的功能都比较单一,且性能较低,处理流,也称为包装流—装饰实际模式,详见[二十六种设计模式]
缓冲流内置了一个默认大小为8192字节或者字符的缓存区,缓冲区的作用用来减少磁盘的IO操作,拿字节缓冲流举例,比如一次性读取8192个字节到内存中,或者存满8192个字节再输出到磁盘中。
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 46 package com.lizhaoloveit._IO._缓冲流;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;public class Test { public static void main (String[] args) { File src = new File("/Users/Ammar/Desktop/sddd/copy_before.mp4" ); File des = new File("/Users/Ammar/Desktop/sddd/copy_after.mp4" ); BufferedInputStream in = null ; BufferedOutputStream out = null ; try { in = new BufferedInputStream(new FileInputStream(src), 8192 ); out = new BufferedOutputStream(new FileOutputStream(des), 8192 ); int len = -1 ; byte [] buff = new byte [1024 ]; len = in.read(buff); while (len > 0 ) { out.write(buff, 0 , len); len = in.read(buff); } } catch (Exception e) { e.printStackTrace(); } finally { try { in.close(); } catch (Exception e) { e.printStackTrace(); } try { out.close(); } catch (Exception e) { e.printStackTrace(); } } } }
对象序列化
序列化,把Java堆内存中的对象数据,通过某种方式把兑现该数据存储到磁盘文件中。序列化在分布式系统中应用非常广泛
反序列化,把磁盘文件中的对象的数据或者把网络节点上的对象数据恢复成Java对象的过程
需要做序列化的类必须实现序列化接口:java.io.Serializable
可以通过IO中的对象流来做序列化和反序列化操作。
ObjectOutputStream:通过writeObject方法做序列化操作 ObjectInputStream:通过readObject方法做反序列化操作
如果字段使用 transient 修饰则不会被序列化
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 46 47 48 package com.lizhaoloveit._IO._serializable;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Test { public static void main (String[] args) { File file = new File("/Users/Ammar/Desktop/sddd/obj.txt" ); ObjectOutputStream out = null ; try { out = new ObjectOutputStream(new FileOutputStream(file)); User u = new User("Will" , "1111" , 17 ); out.writeObject(u); } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null ) { out.close(); } } catch (Exception e) { e.printStackTrace(); } } ObjectInputStream in = null ; try { in = new ObjectInputStream(new FileInputStream(file)); Object obj = in.readObject(); System.out.println(obj); } catch (Exception e) { e.printStackTrace(); } finally { try { if (in != null ) { in.close(); } } catch (Exception e) { e.printStackTrace(); } } } }
序列化版本问题
当类实现 Serializable 接口后,编译时会根据字段生成一个缺省的 serialVersionUID值,并在序列化操作时,写到序列化数据文件中。
随着项目升级系统的class文件也会升级,此时从新编译,serialVersionUID值又会改变,所以在反序列化时,JVM会把对象数据中的 serialVersionUID 与本地字节码中的 serialVersionUID 进行比较,如果不同,意味着版本不同,会报异常 InvalidCastException。 类版本不对应,不能反序列化。如果版本号相同 则可以反序列化。
图
为了避免代码版本升级造成反序列化不兼容,开发中可以故意在类中提供一个固定的 serialVersionUID值。
打印流
打印流是一种特殊的输出流,可以输出任意类型的数据,可以作为处理流包装一个平台的节点流使用
PrintStream 字节打印流
PrintWriter 字符打印流
print方法,打印不换行,
println方法,打印后换行。
标准IO
通过键盘输入 称为标准输入
通过屏幕上显示程序数据称为 标准输出
1 2 3 4 5 6 7 public static void main (String[] args) throws Exception { Scanner sc = new Scanner(System.in); while (sc.hasNextLine()) { String line = sc.nextLine(); System.out.println("ECHO:" + line); } }
综合练习:
输出文件中的代码行数:
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 46 47 48 49 50 51 52 53 54 55 public static int lineCountOfFilePath (String filePath) { File file = new File(filePath); int count = 0 ; if (file.isDirectory()) { File[] fileList = file.listFiles(); for (File f : fileList) { count += lineCountOfFilePath(f.getAbsolutePath()); } } else { if (file.getName().contains(".java" )) { Integer fileCount = lineCountOfFile(filePath); if (fileCount != null ) { try { Thread.sleep(15 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("File:" + file.getAbsolutePath() + ", " + fileCount + "行。" ); count += fileCount; } } } return count; } public static Integer lineCountOfFile (String filePath) { File file = new File(filePath); BufferedReader in = null ; int count = 0 ; try { in = new BufferedReader(new FileReader(file)); while (in.readLine() != null ) { count++; } } catch (Exception e) { e.printStackTrace(); return null ; } finally { try { if (in != null ) { in.close(); } } catch (Exception e) { e.printStackTrace(); return null ; } } return count; }
AutoClose
java 1.7 以后的新语法,实现 AutoCloseAble 接口的流对象都可使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test { public static void main (String[] args) { File file = new File("/Users/Ammar/Desktop/sddd/copy_before.txt" ); try (FileReader in = new FileReader(file); FileWriter out = new FileWriter(file);) { int length = -1 ; char [] buff = new char [1024 ]; length = in.read(buff); while (length > 0 ) { out.write(buff, 0 , length); length = in.read(buff); } } catch (Exception e) { e.printStackTrace(); } } }
动态编程和可配置文件
使用场景:一个类的成员变量是可变的,不能写死一个类。(硬编码:写死的代码,需要经常改动。)
解决方案,使用一个 a.txt ,其内容为 username=zhangsan1password=123
,这个文件就是可配置文件。
配置文件可以解决硬编码。应用场景:框架中大量使用。
配置文件
习惯使用两种配置文件:
properties 文件 / 属性文件 / 资源文件 / 配置文件
以 properties 作为文件名的后缀名,可以存储 key = value 结构的简单数据。
XML 文件,树状结构,存储复杂数据
1 2 3 4 <user> <user> </user> </user>
properties 文件的数据存储
基本语法:
配置文件需要跟随字节码,放在 resource 文件夹中,
配置文件夹中,所有的数据都是字符串类型,而且不需要引号
配置文件中,不适用空格
使用#表示注释内容 存储数据格式key=value 不用空格,多个属性另起一行
properties 文件解析
properties 是 Map 的实现类,Properties 文件比较特殊,使用 Properties 新增的方法
Source Folder 名字就叫 resources
1 2 3 4 public void load (InputStream inStream) public String getProperty (String key)
使用相对路径加载资源文件 ,相对于字节码的输出路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testGetUser () throws Exception { Properties ps = new Properties(); Thread t = Thread.currentThread(); ClassLoader loader = t.getContextClassLoader(); InputStream in = loader.getResourceAsStream("user.properties" ); ps.load(in); String username = ps.getProperty("username" ); String password = ps.getProperty("password" ); }
使用配置文件创建对象
1 className=cn.lizhaoloveit.ps.Student
1 2 3 4 5 6 7 8 9 @Test public void testSleep () throws Exception { InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("person.properties" ); Properties ps = new Properties(); ps.load(in); Person p = (Person) Class.forName(ps.getProperty("className" ).newInstance(); p.sleep(); }
反射的优势:
提高程序灵活性和扩展性
降低耦合性,提高自适应能力
允许程序创建和控制任何类,无需提前编码目标类
缺点:
性能:使用反射基本上是一种运行期间解析字节码操作,效率较低,一般程序不建议使用,对灵活性和拓展性要求较高的工具和框架上使用较多
代码复杂性:反射在运行时,程序员无法再源代码中看到程序的逻辑,反射代码比响应的直接代码更复杂,会带来维护的问题
反射是框架的基石。
XML概述
XML(eXtensible Markup Language),一种可扩展的标记语言,用于传输数据。XML是一种通用的数据交换格式,很多系统配置文件都是 XML 任意一个javaEE框架中都要用到 XML
XML 语法:
只能有一个根标签
XML 文档结构
把 XML 文档加载到内存中,使用 Document 对象来描述整个文档
所有的标签,使用 Element 描述
标签的属性,使用 Attr 来描述
其他文本内容 使用 Text 描述
所有的类有一个抽象的类 Node,在 XML 中一切皆节点
XML 约束 XML:可拓展的标记语言,可以自定义标签。
约束 XML:dtd 约束, schema 约束
dtd 约束、schema 约束 会告诉我们只能写哪些标签,哪些属性。
运用方式为:正则匹配
DOM树(Document Object Model)
基于 DOM 的 XML 分析器将一个 XML 文档转换成一个对象模型的集合,称为 DOM 树。
优点:树状结构,有助于理解,代码更容易编写,解析过程中,树状结构保存在内存中,方便修改 缺点:一次性读取,内存耗费较大,容易内存溢出
获取DOM对象的步骤和过程
获取 XML 文件
1 2 3 4 5 @Test public void testGetXML () throws Exception { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file); System.out.println(doc);
创建 XML 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 File file = new File("day02/contacts.xml" ); @Test public void testCreateNewDocument () throws Exception { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); Element root = doc.createElement("contacts" ); doc.appendChild(root); Transformer trans = TransformerFactory.newInstance().newTransformer(); trans.setOutputProperty(OutputKeys.INDENT, "yes" ); trans.transform(new DOMSource(doc), new StreamResult(file)); }
新增联系人信息
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 @Test public void testInsertNewElement () throws Exception { Linkman man = new Linkman("2" , "李朝" , "18" , "广州" , "java" , "lacuself@126.com" ); Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file); Element root = doc.getDocumentElement(); Element linkmanE = doc.createElement("linkman" ); Element nameE = doc.createElement("name" ); Element ageE = doc.createElement("age" ); Element addressE = doc.createElement("address" ); Element courseE = doc.createElement("course" ); Element emailE = doc.createElement("email" ); linkmanE.setAttribute("id" , man.getId()); nameE.setTextContent(man.getName()); ageE.setTextContent(man.getAge()); addressE.setTextContent(man.getAddress()); courseE.setTextContent(man.getCourse()); emailE.setTextContent(man.getEmail()); linkmanE.appendChild(nameE); linkmanE.appendChild(ageE); linkmanE.appendChild(addressE); linkmanE.appendChild(courseE); linkmanE.appendChild(emailE); root.appendChild(linkmanE); Transformer transformer = TransformerFactory.newInstance().newTransformer(); Source xmlSource = new DOMSource(doc); Result outputTarget = new StreamResult(file); transformer.transform(xmlSource, outputTarget); }
修改 XML 文件中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testSetElement () throws Exception { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file); Element root = doc.getDocumentElement(); Element linkmanE = (Element) root.getElementsByTagName("linkman" ).item(0 ); Element addressE = (Element) linkmanE.getElementsByTagName("address" ).item(0 ); addressE.setTextContent("北京" ); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(new DOMSource(doc), new StreamResult(file)); }
删除 XML 文件中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testRemoveElement () throws Exception { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file); Element root = doc.getDocumentElement(); Element linkmanE = (Element) root.getElementsByTagName("linkman" ).item(0 ); linkmanE.getParentNode().removeChild(linkmanE); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(new DOMSource(doc), new StreamResult(file)); }
查询 XML 中的节点和值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testGetXML () throws Exception { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file); Element root = doc.getDocumentElement(); NodeList linkmanList = root.getElementsByTagName("linkman" ); for (int i = 0 ; i < linkmanList.getLength(); i++) { System.out.println(linkmanList.item(i)); } Element ele = (Element) linkmanList.item(0 ); Element nameE = (Element) ele.getElementsByTagName("name" ).item(0 ); String name = nameE.getTextContent(); System.out.println(name); }