Java IO

File类

常用方法

boolean exists() //表示当前路径是否存在
boolean isFile() //判断当前路径是否是文件
boolean isDirectory() //判断当前是否为目录
boolean isHidden() //判断是否是隐藏文件
void deleteOnExit() //在程序退出时删除
boolean delete() //直接删除

boolean createNewFile() //创建新文件,当前文件不存在时,创建成功返回true,文件存在返回false
mkdir() //创建一级目录  
mkdirs() //创建多级目录

file.getName(); // 获取文件名
file.getPath(); //获取相对路径
file.getParent(); //获取文件的父路径
file.lastModified(); //获取文件最后一次修改的时间
file.getAbsolutePath(); //获取绝对路径
file.length(); //获取文件长度

file.list(); //获取当前目录下所有文件
file.listFiles()//获取当前目录下所有文件及其路径

public boolean renameTo(File dest)//修改文件名、地址

Demo

public class FileTest {
    public static void main(String[] args) {
        File file = new File("lql/lql1.txt");
        try {
            System.out.println(file.createNewFile());
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(file.exists());
        System.out.println(file.isFile());
        System.out.println(file.isDirectory());
        System.out.println(file.isHidden());

        System.out.println(file.getName());
        System.out.println(file.getPath());
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getParent());

        Date date = new Date(file.lastModified());
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String format = simpleDateFormat.format(date);
        System.out.println(format);

        System.out.println(file.length());

        File file1 = new File("F:\\java代码\\java\\001\\001");
        file1.mkdirs();
        String[] list = file1.list();
        System.out.println(Arrays.toString(list));

        File[] files = file1.listFiles();
        System.out.println(Arrays.toString(files));

        File file2 = new File("F:\\java代码\\java\\001\\001\\002.txt");
        String newName = new String("F:\\java代码\\java\\001\\00002.txt");
        boolean b = file2.renameTo(new File(newName));
    }
}

Collections类

常用方法

// 排序操作(主要针对List接口相关)
reverse(List list);  //反转指定List集合中元素的顺序
shuffle(List list);  //对List中的元素进行随机排序(洗牌)
sort(List list);  //对List里的元素根据自然升序排序
sort(List list, Comparator c);  //自定义比较器进行排序
swap(List list, int i, int j);  //将指定List集合中i处元素和j出元素进行交换
rotate(List list, int distance);  //将所有元素向右移位指定长度,如果distance等于size那么结果不变

// 查找和替换(主要针对Collection接口相关)
binarySearch(List list, Object key);  //使用二分搜索法,以获得指定对象在List中的索引,前提是集合已经排序
max(Collection coll);  //返回最大元素
max(Collection coll, Comparator comp);  //根据自定义比较器,返回最大元素
min(Collection coll);  //返回最小元素
min(Collection coll, Comparator comp);  //根据自定义比较器,返回最小元素
fill(List list, Object obj);  //使用指定对象填充
frequency(Collection, Object o);  //返回指定集合中指定对象出现的次数
replaceAll(List list, Object old, Object new);  //替换

// 同步控制
Collections.synchronizedXXX;
// 示例
List<Integer> list = new ArrayList();
List<Integer> integers = Collections.synchronizedList(list);

// 设置不可变集合
emptyXxx();  //返回一个空的不可变的集合对象
singletonXxx();  //返回一个只包含指定对象的,不可变的集合对象。
unmodifiableXxx();  返回指定集合对象的不可变视图

IO

概念

流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。
流的本质:数据传输。根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
流的作用:为数据源和目的地建立一个输送通道。

分类

  1. 按照流向分,可以分为输入流和输出流。
    输入流:只能从中读取数据,而不能向其写入数据。 把文件中的信息读取到程序中。
    输出流:只能向其写入数据,而不能向其读取数据。把程序中的信息输出到文件中。
  2. 按照操作单元分,可以分为字节流和字符流。
    字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符
    字节流主要是由InputStream和outPutStream作为基类,而字符流则主要有Reader和Writer作为基类。
  3. 按照是否直接处理文件处理节点流和处理流。
    可以从/向一个特定的IO设备(如磁盘,网络)读/写数据的流,称为节点流。节点流也被称为低级流。从图中可以看出,当使用节点流进行输入和输出时,程序直接连接到实际的数据源,和实际的输入/输出节点连接。
    处理流则用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。处理流也被称为高级流。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

