day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流

1 API

1.1 Api文档下载

1.API (Application Programming Interface,应用程序编程接口)是 Java 提供的基本编程接口,一切可以调用的东西都是API。

2.Java语言提供了大量的基础类,因此 Oracle 也为这些基础类提供了相应的API文档,用于告诉开发者如何使用这些类,以及这些类里包含的方法。

3.下载API
Additional Resources-Java SE 8 Documentation下载。
http://www.oracle.com/technetwork/java/javase/downloads/index.html

4.详见:JDK8版本的Api的下载-安装-配置.doc
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第1张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第2张图片

1.2 常用类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第3张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第4张图片

1.3 Object

1.3.1 概念

  1. Object类是所有Java类的根父类

  2. 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类

  3. Object类中的功能(属性、方法)就具有通用性。
    属性:无
    方法:equals() / toString() / getClass() /hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
    构造方法:Object类只声明了一个空参的构造器
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第5张图片

  4. Object类存在于java.lang包中,这个lang包不需要我们手动导包
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第6张图片

1.3.2 Object中的方法讲解

//1.clone()不常用,用的时候在查找资料,这里不在讲解。
protected  Object clone() 
          创建并返回此对象的一个副本

//2.此时讲解。          
 boolean equals(Object obj) 
          指示其他某个对象是否与此对象“相等”。 
 
 String toString() 
          返回该对象的字符串表示。

//3.剩下的方法在后续反射、集合、线程通信时讲。          
 int hashCode() 
          返回该对象的哈希码值。 
 protected  void finalize() 
          当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调 用此方法。 
 Class<?> getClass()
          返回此Object运行时的类。
 ......

1.3.3 == 和 equals() 区别

EqualsTest 类

package com.atguigu.java1;

import java.util.Date;

/*
 * 
 * 面试题: == 和 equals() 区别
 * 
 * 一、回顾 == 的使用:
 * == :运算符
 * 1. 可以使用在基本数据类型变量和引用数据类型变量中
 * 2. 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
 *    如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
 * 补充: == 符号使用时,必须保证符号左右两边的变量类型一致。(注意指的是一致而不是相同,如都是数字类型int 和double可以比较,而int和boolean不可以比较)
 * 
 * 二、equals()方法的使用:
 * 1. 是一个方法,而非运算符
 * 2. 只能适用于引用数据类型(解释:调用方法都是通过 对象名.方法名()为引用数据类型,基本类型没法调方法,除非包装类)
 * 3. Object类中equals()的定义:
 *    public boolean equals(Object obj) {
	        return (this == obj);
	  }
 *    说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
 * 
 * 4. 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是
 *    两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
 *    
 * 5. 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们
 *    就需要对Object类中的equals()进行重写(可以是自己手写,也可以通过开发工具如eclipse,右键--source...自动生成).
 *    重写的原则:比较两个对象的实体内容是否相同.
 * 6.注意事项:1.x.equals(null) 在任何情况下返回值都为false,
 *            2.null.equals(y) 就不能这样写会报空指针异常。所以x.equals(y)一般进行equals判断时最好先判断x是否为null 
 *            不是Null时在向下比。如果后面的y是明确的字符串如:abc这样确定的值时,可以把确定的字符串写到前面避免出现空指针异常。
 *            例如:
 *              public class Student extends Person {
                    public static void main(String[] args) {

                        System.out.println("请输入字符串name:");
                        String name= new Scanner(System.in).nextLine();
                        System.out.println("判断输入的字符串name存的值是否为Tom:");
        
                      //if(name.equals("Tom")){有风险如果name为null会报空指针
                        if("Tom".equals(name)){//修改为:把确定的字符串放到前面,这样可以保证不会变为 null.(y),避免空指针。
                            System.out.println("name的值为Tom");
                        }else {
                            System.out.println("name值不是Tom");
                        }

                }           
 */
public class EqualsTest {
	public static void main(String[] args) {
		
		//基本数据类型
		int i = 10;
		int j = 10;
		double d = 10.0;
		System.out.println(i == j);//true
		System.out.println(i == d);//true
		
		boolean b = true;
//		System.out.println(i == b);
		
		char c = 10;
		System.out.println(i == c);//true
		
		char c1 = 'A';
		char c2 = 65;
		System.out.println(c1 == c2);//true
		
		//引用类型:
		Customer cust1 = new Customer("Tom",21);
		Customer cust2 = new Customer("Tom",21);
		System.out.println(cust1 == cust2);//false
		
		String str1 = new String("atguigu");
		String str2 = new String("atguigu");
		System.out.println(str1 == str2);//false
		System.out.println("****************************");
		System.out.println(cust1.equals(cust2));//false--->true
		System.out.println(str1.equals(str2));//true
		
		Date date1 = new Date(32432525324L);
		Date date2 = new Date(32432525324L);
		System.out.println(date1.equals(date2));//true
		
		
	}
}

Customer类

package com.atguigu.java1;

public class Customer {
	
	private String name;
	private int 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;
	}
	public Customer() {
		super();
	}
	public Customer(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	//1.1通过工具自动生成的equals()(eclipse工具:右键--source...)
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Customer other = (Customer) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
	
	
	//重写的原则:比较两个对象的实体内容(即:name和age)是否相同
	//1.2手动实现equals()的重写
//	@Override
//	public boolean equals(Object obj) {
//		
		System.out.println("Customer equals()....");
//		if (this == obj) {
//            return true;
//        }
//		
//		if(obj instanceof Customer){
//			Customer cust = (Customer)obj;
//			//比较两个对象的每个属性是否都相同
			if(this.age == cust.age && this.name.equals(cust.name)){
				return true;
			}else{
				return false;
			}
//			
//			//或
//			return this.age == cust.age && this.name.equals(cust.name);
//		}else{
//			return false;
//			
//		}
//		
//	}



	//2.1手动实现
//	@Override
//	public String toString() {
//		return "Customer[name = " + name + ",age = " + age + "]"; 
//	}
	//2.2自动实现(eclipse工具:右键--source...)
	@Override
	public String toString() {
		return "Customer [name=" + name + ", age=" + age + "]";
	}
}

1.3.4 toString

package com.atguigu.java1;

import java.util.Date;

/*
 * Object类中toString()的使用:
 * 
 * 1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
 * 
 * 2. Object类中toString()的定义:
 *   public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
     }
 * 
 * 3. 像String、Date、File、包装类等都重写了Object类中的toString()方法。
 *    使得在调用对象的toString()时,返回"实体内容"信息
 *    
 * 4. 自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"(自己手写或者通过eclipse工具自动生成)
 */
public class ToStringTest {
	public static void main(String[] args) {
		
		Customer cust1 = new Customer("Tom",21);
		System.out.println(cust1.toString());//com.atguigu.java1.Customer@15db9742-->Customer[name = Tom,age = 21]
		System.out.println(cust1);//com.atguigu.java1.Customer@15db9742-->重写后输出Customer[name = Tom,age = 21]
		
		String str = new String("MM");
		System.out.println(str);//MM
		
		Date date = new Date(4534534534543L);
		System.out.println(date.toString());//Mon Sep 11 08:55:34 GMT+08:00 2113
		
	}
}

1.4 String

1.4.1 继承关系

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第7张图片

	--

;

1.4.2 特点

public final class String :String是final修饰的,不能被继承。
private final char value[ ]:String底层维护一个char[ ],数组长度不能改,而且是final修饰的,数组的值也不能被修改。- - - 也就是说字符串是个常量,一旦定义不能修改。
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第8张图片

1.4.3 创建String对象

方式1:字面量方式创建

  1. 语法:String s1 = “abc”;//字面量的定义方式
  2. 如果是第一次使用字符串,java会在字符串常量池创建一个对象。
  3. 常量池里直接创建对象(本质还是char[]),再次使用相同内容,会去常量池中找到已经存在的对象,不会新建。(减少内存的开销)

测试:
注意:不同版本的jvm虚拟机里面存储方式有区别:jdk1.6和jdk1.8的版本常量池在方法区中,jdk1.7常量池在堆中。
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第9张图片

/*
    String:字符串,使用一对""引起来表示。
    1.String声明为final的,不可被继承
    2.String实现了Serializable接口:表示字符串是支持序列化的。
            实现了Comparable接口:表示String可以比较大小
    3.String内部定义了final char[] value用于存储字符串数据
    4.String:代表不可变的字符序列。简称:不可变性。
        体现:1.当对字符串重新赋值时,需要重写指定内存区域赋值(地址值发生了变化),不能使用原有的value进行赋值。
             2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
             3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
             总结:以字面量的方式创建String字符串,一旦字符串里面的内容发生了变化,说明重新造了个新的字符串String,地址值发生了变化。
    5.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
    6.字符串常量池中是不会存储相同内容的字符串的。
     */
    @Test
    public void test1(){
        String s1 = "abc";//字面量的定义方式
        String s2 = "abc";
        s1 = "hello";//因为String是final修饰的char类型的数组,一旦重新赋值说明新造了一个字符串 即地址值发生了变化。

        System.out.println(s1 == s2);// 比较s1和s2的地址值

        System.out.println(s1);//hello
        System.out.println(s2);//abc

        System.out.println("*****************");

        String s3 = "abc";
        s3 += "def";
        System.out.println(s3);//abcdef
        System.out.println(s2);

        System.out.println("*****************");

        String s4 = "abc";
        String s5 = s4.replace('a', 'm');
        System.out.println(s4);//abc
        System.out.println(s5);//mbc

    }

方式2:new + 构造器的方式
语法:

第一种:
//本质上this.value = new char[0];
String s1 = new String(); 
第二种:
//this.value = original.value;
String s2 = new String(String original); 
第三种:
//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a); 
第四种:
String s4 = new String(char[] a,int startIndex,int count);
......还有很多种,具体查看Api

测试:

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第10张图片

 /*
    String的实例化方式:
    方式一:通过字面量定义的方式
    方式二:通过new + 构造器的方式

     面试题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
            两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"

     */
    @Test
    public void test2(){
        //通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
        String s1 = "javaEE";
        String s2 = "javaEE";
        //通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
        String s3 = new String("javaEE");
        String s4 = new String("javaEE");

        System.out.println(s1 == s2);//true
        System.out.println(s1 == s3);//false
        System.out.println(s1 == s4);//false
        System.out.println(s3 == s4);//false

        System.out.println("***********************");
        Person p1 = new Person("Tom",12);
        Person p2 = new Person("Tom",12);

        System.out.println(p1.name.equals(p2.name));//true
        /*存储过程:1.创建的对象p1 p2储存在堆中,p1 p2栈的地址值不同。
        			2.p1 p2对象里面的value值是char类型的数组对应常量池的地址,
        			 常量池的值相同代表p1.name,p2.name的地址值相同。*/
        System.out.println(p1.name == p2.name);//true

        p1.name = "Jerry";
        System.out.println(p2.name);//Tom
    }

1.4.3 字符串不同拼接操作的对比

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第11张图片

 /*
    结论:
    1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
    2.只要其中有一个是变量,结果就在堆中。
    3.如果拼接的结果调用intern()方法,返回值就在常量池中
     */
      @Test
    public void test4(){
        String s1 = "javaEEhadoop";
        String s2 = "javaEE";
        String s3 = s2 + "hadoop";
        System.out.println(s1 == s3);//false

        final String s4 = "javaEE";//s4:常量
        String s5 = s4 + "hadoop";
        System.out.println(s1 == s5);//true

    }
 @Test
    public void test3(){
        String s1 = "javaEE";
        String s2 = "hadoop";

        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";
        String s5 = s1 + "hadoop";
        String s6 = "javaEE" + s2;
        String s7 = s1 + s2;

        System.out.println(s3 == s4);//true
        System.out.println(s3 == s5);//false
        System.out.println(s3 == s6);//false
        System.out.println(s3 == s7);//false
        System.out.println(s5 == s6);//false
        System.out.println(s5 == s7);//false
        System.out.println(s6 == s7);//false

        String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
        System.out.println(s3 == s8);//true


    }

1.4.4 常用方法

        char charAt(int index)  index 索引(即:下标,底层是数组)
          	返回指定索引处的 char 值。 
		String concat(String str) 
		          将指定字符串连接到此字符串的结尾。 
		boolean contains(CharSequence s) 
		          当且仅当此字符串包含指定的 char 值序列时,返回 trueboolean endsWith(String suffix) 
		          测试此字符串是否以指定的后缀结束。 
		boolean equals(Object anObject) 
		          将此字符串与指定的对象比较。 
		byte[] getBytes() 
	  	          使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
		int indexOf(String str) 
		 		  返回指定子字符串在此字符串中第一次出现处的索引。
		int lastIndexOf(String str) 
		          返回指定子字符串在此字符串中最右边出现处的索引。 
		boolean isEmpty() 
		          当且仅当 length()0 时返回 trueint length() 
		          返回此字符串的长度。
		String replace(char oldChar, char newChar) 
		          返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 
		String[] split(String regex) 
		          根据给定正则表达式的匹配拆分此字符串。 
		boolean startsWith(String prefix) 
		          测试此字符串是否以指定的前缀开始。 
		String substring(int beginIndex) 
		          返回一个新的字符串,它是此字符串的一个子字符串。 
		String substring(int beginIndex, int endIndex) 
		          返回一个新字符串,它是此字符串的一个子字符串。 
		char[] toCharArray() 
		          将此字符串转换为一个新的字符数组。 
		String toLowerCase() 
		          使用默认语言环境的规则将此 String 中的所有字符都转换为小写。 
		String toUpperCase() 
		          使用默认语言环境的规则将此 String 中的所有字符都转换为大写。 
		String trim() 
		          返回字符串的副本,忽略前导空白和尾部空白。
		static String valueOf(int i) 
		          返回 int 参数的字符串表示形式。

1.4.5 测试常用方法

     package cn.tedu.api;

		import java.util.Arrays;

		//测试 String工具类
		public class Test2_String {
			public static void main(String[] args) {
				//1, 字符串底层维护了一个char[]  , 而且是final的,也就是数组长度不能改,数组里的值也不能改,----字符串是一个常量 !!
				char[] c = new char[] {'a','b','c'} ;
				String s = new String(c) ;  //  触发char[]类型的含参构造--存在了堆内存中
				
				String str = "abc" ;  //  直接赋值,存在堆内存中的常量池中--高效--因为常量池的相同数据,只会存一次
				String str2 = "abc";
				System.out.println(str2==str);//true, 相同数据,拥有相同的存储空间,内存中就是相同的地址值
				System.out.println(s==str);//false, 在堆里的地址值,str在常量池的地址值,不相同
				
				//2,常用方法
//前面有返回值类型,
				System.out.println( s.charAt(1) ); // 根据下标获取对应的字符
				System.out.println( s.concat("123") );//在字符串的末尾处拼接自定字符串
				System.out.println( s.contains("bc") );//判断是否包含指定的字符串
				System.out.println( s.endsWith("c") );//判断字符串 是否以指定后缀 结尾
				System.out.println( s.equals("abc") );//判断字符是 是否 与指定的字符串  相等
				System.out.println( s.indexOf("a") );//获取指定字符串在s出现的第一次的下标值
				
				s = "abca";
				System.out.println( s.lastIndexOf("a") );//获取指定字符串在s出现的最后一次的下标值
				System.out.println( s.isEmpty() );//判断字符串是否为空
				System.out.println( s.length() );//获取字符串的长度
				System.out.println( s.replace('a', '0') );//把旧字符用新字符替换 , 0bc0
				System.out.println( s.startsWith("ab") );//判断是否以指定字符串开始
				System.out.println( s.substring(1) );//从指定下标处开始,截取所有字符串
				System.out.println( s.substring(0,2) );//从指定下标开始,到指定下标结束,截取中间段[0,2)--含头不含尾
				System.out.println( s.toLowerCase() );//自动转成小写
				System.out.println( s.toUpperCase() );//自动转成大写
				
				s = "   ab  ca    ";
				System.out.println( s.trim() );//去除前面空格和后面空格
				
				String num=String.valueOf(123);//用来把各种类型的数据  转成 String类型
				System.out.println(num+1);//1231
				
				System.out.println("------字符串转数组------");
				byte[] bs = s.getBytes();//把字符串的数据放入byte[]里
				System.out.println( Arrays.toString(bs) );
				
				char[] cs = s.toCharArray(); //把字符串的数据放入char[]里
				System.out.println( Arrays.toString(cs) );
				
				s = "a0b0c0";
				String[] ss = s.split("0");//按照指定的规则切割字符串
				System.out.println( Arrays.toString(ss) );//[a, b, c]
				
			}
		}

