java编程基础总结——27.IO流

一、IO流概念
    

1. 什么是IO流:

input output stream:输入输出流

计算数据是在cpu,有cpu两大功能:运算器和控制器。但是cpu不能存储数据,数据存在存储设备,如磁盘、硬盘、U盘、光盘,才能永久存储数据,需要将数据计算之后由cpu运算到磁盘上,会用到IO流;同样的,要计算某个数据,需要将数据先传到cpu中,也要用到IO流

1)、从狭义上来说:数据在内存中输入和输出     

        将数据提前缓存,加快程序的运行

        代码包括操作系统,普通应用层软件,jvm,都是在在内存中运行

2)、从广义来说,不同电脑之间的数据流动,(借助网络设备)也是一种IO流
        
狭义上的IO流:本地进程间的数据流动 (同一个台电脑,不同元器件之间的流动)
广义上的IO流:远程进程间的数据流动 (不同电脑)

    
2. IO流的分类:

1)、流的流动方法(数据的传输方向)站在内存的角度
        |-- 输入流              如将数据从磁盘读到内存
        |-- 输出流              如将计算好的数据存到磁盘上(内存读到磁盘上)

2)、流的数据格式
        |-- 字节流            (二进制形式) 所有都能用
        |-- 字符流            (二进制效率差,比如一句话,按照字节去翻译就很长)注意:不是所有的                                       流都能用字符流,如一张图片,字符串适用于肉眼能看到的符号

3)、流的主要作用


        |-- 节点流
        |-- 装饰流(过滤流)装饰节点流
    

4)、转换流         

     转换字节和字符流的特殊流

二、字节流

字节流:
    |-- InputStream           输入流
    |-- OutputStream        输出流

字符流:
    |-- Reader                   输入流
    |-- Writer                     输出流

字节流
    装饰流
    数据流
    字符流
    对象流
    对象序列化
    转换流

    

字节流:

    输入流:

    输出流:

示例:

存在bug,读整个字符数组不合适,应该给多少位读多少位。不能保证刚好就是1024,会出现多读的情况

对比原代码,程序本来到红色的部分已经结束了,但是控制台却多输出了很多其他的代码(绿色部分及之后未展示出来的结果)显然不对.读满了会重复去读

