黑马程序员--java基础复习之File类
2015-04-05 12:02
537 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
File类
1、用来将文件或者文件夹封装成对象
2、方便对文件与文件夹的属性信息进行操作
3、File对象可以作为参数传递给流的构造函数
4、了解File类中的常用方法
注意第一条,是将文件或文件夹封装成对象,这里包括了文件夹。从字面意思来看File不就是文件么,所以很容易形成误会,会将文件夹忽略掉
创建File对象的几种常见方式:
在上面的代码中,可以看到f4中有 File.separator
来看看File类中的常用方法
File常见基本方法
1、创建
boolean createNewFile();在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输
出流对象一建立就创建文件,而且若文件已存在则覆盖。
boolean mkdir()
创建目录
boolean mkdirs() 创建多级文件夹
2、删除
boolean delete() :删除失败返回false
boolean deleteOnExit():在程序退出时删除指定文件
3、判断
boolean canExecute()
是否可执行。
boolean exists() 是否存在
boolean isDirectory() 是否是目录
boolean isFile()
是否是文件
boolean isHidden() 是否是隐藏文件
boolean isAbsolute();
是否是绝对路径
4、获取信息
String getName()
getPath():获取路径
getParent():获取父目录
getAbsolutePath():获取绝对路径
getAbsoluteFile():获取绝对路径,并将此绝对路径封装成对象,可操作
lastModified() 上次修改时间
length()
文件大小
renameTo()
重命名
看上面这些方法的应用:
File类中的常用方法二
File[] listRoots() 列出可用的文件系统根
String[] list()
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录,获取指定目录下的所有文件或文件夹(包括隐藏文件)
String[] list(FilenameFilter filter)
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。
File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
代码:
下面介绍一个比较重要的表现方式或者编程手法:递归
练习:列出指定目录下文件或者文件夹,包含子目录中的内容,也就是列出指定目录下的所有内容。
因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身,这种表现形式,或者编程手法,称为递归
看下面的代码中的递归的应用:
结果:
下面看一个将判断,递归、删除结合在一起的例子:
上面演示了删除文件夹,删除文件夹前要删除文件夹中的文件,然后再删除文件夹。下面这个例子是将File和IO流结合起来的一个例子
结果:
Properties类
Properties是hashTable的子类。也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串,是集合中和IO技术相结合的集合容器。
该对象的特点:可以用于键值对形式的配置文件。
那么在加载数据时,需要数据有固定格式:键=值
String getProperty(String key) 用指定的键在此属性列表中搜索属性。
Object setProperty(String key, String value) 设置 调用 Hashtable 的方法 put。
Set<String> stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键
通过代码来看这些方法的应用:
下面再来看看Properties类中的另两个重要的方法load和store的应用:
其中,load方法是将流中的数据加载到Properties类的对象中。然后通过Properties 对象对流中的数据进行getProperty(),setProperty()等操作,最后,通过store方法将操作后的流中的数据保存到流所关联的文件中。
再看下面一个小应用
结果:
IO包中的其他对象
一、打印流:PrintWriter和PrintStream
打印流
该流提供了打印方法,可以将各种数据类型的数据都原样打印
字节打印流和字符打印流。
字节打印流:PrintStream
构造函数可以接收的参数类型:
1、file对象 File
2、字符串路径 String
3、字节输出流 OutputStream
字符打印流:PrintWriter
构造函数可以接收的参数类型:
1、file对象 File
2、字符串路径 String
3、字节输出流 OutputStream
4、字符输出流 Writer
代码:
当我们不想使用flush刷新时,可以使用PrintWriter的重截方法 PrintWriter(OutputStream out, boolean autoFlush) ,当参数autoFlush的值为true时,会自动刷新
二、序列流
SequenceInputStream 将多个流合并(将多个文件合并成一个文件)
SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,
直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
来看实现代码:
结果:
代码中通过SequenceInputStream将三个FileInput流合并,并生成一个新文件。
小知识:切割文件
其原理是在读取文件时,定义一个一定长度的byte数组,将数据存储到数据中,然后通过输出流写入文件。依次类推,每个文件的大小即为数组的长度。到直读取完文件。
实现代码:
结果:
File类
1、用来将文件或者文件夹封装成对象
2、方便对文件与文件夹的属性信息进行操作
3、File对象可以作为参数传递给流的构造函数
4、了解File类中的常用方法
注意第一条,是将文件或文件夹封装成对象,这里包括了文件夹。从字面意思来看File不就是文件么,所以很容易形成误会,会将文件夹忽略掉
创建File对象的几种常见方式:
//创建 File对象 public static void consMethod() { //将a.txt封装成File对象。可以将已有的和未出现的文件或文件夹封装成对象 File f1=new File("a.txt"); //第二种方式 File f2=new File("c:\\abc","a.txt"); //第三种方式 File d=new File("c:\\"); File f3=new File(d,"b.txt"); //File f4=new File("c:\\aaa\\bbb\\ccc\\d.txt"); File f4=new File("c:"+File.separator+"aaa"+File.separator+"bbb"+File.separator+"ccc"+File.separator+"d.txt"); sop("f1:"+f1); sop("f2:"+f2); sop("f3:"+f3); }
在上面的代码中,可以看到f4中有 File.separator
separator与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。代表的是目录分隔符。与系统有关的默认名称分隔符?从这里可以看出,这个分隔符是在各个系统中都是通用的。
来看看File类中的常用方法
File常见基本方法
1、创建
boolean createNewFile();在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输
出流对象一建立就创建文件,而且若文件已存在则覆盖。
boolean mkdir()
创建目录
boolean mkdirs() 创建多级文件夹
2、删除
boolean delete() :删除失败返回false
boolean deleteOnExit():在程序退出时删除指定文件
3、判断
boolean canExecute()
是否可执行。
boolean exists() 是否存在
boolean isDirectory() 是否是目录
boolean isFile()
是否是文件
boolean isHidden() 是否是隐藏文件
boolean isAbsolute();
是否是绝对路径
4、获取信息
String getName()
getPath():获取路径
getParent():获取父目录
getAbsolutePath():获取绝对路径
getAbsoluteFile():获取绝对路径,并将此绝对路径封装成对象,可操作
lastModified() 上次修改时间
length()
文件大小
renameTo()
重命名
看上面这些方法的应用:
import java.io.*; class FileDemo1 { public static void main(String[] args) throws IOException,InterruptedException { method_5(); } public static void method_5() { File f1=new File("file.txt"); File f2=new File("text.java"); //重命名,将f1改为f2 sop("renameTo:"+f1.renameTo(f2)); } public static void method_4() { File f=new File("tt.txt"); //文件未创建 sop("path:"+f.getPath()); //获取路径 sop("AbsoluPath:"+f.getAbsolutePath());//获取绝对路径 //虽然文件不存在,但是能获取到路径和绝对路径 //该方法返回的是绝对路径中的父目录 //如果是获取的是相对目录,则返回为null //如果相对路径中有上一层上当,那么该目录就是返回结果 sop("parent:"+f.getParent()); } public static void method_3() { File f=new File("tt.txt"); //记住,在判断文件对象是否是文件或者文件夹时,必须要先该文件对象封装的内容是否存在 //通过exists判断 sop("dir:"+f.isDirectory());//结果为false 因为f这个文件没有创建 sop("file:"+f.isFile());//结果为false 因为f这个文件没有创建 sop(f.isAbsolute());//是否是绝对路径(即使文件不存在也能判断) } public static void method_2() { File f=new File("file.txt"); sop(f.canExecute());//是否可执行 //是否存在 sop(f.exists()); } public static void method_1()throws IOException,InterruptedException { File f=new File("file.txt"); //程序退出时删除 f.deleteOnExit(); //新建 sop(f.createNewFile()); //Thread.sleep(5000); //删除 //sop("delete:"+f.delete()); //创建目录(只能创建一级目录) File dir=new File("abc"); sop("mkdir:"+dir.mkdir()); //创建多级目录 File dirs=new File("aaa\\bb\\cc\\dd"); sop("mkdirs:"+dirs.mkdirs()); } public static void sop(Object obj) { System.out.println(obj); } }
File类中的常用方法二
File[] listRoots() 列出可用的文件系统根
String[] list()
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录,获取指定目录下的所有文件或文件夹(包括隐藏文件)
String[] list(FilenameFilter filter)
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。
File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
代码:
import java.io.*; class FileDemo2 { public static void main(String[] args) { //listRootsDemo(); //listDemo(); //ListDemo2(); ListDemo3(); } public static void ListDemo3() { File dir=new File("D:\\JavaDemo\\date05"); //这里使用了匿名内部类 String[] arr=dir.list(new FilenameFilter(){ //此处的d就是上面的dir,而name是dir下的所有文件 public boolean accept(File d,String name) { return name.endsWith(".java"); } }); for(String s:arr) { sop(s); } } public static void ListDemo2() { File f=new File("D:\\java博客图片\\"); String[] filenames=f.list();//获取指定目录下的所有文件或文件夹对象 for(File f1:files) { sop(f1.getName()); } } //获取指定目录下的所有文件或文件夹,返回字符串数据 public static void listDemo() { File f=new File("c:\\"); //获取指定目录下的所有文件或文件夹(包括隐藏文件) String[] names=f.list(); //调用list方法的file对象必须是封装了一个目录,该目录必须存在 for(String s:names) { sop(s); } } //listRoots所有系统盘符 public static void listRootsDemo() { //列出当前电脑所有的盘符 File[] files=File.listRoots(); for(File f:files) { sop(f); } } public static void sop(Object obj) { System.out.println(obj); } }
下面介绍一个比较重要的表现方式或者编程手法:递归
练习:列出指定目录下文件或者文件夹,包含子目录中的内容,也就是列出指定目录下的所有内容。
因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身,这种表现形式,或者编程手法,称为递归
看下面的代码中的递归的应用:
import java.io.*; class FileDemo3 { public static void main(String[] args) { File f=new File("E:\\U盘"); getAllFils(f,0); } //递归目录下的所有文件和文件夹 public static void getAllFils(File dir,int num) { if(dir!=null) { File[] files=dir.listFiles(); sop(addFlag(num)+(num>0?("|--"+dir.getName()):dir.getName())); num++; for(File f:files ) { //若是目录 if(!f.isHidden()&&f.isDirectory()) getAllFils(f,num);//递归 else sop(addFlag(num)+"|--"+f.getName()); } } } public static String addFlag(int num) { if(num>0) { StringBuilder sb=new StringBuilder(); for(int i=0;i<num;i++) { sb.append(" "); } return sb.toString(); } return ""; } public static void sop(Object obj) { System.out.println(obj); } }
结果:
下面看一个将判断,递归、删除结合在一起的例子:
/* 删除一个带内容的目录。 删除原理: 在window中,删除目录是从里面往外删除的。 既然是从里往外删除,就需要用到递归。 **/ import java.io.*; class RemoveDir { public static void main(String[] args) { File f=new File("D:\\JavaDemo\\date20\\aaa"); removeDir(f); } public static void removeDir(File dir) { File[] files=dir.listFiles(); for(File f:files) { //若为目录 if(f.isDirectory()) { //递归 removeDir(f); } else { //若是文件,则删除 sop(dir+":File:"+f.delete()); } } sop(dir+":Dir:"+dir.delete()); } public static void sop(Object obj) { System.out.println(obj); } }
上面演示了删除文件夹,删除文件夹前要删除文件夹中的文件,然后再删除文件夹。下面这个例子是将File和IO流结合起来的一个例子
/* 练习: 将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。 建立一个java文件列表文件。 */ import java.io.*; import java.util.*; class JavaFileList { public static void main(String[] args) { //指定目录 File f=new File("D:\\JavaDemo\\date20\\aaa"); //创建一个集合,用于存储文件 List<String> list=new ArrayList<String>(); method(f,list); saveToFile(list); System.out.println(list.size()); } //获取指定目录下的所有java 文件,并将文件绝对路径存储到集合中 public static void method(File dir,List<String> list) { File[] filelist=dir.listFiles(); for(File f:filelist) { //如果是目录,则递归 if(f.isDirectory()) { method(f,list); } else { //判断是否是java文件 if(f.getName().endsWith(".java")) //将文件绝对路径添加到集合中去 list.add(f.toString()); } } } //将集合中内容写入文件中 public static void saveToFile(List<String> list) { BufferedWriter bw=null; try { //创建一个文本写入流对象 bw=new BufferedWriter(new FileWriter("javaFile.txt")); //获取集合中的元素 ListIterator<String> li=list.listIterator(); while(li.hasNext()) { String file=li.next(); //写入文本文件 bw.write(file.toString()+"\t"); bw.newLine(); bw.flush();//刷新 } } catch(IOException e) { System.out.println(e.toString()); } finally { try{ if(bw!=null) bw.close(); } catch(IOException E) { System.out.println(E.toString()); } } } }
结果:
Properties类
Properties是hashTable的子类。也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串,是集合中和IO技术相结合的集合容器。
该对象的特点:可以用于键值对形式的配置文件。
那么在加载数据时,需要数据有固定格式:键=值
String getProperty(String key) 用指定的键在此属性列表中搜索属性。
Object setProperty(String key, String value) 设置 调用 Hashtable 的方法 put。
Set<String> stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键
通过代码来看这些方法的应用:
import java.util.*; class PropertiesDemo { public static void main(String[] args) { setAndget(); } //设置和获取元素 public static void setAndget() { //创建Properties类的对象 Properties prop=new Properties(); //设置(setProperty调用 Hashtable 的方法 put。) prop.setProperty("zhangsan","23"); prop.setProperty("lisi","40"); sop(prop); //获取 String value=prop.getProperty("lisi"); sop(value); //获取(获取properties集合中的键的集合,并转换成Set集合) Set<String> names=prop.stringPropertyNames(); for(String s:names) { sop(s+"::"+prop.getProperty(s)); } } public static void sop(Object obj) { System.out.println(obj); } }
下面再来看看Properties类中的另两个重要的方法load和store的应用:
import java.io.*; import java.util.*; class PropertiesListDemo { public static void main(String[] args) { method_Load(); //myLoad(); } //Properties类中的load方法 public static void method_Load() { FileInputStream fis=null; try { fis=new FileInputStream("D:\\JavaDemo\\Date20\\PropertyList.txt"); Properties p=new Properties(); //将流中的数据加载进集合 p.load(fis); //修改属性 p.setProperty("time","2000"); FileOutputStream fos=new FileOutputStream("D:\\JavaDemo\\Date20\\PropertyList.txt"); //保存 p.store(fos,"wo shi zhushi"); //将数据写到控制台上 p.list(System.out); } catch(IOException e) { sop(e.toString()); } finally { try { if(fis!=null) fis.close(); } catch(IOException e) { sop(e.toString()); } } } //Load方法的原理 //如何将流中的数据存储到集合中 //想要将info.txt中键值数据存到集合中进行操作 /* 1、用一个流和和info.txt文件关联。 2、读取一行数据,将该行数据用“=”进行切割。 3、等号左边作为键,右边作为值。存入到Properties集合中即可 */ public static void myLoad() { BufferedReader br=null; try { br=new BufferedReader(new FileReader("PropertyList.txt")); Properties prop=new Properties(); String line=null; while((line=br.readLine())!=null) { String[] arr=line.split("="); //sop(arr[0]+"..."+arr[1]); prop.setProperty(arr[0],arr[1]); } sop(prop); } catch(IOException e) { sop(e.toString()); } finally { try { if(br!=null) br.close(); } catch(IOException e) { sop(e.toString()); } } } public static void sop(Object obj) { System.out.println(obj); } }
其中,load方法是将流中的数据加载到Properties类的对象中。然后通过Properties 对象对流中的数据进行getProperty(),setProperty()等操作,最后,通过store方法将操作后的流中的数据保存到流所关联的文件中。
再看下面一个小应用
/* 练习:用于记录应用程序运行次数。 如果使用次数已到,那么给出注册提示。 很容易想到的是计数器。 可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。 可是随着程序的退出,该计算器也在内存中消息了。 下一次在启动该程序,又重新开始从0计数。这样不是我们想要的 程序即使结束,该计算器的值也存在。下次程序启动时会先加载该计算器的值并 加1后再重新存储起来。所以要建立一个配置文件。用于记录该软件的使用次数。 该配置文件使用键值对的形式。这样便于阅读数据,并操作数据。 */ import java.io.*; import java.util.*; class PropertiesTest { public static void main(String[] args) { method(); } public static void method() { FileInputStream fis=null; try { File f=new File("properties.ini"); if(!f.exists()) //若文件不存在,则创建 f.createNewFile(); fis=new FileInputStream(f); //创建一个Properties类对象 Properties prop=new Properties(); prop.load(fis);//加载目的文件 //获取键对应的值 String value=prop.getProperty("time"); int num=0; if(value!=null) { num=Integer.parseInt(value); if(num>=5) { sop("使用次数已到,请注册!"); return; } } num++; prop.setProperty("time",String.valueOf(num)); //保存 prop.store(new FileOutputStream(f),"hehe"); } catch(IOException e) { sop(e.toString()); } finally { try{ if(fis!=null) fis.close(); } catch(IOException e) { sop(e.toString()); } } } public static void sop(Object obj) { System.out.println(obj); } }
结果:
IO包中的其他对象
一、打印流:PrintWriter和PrintStream
打印流
该流提供了打印方法,可以将各种数据类型的数据都原样打印
字节打印流和字符打印流。
字节打印流:PrintStream
构造函数可以接收的参数类型:
1、file对象 File
2、字符串路径 String
3、字节输出流 OutputStream
字符打印流:PrintWriter
构造函数可以接收的参数类型:
1、file对象 File
2、字符串路径 String
3、字节输出流 OutputStream
4、字符输出流 Writer
代码:
import java.io.*; class PrintStreamDemo { public static void main(String[] args) { BufferedReader br=null; PrintWriter out=null; try { //InputStreamReader()构造函数接收的类型是 InputStream ,而 System.in的返回值类型就是InputStream. br=new BufferedReader(new InputStreamReader(System.in)); //PrintWriter构造函数可以接收的类型为OutputStream,而System.out返回类型为PrintStream, //而PrintStream是OutputStream的子类 out=new PrintWriter(System.out); String line=null; while((line=br.readLine())!=null) { if("over".equals(line)) break; //out.write(line.toUpperCase()); out.println(line.toUpperCase()); out.flush();//此处不刷新的话,在PrintWriter() 方法中要再添加一个参数autoFlush } } catch(IOException e) { System.out.println(e.toString()); } finally { try { if(br!=null) { br.close(); } } catch(IOException e0) { System.out.println(e0.toString()); } finally { try { if(out!=null) { out.close(); } } catch(Exception e0) { System.out.println(e0.toString()); } } } } }
当我们不想使用flush刷新时,可以使用PrintWriter的重截方法 PrintWriter(OutputStream out, boolean autoFlush) ,当参数autoFlush的值为true时,会自动刷新
二、序列流
SequenceInputStream 将多个流合并(将多个文件合并成一个文件)
SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,
直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
来看实现代码:
import java.io.*; import java.util.*; class SequenceDemo { public static void main(String[] args) throws IOException { //Vector集合 Vector<FileInputStream> v=new Vector<FileInputStream>(); v.add(new FileInputStream("aaa\\1.txt")); v.add(new FileInputStream("aaa\\2.txt")); v.add(new FileInputStream("aaa\\3.txt")); //枚举 Enumeration<FileInputStream> en=v.elements(); SequenceInputStream sis=new SequenceInputStream(en); FileOutputStream fos=new FileOutputStream("aaa\\4.txt"); byte[] byt=new byte[1024]; int len=0; while((len=sis.read(byt))!=-1) { fos.write(byt,0,len); } fos.close(); sis.close(); } }
结果:
代码中通过SequenceInputStream将三个FileInput流合并,并生成一个新文件。
小知识:切割文件
其原理是在读取文件时,定义一个一定长度的byte数组,将数据存储到数据中,然后通过输出流写入文件。依次类推,每个文件的大小即为数组的长度。到直读取完文件。
实现代码:
import java.io.*; import java.util.*; class SplitFile { public static void main(String[] args) throws IOException { splitFile(); merge(); } //合并文件(将切割后的文件再合并成一个文件) public static void merge() throws IOException { ArrayList<FileInputStream> al=new ArrayList<FileInputStream>(); for(int i=1;i<=4;i++) { al.add(new FileInputStream("aaa\\"+i+".part")); } Iterator<FileInputStream> it=al.iterator(); Enumeration<FileInputStream> en=new Enumeration<FileInputStream>() { public boolean hasMoreElements() { return it.hasNext(); } public FileInputStream nextElement() { return it.next(); } }; SequenceInputStream sis=new SequenceInputStream(en); FileOutputStream fos=new FileOutputStream("aaa\\4.mp3"); byte[] byt=new byte[1024]; int len=0; while((len=sis.read(byt))!=-1) { fos.write(byt,0,len); } fos.close(); sis.close(); } //切割文件 public static void splitFile() { FileInputStream fis=null; FileOutputStream fos=null; try { fis=new FileInputStream("aaa\\姚婷 - 两两相望.mp3"); byte[] byt=new byte[1024*1024]; int index=1; int length=0; while((length=fis.read(byt))!=-1) { fos=new FileOutputStream("aaa\\"+(index++)+".part");//每次循环就保存为一个文件 fos.write(byt,0,length); } } catch(IOException e) { System.out.println(e.toString()); } finally { try { if(fis!=null) fis.close(); } catch(IOException e) { System.out.println(e.toString()); } try { if(fos!=null) fos.close(); } catch(IOException e) { System.out.println(e.toString()); } } } }
结果:
相关文章推荐
- 黑马程序员---java基础之IO(File类及其他流对象)
- 黑马程序员_java基础复习六之io
- 黑马程序员--JAVA基础复习之多线程(三)线程间通信 生产者消费者
- 黑马程序员—19—java基础:有关File类的学习笔记和学习心得体会
- 黑马程序员--JAVA基础复习之泛型
- 黑马程序员_java基础File类篇Day4
- 黑马程序员-JAVA基础-IO流之File 类
- 黑马程序员 java基础复习二 之面向对象
- 黑马程序员--JAVA基础复习之正则表达式
- 黑马程序员_java基础复习三流程控制
- 黑马程序员_java基础复习二面向对象
- 黑马程序员_java基础复习之八集合
- 黑马程序员--JAVA基础复习之String
- 黑马程序员_java基础复习总结01
- 黑马程序员_java基础复习五多线程
- 黑马程序员_java基础复习之十常用设计模式总结
- 黑马程序员:Java基础总结----类 File
- 黑马程序员--Java基础学习之IO流之File类、Properties对象、打印流、序列流等
- 黑马程序员--JAVA基础复习之myeclipse开发环境 常用配置
- 黑马程序员--JAVA基础复习之多线程(二)线程安全与解决方法