1.4.6 回顾:String 与基本数据类型、包装类之间的转换。

 /*
    复习:
    String 与基本数据类型、包装类之间的转换。

    String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
    基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)

     */
     
    @Test
    public void test1(){
        String str1 = "123";
//        int num = (int)str1;//错误的
        int num = Integer.parseInt(str1);

        String str2 = String.valueOf(num);//"123"
        String str3 = num + "";

        System.out.println(str1 == str3);
    }

1.4.7 String 与 字符数组char[ ]之间的转换

 /*
    String 与 char[]之间的转换

    String --> char[]:调用String的toCharArray()
    char[] --> String:调用String的构造器
     */
    @Test
    public void test2(){
        String str1 = "abc123";  //题目: a21cb3

        char[] charArray = str1.toCharArray();
        for (int i = 0; i < charArray.length; i++) {
            System.out.println(charArray[i]);
        }

        char[] arr = new char[]{'h','e','l','l','o'};
        String str2 = new String(arr);
        System.out.println(str2);
    }

1.4.8 String 与 字节数组byte[ ]之间的转换

/*
    String 与 byte[]之间的转换
    编码:String --> byte[]:调用String的getBytes()
    解码:byte[] --> String:调用String的构造器

    编码:字符串 -->字节  (看得懂 --->看不懂的二进制数据)
    解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂)

    说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
     */
    @Test
    public void test3() throws UnsupportedEncodingException {
        String str1 = "abc123中国";
        byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
        System.out.println(Arrays.toString(bytes));

        byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
        System.out.println(Arrays.toString(gbks));

        System.out.println("******************");

        String str2 = new String(bytes);//使用默认的字符集,进行解码。
        System.out.println(str2);

        String str3 = new String(gbks);
        System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!


        String str4 = new String(gbks, "gbk");
        System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!


    }

1.5 StringBuilder/StringBuffer

1.5.1 特点

  1. 封装了char[ ]数组
  2. 是可变的字符序列
  3. 提供了一组可以对字符内容修改的方法
  4. 常用append()来代替字符串做字符串连接
  5. 内部字符数组默认初始容量是16:initial capacity of 16 characters
  6. 如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
  7. StringBuffer 1.0出道线程安全,StringBuilder1.5出道线程不安全
  8. public final class StringBuilder -----是final的类,不可以被继承
    char[] value --------底层仍然像String一样,维护了一个char[] ,只不过值可以随时被修改

1.5.2 String、StringBuffer、StringBuilder三者的异同?

 /*
    String、StringBuffer、StringBuilder三者的异同?
    String:不可变的字符序列;底层使用 final char[]存储
    StringBuffer:可变的字符序列;jdk1.0线程安全的,效率低;底层使用char[]存储
    StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

    源码分析:StringBuffer StringBuilder底层扩容方式相同。
    String str = new String();//char[] value = new char[0];
    String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};

    StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
    System.out.println(sb1.length());//
    sb1.append('a');//value[0] = 'a';
    sb1.append('b');//value[1] = 'b';

    StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];

    //问题1. System.out.println(sb2.length());//3 输出的是长度是存储的个数
    //问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
             默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。

			开发中如果对字符串需要频繁的修改,该如何选择???
			首先:排除String考虑 StringBuffer和 StringBuilder,因为String每次改变都要新创建一个效率低。
			其次:在考虑线程安全问题,如果是多线程操作共享的资源有线程安全问题时,考虑使用StringBuffer。否则不是多线程或者是                 
			     多线程但没有线程安全问题用 StringBuilder。
			之后:StringBuffer和 StringBuilder创建好后默认长度为16,当长度不够时需要进行扩容影响效率。所以开发中如果知
			    道存的字符个数时建议使用知道长度的构造方法创建对象:StringBuffer(int capacity)
			                                           StringBuilder(int capacity)


     */
    @Test
    public void test1(){
        StringBuffer sb1 = new StringBuffer("abc");
        sb1.setCharAt(0,'m');
        System.out.println(sb1);

        StringBuffer sb2 = new StringBuffer();
        System.out.println(sb2.length());//0
    }

1.5.3 常用方法

注意:StringBuffer和StringBuilder 里面的方法名相同,用法也相同。

 /*
    StringBuffer的常用方法:
    StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
    StringBuffer delete(int start,int end):删除指定位置的内容
    StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str  只要涉及到开始和结束位                                                                          置的都是左闭右开。
    StringBuffer insert(int offset, xxx):在指定位置插入xxx
    StringBuffer reverse() :把当前字符序列逆转
    public int indexOf(String str) 返回第一次出现的指定子字符串在该字符串中的索引。
    public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
    public int length() 返回长度(字符数)。
    public char charAt(int n )返回此序列中指定索引处的 char 值。
    public void setCharAt(int n ,char ch)将给定索引处的字符设置为 ch。

        总结:
        增:append(xxx)
        删:delete(int start,int end)
        改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
        查:charAt(int n )
        插:insert(int offset, xxx)
        长度:length();
        *遍历获取内容:for() + charAt() / 直接toString()获取内容
     */
    @Test
    public void test2(){
        StringBuffer s1 = new StringBuffer("abc");
        s1.append(1);
        s1.append('1');
        System.out.println(s1);
//        s1.delete(2,4);
//        s1.replace(2,4,"hello");
//        s1.insert(2,false);
//        s1.reverse();
        String s2 = s1.substring(1, 3);
        System.out.println(s1);
        System.out.println(s1.length());
        System.out.println(s2);
    }

1.5.4 三种字符串的效率对比


/**
 * 关于StringBuffer和StringBuilder的使用
 *
 * @author shkstart
 * @create 2019 下午 3:32
 */
public class StringBufferBuilderTest {
    /*
    对比String、StringBuffer、StringBuilder三者的效率:
    从高到低排列:StringBuilder > StringBuffer > String
     */
    @Test
    public void test3(){
        //初始设置
        long startTime = 0L;
        long endTime = 0L;
        String text = "";
        StringBuffer buffer = new StringBuffer("");
        StringBuilder builder = new StringBuilder("");
        //开始对比
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            buffer.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuffer的执行时间:" + (endTime - startTime));//7

        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            builder.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuilder的执行时间:" + (endTime - startTime));//4

        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            text = text + i;
        }
        endTime = System.currentTimeMillis();
        System.out.println("String的执行时间:" + (endTime - startTime));//1026

    }

1.6 字符串课后练习(473集)

1.6.1 字符串进行反转

package com.atguigu.exer;

import org.junit.Test;

/**
 * @author shkstart
 * @create 2019 上午 10:07
 */
public class StringDemo {

    /*
    将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”

    方式一:转换为char[]
     */
    public String reverse(String str,int startIndex,int endIndex){

        if(str != null){
            char[] arr = str.toCharArray();
            for(int x = startIndex,y = endIndex;x < y;x++,y--){
                char temp = arr[x];
                arr[x] = arr[y];
                arr[y] = temp;
            }

            return new String(arr);
        }
        return null;
    }

    //方式二:使用String的拼接
    public String reverse1(String str,int startIndex,int endIndex){
        if(str != null){
            //第1部分
            String reverseStr = str.substring(0,startIndex);
            //第2部分
            for(int i = endIndex;i >= startIndex;i--){
                reverseStr += str.charAt(i);
            }
            //第3部分
            reverseStr += str.substring(endIndex + 1);

            return reverseStr;

        }
        return null;
    }
    //方式三:使用StringBuffer/StringBuilder替换String
    public String reverse2(String str,int startIndex,int endIndex){
        if(str != null){
            StringBuilder builder = new StringBuilder(str.length());

            //第1部分
            builder.append(str.substring(0,startIndex));
            //第2部分
            for(int i = endIndex;i >= startIndex;i--){

                builder.append(str.charAt(i));
            }
            //第3部分
            builder.append(str.substring(endIndex + 1));

            return builder.toString();
        }
        return null;

    }

    @Test
    public void testReverse(){
        String str = "abcdefg";
        String reverse = reverse2(str, 2, 5);
        System.out.println(reverse);
    }

}

1.6.2 获取一个字符串在另一个字符串中出现的次数

package com.atguigu.exer;

import org.junit.Test;

/**
 * @author shkstart
 * @create 2019 上午 10:26
 */
public class StringDemo1 {
    /*
    获取一个字符串在另一个字符串中出现的次数。
      比如:获取“ab”在 “abkkcadkabkebfkaabkskab” 中出现的次数

     */

    /**
     * 获取subStr在mainStr中出现的次数
     * @param mainStr
     * @param subStr
     * @return
     */
    public int getCount(String mainStr,String subStr){
        int mainLength = mainStr.length();
        int subLength = subStr.length();
        int count = 0;
        int index = 0;
        if(mainLength >= subLength){
            //方式一:
//            while((index = mainStr.indexOf(subStr)) != -1){
//                count++;
//                mainStr = mainStr.substring(index + subStr.length());
//            }
            //方式二:对方式一的改进
            while((index = mainStr.indexOf(subStr,index)) != -1){
                count++;
                index += subLength;
            }

            return count;
        }else{
            return 0;
        }
    }

    @Test
    public void testGetCount(){
        String mainStr = "abkkcadkabkebfkaabkskab";
        String subStr = "ab";
        int count = getCount(mainStr, subStr);
        System.out.println(count);
    }
}

1.6.3 获取两个字符串中最大相同子串

package com.atguigu.exer;

import org.junit.Test;

import java.util.Arrays;

/**
 * @author shkstart
 * @create 2019 上午 10:42
 */
public class StringDemo2 {
    /*
    获取两个字符串中最大相同子串。比如:
   str1 = "abcwerthelloyuiodefabcdef";str2 = "cvhellobnm"
   提示:将短的那个串进行长度依次递减的子串与较长的串比较。

     */
    //前提:两个字符串中只有一个最大相同子串
    public String getMaxSameString(String str1,String str2){
        if(str1 != null && str2 != null){
            String maxStr = (str1.length() >= str2.length())? str1 : str2;
            String minStr = (str1.length() < str2.length())? str1 : str2;
            int length = minStr.length();

            for(int i = 0;i < length;i++){
                for(int x = 0,y = length - i;y <= length;x++,y++){
                    String subStr = minStr.substring(x,y);
                    if(maxStr.contains(subStr)){
                        return subStr;
                    }

                }
            }

        }
        return null;
    }

    // 如果存在多个长度相同的最大相同子串
    // 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
    public String[] getMaxSameString1(String str1, String str2) {
        if (str1 != null && str2 != null) {
            StringBuffer sBuffer = new StringBuffer();
            String maxString = (str1.length() > str2.length()) ? str1 : str2;
            String minString = (str1.length() > str2.length()) ? str2 : str1;

            int len = minString.length();
            for (int i = 0; i < len; i++) {
                for (int x = 0, y = len - i; y <= len; x++, y++) {
                    String subString = minString.substring(x, y);
                    if (maxString.contains(subString)) {
                        sBuffer.append(subString + ",");
                    }
                }
//                System.out.println(sBuffer);
                if (sBuffer.length() != 0) {
                    break;
                }
            }
            String[] split = sBuffer.toString().replaceAll(",$", "").split("\\,");
            return split;
        }

        return null;
    }

    @Test
    public void testGetMaxSameString(){
        String str1 = "abcwerthello1yuiodefabcdef";
        String str2 = "cvhello1bnmabcdef";
        String[] maxSameStrings = getMaxSameString1(str1, str2);
        System.out.println(Arrays.toString(maxSameStrings));

    }

}

1.7 单元测试方法的使用

说明:运行程序代码都要在main方法进行测试时,代码太多不好区分,这时可以使用单元测试方法。

package com.atguigu.java2;

import java.util.Date;

import org.junit.Test;

/*
 * Java中的JUnit单元测试
 * 
 * 步骤:
 * 1.选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
 *                       (可选,这一步也可以放在第四步直接add添加依赖和导包)
 * 2.创建Java类,进行单元测试。
 *   此时的Java类要求:① 此类是public的  ②此类提供公共的无参的构造器
 * 3.此类中声明单元测试方法。
 *   此时的单元测试方法:方法的权限是public,没有返回值,没有形参
 * 
 * 4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
 * 
 * 5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。
 * 6.写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
 * 
 * 说明:
 * 1.如果执行结果没有任何异常:绿条
 * 2.如果执行结果出现异常:红条
 */
public class JUnitTest {
	
	int num = 10;
	
	@Test
	public void testEquals(){
		String s1 = "MM";
		String s2 = "MM";
		System.out.println(s1.equals(s2));
		//ClassCastException的异常
//		Object obj = new String("GG");
//		Date date = (Date)obj;
		
		System.out.println(num);
		show();
	}
	
	public void show(){
		num = 20;
		System.out.println("show()....");
	}
	
	@Test
	public void testToString(){
		String s2 = "MM";
		System.out.println(s2.toString());
	}
}

1.8 包装类

把基本类型进行包装,提供更加完善的功能。

1.8.1 与基本类型的对应关系(注意int和char)

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第12张图片

1.8.2 数字类型的包装类的父类:Number

源码摘抄: 是一个抽象的父类 public abstract class Number
继承关系
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第13张图片

常用的方法:(用法几乎一样)

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第14张图片

1.8.3 基本类型、包装类与String类间的转换说明

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第15张图片

1.8.4 基本类型 和 其对应的包装类 互转

package com.cn.ins;


import org.junit.Test;

/*
 * 包装类的使用:
 * 1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
 * 
 * 2.掌握的:基本数据类型、包装类、String三者之间的相互转换
 */
