集合概述
Java集合存放在java.util包中,是一个用来存放对象的容器
1.集合只能存放对象。比如有一个int类型的数据放入集合中,它将自动转换成Integer类后存入,Java中每一种基本类型都有对应的引用类型。
2.集合存放的是多个对象的引用,对象本身还是存放在堆内存中
3.集合可以存放不同类型,不限数量的数据类型
Java集合可分为Set,List,和Map三种大体系
- Set: 无序、不可重复的集合
- List: 有序、可重复的集合
- Map: 具有映射关系的集合
在JDK5之后,增加了泛型,Java集合可以记住容器中对象的数据类型
ArrayList
ArrayList 和 Vector 是List接口的两个典型实现
区别:
- Vertor是一个古老的集合,通常建议适应ArrayList
- ArrayList是线程不安全的,而Vector是线程安全的
- 即使为保证List集合线程安全,也不推荐使用Vector
import java.util.ArrayList; //引入ArrayList类
ArrayList objectName = new ArrayList<>(); //初始化
E是泛型数据,用于设置objectName的数据类型,只能为引用数据类型
objectName 对象名
添加元素
objectName.add(E e);
截取元素
ArrayList newlist = objectName.subList(startIndex,endIndex);
访问元素
E e = objectName.get(int index);
删除元素
objectName.remove(int index);
修改元素
objectName.set(int index, E e);
计算大小
int size = objectName.size();
迭代数组列表
for (int i = 0; i
LinkedList
HashSet
HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。我们大多数时候说的Set集合指的都是HashSet
HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能
HashSet具有一下特点:
- 不能保证元素的排列顺序
- 不可重复
- HashSet不是线程安全的
- 集合元素可以使null
当向HashSet集合中存入一个元素时,HashSet会调用该对象的HashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置。如果两个元素的equals() 方法返回true,但它们的hashCode返回值不相等,hashSet将会把它们存储在不同的位置,但依然可以添加成功。
如果两个元素的equals 相等,那么他们的hashCode 也应该相等。
添加元素
set.add(E e);
删除元素
set.remove(E e);
判断元素是否存在
set.contains(E e);
计算大小
int size = set.size();
迭代集合
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
for (E e : set) {
System.out.println(e);
}
TreeSet
Treeset是SortedSet接口的实现类,TreeSet可以确保集合元素处排序状态。TreeSet支持两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
TreeSet会调用集合元素的Comparator(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序 排列
如果 this > obj,返回正数1
如果 this < obj,返回负数 -1
如果 this = obj,返回0
必须放入同样类的对象,否则可能发生类型转换异常,可以使用泛型来进行限制。
自定义排序代码示例
//People.java
import java.util.Comparator;
public class People implements Comparator {
public int age;
public String name;
public People(){
}
public People(int age, String name){
this.age = age;
this.name = name;
}
@Override
public int compare(People o1, People o2) {
if(o1.age > o2.age){
return 1;
}else if (o1.age < o2.age){
return -1;
}else{
return 0;
}
}
}
//test.java
import java.util.Set;
import java.util.TreeSet;
public class Test{
public static void main(String[] args) {
People p1 = new People(12,"张三");
People p2 = new People(9,"李四");
People p3 = new People(25,"王五");
People p4 = new People(10,"赵六");
Set set = new TreeSet(new People());
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
for(People p :set){
System.out.println(p.name + ":" + p.age);
}
}
}
HashMap
Map用于保存具有映射关系的数据,因此Map集合里保存这两组值,一组值用于保存Map里的Key,另一组用于保存Map里的Value
Map中的key和value都可以是任何引用类型的数据,Map中的Key不允许重复,即同一个Map对象的任何两个Key通过equals方法比较中返回false
Key和Value之间存在单向一对一关系,即通过指定的Key总能找到唯一的,确定的Value
HashMap和Hashtable是Map接口的两个典型实现类区别:
- Hashtable是一个古老的Map实现类,不建议使用
- Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的
- Hashtable不允许使用null作为key和value,而HashMap可以与HashSet集合不能保证元素顺序一样,Hashtable、HashMap也不能保证key-value对的顺序
- Hashtable 和HashMap判断两个Key相等的标准是:两个Key通过equals方法返回true,hashCode值也相等
添加元素
map.put(key, value);
删除元素
map.remove(key);
判断键是否存在
map.containsKey(key);
判断值是否存在
map.containsValue(1);
访问元素
map.get(key);
计算大小
int size = map.size();
迭代集合
Set keys = map.keySet(); //获取map集合的key集合
for(String key:keys){
System.out.println(key + ":" +map.get(key));
}
Set> sets = map.entrySet();
for(Map.Entry set : sets){
System.out.println(set.getKey() + ":" + set.getValue());
}
TreeMap
TreeMap存储Key_Value对时,需要根据Key对Key-Value对进行排序。TreeMap可以保证所有的Key-Value对处于有序状态
TreeMap的Key的排序:
- 自然排序:TreeMap所有的Key必须实现Comparable接口,而且所有的Key应该是同一个类的对象,否则将会抛出ClassCastException
- 定制排序(了解):创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有key进行排序。此时不需要Map的key实现Comparable接口
Collections
Collections是一个操作Set、List、Map等集合的工具类
Collections中提供了大量的方法对集合元素进行排序、查询修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
反转List中元素顺序
reverse(List)
对List中的元素进行随机排序
shuffle(List)
交换两个元素位置
swap(List,i,j)
sort 根据Comparator指定的顺序,进行排序
//People.java
等同于TreeSet 中的people.java
//test.java
import java.util.*;
public class Test{
public static void main(String[] args) {
People p1 = new People(12,"张三");
People p2 = new People(9,"李四");
People p3 = new People(25,"王五");
People p4 = new People(10,"赵六");
List list = new ArrayList();
list.add(p1);
list.add(p2);
list.add(p3);
list.add(p4);
for(People p :list){
System.out.println(p.name + ":" + p.age);
}
System.out.println("--------------------");
Collections.sort(list,new People());
for(People p :list){
System.out.println(p.name + ":" + p.age);
}
}
}
max,min 根据Comparator指定的顺序,返回集合中的最大值最小值
//people.java
等同于TreeSet 中的people.java
//test.java
People max_p = Collections.max(list ,new People());
People min_p = Collections.min(list ,new People());
System.out.println(max_p.name + ":" + max_p.age);
System.out.println(min_p.name + ":" + min_p.age);
返回指定元素出现的次数
Collections.frequency(list, "");
多态
一个引用变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
Student m = new Student();
m.school = "sdut"; // 合法,student类中有school成员变量
Person e = new Student();
e.school = "sdut"; //非法,Person类没有school成员变量
属性是在编译时确定的,编译时e为Person 类型,没有school成员变量,因而编译错误。
多态方法的调用
//正常的方法调用
Person p = new Person();
p.getInfo();
Student s = new Student();
s.getInfo();
//虚拟方法调用(多态情况下)
Person e = new Student();
e.getInfo(); //调用Student类的getInfo() 方法
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。--动态绑定
static
使用范围
在Java类中,可用static修饰属性、方法、代码块、内部类
被修饰后的成员特点
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可以不创建对象,直接被类调用
static修饰的方法
因为不需要实例就可以访问static方法,因此static方法内部不能有this也不能有super。重载方法需要同时为static或者非static
非静态代码块
没有static修饰的代码块
可以有输出语句,可以对类的属性声明进行初始化操作,可以调用静态和非静态的变量或方法,若有多个非静态代码块,那么按照从上到下的顺序依次执行,每次创建对象的时候,都会执行一次。且先于构造器执行。
静态代码块
用static修饰的代码块
可以有输出语句,可以对类属性声明进行初始化操作,不可以对非静态的属性初始化即不可以调用非静态的属性和方法,若有多个静态的代码块,那么按照 从上到下的顺序依次执行,静态 代码块的执行要先于非静态代码块,静态代码块只执行一次
final
- 在Java中声明类、属性、方法时,可使用关键字final来修饰,表示“最终”
- final 标记的类不能被继承。提高安全性,提高程序的可读性
- final 标记的方法不能被子类重写
- final 标记的变量即成为常量
单例设计模式
饿汉式设计模式
public class Single {
//构造方法需要执行很长时间,适合单例模式
private Single(){
//私有构造,不能通过new 创建队对象
}
private static Single single = new Single();
public static Single getInstance(){
return single;
}
}
懒汉式设计模式
public class Single {
//构造方法需要执行很长时间,适合单例模式
private Single(){
//私有构造,不能通过new 创建队对象
}
private static Single single = null;
public static Single getInstance(){
if(single == null) {
single = new Single();
}
return single;
}
}
模板设计模式
抽象类体现的就是一种模板模式的设计,抽象作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会 保留抽象类的行为模式。
解决问题:
当功能内部实现使确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
编写一个抽象的父类,父类提供多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
public abstract class Template {
public abstract void code();
public void getTime(){
long startTime = System.currentTimeMillis();
code();
long endTime = System.currentTimeMillis();
System.out.println(endTime-startTime);
}
}
class Child extends Template{
@Override
public void code() {
int k = 0;
for(int i = 0; i < 100000; i++){
k++;
}
System.out.println(k);
}
}
工厂设计模式
开闭原则:软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的
简单工厂模式
简单工厂模式又称静态工厂方法模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其它类的实例,被创建的实例通常都有共同的父类。
代码实现
public class SimpleFactory {
public static Product createProduct(String type) {
if (type.equals("A")) {
return new ProductA();
} else {
return new ProductB();
}
}
public static void main(String[] args) {
Product product = createProduct("A");
product.print();
}
}
abstract class Product{
public abstract void print();
}
class ProductA extends Product{
@Override
public void print() {
System.out.println("ProductA");
}
}
class ProductB extends Product{
@Override
public void print() {
System.out.println("ProductB");
}
}
工厂方法模式
定义一个用于创建对象的接口,让子类决定去实例化哪个类。工厂方法使一个类的实例化延伸到其子类
代码实现
//Product.java
public interface Product {
void print();
}
class ProductA implements Product{
@Override
public void print() {
System.out.println("生产产品A");
}
}
class ProductB implements Product{
@Override
public void print() {
System.out.println("生产产品B");
}
}
class ProductC implements Product{
@Override
public void print() {
System.out.println("生产产品C");
}
}
//Factory.java
public interface Factory {
Product createProduct();
}
class ProductAFactory implements Factory{
@Override
public Product createProduct() {
return new ProductA();
}
}
class ProductBFactory implements Factory{
@Override
public Product createProduct() {
return new ProductB();
}
}
class ProductCFactory implements Factory{
@Override
public Product createProduct() {
return new ProductC();
}
}
//test.java
public class Test{
public static void main(String[] args) {
Product productA = new ProductAFactory().createProduct();
productA.print();
Product productB = new ProductBFactory().createProduct();
productB.print();
Product productC = new ProductCFactory().createProduct();
productC.print();
}
}
使用工厂模式,使 用的人也就是编写test.java 文件中的代码的人与建立对象的人互不影响,当改变Product.java文件中的内容,只会影响Factory.java工厂文件。
抽象类
用abstract关键字来修饰一个类是,这个类叫做抽象类
用abstract来修饰一个方法时,该方法叫做抽象方法,抽象方法只有方法的声明,没有方法的实现。
含有抽象方法的类必须声明为抽象类
抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍未抽象类。
不能用abstract修饰属性、私有方法、构造器、静态方法、final方法
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类
抽象类不能被实例化,是用来被继承的,抽象类的子类 必须 重写父类的抽象方法,并提供方法体,但是抽象类可以有构造方法。
内部类
使用内部类的作用就是解决Java不能多重继承的问题
可以声明为final
可以声明为private或protected
可以声明为static,但此时就不能使用外层类的非static的成员变量
可以声明为abstract类,可以被其它的内部类继承
非static的内部类中的成员不能声明为static,只有在外部类或static的内部类才能声明static成员
public class Test{
public static void main(String[] args) {
new A().text();
}
}
class A{
public void text(){
new InnerB().print();
new InnerC().print();
}
public class InnerB extends B{
@Override
public void print() {
System.out.println("class B");
}
}
public class InnerC extends C{
@Override
public void print() {
System.out.println("class C");
}
}
}
class B{
public void print(){}
}
class C{
public void print(){}
}
匿名内部类
//构建了一个没有类名的People的子类
People people = new People(){
@Override
public void showAge() {
super.showAge();
}
};
}
因为其没有类名没有办法通过new的方式创建对象,如果还要在构造器中初始化属性就没有办法,这种情况就要用代码块做初始化的工作,这种情况下可以通过代码块代替构造函数的工作。
异常处理
如果父类方法抛出一个异常,子类去重写这个方法时也要抛出一个异常,并且,抛出的异常不能比父类的大
人工抛出异常
Java异常类对象除在程序执行过程中出现异常时有系统自动生成并抛出,也可根据需要人工创建并抛出
throw new IOException();
创建用户自定义异常类
用户自定义异常类MyException,用于描述数据取值范围错误信息。用户自己的异常类必须继承现有的异常类
public class Test{
private int num;
public static void main(String[] args) {
Test test = new Test();
try{
test.setNum(-100);
}catch (MyException e){
e.printStackTrace();
}
}
public void setNum(int num) throws MyException {
if(num > 0){
this.num = num;
}else{
throw new MyException("数字必须大于等于0");
}
}
}
class MyException extends Exception{
public MyException(String msg){
super(msg);
}
}
泛型
泛型时JDK1.5新加入的,解决数据类型的安全性问题,其主要原理是在类声明时通过一个表示表示类中某个属性的类型或者时某个方法的的返回值以及参数类型。这样在类声明或实例化时只要指定好需要的具体类型即可。
Java泛型可以 保证如果程序在编译时没有发出警告,运行时就不会残生ClassCastException异常。同时,代码更加简洁健壮。
Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加检查和类型转换的方法,也就是说,泛型信息不会进入到运行时的阶段。
泛型类
public class Test{
public static void main(String[] args) {
A a1 = new A();
a1.setT("123");
}
}
//自定义一个泛型类
class A{
private T t;
public void setT(T t) {
this.t = t;
}
public T getT() {
return t;
}
}
泛型接口
public class Test{
public static void main(String[] args) {
A a1 = new A();
a1.setT("123");
//使用时不需要指定泛型,因为泛型已经确定
B b1 = new B();
b1.setT("123");
}
}
interface I{
void setT(T t);
T getT();
}
//未传入泛型实参时,与 泛型类的定义相同在声明的时候,需要将泛型的声明也一起加入到类中。
class A implements I{
private T t;
@Override
public void setT(T t) {
this.t = t;
}
@Override
public T getT() {
return t;
}
}
//如果实现接口时指定接口的泛型的具体数据类型,这个类实现接口所有方法的位置都要用泛型替换实际的具体数据类型
class B implements I{
private String t;
@Override
public void setT(String s) {
}
@Override
public String getT() {
return t;
}
}
泛型方法
//有参数的泛型方法
public void setT(T t){
System.out.println(t);
}
//有返回值的泛型方法
public T get(T t){
return t;
}
//形参为可变参数的泛型方法
public void print(T... t){
for(T item:t){
System.out.println(t);
}
}
在类上定义的泛型可以在普通方法中使用,在静态方法中不能使用类定义的泛型,如果使用只能使用静态方法自己定义的泛型
通配符
不确定集合中的元素具体的数据类型,使用?表示所有类型
import java.util.ArrayList;
import java.util.List;
public class Test{
public static void main(String[] args) {
A a = new A();
List list1 = new ArrayList();
List list2 = new ArrayList();
a.print(list1);
a.print(list2);
}
}
class A{
public void print(List> list){
}
}
有限制的通配符
import java.util.List;
public class Test{
public void print1(List extends C> list){
//list集合中的元素只能是C和C的子类
}
public void print2(List super B> list){
//list集合中的元素只能是B 和B的父类
}
public void print1(List extends C> list){
//list集合中的元素只能是实现D接口的实现类的引用调用
}
}
class A{}
class B extends A{}
class C extends B{}
class D extends C{}
interface D{}
枚举
JDK1.5新增的enum关键字用于定义枚举类
枚举类和普通类的区别:
- 使用enum定义的枚举类默认继承了java.lang.Enum类
- 枚举类的构造器只能使用private访问控制符
- 枚举类的所有实例必须在枚举类中显式列出(, 分割; 结尾),列出的实例系统会自动添加public static final修饰
- 所有的枚举类都提供了一个values方法,该方法可以很方便的遍历所有的枚举值
JDK1.5中可以在switch表达式中国使用枚举对象作为表达式,case子句可以直接使用枚举值的名字,无需添加枚举类作为限定。
使用场景和方法
public class Test{
public static void main(String[] args) {
Test test = new Test();
Color color = test.judgeColor(3);
System.out.println(color == Color.GREEN ? "绿色的":"不是绿色");
}
public Color judgeColor(int number){
if(number == 1){
return Color.RED;
}else if(number == 2){
return Color.GREEN;
}else if(number == 3){
return Color.WHITE;
}else if(number == 4){
return Color.YELLOW;
}else{
return Color.BLACK;
}
}
}
enum Color{
RED,GREEN,BLACK,WHITE,YELLOW
}
switch和枚举结合
//->可以不加break : 必须加
int number = Integer.parseInt(new Scanner(System.in).next());
Color color = test.judgeColor(number);
switch (color){
case RED -> System.out.println("红色");
case BLACK -> System.out.println("黑色");
case GREEN -> System.out.println("绿色");
case WHITE -> System.out.println("白色");
case YELLOW -> System.out.println("黄色");
default -> System.out.println("error");
}
注解
从JDK5.0开始,Java增加了对元数据(MetaData)的支持,也就是Annotation(注释)
Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理,通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。
Annotation可以像修饰符一样被使用,可以修饰包,类构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的name=value对中
Annotation能被用来为程序 元素(类,方法,成员变量等)设置元数据
使用Annotation时要在其前面加@符号,并把该Annotation当成一个修饰符使用,用于 修饰他支持的程序元素
三个基本的Annotation
- @Override:限定重写父类方法,该注释只能用于方法
- @Deprecated:用于表示 某个程序元素(类,方法等)已过时
- @SuppressWarnings:抑制编译器的警告
自定义Annotation
定义新的Annotation类型使用@interface关键字
Annotation的成员变量在Annotation定义中无参数方法的形式来声明,其方法名和返回值定义了该成员的名字和类型
可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字
没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元素据Annotation
public class Test{
@MyAnnotation(id = 1, name = "Frist")
private int number;
}
@Target(ElementType.FIELD) //修饰属性
@Retention(RetentionPolicy.RUNTIME) //声明周期
@Documented
@interface MyAnnotation{
public int id() default 0;
public String name() default "";
}
File类
File类代表与平台无关的文件和目录。File能创建、删除、重命名文件和目录,但File不能访问文件内同本身。如果需要访问文件内容本身,则需要使用输入/输出流
File 类常见的构造方法:
- public File(String pathname) 以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统user.dir中存储。
- public File(String parent, String child) 以parent为父路径,child为子路径创建File对象。File的静态属性String separate存储了当前系统的分隔符。
常用方法
getName() //是路径就获取当前路径名,是文件就获取文件名
getPath() //获取相对路径
getAbsoluteFile() //获取绝对路径,返回的是File对象
getAbsolutePath() // 获取绝对路径,返回的是字符串
renameTo(newName) //重命名
exists() // 检测文件或路径是否存在
canWrite() //检测文件是否可写
canRead() //检测文件是否可读
isFile() //判断是否是文件
isDirectory() //判断是否为文件夹
lastModify() // 获取文件最后修改时间
Length() //获取文件长度
createNewFile() //创建新的文件
delete() //删除文件
mkDir() //创建新的文件夹,如果创建多层的话需要一层一层执行
mkDirs() //可以创建多层文件夹
list() // 返回当前文件夹下的所有子集,返回 类型是String数组
listFiles() // 返回当前文件夹下的所有子集,返回 类型是File数组
递归循环遍历文件夹中的文件和文件夹
import java.io.File;
public class Test {
public static void main(String[] args) {
Test test = new Test();
File file = new File("D:\\program files\\IdeaProject");
test.getFile(file);
}
public void getFile(File file) {
if(file.isFile()){
System.out.println(file.getAbsoluteFile()+"是一个文件");
}else{
System.out.println(file.getAbsoluteFile()+"是一个文件夹");
File[] files = file.listFiles();
if (files !=null && files.length>0){
for(File f:files){
getFile(f);
}
}
}
}
}
IO流
流的分类
- 按操作数据单位不同分为:字节流(8bit),字符流(16bit)
- 按数据流的流向不同分为:输入流,输出流
-
按流的角色不同分为:节点流,处理流
IO流体系
通过文件字节流复制文件内容
//通过字节流打印字符串
System.out.println(new String(bytes,0,len));
//通过字节流写字符串
getBytes() 是Java编程语言中将一个字符串转化为一个字节数组byte[]的方法
String str = "********"
fileOutputStream.write(str.getBytes());
public class Test {
public static void main(String[] args) throws Exception {
Test test = new Test();
File file = new File("C:\\Users\\13667\\Documents\\file.txt");
test.copyFile(file);
}
public void copyFile(File file) throws Exception {
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[100];
int len = 0;
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\13667\\Documents\\copy.txt");
while((len = fileInputStream.read(bytes))!=-1){
fileOutputStream.write(bytes,0, len); //将内容写入缓冲区,第一个参数是缓冲数据数组,第二个参数是数组的开始位置,第三个参数是数组结束位置
}
fileOutputStream.flush(); //将数据刷入硬盘
fileOutputStream.close();
fileInputStream.close();
}
}
通过文件字符流复制文件内容
//通过字符流输入字符串
String str = "*******"
fileWriter.write(str);
//通过字符流打印字符串
System.out.println(chars);
public class Test {
public static void main(String[] args) throws Exception {
Test test = new Test();
File file = new File("C:\\Users\\13667\\Documents\\file.txt");
File copy = new File("C:\\Users\\13667\\Documents\\copy.txt");
test.copyFile(file, copy);
}
public void copyFile(File file, File copy) throws Exception {
FileReader fileReader = new FileReader(file);
char[] chars = new char[100];
int len = 0;
FileWriter fileWriter = new FileWriter(copy);
while((len = fileReader.read(chars))!=-1){
fileWriter.write(chars, 0, len);
}
fileWriter.flush();
fileWriter.close();
fileReader.close();
}
}
缓冲流
为了提高数据的读写速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组。
根据数据操作单位可以把缓冲流分为:
- 缓冲字节流 BufferedInputStream BufferedOutputStream
- 缓冲字符流 BufferedReader BufferedWriter
缓冲流要套接在相应的节点流之上,对 读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法
对于输出的缓冲流,写出的数据会先在内存中缓存,使用flush()将会使内存中的数据立刻写出
缓冲字节流复制文件内容
import java.io.*;
public class Test {
public void copyByBufferedStream() throws Exception {
FileInputStream fileInputStream = new FileInputStream("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/file.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
FileOutputStream fileOutputStream = new FileOutputStream("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/copy.txt");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
byte[] bytes = new byte[100];
int len = 0;
while ((len = bufferedInputStream.read(bytes))!=-1){
bufferedOutputStream.write(bytes,0, len);
System.out.println(new String(bytes,0,len));
}
bufferedOutputStream.flush();
bufferedOutputStream.close();
fileOutputStream.close();
bufferedInputStream.close();
fileInputStream.close();
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.copyByBufferedStream();
}
}
缓冲字符流复制文件内容
import java.io.*;
public class Test {
public void copyByBufferedReader() throws Exception {
FileReader filereader = new FileReader("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/file.txt");
BufferedReader bufferedReader = new BufferedReader(filereader);
FileWriter fileWriter = new FileWriter("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/copy.txt");
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
char[] chars = new char[100];
int len = 0;
while((len = bufferedReader.read(chars))!=-1){
bufferedWriter.write(chars,0,len);
System.out.println(chars);
}
bufferedWriter.flush();
bufferedWriter.close();
fileWriter.close();
bufferedReader.close();
filereader.close();
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.copyByBufferedReader();
}
}
转换流
通过转换流复制文件
import java.io.*;
public class Test {
public void copyByInputStreamReader() throws Exception {
FileInputStream fileInputStream = new FileInputStream("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/file.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");
FileOutputStream fileOutputStream = new FileOutputStream("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/copy.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"UTF-8");
char[] chars = new char[100];
int len = 0;
while((len = inputStreamReader.read(chars))!=-1){
System.out.println(chars);
outputStreamWriter.write(chars,0,len);
}
outputStreamWriter.flush();
outputStreamWriter.close();
fileOutputStream.close();
inputStreamReader.close();
fileInputStream.close();
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.copyByInputStreamReader();
}
}
标准输入,输出流
使用标准输入流写入文件
public class Test {
public void inputPrint() throws Exception {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String input = "";
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/input.txt"));
while ((input = bufferedReader.readLine())!=null){
if(input.equals("over")){
break;
}
bufferedWriter.write(input);
}
bufferedWriter.flush();
bufferedWriter.close();
bufferedReader.close();
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.inputPrint();
}
}
数据输入输出流
示例
//用数据输出流写入文件里的数据,是乱码的,只能通过对应数据输入流读取
public class Test {
public void dataStream() throws Exception {
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/input.txt"));
dataOutputStream.writeDouble(1.35d);
dataOutputStream.writeInt(100);
dataOutputStream.writeBoolean(true);
dataOutputStream.flush();
dataOutputStream.close();
DataInputStream dataInputStream = new DataInputStream(new FileInputStream("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/input.txt"));
double len = dataInputStream.readDouble();
System.out.println(len);
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.dataStream();
}
}
对象流
用于存储和读取对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从 数据源中还原回来。
序列化和反序列化针对的是对象的各种属性,不包括类的属性
序列化(Serialize):用ObjectOutputStream类将一个Java对象写入IO流中
反序列化(Deserialize):用ObjectInputstream 类从IO流中恢复该Java对象
ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
对象的序列化机制允许把内存中的Java对象转换成平台无关的二进制 流,从而允许把这种二进制流持久的保存到磁盘上,或通过网路将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复原来的Java对象
序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
序列化是RMI过程的参数和返回值都必须实现的机制,而,RMI 是JavaEE的基础,因此序列化机制是Java平台的基础
如果需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
- Serializable(常用)
- Externalizable
//People.java
import java.io.Serializable;
public class People implements Serializable {
public int age;
public String name;
public People(int age, String name){
this.age = age;
this.name = name;
}
public void print(){
System.out.println("姓名:"+this.name +" 年龄:"+this.age);
}
}
//Test.java
import java.io.*;
//对象的序列化和反序列化使用的类要严格一致,包名,类名,类机构等等所有都要一致
public class Test {
public void objectStream() throws Exception {
People people = new People(18,"张三");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/input.txt"));
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/input.txt"));
objectOutputStream.writeObject(people);
objectOutputStream.flush();
objectOutputStream.close();
People newpeople = (People) objectInputStream.readObject();
objectInputStream.close();
newpeople.print();
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.objectStream();
}
}
随机存储流
RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读写文件。
- 支持只访问文件的部分内容
- 可以向已存在的文件后追加内容
RandomAccessFile对象包含一个记录指针,用以标识当前读写处的位置。
RandomAccessFile类对象可以自由移动记录指针:
- long getFilePointer() :获取文件疾苦指针的当前位置
- void seek(long pos): 将问及那记录指针定位到pos 位置
构造器:
- public RandomAccessFile(File file, String mode)
- public RandomAccessFile(String name, String mode)
创建RandomAccessFile类实例需要指定一个mode参数,该参数 指定RandomAccessFile 的访问模式:
- r : 以只读方式打开
- rw : 打开以便读取和写入
- rwd : 打开以便读取和写入;同步文件内容的更新
- rws : 打开以便读取和写入;同步文件内容和元数据的更新
public class Test {
public void randomStream() throws Exception {
RandomAccessFile randomAccessFile = new RandomAccessFile("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/file.txt","r");
RandomAccessFile randomAccessFile1 = new RandomAccessFile("D:/program files/IdeaProject/javatest/javatest/src/com/cn/test/copy.txt","rw");
byte[] bytes = new byte[100];
int len = 0;
randomAccessFile.seek(6); //选取文件开始读取的额位置
randomAccessFile1.seek(randomAccessFile1.length());//设置写的开始点为文件末尾开始
while((len = randomAccessFile.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
randomAccessFile1.write(bytes,0,len);
}
randomAccessFile1.close();
randomAccessFile.close();
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.randomStream();
}
}
反射
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助Reflection API 取得任何类的内部信息,并能直接操作任意的内部属性及方法
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
反射相关API:
- java.lang.Class :代表一个类
- java.lang.reflect.Method :代表类的方法
- java.lang.reflect.Field :代表类的成员变量
- java.lang.reflect.Constructor :代表类的构造方法
Class 类
在Object类中定义了以下方法,此方法将被所有子类继承:
- public final Class getClass()
该方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序运行结果来看也很好理解,即:可以 通过对象反射求出类的名称。
反射可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含 了特定某个类的有关信息。
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个类在JVM中只会有一个Class实例,
- 一个Class对象应该是一个加载到JVM中的一个.class 文件
- 每个类的实例都会记得自己是由哪个Class实例所生成的
- 每个Class可以完整地得到一个类中的完整结构
Class类的常用方法:
反射获取一个类的对象和接口
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
B b = new B();
// Class clazz = B.class; //通过类名直接获取实例
Class clazz = Class.forName("com.cn.test.B"); //通过Class的方法获取一个类的实例,参数 :包名.类名
System.out.println("父类名为:"+clazz.getSuperclass().getName());
Class[] interfaces = b.getClass().getInterfaces(); //通过类的对象获取类的实例
for(Class c : interfaces){
System.out.println("接口名为:"+c.getName());
}
}
}
interface C{
}
interface D{
}
class A{
}
class B extends A implements C,D{
}
反射获取一个类都构造方法
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = A.class;
// Constructor[] constructors = clazz.getConstructors(); //获取公有的构造方法
Constructor[] constructors = clazz.getDeclaredConstructors(); //获取所有的构造方法
for(Constructor constructor:constructors){
//修饰符为1 代表public 修饰符2 代表 private
System.out.println("构造方法的名称为:"+constructor.getName()+" 修饰符为:"+constructor.getModifiers());
Class[] parameters = constructor.getParameterTypes(); //获取参数类型
for(Class parameter:parameters){
System.out.println("参数类型为:"+parameter.getName());
}
}
}
}
class A{
public int id;
public String name;
public A(){
}
public A(int id){
this.id = id;
}
private A(int id, String name){
this.id = id;
this.name = name;
}
}
通过反射创建对象
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = A.class;
//调用无参构造
// Constructor constructor = clazz.getConstructor();
// constructor.newInstance();
//调用一个参数的公有构造
// Constructor constructor = clazz.getConstructor(int.class);
// constructor.newInstance(12);
//调用两个参数的私有构造
Constructor constructor = clazz.getDeclaredConstructor(int.class, String.class);
constructor.setAccessible(true); //解除封装
constructor.newInstance(12,"张三");
}
}
class A{
public int id;
public String name;
public A(){
System.out.println("调用无参构造");
}
public A(int id){
this.id = id;
System.out.println("调用公有有参构造 " + this.id);
}
private A(int id, String name){
this.id = id;
this.name = name;
System.out.println("调用私有有参构造 " + this.name + ":" + this.id);
}
}
反射机制获取方法
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = A.class;
Method[] methods = clazz.getDeclaredMethods();
for(Method method : methods){
System.out.println("方法名:"+method.getName() +" 修饰符:"+method.getModifiers()+" 返回值:"+method.getReturnType());
Class[] parameters = method.getParameterTypes();
for(Class parameter:parameters){
System.out.println("参数类型:"+parameter.getName());
}
}
}
}
class A{
public int id;
public String name;
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setMsg(int id, String name){
this.name = name;
this.id = id;
}
}
反射机制获取属性
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = A.class;
// Field[] fields = clazz.getDeclaredFields(); //获取所有属性,不包含父类属性
Field[] fields = clazz.getFields(); //获取公有属性,包含父类
for(Field field :fields){
System.out.println("属性名:"+field.getName()+" 属性修饰符:"+field.getModifiers()+" 属性类型:"+field.getType());
}
//获取类所在的包名
Package pack = clazz.getPackage();
System.out.println(pack.getName());
}
}
class A{
public int id;
public String name;
private Boolean sex;
}
反射机制调用指定方法
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = A.class;
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
Method method = clazz.getMethod("setName", String.class);
method.invoke(object,"张三");
System.out.println("===========================");
Method method1 = clazz.getDeclaredMethod("print");
method1.setAccessible(true);//解除私有封装
method1.invoke(object);
Method method2 = clazz.getMethod("getName");
System.out.println("===========================");
String name = (String)method2.invoke(object);
System.out.println("获取名字成功:"+name);
}
}
class A{
public int id;
public String name;
private Boolean sex;
public A(){
System.out.println("调用无参构造创建对象");
}
public void setName(String name) {
this.name = name;
System.out.println("名字设置成功!");
}
public String getName() {
return name;
}
private void print(){
System.out.println("打印成功");
}
}
反射机制调用指定属性
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = A.class;
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
Field field = clazz.getField("name");
field.set(object,"张三");
System.out.println(field.get(object));
}
}
class A{
public int id;
public String name;
private Boolean sex;
public A(){
System.out.println("创建对象成功");
}
}
Java动态代理
Proxy :专门完成代理的操作类,是所有动态代理的父类。通过此类为一个或多个接口动态地生成实现类。
创建一个动态代理类所对应的Class对象
static Object newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)
//参数1 是代理对象的类加载器
//参数2 是代理对象的接口
//参数3 是代理对象
//PeopleInterface.java 接口
public interface PeopleInterface {
void showSuccess();
void showError();
}
示例
//People.java 实现类
public class People implements PeopleInterface{
@Override
public void showSuccess() {
System.out.println("执行showSuccess方法!");
}
@Override
public void showError() {
System.out.println("执行ShowError方法!");
}
}
//ProxyDemo.java 代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyDemo implements InvocationHandler {
Object object; //被代理的对象
public ProxyDemo(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始执行"+method.getName()+"方法");
Object result = method.invoke(object);
System.out.println(method.getName()+"方法执行完毕!");
return result;
}
}
//Test.java 实现代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception {
PeopleInterface people = new People();
InvocationHandler invocationHandler = new ProxyDemo(people); //创建代理对象
//参数1 是代理对象的类加载器
//参数2 是代理对象的接口
//参数3 是代理对象
PeopleInterface peopleInterface = (PeopleInterface) Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(), people.getClass().getInterfaces(),invocationHandler);
peopleInterface.showError();
}
}
线程
多线程的优点
- 提高应用程序的相应。对图形化解面更有意义,可增强用户体验。
- 提高计算机系统的CPU的利用率
- 改善程序结构,将既长又复杂的进程分为多个进程,独立运行,利于理解和修改
继承方式和实现方式的联系与区别
区别
- 继承Thread:线程代码存放Thread子类run方法中
- 实现Runable:线程代码在接口子类的run方法
实现方法的好处
- 避免了单继承的局限性
- 多个线程可以共享一个接口实现类的对象,非常适合多个线程来处理同一份资源
继承Thread类实现多线程
public class Test {
public static void main(String[] args) throws InterruptedException {
TestThread testThread = new TestThread();
testThread.start();
for(int i = 0;i<9;i++){
System.out.println("正在执行主程序,当前循环 " + i +" 次。");
Thread.sleep(1000);
}
}
}
class TestThread extends Thread{
@Override
public void run() {
for(int i=1;i<=10;i++){
System.out.println("正在执行线程,当前循环 " + i + " 次。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
通过Runable接口实现多线程
public class Test {
public static void main(String[] args) throws InterruptedException {
//第二个参数用来定义线程名称,使用Thread.currentThread().getName()方法获取
Thread thread1 = new Thread(new RunableTest(),"线程1:");
thread1.start();
Thread thread2 = new Thread(new RunableTest(),"线程2:");
thread2.start();
for(int i = 0;i<9;i++){
System.out.println("正在执行主程序,当前循环 " + i +" 次。");
Thread.sleep(1000);
}
}
}
class RunableTest implements Runnable{
@Override
public void run() {
for(int i=1;i<=10;i++){
System.out.println("正在执行"+Thread.currentThread().getName()+"当前循环 " + i + " 次。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread 类的相关方法
public class Test {
public static void main(String[] args) throws InterruptedException {
//第二个参数用来定义线程名称,使用Thread.currentThread().getName()方法获取
RunableTest runableTest = new RunableTest(); //使用相同的实现类对象,共享同一个count
Thread thread1 = new Thread(runableTest);
Thread thread2 = new Thread(runableTest);
thread1.setPriority(10); //设置thread1 的优先级为10最高 (1-10)
thread2.setPriority(1);
thread1.setName("线程-1:"); //设置线程名
thread2.setName("线程-2:");
System.out.println(thread1.getName() + " " +thread2.getName()); //获取线程名
thread1.start();
thread2.start();
}
}
class RunableTest implements Runnable{
int count = 0;
@Override
public void run() {
for(int i=1;i<=10;i++){
count ++;
//获取当前线程名称
System.out.println(Thread.currentThread().getName()+"当前计数 " +count + " 次。");
}
}
}
线程让步
static void yield:
- 暂停当前正在执行的线程,把执行机会让给优先级相同或跟高的线程
- 若队列中没有同优先级的线程,忽略此方法
join:
- 当某个程序执行流中调用其它线程的join()方法时,调用线程将阻塞,直到join()方法加入的线程执行完毕
- 低优先级的线程也可以获得 执行
stop():
- 强制线程生命期结束
boolean isAlive():
- 判断线程是否还活着
public class Test {
public static void main(String[] args) {
RunableTest runableTest = new RunableTest();
Thread thread1 = new Thread(runableTest,"线程-1:");
Thread thread2 = new Thread(runableTest,"线程-2:");
thread1.start();
thread2.start();
}
}
class RunableTest implements Runnable{
int count = 0;
@Override
public void run() {
for(int i=1;i<=10;i++){
if(i==5 && Thread.currentThread().getName().equals("线程-1:")){
Thread.yield(); //当i=5时线程1进行线程让步
}
count ++;
//获取当前线程名称
System.out.println(Thread.currentThread().getName()+"当前计数 " +count + " 次。");
}
}
}
线程阻塞
public class Test {
public static void main(String[] args) throws InterruptedException {
RunableTest runableTest = new RunableTest();
Thread thread1 = new Thread(runableTest,"线程-1:");
Thread thread2 = new Thread(runableTest,"线程-2:");
thread1.start();
thread1.join(); //线程阻塞,只有他thread1 的线程执行完之后后面程序才能执行
System.out.println("线程阻塞停止");
thread2.start();
}
}
class RunableTest implements Runnable{
int count = 0;
@Override
public void run() {
for(int i=1;i<=10;i++){
count ++;
//获取当前线程名称
System.out.println(Thread.currentThread().getName()+"当前计数 " +count + " 次。");
}
}
}
线程终止
public class Test {
public static void main(String[] args) throws InterruptedException {
RunableTest runableTest = new RunableTest();
Thread thread1 = new Thread(runableTest,"线程-1:");
Thread thread2 = new Thread(runableTest,"线程-2:");
thread1.start();
Thread.sleep(300);
thread1.stop(); //停止线程
thread2.start();
}
}
class RunableTest implements Runnable{
int count = 0;
@Override
public void run() {
for(int i=1;i<=10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count ++;
System.out.println(Thread.currentThread().getName()+"当前计数 " +count + " 次。");
}
}
}
判断线程是否存活
thread1.isAlive();
线程安全
当我们使用多个线程访问同一个资源的时候,且多个线程对资源有写的操作,就容易出现线程安全问题。要解决多线程并发访问一个资源的安全性问题;也就是解决重复票与不存在票问题,Java提供了同步机制(synchronized)来解决。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
- 同步代码块
- 同步方法
- 锁机制
同步技术的原理
使用一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
比如三个线程一起抢夺CPU的执行权,谁抢到了谁执行run方法进行买票
当t0抢到了CPU的执行权,执行run方法,遇到synchronized代码块这时t0会检查synchronized 代码块是否有锁对象,发现有就会获取锁对象,进入到同步中执行
然后他t1抢到了CPU的执行权,执行run方法,遇到synchronized代码块这时t1会检查synchronized代码块是否有锁对象,发现没有,t1就会进入到阻塞状态会一直等待t0线程执行完同步中的代码,会把锁对象归还给同步代码块t1才能获得锁对象进入到同步中执行。
总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步
同步代码块
解决线程安全问题的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1. 通过代码块中的锁对象,可以使用任意的对象
2. 但是必须保证多个线程使用的锁对象是同一个
3. 锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
代码实现:
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
Sale sale = new Sale(20);
Thread thread1 = new Thread(sale,"张三");
Thread thread2 = new Thread(sale,"王五");
Thread thread3 = new Thread(sale,"赵六");
thread1.start();
thread2.start();
thread3.start();
}
}
//Sale.java
public class Sale implements Runnable{
private int number;
public Sale(int number){
this.number = number;
}
Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(number>0){
System.out.println(Thread.currentThread().getName() + "正在卖第 " + this.number + " 张票");
number--;
}
}
}
}
}
同步方法
解决线程安全的第二种方案:使用同步方法
使用步骤:
- 把访问了共享数据的代码抽取出来,放到一个方法中
- 在方法上添加synchronized修饰符
同步方法的锁对象是实现类的对象 new RunableImpl() 也就是this
代码实现:
//等同同步代码块Test.java
//Sale.java
public class Sale implements Runnable{
private int number;
public Sale(int number){
this.number = number;
}
@Override
public void run() {
while(true){
saleTicket();
}
}
public synchronized void saleTicket(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(number>0){
System.out.println(Thread.currentThread().getName() + "正在卖第 " + this.number + " 张票");
number--;
}
}
}
静态同步方法
在原有同步方法的基础上加static关键字
静态同步方法的锁对象不能是this,因为this是创建对象之后产生的,静态方法优先于对象。静态方法的锁对象是本类的class属性 -->class文件对象
Lock锁
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized 方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更具有面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- public void lock() :加同步锁
- public void ulock() :释放同步锁
代码实现:
//等同同步代码块Test.java
//Sale.java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Sale implements Runnable{
private int number;
public Sale(int number){
this.number = number;
}
Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
![线程间的通信.png](https://upload-images.jianshu.io/upload_images/24692692-2021ac8e04fb994d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
}
if(number>0){
System.out.println(Thread.currentThread().getName() + "正在卖第 " + this.number + " 张票");
number--;
}
lock.unlock();
}
}
}
线程通信
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板包子的数量,调用wait方法,放弃cpu的执行,进入到waiting状态(无限等待)
创建一个老板线程(生产者):花费相应时间做包子,做好包子后,调用notify方法,唤醒顾客
注意
顾客和老板必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
Object类中的方法
void wait() 在其它线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
void notify() 唤醒在此对象监视器上等待的单个线程,会继续执行wait方法之后的代码
public class Sale{
public static void main(String[] args) {
Object obj = new Object();
new Customer(1,3,obj).start();
new Customer(2,6,obj).start();
// new Boss(obj).start();
}
}
class Customer extends Thread{
private int order;
private int number;
private Object obj;
public Customer(int order, int number, Object obj){
this.order = order;
this.number = number;
this.obj = obj;
}
@Override
public void run() {
synchronized (obj){
System.out.println(order+"号顾客到店,告知老板需要"+number+"个包子");
new Boss(order, number, obj).start();
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(order+"号顾客拿到包子");
}
}
}
class Boss extends Thread{
private int order;
private int time;
private Object obj;
public Boss(int order, int time, Object obj){
this.order = order;
this.time = time;
this.obj = obj;
}
@Override
public void run() {
synchronized (obj){
try {
Thread.sleep(time*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("老板做好包子,告诉顾客"+order);
obj.notify();
}
}
}
线程池
当程序第一次启动的时候,创建多个线程,保存到一个集合中,当我们想要使用线程的时候,就可以从集合中取出来线程使用
- Thread t = list.remove(0);(list存储)
- Thread t = linked.removefFirst();(link存储)
返回的是被移除的元素,线程只能被一个任务使用。当我们使用完毕线程,需要把线程还给线程池
- list.add(t);
- linked.addLast(t);
JDK1.5之后内置额线程池
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少创建和系哦啊会线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而使服务器死机。
线程池的使用步骤:
- 使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(3);
- 创建一个类,实现Runnable接口,重写run方法,设置线程任务
class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
- 调用ExecutorService 中的方法submit,传递线程任务(实现类),开始线程,执行run方法
es.submit(new RunnableImpl());
- 调用ExcutorService中的方法shutdown销毁线程池(不建议执行)
es.shutdown();
lambda表达式 (面向函数式编程)
lambda 无参使用方法
package com.cn.test;
public class People{
public static void main(String[] args) {
//匿名内部类实现
print(new PeopleInterface() {
@Override
public void showSuccess() {
System.out.println("调用成功");
}
});
//lambda表达式实现
print(()->{
System.out.println("通过lambda调用成功");
});
}
public static void print(PeopleInterface peopleInterface){
peopleInterface.showSuccess();
}
}
interface PeopleInterface{
void showSuccess();
}
lambda有参数返回值
import java.util.Arrays;
import java.util.Comparator;
public class Test {
public static void main(String[] args){
People[] peoples = {
new People("张三",18),
new People("李四",12),
new People("王五",38),
new People("赵六",24),
new People("孙七",59),
};
//通过内部类实现
Arrays.sort(peoples, new Comparator() {
@Override
public int compare(People o1, People o2) {
return o1.getAge()- o2.getAge();
}
});
//通过lambda表达式实现
Arrays.sort(peoples,(People o1, People o2)->{
return o1.getAge()-o2.getAge();
});
for(People people :peoples){
System.out.println(people.name+people.age);
}
}
}
TCP通信
//Server
import java.io.OutputStream;
import java.io.InputStream;
import java.util.Scanner;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer{
public static void main(String[] args)throws Exception{
ServerSocket server = new ServerSocket(8000);
Socket socket = server.accept();
Scanner scanner = new Scanner(System.in);
while(true){
InputStream in = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = in.read(bytes);
System.out.println(new String(bytes,0,len));
OutputStream os = socket.getOutputStream();
os.write(scanner.next().getBytes());
}
}
}
//client
import java.util.Scanner;
import java.io.OutputStream;
import java.net.Socket;
import java.io.InputStream;
public class Test{
public static void main(String[] args)throws Exception{
Socket socket = new Socket("127.0.0.1", 8000);
Scanner scanner = new Scanner(System.in);
while(true){
OutputStream os = socket.getOutputStream();
os.write(scanner.next().getBytes());
InputStream in = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = in.read(bytes);
System.out.println(new String(bytes,0,len));
}
}
}