目录
- 1 常用类
- 2 异常
- 3 集合
- 4 多线程
- 5 IO流
- 6. Socket
- 7. NIO
- 8 反射,枚举,注解
- 9 JDK8 新特性
- 10 JVM,GC
1 常用类
1.1 内部类
1.1.1 概念
在类的内部在定义一个完整的类。编译之后可生成独立的字节码文件。内部类可以直接访问外部类的私有成员。可以为外部类提供必要的功能组件
1.1.2 成员内部类
-
在类的内部定义,与实例变量,实例方法同级别。类和内部类中有同名变量时。优先使用内部类变量。
-
成员内部类可以使用任意访问修饰符,而外部类只能使用public或者默认
-
成员内部类可以直接访问外部类的属性和方法
-
当内部类的属性和外部类的属性同名时,使用外部类.this访问外部类的属性
-
成员内部类中不能声明静态成员,但是可以包含常量。(静态成员可以直接通过类名去调用。而成员内部类要通过外部类创建对象去调用内部类,而此时还没有外部内对象)
Outer类
public class Outer {
private String name = "黄铭";
private int age = 21;
public void show() {
System.out.println("姓名:" + this.name + ";年龄" + this.age);
}
class Inner{
private String dept = "信息与计算科学";
private int sno = 1700080216;
public void show(){
System.out.println("专业:" + this.dept + ";学号" + this.sno );
}
public void print(){
System.out.println("姓名:" + Outer.this.name + ";年龄" + Outer.this.age);
System.out.println("专业:" + this.dept + ";学号" + this.sno);
}
}
}
Test类
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
outer.show();
System.out.println("-----第一种创建内部类-----");
Outer.Inner inner = outer.new Inner();
inner.show();
inner.print();
System.out.println("-----第二种创建内部类-----");
Outer.Inner inner1 = new Outer().new Inner();
inner.show();
inner.print();
}
}
1.1.3 静态内部类
-
级别和外部类一样,为外部类提供功能
-
静态内部类可以使用任意访问修饰符
-
不能直接访问外部类的实例属性和方法,必须创建外部类对象去访问外部类的实例属性和方法。可以访问静态的属性和方法。
-
静态内部类可以包含静态成员
Outer类
public class Outer {
private String name = "黄铭";
private int age = 21;
private static String course = "java开发";
public void show() {
System.out.println("姓名:" + this.name + ";年龄" + this.age);
}
static class Inner{
private String dept = "信息与计算科学";
private int sno = 1700080216;
private static double score = 90;
Outer outer = new Outer();
public void print(){
System.out.println("姓名:" + outer.name + ";年龄" + outer.age);
System.out.println("专业:" + this.dept + ";学号:" + this.sno + ";课程:" + Outer.course + ";成绩:" + Inner.score);
}
}
}
Test类
public class Test {
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.print();
inner.outer.show();
}
}
结果:
姓名:黄铭;年龄21
专业:信息与计算科学;学号:1700080216;课程:java开发;成绩:90.0
姓名:黄铭;年龄21
1.1.4 局部内部类
- 在方法内部定义局部内部类
- 不能使用任何访问修饰符
- 如果局部内部类所在方法是非静态方法,可以直接访问外部类的实例属性和方法
- 如果局部内部类所在方法是静态方法,可以直接访问外部类的静态属性和方法
- 局部内部类可以访问当前外部方法的局部变量,但是变量必须是final,JDK8后final可以省略
- 不能声明静态成员,但是可以声明静态常量
Outer类
public class Outer {
private String name = "黄铭";
private int age = 21;
private static int count = 1;
public void show(){
String dept = "信息与计算科学";
System.out.println(name + "...." + age);
class Inner{
private int sno = 1700080216;
public void print(){
System.out.println(Outer.this.name + "...." + Outer.this.age);
System.out.println(dept + "..." + this.sno);
}
}
Inner inner = new Inner();
inner.print();
}
public static void Print(){
String dept = "信息与计算科学";
class Inner{
private int sno = 1700080216;
public void print(){
System.out.println(dept + "..." + this.sno + "..." + Outer.count);
}
}
Inner inner = new Inner();
inner.print();
}
}
Test类
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
outer.show();
Outer.Print();
}
}
答案
黄铭....21
黄铭....21
信息与计算科学...1700080216
信息与计算科学...1700080216...1
1.1.4 匿名内部类
- 创建匿名内部类可以使用接口,抽象类,普通类,必须实现接口或抽象类中的抽象方法
- 不能手动添加构造方法,不能包含静态成员。
- 匿名内部类中一般不包含特有的方法,不能直接访问,可以通过内部对象方法。
- 生成的class文件所在的类名$编号.class
USB接口
public interface USB {
void service();
}
Test
public class Test {
public static void main(String[] args) {
class Fan implements USB{
@Override
public void service() {
System.out.println("连接成功,风扇开始工作");
}
}
Fan fan = new Fan();
fan.service();
new USB(){
@Override
public void service() {
System.out.println("连接成功,开始工作");
}
}.service();
USB upan = () -> System.out.println("连接成功,upan开始工作");
upan.service();
}
}
1.2 Object类
1.2.1 getClass
System.out.println(student1.getClass() == student2.getClass());
//获得对象的类型,判断类型是否一致
System.out.println(student1.getClass().getName());
//获得带包类名
System.out.println(student1.getClass().getSimpleName());
//获得对象类名
System.out.println(student1.getClass().getPackage().getName());
//获得包名
1.2.2 hashCode
//返回16进制hash码
public int hashCode() {
return sno + age + name.hashCode();
}
1.2.3 toString
//自定义打印的格式
public String toString() {
return "[sno:" + sno + ";name:" + name + ";age:" + age + "]";
}
1.2.4 equals
public boolean equals(Object obj) {
//比较两个引用是否指向同一对象
if (obj == this)
return true;
//判断obj是否为空或者两个引用的所指的类型是否相同
if (obj == null || obj.getClass() != this.getClass())
return false;
Student s = (Student) obj;
//比较两个对象的属性值
return s.getSno() == this.sno && s.getName().equals(this.name) && s.getAge() == this.age;
}
1.2.5 finalize
- 垃圾回收机制,当对象不再被有效引用时,会被垃圾回收机制回收
1.3 包装类
- 当包装类和基本类型比较的时候,包装类会进行拆箱后在比较
- 当包装类的值在缓冲数组的范围时,包装类相等。缓冲数组范围[-128,127]
- 装箱是
Integer.valueof(int i)
,拆箱是Integer.intvalue.
,其他的基本类型也是这样 - 基本类型可以转字符串
toString
,也可以由字符串转为基本类型Integer.parseInt()
public class Test {
public static void main(String[] args) {
Integer integer = new Integer(100);
Integer integer1 = new Integer(100);
Integer integer2 = 100;
Integer integer3 = 100;
int i = 100;
Integer integer4 = 200;
Integer integer5 = 200;
System.out.println(integer == integer1);//false
System.out.println(integer2 == integer);//false
System.out.println(integer2 == integer3);//true
System.out.println(i == integer);//true
System.out.println(i == integer2);//true
System.out.println(integer4 == integer5);//false
}
}
1.4 String
- 直接赋值,在常量池创建对象。用
new String()
则在堆和常量池都创建对象
public class Test {
public static void main(String[] args) {
String name = "黄铭";
//name = "小芳";
String name2= new String("黄铭");
System.out.println(name == name2);//false
System.out.println(name.equals(name2));//true
}
}
charAt(int i)
取出字符串的第i个值contains(String str)
判断字符串是否存在str
toCharArray()
将字符串转换为字符数组indexOf(String str)
找到str
在字符串第一次出现的下标trim()
去掉字符串前后的空格replace(String target,String replacement)
将字符串的target
转换为replacement
split(String regex)
将字符串以regex
拆分成多个字符串数组substring(int start,int end)
截取字符串,截头不截尾
public class Test1 {
public static void main(String[] args) {
String str = "我爱java,java是世界上最好的语言!";
System.out.println(str.charAt(0));//我
System.out.println(str.contains("java"));//true
System.out.println(Arrays.toString(str.toCharArray()));
//[我, 爱, j, a, v, a, ,, j, a, v, a, 是, 世, 界, 上, 最, 好, 的, 语, 言, !]
System.out.println(str.indexOf("java"));//2
String str2 = " 我爱java,java是世界上最好的语言! ";
System.out.println(str2);
System.out.println(str2.trim());//去掉前后的空格
System.out.println(str.endsWith("!"));//true
System.out.println(str.replace("java", "JAVA"));//替换
String[] s = str.split("java");//以什么拆分字符串
for (String s1 : s) {
System.out.println(s1);
}
System.out.println(str.substring(0, 6));//截取,包含头,不包含尾
String name1 = "abc";
String naem2 = "abcd";
}
}
/*
结果:
我
true
[我, 爱, j, a, v, a, ,, j, a, v, a, 是, 世, 界, 上, 最, 好, 的, 语, 言, !]
2
我爱java,java是世界上最好的语言!
我爱java,java是世界上最好的语言!
true
我爱JAVA,JAVA是世界上最好的语言!
我爱
,
是世界上最好的语言!
我爱java
*/
字符串s1
直接赋值abc
,会在字符串常量池找是否存在此字符串常量abc
,若存在则把这个字符串常量abc
的地址值传给s1
。若不存在,则在常量池创建一个新的字符串abc
,在把abc
的地址值传给s1
。
s3
是两个变量的值相加,反编译可知,这是先进行new StringBuild()
的操作,然后进行两次apend
操作,所以s3
存在于堆中。
s5 = s3.intern()
,会先在字符串常量池中找是否存在有.equals(s3) == true
的值,如有,则把常量池的地址值传s5
;若不存在,则把堆中的s3
的地址值在字符串常量池中复制一份,然后把此地址值传递给s5
.
public class Test3 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = s1 + s2;
String s4 = "ab";
String s5 = s3.intern();
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//false
System.out.println(s4 == s5);//true
}
}
1.5 正则表达式
public class Test {
public static void main(String[] args) {
String reg = "^1[3456789]\\d{9}$";
String phone = "13176131767";
System.out.println(phone.matches(reg));//true
String reg2 = "^1\\d{5,10}@[qQ]{2}\\.com$";
String qq = "huangming_0815@qq.com";
System.out.println(qq.matches(reg));//false
String word = "Hello,I am Zhuxi,How are you!";
String[] split = word.split("[, !]+");
for (String s : split) {
System.out.println(s);
}
//获取
String h1 = "我爱java,java是世界上最好的语言,Java真香";
Pattern compile = Pattern.compile("[jJ]ava");
Matcher matcher = compile.matcher(h1);
while (matcher.find()){
System.out.println(matcher.group());
}
//替换
String h2 = h1.replaceAll("[jJ]ava", "php");
System.out.println("h2 = " + h2);
//叠词处理
String h3 = "我..我..我喜喜喜喜喜...喜欢欢欢...欢jjjjjjava";
String s = h3.replaceAll("[.]", "");
String $1 = s.replaceAll("(.)\\1+", "$1");
System.out.println("$1 = " + $1);
}
}
/*
true
false
Hello
I
am
Zhuxi
How
are
you
java
java
Java
h2 = 我爱php,php是世界上最好的语言,php真香
$1 = 我喜欢java
*/
1.6 BigDecimal
- BigDecimal add(BigDecimal bd) 加
- BigDecimal subtract(BigDecimal bd) 减
- BigDecimal multiply(BigDecimal bd) 乘
- BigDecimal divide(BigDecimal bd) 除
1.7 Date,Calendar
public class Test {
public static void main(String[] args) {
Calendar calendar=Calendar.getInstance();
System.out.println(calendar.get(Calendar.YEAR));
System.out.println(calendar.get(Calendar.MONTH)+1);//0 -11
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
System.out.println(calendar.get(Calendar.MINUTE));
System.out.println(calendar.get(Calendar.SECOND));
Calendar calendar2=Calendar.getInstance();
calendar2.set(2020, 6, 29);
System.out.println(calendar2.getTime().toLocaleString());
//after before
System.out.println(calendar.after(calendar2));
//获取这个月的最大天数 1 3 5 7 8 10 12 31天 2 29 28 4 6 9 30
System.out.println(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
//获取这个月的最小天数
System.out.println(calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
//获取月的最大天数
System.out.println(calendar.getMaximum(Calendar.DAY_OF_MONTH));
//获取月的最大天数
System.out.println(calendar.getMinimum(Calendar.DAY_OF_MONTH));
//转换
//Date----Calendar
Calendar instance = Calendar.getInstance();
instance.setTime(new Date());
//Calendar---Date
Date date = instance.getTime();
}
}
1.8 SimpleDateFormat
public class Test {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化日期
sdf.applyPattern("yyyy-MM-dd HH:mm:ss:SSS");
Date date = new Date();
System.out.println("sdf.format(date) = " + sdf.format(date));
String time = "2020-12-12 12:12:12:123";
//解析
Date parse = sdf.parse(time);
System.out.println("parse = " + parse);
}
}
1.9 Math,Random,System,Runtime
-
数学类
-
随机数。new Random().nextInt();
-
static void arraycopy(…) 复制数组
-
static long currentTimeMillis(); 获得当前系统时间
-
static void gc(); 垃圾回收
-
static void exit(int status); 退出jvm,如果参数是0表示正常退出jvm,非0表 示异常退出jvm
2 异常
2.1 异常分类
- Throwable可抛出的,一切错误或异常的父类,位于java.lang包中
- Error: JVM、硬件、执行逻辑错误,不能手动处理。
- Exception:程序在运行和配置中产生的问题,可处理。
- RuntimeException:运行时异常,可处理,可不处理。
- [CheckedException]:检查时异常,必须处理
2.2 常见的运行时异常
- NullPointerException 空指针异常
- ArrayIndexOutOfBoundsException 数组下标越界异常
- ClassCastException 类型转换异常
- NumberFormatException 数字格式化异常
- ArithmeticException 算数异常
2.3 异常的传递
- 检查时异常:throws 声明异常,修饰在方法参数列表后端
- 运行时异常:因可处理可不处理,无需声明异常
2.4 异常的处理
2.4.1 throw
- throw new Exception(); 在方法也有抛出Exception
2.4.2 try catch finally
- try 中是可能出现异常的代码
- catch是对可能出现的异常进行处理
- finally一般是清理资源,不管try catch如何执行,finally一定会执行
public static int getNum() {
int num = 10;
try {
return num++; // 1
} catch (Exception e) {
return num++;
} finally {
return num++; //2
}
}
如果try
和 catch
中有return
,则会先执行return
后的语句,然后再执行finally
中的语句,如果finally
中有return
,则执行完finally
中的return
语句就结束,如果finally
中没有return
语句,则最后返回 try , catch
中的return
。
上述代码返回值应是11
,如果把2处去掉,则返回值应是10
。
2.5 自定义异常
- 需要继承
Exception
或者Exception
的子类,代表特定的问题
3 集合
3.1 Collection
- boolean add(Object obj) 添加一个元素
- boolean addAll(Collection c) 将一个集合中的所有对象添加到此集合中。
- void clear() 清空集合中的元素
- boolean contains(Object o) 判断集合中是否存在此元素
- boolean isEmpty() 判断此集合是否为空
- boolean remove(Ojbect o) 在此集合中移除o对象
- int size() 返回集合中的元素个数
- Object[] toArray() 把集合转换成数组
public class Test1 {
public static void main(String[] args) {
ArrayList collections = new ArrayList();
collections.add("上海");
collections.add("北京");
collections.add("深圳");
collections.add("杭州");
collections.add(1);
collections.add("广州");
for (Object collection : collections) {
System.out.print(collection + ";");
}
System.out.println();
collections.remove("广州");
for (Object collection : collections) {
System.out.print(collection + ";");
}
System.out.println();
Iterator iterator = collections.iterator();
while (iterator.hasNext()){
System.out.print(iterator.next() + ";");
}
System.out.println();
System.out.println("collections.equals(collections) = " + collections.equals(collections));
}
}
3.2 List
- void add(int index,Object o) 在指定位置插入元素
- boolean(int index,Collection c) 在指定位置添加一个集合中的元素
- Object get(int index) 获取指定位置的元素的值
- List subList(int fromIndex,int toIndex) 返回集合中fronIndex - toIndex的元素
- hasPrevious,,,迭代器从后往前遍历
public class Test3 {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Student("黄铭",21));
list.add(new Student("小芳",18));
list.add(new Student("胖铭",20));
list.add(new Student("胖铭",20));
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("----------------");
list.remove(new Student("胖铭",20));
for (Object o : list) {
System.out.println(o);
}
System.out.println("----------------");
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()){
System.out.println(listIterator.next());
}
while (listIterator.hasPrevious()){
System.out.println(listIterator.previous());
}
System.out.println(list.contains(new Student("胖铭", 20)));
System.out.println(list.indexOf(new Student("胖铭", 20)));
}
}
/*
Student{name='黄铭', age=21}
Student{name='小芳', age=18}
Student{name='胖铭', age=20}
Student{name='胖铭', age=20}
----------------
Student{name='黄铭', age=21}
Student{name='小芳', age=18}
Student{name='胖铭', age=20}
----------------
Student{name='黄铭', age=21}
Student{name='小芳', age=18}
Student{name='胖铭', age=20}
Student{name='胖铭', age=20}
Student{name='小芳', age=18}
Student{name='黄铭', age=21}
true
2
*/
3.3 ArrayList
3.3.1 构造方法
ArrayList()无参构造。通过空参构造方法创建集合对象并未构造一个初始容量为十的空列表,仅仅将 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址赋值给elementData 。只有当第一次向空集合添加元素时,集合的容量才会变成10。
ArrayList(int initialCapacity)。if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];}
创建指定长度的集合。
ArrayList(Collection<? extends E> c)。通过c.toArray();
对传入的集合转成数组A,然后在调用Array.copyOf()
创建一个新的数组B,将A的值复制给B,并将B返回,传递给构造方法中的elementData
。
3.3.2 添加方法
add(E e) 将指定元素添加到集合的尾部。
add(int index,E e) 将指定元素添加到指定位置
addAll(Collection<? extends E> c) extends E> c) 按指定集合的Iterator返回的顺序将指定集合中的所有元素 追加到此列表的末尾。
addAll(i nt index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置 开始。
3.3.3 其他方法
remove()
将集合中的元素移除,参数可以是元素,也可以是集合的下标。
clone()
将集合中的元素拷贝,放到Object的数组中。分为浅拷贝和深拷贝。
ArrayList
迭代的方式:for循环,增强for,itetator迭代器和listIterator迭代器。
3.3.4 Vector
与ArrayList相比,vector有同步方法,所以vector是线性安全的,ArrayList是线性不安全的,但是Vector的速度会比ArrayList慢。
3.4 LinkedList
LinkedList是用双向链表实现的。添加的方法是push
,删除的方法是pop
。
1.LinkedList<String> linked = new LinkedList<>();
2.linked.push();=linked.addFirst();//在列表开始添加元素
3.linked.addLast();//在集合最后面添加元素
4.linked.getFirst();linked.getLast();//获得第一个和最后一个元素
5.linked.removeFirst();linked.removeLast();//移除第一个和最后一个元素并返回这个值
3.5 Stack,Queue
栈:是一种先进后出的数据结构。 • Stack类:继承Vector类 • push方法入栈、pop方法出栈 • LinkedList也实现了栈结构
队列:是一种先进先出的数据结构。 • Queue接口:继承Collection接口 • offer方法入队、pop方法出队 • LinkedList实现了Queue接口
双栈实现队列
public class Queue {
private LinkedList stack1 = new LinkedList();
private LinkedList stack2 = new LinkedList();
public Queue(LinkedList stack1, LinkedList stack2) {
this.stack1 = stack1;
this.stack2 = stack2;
}
public void queue(){
int size = stack1.size();
for (int i = 0; i < size; i++) {
stack2.push(stack1.pop());
}
}
}
public class Test {
public static void main(String[] args) {
LinkedList list1 = new LinkedList();
LinkedList list2 = new LinkedList();
Queue queue = new Queue(list1, list2);
list1.push(1);
list1.push(2);
list1.push(3);
list1.push(4);
queue.queue();
int size = list2.size();
for (int i = 0; i < size; i++) {
System.out.println(list2.pop());
}
}
}
3.6 泛型
3.6.1 类的泛型的定义与使用
1.定义:
public class a<E>{
private E name;
public void 方法名(E e){方法体}
getter and setter();
};
2.使用:
a<String> b = new a<>();
3.6.2 接口的泛型的定义与使用
1.定义
1.public interface 接口名<I>{抽象方法}
public class 实现类名 implements 接口名<String> {重写抽象方法}
2.public interface 接口名<I>{抽象方法}
public class 实现类名<I> implements 接口名<I> {重写抽象方法}
2.实现
1.实现类名 对象名 = new 实现类名();
2.实现类名<I> 对象名 = new 实现类名<>();
3.6.3 方法的泛型的定义与使用
1.定义
public <M> M 方法名(M m) {方法体;}
2.使用
方法名(m);
3.6.4 泛型通配符
1.不能创建对象使用,只能作为方法的参数使用
2.public void 方法名(ArrayList<?> list){方法体}
3.泛型的上限限定: ? extends E 代表使用的泛型只能是E类型的子类/本身
4.泛型的下限限定: ? super E 代表使用的泛型只能是E类型的父类/本身
3.7 Collections
/*
Collections.addAll(集合名,数据,数据,数据,数据,...);//添加多个元素
Collections.cort(集合名);//排序,默认升序
Collections.shuffle(集合名);//打乱顺序
Comparable接口的排序规则:自己(this)-参数:升序;参数-自己(this):降序
compare方法的排序规则:o1-o2:升序;o2-o1:降序
*/
public class CollectionsDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
//采用工具类 完成 往集合中添加元素
Collections.addAll(list, 5, 222, 1,2);
System.out.println(list);
//排序方法
Collections.sort(list);
System.out.println(list);
//打乱顺序
Collections.cort(list);
System.out.println(list);
//匿名内部类方法的对排序进行降序降序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.charAt(0) - o1.charAt(0);
}
});
System.out.println(list03);
ArrayList<Student> list02 = new ArrayList<>();
list02.add(new Student("a迪丽热巴",18));
list02.add(new Student("古力娜扎",20));
list02.add(new Student("杨幂",17));
list02.add(new Student("b杨幂",18));
System.out.println(list02);
Collections.sort(list02, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//按照年龄升序排序
int result = o1.getAge()-o2.getAge();
//如果两个人年龄相同,再使用姓名的第一个字比较
if(result==0){
result = o1.getName().charAt(0)-o2.getName().charAt(0);
}
return result;
}
});
Sysytem.out.println(list02);
}
}
结果:
[5, 222, 1, 2]
[1, 2, 5, 222]
[2, 222, 1, 5]
[222,5,2,1]
3.8 Set
-
无序,无下标,不可重复
-
add() 添加
-
remove() 移除
-
contains() 判断集合中是否存在此值
-
因为Set集合没有下标,所以不能用普通的for循环进行集合的遍历。可用for each和迭代器遍历
-
当set集合存储引用对象的时候,应该重写hashCode()方法和equals()方法
3.8.1 HashSet
-
基于hashCode实现元素不重复
-
在JDK1.7是数组+hash表,JDK1.8+开始是数组+hash表+红黑树。
-
当链表长度大于8,元素个数大于64个时,链表换成红黑树。当链表长度<=6时,红黑树重新转化为链表
3.8.2 LinkedSet
- 链表实现的HashSet,按照链表进行存储,即可保留元素的插入顺序。
3.8.3 TreeSet
- 基于排序实现元素不重复
- 实现了SortedSet接口,对集合元素自动排序
- 元素对象的类型必须实现Comparable接口,并重写compareTo方法。
- 通过CompareTo方法确定是否为重复元素。
public int compareTo(Student o) {
int result = this.age - o.age;
if (result == 0) {
result = this.name.charAt(0) - o.name.charAt(0);
}
return result;
}
- 通过重写Comparator的compare方法也能进行自定义排序
TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int n1 = o1.length() - o2.length();
int n2 = o1.compareTo(o2);
return n1 == 0 ? n2 : n1;
}
});
- 二叉查找树
public class BinarySortTree {
private Node root;
private int size;
public int getSize(){
return size;
}
//添加元素
public void add(int num){
if (root == null){
root = new Node(num);
size++;
}else {
boolean b = root.addChild(num);
if (b){
size++;
}
}
}
//遍历元素
public void middleList(){
root.printNode();
}
//静态内部类提供Node类型
static class Node{
private int item;
private Node left;
private Node right;
public Node(int item) {
this.item = item;
}
//添加子节点
public boolean addChild(int num){
boolean b = true;
if (this.item > num){
if (this.left == null){
this.left = new Node(num);
}else {
return this.left.addChild(num);
}
}else if (this.item < num){
if (this.right == null){
this.right = new Node(num);
}else {
return this.right.addChild(num);
}
}else {
System.out.println("元素重复!");
b = false;
}
return b;
}
//打印节点
public void printNode(){
if (this.left != null){
this.left.printNode();
}
System.out.println(this.item);
if (this.right != null){
this.right.printNode();
}
}
}
}
public class Test {
public static void main(String[] args) {
BinarySortTree binarySortTree = new BinarySortTree();
binarySortTree.add(10);
binarySortTree.add(12);
binarySortTree.add(8);
binarySortTree.middleList();
}
}
3.9 Map
3.9.1 特点
- Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)
- Map集合中的元素,key和value的数据类型可以相同,也可以不同
- Map集合中的元素,key是不允许重复的,value是可以重复的
- Map集合中的元素,key和value是一一对应
3.9.2 常用方法
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中。public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。public V get(Object key)
根据指定的键,在Map集合中获取对应的值。boolean containsKey(Object key)
判断集合中是否包含指定的键。boolean containsValue(Object value)
判断集合中是否存在指定的值public Set<K> keySet()
: 获取Map集合中所有的键,存储到Set集合中。public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
3.9.3 遍历
- 通过
keySet()
获取所有的key存储到set集合中,再遍历set集合通过map.get(key)
获得value.
//获取所有的键 获取键集
Set<Object> keys = map.keySet();
// 遍历键集 得到 每一个键
for (Object key : keys) {
//key 就是键
//获取对应值
Object value = map.get(key);
System.out.println(key+"的CP是:"+value);
}
- 通过Entry键值对对象遍历map集合
for (Map.Entry<Student, String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + "-----" + entry.getValue());
}
Iterator<Map.Entry<Student, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Student, String> next = iterator.next();
System.out.println(next.getKey() + "---" + next.getValue());
}
3.9.4 LinkedHashMap
- 在HashMap下面有一个子类LinkedHashMap,它是链表和哈希表组合的一个数据存储结构,保证数据存储的有序性
3.9.5 Properties
-
键值对方式存储,key和value都只能是字符串
-
setProperties(String key,String value) 向集合中存值
-
getProperties(String key) 通过key向集合查询value
-
StringPropertiesNames 遍历集合
4 多线程
4.1 Thread
- 线程的运行是抢占cpu的时间片,来运行线程。
- 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
- 调用thread的start方法来开启心得线程,调用run方法不会开启新的线程,还是会让main线程执行
- 获取线程名称和ID的方法有两种,第一种是
this.getName()
和this.getId()
,第二种是Thread.currentThread.getName()
和Thread.currentThread.getId()
,但是推荐使用第二种,因为第一种只能在线程类使用 - 修改线程的名称对应获取线程的名称,
get
换成set
,但是线程的Id
不能修改。
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(this.getName() + this.getId() + ":" + i);
}
}
}
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
Thread thread1 = new MyThread("憨憨二号:");
thread.setName("憨憨一号:");
thread.start();
thread1.start();
Thread.currentThread().setName("憨憨头子:");
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + ":" + i);
}
}
}
4.2 Runnable
- 类实现
Runnable
方法并重写run
方法 - 利用线程调用实现了
runnable
方法的类
public class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if (ticket <= 0){
break;
}
System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票!");
ticket--;
}
}
}
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() +"..." + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
4.3 Thread中的方法
Thread.sleep(long millis)
线程睡眠,单位为毫秒
public class Test {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() +"..." + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable,"子线程:");
thread.start();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
}
Thread.yield()
放弃当前时间片的抢占,进入就绪态,进行下一次的时间片的争夺
public class Test2 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (i % 2 == 0) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
};
Thread thread = new Thread(runnable, "子线程");
thread.start();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
}
Thread.join()
把其他线程加入当前线程,当前线程无限期等待,直到加入的线程结束
public class Test3 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"..."+i);
}
}
};
Thread thread = new Thread(runnable,"子线程");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"..."+i);
}
}
}
thread.setPriority(int n)
设置线程的优先级,范围在1-10.1最低,10最高
public class Test5 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"..."+i);
}
}
};
Thread thread1 = new Thread(runnable,"线程1");
Thread thread2 = new Thread(runnable,"线程2");
Thread thread3 = new Thread(runnable,"线程3");
thread1.setPriority(1);
thread3.setPriority(9);
thread1.start();
thread2.start();
thread3.start();
}
}
thread.interuput()
强制终端线程
public class Test4 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("开始休眠");
try {
Thread.sleep(20000);
System.out.println("自然醒");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("被打醒了");
}
System.out.println("线程结束");
}
};
Thread thread = new Thread(runnable);
thread.start();
Scanner scanner = new Scanner(System.in);
System.out.println("20秒之内输入一个字符串");
scanner.next();
thread.interrupt();
System.out.println("主线程结束");
}
}
thread.setDeamon(boolean bool)
将该线程标记为守护线程或用户线程,当主线程结束时,不管子线程有没有结束,结束当前程序
public class Test6 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
};
Thread thread = new Thread(runnable, "子线程");
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 30; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
}
4.4 synchronized
-
当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
-
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
-
原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
-
同步代码块:synchronized(临界资源对象){ //对临界资源对象加锁 monitor //代码(原子操作) }。只有拥有对象互斥锁的线程才能进入该对象加锁的同步代码块,线程退出同步代码块的时候,会释放对象锁。
-
同步方法:synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁 // 代码(原子操作) }。只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。 线程退出同步方法时,会释放相应的互斥锁标记
-
锁优化
- JDK1.6以前只有无锁和重量级锁。JDK1.6以后在无锁和重量级锁中间加入了偏向锁和轻量级锁。锁只能升级不能降级
- 优化策略:锁消除,锁粗化,自旋锁,自适应自旋锁,自旋锁是轻量级锁向重量级锁升级的优化。
public class Ticket implements Runnable {
private int ticket = 1000;
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket <= 0) {
break;
}
System.out.println(java.lang.Thread.currentThread().getName() + "卖出第" + ticket + "张票");
ticket--;
}
}
}
}
public class Test {
public static void main(String[] args) {
Runnable runnable = new Ticket();
Thread thread1 = new Thread(runnable,"窗口1");
Thread thread2 = new Thread(runnable,"窗口2");
Thread thread3 = new Thread(runnable,"窗口3");
Thread thread4 = new Thread(runnable,"窗口4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥 有B对象锁标记,并等待A对象锁标记时,产生死锁。一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥 有的锁标记,由此可能造成死锁。
public class Sticks {
public static Object lock1 = new Object();
public static Object lock2 = new Object();
}
public class Boy extends Thread{
public Boy(String name) {
super(name);
}
@Override
public void run() {
synchronized (Sticks.lock1){
System.out.println(Thread.currentThread().getName()+"拿了a");
synchronized (Sticks.lock2){
System.out.println(Thread.currentThread().getName()+"拿了b");
System.out.println(Thread.currentThread().getName()+"可以吃了...");
}
}
}
}
public class Girl extends Thread{
public Girl(String name) {
super(name);
}
@Override
public void run() {
synchronized (Sticks.lock2){
System.out.println(Thread.currentThread().getName()+"拿了b");
synchronized (Sticks.lock1){
System.out.println(Thread.currentThread().getName()+"拿了a");
System.out.println(Thread.currentThread().getName()+"可以吃了...");
}
}
}
}
public class Test {
public static void main(String[] args) {
Thread boy = new Boy("黄铭");
Thread girl = new Girl("小芳");
boy.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
girl.start();
}
}
4.5 线程通信
-
必须在同步代码块中进行线程通信,
-
等待,
object.wait()
或者object.wait(long timeout)
,此线程释放其拥有的所有锁标记,同时此线程阻塞在0的等待队列中。 -
通知,
object.notify
和objet.notifyAll
,第一个是随机释放一个线程,第二个是释放阻塞的全部线程。 -
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使 生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区, 生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费, 显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区 中取产品,也不允许生产者向一个满的缓冲区中放入产品。
面包类
public class Bread {
private int idNo;
private String produce;
public Bread() {
}
public Bread(int idNo, String produce) {
this.idNo = idNo;
this.produce = produce;
}
public int getIdNo() {
return idNo;
}
public void setIdNo(int idNo) {
this.idNo = idNo;
}
public String getProduce() {
return produce;
}
public void setProduce(String produce) {
this.produce = produce;
}
@Override
public String toString() {
return "Bread{" +
"idNo=" + idNo +
", produce='" + produce + '\'' +
'}';
}
}
队列类
public class BreadCon {
private Bread[] con = new Bread[6];
private int size;
public synchronized void put(Bread b) {
while (size > 5) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
con[size] = b;
System.out.println(Thread.currentThread().getName() + "生产了" + b.getIdNo() + "号面包");
size++;
this.notifyAll();
}
public synchronized void take() {
while (size <= 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size--;
Bread b = con[size];
con[size] = null;
System.out.println(Thread.currentThread().getName() + "取了"+b.getIdNo()+"面包,生产者是:"+b.getProduce());
this.notifyAll();
}
}
消费者类
public class Consume implements Runnable{
private BreadCon con;
public Consume(BreadCon con) {
this.con = con;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
con.take();
}
}
}
生产者类
public class Produce implements Runnable{
private BreadCon con;
public Produce(BreadCon con) {
this.con = con;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
con.put(new Bread(i,Thread.currentThread().getName()));
}
}
}
测试类
public class Test {
public static void main(String[] args) {
BreadCon con = new BreadCon();
Runnable produce = new Produce(con);
Runnable consume = new Consume(con);
Thread thread1 = new Thread(produce,"生产者1");
Thread thread2 = new Thread(produce,"生产者2");
Thread thread3 = new Thread(consume,"消费者1");
Thread thread4 = new Thread(consume,"消费者2");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
4.6 线程池
- 线程是内存资源,单个线程占1MB空间左右,过多的分配容易造成内存溢出。频繁的创建和销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降
- 线程池原理是创建一个池,在池中存入线程对象,当需要用线程时向池中取,用完后把线程对象放回线程池中,避免的频繁的创建和销毁
- 通过Executors工厂类可以获得一个线程池。
-
通过
newFixedThreadPool(int nThreads)
获取固定数量的线程池。参数:指定线 程池中线程的数量 -
通过
newCachedThreadPool()
获得动态数量的线程池,如不够则创建新的,上限是int的最大值。 -
通过
newSingleThreadExecutor()
获得单线程的线程池 -
通过
newScheduledThreadPool()
获得一个具有延时效果的线程池,周期执行scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
,延时,会把睡眠中没有完成的任务在睡眠后一次性完成。scheduleWithFixedDelay(Runnable command,long initialDelay,long period,TimeUnit unit);
,延时,不会把随眠中没有完成的任务补回来。
public class Test2 { public static void main(String[] args) { Runnable runnable = new Runnable() { private int count = 1; @Override public void run() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:sss"); System.out.println(Thread.currentThread().getName() + ":" + sdf.format(new Date())); count++; if (count == 10) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; ScheduledExecutorService ses = Executors.newScheduledThreadPool(1); //延时,sleep后会把睡眠的补回来 ses.scheduleAtFixedRate(runnable, 0, 1000, TimeUnit.MILLISECONDS); //计划,sleep后不会把睡眠的补回来 //ses.scheduleWithFixedDelay(runnable, 0, 1000, TimeUnit.MILLISECONDS); } }
-
4.7 ThreadPoolExecutor
-
通过ThreadPoolExecutor创建线程池有7个参数:
- 核心线程数,,创建线程池初始的线程数
- 最大线程数,,线程池能够存在最多的线程数
- 线程存活时间,,当核心线程数以外的线程空闲达到存活时间时,线程销毁
- 时间单位,,线程存活时间的时间单位设置
- 请求队列,,创建一个新的队列来存储核心线程数满了以后的任务
- 线程创建工厂
- 拒绝策略,主线程和核心线程,最大线程的线程同一优先级,队列的任务优先级略低
/* 拒绝策略有4种。 1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。 2.ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。 3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。 4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。如果任务被拒绝了,则由调用线程(提交任务的线程,一般是主线程)直接执行此任务 */ public class Test { public static void main(String[] args) { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 3, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(2), Executors.defaultThreadFactory(), new 拒绝策略); for (int i = 0; i < 6; i++) { int num = i+1; threadPoolExecutor.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始执行:线程" + num); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行完毕:" + num); } }); } } }
4.8 Callable
- Callable和Runnable接口相似,实现之后代表一个线程任务。
- Callable具有泛型返回值,可以声明异常
- Callable可以通过线程池直接使用,返回Future类型的对象,在通过Future类的get方法可以得到Callable返回的值。V get()以阻塞形式等待Future中的异步处理结果。
public class Test {
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.currentThread().setName("线程");
System.out.println(Thread.currentThread().getName() + "计算开始");
int sum = 0;
for (int i = 0; i < 101; i++) {
sum += i;
Thread.sleep(50);
}
System.out.println(Thread.currentThread().getName() + "计算结束");
return sum;
}
};
ExecutorService es = Executors.newSingleThreadExecutor();
Future<Integer> future = es.submit(callable);
try {
System.out.println("结果是:" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
es.shutdown();
}
}
4.9 Lock锁
ReentrantLock
重入锁,lock方法对资源加锁,unlock对资源释放锁,放在finally中。boolean tryLock()
尝试获取锁ReentrantReadWriteLock
读写锁,lock.readLock().lock();``lock.readLock().unlock();``lock.writeLock().lock();``lock.writeLock().unlock();
写写互斥,读写互斥,读读不互斥。
public class WriterRead {
private int value;
//可重入锁
private Lock lock=new ReentrantLock();
//读写锁
// ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
public int getValue(){
// lock.readLock().lock();
lock.lock();
try {
try {
Thread.sleep(1000);
System.out.println("获取了"+this.value);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
}finally {
// lock.readLock().unlock();
lock.unlock();
}
}
public void setValue(int value){
// lock.writeLock().lock();
lock.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("设置了:"+value);
this.value = value;
}finally {
// lock.writeLock().unlock();
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
//读写数据
WriterRead readWrite=new WriterRead();
//可运行对象
Runnable task1=new Runnable() {
@Override
public void run() {
int v=readWrite.getValue();
}
};
Runnable task2=new Runnable() {
@Override
public void run() {
readWrite.setValue(new Random().nextInt(100));
}
};
long start=System.currentTimeMillis();
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(20);
for(int i=0;i<2;i++){
es.submit(task2);
}
for(int i=0;i<18;i++){
es.submit(task1);
}
//关闭线程池
es.shutdown();
//计算时间
while(!es.isTerminated()){}
long end=System.currentTimeMillis();
System.out.println("用时:"+(end-start));
}
}
4.10 Condition
关键字synchronize可以与wait()和nitify()方法相结合实现实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助condition对象。condition类是在JDK5中出现的技术,使用他有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里可以创建多个condition实例,线程对象可以注册在指定的condition中从而选择性的进行线程通知,在调度线程上更加灵活。
而在使用notify()/notifuAll()
方法进行通知时,被调度的线程却是由JVM随机选择的。但使用ReentrantLock
结合condition类是可以实现上面讲的“选择性通知”,这个功能是非常重要的,而且在condition类中默认提供的。
而synchronize就相当于整个Lock对象中只有一个单一的condition对象,所有的线程都注册在它一个对象上。线程开始notifyAll()
时,需要通知所有的WAITING线程,没有选择权,会出现相当大的效率问题。
- Condition是个接口,基本的方法就是await()和signal()方法
- Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
- 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
- Conditon中的await()对应Object的wait();
- Condition中的signal()对应Object的notify();
- Condition中的signalAll()对应Object的notifyAll()。
先创建一个Bread类,再创建一个BreadList类
public class BreadList {
private Bread[] breads = new Bread[6];
private int size = 0;
private ReentrantLock lock = new ReentrantLock();
private Condition proCondition = lock.newCondition();
private Condition conCondition = lock.newCondition();
public void put(Bread bread){
lock.lock();
try {
while (size > 5){
try {
proCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
breads[size] = bread;
size++;
System.out.println(Thread.currentThread().getName() + "生产了" +bread.getId() + "号面包");
conCondition.signal();
}finally {
lock.unlock();
}
}
public void get(){
lock.lock();
try {
while (size <= 0){
try {
conCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size--;
Bread bread = breads[size];
System.out.println(Thread.currentThread().getName() + "拿了" +bread.getId() + "号面包");
proCondition.signal();
}finally {
lock.unlock();
}
}
}
测试类
public class Test {
public static void main(String[] args) {
BreadList breadList = new BreadList();
Runnable product = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
breadList.put(new Bread(i,Thread.currentThread().getName()));
}
}
};
Runnable consume = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
breadList.get();
}
}
};
ExecutorService es = Executors.newCachedThreadPool();
es.submit(product);
es.submit(product);
es.submit(consume);
es.submit(consume);
es.shutdown();
}
}
整体就是调用condition的await()方法后,会将当前线程加入到等待队列中,然后释放锁,然后循环判断节点是否在同步队列中,再获取锁,否则一直阻塞。
调用signal()方法后,先判断当前线程是否有锁,然后调用doSignal()方法,并唤醒线程,被唤醒的线程,再调用acquireQueude()方法,重新开始竞争锁,得到锁后返回,退出该方法。
面试题:使用Lock和Condition实现三个线程交替输出20遍“ABC
public class Print {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
private int num = 1;
private int count = 1;
public void printA(){
lock.lock();
try{
if (num!=1){
try {
conditionA.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A");
num = 2;
conditionB.signal();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
if (num!=2){
try {
conditionB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B");
num = 3;
conditionC.signal();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
if (num!=3){
try {
conditionC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("C");
System.out.println("----"+ (count++) +"---");
num = 1;
conditionA.signal();
}finally {
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
Print print = new Print();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
print.printA();
}
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
print.printB();
}
}
}).start();
new Thread(new Runnable(){
int count = 1;
@Override
public void run() {
for (int i = 0; i < 20; i++) {
print.printC();
}
}
}).start();
}
}
4.11 线程安全集合
- Vector: Vector和ArrayList类似,是长度可变的数组,与ArrayList不同的是,Vector是线程安全的,它给几乎所有的public方法都加上了synchronized关键字。由于加锁导致性能降低,在不需要并发访问同一对象时,这种强制性的同步机制就显得多余,所以现在Vector已被弃用
- Hashtable HashTable和HashMap类似,不同点是HashTable是线程安全的,它给几乎所有public方法都加上了synchronized关键字,还有一个不同点是HashTable的K,V都不能是null,但HashMap可以,它现在也因为性能原因被弃用了
- Collections针对每种集合都声明了一个线程安全的包装类,在原集合的基础上添加了锁对象,集合中的每个方法都通过这个锁对象实现同步
List<E> synArrayList = Collections.synchronizedList(new ArrayList<E>());
Set<E> synHashSet = Collections.synchronizedSet(new HashSet<E>());
Map<K,V> synHashMap = Collections.synchronizedMap(new HashMap<K,V>());
-
ConcurrentHashMap
- ConcurrentHashMap和HashTable都是线程安全的集合,它们的不同主要是加锁粒度上的不同。HashTable的加锁方法是给每个方法加上synchronized关键字,这样锁住的是整个Table对象。而ConcurrentHashMap是更细粒度的加锁
- 在JDK1.8之前,ConcurrentHashMap加的是分段锁,也就是Segment锁,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响
- JDK1.8对此做了进一步的改进,它取消了Segment字段,直接在table元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率
-
CopyOnWriteArrayList和CopyOnWriteArraySet
- 它们是加了写锁的ArrayList和ArraySet,锁住的是整个对象,但读操作可以并发执行
-
除此之外还有ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue、ConcurrentLinkedDeque
4.12 CAS(比较交换算法)
1. 什么是CAS
CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。
CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。
2. CAS的问题
第一个ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
第二个就是 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
第三个只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
public class CAS {
private int v;
private int version;
public int get(){
return this.v;
}
public int getVersion(){
return this.version;
}
public boolean compare(int v,int eversion,int n){
if (this.v == v && eversion == version){
this.v = n;
return true;
}
return false;
}
}
public class Test {
public static void main(String[] args) {
CAS cas = new CAS();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (;;){
int v = cas.get();
int version = cas.getVersion();
boolean compare = cas.compare(v, version, new Random().nextInt(100));
if (compare){
System.out.println(Thread.currentThread().getName() + "修改成功");
break;
}else {
System.out.println(Thread.currentThread().getName() + "修改失败");
}
}
}
}).start();
}
}
}
4.13 BlockingQueue接口
- 此接口的实现类加入了Condition队列和ReenranLock锁,可用于解决生产生、消费者问题
- 常见的实现类有ArrayBlockingQueue和LinkedBlockingQueue,
- put方法向队列末尾添加元素,若队列已满,则一直阻塞
- take方法获取并移除队列的头元素,若没有元素则一直阻塞
public class Test {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(6);
Runnable produce = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
blockingQueue.put(Thread.currentThread().getName() + "---" + i);
System.out.println(Thread.currentThread().getName() + "生产了:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Runnable comsume = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
String take = blockingQueue.take();
System.out.println(Thread.currentThread().getName() + "消费了:" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
ExecutorService es = Executors.newFixedThreadPool(4);
es.submit(produce);
es.submit(produce);
es.submit(comsume);
es.submit(comsume);
es.shutdown();
}
}
4.14 Atomic
实现了CAS算法的类。
compareAndSet(V expectedReference, V newReference,int expectedStamp,int newStamp)
//如果当前引用 == 预期引用,并且当前标志等于预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。
//expectedReference - 该引用的预期值
//newReference - 该引用的新值
//expectedStamp - 该标志的预期值
//newStamp - 该标志的新值
public class Test {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(0, 0);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
Integer reference = atomicStampedReference.getReference();
int stamp = atomicStampedReference.getStamp();
boolean b = atomicStampedReference.compareAndSet(reference, new Random().nextInt(100), stamp, ++stamp);
if (b) {
System.out.println(Thread.currentThread().getName() + "修改成功");
break;
} else {
System.out.println(Thread.currentThread().getName() + "修改不成功");
}
}
}
}).start();
}
}
}
4.15 多线程的三大特性
-
原子性:即一个操作或者多个操作 *要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
-
可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
-
有序性:即程序执行的顺序按照代码的先后顺序执行。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在Java里面,可以通过volatile关键字来保证一定的“有序性”(具体原理在下一节讲述)。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
*另外,*Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
5 IO流
5.1 字节节点流
5.11 FileInputStream
1. FileInputStream(File file) //通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。没有此文件路劲会抛出异常
2. read() //向流中读取一个字节
3. read(byte[] b) //向流中读取b.length个字节并存到数组中,当末尾没有字节时返回-1
4. read(byte[] b, int off, int len) //从第off位开始存,最多存len个字节到b中
5.12 FileOutputStream
1. FileOutputStream(File file) //创建一个向具有指定名称的文件中写入数据的输出文件流。创建一个新 FileDescriptor 对象来表示此文件连接。
2. FileOutputStream(File file, boolean append) //通过append决定流是否具有续写的功能
3. writer(int b) //要写入的字节
4. writer(byte[] b) //要写入的字节数组
5.13 通过字节节点流完成复制
public class Test {
public static void main(String[] args) {
long l1 = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D:\\日常文件\\a.docx");
fos = new FileOutputStream("D:\\日常文件\\b.docx");
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1) {
fos.write(len);
System.out.println(new String(bytes, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
long l2 = System.currentTimeMillis();
System.out.println("l2-l1 = " + (l2 - l1));
}
}
5.2 字节缓冲流
5.21 BufferedInputStream
1. BufferedInputStream(InputStream in) //创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。创建一个内部缓冲区数组并将其存储在 buf 中。
2. BufferedInputStream(InputStream in,int size) //创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。创建一个长度为 size 的内部缓冲区数组并将其存储在 buf 中。
3. read() //向流中读取一个字节
4. read(byte[] b) //向流中读取b.length个字节并存到数组中,当末尾没有字节时返回-1
5. read(byte[] b, int off, int len) //从第off位开始存,最多存len个字节到b中
5.22 BufferedOutputStream
1. BufferedOutputStream(OutputStream out) //创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
2. write(int b) //将指定的字节写入此缓冲的输出流。
3. write(byte[] b, int off, int len) // 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流。
4. flush() //刷新缓冲区
5.23 通过字节缓冲流复制
public class Test2 {
public static void main(String[] args) {
long l1 = System.currentTimeMillis();
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream("D:\\日常文件\\aa.txt"));
bos = new BufferedOutputStream(new FileOutputStream("D:\\日常文件\\bb.txt"));
int len = 0;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1){
//System.out.println("len = " + len);
bos.write(bytes,0,len);
bos.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
long l2 = System.currentTimeMillis();
System.out.println("l2-l1 = " + (l2 - l1));
}
}
5.3 序列化流
ObjectInputStream
如果流要对对象进行输入和输出,那么此对象的类就必须实现Serializable
接口。在对象内要定义一个静态常量UID,以此来判断在序列化和反序列化的时候是否是在同一个对象进行操作,private static final long serialVersionUID = 1L;
,如果不手动定义,那么程序会自动赋一个UID给此类。如果不想某个变量被序列化,可以用transient
修饰此变量。
5.31 ObjectOutputStream
/*
构造方法:
ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream。
参数:
OutputStream out:字节输出流
特有的成员方法:
void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。
使用步骤:
1.创建ObjectOutputStream对象,构造方法中传递字节输出流
2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
3.释放资源
*/
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("10_IO\\person.txt"));//创建对象
oos.writeObject(new Person("小美女",18));//用oos的方法把person对象写入文件
oss.close();
5.32 ObjectOutputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("10_IO\\person.txt"));
Object o = ois.readObject();
ois.close();
System.out.println(o);
Person p = (Person)o;
System.out.println(p.getName()+p.getAge());
5.33 Externalizable
此接口是手动序列化接口,实现此接口要重写两个方法,使用此接口能对使用了transient关键字的属性进行序列化。
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeObject(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name=(String)in.readObject();
age=(int)in.readObject();
}
5.4 字符节点流
Reader Writer
5.41 FileReader
1. FileReader fileReader = new FileReader(File file); //在给定从中读取数据的 File 的情况下创建一个新 FileReader。
2. read() //读取一个字符
3. read(char[] cbuf) //读取一个字符数组
4. read(charp[] cbuf, int off, int len) //将字符读入数组的某一部分。在某个输入可用、发生 I/O 错误或者到达流的末尾前,此方法一直阻塞。
FileReader fr = new FileReader("09_IOAndProperties\\c.txt");
char[] cs = new char[1024];
int len = 0;
while((len = fr.read(cs)) != -1){
sout(new String(cs,0,len));
}
5.42 FileWriter
1. FileWriter(File file) //根据给定的 File 对象构造一个 FileWriter 对象。
2. FileWriter(File file, boolean b) //指定是否可以续写
3. writer() //写入单个字符
4. writer(char[] c) //写入字符数组。
5. writer(String str) //写入一个字符串
FileWriter fileWriter = new FileWriter("09_IOAndProperties\\g.txt",true);
for (int i = 0; i < 10; i++) {
fileWriter.write("HelloWorld" + i);
fileWriter.write("\r\n");
}
fileWriter.close();
5.5 字符缓冲流
5.51 BufferedReader
1. BufferedReader(Read in) //创建一个使用默认大小输入缓冲区的缓冲字符输入流。
2. read() //向流中读取一个字符
3. read(char[] chars) //向流中读取b.length个字符并存到数组中,当末尾没有字符时返回-1
4. read(char[] chars, int off, int len) //从第off位开始存,最多存len个字符到b中
5. readLine() //读取一行,末尾没有时返回null
BufferedReader br = new BufferedReader(new FileReader("10_IO\\c.txt"));
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}
br.close();
5.52 BufferedWriter
1. BufferedWriter(Writer out) //创建一个使用默认大小输出缓冲区的缓冲字符输出流。
2. write(int b) //将指定的字符写入此缓冲的输出流。
3. write(char[] c, int off, int len) // 将指定 char 数组中从偏移量 off 开始的 len 个字符写入此缓冲的输出流。
4. flush() //刷新缓冲区
BufferedWriter bw = new BufferedWriter(new FileWriter("10_IO\\c.txt"));
for (int i = 0; i <10 ; i++) {
bw.write("传智播客");
//bw.write("\r\n");
bw.newLine();//获取行分隔符
}
bw.flush();
5.6 转换流
5.61 OutputStreamWriter
/*
构造方法:
OutputStreamWriter(OutputStream out)创建使用默认字符编码的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的 OutputStreamWriter。
参数:
OutputStream out:字节输出流,可以用来写转换之后的字节到文件中
String charsetName:指定的编码表名称,不区分大小写,可以是utf-8/UTF-8,gbk/GBK,...不指定默认使用UTF-8
使用步骤:
1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码)
3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
4.释放资源
*/
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("10_IO\\gbk.txt"),"GBK");//不指定则是UTF-8
osw.write("你好");
osw.flush();
5.62 InputStreamReader
//文件是什么格式,读取的时候就只能用什么格式
InputStreamReader isr = new InputStreamReader(new FileInputStream("10_IO\\gbk.txt"),"GBK");
char[] ch = new char[1024];
int len = 0;
while ((len = isr.read(ch)) != -1){
System.out.println(new String(ch,0,len));
}
iso.close();
5.7 打印流
/*
如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97
*/
PrintStream ps = new PrintStream("10_IO\\print.txt");
ps.println();
ps.close();
/*
使用System.setOut方法改变输出语句的目的地改为参数中传递的打印流的目的地
static void setOut(PrintStream out)
重新分配“标准”输出流。
*/
System.out.println("我是在控制台输出");
PrintStream ps = new PrintStream("10_IO\\目的地是打印流.txt");
System.setOut(ps);//把输出语句的目的地改变为打印流的目的地
System.out.println("我在打印流的目的地中输出");
5.8 RandomAccessFile
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer
方法读取,并通过 seek
方法设置。
- RandomAccessFile(File file,String mode) //创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。将创建一个新的 FileDescriptor 对象来表示此文件的连接
值 | 含义 |
---|---|
“r” | 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。 |
“rw” | 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 |
“rws” | 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
“rwd” | 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。 |
- read() 读
- writer() 写
- seek()从文件开头位置设置文件指针的偏移量
- skipBytes(int len)跳过指定字节数
public class TestRandomAccessFile {
public static void main(String[] args) throws Exception{
//write();
read();
}
public static void write() throws Exception{
//1创建随机读写文件类。 r read w write
RandomAccessFile raf=new RandomAccessFile("d:\\ran.txt", "rw");
//2写
raf.writeUTF("吴铸玺");
raf.writeInt(20);
raf.writeBoolean(true);
raf.writeDouble(180);
raf.writeUTF("雄飞");
raf.writeInt(18);
raf.writeBoolean(false);
raf.writeDouble(181);
//3关闭
raf.close();
}
public static void read() throws Exception{
//1创建创建随机读写文件类。
RandomAccessFile raf=new RandomAccessFile("d:\\ran.txt", "r");
//2读取
//跳过铸玺
//seek()从文件开头位置设置文件指针的偏移量
//raf.seek(24);
//raf.seek(24);
//跳过指定字节个数
raf.skipBytes(24);
//raf.skipBytes(24);
String name=raf.readUTF();
System.out.println(name);
int age=raf.readInt();
System.out.println(age);
boolean b=raf.readBoolean();
System.out.println(b);
double height=raf.readDouble();
System.out.println(height);
//3关闭
raf.close();
}
}
5.9 Properties
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
Properties集合是一个双列集合,key和value默认都是字符串
5.91 store
/*
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
void store(OutputStream out, String comments)
void store(Writer writer, String comments)
*/
Properties prop = new Properties();
prop.setProperty("赵丽颖","168");
prop.store(new FileWriter("09_IOAndProperties\\prop.txt"),"sava data");//写入集合中的数据
5.92 load
/*
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
void load(InputStream inStream)
void load(Reader reader)
参数:
InputStream inStream:字节输入流,不能读取含有中文的键值对
Reader reader:字符输入流,能读取含有中文的键值对
使用步骤:
1.创建Properties集合对象
2.使用Properties集合对象中的方法load读取保存键值对的文件
3.遍历Properties集合
注意:
1.存储键值对的文件中,键与值默认的连接符号可以使用=,空格(其他符号)
2.存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取
3.存储键值对的文件中,键与值默认都是字符串,不用再加引号
*/
Properties prop = new Properties();
prop.load(new FileReader("09_IOAndProperties\\prop.txt"));
6. Socket
6.1 网络模型
第一层:物理层为设备之间的数据通信提供传输信号和物理介质。(双绞线、光导纤维)
第二层:链路层在物理层上,通过规程或协议(差错控制)来控制传输数据的正确性。(MAC)
第三层:网络层负责定义了能够标识所有网络节点的逻辑地址。(IP地址)
第四层:传输层负责是否选择差错恢复协议、数据流重用、错误顺序重排。(TCP、UDP)
第五层:会话层负责使应用建立和维持会话,使通信在失效时继续恢复通信。(断点续传)
第六层:表示层负责定义转换数据格式及加密,允许选择以二进制或ASCII格式传输。
第七层:应用层负责文件访问和管理、可靠运输服务、远程操作服务。(HTTP、FTP、SMTP)
6.2 InetAddress类
概念:表示互联网协议(IP)地址对象,封装了与该IP地址相关的所有信息, 并提供获取信息的常用方法
- public static InetAddress getLocalHost() 获得本地主机地址对象
- public static InetAddress getByName(String host) 根据主机名称获得地址对象
- public static InetAddress[] getAllByName(String host) 获得所有相关地址对象
- public String getHostAddress() 获取IP地址字符串
- public String getHostName() 获得IP地址主机名
6.3 tcp案例1,聊天
TcpServer
public class TcpServer {
public static void main(String[] args) {
ServerSocket linstener = null;
ExecutorService es = Executors.newCachedThreadPool();
System.out.println("聊天室已启动");
try {
linstener = new ServerSocket(8888);
while (true) {
Socket accept = linstener.accept();
System.out.println(accept.getInetAddress().getHostAddress() + "进入了聊天室");
es.submit(new ChatSocket(accept));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
linstener.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ChatSocket
public class ChatSocket implements Runnable {
private Socket socket;
public ChatSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
if (socket != null) {
BufferedReader br = null;
try {
InputStream is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is, "utf-8"));
while (true) {
String s = br.readLine();
if (s != null) {
System.out.println(socket.getInetAddress().getHostAddress() + "说:" + s);
if (s.equals("886")) {
System.out.println(socket.getInetAddress().getHostAddress() + "退出了群聊");
break;
}
} else {
System.out.println(socket.getInetAddress().getHostAddress() + "退出了群聊");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
TcpClient
public class TcpClient {
public static void main(String[] args) throws Exception{
//1创建Socket ,并指定服务器的地址和端口号
Socket socket=new Socket("localhost", 8888);
//2发送线程
new Thread(new Runnable() {
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(os,"utf-8"));
//3处理数据
//3.1创建Scanner
Scanner input=new Scanner(System.in);
while(true){
String data=input.next();
bw.write(data);
bw.newLine();
bw.flush();
if(data.equals("byebye")||data.equals("886")){
break;
}
}
//4关闭
bw.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
//读取线程
}
}
6.4 Tcp案例2,注册,登录
User
public class User implements Serializable {
private int id;
private String name;
private String pwd;
private int age;
private double score;
public User(int id, String name, String pwd, int age, double score) {
this.id = id;
this.name = name;
this.pwd = pwd;
this.age = age;
this.score = score;
}
public User() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return this.id + " = {" +
"id:" + id +
",name:" + name +
",pwd:" + pwd +
",age:" + age +
",score:" + score +
'}';
}
}
Server
public class Server {
public static void main(String[] args) {
//ServerSocket linstener = new ServerSocket(8888);
new Thread(new Rejister()).start();
new Thread(new Login()).start();
}
}
Rejiseter
public class Rejister implements Runnable{
@Override
public void run() {
//注册功能
//(1)创建ServerSocket,并指定端口号
try {
ServerSocket listener = new ServerSocket(8080);
System.out.println("注册服务器已启动...");
//(2)侦听,返回socket
Socket socket = listener.accept();
//(3)获取输入输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//(4)处理数据
User user = (User)ois.readObject();
//4.2读operties
File file = new File("user.properties");
//判断文件是否存在
Properties properties = new Properties();
if (file.exists()){
FileInputStream fis = new FileInputStream(file);
properties.load(fis);
fis.close();
}
//4.3判断id是否存在
if (properties.containsKey(user.getId())){
bw.write(user.getId() + "用户已存在,请重新注册!");
}else {
//注册成功
properties.setProperty(user.getId() + "", user.toString().split("=")[1]);
//保存
FileOutputStream fos = new FileOutputStream(file);
properties.store(fos,"用户信息");
fos.close();
bw.write("注册成功");
}
bw.newLine();
bw.flush();
//5关闭资源
ois.close();
bw.close();
listener.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Login
public class Login implements Runnable {
@Override
public void run() {
try {
// 创建serverSocket,并指定端口号
ServerSocket listener = new ServerSocket(8000);
// 侦听
Socket socket = listener.accept();
// 创建输入输出流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
PrintWriter pw = new PrintWriter(bw);
// 读取客户端传入的数据
User user = (User) ois.readObject();
//System.out.println(user);
// 创建properties流,用来对读properties文件进行读取
Properties properties = new Properties();
properties.load(new FileInputStream("user.properties"));
// 登录判断
// 1.判断properties文件中是否存在user.getId()的key
if (properties.containsKey(user.getId()+"")) {
// 得到此key的value并拆分
String property = properties.getProperty(user.getId() + "");
String[] split = property.split(",");
// 对pwd段再拆分得到properties中的pwd
String pwd = null;
for (String s : split) {
if (s.startsWith("pwd")) {
pwd = s.split(":")[1];
}
}
// 判断pwd是否相等并返回信息给客户端
if (pwd.equals(user.getPwd())) {
pw.println("登录成功");
} else {
pw.println("密码错误");
}
} else {
pw.println("用户ID或密码错误");
}
pw.close();
ois.close();
bw.close();
listener.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Client
public class Client {
public static void main(String[] args) {
System.out.println("------------1注册 2登录--------------");
System.out.println("请选择");
Scanner input=new Scanner(System.in);
int choice=input.nextInt();
try {
switch (choice){
case 1:
regist();
break;
case 2:
login();
break;
default:
System.out.println("输入有误");
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void regist() throws Exception{
User user = getInfo();
//1 创建Scoket
Socket socket = new Socket("localhost",8080);
//2 获取输入输出流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//3 处理数据
//3.1写入//使用对象流
oos.writeObject(user);
oos.flush();
//3.2读取
String s = br.readLine();
System.out.println("服务器回应" + s);
//4 关闭
oos.close();
br.close();
socket.close();
}
public static User getInfo(){
Scanner input=new Scanner(System.in);
System.out.println("请输入用户id");
int id=input.nextInt();
System.out.println("请输入姓名");
String name=input.next();
System.out.println("请输入密码");
String pwd=input.next();
System.out.println("请输入年龄");
int age=input.nextInt();
System.out.println("请输入成绩");
double score=input.nextDouble();
User user=new User(id, name, pwd,age,score);
return user;
}
public static void login() throws Exception {
User u = getInfo1();
Socket socket = new Socket("localhost",8000);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(u);
oos.flush();
String s = br.readLine();
System.out.println("服务端回应" + s);
oos.close();
br.close();
socket.close();
}
public static User getInfo1(){
Scanner input=new Scanner(System.in);
System.out.println("请输入用户id");
int id=input.nextInt();
System.out.println("请输入密码");
String pwd=input.next();
User user = new User(id,null,pwd,0,0);
return user;
}
}
6.5 UDP
- 无连接,不可靠,每个数据包64k
- 速度快,可实现广播发送
- DatagramSocket 发送和接收数据报包的套接字
- DatagramPacket 数据报包
Send
public class SendUdp {
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
datagramSocket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true) {
String data = scanner.nextLine();
byte[] sendData = data.getBytes();
DatagramPacket datagramPacket = new DatagramPacket(sendData, sendData.length,
InetAddress.getByName("10.0.139.255"), 8899);
datagramSocket.send(datagramPacket);
if (data.equals("886")) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
datagramSocket.close();
}
}
}
Receive
public class SendUdp {
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
datagramSocket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true) {
String data = scanner.nextLine();
byte[] sendData = data.getBytes();
DatagramPacket datagramPacket = new DatagramPacket(sendData, sendData.length,
InetAddress.getByName("10.0.139.255"), 8899);
datagramSocket.send(datagramPacket);
if (data.equals("886")) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
datagramSocket.close();
}
}
}
7. NIO
7.1 Buffer缓冲区
- 创建缓冲区,调用put方法写入数据到Buffer
- 间接缓冲区:
- 在堆中开辟,易于管理,垃圾回收器可以回收,空间有限,读写文件速度较慢
- 直接缓冲区
- 直接在物理内存中开辟空间,空间比较大,读写文件速度快
- 缺点:不受垃圾回收器控制,创建和销毁耗性能。
- 间接缓冲区:
- 调flip()方法
- 调用get方法从Buffer中读取数据
- 调用clear()方法或者compact()方法
方法名 | 描述 |
---|---|
ByteBuffer allocate(int capacity) | 在堆中开辟空间,间接缓冲区 |
ByteBuffer allocateDirect(int capacity) | 在直接内存开辟空间,直接缓冲区 |
ByteBuffer put(byte[] src) | 通过Buffer的put()方法写到Buffer里 |
Buffer flip() | flip方法将Buffer从写模式切换到读模式。调用 flip()方法会将position设回0,并将limit设置成 之前position的值 |
ByteBuffer get(byte[] dst) | 使用get()方法从Buffer中读取数据 |
Buffer clear() | 清空缓冲区,position将被设回0,limit被设置 成 capacity的值。 |
ByteBuffer compact() | compact()方法将所有未读的数据拷贝到Buffer 起始处。然后将position设到最后一个未读元素 正后面 |
public class Test {
public static void main(String[] args) {
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
String data = "我爱java";
// 用put方法把数据存到缓冲区
buffer.put(data.getBytes());
// 缓冲区翻转
buffer.flip();
// 读取缓冲区数据到byte数组
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
// 打印
System.out.println(new String(bytes,0,bytes.length));
// 清空缓冲区
buffer.clear();
}
}
7.2 Channel通道
7.2.1 Channel类似流。
数据可以从Channel读到Buffer中,也可以从Buffer 写 到Channel中。
创建的三种方式
- 使用字节流或RandomAccessFile来获取一个FileChannel实例
- 使用Channels工具类
- 使用FileChannel.open()
FileChannel fileChannel = FileChannel.open(Paths.get("D:\\channel.txt"),StandardOpenOption.CREATE,StandardOpenOption.WRITE,StandardOpenOption.APPEND);
FileChannel fileChannel = (FileChannel) Channels.newChannel(new FileInputStream("d:\\a.txt"));
FileChannel Channel = FileChannel.open(Paths.get("D:\\channel.txt"));
文件复制
public class CopyFile {
public static void main(String[] args) throws IOException {
// 创建通道
FileChannel readChannel = FileChannel.open(Paths.get("D:\\日常文件\\新建文件夹\\1.png"), StandardOpenOption.READ);
FileChannel writeChannel = FileChannel.open(Paths.get("D:\\copy.jpg"), StandardOpenOption.CREATE,StandardOpenOption.WRITE);
// 边读,边写
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (readChannel.read(buffer) > 0){
buffer.flip();
writeChannel.write(buffer);
buffer.clear();
}
// 关闭
readChannel.close();
writeChannel.close();
buffer.clear();
System.out.println("复制完毕");
}
}
7.2.2 内存映射文件
public class TestMapFile {
public static void main(String[] args) throws IOException {
long l = System.currentTimeMillis();
// 创建FileChannel
FileChannel read = FileChannel.open(Paths.get("D:\\game\\steam\\steamapps\\workshop\\content\\431960\\878654942\\1.mp4"), StandardOpenOption.READ);
FileChannel write = FileChannel.open(Paths.get("D:\\1.mp4" ),StandardOpenOption.CREATE,StandardOpenOption.WRITE);
// 直接内存映射文件
MappedByteBuffer map = read.map(FileChannel.MapMode.READ_ONLY, 0, read.size());
// 内存映射文件写入文件
write.write(map);
// 关闭
read.close();
write.close();
System.out.println("复制完毕");
long l1 = System.currentTimeMillis();
System.out.println((l1 - l));
}
}
7.2.3 Channel实现聊天(阻塞式网络编程)
public class NIOServer {
public static void main(String[] args) throws Exception {
// 创建服务器套接字通道
ServerSocketChannel listener = ServerSocketChannel.open();
// 绑定地址和端口号
listener.bind(new InetSocketAddress("localhost",8888));
// 监听
SocketChannel channel = listener.accept();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取
channel.read(buffer);
// 处理数据
buffer.flip();
String data = new String(buffer.array(),0,buffer.limit());
System.out.println(channel.getRemoteAddress() + "说:" + data);
buffer.clear();
// 关闭
listener.close();
}
}
public class NIOClient {
public static void main(String[] args) throws IOException {
// 创建SocketChannel
SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost",8888));
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
String data = "i love java";
buffer.put(data.getBytes());
buffer.flip();
// 写入
channel.write(buffer);
buffer.clear();
// 关闭
channel.close();
}
}
7.3 Selector选择器
选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一 起被注册的,并且使用选择器来更新通道的就绪状态。
- SelectionKey
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
NIO非阻塞式网络编程
ChatServer
public class ChatServer {
public static void main(String[] args) {
ServerSocketChannel listener = null;
try {
// 创建ServerSocketChannel
listener = ServerSocketChannel.open();
// 绑定地址
listener.bind(new InetSocketAddress("localhost",8888));
// 设置非阻塞式连接
listener.configureBlocking(false);
// 创建选择器
Selector selector = Selector.open();
// 把通道注册到选择器中
listener.register(selector, SelectionKey.OP_ACCEPT);
// 轮询
System.out.println("服务器已启动");
while (selector.select() > 0){
// 处理
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
// 判断
if (selectionKey.isAcceptable()){
// 表示有客服请求,接受请求
SocketChannel socketChannel = listener.accept(); // 不会阻塞
System.out.println(socketChannel.getRemoteAddress() + "进入了群聊");
// 设置非阻塞模式
socketChannel.configureBlocking(false);
// 注册到轮询
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 读取数据
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
try {
while ((len = channel.read(buffer)) > 0) {
buffer.flip();
String data = new String(buffer.array(),0,buffer.limit());
System.out.println(channel.getRemoteAddress() + "说:" + data);
buffer.clear();
}
// 开启线程回复
if (len == -1){
// 关闭客户端
System.out.println(channel.getRemoteAddress() + "退出了");
channel.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
// 客户端退出,删除轮询中的key
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
listener.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ChatClinet
public class ChatClient {
public static void main(String[] args) throws Exception {
//1创建SocketChannel
SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("localhost", 8888));
//2设置为非阻塞模式
socketChannel.configureBlocking(false);
//3控制台录入并写出
Scanner input=new Scanner(System.in);
while(true){
String data=input.nextLine();
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put(data.getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
if(data.equals("886")){
break;
}
}
//4关闭
socketChannel.close();
}
}
8 反射,枚举,注解
8.1 反射
类对象就是把类看成Class
的对象。获取类对象的方式有三种
- Object o = new Object; Class class1 = o.getClass();
- Class class1 = Object.class;
- Class class1 = Class.forName(“全限定名”);
Class对象功能:
获取功能:
1. 获取成员变量们
Field[] getFields() :获取所有public修饰的成员变量
Field getField(String name) 获取指定名称的 public修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name)
2. 获取构造方法们
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>... parameterTypes)
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?>[] getDeclaredConstructors()
3. 获取成员方法们:
* Method[] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4. 获取全类名
* String getName()
成员变量,获取Field
Class personClass = Person.class;
//1.Field[] getFields()获取所有public修饰的成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
//2.Field getField(String name)
Field a = personClass.getField("a");
//获取成员变量a 的值
Person p = new Person();
Object value = a.get(p);
System.out.println(value);
//设置a的值
a.set(p,"张三");
System.out.println(p);
//Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//Field getDeclaredField(String name)
Field d = personClass.getDeclaredField("d");
//忽略访问权限修饰符的安全检查
d.setAccessible(true);//暴力反射
Object value2 = d.get(p);
System.out.println(value2);
构造方法,获取Construct
Class personClass = Person.class;
//Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor constructor = personClass.getConstructor(String.class, int.class);
System.out.println(constructor);
//创建对象
Object person = constructor.newInstance("张三", 23);
System.out.println(person);
Constructor constructor1 = personClass.getConstructor();
System.out.println(constructor1);
//创建对象
Object person1 = constructor1.newInstance();
System.out.println(person1);
Object o = personClass.newInstance();//空参创建对象
System.out.println(o);
成员方法,获取method
//0.获取Person的Class对象
Class personClass = Person.class;
//获取指定名称的方法
Method eat_method = personClass.getMethod("eat");
Person p = new Person();
//执行方法
eat_method.invoke(p);
Method eat_method2 = personClass.getMethod("eat", String.class);
//执行方法
eat_method2.invoke(p,"饭");
//获取所有public修饰的方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
String name = method.getName();
System.out.println(name);
//method.setAccessible(true);
}
//获取类名
String className = personClass.getName();
System.out.println(className);//cn.itcast.domain.Person
习题
* 案例:
* 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
* 实现:
1. 配置文件
2. 反射
* 步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法
Field:pro.properties
className=cn.itcast.domain.Person
methodName=eat
Person p = new Person();
p.eat();
Student stu = new Student();
stu.sleep();
//1.加载配置文件
//1.1创建Properties对象
Properties pro = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
//2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
通过内省完成取值赋值
public class Test {
public static void main(String[] args) throws Exception{
Class<?> userClass = Class.forName("cn.qf.ketang.day812.demo04.User");
BeanInfo beanInfo = Introspector.getBeanInfo(userClass);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
System.out.println(propertyDescriptor.getName() + ":" + propertyDescriptor.getPropertyType());
}
// 设置属性
PropertyDescriptor pdName = new PropertyDescriptor("name", userClass);
Method nameWriteMethod = pdName.getWriteMethod();
nameWriteMethod.invoke(userClass, "黄铭");
PropertyDescriptor pdAge = new PropertyDescriptor("age", userClass);
Method ageWriteMethod = pdAge.getWriteMethod();
ageWriteMethod.invoke(userClass, 21);
// 获取属性
Method nameReadMethod = pdName.getReadMethod();
nameReadMethod.invoke(userClass);
System.out.println(nameReadMethod);
Method ageReadMethod = pdAge.getReadMethod();
ageReadMethod.invoke(userClass);
System.out.println(ageReadMethod);
}
}
8.2 简单工厂模式
制定一个规范接口,定义常量,抽象方法,方法的功能有别人实现。实现类的全限定名放到properties文件中,工厂类读取properties文件并完成相关操作。每次有新的实现类不用更改代码,之用更改相应的properties文件即可。
public interface Clothes {
void ready();
void start();
void end();
}
一个实现类
public class Dress implements Clothes {
@Override
public void ready() {
System.out.println("准备好裙子的布料");
}
@Override
public void start() {
System.out.println("开始制作裙子");
}
@Override
public void end() {
System.out.println("裙子制作完毕");
}
}
工厂类
public class ClotherFactory {
private static Properties properties = new Properties();
static {
FileInputStream fis = null;
try {
fis = new FileInputStream("clothes.properties");
properties.load(fis);
} catch (Exception e) {
System.out.println("初始化失败,你个憨批!");
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static Clothes creatClothes(String type) throws Exception{
Clothes clothes = null;
if (properties.containsKey(type)){
String property = properties.getProperty(type);
clothes = (Clothes) Class.forName(property).newInstance();
}
if (clothes != null){
clothes.ready();
clothes.start();
clothes.end();
}
return clothes;
}
}
8.3 单例模式
不通过构造方法直接创建对象。构造方法私有化,利用静态方法返回一个静态常量的引用地址,地址指向new对象
public class SingleTon {
//
// private static final SingleTon instens = new SingleTon();
//
// private SingleTon(){}
//
// public static SingleTon getInstance(){
// return SingleTon.instens;
// }
// private static boolean flag = false;
//
// private SingleTon() {
// if (flag == true) {
// throw new RuntimeException("不能用反射暴力破解");
// }
// }
//
// private static volatile SingleTon instens;
//
// public static SingleTon getInstens() {
// if (instens == null) {
// synchronized (SingleTon.class) {
// if (instens == null) {
// instens = new SingleTon();
// flag = true;
// }
// }
// }
// return instens;
// }
private SingleTon(){}
private static class Single{
private static final SingleTon instens = new SingleTon();
}
public static SingleTon getInstens(){
return Single.instens;
}
}
8.4 枚举和注解
枚举是一个引类类型。规定了取值范围的数据类型。枚举变量不能使用其他变量,只能使用枚举类型中已定义的常量。
注解(Annotation)是代码里的特殊标记,程序可以读取注解,-一般用于替代配置文件。
在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
定义注解使用@interface关键字,注解中只能包含属性。
注解属性类型:基本类型,String,Class类型,枚举类型,注解类型,以及以上类型的一位数组
-
元注解:用来描述注E解的注解。
-
@Retention:用于指定注E解可以保留的域。
- RetentionPolicy.CLASS: 注解记录在class文件中,运行Java程序时, JVM不会保留,此为默认值。
- RetentionPolicy.RUNTIME:注解记录在class文件中,运行Java程序时, JVM会保留,程序可以通过反射获取该注释。
- RetentionPolicy.SOURCE:编译时直接丢弃这种策略的注释。
-
@Target:
- 指定注解用于修饰类的哪个成员。
例子
public enum Sex {
MAN,WUMAN;
}
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
int age();
Sex sex();
}
public class Student {
private String name;
private int age;
private Sex sex;
public Student(String name, int age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public Student() {
}
@MyAnnotation(name = "黄铭",age = 21,sex = Sex.MAN)
public void show(){
try {
Class<?> aClass = Class.forName("cn.qf.ketang.day812.demo07Emue.Student");
Method method = aClass.getMethod("show");
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.name());
System.out.println(annotation.age());
System.out.println(annotation.sex());
} catch (Exception e) {
e.printStackTrace();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.show();
}
}
9 JDK8 新特性
9.1 函数式接口
9.1.1 格式
//只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
9.1.2 @FunctionalInterface注解
//一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注 意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
9.2 Lambda的延迟执行
9.2.1 性能浪费
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
9.2.2 Lambda优化日志
/*
使用Lambda优化日志案例
Lambda的特点:延迟加载
Lambda的使用前提,必须存在函数式接口
*/
public interface MessageBuilder {
//定义一个拼接消息的抽象方法,返回被拼接的消息
public abstract String builderMessage();
}
public class Demo02Lambda {
//定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口
public static void showLog(int level, MessageBuilder mb){
//对日志的等级进行判断,如果是1级,则调用MessageBuilder接口中的builderMessage方法
if(level==1){
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args) {
//定义三个日志信息
String msg1 = "Hello";
String msg2 = "World";
String msg3 = "Java";
showLog(1,() -> {return msg1+msg2+msg3;});
9.2.3 Lambda带返回值
public class Demo02Comparator {
public static Comparator<String> getComparator(){
return (o1, o2)->o2.length()-o1.length();
}
public static void main(String[] args) {
//创建一个字符串数组
String[] arr = {"aaa","b","cccccc","dddddddddddd"};
//输出排序前的数组
System.out.println(Arrays.toString(arr));//[aaa, b, cccccc, dddddddddddd]
//调用Arrays中的sort方法,对字符串数组进行排序
Arrays.sort(arr,getComparator());
//输出排序后的数组
System.out.println(Arrays.toString(arr));//[dddddddddddd, cccccc, aaa, b]
}
}
9.3 Supplier接口(生产数据)
//Supplier<T>接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
public class Demo01Supplier {
//定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String,sup(T t)
public static String getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
//调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
String s = getString(()->{
//生产一个字符串,并返回
return "胡歌";
});
System.out.println(s);
//优化Lambda表达式
String s2 = getString(()->"胡歌");
System.out.println(s2);
}
}
数组的最大值
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
int[] arr = {100,22,45,23,543};
int max1 = getMax(() -> {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
});
System.out.println(max1);
}
9.4 Consumer接口(消费数据)
consumer接口
/*
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。
Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
至于具体怎么消费(使用),需要自定义(输出,计算....)
*/
public static void method(String name, Consumer<String> con){
con.accept(name);
}
public static void main(String[] args) {
//调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
method("赵丽颖",(String name)->{
//对传递的字符串进行消费
//消费方式:直接输出字符串
System.out.println(name);
//消费方式:把字符串进行反转输出
String reName = new StringBuffer(name).reverse().toString();
System.out.println(reName);
});
}
AndThen默认方法
/*
Consumer接口的默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费
*/
public static void method(String s, Consumer<String> con1 ,Consumer<String> con2){
con1.andThen(con2).accept(s);//con1连接con2,先执行con1消费数据,在执行con2消费数据
}
public static void main(String[] args) {
//调用method方法,传递一个字符串,两个Lambda表达式
method("Hello",
(t)->{
//消费方式:把字符串转换为大写输出
System.out.println(t.toUpperCase());
},
(t)->{
//消费方式:把字符串转换为小写输出
System.out.println(t.toLowerCase());
});
}
格式化打印信息
public static void printInfo(String[] arr,Consumer<String> con1,Consumer<String> con2){
for (String message : arr) {
//andthen连接两个接口
con1.andThen(con2).accept(message);
}
}
public static void main(String[] args) {
String[] arr = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
printInfo(arr,(message) -> {
String name = message.split(",")[0];
System.out.print("姓名:"+name);
},(message) -> {
String age = message.split(",")[1];
System.out.println("。年龄:"+ age + "。");
});
}
9.5 Predicate接口
predicate
//作用:对某种数据类型的数据进行判断,结果返回一个boolean值
public static void method(String s,Predicate<String> pre){
System.out.println("字符串很长吗:" + pre.test(s));
}
public static void main(String[] args) {
String s = "abcdef";
method(s,str -> str.length() > 5);
System.out.println("==============");
}
and
/*
逻辑表达式:可以连接多个判断的条件
&&:与运算符,有false则false
||:或运算符,有true则true
!:非(取反)运算符,非真则假,非假则真
*/
public static void method(String s, Predicate<String> pre1,Predicate<String> pre2){
System.out.println(pre1.and(pre2).test(s));
}
public static void main(String[] args) {
String a = "abcdef";
method(a,s1 -> s1.length() > 5,s2 -> s2.contains("a"));
}
or
public static void method(String s,Predicate<String> pre1,Predicate<String> pre2){
System.out.println(pre1.or(pre2).test(s));
}
public static void main(String[] args) {
String a = "abcdef";
method(a,s1 -> s1.length() > 5,s2 -> s2.contains("a"));
}
negate
public static void method(String s,Predicate<String> pre){
System.out.println(pre.negate().test(s));
}
String a = "abcdef";
method(a,str -> str.length() > 5);
9.6 Function接口
Function
//Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
public static void change(String s, Function<String,Integer> fun){
System.out.println(fun.apply(s));
}
public static void main(String[] args) {
change(s,str->Integer.parseInt(str));把字符串类型的整数,转换为Integer类型的整数返回
}
andThen
public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2){
String ss = fun1.andThen(fun2).apply(s);
System.out.println(ss);
}
psvm{
change(s,str->Integer.parseInt(str)+10,i->i+);
}
9.7 stream
public static void main(String[] args) {
//创建一个List集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
//遍历listB集合
list.stream()
.filter(name->name.startsWith("张") && name.length()==3)
//.filter(name->name.length()==3)
.forEach(name-> System.out.println(name));
}
9.8 获取stream流
//把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String,String> map = new HashMap<>();
//获取键,存储到一个Set集合中
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
//获取值,存储到一个Collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对(键与值的映射关系 entrySet)
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entries.stream();
//把数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);
//可变参数可以传递数组
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream7 = Stream.of(arr);
String[] arr2 = {"a","bb","ccc"};
Stream<String> stream8 = Stream.of(arr2);
9.9 Stream流的常用方法
forEach
/*
forEach方法,用来遍历流中的数据
是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
*/
psvm{
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
stream.forEach(name -> sout(name));
}
Filter
/*
Stream流属于管道流,只能被消费(使用)一次
第一个Stream流调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了
IllegalStateException: stream has already been operated upon or closed
*/
Stream<String> stream2 = stream.filter( name -> name.startsWith("张"));
stream2.forEach(name-> System.out.println(name));
map
/*
将流中的元素映射到另一个流中,可以使用map方法
改方法需要一个function接口参数,可以将当前流中的类型数据转换为另一种类型的流
*/
Stream<String> stream = Stream.of("1", "2", "3", "4");
Stream<Integer> stream1 = stream.map(s -> Integer.parseInt(s));
stream1.forEach(i -> System.out.println(i));
count
//终结方法
//返回值是long类型
List<Integer> list1 = List.of(1,2,3,4,5,6,7);
Stream<Integer> stream = list1.stream();
long count = stream.count();
System.out.println(count);//7
limit
/*
延迟方法
Stream流中的常用方法_limit:用于截取流中的元素
limit方法可以对流进行截取,只取用前n个。方法签名:
*/
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr).limit(3);
stream2.forEach(name-> System.out.println(name));
skip
/*
Stream流中的常用方法_skip:用于跳过元素
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
*/
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr).skip(3);
stream.forEach(name-> System.out.println(name));
concat
/*
Stream流中的常用方法_concat:用于把流组合到一起
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
*/
Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
Stream<String> stream2 = Stream.of("美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼");
Stream<String> stream3 = Stream.concat(stream1,stream2);
练习
/*
练习:集合元素处理(传统方式)
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
5. 将两个队伍合并为一个队伍;存储到一个新集合中。
6. 根据姓名创建Person对象;存储到一个新集合中。
7. 打印整个队伍的Person对象信息。
*/
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
ArrayList<String> one1 = new ArrayList<>();
for (String name : one) {
if(name.length()==3){
one1.add(name);
}
}
//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
ArrayList<String> one2 = new ArrayList<>();
for (int i = 0; i <3 ; i++) {
one2.add(one1.get(i));//i = 0,1,2
}
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
ArrayList<String> two1 = new ArrayList<>();
for (String name : two) {
if(name.startsWith("张")){
two1.add(name);
}
}
//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
ArrayList<String> two2 = new ArrayList<>();
for (int i = 2; i <two1.size() ; i++) {
two2.add(two1.get(i)); //i 不包含0 1
}
//5. 将两个队伍合并为一个队伍;存储到一个新集合中。
ArrayList<String> all = new ArrayList<>();
all.addAll(one2);
all.addAll(two2);
//6. 根据姓名创建Person对象;存储到一个新集合中。
ArrayList<Person> list = new ArrayList<>();
for (String name : all) {
list.add(new Person(name));
}
//7. 打印整个队伍的Person对象信息。
for (Person person : list) {
System.out.println(person);
}
}
public static void main(String[] args) {
List<String> list = List.of("张无忌","周芷若","赵敏","张强","张三丰");
Stream<String> stream1 = list.stream().filter(name -> name.length()==3).limit(3);
List<String> two = List.of("古力娜扎","张无忌","赵丽颖","张三丰","尼古拉斯赵四","张天爱","张二狗");
Stream<String> stream2 = two.stream().filter(name -> name.startsWith("张")).skip(2);
// 合并 数据类型转换 遍历
Stream.concat(stream1, stream2).map(name -> new Person(name)).forEach(name -> System.out.println(name));
}
9.10 方法引用
通过对象名引用成员方法
/*
通过对象名引用成员方法
使用前提是对象名是已经存在的,成员方法也是已经存在
就可以使用对象名来引用成员方法
*/
public class MethodRefObject {
public void printUpperCase(String str) {
System.out.println(str.toUpperCase());
}
}
@FunctionalInterface public interface Printable { void print(String str); }
public class Demo04MethodRef {
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCase);
}
}
通过类名引用静态成员方法
/*
通过类名引用静态成员方法
类已经存在,静态成员方法也已经存在
就可以通过类名直接引用静态成员方法
*/
@FunctionalInterface
public interface Calcable {
int calc(int num);
}
public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, Math::abs);
}
}
通过super引用父类的成员方法
//接口
@FunctionalInterface
public interface Greetable {
void greet();
}
//父类
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
//子类
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(super::sayHello);
}
public static void main(String[] args) {
new Man().show();
}
}
this引用本类的成员方法
//函数式接口
@FunctionalInterface
public interface Richable {
void buy();
}
public class Husband {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(this::buyHouse);
}
}
public static void main(String[] args) {
new Husband().soHappy();
}
类的构造器引用
//函数式接口
public interface PersonBuilder {
Person buildPerson(String name);
}
public class Demo10ConstructorRef {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("赵丽颖", Person::new);
}
}
数组构造器引用
public class Demo12ArrayInitRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray(10, int[]::new);
}
}
10 JVM,GC
10.1 虚拟机
程序通过Class Loader
(类加载器)进入Runtime Data Area
(运行时数据区),运行时数据区包括:Stack
(栈),Heap
(堆),Method Area
(方法区),PC Rejister
(程序计数器),Native Method Stack
(本地方法栈)。
10.2 类加载
1 -verbose:class: 显示类加载过程
2 JVM有三个默认的类加载器:
2.1 引导类加载器:负责加载核心java库
2.2 扩展类加载器:在指定的目录中查找并加载java类
2.3 Apps类加载器:根据应用程序的类路径来加载java类
2.4 引导是扩展的父装载器,扩展是Apps的父扩展器
3 获取类加载器的方式
// Apps类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader classLoader = Test.class.getClassLoader();
// 扩展类加载器
ClassLoader systemClassLoaderParent = systemClassLoader.getParent();
ClassLoader classLoaderParent = SunEC.class.getClassLoader();
// 启动类加载器(c语言实现的加载器)
System.out.println(systemClassLoaderParent.getParent());
4 类加载过程
加载:通过一个类的完全限定名查找此类的字节码文件,并利用字节码文件创建class类
验证:文件格式验证,元数据验证,字节码验证,符号引用验证
准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象-起分配到Java堆中。
解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一一个间接定位到目标的句柄。
初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
5 双亲委托机制
- 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
-
当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
-
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
可以避免重复加载
加载文件:
InputStream inputStream = Test2.class.getClassLoader().getResourceAsStream("clothe.properties");
Properties properties = new Properties();
properties.load(inputStream);
inputStream.close();
properties.list(System.out);
10.3 运行时数据区
https://i.loli.net/2020/08/17/zrh5BELWvyQRj2m.png
10.4 程序计数器
1 一块较小的的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。
2 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。
3 此内存区域是唯–个在 Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
10.5 虚拟机栈和本地方法栈
- 虚拟机栈的特征
- 线程私有
- 后进先出(LIFO)栈
- 存储栈帧,支撑Java方法的调用、执行和退出
- 可能出现OutOfMemoryError异常和StackOverflowError异常
- 本地方法栈的特征
- 线程私有
- 后进先出(LIFO)栈
- 作用是支撑Native方法的调用、执行和退出
- 可能出现OutOfMemoryError异常和StackOverflowError异常
- HotSpot虚拟机将Java虚拟机栈和本地方法栈合并实现
10.6 栈帧
一个县城对应一个JVM栈,栈用来存储栈侦,一个栈帧对应一个方法。一个方法的调用与执行完毕,就是一个栈帧在JVM中入栈和出栈的过程。一个完整的栈帧包括,局部变量表,操作数栈,动态连接,方法返回地址等附加信息。
局部变量表:●由若千个Slot (槽) 组成,长度由编译期决定,Code属性的locals指定。
●单个Slot可以存储一 个类型为boolean、 byte、 char, short, int. float,reference和 returnAddress (已过时)的数据,两个Slot可以存储-个类型为long或double的数据。
●局部变量表用于方法间参数传递,以及方法执行过程中存储基本数据类型的值和对象的引用
操作数栈:●是一个后进先出栈,由若千个Entry组成,长度由编译期决定、由Code属性的stacks指定。
●单个Entry即可以存储-一个Java虚拟机中定义的任意数据类型的值,包括long和double类型,但是存 储long和double类型的Entry深度为2,其他类型的深度为1
●在方法执行过程中,操作数栈用于存储计算参数和计算结果;在方法调用时,操作数栈也用来准备调用 方法的参数以及接收方法返回结果。
栈帧演示:●如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量时,Java虚拟机将会抛出-一个 StackOverflowError异常。
●如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到
足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,
那Java虚拟机将会抛出一个OutOfMemoryError异常。
●调整栈空间大小: -Xss1m
●在JDK1.5之前栈默认大小为256k,JDK1.5之后为1m。
动态连接:●每个栈帧内部都包含-个指向运行时常量池中该栈帧所属方法的引用。
●每一次方法调用时,动态的将符号引用转成直接引用(入口地址),支持多态。
**方法返回地址:**●方法结束有正常结束和异常结束:
●正常结束:
●当前栈帧承担着恢复调用者状态的责任,其中包括恢复调用者的局部变量表和操作数栈、
正确递增程序计数器、将返回值压入调用者的操作数栈。
●异常结束:
●如果当前方法中没有处理此种异常、当前栈帧恢复调用者状态的同时,不会返回任何值,
而是通过athrow指令将当前异常抛给调用者。
10.7 堆
10.7.1 特征
- 全局共享
- 通常是Java虚拟机中最大的一块内存区域
- 作用是做为Java对象或数组的主要存储区域
- JVMS明确要求该区域需要实现自动内存管理,即常说的GC,但并不限制采用哪种算法和技术去实现
- 可能出现OutOfMemoryError异常
10.7.2 相关参数
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:+ PrintGC:打印查看GC日志
- -XX:+PrintGCDetails:打印查看GC日志详情
- .-XX:+ PrintCommandLineFlags:打印虚拟机的参数
10.7.3 分代存储相关参数
- -Xmn:新生代大小
- -XX:NewRatio=?:表示年轻代和老年代的比例,默认1:2
- -XX:SurvivorRatio=?: Eden和Survivor的比例, 默认是8:1:1, 但实际调整6:1:1
10.7.4 对象分配机制
- 优先分配Eden区:绝大多数对象都是“朝生夕死”的对象, Eden区的回收时间短、效率高,适用于频繁回收。
- 大对象直接进入老年代: Enden和Survivor的空间不足时, 大对象直接进入老年代。-XX:PretenureSize’ Threshold=KB ,对象大小超过此值,直接进入老年代。
- 长期存活对象进入老年代:在Survivor区存活N岁(来回复制N次)的对象,将直接进入老年代。-XX:MaxTenuringThreshold= 15(只有单线程收集器可用)
10.7.5 java对象
- 对象:对象头、实例数据、对齐补白三部分组成。
- 对象头: Mark Word(8)、Klass Pointer(4)、数组长度
- 实例数据:对象属性信息
- 对齐填充:必须是8的倍数
10.7.6 堆内存异常
如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那Java虚拟
机将会抛出-一个OutOfMemoryError异常。
10.8 方法区与运行时常量池
10.8.1 方法区的特征
- 全局共享
- 作用是存储Java类的结构信息、常量、静态变量、即时编译器编译后的代码
- JVMS不要求该区域实现自动内存管理,但是商用Java虚拟机都能够自动管理该区域
的内存 - 可能出现OutOfMemoryError异常
10.8.2 常量池的特征
- 全局共享
- 是方法区的一部分
- 作用是存储Java类文件常池中的符号信息
- 可能出现OutOfMemoryError异常
10.9 逃逸分析
使用逃逸分析可以分析新创建的对象的适用范围,并决定是否在堆内分配内存的一项技术。如果一份方法创建了一个对象,那个这个对象有可能通过形参或者方法返回值传递到其他地方,那么这就成为对象逃逸。反之则没有逃逸。
开启逃逸分析:xX:+DoEscapeAnalysis
关团逃逸分析: -Xx- DosapeAnalysis
显示分析结果: X:tEcapenaxisi
-
锁消除:线程同步非常消耗性能,当一个对象只存在当前线程时,移除该对象的同步锁。开启锁消除: -XX:+ EliminateLocks 关闭锁消除: -XX:-EliminateLocks
-
标量替换
- 标量:不能被进一步分解的数据,基础类型和对象的引用可以理解为标量
- 聚合量:可以被进一步分解成标量,比如对象
- 标量替换:将对象成员变量分解为分散的标量,这就叫做标量替换。
如果一个对象没有发生逃逸,那压根就不用创建它,只会在栈或者寄存器.上创建它用到的成员标量,节省了内存空间,也提升了应用程序性能。
- 开启标量替换: -XX:+ EliminateAllocations
- 关闭标量替换: -XX:-EliminateAllocations
- 显示标量替换详情: -XX:+ PrintEliminateAllocations
-
栈上分配
当对象没有发生逃逸时,该对象就可以通过标量替换分解成成员标量分配在栈内存中,和方法的生命周期一致,随着栈帧出栈时销毁,减少了GC压力,提高了应用程序性能。
10.10 垃圾回收
可达性分析算法
通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当-个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。此算法解决了循环引用的问题。
Root对象:可作为GC Roots的对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 字符串常量池里的引用
- 本地方法栈中JNI (Native方法) 引用的对象
- synchronized锁对象
- Class对象
4钟对象引用
- 强引用(Strong Reference)
●垃圾收集器永远不会回收存活的强引用对象。类似"Object obj=new Object()"这类的引用 - 软引用(Soft Reference)
●还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范
围之中进行第二次回收。 - 弱引用(Weak Reference)
●当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象。 - 虚引用(Phantom Reference)
●无法通过虚引用来取得一 个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个
对象被收集器回收时收到一个系统通知。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3d5oX5bM-1598063283997)(https://i.loli.net/2020/08/18/QrvTJUmZVtS6agj.png)]