public class WrapperTest {	
	//基本数据类型--->包装类,方式一:调用包装类的构造器(已过时)
	@Test
	public void test1(){
		
		int num1 = 10;
//		System.out.println(num1.toString());
		Integer in1 = new Integer(num1);
		System.out.println(in1.toString());//10
		
		Integer in2 = new Integer("123");//传入的值可以直接是数字,也可以是String类型的数字
		System.out.println(in2.toString());//123
		
		//报异常
//		Integer in3 = new Integer("123abc");
//		System.out.println(in3.toString());
		
		Float f1 = new Float(12.3f);
		Float f2 = new Float("12.3");//类型转化为包装类,想要转化成功必须是存纯字
		System.out.println(f1);//12.3
		System.out.println(f2);//12.3
		
		Boolean b1 = new Boolean(true);
		Boolean b2 = new Boolean("TrUe");
		System.out.println(b2); //true
		Boolean b3 = new Boolean("true123");//类型转化为包装类,boolean类型有优化,想要转化成功不一定是存纯数字
		System.out.println(b3);//false
		
		
		Order order = new Order();
		System.out.println(order.isMale);//false
		System.out.println(order.isFemale);//null
	}

/*
 * 基本数据类型--->包装类,方式二:static  Integer.valueOf(5);静态方法通过类名调用。
 * 在Integer类中,Integer底层里有一个缓存数组:包含256个Integer缓存对象,范围是 -128到127。
 * 使用valueOf()时,第一次使用时创建对象如果在范围内会进行缓存,如果第二次创建对象的数据相同,
 * 会先访问缓存对象,而不新建。如果指定范围外的值,直接新建对象>
 * 
 * 好处:在范围内创建对象,第二次创建时是相同的数据会节省一部分空间. 
 * 
 * 注意:只有Integer 有这个效果,换成别的类型在这个范围内也没有,与new对象效率一样。
 *     为了方便创建对象统一格式所以我们最好都是用第二种方式。
 */
@Test
public void test11(){
	
	Integer i2 = Integer.valueOf(5) ;//valueOf():2.如果数据再-128~127之间是高效的,相同数据只会创建一次
	Integer i3 = Integer.valueOf(5) ;
	System.out.println(i2==i3);//true 用的是同一个对象
	Integer i4 = Integer.valueOf("5") ;
	System.out.println(i3==i4);//true,值可以是数字 也可以是字符串类型的数字
	
}


//包装类--->基本数据类型:调用包装类Xxx的xxxValue()
	@Test
	public void test2(){
		Integer in1 = new Integer(12);
		
		int i1 = in1.intValue();//注意对应的是前面转化为包装类的。
		System.out.println(i1 + 1);
		
		
		Float f1 = new Float(12.3);
		float f2 = f1.floatValue();
		System.out.println(f2 + 1);
	}

/*
	 * JDK 5.0 新特性:自动装箱 与自动拆箱
	 */
	@Test
	public void test3(){
//		int num1 = 10;
//		//基本数据类型-->包装类的对象
//		method(num1);
		
		//自动装箱:基本数据类型 --->包装类
		int num2 = 10;
		Integer in1 = num2;//自动装箱
		
		boolean b1 = true;
		Boolean b2 = b1;//自动装箱
		
		//自动拆箱:包装类--->基本数据类型
		System.out.println(in1.toString());
		
		int num3 = in1;//自动拆箱
		
	}
	
	public void method(Object obj){
		System.out.println(obj);
	}	
}

class Order{
	
	boolean isMale;//默认值变为false
	Boolean isFemale;//默认值变为null了
}


1.8.5 基本类型,以及对应的包装类 和 String类型互转

总结:

  1. 基本类型之间的互转
  2. 引用类型之间的互转
  3. 基本类型和基本类型对应的包装类型互转
  4. 基本类型,基本类型对应的包装类型 和String类型互转。
package com.cn.ins;


import org.junit.Test;

/*
 * 包装类的使用:
 * 1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
 * 
 * 2.掌握的:基本数据类型、包装类、String三者之间的相互转换
 */
public class WrapperTest {
	
	
	
	//基本数据类型、包装类--->String类型:调用String重载的valueOf(Xxx xxx)
	@Test
	public void test4(){
		
		int num1 = 10;
		//方式1:连接运算
		String str1 = num1 + "";
		//方式2:调用String的valueOf(Xxx xxx)
		float f1 = 12.3f;
		String str2 = String.valueOf(f1);//"12.3"
		
		Double d1 = new Double(12.4);
		String str3 = String.valueOf(d1);
		System.out.println(str2);
		System.out.println(str3);//"12.4"
	
		
	}
//包装类--->化为String类型:toString方法
	@Test
	public void test6(){
		
		Double d1 = new Double(12.4);
		String d2 = d1.toString();
		System.out.println(d2);
		
	}
	
//String类型 --->基本数据类型、包装类:调用包装类的parseXxx(String s)
	@Test
	public void test5(){
		String str1 = "123";
		//错误的情况:
//		int num1 = (int)str1; 基本类型和引用数据类型不能直接进行转
//		Integer in1 = (Integer)str1;引用数据类型的强转即向下造型要有子父类关系
		//可能会报NumberFormatException
		int num2 = Integer.parseInt(str1);
		System.out.println(num2 + 1);//必须为纯数字
		
		String str2 = "true1";//同样有优化功能
		boolean b1 = Boolean.parseBoolean(str2);
		System.out.println(b1);
	}	
	
}	

1.9 JDK8之前日期时间API

1.9.1 java.lang.System类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第16张图片

//1.System类中的currentTimeMillis()
    @Test
    public void test1(){
        long time = System.currentTimeMillis();
        //返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
        //称为时间戳
        System.out.println(time);
    }

1.9.2 java.util.Date类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第17张图片

 /*
    java.util.Date类
           |---java.sql.Date类

    1.两个构造器的使用
        >构造器一:Date():创建一个对应当前时间的Date对象
        >构造器二:创建指定毫秒数的Date对象
    2.两个方法的使用
        >toString():显示当前的年、月、日、时、分、秒
        >getTime():获取当前Date对象对应的毫秒数。(时间戳)

    3. java.sql.Date对应着数据库中的日期类型的变量,数据库交互时使用,平常用的还是 util.Date
        >如何实例化
        >如何将java.sql.Date对象转换为java.util.Date对象----多态
        >如何将java.util.Date对象转换为java.sql.Date对象
     */
    @Test
    public void test2(){
        //构造器一:Date():创建一个对应当前时间的Date对象
        Date date1 = new Date();
        System.out.println(date1.toString());//Sat Feb 16 16:35:31 GMT+08:00 2019

        System.out.println(date1.getTime());//1550306204104

        //构造器二:创建指定毫秒数的Date对象
        Date date2 = new Date(155030620410L);
        System.out.println(date2.toString());

        //创建java.sql.Date对象
        java.sql.Date date3 = new java.sql.Date(35235325345L);
        System.out.println(date3);//1971-02-13 只显示年 月 日不显示时 分 秒

        //如何将java.util.Date对象转换为java.sql.Date对象
        //情况一:多态形式 new的是子类sql.Date,子类赋值给父类,父类转子类可以用多态。
//        Date date4 = new java.sql.Date(2343243242323L);
//        java.sql.Date date5 = (java.sql.Date) date4;
        //情况二:new的直接是util.Date对象,创建的对象是父类没办法直接转子类。
        Date date6 = new Date();//可以获取毫秒数,sql又恰好有一个毫秒数的构造方法。
        java.sql.Date date7 = new java.sql.Date(date6.getTime());


    }

1.9.3 java.text.SimpleDateFormat类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第18张图片

/**
 * jdk 8之前的日期时间的API测试
 * 1. System类中currentTimeMillis();
 * 2. java.util.Date和子类java.sql.Date
 * 3. SimpleDateFormat
 * 4. Calendar
 *
 * @author shkstart
 * @create 2019 上午 11:35
 */
public class DateTimeTest {
    /*
    SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析

    1.两个操作:
    1.1 格式化:日期 --->字符串
    1.2 解析:格式化的逆过程,字符串 ---> 日期

    2.SimpleDateFormat的实例化

     */
    @Test
    public void testSimpleDateFormat() throws ParseException {
        //实例化SimpleDateFormat:使用默认的构造器
        SimpleDateFormat sdf = new SimpleDateFormat();

        //格式化:日期 --->字符串
        Date date = new Date();
        System.out.println(date);

        String format = sdf.format(date);
        System.out.println(format);

        //解析:格式化的逆过程,字符串 ---> 日期
        String str = "19-12-18 上午11:43";
        Date date1 = sdf.parse(str);
        System.out.println(date1);

        //*************按照指定的方式格式化和解析:调用带参的构造器*****************
//        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        //格式化
        String format1 = sdf1.format(date);
        System.out.println(format1);//2019-02-18 11:48:27
        //解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
        //否则,抛异常
        Date date2 = sdf1.parse("2020-02-18 11:48:27");
        System.out.println(date2);
    }
    /*
    练习一:字符串"2020-09-08"转换为java.sql.Date

    练习二:"三天打渔两天晒网"   1990-01-01  xxxx-xx-xx 打渔?晒网?

    举例:2020-09-08 ? 总天数

    总天数 % 5 == 1,2,3 : 打渔
    总天数 % 5 == 4,0 : 晒网

    总天数的计算?
    方式一:( date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24) + 1
    方式二:1990-01-01  --> 2019-12-31  +  2020-01-01 -->2020-09-08
     */
    @Test
    public void testExer() throws ParseException {
        String birth = "2020-09-08";

        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
        Date date = sdf1.parse(birth);
//        System.out.println(date);

        java.sql.Date birthDate = new java.sql.Date(date.getTime());
        System.out.println(birthDate);
    }

1.9.4 java.util.Calendar(日历)类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第19张图片

 /*
    Calendar日历类(抽象类)的使用

     */
    @Test
    public void testCalendar(){
        //1.实例化
        //方式一:创建其子类(GregorianCalendar)的对象
        //方式二:调用其静态方法getInstance()
        Calendar calendar = Calendar.getInstance();
//        System.out.println(calendar.getClass());

        //2.常用方法
        //get() 返回给定日历字段的值。
        int days = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(days);
        System.out.println(calendar.get(Calendar.DAY_OF_YEAR));

        //set()  将给定的日历字段设置为给定值。
        //calendar可变性
        calendar.set(Calendar.DAY_OF_MONTH,22);
        days = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(days);

        //add() 根据日历的规则,为给定的日历字段添加或减去指定的时间量。
        calendar.add(Calendar.DAY_OF_MONTH,-3);
        days = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(days);

        //getTime():日历类---> Date
        Date date = calendar.getTime();
        System.out.println(date);

        //setTime():Date ---> 日历类
        Date date1 = new Date();
        calendar.setTime(date1);
        days = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(days);

    }

1.10 JDK8中的新日期时间API

1.10.1 出现背景

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第20张图片

@Test
    public void testDate(){
        //偏移量 想要输出2020 9.8,就要年份减去1900  月份减去1,所以util.Date大部分方法都过时了。
        Date date1 = new Date(2020 - 1900,9 - 1,8);
        System.out.println(date1);//Tue Sep 08 00:00:00 GMT+08:00 2020
    }

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第21张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第22张图片

1.10.2 LocalDate、LocalTime、LocalDateTime

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第23张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第24张图片

/*
    LocalDate、LocalTime、LocalDateTime 的使用
    说明:
        1.LocalDateTime相较于LocalDate、LocalTime,使用频率要高
        2.类似于Calendar
     */
    @Test
    public void test1(){
        //now():获取当前的日期、时间、日期+时间
        LocalDate localDate = LocalDate.now();
        LocalTime localTime = LocalTime.now();
        LocalDateTime localDateTime = LocalDateTime.now();

        System.out.println(localDate);
        System.out.println(localTime);
        System.out.println(localDateTime);

        //of():设置指定的年、月、日、时、分、秒。没有偏移量
        LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
        System.out.println(localDateTime1);

		//2种实例化对象的方法相同
        //getXxx():获取相关的属性
        System.out.println(localDateTime.getDayOfMonth());
        System.out.println(localDateTime.getDayOfWeek());
        System.out.println(localDateTime.getMonth());
        System.out.println(localDateTime.getMonthValue());
        System.out.println(localDateTime.getMinute());//获取当前分钟

        //体现不可变性
        //withXxx():设置相关的属性
        LocalDate localDate1 = localDate.withDayOfMonth(22);
        System.out.println(localDate);//原来的日期没有变化
        System.out.println(localDate1);//变化的是新的日期


        LocalDateTime localDateTime2 = localDateTime.withHour(4);
        System.out.println(localDateTime);
        System.out.println(localDateTime2);

        //不可变性
        LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
        System.out.println(localDateTime);
        System.out.println(localDateTime3);

        LocalDateTime localDateTime4 = localDateTime.minusDays(6);
        System.out.println(localDateTime);
        System.out.println(localDateTime4);
    }

1.10.3 Instant

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第25张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第26张图片

 /*
    Instant的使用
    类似于 java.util.Date类

     */
    @Test
    public void test2(){
        //now():获取本初子午线对应的标准时间,和东八区相差8小时,通过偏移量来解决。
        Instant instant = Instant.now();
        System.out.println(instant);//2019-02-18T07:29:41.719Z

        //添加时间的偏移量,相当于加了8小时。
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
        System.out.println(offsetDateTime);//2019-02-18T15:32:50.611+08:00

        //toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数  ---> Date类的getTime()
        long milli = instant.toEpochMilli();
        System.out.println(milli);

        //ofEpochMilli():通过给定的毫秒数,获取Instant实例  -->Date(long millis)
        Instant instant1 = Instant.ofEpochMilli(1550475314878L);
        System.out.println(instant1);
    }

1.10.4 java.time.format.DateTimeFormatter 类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第27张图片

/*
    DateTimeFormatter:格式化或解析日期、时间
    类似于SimpleDateFormat

     */

    @Test
    public void test3(){
//        方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
        DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        //格式化:日期-->字符串
        LocalDateTime localDateTime = LocalDateTime.now();
        String str1 = formatter.format(localDateTime);
        System.out.println(localDateTime);
        System.out.println(str1);//2019-02-18T15:42:18.797

        //解析:字符串 -->日期
        TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
        System.out.println(parse);

//        方式二:
//        本地化相关的格式。如:ofLocalizedDateTime()
//        FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
        DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
        //格式化
        String str2 = formatter1.format(localDateTime);
        System.out.println(str2);//2019年2月18日 下午03时47分16秒


//      本地化相关的格式。如:ofLocalizedDate()
//      FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
        DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
        //格式化
        String str3 = formatter2.format(LocalDate.now());
        System.out.println(str3);//2019-2-18


//       重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
        DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
        //格式化
        String str4 = formatter3.format(LocalDateTime.now());
        System.out.println(str4);//2019-02-18 03:52:09

        //解析
        TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
        System.out.println(accessor);

    }

1.10.5 jdk8中的其它日期时间API

在这里插入图片描述

1.11 Java比较器

说明:用于对象之间比较大小,实际上是根据对象之间的属性进行比较。
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第28张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第29张图片

1.11.1 自然排序:java.lang.Comparable

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第30张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第31张图片
CompareTest :

package com.atguigu.java;

import org.junit.Test;

import java.util.Arrays;
import java.util.Comparator;

/**
 * 一、说明:Java中的对象,正常情况下,只能进行比较:==  或  != 。不能使用 > 或 < 的
 *          但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
 *          如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
 *
 * 二、Comparable接口与Comparator的使用的对比:
 *    Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
 *    Comparator接口属于临时性的比较。
 *
 *
 *
 *
 *thor shkstart
 * @create 2019 下午 4:41
 */
public class CompareTest {
    /*
    Comparable接口的使用举例:  自然排序
    1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
    2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列。(系统提供的类重写compareTo方法默认是从小到大排序的自然排序)
    3. 重写compareTo(obj)的规则:
        如果当前对象this大于形参对象obj,则返回正整数,
        如果当前对象this小于形参对象obj,则返回负整数,
        如果当前对象this等于形参对象obj,则返回零。
    4. 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。
       在compareTo(obj)方法中指明如何排序
     */
     
