反射概述
什么是反射?
反射的的概念是有smith1982年首次提出的,zhuy主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。
JAVA反射机制是运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能都调用它的任意一个方法;
这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
简单来说,反射机制指的是程序在运行时能够获取自身的信息。在JAVA中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
包括其访问修饰符、父类、实现的接口、属性和方法的所有信息,并可在运行时创建对象、修改属性(包括私有的)、调用方法(包括私有的)。
为什么要用反射机制?直接创建对象不就可以了么?这就涉及到了动态和静态的概念?
静态编译:在编译时确定类型,绑定队形,即通过。
Student stu = new Student("zhangsan",20);
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,用以降低类之间的耦合性。
Class.forName("com.mysql.jdbc.Driver.class").newInstance();
一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特点是在J2EE的开发中。
它的缺点是对性能有影响。使用反射基本上是一种解释操作,这类操作总是慢于直接执行的相同操作。
Java反射机制主要提供了以下功能
在运行时判断任意一个对象所属的类
在运行时构建任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法
Class对象是Reflection故事起源。要想操纵类中的属性和方法,都必须从获取Class对象开始
类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译一个新类,就会产生与之对应的一个Class对象。
Class类没有公共的构造方法。Class对象实在加载类时由JAVA虚拟机以及通过类加载器中的方法自动构造的,因此不能显示地声明一个Class对象
获取Class对象的方式有多种
package com.ab.tzy; public class ClassDemo { public static void main(String[] args) { // 对像名.getClass() // Class<?> getClass() 返回此 Object 的运行时类。 Employee employee = new Employee("zhangsan",15); Class<?> classType = employee.getClass(); //String getName() 以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 System.out.println(classType.getName());//com.ab.tzy.Employee //Class<? super T> getSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。 System.out.println(classType.getSuperclass().getName());//java.lang.Object //类名.class Class<?> classType1 = Employee.class; System.out.println(classType1.getName());//com.ab.tzy.Employee System.out.println(classType1.getSuperclass().getName());//java.lang.Object //使用Class.forName(); //static Class<?> forName(String className) 返回与带有给定字符串名的类或接口相关联的 Class 对象。 try { Class<?> classType2=Class.forName("com.ab.tzy.Employee"); System.out.println(classType2.getName());//com.ab.tzy.Employee System.out.println(classType2.getSuperclass().getName());//java.lang.Object } catch (ClassNotFoundException e) { e.printStackTrace(); } //获取基本数据类型的Class对象 Class<?> classType3 = int.class; System.out.println(classType3.getName());//int //System.out.println(classType3.getSuperclass().getName()); // java.lang.NullPointerException //通过基本数据类型的包装类来获取对应的基本数据类型所对应的Class对象 //static Class<Integer> TYPE 表示基本类型 int 的 Class 实例。 (基本数据类型包装类都有该属性) Class<?> classType4 = Integer.TYPE; System.out.println(classType4.getName());//int //System.out.println(classType4.getSuperclass().getName()); // java.lang.NullPointerException Class<?> classType5 = Integer.class; System.out.println(classType5.getName());//java.lang.Integer System.out.println(classType5.getSuperclass().getName());//java.lang.Number(除了character都是) Class<?> classType6 = String.class; System.out.println(classType6.getName());//java.lang.String System.out.println(classType6.getSuperclass().getName());//java.lang.Object Class<?> classType7 = Character.class; System.out.println(classType7.getName());//java.lang.Character System.out.println(classType7.getSuperclass().getName());//java.lang.Object } } class Employee{ private String name; private int age; public Employee(String name, int age) { super(); this.name = name; this.age = age; } 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; } }
反射入门
Java.lang.reflect库
Class类与java.lang.reflect类库一起对反射的概念进行支持。
Java.lang包下
Class<T>:表示一个正在运行的Java应用程序中的类和接口,是Reflection的起源
Java.lang.reflect包下:
Field类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方式。
Constructor类:代表类的构造方法.
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
通过反射实例化对象
平常情况我们通过new Object来生成一个类的实例,但有时候我们没法直接new,只能通过反射动态生成。
实例化无参构造函数的对象,两种方式:
Class.newInstance();
Class.getConstructor(new Class[]{}).newInstance(new Object[]{})
实例化带参构造函数的对象
clazz.getConstructor(Class<?>...parameterTyprs).newInstance(Object...initargs)
通过反射获取并调用方法
获取当前类以及超类的public Method
Method[] arrMethods = classType.getMethodes();
获取当前类申明的所有Method
Method[] arrMethods = classType.getDeclaredMethodes();
获取当前类以及超类指定的public Method
Method[] arrMethod = classType.getMethode(String name,Class<?>...parameterTypes);
获取当前类申明的指定的Method
Method[] arrMethod = classType.getDeclaredMethode(String name,Class<?>...parameterTypes);
通过反射动态运行指定Method
Object obj = method.invoke(Object obj,Object...args);
通过反射获取并调用属性
获得当前类以及超类的public Field
Field[] arrFields = classType.getFields();
获取当前类申明的所有Field
Field[] arrFields = classType.getDeclaredFields();
获取当前类以及超类指定的public Field
Field[] arrField = classType.getField();
获取当前类申明的指定的Field
Field[] arrField = classType.getDeclaredFields();
通过反射动态设定Field的值
Field.set(Object obj,Object value)
通过反射动态获取Field的值
Object obj = field.get(Object obj);
package com.ab.tzy; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectionAPIDemo { public static void main(String[] args) throws Exception { // 获取Employee这个类所关联的Class对象 Class<?> classType = Class.forName("com.ab.tzy.Employee"); // 通过反射机制来构造一个Employee的实例对象 // T newInstance() 创建此 Class 对象所表示的类的一个新实例。 (Class) Object newInstance = classType.newInstance(); Employee employee = (Employee) newInstance; // java.lang.InstantiationException默认调用无参构造方法,如果雇员类里面没有无参就会包实例化异常 System.out.println(employee);// Employee [name=null, age=0] // 调用指定的构造方法来构造对象(无参构造方法) // Constructor<T> getConstructor(Class<?>... parameterTypes) // 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 (Class) Constructor<?> constructor = classType.getConstructor(new Class[] {}); // T newInstance(Object... initargs) // 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。 // (Constructor) Object newInstance2 = constructor.newInstance(new Object[] {}); Employee employee2 = (Employee) newInstance2; System.out.println(employee2);// Employee [name=null, age=0] // 调用指定的构造方法来构造对象(无参构造方法) // Constructor<T> getConstructor(Class<?>... parameterTypes) // 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 (Class) Constructor<?> constructor3 = classType.getConstructor(new Class[] {String.class,int.class}); // T newInstance(Object... initargs) // 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。 // (Constructor) Object newInstance3 = constructor3.newInstance(new Object[] {"张三",30}); Employee employee3 = (Employee) newInstance3; System.out.println(employee3);// Employee [name=张三, age=30] //获取Class对象所指定的所有方法,包括私有的 // Method[] getDeclaredMethods() 返回 Method 对象的一个数组, // 这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 (Class) // Method[] getMethods() 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象 //所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 (Class) System.out.println("*****"); Method[] methods = classType.getDeclaredMethods(); for (Method method : methods) { //String getName() 以 String 形式返回此 Method 对象表示的方法名称。 //int getModifiers() 以整数形式返回此 Method 对象所表示方法的 Java 语言修饰符。 // Class<?> getReturnType() 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。 System.out.println(method.getName()+"--"+method.getModifiers()+"--"+method.getReturnType()); } /* toString--1--class java.lang.String getName--1--class java.lang.String setName--1--void eat--2--class java.lang.String work--2--void setAge--1--void getAge--1--int work1--0--void work2--4--void */ System.out.println("*****"); //获取Class对象所指定的方法,包括私有 // Method getDeclaredMethod(String name, Class<?>... parameterTypes) //返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 (Class) //获取Class对象所指定的方法,不包括私有 // Method getMethod(String name, Class<?>... parameterTypes) //返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 (Class) Method method = classType.getMethod("toString", new Class[]{}); //String toString() 返回描述此 Method 的字符串。 (Method) System.out.println(method); //public java.lang.String com.ab.tzy.Employee.toString() //String getName() 以 String 形式返回此 Method 对象表示的方法名称。 (Method) System.out.println(method.getName());//toString Object desc = method.invoke(employee3, new Object[]{}); System.out.println(desc);//Employee [name=张三, age=30] //方法的调用 // Method getDeclaredMethod(String name, Class<?>... parameterTypes) //返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 (Class) Method method1 = classType.getDeclaredMethod("eat", new Class[]{String.class}); // Object invoke(Object obj, Object... args) //对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 (Method) System.out.println(method1.getName());//eat //void setAccessible(boolean flag) 将此对象的 accessible 标志设置为指示的布尔值。 //(method父类AccessibleObject的方法) method1.setAccessible(true); Object desc1 = method1.invoke(employee3, new Object[]{"拉拉"}); System.out.println(desc1);//拉拉 //java.lang.IllegalAccessException非法访问异常 必须要调用一个权限方法设置 //获取Class对象所指定的属性,包括私有的 Field field = classType.getDeclaredField("name"); //void setAccessible(boolean flag) 将此对象的 accessible 标志设置为指示的布尔值。 //(field父类AccessibleObject的方法和method共同的父类) field.setAccessible(true); // void set(Object obj, Object value) //将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 (Field) field.set(employee3, "李四"); // String toString() 返回一个描述此 Field 的字符串。 (Field) System.out.println(field);//private java.lang.String com.ab.tzy.Employee.name // Object get(Object obj) 返回指定对象上此 Field 表示的字段的值。 (Field) System.out.println(field.get(employee3));//李四 // Field getDeclaredField(String name) //返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。 (Class) // Field[] getDeclaredFields() //返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。 (Class) // Field getField(String name) 返回一个 Field 对象,它反映此 Class 对象所表 //示的类或接口的指定公共成员字段。 (Class) // Field[] getFields() 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表 //示的类或接口的所有可访问公共字段。 (Class) } } class Employee { private String name; private int age; public Employee() { super(); } public Employee(String name, int age) { super(); this.name = name; this.age = age; } 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; } @Override public String toString() { return "Employee [name=" + name + ", age=" + age + "]"; } private String eat(String s){ return s; } private void work(){ System.out.println("我要工作---私有"); } void work1(){ System.out.println("我要工作---默认"); } protected void work2(){ System.out.println("我要工作---保护"); } }
反射常用
package com.abc.tzy; import java.lang.reflect.Array; public class ArrayDemo { public static void main(String[] args) throws Exception { //创建一个一维数组(String) Class<?> classType = Class.forName("java.lang.String"); //static Object newInstance(Class<?> componentType, int length) //创建一个具有指定的组件类型和长度的新数组。 (Array) Object array = Array.newInstance(classType, 5); //static void set(Object array, int index, Object value) //将指定数组对象中索引组件的值设置为指定的新值。 Array.set(array, 3, "abc"); //static Object get(Object array, int index) //返回指定数组对象中索引组件的值。 //System.out.println(Array.get(array, 5)); // java.lang.ArrayIndexOutOfBoundsException System.out.println(Array.get(array, 3));//abc //创建一个二维数组(3行3列) //static Object newInstance(Class<?> componentType, int... dimensions) //创建一个具有指定的组件类型和维度的新数组。 int [] dimens={3,3}; Object array1 = Array.newInstance(int.class, dimens); Object arrayobj = Array.get(array1, 2);//获取第三行(就是一个一维数组) //static void setInt(Object array, int index, int i) //将指定数组对象中索引组件的值设置为指定的 int 值。 (基本数据类型方法相似) Array.setInt(arrayobj, 2, 10);//给指定数组位置的元素赋新值 int [] [] arr = (int[][]) array1; for (int[] is : arr) { for (int i : is) { System.out.print(i+"\t"); } System.out.println(); } //0 0 0 //0 0 0 //0 0 10 } }
反射创建数组
反射总结
只要用到反射,先获得Class对象。
没有方法能获得当前类的超类的private方法和属性,你必须通过getSuperclass()找到超类以后再去尝试获得
通常情况即使是当前类,private属性或方法也是不能访问的,你需要设置压制权限setAccessible(true)来取得private的访问权。但说实话,这已经破坏了面向对象的规则,
所以除非万不得已,请尽量少用。
package com.abc.tzy; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectionDemo { public static void main(String[] args) { // 2生成一个学生对象(被赋值的哪个对象,源对象) Student stu = new Student(1, "张三", 30); try { Student copystu = (Student) ObjectCopyUtil.copyObj(stu); System.out.println("复制对象成功"); System.out.println(copystu); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } //复制对象成功 //Student [id=1, name=张三, age=30] /** * 这是一个Copy对象的工具类,内部提供了一个Copy对象的方法,接受源对象 * * @author Administrator * */ class ObjectCopyUtil { public static Object copyObj(Object obj) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { // 3获取Student对象所对应类型的Class对象(也就是Student这个类所对应的Class对象) Class<?> classType = obj.getClass(); // 4通过Class对象的newInstance方法构建一个目标对象。 Object objRes = classType.newInstance(); // 5获取Class对象的set和get方法 Field[] fields = classType.getDeclaredFields(); for (Field field : fields) { // 得到属性所对应的get和set方法 // String substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。 // String substring(int beginIndex, int endIndex) // 返回一个新字符串,它是此字符串的一个子字符串。 // String toUpperCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为大写。 // String toLowerCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为小写。 String getMethodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);// 拼接getId String setMethodName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);// 拼接SetId //调用源对象的get方法获取属性值 Method getMethod = classType.getDeclaredMethod(getMethodName, new Class[]{});//方法名 参数 Object value = getMethod.invoke(obj, new Object[]{}); //调用源对象的set方法给属性赋值 // Class<?> getType() 返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。 Method setMethod = classType.getDeclaredMethod(setMethodName, new Class[]{field.getType()});//参数类型就是属性的类型 setMethod.invoke(objRes, new Object[]{value}); /* //方法二: field.setAccessible(true); // Object get(Object obj) 返回指定对象上此 Field 表示的字段的值。 Object value1 = field.get(obj); // void set(Object obj, Object value) 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 field.set(objRes, value1); */ } return objRes; } } // 1申明一个学生类 class Student { private int id; private String name; private int age; public Student(int id, String name, int age) { super(); this.id = id; this.name = name; this.age = age; } 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 int getAge() { return age; } public void setAge(int age) { this.age = age; } public Student() { super(); } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
通过反射赋值对象
线程模式
什么是程序:
安装在磁盘上的一段指令集合,它是静态的概念。
什么是进程:
它是运行中的程序,是动态的概念。每个进程都有独立的资源空间。
什么是线程:
线程,又称为轻量级进程,是程序执行流的最小单元,是程序中一个单一的顺序控制流程。线程是进程中的一个事例,是被系统独立调度和分派的基本单位。
什么是多线程:
多线程则指的是在单个程序中可以同时运行多个不同的线程执行不同的任务。
多线程的特点:
一个程序可以包含一个或多个线程。
一个程序实现多个代码同时交替运行就需要产生多个线程。
线程本身不拥有系统资源,与同属一个进程的其他线程共享所在进程所拥有的资源。
同一进程中的多个线程之间可以并发执行。CPU会随机抽出事件,让我们的程序一会做这件事情,一会做另一件事情。
多线程的目的:
就是“最大限度地利用CPU资源”,当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。
从根本上说,这就是多线程编程的目的。
JAVA运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程。JAVA是纯面向对象语言,JAVA的线程模型也是面向对象的。
线程模型
通过继承Thread类创建线程
普通JAVA类如继承自Thread类,就成为一个线程类,并可通过该类的start方法来启动线程,执行线程代码。
Thread类的子类可直接实例化,但在子类中必须覆盖run方法才能真正运行线程的代码。
package com.abc.tzy; public class HelloThreadDemo { public static void main(String[] args) { Thread thread1 = new HelloThread("a"); //void setName(String name) 改变线程名称,使之与参数 name 相同。 //thread1.setName("线程1"); thread1.start(); Thread thread2 = new HelloThread(); thread2.setName("线程2"); thread2.start(); } } class HelloThread extends Thread{ public HelloThread(String name){ super(name); } public HelloThread(){ super(); } @Override public void run() { for (int i = 0; i < 5; i++) { // String getName() 返回该线程的名称。 System.out.println(this.getName()+":"+i); } } }
extends--->Thread
通过实现Runnable接口创建线程
实现Runnable借口的类必须借助Thread类才能创建线程。通过Runnable接口创建线程分为两步:
创建实现Runnable接口的类的实例。
创建一个Thread类对象,将第一步实例化得到的Runnable对象作为参数传入Thread类的构造方法。
通过Thread类的start方法启动线程
package com.abc.tzy; public class RunnableDemo { public static void main(String[] args) { HelloRunnable helloRunnable = new HelloRunnable(); Thread t1 = new Thread(helloRunnable,"A"); t1.start(); Thread t2 = new Thread(helloRunnable,"B"); t2.start(); } } //避免单继承的局限,一个类可以实现多个接口,但只能继承一个类 //适合资源的共享 class HelloRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++) { //static Thread currentThread() 返回对当前正在执行的线程对象的引用。 System.out.println(Thread.currentThread().getName()+":"+i); } } }
implements-->Runnable
package com.abc.tzy; public class SharedDataThreadDemo { public static void main(String[] args) { TicketThread s1 = new TicketThread("一号窗口"); s1.start(); TicketThread s2 = new TicketThread("二号窗口"); s2.start(); } } class TicketThread extends Thread{ private static int ticket = 5;//加上static变成静态变量就可以实现卖五张 public TicketThread(String name){ super(name); } @Override public void run() { while(true){ System.out.println(this.getName()+":"+(ticket--)); if(ticket<1){ break; } } } }
卖票Thread
package com.abc.tzy; public class SharedDataThreadDemo { public static void main(String[] args) { TicketRunnable run = new TicketRunnable(); Thread t1 = new Thread(run, "一号窗口"); t1.start(); Thread t2 = new Thread(run, "二号窗口"); t2.start(); } } //不需要设置静态变量就可以数据共享 class TicketRunnable implements Runnable{ private int ticket = 5; @Override public void run() { while(true){ System.out.println(Thread.currentThread().getName()+":"+(ticket--)); if(ticket<1){ break; } } } }
卖票Runnable
线程的生命周期
线程状态:
与人的生老病死一样,线程野同样要经历新建、就绪、运行(活动)、阻塞和死亡五种不同的状态。这五种状态都可以通过Thread类中的方法进行控制。
创建并运行线程
新建状态(New Thread)
在JAVA语言中使用new操作符创建一个线程后,该线程仅仅是一个空对象,它具备了线程的一些特征,但此时系统没有为其分配资源,这时的线程处于创建状态。
线程处于创建状态时,可通过Thread类的方法来设置线程各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。
就绪状态(Runnable)
使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于就绪状态。此外,如果某个线程执行了yield()方法,那么该线程会被暂时
剥夺CPU资源,重新进入就绪状态。
运行状态(Running)
JAVA运行系统通过调度选中一个处于就绪状态的线程,使其占有CPU并转为运行状态。此时,系统真正执行线程的run()方法。
可以通过Thread类的isAlive方法来判断线程是否处于就绪/运行状态:当线程处于就绪/运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于阻塞状
态,也可能处于停止状态
阻塞状态(Blocked)
一个正在运行的线程因某些原因不能继续运行时,就进入阻塞状态。这个原因包括:
当执行了某个线程对象的sleep()等阻塞类型的方法时,该线程对象会被置入一个阻塞集(Blocked Pool)内,等待超时而自动苏醒。
当多个线程试图进入某个同步区域(synchronized)时,没能进入该同步区域的线程会被置入锁定集(Lock Pool),知道获得该同步区域的锁,进入就绪状态。
当线程执行了某个对象的wait()方法时,线程会被置入该对象的等待集(Wait Pool)中,知道执行了该对象的notify()方法,wait()/notify()方法的执行要求线程首先获取到该对象的锁
死亡状态(Dead)
线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。
终止线程的三种方法:
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止,推荐使用。
使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
使用interrupt方法中断线程。
线程同步
为什么需要同步
线程同步是为了防止多个线程访问一个数据对象时,对数据造成破坏
线程的同步是保证多线程安全访问竞争资源的一种手段。
同步和锁
Java中每个对象都有一个内置锁。
当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁;当程序运行到synchronized同步代码块时,自动获得锁定对象的锁。
获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。当程序运行到synchronized同步方法或代码块时该对象锁才起作用。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,知道第一个线程释放锁。这也意味着任何其他线程都不能进入synchronized方法或代码块,知道该
锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块.
对于同步,一般而言在java代码中需要完成两个操作:
把竞争访问的资源标识为private。
同步那些访问资源的代码,使用synchronized关键字来修饰方法或代码块。当synchronized方法执行完或发生异常时,会自动释放锁。
看一下需求:
某银行卡帐号上有500元现金。一个人拿着存折去取钱,同时另一个人拿着卡去ATM机上取钱,各自取400元。
要求取钱过程中不能出现资源竞争:比如400元被取出两次、银行卡的账目不能小于0等.
package com.abc.tzy; public class BankDemo { public static void main(String[] args) { Bank bank = new Bank(); BankThread p1 = new BankThread(bank, "取钱人1"); p1.start();// 柜台取钱 BankThread p2 = new BankThread(bank, "取钱人2"); p2.start();// ATM取钱 } } class BankThread extends Thread { private Bank bank = null; public BankThread(Bank bank, String name) { super(name); this.bank = bank; } @Override public void run() { System.out.println(this.getName()+"取钱:" + bank.getMoney(300)); } } class Bank { private int money = 500; // 取钱的方法,返回取钱的数目 // 当一个线程去调用同步方法的时候,这个线程就获取了当前对象的锁。 // 其他线程当调用同步方法的时候,只能等待,因为无法获取对象的锁。 // 只有等第一个线程释放对象的锁方可进入. //this 位置上可以换成任何对象,获取哪个对象的锁不影响 public /* synchronized */ int getMoney(int number) { synchronized (this) { if (number < 0) { return -1; } else if (money < 0) { return -2; } else if (money < number) { return -3; } else { try { // static void sleep(long millis) // 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 Thread.sleep(1000);// 模拟取钱的时间 } catch (InterruptedException e) { e.printStackTrace(); } money -= number; System.out.println("余额:" + money); } return number; } } }
案例
同步产生死锁的原因
当一个线程已获取了对象1的锁,同时又想获取对象2的锁。而此时另一个线程当前已持有了对象二的锁,而又想获取对象1的锁。这种互相等待对方释放锁的过程,会导致死锁。
package com.abc.tzy; public class DieThreadDemo { public static void main(String[] args) { Example ex = new Example(); DieThread1 die1 = new DieThread1(ex); die1.start(); DieThread2 die2 = new DieThread2(ex); die2.start(); } } class DieThread1 extends Thread{ private Example example = null; public DieThread1(Example example) { super(); this.example = example; } @Override public void run() { example.method1(); } } class DieThread2 extends Thread{ private Example example = null; public DieThread2(Example example) { super(); this.example = example; } @Override public void run() { example.method2(); } } class Example{ private Object obj1 = new Object(); private Object obj2 = new Object(); public void method1(){ synchronized (obj1){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2){ System.out.println("method1"); } } } public void method2(){ synchronized (obj2){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj1){ System.out.println("method2"); } } } }
死锁
线程通信
Java提供了3个重要的方法巧妙的解决线程间的通信问题。这3个方法分别是:wait()、notify()、notifyAll()。(Object类)
调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待列队,直到被再次唤醒。
调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。
调用notifyAll()方法可以使所有正在等待列队中同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程可能会最先执行。
package com.abc.tzy; import java.util.LinkedList; public class ProductorConsumerDemo { public static void main(String[] args) { Basket basket = new Basket(); Productor productor = new Productor(basket); Consumer consumer = new Consumer(basket); productor.start(); consumer.start(); } } class Productor extends Thread{ private Basket basket = null; public Productor(Basket basket) { super(); this.basket = basket; } @Override public void run() { basket.pushApple(); } } class Consumer extends Thread{ private Basket basket = null; public Consumer(Basket basket) { super(); this.basket = basket; } @Override public void run() { basket.popApple(); } } //篮子类 class Basket{ private LinkedList<Apple> basket = new LinkedList<Apple>(); //放4轮苹果 public synchronized void pushApple(){ for (int i = 0; i < 20; i++) { Apple apple = new Apple(i); push(apple); } } //吃4轮苹果 public synchronized void popApple(){ for (int i = 0; i < 20; i++) { pop(); } } //向篮子放苹果 private void push(Apple apple){ if(basket.size()==5){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }//等待并释放当前对象的锁 } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } basket.addFirst(apple); System.out.println("存放"+apple.toString()); notify();//通知消费者来消费 } //向篮子取苹果 private void pop(){ //当篮子中苹果数为0的时候就等待并通知生产者来生产 if(basket.size()==0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }//等待并释放当前对象的锁 } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } Apple apple = basket.removeFirst(); System.out.println("吃掉"+apple.toString()); notify();//通知消费者来消费 } } //苹果类 class Apple{ private int id; public Apple(int id){ this.id=id; } @Override public String toString() { return "苹果 :" + (id+1)+"号"; } }
生产者消费者
IO框架
IO(Input/Output)是计算机输入/输出的接口。JAVA的核心库java.io提供了全方面的IO接口,包括:文件系统的操作,文件读写,标准设备输出等等
java.io
/ | \
File InputStream Reader
|| OutputStream Writer
文件和目录类 || ||
字节流读写类 字符流读写类
File类及使用
一个File类的对象,表示了磁盘上的文件或目录。
File类提供了与平台无关的方法来对磁盘上的文件或目录进行操作。
File类直接处理文件和文件系统。比如删除文件,获取文件长度大小等信息。
File没有提供方法从文件读取或向文件储存信息。
构造方法:File(String directoryPath) 文件所在路径 File(String directoryPath,String filename) 文件所在路径,文件名 File(File dirObj,String filename) 文件所在路径封装,文件名
package com.abc.tzy; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; import java.io.IOException; public class FileDemo { public static void main(String[] args) throws IOException { File file = new File("d:\\qq");// "d:/qq" // File getAbsoluteFile() 返回此抽象路径名的绝对路径名形式。 System.out.println(file.getAbsolutePath());// d:\qq // String getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null System.out.println(file.getParent());// d:\ // boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 System.out.println(file.isDirectory());// true // boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 System.out.println(file.isFile());// false // boolean exists() 测试此抽象路径名表示的文件或目录是否存在。 System.out.println(file.exists());// true // boolean delete() 删除此抽象路径名表示的文件或目录。 System.out.println(file.delete());// false File myFile = new File("d:\\zhangsan"); // boolean mkdir() 创建此抽象路径名指定的目录。 System.out.println(myFile.mkdir());// 没有时true 有了就是false File myFile2 = new File("d:/zhangsan/tzy.txt"); // boolean createNewFile() 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。 System.out.println(myFile2.createNewFile());// 没有时true 有了就是false // String[] list() 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。 /* String[] files = file.list(); for (String f : files) { System.out.println(f); } System.out.println("*************"); */ // String[] list(FilenameFilter filter) // 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。 /* String[] list = file.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { // boolean endsWith(String suffix) 测试此字符串是否以指定的后缀结束。 (String) return name.endsWith(".exe"); } }); for (String f : list) { System.out.println(f); } */ //File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 /* File[] listFiles = file.listFiles(); for (File f : listFiles) { // String getName() 返回由此抽象路径名表示的文件或目录的名称。 //long length() 返回由此抽象路径名表示的文件的长度。 System.out.println(f.getName()+"---"+f.length()); } */ //public interface FileFilter用于抽象路径名的过滤器。 //File[] listFiles(FilenameFilter filter) 返回抽象路径名数组, //这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 /* File[] listFiles = file.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".exe"); } }); for (File f : listFiles) { System.out.println(f.getName()+"---"+f.length()); } */ //File[] listFiles(FileFilter filter) //返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 File[] listFiles = file.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().endsWith(".exe"); } }); for (File f : listFiles) { System.out.println(f.getName()+"---"+f.length()); } } }
File测试
package com.abc.tzy; import java.io.File; import java.util.ArrayList; import java.util.List; public class IteratorDirDemo { public static void main(String[] args) { IteratorUtil.IteratorDir(new File("d:\\qq")); } } class IteratorUtil{ private static int level = 0;//层级数 public static void IteratorDir(File file){ if(file!=null){ //找出递归的出口 //假设是文件或者时空文件夹 if(file.isFile()||file.listFiles().length==0){ return; }else{ File[] files = file.listFiles(); //要求时先输出文件夹再输出文件 files = sort(files); for (File f : files) { //这是一个动态字符串 StringBuilder sb = new StringBuilder(); if(f.isFile()){ sb.append(getTab(level)); sb.append(f.getName()); }else{ sb.append(getTab(level)); sb.append(f.getName()); sb.append("\\"); } System.out.println(sb.toString()); //加入是文件夹 if(f.isDirectory()){ level++;//进入目录遍历,层级+1; IteratorDir(f);//递归调用遍历目录的方法 level--;//目录层级减一,退回上一级目录继续打印输出 } } } } } /** * 对File类型的数组进行先目录后文件的排列 * @param files * @return */ private static File[] sort(File[] files){ List<File> fList = new ArrayList<File>(); //先存放文件夹 for (File f : files) { if(f.isDirectory()){ fList.add(f); } } //再存放文件 for (File f : files) { if(f.isFile()){ fList.add(f); } } //<T> T[] toArray(T[] a) 按适当顺序(从第一个到最后一个元素) //返回包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 (ArrayList) return fList.toArray(new File[fList.size()]); } //根据层级数来得到\t的个数 private static String getTab(int level){ StringBuilder sb = new StringBuilder(); for (int i = 0; i < level; i++) { sb.append("\t"); } return sb.toString(); } }
递归打印文件夹及文件
流的概念和分类
流时一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启
一个通向目的地的流。这时候你就可以想像数据好像在这其中流动一样。
流的分类:
流按其流向分为“输入流”和“输出流”
流按数据传输单位分为“字节流”和“字符流”
“字节流”用来读写8位二进制的字节;(MP3,MP4,图片视频等多媒体文件)
“字符流”用来读写16位二进制字符;(文本文件)
流按功能分为“节点流”和“过滤流”
“节点流”用于直接操作目标设备的流。例如:磁盘或一块内存区域
“过滤流”时对一个已存在的流的连接和封装,通过对数据进行处理,为程序提供功能强大、灵活的读写功能。
InputStream抽象类
字节流类用于向字节流读写8位二进制的字节。一般的,字节流类主要用于读写诸如图片或者声音等的二进制数据。
字节流类以InputStream和OutputStream为顶层类。它们都是抽象类。
InputStream是定义了字节输入流的抽象类
Outputstream抽象类
Outputstream是定义了字节输出流的抽象类
该类所有方法返回void值,在出错情况下抛IOException异常
InputStream和Outputstream
每个抽象类都有多个具体的子类,这些子类对不同的外设进行处理,例如磁盘文件,网络连接,甚至是内存缓冲区。
FileInputStream类表示能从文件读取字节的InputStream类
常用构造: FileInputstream(String filepath) FileInputStream(File fileObj)
FileOutStream表示能向文件写入字节的OutputStream类
常用构造:FileOutStream(String filepath) FileOutStream(File fileObj) FileOutStream(String filepath,boolean append)
package com.abc.tzy; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class FileInputStreamOutputStreamDemo { public static void main(String[] args) { try { FilecopyUtil.copyFile(new File("D:\\oo\\photo02.jpg"), new File("D:\\oo\\photo01.jpg")); } catch (IOException e) { e.printStackTrace(); } } } class FilecopyUtil{ public static void copyFile(File src,File dst) throws IOException{ FileInputStream fis = new FileInputStream(src); FileOutputStream fos = new FileOutputStream(dst); long t1 = System.currentTimeMillis(); int data = -1; int count=0; while((data=fis.read())!=-1){ fos.write(data); count++; } fos.close(); fis.close(); long t2 = System.currentTimeMillis(); System.out.println("复制完成花费:"+(t2-t1)+"毫秒,读了"+count+"次"); } }
字节流复制图片
package com.abc.tzy; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class FileInputStreamOutputStreamDemo { public static void main(String[] args) { try { FilecopyUtil.copyFile(new File("D:\\oo\\photo02.jpg"), new File("D:\\oo\\photo01.jpg")); } catch (IOException e) { e.printStackTrace(); } } } class FilecopyUtil{ public static void copyFile(File src,File dst) throws IOException{ FileInputStream fis = new FileInputStream(src); FileOutputStream fos = new FileOutputStream(dst); int count=0; int len = 0;//*****为什么需要这个变量**** //因为下面的read(buf)是将文件以8位二进制形式(也就是1b即一字节)装入byte数组作为一个元素.那么当数组读到最后一次时肯定时读不满的, //故要将最后一次装入的---元素的个数---统计出来。 byte [] buf = new byte[1024];//创建一个1kb大小的缓冲区,用来存放输入流中的字符数 long t1 = System.currentTimeMillis(); // int read() 从此输入流中读取一个数据字节。 // int read(byte[] b) 从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。 // int read(byte[] b, int off, int len) 从此输入流中将最多 len 个字节的数据读入一个 byte 数组中 // void write(int b) 将指定字节写入此文件输出流。 // void write(byte[] b) 将 b.length 个字节从指定 byte 数组写入此文件输出流中。 //write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。 while((len=fis.read(buf))!=-1){ fos.write(buf, 0, len); count++; } fos.close(); fis.close(); long t2 = System.currentTimeMillis(); System.out.println("复制完成花费:"+(t2-t1)+"毫秒,读了"+count+"次"); } }
字节流--数组--读取与读出-内存解析
总结:输入流其实就是文件读入内存的桥接,输出流就是内存读出到文件的桥接.
ByteArrayInputStream/OutputStream
ByteArrayInputStream是把字节数组当成源的输入流
两个构造方法,每个都需要一个字节数组提供数据源
ByteArratInputStream(byte array[])
ByteArrayInputStream(byte array[],int start,int numBytes)
ByteArrayOutStream是把字节数组当作目标的输出源
两个构造方法:
ByteArrayOutStream()创建一个新的byte数组输出流
ByteArrayOutStream(int numBytes)创建一个新的byte数组输出流,具有制定大小的缓冲区(字节为单位)
package com.abc.tzy; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class ByteArrayInputStreamOutputStreamDemo { public static void main(String[] args) throws IOException { String str = "hello,shanghai"; ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes()); int data = 0; while((data=bis.read())!=-1){ System.out.println((char)data); } System.out.println("******"); ByteArrayOutputStream bos = new ByteArrayOutputStream();//默认32位长度 bos.write(97); bos.write(65); bos.write("hello,world".getBytes()); byte[] buff = bos.toByteArray(); for (byte b : buff) { System.out.println((char)b); } System.out.println("********"); FileOutputStream fos = new FileOutputStream("d://aa.txt",false);//false不覆盖 bos.writeTo(fos);//把ByteArrayOutputStream内部缓冲区的数据写到对应文件输出流中 fos.close(); } }
ByteArrayInput/OutputStream
过滤流介绍
过滤流(filtered Stream)仅仅是为底层透明地提供扩展功能的输入流(输出流)的包装。这些流一般由普通类的方法(即过滤流的一个父类)访问。
过滤字节流FilterInputStream和FilterOutputStream.构造方法:FilterOutStream(OutputStream os) FilterInputStream(InputStream is)
这些类提供的方法和InputStream及OutputStream类的方法相同
常用的过滤流BufferedInputStream和BuffOutputStream,DataInputStream和DataOutputStream
package com.abc.tzy; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class BufferedInputStreamOutputStreamDemo { public static void main(String[] args) { try { FilecopyUtil1.copyFile(new File("D:\\oo\\photo02.jpg"), new File("D:\\oo\\photo01.jpg")); } catch (IOException e) { e.printStackTrace(); } } } class FilecopyUtil1 { public static void copyFile(File src, File dst) throws IOException { FileInputStream fis = new FileInputStream(src); FileOutputStream fos = new FileOutputStream(dst); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos); int data = 0; long time1 = System.currentTimeMillis(); while ((data = bis.read()) != -1) { bos.write(data); } bos.close(); bis.close(); long time2 = System.currentTimeMillis(); System.out.println("复制完成,共花费"+(time2-time1)+"毫秒"); } }
过滤流Buffered
package com.abc.tzy; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class DataInputOutputStreamDemo { public static void main(String[] args) throws IOException { String name = "zhangsan"; int age = 10; boolean flag = true; char sex = '男'; double money = 100.56; DataOutputStream dos = new DataOutputStream(new FileOutputStream("d:\\b.txt")); dos.writeUTF(name); dos.writeInt(age); dos.writeBoolean(flag); dos.writeChar(sex); dos.writeDouble(money); dos.close(); DataInputStream dis = new DataInputStream(new FileInputStream("d:\\b.txt")); //读写顺序必须一致 System.out.println(dis.readUTF()); System.out.println(dis.readInt()); System.out.println(dis.readBoolean()); System.out.println(dis.readChar()); System.out.println(dis.readDouble()); } }
过滤流Data
BufferedInputStream和BuffOutputStream
需要使用已经存在的节点流来构造,提供带缓冲区的读写,提高了读写的效率
文件-->从文件中获取输入字节(FileInputStream)-->增加字节缓冲区功能(BufferedInputStream)-->数据
数据-->提供数据写入到缓存区 (FileOutputStream)-->将数据以字节写入到文件中-->文件
DataInputStream和DataOutputStream
数据输入输出流允许应用程序读写基本Java数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取。读写顺序要保持一致。
package com.asd.tzy; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class CopyDirDemo { public static void main(String[] args) { try { CopyDirUtil.copyDir(new File("D:\\xmind"), new File("D:\\xmind2")); System.out.println("拷贝成功"); } catch (IOException e) { e.printStackTrace(); } } } class CopyDirUtil{ public static void copyDir(File src,File dst) throws IOException{ dst.mkdirs();//d:\\zz\\bb创建目标文件夹 if(src!=null){ File[] files = src.listFiles();//遍历源文件夹中的所有文件或目录 for (File f : files) { if(f.isFile()){ //是文件就复制 FileInputStream fis = new FileInputStream(f); FileOutputStream fos = new FileOutputStream(dst.getAbsolutePath()+"\\"+f.getName()); byte [] buff = new byte[1024*1024];//1M int len = 0 ;//保存的时读到的字节个数 while((len=fis.read(buff))!=-1){ fos.write(buff,0,len); } fos.close(); fis.close(); }else{ copyDir(f,new File(dst.getAbsolutePath()+"\\"+f.getName())); } } } } }
使用字节流复制一个文件夹
Reader和Writer抽象类
字节流提供处理任何类型输入输出操作的足够功能,但不能直接操作Unicode字符(比如中文2字节),因而需要字符流
字符流层次结构的顶层是Reader和Writer抽象类
Reader是定义Java的流式字符输入模式的抽象类
Writer是定义流式字符输出的抽象类:该类的方法都返回void值并在出错条件下抛出IOException异常
FileReader和FileWriter
FileReader类表示可以读取文件内容的Reader类
构造方法:FileReader(String filePath) FileReader(File fileObj)
FileWriter表示可以写文件的Writer类
构造方法:FileWriter(String filePath) FileWriter(String filePath,boolean append) FileWriter(File fileObj)
package com.asd.tzy; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class FileReaderWriterDemo { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("d:\\tzy.txt"); FileWriter fw = new FileWriter("d:\\tzy1.txt"); char[] buff = new char[100]; int len = 0;//实际读取的字符个数 while((len=fr.read(buff))!=-1){ fw.write(buff,0,len); } fw.close();//如果不关闭 那么缓冲区文件不会被读出来,可以使用fw.flush()强制清空缓存区 fr.close(); } }
字符流复制文件
BufferedReader和BufferedWriter
BufferedReader通过缓冲输入提高性能
构造方法:BufferedReader(Reader inputStream) BufferedReader(Reader inputStream,int bufSize)
BufferedWriter通过缓冲输出提供性能
构造方法:BufferedWriter(Writer outputStream) BufferedWriter(Writer outputStream,int bufSize)
package com.asd.tzy; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class BufferedReaderWriterDemo { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("d:\\tzy.txt"); BufferedReader br = new BufferedReader(fr); FileWriter fw = new FileWriter("d:\\tzy2.txt"); BufferedWriter bw = new BufferedWriter(fw); String line = null; while((line=br.readLine())!=null){ System.out.println(line); bw.write(line); bw.newLine();//使文件换行 bw.flush(); } bw.close(); br.close(); } }
缓冲字符流复制文件
ObjectInputStream/ObjectOutputStream
ObjectOutputStream和ObjectInputStream分别与FileOutStream和FileInputStream一起使用,可以为应用程序提供对对象的持久储存。我们把对象以某种特定得到编码格式写入
称之为序列化。把写入的编码格式内容还原成对象称之为反序列化
序列化的对象必须实现Serializable接口
package com.asd.tzy; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectInputOutputDemo { public static void main(String[] args) throws IOException { Stduent st = new Stduent("张三", 30); FileOutputStream fos = new FileOutputStream("d:\\tzy.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(st);//把对象序列化到制定文件输出流中 // java.io.NotSerializableException ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\tzy.txt")); try { Stduent stu = (Stduent) ois.readObject(); System.out.println(stu); } catch (ClassNotFoundException e) { e.printStackTrace(); }finally{ ois.close(); } } } class Stduent implements Serializable{ /** * */ private static final long serialVersionUID = 7425793184115828439L; private String name; private int age; private String address; public Stduent(String name, int age) { super(); this.name = name; this.age = age; } 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; } @Override public String toString() { return "Stduent [name=" + name + ", age=" + age + "]"; } }
序列化及反序列化对象
InputStreamReader,OutputStreamWrite
转换流是指将字节流与字符流之间的转换。
转换流的出现方便了对文件的读写,它在字符流和字节流之间架起了一座桥梁,使原本无关联的两种流操作能够进行转化,提高了程序的灵活性。
字节流中的数据都是字符时,转成字符流操作更搞笑。
如果使用非默认编码保存文件或者读取文件时,需要用到转换流,因为字节流的重载构造方法中右制定编码格式的参数,而FileReader和FileWriter是默认编码的文本文件。
常见的编码表:
ASCLL:美国标准信息交换码。用一个字节的7位可以表示
ISO8859-1:拉丁码表,欧洲码。用一个字节的8位表示
GB2312:中国的中文编码表
GBK:中国的中文编码表升级,融合了更多的中文文字符号。
Unicode:国际标准码,融合了多种文字。所有文字都用2个字节来表示,Java语言使用的就是Unicode
UTF-8:最多用3个字节来表示一个字符。
package com.asd.tzy; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStreamWriter; import java.io.Serializable; public class InputOutputStreamReaderWriterDemo { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("d:\\tzy.txt"); OutputStreamWriter oos = new OutputStreamWriter(fos, "utf-8"); BufferedWriter osw = new BufferedWriter(oos); osw.write("我是谁");//在utf-8里中文占3个字节 如果直接用FileReader读,那么用的是默认编码读的是2个字节,所以读出来是乱码 osw.newLine();//换行 osw.write("我时你"); osw.flush();// osw.close(); BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("d:\\tzy.txt"), "utf-8")); String line = null; while((line=br.readLine())!=null){ System.out.println(line); } } }
转换流对指定编码文件读写
RandomAccessFile随机访问文件
支持对随机访问文件的读写和写入
随机访问文件的行为类似存储在文件系统中的一个大型byte数组。存在指向该隐含数组的光标或索引,称为文件指针
输入操作从文件指针开始读取字节,随着对字节的读取而前移此文件指针
如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,随着对字节的写入而前移此文件指针
写入隐含数组末尾之后的输出操作导致该数组扩展。
该文件指针可以通过getFilePointer方法读取,通过seek方法设置
package com.asd.tzy; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Scanner; public class RandomAccessFileDemo { public static void main(String[] args) throws IOException { Person[] persons = { new Person("zhangsan", 90), new Person("justin", 30), new Person("bush", 88), new Person("lisi", 20) }; RandomAccessFile randomAccessFile = new RandomAccessFile("d:\\tzy.txt", "rw");// r只读// rw可读可写 //写入数据到RandomAccessFile for (int i = 0; i < persons.length; i++) { randomAccessFile.writeChars(persons[i].getName()); randomAccessFile.writeInt(persons[i].getAge());//按4个字节写入数字 } //读取指定位置上的Person对象 Scanner sc = new Scanner(System.in); System.out.println("读取第几个Person对象数据"); int num = sc.nextInt(); //使用seek方法来操作存取位置 randomAccessFile.seek((num-1)*Person.size());//跳过多少字节读 Person person = new Person(); person.setName(readName(randomAccessFile)); person.setAge(randomAccessFile.readInt()); System.out.println("姓名:"+person.getName()+";年龄:"+person.getAge()); randomAccessFile.close(); } private static String readName(RandomAccessFile randomAccessFile) throws IOException{ char [] name = new char[15]; for (int i = 0; i < name.length; i++) { name[i]=randomAccessFile.readChar(); } return new String(name).replace("\u0000", ""); } } class Person { private String name; private int age; public Person(String name, int age) { StringBuilder builder = null; if (name != null) { builder = new StringBuilder(name);// 默认为16长度+传入字符串长度的字符串 } else { builder = new StringBuilder(15);// 制定为15长度的字符串 } builder.setLength(15);// 固定长度15个字符,占了30个字节的大小 this.name = builder.toString(); this.age = age; } public Person() { super(); } 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 static int size() { return 34; } }
RandomAccessFile读写对象
网络基础知识
计算机网络
计算机网络,就是把分布在不同地理区域的计算机与专门的外部设备通信线路互联成一个规模大,功能强的网络系统,从而使众多的计算机可以方便地互相传递细心,共享硬件、
软件、数据信息等资源。
网络体系结构
网络体系结构定义计算机设备和其他设备如何连接在一起以形成一个允许用户共享信息和资源的通信系统。
国际标准化组织IOS于1978年提出“开放系统互连参考模型”,即著名的OSI模型。OSI模型保证了各类设备生产厂家的产品兼容新。
该模型把计算机网络分成物理层、数据链路层、网络层、传输层、会话层、表示层、应用层等七层
OSI模型分层的好处
不同厂商生产的设备都可以互相兼容。
设备可以专注于某一层的功能,如交换机工作在第二层,路由器工作在第三层。
方便网络故障排错
不用过多考虑物理接口等这些物理层的东西
TCP/IP是一组用于实现网络互联的通信协议。Internet网络体系结构以TCP/IP为核心。基于TCP/IP的参考模型将协议分成四个层次,它们分别是接口层、网络层、传输层、和应用层。
网络编程的目的
网络编程的目的就是指直接或简介地通过网络协议与其他计算机进行通讯。网络编程中有两个主要的问题,一个是如何准确的定位网络上的一台或多台主机,
另一个就是找到主机后
如何可靠高效的进行数据传输。
网络通训要素
IP地址;端口号;传输协议
IP地址
网络中每台主机都必须有一个唯一的IP地址。
因特网上的IP地址具有全球唯一性。
IP地址由32位2进制组成,占4个字节,常用十进制的格式表示,
例如:192.168.0.5
对应的类-InetAddress
端口号
端口号用来表示该计算机上的应用程序,代表此应用程序逻辑地址。
端口号使用一个16位的数字来表示,它的范围是0~65535,1024以下的端口号保留给预定义的服务。例如http使用80端口。
协议概念
为计算机网络中进行数据交换而建立的规则、标准或约定的集合。
常见的传输协议
TCP是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流
UDP是一种无连接的协议,每个数据包都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否达到目的地,
到达目的地的事件以及内容的正确性都是不能被保证的。
Socket
目前较为流行的网络编程模型是客户机/服务器通信模式(C/S架构:客户机上面要安装客户端 B/S架构:只要有浏览器就可以了,不需要安装客户端)
客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求。通常,一个服务器进程会同时为多个客户进程服务
所谓的socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。。
Socket是连接运行在网络上的两个程序间的双向通许的端点。
网络通许其实指的就是Socket间的通讯。
通讯的两端都有Socket,数据在两个Socket之间通过IO来进行传输。
使用Socket进行网络通信的过程
服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户的连接请求。
客户程序根据服务器程序所在的主机名和端口号发出连接请求。
如果一切正常,服务器接受请求。并获得一个新的绑定到不同端口地址的套接字。
客户和服务器通过读、写套接字进行通讯。
创建TCP服务器的步骤
创建一个ServerSocket对象
调用accept()方法接受客户端请求
从Socket中获取IO流
对IO流进行读写操作,完成与客户端的互交。
关闭IO流和Socket
创建TCP客户端步骤
创建一个Socket对象
从Socket中获取IO流
对IO流进行读写操作,完成与服务器的交互。
关闭IO流和Socket
package com.asd.tzy; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class TCPServer { public static void main(String[] args) throws IOException { // 创建一个ServerSocket对象(服务器) ServerSocket serverSocket = new ServerSocket(8888); // 调用accept()方法来接受客户端的请求; // Socket accept() 侦听并接受到此套接字的连接。 Socket socket = serverSocket.accept(); // InetAddress getInetAddress() 返回套接字连接的地址。 // String getHostAddress() 返回 IP 地址字符串(以文本表现形式)。 // String getHostName() 获取此 IP 地址的主机名 System.out.println("主机名" + socket.getInetAddress().getHostName() + "IP地址" + socket.getInetAddress().getHostAddress() + "连接成功"); // 获取socket对象的输入输出流 // InputStream getInputStream() 返回此套接字的输入流。 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); PrintWriter pw = new PrintWriter(socket.getOutputStream(),true); String line = null; //读取客户端传过来的数据 while ((line = br.readLine()) != null) { if (line.trim().equals("over")) { break; } else { System.out.println(line); //bw.write(line.toUpperCase());//转换成大写的传给客户端 //bw.newLine(); //bw.flush(); pw.println(line.toUpperCase()); } } //bw.close(); pw.close(); br.close(); socket.close(); System.out.println("主机名" + socket.getInetAddress().getHostName() + "IP地址" + socket.getInetAddress().getHostAddress() + "连接断开"); } } TCPServer |
package com.asd.tzy; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import java.net.UnknownHostException; public class TCPClient { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 8888); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); BufferedReader br1 = new BufferedReader(new InputStreamReader(socket.getInputStream())); while(true){ String line = br.readLine();//获取键盘输入的字符串 bw.write(line); bw.newLine();//要加换行 服务器才知道读了一行 bw.flush(); if(line.trim().equals("over")){ break; } System.out.println(br1.readLine());//获取服务端传来的大写字符串 } br1.close(); bw.close(); br.close(); socket.close(); } } TCPClient |
基于UDP协议的Socket编程
创建发送端
创建DatagramSocket对象。 该断点建立,系统会随机分配一个端口。如果不想随机配置,可以手动指定。
将数据进行packet包的封装,必须要指定目的地 地址和端口
通过socket服务的send方法将该包发出。
将socket关闭
创建接受端
建立DatagramSocket对象。要监听一个端口。
透过socket的receive方法将数据存入数据包中。
通过数据包dp的方法getData(),getAddredss(),getPort()等方法获取包中的指定信息。
将socket关闭
package com.asd.tzy; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; public class UDPDemo2 { public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(8000); byte [] buff = new byte[100]; DatagramPacket packet = new DatagramPacket(buff, 100); socket.receive(packet);//接受传来的数据包 System.out.println(new String(packet.getData())); String str = "me too!"; DatagramPacket packet2 = new DatagramPacket(str.getBytes(), 0, str.length(), packet.getAddress(), packet.getPort()); socket.send(packet2); socket.close(); } } UDPDemo2 先启动 |
package com.asd.tzy; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; public class UDPDemo1 { public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(); String str = "i love you!"; //把数据进行封装,封装到数据报 包中; DatagramPacket packet = new DatagramPacket(str.getBytes(), 0, str.length(), InetAddress.getByName("localhost"), 8000); socket.send(packet);//发送 byte [] buff = new byte[100]; DatagramPacket packet2 = new DatagramPacket(buff, 100); socket.receive(packet2); System.out.println(new String(packet2.getData())); socket.close(); } } UDPDemo1后启动 |
多线程Socket
package com.asd.tzy; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class ChatServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9999); int number = 1;//保存客户端个数 while(true){ Socket socket = serverSocket.accept(); System.out.println("客户端"+number+"连接成功"); //服务端开启一个独立的线程来对客户端进行读写操作 new Thread(new ServerStream(socket, number)).start();; number++; } } } 服务器端 |
package com.asd.tzy; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class ServerStream implements Runnable { private Socket socket = null; private int number; public ServerStream(Socket socket, int number) { super(); this.socket = socket; this.number = number; } @Override public void run() { try { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter pw = new PrintWriter(socket.getOutputStream(),true); while(true){ String line = br.readLine(); System.out.println("客户端"+number+":"+line); pw.println(line.toUpperCase()); if(line.trim().equals("bye")){ System.out.println("客户端:"+number+"断开连接"); break; } } br.close(); pw.close(); } catch (IOException e) { e.printStackTrace(); } } } 服务器读写 |
package com.asd.tzy; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; public class ChatClient { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("127.0.0.1", 9999); new Thread(new ClirentOutputStream(socket)).start(); new Thread(new ClientInputStream(socket)).start(); } } 客户端 |
package com.asd.tzy; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class ClirentOutputStream implements Runnable{ private Socket socket = null; public ClirentOutputStream(Socket socket) { super(); this.socket = socket; } @Override public void run() { try { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(socket.getOutputStream(),true); while(true){ String line = br.readLine(); pw.println(line); if(line.trim().equals("bye")){ break; } } br.close(); pw.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } 客户端读 |
package com.asd.tzy; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.net.SocketException; public class ClientInputStream implements Runnable { private Socket socket = null; public ClientInputStream(Socket socket) { super(); this.socket = socket; } @Override public void run() { try { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while(true){ try { String line = br.readLine(); System.out.println(line); } catch (SocketException e) { break; } } br.close(); } catch (IOException e) { e.printStackTrace(); } } } 客户端写 |
URL类
URL是统一资源定位器的简称,它表示Internet上某个资源的地址。通过URL我们可以访问internet上的各种网络资源,比如常见的www,FPT站点。浏览器通过解析给定的
URL可以在网络上查找相应的文件或其他资源。
URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#<引用>
http://www.comcat.com:80/Gamelan/network.html#BOTTOM
为了表示URL,java.net包中实现了类URL。我们可以通过下面的构造方法来初始化一个URL对象
获取URL对象的属性
boolean |
equals(Object obj) 比较此 URL 是否等于另一个对象。 |
String |
getAuthority() 获取此 URL 的授权部分。 |
Object |
getContent() 获取此 URL 的内容。 |
Object |
getContent(Class[] classes) 获取此 URL 的内容。 |
int |
getDefaultPort() 获取与此 URL 关联协议的默认端口号。 |
String |
getFile() 获取此 URL 的文件名。 |
String |
getHost() 获取此 URL 的主机名(如果适用)。 |
String |
getPath() 获取此 URL 的路径部分。 |
int |
getPort() 获取此 URL 的端口号。 |
String |
getProtocol() 获取此 URL 的协议名称。 |
String |
getQuery() 获取此 URL 的查询部分。 |
String |
getRef() 获取此 URL 的锚点(也称为“引用”)。 |
String |
getUserInfo() 获取此 URL 的 userInfo 部分。 |
int |
hashCode() 创建一个适合哈希表索引的整数。 |
URLConnection |
openConnection() 返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。 |
URLConnection |
openConnection(Proxy proxy) 与 openConnection() 类似,所不同是连接通过指定的代理建立;不支持代理方式的协议处理程序将忽略该代理参数并建立正常的连接。 |
InputStream |
openStream() 打开到此 URL 的连接并返回一个用于从该连接读入的InputStream 。 |
boolean |
sameFile(URL other) 比较两个 URL,不包括片段部分。 |
protected |
set(String protocol, String host, 设置 URL 的字段。 |
protected |
set(String protocol, String host, 设置 URL 的指定的 8 个字段。 |
static void |
setURLStreamHandlerFactory(URLStreamHandlerFactory fac) 设置应用程序的 URLStreamHandlerFactory 。 |
String |
toExternalForm() 构造此 URL 的字符串表示形式。 |
String |
toString() 构造此 URL 的字符串表示形式。 |
URI |
toURI() 返回与此 URL 等效的 URI 。 |
URL右两种方法可以访问Internet上资源
package com.asd.tzy; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; public class URLDemo { public static void main(String[] args) { try { DownloadUtil.download("http://scimg.jb51.net/allimg/121209/2-1212091UH0339.jpg", "my.jpg", "d:\\abcimage"); } catch (IOException e) { e.printStackTrace(); } } } class DownloadUtil{ public static void download(String urlString,String fileName,String savePath) throws IOException{ URL url = new URL(urlString); //URLConnection conn = url.openConnection(); //InputStream is = conn.getInputStream(); InputStream is = url.openStream(); byte[] buff = new byte[1024]; int len = 0; File file = new File(savePath); if(!file.exists()){ file.mkdirs(); } OutputStream os = new FileOutputStream(file.getAbsolutePath()+"\\"+fileName); while((len=is.read(buff))!=-1){ os.write(buff, 0, len); } //施放资源 os.close(); is.close(); } }
URL网页下载图片
XML定义
XML指可扩展标记语言,适合Wed传输,类似HTML,不同的是它的设计宗旨是传输数据,而非显示数据。(不是展示数据)
XML提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。XML标签没有被预定义,开发者需要自行定义标签。XML被设计为具有自我描述性,
是W3C的推荐标准。
XML文档结构
XML申明:<?xml version="1.0" standalone="yes" encoding="UTF-8"?>
XML根元素定义:XML文档的树形结构要求必须有一个根元素。根元素的起始标记要放在所有其它元素起始标记之前,
根元素的结束标记放在其它所有元素的结束标记之后。
XML元素:元素的基本结构由 开始标记,数据内容,结束标记组成
XML中的注释:<!--this is comment-->
XML的语法规则
所有的XML元素都必须有关闭标签
XML标签对大小写敏感
XML必须正确嵌套
XML文档必须有根元素
XML的属性以名值对方式组成,值需加引号
在XML中,空格会被保留,文档中的空格不会被删节
SAX解析器
SAX(Simple API For XML)是一个公共的基于事件的XML文档解析标准,能够通过一个简单的、快速的方法来对XML文档进行处理,和DOM相比它所占用的
系统资源更少
SAX即是一个接口,也是一个软件包。作为接口,SAX是事件驱动型XML解析的一个标准接口,对文档进行顺序扫描,当扫描到文档(document)开始、
元素(element)开始与结束、文档结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
SAX解析器API
大多数SAX会产生以下类型的事件
在文档的开始和结束时触发文档处理事件
在文档内每一个XML元素接受解析的前后触发元素事件
在任元数据通常由单独的数据来处理
举例:
<doc>
<para>Hello,tom<para>
<doc>
其解析过程:1:satrt document 2:start element:doc.. 3:start:element:para.. 4:characters:Hello... 5:end element:para... 6:end element:doc 7:end document
解析步骤:
创建事件处理程序(即编写ContentHandler的实现类,一般继承自DefaultHandler类,采用adapter模式)
创建SAX解析器
将事件处理程序分配到解析器
对文档进行解析,将每个事件发送给事件处理程序
常用接口:ContentHandler 接口
ContentHandler是JAVA类包中一个特殊的SAX接口
该接口封装了一些对事件处理的方法,当XML解析器开始解析XML输入文档时,它会遇到某些特殊的事件,比如文档的开头与结束、元素的开头和结束、一级
元素中的字符数据等事件。当遇到这些事件时,XML解析器会调用ContentHandler接口中相应的方法来响应事件
ContentHander接口常用方法:
void startDocument()//文档解析开始的处理
void endDocument()//文档解析结束的处理
void startElement(String uri,String localName,String qName,Attributes atts)//ElementNode开始的处理
void endElement(String uri,String localName,String qName)//ElementNode结束的处理
void characters(char[] ch, int start,int lenght)//具体在某一节点中的处理