流的使用

InputStream

字节流读

public class demo1 {
    public static void main(String[] args) {
        FileInputStream inputStream = null;
        try {
            // File.separator <=> '/'
            inputStream = new FileInputStream("E:/test/123.txt");
            // XXXXXXXXXXXXXXXXXXXXXXX
            // XXXXXXXXXXXXXXXXXXXXXXX
            // XXXXXXXXXXXXXXXXXXXXXXX
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(inputStream != null){
                    inputStream.close();
                    inputStream = null;// 释放强引用
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

int read();

	int read = -1;
	while((read = inputStream.read()) != -1){
	    System.out.print((char) read);
	}

int read(byte[] buffer);

	int len = inputStream.available();
	byte[] bytes = new byte[len];
	inputStream.read(bytes);
	System.out.println(new String(bytes));
	// 读中文时:System.out.println(new String(bytes,"utf-8"));
	
	//规定好数组大小
	byte[] bytes = new byte[5];
	int read = -1;
	while((read = inputStream.read(bytes)) != -1){
	    System.out.print(new String(bytes));
	}

int read(byte[] buffer, int off, int len);

	byte[] bytes = new byte[1024];
	int read = -1;
	int off = 0; // 下标
	int len = 3; // 步长
	while((read = inputStream.read(bytes,off,len)) != -1){
		off += len;
	}
	System.out.print(new String(bytes));

OutputStream

字节流写

FileOutputStream outputStream = null;
try {
    outputStream = new FileOutputStream("E:/test/123.txt",true);
    // XXXXXXXXXXXXXXXXXXXXXXX
    // XXXXXXXXXXXXXXXXXXXXXXX
    // XXXXXXXXXXXXXXXXXXXXXXX
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}finally {
    try {
        if(outputStream != null){
            outputStream.close();
            outputStream = null;
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
}

void write(int b);

    // 将一个字节类型的数组中的数据写入输出流。
	String str = "doingwhat";
	for(int i = 0;i<str.length();i++){
	    int b = (int)str.charAt(i);
	    outputStream.write(b);
	}

void write(byte[] b);

    // 将一个字节类型的数组中的从指定位置(off)开始的,len个字节写入到输出流。
	String str = "doingwhat";
	outputStream.write(str.getBytes());

void write(byte[] b, int off, int len);

    // 将输出流中缓冲的数据全部写出到目的地。
	String str = "doingwhat";
	// 第三个字符开始
	outputStream.write(str.getBytes(),3,2);

void flush();

//刷新缓冲区
fileWriter.flush();

FileReader

字符流读
在缓冲区中将将读到的字节(默认utf-8)转成字符展示给用户。
使用方法同InputStream。

// InputStream 和 Reader还支持如下方法来移动流中的指针位置:
// 在此输入流中标记当前的位置
//readlimit - 在标记位置失效前可以读取字节的最大限制。
void mark(int readlimit) // 标记位置
void reset() // 回到mark位置
boolean markSupported() // 跳过和丢弃此输入流中数据的 n 个字节/字符
long skip(long n) // 跳过指定字符

FileWriter

字符流写
写入的字符在缓冲区中全部转成字节(默认使用utf-8),再通过字节流将数据写入。使用close方法或flush方法刷新缓冲区。
使用方法同OutputStream。write方法可传String字符串。

转换流

转换流是字符流和字节流之间的桥梁。
有缓冲区,可以将读取到的字符/字节数据通过指定编码转换为字节/字符。
缓冲区较小。

public static void main(String[] args) {
    Reader reader = null;
    try {
        reader = new InputStreamReader(new FileInputStream("E:/test/456.txt"),"gbk");
        int read = -1;
        while((read = reader.read())!=-1){
            System.out.print((char) read);
        }
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    Writer writer = null;
    try {
        //                                       真正写文件的流
        writer = new OutputStreamWriter(new FileOutputStream("E:/test/456.txt",true),"gbk");
        writer.write("来学习");
        writer.flush();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

带Buffered的字节流

带缓冲区的字节流(推荐使用)
使用方法同FileInputStream和FileOutputStream。
缓冲区的作用:减少IO次数,提高效率。
读:从文件中读取8k放入缓冲区,直到将缓冲区的数据读完,再次读取8k数据刷新缓冲区。
写:将写的数据存入缓冲区,攒够8k后一次性写入到文件中,写入之后清空缓冲区(将下标移动到初始位置,覆盖原数据)。

public class demo3 {
    public static void main(String[] args) {
        BufferedInputStream inputStream = null;
        BufferedOutputStream outputStream = null;

        try {
            //                                           参数:输入字节流                         指定缓冲区大小
            inputStream = new BufferedInputStream(new FileInputStream("E:/test/123.txt"),8 * 1024);
            int read = -1;
            while((read = inputStream.read()) != -1){
                System.out.print((char)read);
            }


            outputStream = new BufferedOutputStream(new FileOutputStream("E:/test/123.txt",true));
            String str = "45645465";
            outputStream.write(str.getBytes());
            outputStream.flush(); // **注意**

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

带Buffered的字符流

推荐使用。
同带Buffered的字节流。

public class demo4 {
    public static void main(String[] args) {
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            bufferedReader = new BufferedReader(new FileReader("E:/test/123.txt"));
            String str = null;
            while((str = bufferedReader.readLine()) != null){ //BufferedReader特有,按行读取
                System.out.println(str); //会自动去掉换行符
            }

            bufferedWriter = new BufferedWriter(new FileWriter("E:/test/123.txt",true));
            String str1 = "afds份未发";
            for(int i = 0;i<str1.length();i++){
                bufferedWriter.write((int) str1.charAt(i));
            }
            bufferedWriter.newLine();  BufferedWriter特有,写一个换行符
            bufferedWriter.flush();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

序列化

定义

序列化:一种对象持久化的手段,指将对象转换为字节流的过程。
反序列化:根据字节流恢复对象的过程。

用途

  • 把对象的字节序列化永久的存放在磁盘上,通常放在一个文件夹下。
  • 在网络上传输对象的字节序列。

注意事项

  1. 通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化。

  2. 继承java.io.Serializable接口,如果没有继承会有如下错误。

  3. 要想将父类(或其他类)对象也序列化,就需要让父类也实现Serializable 接口,否则会抛出异常。
    在这里插入图片描述

  4. 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还取决于两个类的序列化 ID 是否一致(private static final long serialVersionUID)。序列化操作的时候系统会把当前类的serialVersionUID(特殊处理)写入到序列化文件中,当反序列化时系统会检测文件中的serialVersionUID,判断是否与当前类一致,如果一致,说明序列化类的版本与当前类版本一样,反序列化成功,否则失败。serialVersionUID 用来表明类的不同版本间的兼容性。它有两种生成方式: 一个是默认的1L;另一种是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段 。

  5. 序列化并不保存静态变量。因为静态变量属于类而不属于方法。

  6. transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。transient 只能修饰属性。 加上关键字之后,重写readObject和writeObject方法。如果没有重写的话,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及ObjectInputStream 的 defaultReadObject 方法。

  7. transient关键字使用场景:
    1、为了安全起见,有时候不希望以明文数据形式存储到文件中(如身份证号码,密码,银行卡号等)。
    2、当你不想让某个字段序列化时,直接加上关键字,不用做后续处理。
    3、ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。

Demo

class Animal implements Serializable {
    private String kind;
    public Animal(){}
    public Animal(String kind){
        this.kind = kind;
    }
	//省略get和set方法以及toString方法
}
class Dog extends Animal implements Serializable{
    private static final long serialVersionUID = 2289528616255995897L; //序列化ID,光标移动到类名上alt+enter,自动生成
    private String name;
    private int age;
    private static String sex = "male";
    private transient int weight;
    private Animal a;

    public Dog(String name, int age,int weight,String kind) {
        this.name = name;
        this.age = age;
        this.weight = weight;
        a = new Animal(kind);
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();//对没有transient关键字的属性使用默认方式序列化
        weight = weight * 2;
        out.writeInt(weight);
    }
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        int i = in.readInt();
        weight = i / 2; // weight原本被置零了
    }
	//省略get和set方法以及toString方法
}
public class demo1 { //对象的持久化,数据落地
    public static void main(String[] args) {
        String str = "123456";
        String str1 = "789456";
        List<String> strList = new ArrayList<>(); //存储多个同一个类的对象可以用List
        strList.add(str);
        strList.add(str1);
        Dog dog = new Dog("Tom",1,15,"泰迪");
        ObjectOutputStream outputStream = null;
        ObjectInputStream inputStream = null;
        try {
            outputStream = new ObjectOutputStream(new FileOutputStream("E:/test/objects.txt"));
            outputStream.writeObject(dog);
            outputStream.writeObject(strList);
            outputStream.flush(); //注意***

            inputStream = new ObjectInputStream(new FileInputStream("E:/test/objects.txt"));
            Dog dog1 = (Dog) inputStream.readObject();
            List<String> strList1 = (List<String>)inputStream.readObject();

            Iterator<String> iterator = strList1.iterator();
            while(iterator.hasNext()){
                System.out.println(iterator.next()+"   ");
            }

            System.out.println(dog1);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

结果
在这里插入图片描述

练习

使用4种方式创建一个新的对象

  • 使用new关键字 → 调用了构造函数 (略)
  • 使用Class类的newInstance方法 → 调用了构造函数
    Employee emp2 = (Employee)Class.forName(“org.programming.mitra.exercises.Employee”).newInstance();
    ——————————————————————或者——————————————————————
    Employee emp2 = Employee.class.newInstance();
  • 使用clone方法 → 没有调用构造函数
    Employee emp4 = (Employee) emp3.clone();
  • 使用反序列化→ 没有调用构造函数
    ObjectInputStream in =new ObjectInputStream(newFileInputStream(“E:/test/objects.txt”));
    Dog dog1 = (Dog) inputStream.readObject();

RandomAccessFile

构造函数

public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)

mode表示打开文件共有4种方式:“r”, “rw”, “rws"和"rwd”。

  • “r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
  • “rw” 打开以便读取和写入。 当关闭文件时,会将“文件内容的修改”同步到基础存储设备上。
  • “rws” 打开以便读取和写入。相对于 “rw”,“rws” 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。
  • “rwd” 打开以便读取和写入。相对于 “rw”,“rwd” 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。

说明:

  1. 元数据(metadata)是“关于数据的数据”。在文件系统中,数据被包含在文件和文件夹中;metadata信息包括:“数据是一个文件,一个目录还是一个链接”,“数据的创建时间(简称ctime)”,“最后一次修改时间(简称mtime)”,“数据拥有者”,“数据拥有群组”,“访问权限”等等。
  2. 当操作的文件是存储在本地的基础存储设备上时(如硬盘, NandFlash等),“rws” 或 “rwd”, “rw” 才有区别。

常用方法

raf.seek(raf.length()); 移动光标
getFilePointer();返回文件记录指针的当前位置

Demo

public class demo5 {
	// 随机读取,并返回字符串
	public static String read(String path,int position){
		RandomAccessFile accessFile1 = null;
		String str = "";
		try {
			accessFile1 = new RandomAccessFile(path,"r");
			byte[] bytes = new byte[5];
			accessFile1.seek(position);
			int hasRead = 0;
			while((hasRead = accessFile1.read(bytes)) != -1){
				str += new String(bytes,0,hasRead);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return str;
	}

	// 随机写入
	public static void write(String path,int position ,String content){
		RandomAccessFile accessFile2 = null;
		try {
			accessFile2 = new RandomAccessFile(path, "rw");
			accessFile2.seek(position); //从当前位置开始覆盖
			//accessFile2.seek(accessFile2.length()); //追加
			accessFile2.write(content.getBytes());
			accessFile2.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	//随机插入
	public static void insert(String path,int position,String content){
		try {
			RandomAccessFile accessFile = new RandomAccessFile(path, "rw");
			accessFile.seek(position);
			String str = read(path, position);
			System.out.println(str);

			accessFile.write(content.getBytes());
			position += content.length();
			System.out.println(position);

			accessFile.seek(position); // 重新定位
			accessFile.write(str.getBytes());
			accessFile.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		String path = "E:/test/random.txt";
		System.out.println(read(path,0));
		insert(path,1,"womo");
	}
}

设计模式

装饰器模式

在不改变原类的情况下,动态拓展一个对象的功能。通过创建一个包装对象,月就是装饰来包装真实的对象。

在这里插入图片描述
Component:统一接口,也是装饰类和被装饰类的基本类型。
ConcreteComponent:具体实现类(被装饰类),本身是具有一些完整功能的类。
Decorator:装饰类,实现了Component接口的同时还在内部维护了一个ConcreteComponent的实例,并可以通过构造函数初始化。
ConcreteDecorator:具体装饰类。可以通过构造器声明装饰哪种类型的ConcreteComponent。

注意:装饰器类可以有很多变种
被装饰的统一的接口,可以是接口,也可以是抽象父类。
装饰器类Decorator可以不需要。

Demo

被装饰类统一接口

public interface Component {
	public void method();
}

被装饰类

public class ConcreteComponent implements Component {
	@Override
	public void method() {
		System.out.println("原来的方法!");
	}
}

装饰类:维护了Component属性,实际上可以是不同的Component实现类。

public abstract class Decorator implements Component {
	protected Component component;
	public Decorator(Component component){
		super();
		this.component = component;
	}
	@Override
	public void method(){
		component.method();
	}
}

具体装饰类A:将Component传到父类,再通过调用父类的method方法实现调用具体被装饰类的method

public class ConcreteDecoratorA extends Decorator {
	public ConcreteDecoratorA(Component component) {
		super(component);
	}
	public void methodA(){
		System.out.println("装饰器A独有的方法!");
	}
	@Override
	public void method(){
		System.out.println("针对原方法加一层A包装!");
		super.method();
		System.out.println("A包装结束");
	}
}

具体装饰类B:同A

public class ConcreteDecoratorB extends Decorator {
	public ConcreteDecoratorB(Component component) {
		super(component);
	}

	public void methodB(){
		System.out.println("装饰器B独有的方法!");
	}

	@Override
	public void method(){
		System.out.println("针对原方法加一层B包装!");
		super.method();
		System.out.println("B包装结束");
	}
}

测试类

public class DecoratorTest {
	public static void main(String[] args) {
		//先创建具体被装饰类对象
		Component component = new ConcreteComponent();
		component.method();
		System.out.println("---------------------------------");
		ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);
		concreteDecoratorA.method();
		concreteDecoratorA.methodA();
		System.out.println("---------------------------------");
		ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);
		concreteDecoratorB.method();
		concreteDecoratorB.methodB();
		System.out.println("---------------------------------");
		//装饰类B也可以装饰装饰类
		ConcreteDecoratorB concreteDecoratorB1 = new ConcreteDecoratorB(new ConcreteDecoratorA(component));
		concreteDecoratorB1.method();
	}
}

结果
在这里插入图片描述

在IO中使用案例

在这里插入图片描述
如图所示:
InputStream是统一接口。
FileInputStream是待装饰的原始类。
FilterInputStream是装饰器父类,其下有很多具体装饰器类,如BufferedInputStream。
Reader那边也基本类似。

public class test {
	public static void main(String[] args) throws IOException {
		/**
		 * BufferedInputStream是一个具体的装饰类
		 * 其定义如下
		 * BufferedInputStream extends FilterInputStream
		 *
		 * FilterInputStream是装饰类 继承自InputStream,内部存在一个InputStream实例
		 *
		 * BufferedInputStream提供了特有的mark和reset方法。
		 */
		String path = "E:/test/123.txt";
		InputStream inputStream = new FileInputStream(path);
		BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
		bufferedInputStream.mark(0);
		bufferedInputStream.reset();
	}
}

适配器模式

Demo

定义和示例请看:https://blog.csdn.net/hrnne/article/details/106623148

在IO中的使用案例

在这里插入图片描述
IO最终都以字节流来存储,那么在字符输入流中,就是把字节流转换为字符流的过程。

在此过程中:
目标角色是Reader

public abstract class Reader implements Readable, Closeable
int read(char cbuf[])

源角色是InputStream

public abstract class InputStream implements Closeable 
int read(byte b[])

适配器就是InputStreamReader(转换流)

// 对象适配器,继承了目标角色(字符流),重写了目标角色(Read)的read方法,其中真正调用的是源角色(字节流)的方法
public class InputStreamReader extends Reader {
	//解码类,本应该维护InputStream(源角色)的对象,再由源角色调用其中的方法,但是还多出一步解码
    private final StreamDecoder sd;
	//传入字节流
    public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }
    
     public int read(char cbuf[], int offset, int length) throws IOException {
        return sd.read(cbuf, offset, length); //真正调用的是源角色(字节流)的方法
    }
}

装饰器模式和适配器模式的异同点

装饰器与适配器都有一个别名叫做包装模式(Wrapper),看似都是起到包装一个类或对象的作用,但是他们的目的并不一样。
适配器模式:将一个借口转变为另一个接口,它的目的是通过改变接口来达到重复使用的目的。
装饰器模式:不改变北装饰对象的接口,而要保留原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式二提升性能。

Stream

Java8中有两个非常有名的改进,一个是Lambda表达式,一个是Stream。如果我们了解过函数式编程的话,都知道Stream真正把函数式编程的风格引入到了java中。

Stream是一个流,在Java.util.Stream包路径下,他的主要作用就是对集合数据进行查找过滤等操作。通俗解释就是一种高效且易用的数据处理方式。

Stream和Collection的区别:
Collection:负责存储数据,不对数据做其他处理,主要和内存进行交互。
Stream:负责计算数据,主要和CPU进行交互。

例如:有一个集合Student,我们要找出所有年龄大于18岁的所有学生。此时我们就可以直接使用Stream来筛选。当然了这只是给出了其中一个例子,Stream还有很多其他的功能。

Stream操作步骤

Stream执行流程很简单,主要有三个:
1、创建一个Stream:从一个数据源,如集合、数组中获取流
2、使用Stream操作数据:一个操作的中间链,对数据源的数据进行操作
3、终止Stream:一个终止操作,执行中间操作链,并产生结果

注意:对流的操作完成后需要进行关闭操作

数据准备

创建一个Student类

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private Double score;
// 省略构造器、get和set方法
}

创建一个StudentData类用于获取数据

public class StudentData {
    public static List<Student> getStudents(){
        ArrayList <Student> students = new ArrayList <>();
        students.add(new Student(1,"小白",23,89.5));
        students.add(new Student(2,"小蔡",22,90.9));
        students.add(new Student(3,"小惠",22,87.1));
        students.add(new Student(4,"小韩",25, 89.6));
        return students;
    }
}

创建Stream

方式一:通过集合创建Stream

public static Stream<Student> createStream1(){
	List<Student> students = StudentData.getStudents();
	//第一种:返回一个顺序流
	Stream<Student> stream = students.stream();
	//第二种:返回一个并行流
	Stream<Student> studentStream = students.parallelStream();
	return stream;
}

方式二:通过一个数组创建stream

public static void createStream2(){
	//获取一个整型stream
	int[] arr = {1,2,3,4,5,6};
	IntStream stream = Arrays.stream(arr);
}

方式三:通过Stream.of

public static void createStream3(){
	Stream<String> stringStream = Stream.of("hello","java","world");
	Stream<Student> studentStream = Stream.of(new Student(1,"小白",23,89.5),
			new Student(2,"小蔡",22,90.9),
			new Student(3,"小惠",22,87.1),
			new Student(4,"小韩",25,89.6));
}

方式四:创建一个无限流

public static void createStream4(){
	//每隔5个数取一个,从0开始,此时就会无限循环
	Stream<Integer> iterate = Stream.iterate(0, t -> t + 5);
	//取出一个随机数
	Stream.generate(Math::random);
}

Stream操作数据

筛选和切片

  • filter:接收Lambda,从流中排除某些操作。
  • limit:截断流,使其元素不超过给定对象。
  • skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补。
  • distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。所以需要在Student类中重写hashCode()和equals()方法。
public static void operator1(){
	List<Student> students = StudentData.getStudents();
	Stream<Student> stream = students.stream();
	//过滤:过滤出所有年龄大于22岁的同学
	stream.filter(item->item.getAge() > 22).forEach(System.out::println);
	//截断流:筛选出前3条
	stream.limit(3).forEach(System.out::println);
	//跳过元素:跳过前2个元素
    stream.skip(2).forEach(System.out::println);
    //过滤重复元素
	stream.distinct().forEach(System.out::println);
}

映射

  • map:接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
public static void operator2(){
	//将每一个小写字母的都转化为大写字母映射
	Stream<String> stringStream = Stream.of("hello","java","world");
	stringStream.map(str->str.toUpperCase()).forEach(System.out::println);

	//筛选出所有的年龄,再过滤所有大于23的年龄有哪些
	Stream<Student> studentStream = Stream.of(new Student(1,"小白",23,89.5),
			new Student(2,"小蔡",22,90.9),
			new Student(3,"小惠",22,87.1),
			new Student(4,"小韩",25,89.6));
	//将流中的每一值转化为另一个值
	Stream<Integer> ageStream = studentStream.map(Student::getAge);
	ageStream.filter(age-> age>22).forEach(System.out::println);
}

排序

  • sorted():自然排序(Comparable)
  • sorted(Comparator com):定制排序(Comparator)
public static void operator3(){
	int[] arr = {1,4,3,7,2,6,9,0};
	IntStream stream = Arrays.stream(arr);
	//自然排序
	stream.sorted().forEach(System.out::println);

	//对象排序,对象排序可以先实现comparable接口或者直接指定
	List<Student> students = StudentData.getStudents();
	Stream<Student> studentStream = students.stream();
	//需先实现Comparable接口
	studentStream.sorted().forEach(System.out::println);
	//直接指定比较器
	studentStream.sorted((e1,e2)->(int)(e1.getAge()-e2.getAge())).forEach(System.out::println);
}

终止Stream

匹配和查找

  • allMatch:检查是否匹配所有元素。
  • anyMatch:检查是否至少匹配一个元素。
  • noneMatch:检查是否没有匹配所有元素。
  • findFirst:返回第一个元素。
  • findAny:返回当前流中的任意元素。
  • count:返回流中元素的总个数。
  • max:返回流中最大值。
  • min:返回流中最小值。
public static void operator4(){
    List <Student> list = StudentData.getStudents();
    //判断所有的学生年龄是否都大于20岁
    boolean allMatch = list.stream().allMatch(student -> student.getAge() > 20);
    //判断是否存在学生的年龄大于20岁
    boolean anyMatch = list.stream().anyMatch(student -> student.getAge() > 20);
    //判断是否不存在学生叫小白
    boolean noneMatch = list.stream().noneMatch(student -> student.getName().equals("小白"));
    //查找第一个学生
    Optional <Student> first = list.stream().findFirst();
    //查找当前流中的元素
    Optional <Student> any = list.stream().findAny();
    //查找所有的学生数量
    long count = list.stream().count();
    //查找成绩高于90分的同学数量
    long count1 = list.stream().filter(student -> student.getScore() > 90).count();
    //查找学生的最高分数
    Stream <Double> doubleStream = list.stream().map(student -> student.getScore());
    Optional <Double> max = doubleStream.max(Double::compareTo);
}

归约

  • reduce:归约操作可以将流中元素反复结合起来,得到一个值。
public static void operator5(){
	int[] arr = {4,7,1,5,9,3,1};
	IntStream stream = Arrays.stream(arr);
	//计算数的总和
	System.out.println(stream.reduce(Integer::sum));

	List<Student> students = StudentData.getStudents();
	Stream<Student> stream1 = students.stream();
	//计算学生总分
	Stream<Double> doubleStream = stream1.map(Student::getScore);
	System.out.println(doubleStream.reduce(Double::sum));
}

收集

  • collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法。
public static void operator6() {
    //返回一个list
    List <Student> students = StudentData.getStudents();
    List <Student> list = students.stream()
            .filter(student -> student.getScore() > 88)
            .collect(Collectors.toList());

    //返回一个set
    Set <Student> set = students.stream()
            .filter(s -> s.getAge() > 23)
            .collect(Collectors.toSet());
}

总结

stream基本的语法就是这样,Stream就像一个工具,可以帮我们分析处理数据,极其的好用,但是目前还不知道其效率如何。根据网上一位大佬的内存时间分析,其实在数据量比较庞大的时候,Stream可以为我们节省大量的时间,数据量小的时候并不明显。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值