    //1.java封装好的类,底层已经重写过compareTo方法了,直接使用sort方法比较即可。
    // 数组有:Arrays.sort,集合有:Collentions.sort 都可以调用compareTo方法。只不过写在数组或集合的自定义类的对象需要手动重写方法。
    @Test
    public void test1(){
        String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};

        Arrays.sort(arr);

        System.out.println(Arrays.toString(arr));//[AA, CC, DD, GG, JJ, KK, MM]

    }
    //2.自定义类
    @Test
    public void test2(){
        Goods[] arr = new Goods[5];
        arr[0] = new Goods("lenovoMouse",34);
        arr[1] = new Goods("dellMouse",43);
        arr[2] = new Goods("xiaomiMouse",12);
        arr[3] = new Goods("huaweiMouse",65);
        arr[4] = new Goods("microsoftMouse",43);

        Arrays.sort(arr);//sort方法会自动调用重写后的compareTo方法。

        System.out.println(Arrays.toString(arr));
    }

   

Goods:

package com.atguigu.java;

/**
 * 商品类
 * @author shkstart
 * @create 2019 下午 4:52
 */
public class Goods implements  Comparable{

    private String name;
    private double price;

    public Goods() {
    }

    public Goods(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }

    //指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序
    @Override
    public int compareTo(Object o) {
//        System.out.println("**************");
        if(o instanceof Goods){
            Goods goods = (Goods)o;
            //方式一:
            if(this.price > goods.price){
                return 1; //不一定是1,只要是个正数即可。
            }else if(this.price < goods.price){
                return -1;
            }else{
//                return 0;//只比较价格
        //如果商品价格相同在比较商品的名称
                //this.name为String类型的对象,String类本身提供的方法这个方法 是从低到高,加个负数从高到底
                return -this.name.compareTo(goods.name);
            }
            //方式二:可以直接调用包装类提供的compare方法,底层重写了这些代码步骤
//           return Double.compare(this.price,goods.price);
        }
//        return 0;
        throw new RuntimeException("传入的数据类型不一致!");
    }
    //String提供了:int compareTo(String anotherString) 按字典顺序比较两个字符串。 
    //Double提供了:static int compare(double d1, double d2) 比较两个指定的 double 值。
    //            int compareTo(Double anotherDouble) 对两个 Double 对象所表示的数值进行比较。
 
 
}

1.11.2 定制排序:java.util.Comparator

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第32张图片
CompareTest :

package com.atguigu.java;

import org.junit.Test;

import java.util.Arrays;
import java.util.Comparator;

/**
 * 一、说明:Java中的对象,正常情况下,只能进行比较:==  或  != 。不能使用 > 或 < 的
 *          但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
 *          如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
 *
 * 二、Comparable接口与Comparator的使用的对比:
 *    Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
 *    Comparator接口属于临时性的比较。
 *
 *
 *
 *
 *thor shkstart
 * @create 2019 下午 4:41
 */
public class CompareTest {
   

    /*
    Comparator接口的使用:定制排序
    1.背景:
    当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码(比如jdk中的一些类,没有实现但自己又不能修改源码),
    或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,
    那么可以考虑使用 Comparator 的对象来排序
    2.比较规则:重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
    如果方法返回正整数,则表示o1大于o2;
    如果返回0,表示相等;
    返回负整数,表示o1小于o2。

     */
     //系统提供的类
    @Test
    public void test3(){
        String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
        Arrays.sort(arr,new Comparator(){//调用sort(xx,xx)2个形参的构造方法。这里简写为匿名对象的匿名子类。

            //按照字符串从大到小的顺序排列
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof String && o2 instanceof  String){//学了反泛型后不用在强转可以保证为String类型
                    String s1 = (String) o1;
                    String s2 = (String) o2;
                    return -s1.compareTo(s2);//compareTo从小到大,加上负数冲从大到小。
                }
//                return 0;
                throw new RuntimeException("输入的数据类型不一致");
            }
        });
        System.out.println(Arrays.toString(arr));
    }
//自定义的类
    @Test
    public void test4(){
        Goods[] arr = new Goods[6];
        arr[0] = new Goods("lenovoMouse",34);
        arr[1] = new Goods("dellMouse",43);
        arr[2] = new Goods("xiaomiMouse",12);
        arr[3] = new Goods("huaweiMouse",65);
        arr[4] = new Goods("huaweiMouse",224);
        arr[5] = new Goods("microsoftMouse",43);

        Arrays.sort(arr, new Comparator() {
            //指明商品比较大小的方式:按照产品名称从低到高排序,再按照价格从高到低排序
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof Goods && o2 instanceof Goods){
                    Goods g1 = (Goods)o1;
                    Goods g2 = (Goods)o2;
                    if(g1.getName().equals(g2.getName())){//产品名称相同 在按照价格排序
                        return -Double.compare(g1.getPrice(),g2.getPrice());
                    }else{
                        return g1.getName().compareTo(g2.getName());
                    }
                }
                throw new RuntimeException("输入的数据类型不一致");
            }
        });

        System.out.println(Arrays.toString(arr));
    }

}

Goods:

package com.atguigu.java;

/**
 * 商品类
 * @author shkstart
 * @create 2019 下午 4:52
 */
public class Goods implements  Comparable{

    private String name;
    private double price;

    public Goods() {
    }

    public Goods(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
	/*问题:这个方式是自然排序时需要实现Comparable接口,重写compareTo(obj)的方法,那么我们用定制排序
	可以不写这个方法吗???
	答:定制排序出现的场景之一是:自然排序重写的写的方法不满足,所以需要定制排序重新指定规则,此时是
	   为了修改自然排序而改变,那么这个自定义类的方法就是为了自然排序,你实现接口不重写方法就不是自然排 
	   序了,当然不能省略。*/
    //指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序
    @Override
    public int compareTo(Object o) {
//        System.out.println("**************");
        if(o instanceof Goods){
            Goods goods = (Goods)o;
            //方式一:是自己写的比较规则
            if(this.price > goods.price){
                return 1;
            }else if(this.price < goods.price){
                return -1;
            }else{
//                return 0;
                return -this.name.compareTo(goods.name);
            }
            //方式二:包装类的compare方法底层写好了比较规则,可以自己不用在写了直接调用方法即可。
//           return Double.compare(this.price,goods.price);
        }
//        return 0;
        throw new RuntimeException("传入的数据类型不一致!");
    }
}

1.12 其它常用类

1.12.1 System类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第33张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第34张图片

package com.atguigu.java;

import org.junit.Test;

import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * 其他常用类的使用
 * 1.System
 * 2.Math
 * 3.BigInteger 和 BigDecimal
 *
 * @author shkstart
 * @create 2019 下午 6:23
 */
public class OtherClassTest {

    @Test
    public void test1() {
        String javaVersion = System.getProperty("java.version");
        System.out.println("java的version:" + javaVersion);

        String javaHome = System.getProperty("java.home");
        System.out.println("java的home:" + javaHome);

        String osName = System.getProperty("os.name");
        System.out.println("os的name:" + osName);

        String osVersion = System.getProperty("os.version");
        System.out.println("os的version:" + osVersion);

        String userName = System.getProperty("user.name");
        System.out.println("user的name:" + userName);

        String userHome = System.getProperty("user.home");
        System.out.println("user的home:" + userHome);

        String userDir = System.getProperty("user.dir");
        System.out.println("user的dir:" + userDir);

    }

   

1.12.2 Math类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第35张图片

1.12.3 BigInteger与BigDecimal

1).BigInteger: 常用来解决超大的整数运算。2者用法一样。

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第36张图片

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第37张图片
2).BigDecimal: 常用来解决精确的浮点数运算。可以把以前的±*/的运算,优化成两个对象间的运算 (decimal: 小数)

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第38张图片
创建对象:

BigDecimal(double val) -- 有坑,别用 java规定了定义浮点型运算不准确
BigDecimal(String val) -- 建议使用这个

常用方法:

BigDecimal  add(BigDecimal bd): 做加法运算
BigDecimal  substract(BigDecimal bd) : 做减法运算
BigDecimal  multiply(BigDecimal bd) : 做乘法运算
BigDecimal  divide(BigDecimal bd) : 做除法运算
divide(BigDecimal bd,保留位数,舍入方式):除不尽时使用
setScale(保留位数,舍入方式):同上(它是bd1 输入的数, 保留几位  舍入方式)
pow(int n):求数据的几次幂

测试1:

接收用户输入的两个数字,做运算。
package cn.tedu.bigdecimal;
 
import java.math.BigDecimal;
import java.util.Scanner;
 
//浮点数的解决方案
public class Test1_BigDecimal {
       public static void main(String[] args) {
//           method();//用基本类型做运算
              method2();//BigDecimal
       }
      
       private static void method2() {
              //1,接受键盘输入的两个小数
              double a = new Scanner(System.in).nextDouble();
              double b = new Scanner(System.in).nextDouble();
             
              //!!!创建BigDecimal对象,建议使用String参数的构造方法!!!把它变为字符串的形式输出的才准确
              BigDecimal bd1 = new BigDecimal(a+"");
              BigDecimal bd2 = new BigDecimal(b+"");
             
              //2,做运算
              BigDecimal bd3 ;//保存计算结果
             
              bd3 = bd1.add(bd2);//加法运算
              System.out.println(bd3);
             
              bd3 = bd1.subtract(bd2);//减法运算
              System.out.println(bd3);
             
              bd3 = bd1.multiply(bd2);//乘法运算
              System.out.println(bd3);
             
              //除法运算,如果除不尽,会抛出异常java.lang.ArithmeticException
//           bd3 = bd1.divide(bd2);//除法运算
            
              //3是要保留3位小数,BigDecimal.ROUND_HALF_UP舍入方式是四舍五入
              bd3 = bd1.divide(bd2,3,BigDecimal.ROUND_HALF_UP);
              System.out.println(bd3);
       }
      
      
      
       public static void method() {
              //1,接受键盘输入的两个小数
              double a = new Scanner(System.in).nextDouble();
              double b = new Scanner(System.in).nextDouble();
             
              //2,普通方法做运算 ,可能都不精确!!88888888888
              System.out.println(a+b);
              System.out.println(a-b);
              System.out.println(a*b);
              System.out.println(a/b);
       }
      
}

测试2:

 @Test
    public void test2() {
        BigInteger bi = new BigInteger("1243324112234324324325235245346567657653");
        BigDecimal bd = new BigDecimal("12435.351");
        BigDecimal bd2 = new BigDecimal("11");
        System.out.println(bi);
//         System.out.println(bd.divide(bd2));报错,除尽还好,如果除不尽有没有指定按照什么规则保留位数就报错。
        System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));//除法运算,不指明按照默认保留位数显示,四舍五入
        System.out.println(bd.divide(bd2, 25, BigDecimal.ROUND_HALF_UP));//除法运算,保留15位,四舍五入

    }

1.13 拓展

1.13.1 进制

概念
进制也就是进位计数制,是人为定义的带进位的计数方法,类似于统计“正”字。
对于任何一种进制—X进制,就表示每一位置上的数运算时都是逢X进一位。
十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推。
通常情况下,1byte=8个二进制位
所以表示一个数字用二进制来表示的话就可以这样表示:0000 0000
把这8个位进行组合,每三位组合就形成了八进制,每四位组合就形成了十六进制。
特点
二进制:0和1,逢二进一,以0b开始
八进制:0-7,逢八进一,以0开始
十进制:0-9,逢十进一
16进制:0-9,abcdef,逢16进一,以0x开始

进制的转化:
十进制转二进制:不断除以2商0为止,取余,倒着写。
把十进制11转成2进制:1011。
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第39张图片

二进制转十进制:从低位次,每位乘以2的位次次幂 再求和。
计算二进制数据:0000 1101对应的十进制
计算二进制数据:0110 1110对应的十进制
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第40张图片

二进制转八进制:从低位次开始,每三位为一组,产生一个八进制数字,最高位不足补零。
计算二进制数据110 0111对应的八进制
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第41张图片

八进制转二进制:把一个数字转为3个数字,不足三位的,最高位补零。
计算八进制数据:023 0653对应的二进制数据
在这里插入图片描述

二进制转十六进制:四个一组,转为1个数字,以0x开始
略。。。
十六进制转二进制:一个数字变成4个数字
略。。。

1.13.2 关于各个length的区别

String有length方法。数组没有length方法,有length属性。集合没有length方法,计算集合长度用的是size方法。

2 IO流–01

通常文件的传输需要3个对象:容器(File类)–节点流—处理流。

容器:数据的读入,写出都需要一个端点,数据从哪读入 数据从哪写出。在java中万事万物皆对象,File类就是这个对象。
节点流:直接作用在File类上的流叫做节点流。
处理流:直接或间接套在File类 上的流。

2.1 IO简介

说明一:

  1. 数据的读写抽象成数据,在管道中流动。 day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第42张图片
  2. I----->input:输入流
  3. O—>output:输出流
  4. 流只能单方向流动
  5. 数据只能从头到尾顺序的读写一次
  6. 流的关闭操作:对于物理连接如:数据库连接,输入输出流,Socket连接无能为力,这些资源需要手动关闭。
  7. IO流的所有Api,都来自于java.io包中。
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第43张图片

说明二:io流的输入输出是站在内存(程序)的角度上来分析:

  1. 持久化的数据 传输到 内存中叫做输入流。
  2. 内存中的数据 传输到 持久化的数据叫作输出流。
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第44张图片

2.2 IO分类

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第45张图片

  1. 按数据流的流向不同分为:输入流,输出流

  2. 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
    字节流:适用于图片,视频等。字节流的数据传输是0101这样的二进制数据。
    字符流:适用于文件传输,字符流的数据传输是一个个char类型的字符,就可以知道具体传的是什么了。16bit(位)=2byte(字节)=1个char类型的字符。

  3. 按流的角色的不同分为:节点流,处理流
    节点流/低级流:直接作用在文件上的流,叫做节点流。
    处理流/高级流:在节点流的基础上直接包上或间接包上一层流,这个外面的流叫做处理流,可以包多个。

2.3 继承结构

说明:io流的所有类,都来自4个抽象父类。
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第46张图片
说明:其中深色的单元格代表比较常用的流。可以根据后缀进行区分

字节流:针对二进制文件 更广泛   用来操作电脑里的所有格式的数据。
|---InputStream   //字节输入流的抽象父类
    |---FileInputStream   //文件输入流 子类  (低级流/节点流)
    |---FilterInputStream //略,不常用
        |---BufferedInputStream //缓冲字节输入流 子类   (高级流/处理流)
        |---DataInputStream //数据输入流 子类(高级流)
    |---ObjectInputStream    //反序列化   子类(创建对象不用多态) (高级流)

|---OutputStream  //字节输出流抽象父类
    |---FileOutputStream //文件输出流 子类 (低级流)
    |---FilterOutputStream //略,不常用
        |---BufferedOutputStream //缓冲字节输出流 子类 (高级流)
        |---PrintStream //字节打印输出流 子类(高级流)
        |---DataOutputStream //数据输出流 子类(高级流)

    |---ObjectOutputStream //序列化   子类(创建对象不用多态) (高级流)
    



字符流:针对文本文件。读写容易发生乱码现象,在读写时最好指定编码集为utf-8, 用来处理文本格式的数据txt。
|---Reader  //字符输入流的抽象父类
   |---BufferedReader //缓冲字符输入流 子类 (高级流)
   |---InputStreamReader  //输入的转换流 (可以用多态) (高级流)
       |---FileReader  //字符输入流 子类(低级流/节点流)
  
   