@Test
	void testIStream01() {
		InputStream is = null;
		try {
			is = new FileInputStream(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java"));
			//read()方法是一次读尽数据,若文件很大,内存是有限的,所以不建议
			//所以做一个字节数组
			//也可以是1024*4,文件存在磁盘上,是以页存储,一个页是1024*4
			//返回int值,-1表示读到末尾
			byte[] buf = new byte[1024];
			while(is.read(buf) != -1) {
				System.out.write(buf);
				//println是输出字符串,不能用,操作不了,这里是字节数组
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 关闭流
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

我们将读取的字节数记录下来,只要没读到末尾,一直循环,从0读到记录的位置,这样就可以避免上述情况的发生

@Test
	void testIStream02() {
		InputStream is = null;
		try {
			is = new FileInputStream(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java"));
			byte[] buf = new byte[1024];
			int len = 0;//记录读的字节数
			while((len = is.read(buf)) != -1) {
				System.out.write(buf, 0, len);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 关闭流
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

1. InputStream    OutputStream的子类

1)、FileInputStream 字节输入流

@Test
	void test01() {
		InputStream is = null;
		try {
			// 1、创建一个输入流对象(XXX数据流)
			is = new FileInputStream("a.txt");
			
			// 创建字节数组,通过字节数组读取数据
			byte[] buf = new byte[1024];
			int len = 0;
			while ((len = is.read(buf)) != -1) {
				System.out.write(buf, 0, len);//从buf中读,从第0个读len长度
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 一定要注意:流必须关闭
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

 若是带一次的代码,不用len记录,直接读。会导致字节数组读不满,打出空格(byte[] 默认初始化为0)

@Test
	void test01() {
		InputStream is = null;
		try {
			
			// 1、创建一个输入流对象(XXX数据流)
			is = new FileInputStream("a.txt");
			
			// 创建字节数组,通过字节数组读取数据
			byte[] buf = new byte[1024];
			
			while ((len = is.read(buf)) != -1) {
				System.out.write(buf);//从buf中读,从第0个读len长度
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 一定要注意:流必须关闭
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

  

读字符串时,可以用System.out.println()。(借助字符串对象,将字节数组构造为字符串对象)

@Test
	void test02() {
		InputStream is = null;
		try {
			// 1、创建一个输入流对象(XXX数据流)
			is = new FileInputStream("a.txt");
			
			// 创建字节数组,通过字节数组读取数据
			byte[] buf = new byte[1024];
			int len = 0;
			while ((len = is.read(buf)) != -1) {
				// 借助字符串对象,将字节数组构造为字符串对象
				String str = new String(buf, 0, len);
				System.out.println(str);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 一定要注意:流必须关闭
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

2)、FileOutputStream  字节输出流

@Test
	void test03() {
		String msg = "你好啊";
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(new File("b.txt"));
			// 直接将数据一次写入进去(只适用于比较少,下面的例子时拷贝文件,则必须定义字节数组了)
			fos.write(msg.getBytes());
			System.out.println("写入数据成功!!");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

 3)综合

字节输入流,字节输出流(以文件输入流,文件输出流为例)来拷贝文件

@Test
	void test04() {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try {
			// 使用输入流读取数据
			fis = new FileInputStream("D:\\javaSE\\note\\第23天笔记.txt");
			fos = new FileOutputStream(new File("c:\\a.txt"));
			
			// 一定要通过字节数组进行读取和写入
			// 防止内存过大,导致出现OOM
			byte[] buf = new byte[1024];
			int len = 0;
			while((len = fis.read(buf)) != -1) {
				fos.write(buf, 0, len);
			}
			System.out.println("写入数据成功!!");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

2. 装饰流:

又被称为过滤流,这种流不能直接使用,主要的作用就是用来装饰节点流(各种可以直接使用流)。如果节点流读取数据比较慢,使用装饰流做缓冲
 


    FilterInputStream是个普通类,但是一般使用它的子类,BufferedInputStream

装饰流,本质就是一种装饰者设计模式的体现。对原有对象的功能进一步的装饰增强。

GOF 23中设计模式:
    |-- 单例设计模式(对象不需要多个)节约内存,避免频繁创建对象。加快效率
    |-- 装饰者设计

1)、BufferedInputStream 

案列:

@Test
	void test05() {
		BufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		try {
			//装饰流装饰节点流,所以需要传一个节点流
			bis = new BufferedInputStream(new FileInputStream("G:\\windows 系统\\CentOS-7-x86_64-DVD-1810.iso"));
			bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\a.iso")));
			
			byte[] buf = new byte[1024*8];
			int len = 0;
			
			while ((len = bis.read(buf)) != -1) {
				bos.write(buf, 0, len);
			}
			System.out.println("文件拷贝成功!!");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

如果不关闭流:

文件少了,且拷贝的图片下面部分是灰色的

满了才会将缓冲区的数据刷进去,但是最后一次不一定满,可能把之前的数据拿过来,数据出问题

不满数字是0,0在颜色里面表示为黑色

@Test
	void test06() {
		BufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		try {
			bis = new BufferedInputStream(new FileInputStream("c:\\a.jpg"));
			bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\b.jpg")));
			
			byte[] buf = new byte[1024];
			int len = 0;
			
			while ((len = bis.read(buf)) != -1) {
				bos.write(buf, 0, len);
			}
			System.out.println("文件拷贝成功!!");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
		}
	}

如果不关闭,我们可以利用flush()方法,强制刷新,将缓冲池的东西清到目的地

但是不建议直接手动刷新,建议关闭流。

缓存流,必须要关闭,因为JVM在缓存流关闭时,将最后数据完成自动刷新

节点流也需要关闭,因为操作的是一个具体的文件,你一旦占用不关闭,别人就操作不了。比如打开一个文件,此时删除这个文件是删除不掉的

 

@Test
	void test06() {
		BufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		try {
			bis = new BufferedInputStream(new FileInputStream("c:\\a.jpg"));
			bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\b.jpg")));
			
			byte[] buf = new byte[1024];
			int len = 0;
			
			while ((len = bis.read(buf)) != -1) {
				bos.write(buf, 0, len);
			}
			
			// 当代码执行到这儿的时候,读取完成
			// 如果最后一次数据没有读取完成,则可以手动刷新缓冲区
//			bos.flush();
			
			System.out.println("文件拷贝成功!!");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 缓存流,必须要关闭,因为JVM在缓存流关闭时,将最后数据完成自动刷新
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

2)、DataOutputStream   DataInputStream 

数据流

特殊的装饰流

引入:存入数字

@Test
	void test07() throws Exception {
		FileOutputStream fis = new FileOutputStream("a.dat");
		int msg = 100;
		//fis.write(int b),这个方法是记录读了多少次,并不能存int数据

//		String str = Integer.valueOf(msg).toString();先转成包装类,再转成字符串
//		fis.write(str.getBytes());利用之前示例过的方法,转成字节数组
		// 也可以这样完成转换
		fis.write((msg + "").getBytes());
		fis.close();
	}

但是这样存储也有问题,比如将msg的值放大

我们知道int只占4个字节,但是转成字符串就是一个符号占一个字节,所以就是7个字节了

我们可以用DataOutputStream来解决这个问题

数据流的使用:
        我们之前讲解的流,如果要保存数字,只要将数字转换为字符串,也就是说
        以字符流的形式保存数据,这样有时候并不是我们需要的,我们有时候就是
        需要以字节保存数据,因此就可以使用数据流来包装文件流完成。

int msg = 1000000;

 

@Test
	void test08() throws Exception {
		int msg = 1000000;
		
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("a.dat"));
		dos.writeInt(msg);
		dos.writeInt(100);
		dos.writeInt(10000);
		dos.writeInt(123456);
		dos.close();
	}

 DataInputStream :

读int值:

@Test
	void test09() throws Exception {
		DataInputStream dis = new DataInputStream(new FileInputStream("a.dat"));
		System.out.println(dis.readInt());
		System.out.println(dis.readInt());
		System.out.println(dis.readInt());
		System.out.println(dis.readInt());
		dis.close();
	}

如果再读会抛错

 读long值:

@Test
	void test10() throws Exception {
		long msg = 1000000000L;
		
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("b.dat"));
		dos.writeLong(msg);
		dos.close();
	}

用int读long 数据会返回0

@Test
	void test11() throws Exception {
		DataInputStream dis = new DataInputStream(new FileInputStream("b.dat"));
		System.out.println(dis.readInt());
		dis.close();
	}

 用long读long 结果正常

@Test
	void test11() throws Exception {
		DataInputStream dis = new DataInputStream(new FileInputStream("b.dat"));
		System.out.println(dis.readLong());
		dis.close();
	}

三、字符流


字符流:
    计算机底层使用的二进制数据(字节数据)。所以计算机中都可以用字节去操作,字节流是计        算机最核心的流,但是速度慢。在开发过程中,若是字符串,就非常慢
    字符流是为了加快流的操作,而设计的专门用来操作字符串的一种流。

    注意:字符流 字符串存在编码问题,需要保证在读取和写入时,保持一致!!!(在Ascll码中                 的不影响)

                字节流无编码问题(二进制)

    
字符输入流:
        Reader(抽象类),使用的时候应该使用它的子类

字符串输出流:
        Writer(抽象类)

字符装饰流:
    BufferedReader        是Reader的子类
    BufferedWriter           是Writer的子类
    PrintWriter    更推荐使用这个打印输出流

字符输入流例:

@Test
	void test01() {
		Reader reader = null;
		
		try {
			reader = new FileReader(new File("D:\javaSE\eclipse\code\classDemo\src\com\openlab\demo02\TestFile.java"));
			
			char[] buf = new char[1024];
			int len = 0;
			while((len = reader.read(buf)) != -1) {
				System.out.println(String.valueOf(buf, 0, len));
				//valueOf支持字符数组,不支持字节数组
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

字符输出流例:

@Test
	void test02() {
		Reader reader = null;
		Writer writer = null; 
		
		try {
			reader = new FileReader(new File("D:\javaSE\eclipse\code\classDemo\src\com\openlab\demo02\TestFile.java"));
			writer = new FileWriter("c:\\a.java");
			
			char[] buf = new char[1024];
			int len = 0;
			while((len = reader.read(buf)) != -1) {
				writer.write(buf, 0, len);
			}
			System.out.println("拷贝文件成功");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

 BufferedReader :

@Test
	void test03() {
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
			String msg = null;
			// 字符输入流,可以使用装饰流,按行读取
			while ((msg = br.readLine()) != null) {
				System.out.println(msg);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

BufferedWriter:

@Test
	void test04() {
		BufferedReader br = null;
		BufferedWriter bw = null;
		try {
			br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
			bw = new BufferedWriter(new FileWriter("d:\\a.java"));
			String msg = null;
			// 字符输入流,可以使用装饰流,按行读取
			while ((msg = br.readLine()) != null) {
//				bw.write(msg);
				// 我们可以手动添加换行符
				bw.write(msg + "\n");
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

PrintWriter:

@Test
	void test05() {
		BufferedReader br = null;
		PrintWriter out = null;
		try {
			br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
			out = new PrintWriter("d:\\a.java");
			String msg = null;
			// 字符输入流,可以使用装饰流,按行读取
			while ((msg = br.readLine()) != null) {
				out.println(msg);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (out != null) {
				out.close();
			}
		}
	}

总结:
    |-- 字符缓存流完成字符的操作
        通过行来读取和写入字符串,所以字符流建议带上装饰流(能按行读取)
        |-- 字符串输入缓存流
            BufferedReader
        |-- 字符串输出缓存流
            BufferedWriter
            不太推荐大家使用BufferedWriter,因为需要手动添加换行符(按行读取,readLine找到\n              就解析了,能把数据写进去,但是最终结果是不会换行)
            推荐使用打印输出流(节点流)
            PrintWriter
                |-- print()
                |-- println()
    

四、转换流:

将字节流转换为字符流操作

读取的是字节数据,一般是文本数据(字符串数据)为了加快效率,可以转成字符流

如控制台的标准输入流是字节流,直接转流类型不匹配。BufferedReader只能接受Reader

 

    InputStreamReader        Reader的子类
    OuputStreamWriter        Writer的子类

 InputStreamReader       可以做一个简单人工智能

@Test
	void test01() throws IOException {
		BufferedReader br = null;
		
		// InputStreamReader 转换流
		// 可以将字节输入流转换为字符输入流
		br = new BufferedReader(new InputStreamReader(System.in));
		
		String str = null;
		while ((str = br.readLine()) != null) {
			if (str.equalsIgnoreCase("exit"))
				break;
			System.out.println(str.replace("吗?", ""));
		}
		br.close();
	}

 

@Test
	void test02() throws IOException {
		BufferedReader br = null;
		PrintWriter out = null;
		
		br = new BufferedReader(new InputStreamReader(System.in));
		out = new PrintWriter(new OutputStreamWriter(new FileOutputStream("d:\\a.html")));
		
		String str = null;
		while ((str = br.readLine()) != null) {
			if (str.equalsIgnoreCase("exit"))
				break;
			out.println(str);
		}
		br.close();
		out.close();
	}

五、对象流

对象流:
    对象本质:它是一个抽象概念,是JVM中的一种虚拟出来的抽象概念
                       在jvm中,通过new 关键字,配合class类,就可以构造一个java对象

    
    对象流就是java提供一种,可以将java对象这种虚拟的概念转换为
    一种物理可以存储或者传输的真实数据。

    将虚拟的JVM中的对象转换为字节数据

String实现了Serializable接口,可以直接用

@Test
	void test01() {
		String msg = "你好";
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("c:\\info.dat"));
			
			oos.writeObject(msg);
			System.out.println("对象保存成功");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (oos != null) {
				try {
					oos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
@Test
	void test02() {
		ObjectInputStream ois = null;
		try {
			ois = new ObjectInputStream(new FileInputStream("c:\\info.dat"));
			
//			Object msg = ois.readObject();不强转
			String msg = (String) ois.readObject();//刚刚存的字符串,知道数据类型,所以直接强转了
			System.out.println(msg);
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			if (ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

对象序列化: 

@Test
	void test03() {
		Person p1 = new Person(1, "张三", "法外狂徒", "男", 30);
		
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("c:\\info.dat"));
			oos.writeObject(p1);
			System.out.println("对象保存成功");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (oos != null) {
				try {
					oos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

 对象反序列化:

@Test
	void test04() {
		
		ObjectInputStream ois = null;
		try {
			ois = new ObjectInputStream(new FileInputStream("c:\\info.dat"));
			
			Person p1 = (Person) ois.readObject();
			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();
				}
			}
		}
	}

 

 

1. 对象序列化:

Serialize:将虚拟对象转换为一种可以直接传输或者保存到数据(字节、字符)过程

2. 对象反序列化:

将序列化后的字节或者字符数据重新转换为对象,对象反序列化

3. 对象持久化:

将数据永久保存(可以通过IO、数据库)

java官方提供的序列化,是将java对象转换为字节数据。
注意:java的对象要实现序列化和反序列化,必须实现Serializable(这个标记接口)

        
4. transient关键字

 transient:被这个关键字修饰的属性,无法被持久化。

    运算过程中临时使用的,不需要记录值

例:

将Person的gender用transient关键字修饰,之后重新将数据序列号、持久化(Test03)。

之后反持久化,反序列化(Test04)

private transient String gender;

 

在持久化的时候没有将gender持久化,所以读不到,赋了默认值(String是对象,默认值为null)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值