|---Writer //字符输出流抽象父类
    |---BufferedWriter  //缓冲字符输出流 子类 (高级流)
    |---OutputStreamWriter   //输出的转换流(可以用多态)(高级流)
        |---FileWriter  //字符输出流 子类(低级流/节点流)
    |---PrintWriter  //字符打印输出流 (高级流)




day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第47张图片

2.4 File类的使用

2.4.1 概述

  1. File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
  2. File类声明在java.io包下
  3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,
    并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
  4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".

2.4.2 构造方法实例化对象

构造方法:
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第48张图片
相对路径注意事项:
1.普通java项目:在idea中,写在单元测试方法中的代码相对路径指的是 相对于当前Module下(一个个项目下)。如果是在main方法中则相对于当前project下(即所谓的工作空间)。
2.普通java项目:在eclipse中,认为每个单独的项目都是一个个的工程,所以单元测试和main方法都是相对于当前项目下的相对路径。

路径分隔符注意事项:
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第49张图片

2.4.3 构造方法的测试

package com.file;


import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.Date;


public class FileTest {
    /*
     路径分隔符:路径中的每级目录之间用一个路径分隔符隔开,和系统有关。
     windows和DOS系统:\,java中为了避免转义字符,通常写为\\.
     UNIX和URL:/,一般在windows中这样写也支持。
     为了解决在不同操作系统中路径分隔符的麻烦,File类提供了一个常量,public static final String separator。根据操作系统,动态的提供分隔符。

     */
    @Test
    public void test1(){
        //构造器1
        File file1 = new File("hello.txt");//相对路径:相对于当前module,E:\idea-workspace\IO
        File file2 =  new File("D:\\workspace_idea1\\JavaSenior\\day08\\he.txt");//绝对路径:因为java中,\单斜杠代表转义字符,所以使用\\双斜杠表示路径分隔符。

        System.out.println(file1);//hello.txt
        System.out.println(file2);//D:\workspace_idea1\JavaSenior\day08\he.txt

        //构造器2:
        File file3 = new File("D:\\workspace_idea1","JavaSenior");
        System.out.println(file3);//D:\workspace_idea1\JavaSenior

        //构造器3:
        File file4 = new File(file3,"hi.txt");
        System.out.println(file4);//D:\workspace_idea1\JavaSenior\hi.txt

        //总结:这三种构造器创建对象,此时调用toString()方法,输出的是内存层面的对象路径,而在磁盘中没有对应的文件或文件目录。
    }
}

2.4.4 常用方法测试

package com.file;


import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.Date;


public class FileTest {

    /*
    一:File类的获取功能:

    1.public String getAbsolutePath():获取绝对路径
    2.public String getPath() :获取路径
    3.public String getName() :获取名称
    4.public String getParent():获取上层文件目录路径。若无,返回null
    5.public long length() :获取文件长度(即:以字节为单位,字符数。如:aa 的长度为2)。不能获取目录的长度。
    6.public long lastModified() :获取最后一次的修改时间,毫秒值

    如下的两个方法适用于文件目录:
    7.public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
    8.public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
     */
    @Test
    public void test2(){
        File file1 = new File("hello.txt");//相对路径,值接在项目时右键---new file
        File file2 = new File("d:\\io\\hi.txt");//绝对路径
        //此时方法的调用还只是内存方面的,不涉及磁盘路径。
        System.out.println(file1.getAbsolutePath());//E:\idea-workspace\IO\hello.txt
        System.out.println(file1.getPath());//hello.txt
        System.out.println(file1.getName());//hello.txt
        System.out.println(file1.getParent());//null,文件存在仍为null 它是根据相对路径找的。
        System.out.println(file1.length());//0,因为文件不存在,如果存在可以获取到值。选中项目--右键new file
        System.out.println(new Date(file1.lastModified()));//Thu Jan 01 08:00:00 CST 1970

        System.out.println();

        //此时方法的调用还只是内存方面的,不涉及磁盘路径。
        System.out.println(file2.getAbsolutePath());//d:\io\hi.txt
        System.out.println(file2.getPath());//d:\io\hi.txt
        System.out.println(file2.getName());//hi.txt
        System.out.println(file2.getParent());//d:\io
        System.out.println(file2.length());//0
        System.out.println(file2.lastModified());//0
    }
    //测试文件目录:7,8
    @Test
    public void test3(){
        //磁盘路径必须真实存在才能获取。
        File file = new File("D:\\workspace_idea1\\JavaSenior");

        String[] list = file.list();//只获取到名称数组
        for(String s : list){
            System.out.println(s);//a.txt,aa,b.txt,bb
        }

        System.out.println();

        File[] files = file.listFiles();//获取的是完整路径数组
        for(File f : files){
            System.out.println(f);//D:\workspace_idea1\JavaSenior\a.txt,D:\workspace_idea1\JavaSenior\b.txt
                                  //D:\workspace_idea1\JavaSenior\aa,   D:\workspace_idea1\JavaSenior\bb
        }

    }

    /*
    *二:File类的重命名功能:
    * 9. public boolean renameTo(File dest):把文件重命名为指定的文件路径
    *    比如:file1.renameTo(file2)为例:
    *    要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在(路径存在,文件不存在)。
    */
    @Test
    public void test4(){
        File file1 = new File("hello.txt");//真实存在
        File file2 = new File("D:\\io\\hi.txt");//不能存在

        boolean renameTo = file1.renameTo(file2);
        System.out.println(renameTo);//true hello.txt消失  hi.txt自动生成,相当于把hello.txt文件剪贴到D:\io路径下并改名为hi.txt
                                     //再次执行因为file1已经到file2中了,所以false

    }
    /*三:File类的判断功能:
     10.public boolean isDirectory():判断是否是文件目录
     11.public boolean isFile() :判断是否是文件
     12.public boolean exists() :判断是否存在
     13.public boolean canRead() :判断是否可读
     14.public boolean canWrite() :判断是否可写
     15.public boolean isHidden() :判断是否隐藏

     */
    @Test
    public void test5(){
        //相对路径
        File file1 = new File("hello.txt");//文件真实存在
        file1 = new File("hello1.txt");//文件不存在

        System.out.println(file1.isDirectory());//false  false
        System.out.println(file1.isFile());//true  false
        System.out.println(file1.exists());//true  false
        System.out.println(file1.canRead());//true  false
        System.out.println(file1.canWrite());//true  false
        System.out.println(file1.isHidden());//false  false

        System.out.println();
        //绝对路径
        File file2 = new File("d:\\io");//文件目录存在
        //file2 = new File("d:\\io1");//文件目录不存在
        System.out.println(file2.isDirectory());//true  false
        System.out.println(file2.isFile());//false  false
        System.out.println(file2.exists());//true  false
        System.out.println(file2.canRead());//true  false
        System.out.println(file2.canWrite());//true  false
        System.out.println(file2.isHidden());//false  false

    }
    /*
    四:创建硬盘中对应的文件或文件目录:真正的在硬盘中创建
    16.public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
    17.public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
    18.public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建

    五:删除磁盘中的文件或文件目录:
    19.public boolean delete():删除文件或者文件夹
    删除注意事项:Java中的删除不走回收站。

     */
    @Test
    public void test6() throws IOException {
        File file1 = new File("hi.txt");
        if(!file1.exists()){
            //文件的创建
            file1.createNewFile();
            System.out.println("创建成功");
        }else{//文件存在
            file1.delete();
            System.out.println("删除成功");
        }


    }
    @Test
    public void test7(){
        //文件目录的创建 io 存在 io1 io3不存在,如果上层目录不包含则创建失败。
        File file1 = new File("d:\\io\\io1\\io3");

        boolean mkdir = file1.mkdir();
        if(mkdir){
            System.out.println("创建成功1");
        }
        //io 存在 io1 io4不存在,上层目录不包含,会帮你自动创建上层目录,创建成功。
        File file2 = new File("d:\\io\\io1\\io4");

        boolean mkdir1 = file2.mkdirs();
        if(mkdir1){
            System.out.println("创建成功2");
        }
        //要想删除成功,io4文件目录下不能有子目录或文件
        File file3 = new File("D:\\io\\io1\\io4");
        file3 = new File("D:\\io\\io1");
        System.out.println(file3.delete());
    }
}

2.4.5 练习1:递归求文件总大小

递归:不断的调用方法本身。
需求:统计文件大小
1、把指定目录封装成File对象
2、把文件夹列表列出来
3、判断,如果是文件,直接把f.length()相加
4、判断,如果是文件夹,递归调用方法本身的业务逻辑,循环再次判断,如果是文件,则相加,如果又是文件夹,继续列表,继续判断,如果是文件相加…
注意:文件才有大小,目录没有大小之分。

package com.file;

import java.io.File;

public class Test2_File2 {
    public static void main(String[] args) {
//		1、把指定目录封装成File对象
        File file = new File("D:\\teach\\a");
        int size =count(file);//为了使用File路径,要把它传参到那个方法
        System.out.println(size);
    }

    private static int count(File file) {//file是File类型的
//		2、把文件夹列表列出来
        File[] files = file.listFiles();//获取指定目录下的所有文件或者文件目录的File数组

        //2.1 遍历数组里的每个资源
        int sum = 0;//记录文件的大小
        for (int i = 0; i < files.length; i++) {
//		3、判断,如果是文件,直接把f.length()相加
// files[i]表示每次遍历到的资源
            if(files[i].isFile()) {
                sum += files[i].length();//求文件的和
            }else if(files[i].isDirectory()){
//		4、判断,如果是文件夹,继续列表,继续判断,如果是文件相加,如果又是文件夹,继续列表,继续判断,如果是文件相加......
//		5、如果是文件夹,递归调用方法本身的业务逻辑
                sum += count(files[i]);//把当前遍历到的文件夹继续循环判断求和
            }
        }

        return sum ;
    }


}

2.4.6 练习2:递归删除文件夹

递归:不断的调用方法本身。
需求:递归删除文件夹
1、把指定目录封装成File对象
2、把文件夹列表列出来
3、判断,如果是文件,直接删除
4、判断,如果是文件夹,递归调用方法本身的业务逻辑,循环再次判断,如果是文件,则删除,如果是文件夹继续列表,继续判断,如果是文件删除…
注意:要想删除成功,文件目录下不能有子目录或文件。Java中的删除不走回收站。

package com.file;

import java.io.File;
import java.util.Scanner;

public class Test1_Size {
    public static void main(String[] args) {
        System.out.println("请输入想要删除的文件目录写的路径:");
        //1,接收用户输入的文件夹路径
        String path = new Scanner(System.in).nextLine();

        File dir = new File(path);

        //2,调用指定方法求大小
        //		long max = size(dir);
        //		System.out.println("文件夹总大小是:"+max);

        //i,调用指定方法删除文件夹
        del(dir);
        System.out.println("删除成功");
    }

    //ii,新建删除方法
    public static void del(File dir) {
        //1,列出所有资源
        File[] a = dir.listFiles();

        //遍历数组,拿到每次资源
        for (int i = 0; i < a.length; i++) {
            //2,判断资源是文件还是文件夹
            if (a[i].isFile()) {//如果是文件直接删除
                a[i].delete();
            } else if (a[i].isDirectory()) {//如果是文件夹
                del(a[i]);//重复的 列资源,重复删文件,重复的判断是文件夹继续列资源,删除........
            }
        }
        //TODO  把文件夹里的文件都删掉了,怎么删除空的文件夹呢??
        dir.delete();//文件夹没有文件了,出了for循环之后可以删除空目录了。
    }
}

2.5 字节流读取

说明:

  1. 字节流是由字节组成的,字符流是由字符组成的. Java里字符由两个字节组成.字节流是最基本的,所有的InputStream和OutputStream的子类都是,主要用在处理二进制数据

  2. 流式传输主要指将整个音频和视频及三维媒体等多媒体文件经过特定的压缩方式解析成一个个压缩包,由视频服务器向用户计算机顺序或实时传送。在采用流式传输方式的系统中,用户不必像采用下载方式那样等到整个文件全部下载完毕,而是只需经过几秒或几十秒的启动延时即可在用户的计算机上利用解压设备对压缩的A/V、3D等多媒体文件解压后进行播放和观看。此时多媒体文件的剩余部分将在后台的服务器内继续下载。
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第50张图片

2.5.1 InputStream抽象父类

说明:此抽象类表示所有字节输入流的父类,不学它的构造方法创建对象,只学它的共性方法。

常用方法:

abstract  int read() 
          从输入流中读取数据的下一个字节。 
int read(byte[] b)    注意是byte
          从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 
int read(byte[] b, int off, int len) 
          将输入流中最多 len 个数据字节读入 byte 数组。 
void close()   //使用流后不管是读入还是写出都需要关闭资源
          关闭此输入流并释放与该流关联的所有系统资源。
......

2.5.2 FileInputStream子类(节点流:文件字节流)

说明:直接插在文件上,直接读取文件数据。

创建对象:

1. FileInputStream(File file) 
          通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。 
2. FileInputStream(FileDescriptor fdObj) 
          通过使用文件描述符 fdObj 创建一个 FileInputStream,该文件描述符表示到文件系统中某个实际文件的现有连接。 
3. FileInputStream(String name) 
          通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。 

测试:

package com.io_file;

import org.junit.Test;

import java.io.*;

/**
 * 测试FileInputStream和FileOutputStream的使用
 *
 * 结论:
 * 1. 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理  (因为字节流处理中文是乱码)
 *     注意这里的乱码指的是 数据到内存层面进行查看有乱码,如果是单纯的复制 硬盘-->硬盘 那么文本文件仍然可以使用字节流处理。
 * 2. 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),只能使用字节流处理   (因为这些非文本文件的基本单位比较小,字符的单位比较大处理不了)
 *
 *
 *
 * @author shkstart
 * @create 2019 下午 2:13
 */
public class FileInputOutputStreamTest {
    //把磁盘中的数据读入到内存中,并输出到控制台。
    //使用字节流FileInputStream处理文本文件,可能出现乱码(在各种编码中,一个字节可以存下一个英文字符,而存不一个中文存字符)。
    @Test
    public void testFileInputStream() {
        FileInputStream fis = null;
        try {
            //1. 造文件
            File file = new File("hello.txt");

            //2.造流
            fis = new FileInputStream(file);

            //3.读数据
            byte[] buffer = new byte[1024];//注意使用的是字节数组 每次读取直接的大小 这个值不能太大也不能太小,1024复制速度最快
            int len;//记录每次读取的字节的个数
            while((len = fis.read(buffer)) != -1){

                String str = new String(buffer,0,len);//字节数组转化为String类型
                System.out.print(str);

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                //4.关闭资源
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

   
}


2.5.3 BufferedInputStream子类(处理流:缓冲字节流)

说明:在创建 BufferedInputStream 时,会创建一个内部缓冲区数组(默认8M=8*1024大小)。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节,缓存流的作用都是提高读写效率的

创建对象:

1. BufferedInputStream(InputStream in) 
          创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 
          
2. BufferedInputStream(InputStream in, int size) 
          创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 

测试:

package com.bf_file;

import org.junit.Test;

import java.io.*;

/**
 * 处理流之一:缓冲流的使用
 *
 * 1.缓冲流:
 * BufferedInputStream
 * BufferedOutputStream
 * BufferedReader
 * BufferedWriter
 *
 * 2.作用:提供流的读取、写入的速度
 *   提高读写速度的原因:内部提供了一个缓冲区 (把一定量的数据读入到缓冲区后 一次性写出 写入)
 *
 * 3. 处理流,就是“套接”在已有的流的基础上,处理流不能直接作用在文件上。
 *
 * @author shkstart
 * @create 2019 下午 2:44
 */
public class BufferedTest {

    /*
    实现非文本文件的复制,单纯的复制 字节流可以用于任何类型的文件数据
     */
    @Test
    public void BufferedStreamTest() throws FileNotFoundException {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.造文件
            File srcFile = new File("爱情与友情.jpg");
            File destFile = new File("爱情与友情3.jpg");
            //2.造流
            //2.1 造节点流
            FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.复制的细节:读取、写入
            byte[] buffer = new byte[1024];
            int len;
            while((len = bis.read(buffer)) != -1){
                bos.write(buffer,0,len);

//                bos.flush();//刷新缓冲区:如果不调用,默认数据存满缓冲区后读入 写出。如果在这里显示调用,可能数据还没存满缓冲区后就开始读入 写出。

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            //要求:先关闭外层的流,再关闭内层的流  同级别的流顺序无所谓
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if(bis != null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();
        }



    }

    //工具Api:实现文件复制的方法 单纯的复制 字节流可以用于任何类型的文件数据
    public void copyFileWithBuffered(String srcPath,String destPath){
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.造文件
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);
            //2.造流
            //2.1 造节点流
            FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.复制的细节:读取、写入
            byte[] buffer = new byte[1024];
            int len;
            while((len = bis.read(buffer)) != -1){
                bos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            //要求:先关闭外层的流,再关闭内层的流
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if(bis != null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();
        }
    }

    @Test
    public void testCopyFileWithBuffered(){
        long start = System.currentTimeMillis();

        String srcPath = "D:\\aa\\bb\\01-尚硅谷-Java语言基础.avi";
        String destPath = "D:\\aa\\bb\\02-尚硅谷-Java语言基础.avi";


        copyFileWithBuffered(srcPath,destPath);


        long end = System.currentTimeMillis();

        System.out.println("复制操作花费的时间为:" + (end - start));//618 - 176
    }

}

2.5.4 ObjectInputStream子类(处理流:对象输入流 / 反序列化)

作用:用于存储和读取 基本数据类型数据 或 对象(主要用于对象)的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

名词说明:

  1. 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制 (把对象的信息,按固定格式转成一串字节值输出并持久保存到磁盘中)
  2. 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制 (读取磁盘中序列化数据,重新恢复对象)
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第51张图片

对象序列化机制:允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

特点:

  1. 需要序列化的文件必须实现Serializable接口以启用其序列化功能。
  2. 不需要序列化的数据可以被修饰为static的,由于static属于类,不随对象被序列化输出。
  3. 不需要序列化的数据也可以被修饰为transient临时的,只在程序运行期间,在内存中存在不会被序列化持久保存。
  4. 在反序列化时,如果和序列化的版本号不一致时,无法完成反序列化。
  5. 每个被序列化的文件都有一个唯一id,如果没有添加编译器会根据类的定义信息计算产生一个版本号。
  6. 常用于服务器之间的数据传输,序列化成文件,反序列化读取数据。
  7. 常用于使用套接字流在主机之间传递对象。

测试:
ObjectInputOutputStreamTest类:

package com.io_file;

import org.junit.Test;

import java.io.*;

/**
 * 对象流的使用
 * 1.ObjectInputStream 和 ObjectOutputStream
 * 2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
 *
 * 3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
 *
 * 4.序列化机制:
 * 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种
 * 二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
 * 当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

 *
 * @author shkstart
 * @create 2019 上午 10:27
 */
public class ObjectInputOutputStreamTest {

    /*
    序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
    使用ObjectOutputStream实现
    序列化的文件保存在磁盘中 直接打开有乱码读不懂,想要读懂 可以反序列化到内存中 输出到控制台查看。
     */
    @Test
    public void testObjectOutputStream(){
        ObjectOutputStream oos = null;

        try {
            //1.
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            //2.
            oos.writeObject(new String("我爱北京天安门"));//系统提供的对象 String
            oos.flush();//刷新操作

            oos.writeObject(new Person("王铭",23));
            oos.flush();

            oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    /*
    反序列化:将磁盘文件中的对象还原为内存中的一个java对象
    使用ObjectInputStream来实现
     */
    @Test
    public void testObjectInputStream(){
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));

            Object obj = ois.readObject();//读写顺序保持一致
            String str = (String) obj;

            Person p = (Person) ois.readObject();
            Person p1 = (Person) ois.readObject();

            System.out.println(str);
            System.out.println(p);
            System.out.println(p1);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }



    }

}


Person类:

package com.io_file;

import java.io.Serializable;

/**
 * Person需要满足如下的要求,方可序列化
 * 1.需要实现接口:Serializable    (String为系统提供的类 底层源码实现了序列化接口,这里自定义类需要手动实现序列化接口。没有实现序列化接口 在序列化对象时回报异常)
 *              Serializable是标识接口:里面没有方法和属性,表示凡是实现这个接口的类都可以序列化。
 *
 * 2.当前类提供一个全局常量:serialVersionUID
 *              问题1:为什么要提供这个全局变量序列化接口???
 *              答:因为如果有很多文件序列化磁盘同一个数据源中,在读取的时候根据序列化id可以清楚地还原为对应的对象。
 *              问题2:显示指定序列化id和默认生成的序列化id,在改变成员变量时 2则的区别???
 *              答:2.1显示序列化: 假如类Person显示指定序列化id为100 然后进行序列化生成文件为aa.txt到磁盘中,此时修改Person类的成员变量 因为是示显序列化,
 *                              所以即便修改变量值 序列化id也不会发生变化,此时反序列化根据序列化id为100 读取aa.txt文件中的数据仍能还原为没有修改成员变量
 *                              之前的Person对象。
 *                 2.2自动生成的序列化:假如类Person自动生成的序列化id为100 然后进行序列化生成文件为aa.txt到磁盘中,此时修改Person类的成员变量 因为是自动生成
 *                                  的序列化id,所以成员变量一旦修改序列化id会跟着改变假设为150,此时反序列化的id为150不等于100 还原失败报异常。
 *
 *
 *              如果类没有显示定义这个序列化id,它的值是Java运行时环境根据类的内部细节自动生成的,若类的实例变量做了修改,serialVersionUID 可能发生变化,
 *              这样很容易出现问题,故建议显式声明。
 *
 *
 * 3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性
 *   也必须是可序列化的。(默认情况下,基本数据类型可序列化)
 *
 *
 * 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
 *      在后面进行传输时,一般也不会直接传person 而是转化为json在进行传输,而json本质上是特殊格式的字符串。
 *
 * @author shkstart
 * @create 2019 上午 10:38
 */
public class Person implements Serializable{

    public static final long serialVersionUID = 475463534532L;

    private String name;
    private int age;
    private int id;
    private Account acct;

    public Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public Person(String name, int age, int id, Account acct) {
        this.name = name;
        this.age = age;
        this.id = id;
        this.acct = acct;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                ", acct=" + acct +
                '}';
    }

    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 Person(String name, int age) {

        this.name = name;
        this.age = age;
    }

    public Person() {

    }
}

class Account implements Serializable{
    public static final long serialVersionUID = 4754534532L;
    private double balance;

    @Override
    public String toString() {
        return "Account{" +
                "balance=" + balance +
                '}';
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public Account(double balance) {

        this.balance = balance;
    }
}


day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第52张图片

2.6 字节流写出

2.6.1 OutputStream抽象父类

说明:此抽象类是表示输出字节流的所有类的超类,不研究构造方法。

常用方法:

void close() 
          关闭此输出流并释放与此流有关的所有系统资源。 
 void flush() 
          刷新此输出流并强制写出所有缓冲的输出字节。 
 void write(byte[] b) 
          将 b.length 个字节从指定的 byte 数组写入此输出流。 
 void write(byte[] b, int off, int len) 
          将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 
abstract  void write(int b) 
          将指定的字节写入此输出流。
......

2.6.2 FileOutputStream子类(节点流:文件字节流)

说明:直接插在文件上,直接写出文件数据

创建对象:

1. FileOutputStream(File file) 
          创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 
2. FileOutputStream(File file, boolean append) 
          创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 
3. FileOutputStream(FileDescriptor fdObj) 
          创建一个向指定文件描述符处写入数据的输出文件流,该文件描述符表示一个到文件系统中的某个实际文件的现有连接。 
4. FileOutputStream(String name) 
          创建一个向具有指定名称的文件中写入数据的输出文件流。 
5. FileOutputStream(String name, boolean append) 
          创建一个向具有指定 name 的文件中写入数据的输出文件流。 

测试:

package com.io_file;

import org.junit.Test;

import java.io.*;

/**
 * 测试FileInputStream和FileOutputStream的使用
 *
 * 结论:
 * 1. 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理  (因为字节流处理中文是乱码)
 *     注意这里的乱码指的是 数据到内存层面进行查看有乱码,如果是单纯的复制 硬盘-->硬盘 那么文本文件仍然可以使用字节流处理。
 * 2. 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),只能使用字节流处理   (因为这些非文本文件的基本单位比较小,字符的单位比较大处理不了)
 *
 *
 *
 * @author shkstart
 * @create 2019 下午 2:13
 */
public class FileInputOutputStreamTest {
    
    /*
    实现对图片的复制操作
     */
    @Test
    public void testFileInputOutputStream()  {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //
            File srcFile = new File("爱情与友情.jpg");
            File destFile = new File("爱情与友情2.jpg");

            //
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);

            //复制的过程
            byte[] buffer = new byte[1024];
            int len;
            while((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null){
                //
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    //封装成工具Api的写法:   指定路径下文件(视频,图片,音频等)(文本)的复制     单纯的复制不管是任何类型的文件,字节流都可以使用。
    public void copyFile(String srcPath,String destPath){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);

            //
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);

            //复制的过程
            byte[] buffer = new byte[1024];
            int len;
            while((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null){
                //
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }


    }

    @Test
    public void testCopyFile(){

        long start = System.currentTimeMillis();

        String srcPath = "D:\\aa\\bb\\01-尚硅谷-Java语言基础.avi";
        String destPath = "D:\\aa\\bb\\02-尚硅谷-Java语言基础.avi";


//        String srcPath = "hello.txt";
//        String destPath = "hello3.txt";

        copyFile(srcPath,destPath);


        long end = System.currentTimeMillis();

        System.out.println("复制操作花费的时间为:" + (end - start));//618

    }

}


2.6.3 BufferedOutputStream子类(处理流:缓冲字节流)

说明:该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

创建对象:

1. BufferedOutputStream(OutputStream out) 
          创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 
          
2. BufferedOutputStream(OutputStream out, int size) 
          创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。 

测试·:查看2.5.3中的测试。

2.6.4 ObjectOutputStream子类(处理流:对象输出流 / 序列化)

略,详情查看 2.5.4

2.7 字节流课后练习

2.7.1 图片的加密解密

package com.io_file;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author shkstart
 * @create 2019 下午 4:08
 */
public class PicTest {

    //图片的加密:把原来的图片加密复制后,因为是异或运算打乱顺序,所以图片大小相同 但是打不开。
    @Test
    public void test1() {

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
             /* fis = new FileInputStream("爱情与友情.jpg");等价于 fis = new FileInputStream(new File("爱情与友情.jpg"));
             *  FileInputStream这个直接可以写路径的构造器,实际上里面的路径还是被new File()包裹在里面了。
             *
             * */
            fis = new FileInputStream("爱情与友情.jpg");//真实存在
            fos = new FileOutputStream("爱情与友情secret.jpg");//会自动生成  加密后的图片:因为是异或运算打乱顺序,所以图片大小相同 但是打不开。

            byte[] buffer = new byte[20];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                //字节数组进行修改
                //错误的 增强for循环,这样写是把buffer赋给新的变量了,数组里面的值没有改变。
                //            for(byte b : buffer){
                //                b = (byte) (b ^ 5);    //b ^ 5:异或运算,打乱里面的字节。
                //            }
                //正确的
                for (int i = 0; i < len; i++) {
                    buffer[i] = (byte) (buffer[i] ^ 5); //buffer为byte类型,5位int类型,计算结果和大类型保持一致,所以需要强转。
                }


                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }


    }


    //图片的解密:  原理:异或运算,m^n^n=m
    @Test
    public void test2() {

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("爱情与友情secret.jpg");
            fos = new FileOutputStream("爱情与友情4.jpg");

            byte[] buffer = new byte[20];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                //字节数组进行修改
                //错误的
                //            for(byte b : buffer){
                //                b = (byte) (b ^ 5);
                //            }
                //正确的
                for (int i = 0; i < len; i++) {
                    buffer[i] = (byte) (buffer[i] ^ 5);
                }

                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }


    }
}


2.7.2 获取文本上字符出现的次数,把数据写入文件

package com.io_file;

import org.junit.Test;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


/**
 * 获取文本上字符出现的次数,把数据写入文件
 *
 * 思路:
 * 1.遍历文本每一个字符
 * 2.字符出现的次数存在Map中
 *
 * Map map = new HashMap();
 * map.put('a',18);
 * map.put('你',2);
 *
 * 3.把map中的数据写入文件
 *
 * @author shkstart
 * @create 2019 下午 3:47
 */
public class WordCount {
    /*
    说明:如果使用单元测试,文件相对路径为当前module
          如果使用main()测试,文件相对路径为当前工程
     */
    @Test
    public void testWordCount() {
        FileReader fr = null;
        BufferedWriter bw = null;
        try {
            //1.创建Map集合  k:存放字符 v:存放次数
            Map<Character, Integer> map = new HashMap<Character, Integer>();

            //2.遍历每一个字符,每一个字符出现的次数放到map中
            fr = new FileReader("dbcp.txt");
            int c = 0;
            while ((c = fr.read()) != -1) {
                //int 还原 char
                char ch = (char) c;
                // 判断char是否在map中第一次出现
                if (map.get(ch) == null) {
                    map.put(ch, 1);
                } else {
                    map.put(ch, map.get(ch) + 1);//有值在原有的次数加1
                }
            }

            //3.把map中数据存在文件count.txt,因为上面都是放在内存中不保险。
            //3.1 创建Writer
            bw = new BufferedWriter(new FileWriter("wordcount.txt"));

            //3.2 遍历map,再写入数据
            Set<Map.Entry<Character, Integer>> entrySet = map.entrySet();
            for (Map.Entry<Character, Integer> entry : entrySet) {
                switch (entry.getKey()) {
                    case ' ':
                        bw.write("空格=" + entry.getValue());
                        break;
                    case '\t'://\t表示tab 键字符
                        bw.write("tab键=" + entry.getValue());
                        break;
                    case '\r'://
                        bw.write("回车=" + entry.getValue());
                        break;
                    case '\n'://
                        bw.write("换行=" + entry.getValue());
                        break;
                    default:
                        bw.write(entry.getKey() + "=" + entry.getValue());
                        break;
                }
                bw.newLine();//写完之后换行
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关流
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }
}


3 IO流–02

3.1 字符流读取

说明:常用于处理纯文本数据。

3.1.1 Reader抽象父类

说明:用于读取字符流的抽象父类,既然是抽象类就不能new对象,所以只学习常用的方法不在研究构造方法。

常用方法:

int read() 
          读取单个字符。 一次读取一个字符效率低。
int read(char[] cbuf)     注意是char
          将字符读入数组。 
abstract  int read(char[] cbuf, int off, int len) 
          将字符读入数组的某一部分。 5,3 数组长度为5,但每次最多读进去3个。
int read(CharBuffer target) 
          试图将字符读入指定的字符缓冲区。 
abstract  void close() 
          关闭该流并释放与之关联的所有资源。
......  

3.1.2 FileReader子类(节点流:文件字符流)

说明:用来读取字符文件的便捷类。

构造方法:

1. FileReader(String fileName) //参数为文件的路径
          在给定从中读取数据的文件名的情况下创建一个新 FileReader2. FileReader(File file) 
          在给定从中读取数据的 File 的情况下创建一个新 FileReader3. FileReader(FileDescriptor fd) 
          在给定从中读取数据 的 FileDescriptor 的情况下创建一个新 FileReader

常用方法:没有产生特有方法,都是来自继承与java.lang.Object,java.io.Reader,java.io.InputStreamReader类的方法。

测试

package com.io_file;

import org.junit.Test;

import java.io.*;

/**
 *
 * 一、流的分类:
 * 1.操作数据单位:字节流、字符流
 * 2.数据的流向:输入流、输出流
 * 3.流的角色:节点流、处理流
 *
 * 二、流的体系结构
 * 抽象基类         节点流(或文件流)                               缓冲流(处理流的一种)
 * InputStream     FileInputStream   (read(byte[] buffer))        BufferedInputStream (read(byte[] buffer))
 * OutputStream    FileOutputStream  (write(byte[] buffer,0,len)  BufferedOutputStream (write(byte[] buffer,0,len) / flush()
 * Reader          FileReader (read(char[] cbuf))                 BufferedReader (read(char[] cbuf) / readLine())
 * Writer          FileWriter (write(char[] cbuf,0,len)           BufferedWriter (write(char[] cbuf,0,len) / flush()
 *
 *
 *
 * @author shkstart
 * @create 2019 上午 10:40
 */
public class FileReaderWriterTest {

    public static void main(String[] args) {
        File file = new File("hello.txt");//相对路径使用main方法,相较于当前工程
        System.out.println(file.getAbsolutePath());//E:\idea-workspace\hello.txt

        File file1 = new File("day09\\hello.txt");//相对路径使用main方法,从当前module下开始
        System.out.println(file1.getAbsolutePath());//E:\idea-workspace\day09\hello.txt
    }

    /*
    将idea中io项目下的hello.txt文件内容读入程序中,并输出到控制台

    说明点:
    1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
    2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理。如果用throws处理异常,一旦程序出现异常
       把异常抛出后 后续的代码不会执行。如在第二出 出现异常,那么接些来的代码 close关闭资源不会执行,资源浪费 显然不合适。
    3. 读入的文件一定要存在,否则就会报FileNotFoundException。

     */
    @Test
    public void testFileReader(){
        FileReader fr = null;
        try {
            //1.实例化File类的对象,指明要操作的文件
            File file = new File("hello.txt");//相较于当前Module
            //2.提供具体的流
            fr = new FileReader(file);//产生异常地方一:创建对象会报抛异常

            //3.数据的读入
            //read():返回读入的一个字符。如果达到文件末尾,返回-1    一次读取一个字符效率低。
            //方式一:
//        int data = fr.read();//字符为char类型,这里用int类型接收是因为对应的编码 97...这样存的数据。
//        while(data != -1){
//            System.out.print((char)data);//读第一个,如果不是-1 则进行读取,并且转化为char类型的字符接收 不然输出的是数字。输出不换行
//            data = fr.read();//开始读第二个,并把值赋值给data循环进行判断第二个是否为-1 ......
//        }

            //方式二:语法上针对于方式一的修改
            int data;
            while((data = fr.read()) != -1){//产生异常地方二:read方法回报异常
                System.out.print((char)data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流的关闭操作:对于物理连接如:数据库连接,输入输出流,Socket连接无能为力,这些资源需要手动关闭。
//            try {
//                if(fr != null)//因为fr写在外面声明为null,如果在第一处出现异常后创建对象不成功为null,在走到finally中会出现空指针异常,所以进行if判断。
//                    fr.close();//产生异常地方三:close回报异常,而关闭资源通常在finally中 所以单独的再用一个try-catch,不需要finally了。
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
            //或
            if(fr != null){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    //对read()操作升级:使用read的重载方法
    @Test
    public void testFileReader1()  {
        FileReader fr = null;
        try {
            //1.File类的实例化
            File file = new File("hello.txt");

            //2.FileReader流的实例化
            fr = new FileReader(file);

            //3.读入的操作
            //read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
            char[] cbuf = new char[5];//每次读取指定个数的字符,5个
            int len;
            while((len = fr.read(cbuf)) != -1){//文件数据:helloworld123,每次读取5个到数组中,最后一次读入3个。
                //方式一:
                //错误的写法
//                for(int i = 0;i < cbuf.length;i++){
//                    System.out.print(cbuf[i]);helloworld123ld  因为数组是覆盖操作,第一次[h,e,l,l,o] 第二次[w,o,r,l,d] 第三次[1,2,3,l,d]
                                                //每次新读取的数据覆盖原来的数组值,最后一次只有三个数据,遍历输出数组把另外2个数据也输出了。
//                }
                //正确的写法
//                for(int i = 0;i < len;i++){ //每次读进去几个就遍历几个
//                    System.out.print(cbuf[i]);
//                }
                //方式二:
                //错误的写法,对应着方式一的错误的写法 任然保留ld
//                String str = new String(cbuf);//char转化为String类型
//                System.out.print(str);
                //正确的写法
                String str = new String(cbuf,0,len);//从头开始取值,每次只取len个
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fr != null){
                //4.资源的关闭
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }
 }

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第53张图片

3.1.3 BufferedReader子类(处理流:缓冲字符流)

说明:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

创建对象:

1. BufferedReader(Reader in) 
          创建一个使用默认大小输入缓冲区的缓冲字符输入流。 
2. BufferedReader(Reader in, int sz) 
          创建一个使用指定大小输入缓冲区的缓冲字符输入流。 

测试:

package com.bf_file;

import org.junit.Test;

import java.io.*;

/**
 * 处理流之一:缓冲流的使用
 *
 * 1.缓冲流:
 * BufferedInputStream
 * BufferedOutputStream
 * BufferedReader
 * BufferedWriter
 *
 * 2.作用:提供流的读取、写入的速度
 *   提高读写速度的原因:内部提供了一个缓冲区 (把一定量的数据读入到缓冲区后 一次性写出 写入)
 *
 * 3. 处理流,就是“套接”在已有的流的基础上,处理流不能直接作用在文件上。
 *
 * @author shkstart
 * @create 2019 下午 2:44
 */
public class BufferedTest {


    
    /*
    使用BufferedReader和BufferedWriter实现文本文件的复制

     */
    @Test
    public void testBufferedReaderBufferedWriter(){
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            //创建文件和相应的流
            br = new BufferedReader(new FileReader(new File("dbcp.txt")));
            bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));

            //读写操作
            //方式一:使用char[]数组
//            char[] cbuf = new char[1024];
//            int len;
//            while((len = br.read(cbuf)) != -1){
//                bw.write(cbuf,0,len);
//    //            bw.flush();
//            }

            //方式二:使用String   readLine()一次读一行,当为null时代表后面没数据。
            String data;
            while((data = br.readLine()) != null){
                
                //bw.write(data); //data中不包含换行符,默认不包含换行符 都写在一行中。

                //方法一加上换行符:
//                bw.write(data + "\n");
                //方法二加上换行符:
                bw.write(data);//data中不包含换行符
                bw.newLine();//提供换行的操作

            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if(bw != null){

                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

}


3.1.4 InputStreamReader子类(处理流:转换流)

说明:

  1. 用来作为桥梁,把字节流转成字符流的桥梁。
  2. 用来解决字符流读写乱码问题。

构造方法:

1. InputStreamReader(InputStream in) 
          创建一个使用默认字符集的 InputStreamReader2. InputStreamReader(InputStream in, Charset cs) 
          创建使用给定字符集的 InputStreamReader3. InputStreamReader(InputStream in, CharsetDecoder dec) 
          创建使用给定字符集解码器的 InputStreamReader4. InputStreamReader(InputStream in, String charsetName) 
          创建使用指定字符集的 InputStreamReader

常用方法:

 void close() 
          关闭该流并释放与之关联的所有资源。 
 String getEncoding() 
          返回此流使用的字符编码的名称。 
 int read() 
          读取单个字符。 
 int read(char[] cbuf, int offset, int length) 
          将字符读入数组中的某一部分。 
 boolean ready() 
          判断此流是否已经准备好用于读取。 

测试:

package com.io_file;


import org.junit.Test;

import java.io.*;

/**
 * 处理流之二:转换流的使用
 * 1.转换流:属于字符流
 *   InputStreamReader:将一个字节的输入流转换为字符的输入流
 *   OutputStreamWriter:将一个字符的输出流转换为字节的输出流
 *
 * 2.作用:提供字节流与字符流之间的转换
 *
 * 3. 解码:字节、字节数组  --->字符数组、字符串   (看不懂--->看得懂)
 *    编码:字符数组、字符串 ---> 字节、字节数组   (看的懂--->看不懂)
 *
 *
 * 4.字符集
 *ASCII:美国标准信息交换码。
 用一个字节的7位可以表示。
 ISO8859-1:拉丁码表。欧洲码表
 用一个字节的8位表示。
 GB2312:中国的中文编码表。最多两个字节编码所有字符
 GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
 Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
 UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

 *
 *
 * @author shkstart
 * @create 2019 下午 4:25
 */
public class InputStreamReaderTest {
    /* 把指定的文件读到内存中,并输出到控制台:
    此时处理异常的话,仍然应该使用try-catch-finally,这里是偷懒写法用throws。
    InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
     */
    @Test
    public void test1() throws IOException {

        FileInputStream fis = new FileInputStream("dbcp.txt");
//        InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集,跟你用的idea中设置的编码保持一致。
        //参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集(即:读取时的编码应该和读取的文件设置的编码保持一致)
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用指定的的字符集 和读取的文件字符集编码保持一致。

        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf)) != -1){
            String str = new String(cbuf,0,len);
            System.out.print(str);
        }

        isr.close();

    }

    /*
    此时处理异常的话,仍然应该使用try-catch-finally,这里是偷懒写法用throws。

    综合使用InputStreamReader和OutputStreamWriter
    过程:硬盘中的UTF8.txt--->文件字节流--->字节输入流--->字符输入转换流(UTF-8,和保存文件的编码一致)---->内存
         --->字符输出转换流(GBK,选择想要编码的字符)--->字节输出流--->文件输出流---硬盘中的GBK.txt文件
     */
    @Test
    public void test2() throws Exception {
        //1.造文件、造流
        File file1 = new File("dbcp.txt");
        File file2 = new File("dbcp_gbk.txt");//查看的话只能用 gbk的方式查看才不是乱码。

        FileInputStream fis = new FileInputStream(file1);
        FileOutputStream fos = new FileOutputStream(file2);

        InputStreamReader isr = new InputStreamReader(fis,"utf-8");
        OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");

        //2.读写过程
        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf)) != -1){
            osw.write(cbuf,0,len);
        }

        //3.关闭资源
        isr.close();
        osw.close();


    }


}


3.1.5 常见字符编码表

编码表的由来:计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。

乱码现象,多指中文乱码:

  1. 怎么产生的?是因为你保存时用的码表和 打开时用的码表不一致造成的。
  2. 解决方案:就是保存和打开统一码表就行。
编码 简介 说明 编码范围 字节量
ASCII 美国标准信息交换码 英文,标点,基本指令 0到127 用一个字节的7位可以表示。 (单字节)
ISO8859-1 拉丁码表 ,欧洲码表 兼容了ASCII,在此基础上扩展了西欧字符 128到255 用一个字节的8位表示。(单字节)
GB2312 中国的中文编码表 兼容了ASCII 最多两个字节编码所有字符
GBK 中国的中文编码表的升级 兼容了ASCII,融合了更多的中文字符号 最大65535 最多两个字节编码所有字符。(英文使用单字节,中文使用双字节)
Unicode 国际标准码 国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码,兼容了ASCII。 100万+编码位,分为常用字符表,生僻字符表等,我们只是用常用表 所有的文字都用两个字节来表示。 (常用字符表所有字符都采用双字节)
UTF-8 优化后的国际标准码 为了解决Unicode英文字符字节量翻倍的问题,提出的一种变长的编码格式,兼容了ASCII。 可用1-4个字节来表示一个字符。(英文单字节,某些字符双字节,中文三字节,一些特殊符号四字节 )

Unicode 编码问题:
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第54张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第55张图片

注意事项:

  1. ANSI编码,通常指的是平台的默认编码,例如英文操作系统中是ISO-8859-1,中文系统是GBK。

  2. Unicode字符集只是定义了字符的集合和唯一编号,Unicode编码,则是对UTF-8、UCS-2/UTF-16等具体编码方案的统称而
    已,并不是具体的编码方案。

对后面学习的启示:

  1. 客户端/浏览器端 <--------> 后台(java,GO,Python,Nod.js,php) <--------> 数据库
  2. 要求前前后后使用的字符集都要统一:UTF-8。

3.2 字符流写出

3.2.1 Writer抽象父类

说明:写入字符流的抽象类,同样不研究构造方法。

常用方法:

void write(char[] cbuf)    
          写入字符数组。  char
abstract  void write(char[] cbuf, int off, int len) 
          写入字符数组的某一部分。 
void write(int c) 
          写入单个字符。 
void write(String str) 
          写入字符串。 
void write(String str, int off, int len) 
          写入字符串的某一部分。
abstract  void close() 
          关闭此流,但要先刷新它。
......

3.2.2 FileWriter子类(节点流:文件字符流)

说明:用来写入字符文件的便捷类。

创建对象:

1. FileWriter(File file) 
          根据给定的 File 对象构造一个 FileWriter 对象。 
2. FileWriter(File file, boolean append) 
          根据给定的 File 对象构造一个 FileWriter 对象。 
3. FileWriter(FileDescriptor fd) 
          构造与某个文件描述符相关联的 FileWriter 对象。 
4. FileWriter(String fileName) 
          根据给定的文件名构造一个 FileWriter 对象。 
5. FileWriter(String fileName, boolean append) 
          根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。 

常用方法:没有产生特有方法,都是来自继承与java.lang.Object,java.io.Writer,java.io.OutputStreamWriter类的方法。

测试

package com.io_file;

import org.junit.Test;

import java.io.*;

/**
 *
 * 一、流的分类:
 * 1.操作数据单位:字节流、字符流
 * 2.数据的流向:输入流、输出流
 * 3.流的角色:节点流、处理流
 *
 * 二、流的体系结构
 * 抽象基类         节点流(或文件流)                               缓冲流(处理流的一种)
 * InputStream     FileInputStream   (read(byte[] buffer))        BufferedInputStream (read(byte[] buffer))
 * OutputStream    FileOutputStream  (write(byte[] buffer,0,len)  BufferedOutputStream (write(byte[] buffer,0,len) / flush()
 * Reader          FileReader (read(char[] cbuf))                 BufferedReader (read(char[] cbuf) / readLine())
 * Writer          FileWriter (write(char[] cbuf,0,len)           BufferedWriter (write(char[] cbuf,0,len) / flush()
 *
 *
 *
 * @author shkstart
 * @create 2019 上午 10:40
 */
public class FileReaderWriterTest {
    
    /*
    从内存中写出数据到硬盘的文件里。

    说明:
    1. 输出操作,对应的File可以不存在的。并不会报异常
    2.
         File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
         File对应的硬盘中的文件如果存在:
                如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
                如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容

     */
    @Test
    public void testFileWriter() {//上去写代码可以先throws抛出异常,写完代码后再更改为try-catch这样代码更容易理解。
        FileWriter fw = null;
        try {
            //1.提供File类的对象,指明写出到的文件
            File file = new File("hello1.txt");

            //2.提供FileWriter的对象,用于数据的写出
            fw = new FileWriter(file,false);

            //3.写出的操作
            fw.write("I have a dream!\n");
            fw.write("you need to have a dream!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流资源的关闭
            if(fw != null){

                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }
    //把硬盘hello.txt中的数据 读入到内存中,在把内存中的数据写出到硬盘hello2.txt中。
    @Test
    public void testFileReaderFileWriter() {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            //1.创建File类的对象,指明读入和写出的文件
            File srcFile = new File("hello.txt");
            File destFile = new File("hello2.txt");

            //不能使用字符流来处理图片等字节数据
//            File srcFile = new File("爱情与友情.jpg");
//            File destFile = new File("爱情与友情1.jpg");


            //2.创建输入流和输出流的对象
            fr = new FileReader(srcFile);
            fw = new FileWriter(destFile);


            //3.数据的读入和写出操作
            char[] cbuf = new char[5];
            int len;//记录每次读入到cbuf数组中的字符的个数
            while((len = fr.read(cbuf)) != -1){
                //每次写出len个字符,每次读几个字符 写几个字符
                fw.write(cbuf,0,len);

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流资源   2个流的资源关闭先后顺序没有要求。
            //方式一:
//            try {
//                if(fw != null)
//                    fw.close();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }finally{
//                try {
//                    if(fr != null)
//                        fr.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
            //方式二:
            try {
                if(fw != null)
                    fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if(fr != null)
                    fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

}


3.2.3 BufferedWriter子类(处理流:缓冲流)

说明:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。 可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。

创建对象:

1. BufferedWriter(Writer out) 
          创建一个使用默认大小输出缓冲区的缓冲字符输出流。 
2. BufferedWriter(Writer out, int sz) 
          创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 

测试:详情查看3.1.3

3.2.4 OutputStreamWriter子类(处理流:转换流)

说明:

  1. 用来作为桥梁,把字符流转成字节流的桥梁。
  2. 用来解决字符流写出的乱码问题。

构造方法:

1. OutputStreamWriter(OutputStream out) 
          创建使用默认字符编码的 OutputStreamWriter2. OutputStreamWriter(OutputStream out, Charset cs) 
          创建使用给定字符集的 OutputStreamWriter3. OutputStreamWriter(OutputStream out, CharsetEncoder enc) 
          创建使用给定字符集编码器的 OutputStreamWriter4. OutputStreamWriter(OutputStream out, String charsetName) 
          创建使用指定字符集的 OutputStreamWriter

常用方法:

 void close() 
          关闭此流,但要先刷新它。 
 void flush() 
          刷新该流的缓冲。 
 String getEncoding() 
          返回此流使用的字符编码的名称。 
 void write(char[] cbuf, int off, int len) 
          写入字符数组的某一部分。 
 void write(int c) 
          写入单个字符。 
 void write(String str, int off, int len) 
          写入字符串的某一部分。 

测试:详情查看3.1.4

3.3 其它流的使用(不常用)

3.3.1 标准的输入、输出流(System.in,System.out)

equals参数前后 空指针问题???

说明:

  1. System.in和System.out分别代表了系统标准的输入和输出设备
  2. 默认输入设备是:键盘,输出设备是:显示器
  3. System.in的类型是InputStream
  4. System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
  5. 重定向:通过System类的setIn,setOut方法对默认设备进行改变。(比如打印到控制台的数据变为打印到文件中,详情查看打印流)
    public static void setIn(InputStream in) 参数是InputStream 字节输入流
    public static void setOut(PrintStream out) 参数是PrintStream 字节打印输出流

继承关系:
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第56张图片
注意:用键盘在控制台输入字符(Scanner输入,标准的输入流输出流System.in System.out),可能出现数据不能输入的情况。

  1. 如果是在idea中 写在main方法可以在控制台输入数据,使用测试方法在控制台输入不了数据。
  2. 在eclipse中不论是写在main方法还是测试方法中都可以输入数据。
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第57张图片

练习:

package com.io_file;


import org.junit.Test;

import java.io.*;

/**
 * 其他流的使用
 * 1.标准的输入、输出流
 * 2.打印流
 * 3.数据流
 *
 * @author shkstart
 * @create 2019 下午 6:11
 */
public class OtherStreamTest {

    /*
    1.标准的输入、输出流
    1.1
    System.in:标准的输入流,默认从键盘输入
    System.out:标准的输出流,默认从控制台输出
    1.2
    System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。

    1.3练习:
    从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,
    直至当输入“e”或者“exit”时,退出程序。

    方法一:使用Scanner实现,调用next()返回一个字符串
    方法二:使用System.in实现。System.in (返回值为InputStream类型,字节流转化为字符流中间需要转换流)  --->  转换流 ---> BufferedReader的readLine()

     */
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            InputStreamReader isr = new InputStreamReader(System.in);
            br = new BufferedReader(isr);

            while (true) {
                System.out.println("请输入字符串:");
                String data = br.readLine();//一次读一行数据
                /*
                * equalsIgnoreCase() :忽略大小写进行相等比较
                * equals():不忽略大小写进相等判断。
                *
                * 这种方式不好 有可能出现空指针的问题
                * if (data.equalsIgnoreCase("e") || data.equalsIgnoreCase("exit")){
                *
                * }
                * */
                if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {//可以更好的避免空指针的出现
                    System.out.println("程序结束");
                    break;
                }

                String upperCase = data.toUpperCase();//转化为大写
                System.out.println(upperCase);

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

}


3.3.2 打印流(高级流:PrintStream和PrintWriter)

说明:

  1. 实现将基本数据类型的数据格式转化为字符串输出,可以输出各种各样类型的数据。
  2. 打印流:PrintStream和PrintWriter
  3. 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
  4. PrintStream和PrintWriter的输出不会抛出IOException异常
  5. PrintStream和PrintWriter有自动flush功能
  6. PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。
    在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
  7. System.out返回的是PrintStream的实例

继承关系:

  1. 字节打印输出流
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第58张图片
  2. 字符打印输出流
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第59张图片
    测试:
package com.io_file;


import org.junit.Test;

import java.io.*;

/**
 * 其他流的使用
 * 1.标准的输入、输出流
 * 2.打印流
 * 3.数据流
 *
 * @author shkstart
 * @create 2019 下午 6:11
 */
public class OtherStreamTest {



    /*
    2. 打印流:PrintStream 和PrintWriter  可以输出各种各样类型的数据。

    2.1 提供了一系列重载的print() 和 println()
    2.2 练习:输出0-255数字对应的ASCII字符(十进制),到对应的文件中

      什么需求下使用:把输出的数据 有打印到控制台 改为 打印到文件中即可使用。


     */

    @Test
    public void test2() {
        PrintStream ps = null;
        try {
            //注意这个文件路径:开始时,文件目录(磁盘写的小写io目录,这里写的大写IO,目录在windows中不区分大小写的)需要存在,文件(text.txt)可以不存在会帮你自动造。
            FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt"));
            // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
            ps = new PrintStream(fos, true);//true代表自动flush操作
            if (ps != null) {
                System.setOut(ps);// 把标准输出流(控制台输出)改成文件   setOut():重新指定一个打印流对象ps
            }


            for (int i = 0; i <= 255; i++) { // 输出ASCII字符:a-->97(十进制)  A-->65(十进制)  0-->48(十进制)
                System.out.print((char) i);
                if (i % 50 == 0) { // 每50个数据一行
                    System.out.println(); // 换行
                }
            }


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
        }

    }

    

}


day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第60张图片

3.3.3 数据流(高级流:DataInputStream 和 DataOutputStream)

说明:
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第61张图片
继承关系:

  1. 数据输入流
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第62张图片
  2. 数据输出流
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第63张图片
    测试:
package com.io_file;


import org.junit.Test;

import java.io.*;

/**
 * 其他流的使用
 * 1.标准的输入、输出流
 * 2.打印流
 * 3.数据流
 *
 * @author shkstart
 * @create 2019 下午 6:11
 */
public class OtherStreamTest {


    /*
    3. 数据流
    3.1 DataInputStream 和 DataOutputStream
    3.2 作用:用于读取或写出基本数据类型的变量或字符串

    练习:将内存中的字符串、基本数据类型的变量写出到文件中。但是不能读写对象,读写对象需要使用对象输入流,对象输出流(反序列化,序列化 )。

    注意:处理异常的话,仍然应该使用try-catch-finally.
     */

    //先写才能读:先把内存中的数据写到文件中,这个文件是二进制数据不能直接打开读,想要读取只能把数据读到内存中 输出到控制台查看
    @Test
    public void test3() throws IOException {
        //1.
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
        //2.
        dos.writeUTF("刘建辰");
        dos.flush();//刷新操作,将内存中的数据写入文件(一旦执行刷新操作,就会把内存中已有的数据刷新到文件中)
        dos.writeInt(23);
        dos.flush();
        dos.writeBoolean(true);
        dos.flush();
        //3.
        dos.close();


    }
    /*
    将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。

    注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!

     */
    @Test
    public void test4() throws IOException {
        //1.
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
        //2.
        String name = dis.readUTF();
        int age = dis.readInt();
        boolean isMale = dis.readBoolean();

        System.out.println("name = " + name);
        System.out.println("age = " + age);
        System.out.println("isMale = " + isMale);

        //3.
        dis.close();

    }

}


3.3.4 随机存取文件流(RandomAccessFile)

概述:
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第64张图片
继承结构:
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第65张图片

构造方法:

                   参数1:文件名      参数2:详情查看测试代码
1.RandomAccessFile(File file, String mode) 
          创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。 
          
                    参数1:路径      参数2
2.RandomAccessFile(String name, String mode) 
          创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。 

测试:

package Random_File;

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * RandomAccessFile的使用
 * 1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
 * 2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流。但是创建对象还是需要2个,一个用来读 一个用来写
 *
 * 3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。
 *   如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
 *
 * 4. 可以通过相关的操作,实现RandomAccessFile“插入”数据的效果
 *
 * @author shkstart
 * @create 2019 上午 11:18
 */
public class RandomAccessFileTest {


    //测试图片的复制
    @Test
    public void test1() {

        RandomAccessFile raf1 = null;
        RandomAccessFile raf2 = null;
        try {
            //1.

           /* 参数2mode的取值:
            * r: 以只读方式打开  写r只能读,不能写
            * rw:打开以便读取和写入
            * rwd:打开以便读取和写入;同步文件内容的更新
            * rws:打开以便读取和写入;同步文件内容和元数据的更新
            *
            * 注意事项:
            *   如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。(只能用于读,并且读的时候如果文件不存在不会创建)
            *   如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。(如果文件不存在读、写都会创建文件,只不过文件里面什么也没有。如果在写的时候文件
            *                                                                                                               存在则会覆盖)
            *
            * */
            raf1 = new RandomAccessFile(new File("爱情与友情aa.jpg"),"rw");//作为输入
            raf2 = new RandomAccessFile(new File("爱情与友情aa1.jpg"),"rw");//作为输出
            //2.
            byte[] buffer = new byte[1024];
            int len;
            while((len = raf1.read(buffer)) != -1){
                raf2.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //3.
            if(raf1 != null){
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if(raf2 != null){
                try {
                    raf2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }
    //测试覆盖效果:创建文件hello.txt,向此文件写出内容,看是否被覆盖。
    /*注意不是整个文件的覆盖,而是从头开始(指针为0),对里面的内容进行覆盖。
     * 如:原文件hello.txt:         hello123bbb
     *    写出数据为xyz,则文件内容变为:xyzlo123bbb
     * 此指针可以调整,如raf1.seek(3)将指针调到角标为3的位置,也就是说从角标为3(第4个数据)开始覆盖
     * 如:原文件hello.txt:         hello123bbb
     *    写出数据为xyz,则文件内容变为:helxyz23bbb
     *
     * 思考如何在hello123bbb后面进行追加数据:
     *   需要把指针指定到文件末尾:在File类中提供了一个方法length()可以获取文件的长度
     *    如:hello123bbb 的长度为11,下标从0开始,最后一个b为10,那么末尾为下标11 恰好是文件的长度。
     */
    @Test
    public void test2() throws IOException {

        RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");

        raf1.seek(3);//将指针调到角标为3的位置
        raf1.write("xyz".getBytes());//getBytes():使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

        raf1.close();

    }
    /*
     * 使用RandomAccessFile实现数据的插入效果:它本身只能是覆盖那么如何实现插入效果呢???
     * 如:原文件hello.txt:         hello123bbb
     *    现在想要在123后面插入ccc
     * 思路:先把指针调到b的位置上,把bbb取出来 在复制的时候指针也会向后移动,此时指针在最后一个位置上,
     *      再把指针调到3的后面,在进行追加ccc,在把复制的数据拼接到后面。 hello123cccbbb
     *
     */
    @Test
    public void test3() throws IOException {

        RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");

        raf1.seek(8);//将指针调到角标为3的位置
        //保存指针3后面的所有数据到StringBuilder中  指定长度创建String
        StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
        byte[] buffer = new byte[20];
        int len;
        while((len = raf1.read(buffer)) != -1){
            //append方法的参数不能直接传byte类型的数组,可以传String类型,String类型里面可以传char类型的数组
            builder.append(new String(buffer,0,len)) ;//进行拼接,如果数组没有数据不就是添加到数组中吗(abc+dd=abcdd,null+dd=dd)
        }
        //调回指针,写入“xyz”
        raf1.seek(8);
        raf1.write("ccc".getBytes());//此时指针在文件末尾

        //将StringBuilder中的数据写入到文件中
        raf1.write(builder.toString().getBytes());//write参数为字节,StringBuilder没有提供getBytes()方法,先转化为String类型 在调用getBytes()方法。

        raf1.close();

        //思考:将StringBuilder替换为ByteArrayOutputStream day617
    }
}


3.4 NIO介绍

3.4.1 常识介绍

  1. 阻塞IO,BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。

  2. 非阻塞IO,NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。

  3. 异步IO,AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。但目前还不够成熟,应用不多。

3.4.2 NIO概述

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第66张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第67张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第68张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第69张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第70张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第71张图片
day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第72张图片

3.5 导入第三方的jar包实现读写

3.5.1 概述

说明:

  1. 真正在开发中io流的代码,有可能是自己写,也有可能是直接调用jar包的Api 它的底层源码还是封装了这些io这些操作 ,这样调用更加简单。
  2. 没学springboot之前使用ar包需要手动添加jar包。

3.5.2 添加jar包测试

以idea为例添加jar包:

  1. 复制jar包。
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第73张图片
  2. 在idea项目中创建目录一般是:libsday04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第74张图片
  3. 把jar包复制到此目录下
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第75张图片
  4. 放使jar包生效:
    day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第76张图片

3.5.3 编写代码测试

day04--java高级编程:API常用类,单元测试,java比较器,进制,length,IO流_第77张图片

3.6 扩展

3.6.1 IO中flush()和close()的区别

  1. Flush() 刷新数据到目的地,流可以继续使用。
  2. Close() 关闭流,关闭之前会刷新数据到目的地。关闭后,流不能继续使用。

你可能感兴趣的:(一,Java基础阶段,java,开发语言,后端)