GUI
图形用户接口,针对图形操作提供的公共组件。用图形的方法,显示计算机的操作界面。CLI,命令行方式。java为GUI提供的对象都在java.Awt和javax.Swing中。
java.Awt:抽象窗口工具包,需要调用本地系统方法实现功能,属于重量级控件。有点依赖于平台,跨平台性差。
javax.Swing:在Awt的基础上,提供更多组件,完全由java实现,增强了移植性,属轻量级控件。跨平台性好。
java.Swt,IBM开发的外观包。界面和eclipse一样。
只是与客户进行交互而已,底层仍然是以前学习的编程。
图形界面:容器+组件+布局管理器。
Label:标签,封装文字用于其他组件的使用。
checkbox:复选框。
TextComponent:文本组件,包括文本框和文本区域。
Container:容器,常见包括Window和Panel。内部可以添加组件,需要使用add添加其他组件。
Window:常用子类Frame窗口和Dialog对话框。一般创建Frame比较合适。
Frame:内部封装了集合,将组件添加入集合,都带有角标。
布局管理器
布局,容器中组件的排放方式。默认的是边界布局,如果没有指定方向,平铺。使用前需要合理设计,最好先手绘图形。最方便的布局是坐标布局。
开发图形化界面常用的编辑器:JBuild或者eclipse+图形化界面插件。
Frame
带标题和边框的顶层窗口。图形化界面由独立的线程控制,不是在主线程中。一旦开启,马上启动其他线程,开辟一个窗口。
内部默认一个布局管理器,即边界式布局,会将组件平铺在整个窗口,居中填充。
创建图形化界面:
1. 创建Frame窗体。
2. 对窗体进行基本设置,例如,大小、位置、布局等。
3. 定义组件。
4. 将组件通过窗体的add方法添加到窗体中。
5. 让窗体显示,setVisible(true)。
事件监听机制
组成:
事件源:awt包或swing包中图形界面组件。
事件(Event):每个事件源都有自己特有的对应事件和共性事件(鼠标、键盘)。
监听器(Listener):将可以触发某一个事件的动作(不止一个)封装到监听器中。
以上三者在java中都已经定义好了,直接创建对象使用即可。余下的动作就是对产生的动作进行处理,也是开发中最重要的部分。还需要再导入java.awt.event.*;包。
事件处理:引发事件后的处理方式。以窗体事件为例。因为WindowListener的子类WindowAdapter已经实现了了WindowListener接口,并覆盖了其中的所有方法。那么只要继承WindowAdapter并覆盖相应的方法,并将对象传入监听器即可。更多时候使用使用匿名内部类比较方便。System.exit(0);终止目前正在运行的java虚拟机。
一般需要将事件和图形化界面分离,基本步骤如下:
1. 定义该图形所需的组件的引用:窗体和组件。
2. 图形化界面初始化,对Frame进行基本设置。创建组件,添加到Frame中。显示窗体。将这些设置放在一个函数中,与事件分离。例如,public void init()
3. 将事件单独创立到一个函数,例如myEvent,将各种事件处理放在其中,然后再将其放在init()函数中、显示窗体之前。
4. 最后将init()放在构造函数中,使得窗体一建立就有图形化界面和基本事件。
5. 可以添加一些功能按钮。
如何选择监听器?只有组件知道自己支持哪些监听,这些监听都定义在组件内部。
如果监听器里有多个(>=3个)事件,就会有适配器Adapter,例如,WindowAdapter该类以及覆盖了监听器里的所有方法,需要使用哪个方法,只要覆盖适配器内的方法即可。如果监听器里只有一个方法,就无需适配器,直接使用匿名内部类即可,例如,ActionListener。但没有适配器的接口极少,所以适配器更加常用。
特有事件,例如按钮事件:ActionListener,通过匿名类,复写内部函数actionPerformed()即可,只有一个函数。
4个确定:确定事件源?确定事件?确定监听器?确定需要操作的对象?
共性事件
鼠标事件,作为共性的事件,可以添加到各种组件或窗口中,例如,添加到按钮中。
如果在同一个组件上,例如按钮,定义多个监听器,例如按钮监听和鼠标监听,如果鼠标点击,两个监听的内部都会执行,鼠标监听先执行(因为是共性事件);如果用键盘点击按钮,只执行按钮监听。键盘和鼠标都可以让按钮活动,所以按钮上最好加actionEvent,即特有事件监听器。
共性事件先执行,特有事件后执行。特有事件可以监听多个动作,应用更广。
鼠标事件会将鼠标操作的信息封装在(MouseEvent e)的e中,例如,点击几次,点击左边或右边,可以通过MouseEvent类获取相应的信息,进而提供更多的操作,例如,e.getClickCount(),获取相应点击次数。
可以使用java.awt.event包快速查找监听器和事件。事件内部封装了方法,以便对事件进行操作,获得更多的操作方法。
键盘事件对象,即KeyEvent内部会封装操作键盘的信息,例如,getKeyChar(),获得操作的按键,getKeyCode(),获得按键对应的数字,static String getKeyText(int keyCode),获取按键的文本。按键对应的数字已经封装成常量,可以按照按键名直接调用。常量也是有顺序的,例如,0键对应48,9键对应57。可以按此设定按键范围。
按钮中出现虚线边框,表明已经进入键盘操作状态,即使光标步骤按钮上,也是在操作按钮。
通过InputEvent类中的isControlDown()方法,可以在ctrl键按下的条件下,在加上其他按键的执行,从而可以执行2个键同时按下的操作。称为“组合键”。
可能会点击某个事件源,但是监听器内部却不操作事件源,例如,点击按钮,将文本框中的内容打印出来,所以要明确操作的对象。
事件是连续的,可以加入某些屏蔽动作,使得事件动作停止,例如,按字母键,但是不输入到文本框中,即取消事件。使用InputEvent类(KeyEvent的父类)中的consume()来取消原来默认的操作。
TextComponent文本容器,文本框和文本区域的父类,提供多种操作方法。
TextField文本框
可以指定文本框的长度。长度为字符个数,不是像素数。情况,setText(“”);
TextArea文本区域
构造函数TextArea(int rows, int columns),rows表示y轴上长度,columns表示x轴上的长度,单位是字符长度,不是像素数。
添加字符串到文本区:append()不覆盖追加文本,setText()覆盖。
对话框Dialog
带标题和边界的顶层窗口。一般不单独存在,发生了某些特定事件(错误操作)后跳出,所以对话框依赖于窗口Frame产生。可以在错误产生时,在创建对话框对象,节省内存资源。不要添加到Frame中。例如,Dialog d = new Dialog(f,"提示信息",true);true表示对话框如果不操作无法操作窗口;false表示可以独立操作对话框和窗口。
只要是存放组件,就一定要涉及布局的问题。
对话框不一定要撤销,可以隐形。
FileDialog
对话框子类,用户可以从中选取文件。构造函数提供构造好的文件供选择,可以是 FileDialog.LOAD 或 FileDialog.SAVE,通过模式选择使用操作文件的方式,默认打开文件。通过get()方法获得指定文件的文件名和路径。可以调用IO流将文件导入到窗口中,或者在窗口中修改再保存到文件中。保存文件,如果文件已存在,不需要弹出;如果不存在,需要弹出。
菜单Menu
MenuBar 类封装绑定到框架的菜单栏的平台概念。为了将该菜单栏与 Frame 对象关联,可以调用该框架的 setMenuBar 方法。
内部层次关系:MenuBar(菜单栏)——》Menu(菜单)——》MenuItem(菜单项,最终)、Menu(子菜单)
理清菜单之间的关系。有条目的菜单会带箭头。
MenuItem支持ActionListener监听器,所以键盘和鼠标都可以操作。
能够双击执行的jar包
必须有独立的操作界面,所以控制台是不可以的。
将源代码编译到包中,在将包压缩到jar包中。
给jar包添加自定义的配置信息,即将信息添加入META-INF中的Main-Class manifest,再重新载入即可。将配置清单信息存放在文本文件中,格式:空格Main-Class: 包名.类名+回车。再使用jar编译,格式:jar cvfm jar包名 清单文件名 包名,此时产生的jar文件可以直接运行。
此jar文件必须本地注册才能用,工具—》文件夹选项—》文件类型—》jar文件类型——》高级—》open操作—》编辑,能看到关联的应用程序,如果JDK是双击安装版,会自动注册jar文件类型,例如,"C:\Program Files\Java\jre6\bin\javaw.exe" -jar "%1" %*
关键字:System类、Runtime类、Date类、Math类、Properties类、SimpleDateFormat类、Calendar类、Random类、
IO流、FileReader类、FileWriter类、文件的复制、续写
API中的常用类
System类
不能实例化,属性和方法都是静态的。
out,标准输出,默认打印在控制台上。通过和PrintStream打印流中的方法组合构成输出语句。
In,标准输入,默认是键盘,获取键盘录入的所有内容。
描述系统的一些信息:
获得批量信息:Properties getProperties(),获得系统的属性信息。
Properties是HashTable的子类,是Map集合的子类对象;属性类,比较常用。该集合中存储的都是字符串,没有泛型定义。获取系统属性信息,与每台电脑的系统有关。java是跨平台语言,需要知道系统种类决定以后的操作。开发中,需要先了解平台的系统信息,进而决定采用何种操作。
设置信息:setProperty,在系统中自定义一些特有信息,例如,配置文件、目录等。
获得单个信息:getProperty(String key),获取指定属性的信息,例如,os.name。指定键没有的话,返回null。
虚拟机动态的加载指定系统信息:在DOS通过-D设置信息,然后再运行,例如,java –Dhaha=qq SystemDemo。
Runtime类
每个 Java 程序都有一个 Runtime 类对象,使程序能够与其运行的环境相连接,让程序在某个系统下产生一个进程并运行。
没有构造函数、多是非静态方法,只提供一个静态方法获取本类对象,进而调用各个方法,这就是“单例设计模式”,保证了对象的唯一性。该静态方法是,getRuntime(),返回值类型是本类类型。
exec(String command),在单独的进程中执行指定的字符串命令。字符串命令可能会出现错误,所以需要抛出异常。例如,run.exec("C:\\Program Files\\TTPlayer\\TTPlayer.exe");,启动千千静听。也可以通过path变量,直接按照指定路径启动程序。
exec返回一个进程,Process类,抽象类,没有构造函数,因为底层在系统内创建进程对象。内部的destroy()方法,秒杀子进程。只能杀死自己启动的对象,已经启动的对象无法杀死。可以通过程序打开文件,例如,run.exec("notepad.exe SystemDemo.java");。
Date类
表示特定的瞬间,单位毫秒。将时间封装进对象中,并用toString输出。
时间模式可以修改:将对象封装进SimpleDateFormat类中,再调用format方法让模式格式化指定的Date对象。例如,SimpleDateFormat fo = new SimpleDateFormat("yyyy年MM月dd日"); String d2 = fo.format(d);创建一个模式,然后用这个模式格式化时间。如果格式化模式里只有年,就只能获取年。
SimpleDateFormat类中有关于时间和日期的字符串定义,不同的字符串代表不同的时间和日期,使用时需要注意。
Calendar类提供更加广泛的时间操作,本身是抽象类,getInstance返回一个对象,再由子类GregorianCalendar对这个对象进行具体的操作。这个对象将有关时间的信息封装起来,并且单独进行描述,即年、月、日、时区等分开描述。计算机计算月份从0开始,到11结束,所以需要加1补偿;星期日为1。这些信息可以看做Map集合中的键值对,通过get可以获得,但需要注明所属类,例如,ca.get(Calendar.YEAR)。
可以使用查表法(是一种思想),将月份变成字符串数组,再通过get方法获得对应的字符串。
通过set (int year, int month, int date)方法,将时间设定在某处,例如,ca.set(2012,2,23);参数还可以包括分、秒。
通过add(int field, int amount)方法,在指定的时间量将时间向后推移,称为“时间量的偏移”。例如,ca.add(Calendar.MONTH,5);现在时刻的5个月后;也可以向前推移,例如,ca.add(Calendar.MONTH,-5);5个月前。
Math类
各种数据的计算方法,都是静态方法。
ceil()返回大于或等于指定数据的最小整数。
floor()返回小于或等于指定数据的最大整数。
round():四舍五入,返回值尽量是long。
pow(a,b):a的b次幂,返回值尽量是double型。
randow():随机数,返回带正号的double值,大于或等于 0.0,小于 1.0。通过运算,可以获得其他区间的随机数,例如,int d = (int)(Math.random()*10+1);
也可以调用Random类中的方法,更广泛的获取随机数。创建随机数生成器,在通过各种next方法获得对应的随机数。
IO(Input Output)流
IO流用来处理设备之间的数据传输,例如,硬盘上存放的文件、内存中的数据。也可以理解为“数据流”。本质是在底层调用系统的资源。
Java对数据的操作是通过流的方式,Java用于操作流的对象都在IO包中。
流按照操作数据分为两种:字节流和字符流。所有的数据都是有字节构成,都可以用字节流处理。对于常见的文本数据,用字符流处理更方便。
按流向分为:输入流、输出流。
IO流基类:
字节流的抽象基类:InputStream,OutputStream
字符流的抽象基类:Reader,Writer。
对数据的操作只有读和写,以上的基类都是读和写。整个IO流就是围绕读和写展开的。将读和写分别封装成对象,方便使用。
基类都是抽象类,无法直接使用。由这四个基类派生出的子类名称都是以其父类名作为子类名的后缀。例如,FileReader。前缀名都是该流对象的功能。
Writer基类
数据最常用的体现:文件。专门用于操作文件的子类对象:FileWriter()。
FileWriter类
没有空参数的构造函数,因为必须要有文件供其操作。具体执行步骤如下:
1. 创建一个FileWriter文件,该对象一被初始化就必须要有可以被对象操作的文件。而且该指定名称的文件会被创建到指定的目录下。如果该目录下已有同名文件,覆盖已有文件。该步的本质是,明确数据存放的目的地。例如,FileWriter fw = new FileWriter("Demo.txt");
2. 调用write()方法,将字符串写入流中。例如,fw.write("Stay hungry,stay foolish.----Steve Jobs.");,本质是写入内存中。write()方法可以写入各种参数,例如,字符串、单个字符、字符数组等。
3. 刷新流对象中的缓冲中的数据,将数据刷到目的地。例如,fw.flush();。重复2、3步,可以续写内容。
4. close(),关闭流资源,但是关闭之前会刷新一次缓冲中的数据(调用flush),将数据刷到目的地。例如,fw.close();。和flush()的区别:flush刷新后,流可以继续使用;close()无法继续使用。
实际上只有windows才可以创建文件写数据,所以Java是在调用系统底层的功能,来完成数据的读写,Java本身无法直接写数据。系统底层的这些功能,称为“流资源”。使用完资源后必须释放,即close()动作。
IO异常处理
尽量不要抛。一旦和设备发生了有关数据的关系,基本就是IOException。
多个语句有关联,且都需要抛异常的话,可以放在一起处理,比较高效。
因为关闭资源的动作必须执行,所以应该放入finally{}中。但是不同代码块的引用无法通用,所以在外面建立引用,在try内初始化。close()仍然可以有异常,需要单独处理。例如
public static void main(String[] args)
{
FileWriter fw = null;//在外部建立引用。
try
{
fw = new FileWriter("Demo.txt");//在内部初始化。
fw.write("afdaf");
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
try
{
if(fw != null)// 如果fw初始化出现异常,无法创建对象,也就无法调用close()。
fw.close();//一定要关闭资源。close()的异常也要处理。
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
每个流都要独立的关闭一次。
文件的续写
传递一个true参数,代表不覆盖已有文件。并在已有文件的末尾处进行文件续写。例如,fw = new FileWriter("Demo.txt",true);
windows中使用\r\n表示换行,Lnix中\n表示换行,txt文件属于Lnix。
也可以写入字符数组或单个字符。
Reader
IO中,对于文件的读和写的类基本都是对应的。
close()关闭资源,不刷新。
read(),可以读取各种数据,例如,单个字符、字符数组或字符数组的一部分。
FileReader类
读取字符文件,有默认的字符编码,GBK。
方式一:
通过单个字符进行读取。
1. 创建一个“文件读取流”对象,和指定名称的文件相关联。要保证该文件是已经存在的,否则发生FileNotFoundException异常。
2. 调用读取流的read()方法。底层调用windows扫描硬盘。每个文件的末尾都有系统指定的标识,即分隔符,与下一个文件隔开。Java读到字符,返回编码;读到这个标识,返回-1。所以read()返回的是int整数。例如,
read(),一次读一个字符,而且自动向下读。返回字符对应的编码;读到结尾,返回-1。
int ch = 0;
while((ch=fr.read())!=-1)
{
sop((char)ch);
}
方式二:
通过字符数组进行读取。全部存入数组后再输出,比较高效。
1. 定义一个字符数组,用于存储读到的字符。read(char[] ch),返回读到的字符个数,读到末尾返回-1。当读取的字符超过数组长度,数组停止读取,返回字符的个数;再次读取,覆盖之前的数组中的元素,直到最后返回-1.
char[] buf = new char[1024];//字符数组的长度一般是1K的倍数。
int num = 0;
while((num = fr.read(buf))!=-1)
{
sop(new String(buf,0,num));//用数组的一部分构成一个字符串。
}
复制文件
原理:将一个文件的数据存储到另一文件中。
步骤:例如,将C盘文件复制到D盘。
1. 在D盘创建一个文件,用于存储C盘文件中的数据。
2. 定义读取流并和C盘文件关联。
3. 通过读写完成数据的存储。
4. 关闭资源。
例如,
public static void copy_2()
{
FileWriter fw = null;
FileReader fr = null;
try
{
fw = new FileWriter("Runtime_copy.txt");
fr = new FileReader("RuntimeDemo.java");
char[] buf = new char[1024];
int num = 0;
while((num=fr.read(buf))!=-1)
{
fw.write(buf,0,num);//以数组为桥梁,连接2个文件。读取的内容构成字符数组,然后写入文件。
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败。");//可以删除文件
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
sop("final catch="+e.toString());
}
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
sop("final catch="+e.toString());
}
}
}
关键字:缓冲区思想、BufferedWriter类、BufferedReader类、装饰设计模式、LineNumberReader类、FileInputStream类、FileOutputStream类、BufferedInputStream类、BufferedOutputStream类、缓冲区思想、键盘录入、流操作的规律、转换流、改变设备、异常的日志信息
字符流的缓冲区
缓冲区的出现是为了提高对数据的读写效率。临时存储数据,达到一定数量后一起处理。许多软件都有缓冲区技术。对应类:BufferedWriter类和BufferReadered类。
缓冲区的存在是为了提高流的使用效率,所以在创建缓冲区之前,必须要先有流存在。
原理是对象内部封装了数组,用于存储数据。
BufferedWriter类
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
步骤如下:
1. 创建一个字符写入流对象。例如,FileWriter fw = new FileWriter("buf.txt");
2. 为了提高写入流的效率,加入缓冲技术。将需要被提高效率的流对象作为参数传递给缓冲区构造函数即可。例如,BufferedWriter bufw = new BufferedWriter(fw);
3. 调用BufferedWriter类的方法写入,和FileWriter基本一致,都需要关闭资源。例如,write()、flush()、close()等。
注意:只要用到缓冲区,就一定要刷新。防止因外力停止程序,造成数据丢失。
关闭缓冲区就是关闭缓冲区中的流对象,所以不用再关闭流对象。
该缓冲区中提供了一个跨平台的换行符(特有),newLine(),适合各种系统。底层调用\r\n。
BufferedReader类
从字符输入流中读取数据,缓冲各个字符,从而提供字符、数组和行的高效读取。
步骤如下:
1. 创建一个读取流对象,和文件相关联。例如,FileReader fr = new FileReader("buf.txt");
2. 为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲对象的构造函数。例如,BufferedReader bufr = new BufferedReader(fr);
3. 可以使用readLine()方法,一次读取一行。返回包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null。只返回回车符之前的内容,不返回回车符。例如,
String line = null;
while((line=bufr.readLine())!= null)
{
System.out.println (line);
}
该缓冲区提供了一次读一行的方法readLine(),方便了对文本数据的读取。
readLine()原理:
无论是读一行还是读取多个字符,都是在硬盘上逐个读取。最终使用的是read()方法,一次读取一个。将字符临时存到内部数组,遇到换行符不存,将之前的字符变成字符串返回,换行符相当于停止标识。代码如下:
public String myReadLine() throws IOException//readLine的原理
{
StringBuilder sb = new StringBuilder();//建立容器,存放字符。
int ch = 0;
while((ch=fr.read())!=-1)
{
if(ch=='\r')//换行符不能读取
continue;
if(ch=='\n')
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0)//防止最后一行因为没有换行符而无法输出。
return sb.toString();
return null;
}
装饰设计模式
readLine()方法调用了read()方法,同时增强了read()方法的功能。将被增强的对象传给增强类,增强类提供更强的方法。所以,MyBufferedReader是对BufferedReader的增强。这种模式就是“装饰设计模式”。
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有类,并提供加强功能,那么这个自定义的类称为“装饰类”。
装饰类的特点:
会通过构造函数接受被装饰的对象,并基于被装饰的对象的功能,提供增强功能。例如,FileReader类中的read()方法,装饰类BufferedReader中的readLine()增强了read()方法,将FileReader类的对象传入BufferedReader,新的对象调用readLine()方法。两个类共属于一个类。
装饰模式也有继承,但继承的是父类,与子类并列使用,所以比继承更加灵活,避免了继承的臃肿。因为是增强已有对象,基本功能和已有的是相同的,只是更加强大而已。所以装饰类和被装饰类属于一个体系中,组合结构。例如,A+只能增强A,但是使用装饰类X能增强ABC。。。,更加方便。所以尽量使用通过装饰类来扩展类的功能,少用继承,避免臃肿。
将最顶端的父类的对象传入作为参数,从而可以装饰一组类。BufferedReader的参数就是Reader,但具体使用时运用多态,传入子类的对象。
LineNumberReader类(装饰类)
getLineNumber(),获得行号,默认从1开始。返回的是读取的这一行的行号。
setLineNumber(),设置行号,从多少开始。
继承了BufferedReader类,属于这个体系,增强了行号的功能。
字节流
操作字节,使用系统默认的码表,从系统信息里获取。
InputStream(读取)OutputStream(写入),输出的都是写入。
OutputStream类
和Writer基本一致,不一样的是只能操作字节。对最小单位——字节操作,中间不需要转换,也就不需要缓冲区,直接存入即可。但是,关闭资源是必须的,因为调用了系统底层的一些资源。
写入的是字节,但最终看到的仍是各种文件。
InputStream类
FileInputStream类,和FileReader基本一致。多了availabel()方法,可以获得文件的字符数,包括换行符,依据此方法可以定义一个合适的缓冲区,不用再循环,例如,byte[] ch = new byte[fis.available()];。因为会将整个数据放入缓冲区,如果数据太大,会造成内存溢出,所以不建议太大的数据使用此方法,推荐数组方法。
缓冲区
BufferedInputStream类,BufferedOutputStream类
基本思路:FileInputStream将硬盘中的数据存入内存中的缓冲区BufferedInputStream,缓冲区存满后取出,再将下一批数据存入缓冲区,再取,循环往复,直到数据全部取出。避免了从硬盘到硬盘,减少了硬盘的负担。硬盘——》内存缓冲区——》硬盘。通过记录缓冲区的长度来标识何时停止。
需要3个工具:数组、计数器和指针。
如果单个字节出现1111-1111,会返回-1,表示数据已经读取完毕,造成错误。 0000-0000 0000-0000 0000-0000 1111-1111 是255
1111-1111 1111-1111 1111-1111 1111-1111 是-1
出现-1是因为前面8个1前面补的全是1,如果全部补0,由-1变成了255,但仍然是8个1,原字节数据不变,而且避免了-1的出现。具体操作是,byte变成int,此时仍是-1,再&255就变成了255。先类型提升,然后再&,变成255。
读取(read)时,将byte转换成int型(4个字节);写入(write)时,强转动作,将int转换成byte,只保留最后8位,保证数据的原样性。字符流中的读取、写入本质上也是在操作字节,只是走了编码表,所以表现的都是字符。
字符流
掌握4个类的使用:FileReader、FileWriter、BufferedReader、BufferedWriter。操作的仍然是字节,只是已经被码表编译过。
字节流
掌握4个类的使用:FileInputStream、FileOutputStream、BufferedInputStream、BufferedOutputStream。
键盘录入
System.out:对应标准输出设备,控制台。字节流对象。
System.in:对应标准输入设备,键盘。字节流对象。
read(),阻塞式方法,如果没有读到数据,就会等待。会读取键盘录入的内容。可以读取换行符。
ctrl+c,结束录入,底层返回-1,读取停止。
可以用BufferedReader中的readLine()完成对键盘录入的一行数据的读取,提高效率;而键盘录入的read()方法是InputStream的方法。使用字符流体系中的InputSreamReader来完成字节流向字符流的转换。步骤如下:
1. 获取键盘录入对象。例如,InputStream in = System.in;
2. 将字节流对象转成字符流对象,使用转换流InputStreamReader,例如,InputStreamReader insr = new InputStreamReader(in);
3. 将字符流进行缓冲区处理,提高效率。例如,BufferedReader br = new BufferedReader(insr);
4. 键盘录入结束标记,要么ctrl+c,要么自定义。例如,if("over".equals(line)) break;
5. 关闭资源。
键盘录入常见写法:BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
OutputStreamWriter
录入的是字符,但存入硬盘的是字节。基本步骤和上面一样,例如,
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);//字符流转向字节流。
BufferedWriter bw = new BufferedWriter(osw);
常见写法:BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));目的地是控制台。
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("buf.txt")));目的地是文件。
流操作的基本规律
最麻烦的就是对象很多,不知道使用哪一个。
需要通过3个明确来完成:
1. 明确源和目的。
源:输入流,InputStream,Reader
目的:输出流,OutputStream,Writer
2. 操作的数据是否是纯文本。
是:字符流,Reader、Writer
否:字节流,InputStream、OutputStream
3. 当体系明确后,在明确使用哪个具体对象。
通过设备来进行区分:
源设备:内存、硬盘、键盘
目的设备:内存、硬盘、控制台
是否需要提高效率:是:加入缓冲区(推荐加入);否:不加缓冲区。
余下的就是调用读写方法和关闭资源。
源和设备可能不匹配,需要转换,例如,键盘录入的数据,源是Reader,设备用InputStream。
如果源是键盘录入的话,本来应该是InputStream,但是为了调用readLine()整行读取,转成Reader,使用转换流InputStreamReader。同样原理,如果目的是控制台,使用InputStream,但是为了调用newLine()换行方便,使用OutputStreamWriter,转成FileWriter。
按照指定的编码表(UTF-8或者GBK),将录入的数据存入指定文件中
平时的读写使用默认的字符编码表,即系统的编码,GBK。转换流可以指定编码表,这也是转换流存在的重要原因,不仅仅是调用readLine()和newLine()。也就是说,只要是涉及到字符编码转换,就需要使用转换流。
在OutputStreamWriter构造函数中可以设置编码表,例如,OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("buf.txt"),"UTF-8");
使用不同的编码表,占用的空间不同。再就是,FileReader和FileWriter,默认GBK编码,无法处理UTF-8编码,需要使用转换流才能读取。转换流是以上两类的父类,具有使用多种编码的功能。依照文件的编码来确定使用何种数据处理类:GBK使用FileReader,UTF-8使用InputStreamReader。使用转换流更加广泛,但需要传入字节流,FileReader更加简便。
改变标准输入输出设备
通过System中的setIn()改变设备,例如,将键盘录入改成文件,System.setIn(new FileInputStream("buf.txt"));;将输出目的改为文件,System.setOut(new PrintStream("buf.txt"));。如果源和目的都改成文件,就是复制。
异常的日志信息
异常中,异常对象调用printStackTrace(),将异常信息打印在控制台,底层调用System.out,或者创建一个PrintStream对象,将信息打印在控制台。PrintStream也可以调用println()方法。
需要将异常信息打印在某个文件中,便于观察,例如,System.setOut(new PrintStream("exception.log"));e.printStackTrace(System.out);最好在加上时间。
一般使用网络上的log4j工具,帮助完成日志信息的建立。
获得系统信息
Properties p = System.getProperties();
p.list(new PrintStream("system.txt"));
File类
流操作的只有数据,数据最明显的体现是文件,文件有很多的属性和行为,例如,大小、类型和扩展名,数量也比较多,所以可以抽象描述成类,然后封装成对象。
用来将文件或文件夹封装成对象,从而方便对文件或文件夹的属性信息进行操作。流不能操作文件或文件夹,只能操作数据。File类对象≠硬盘上的文件。
File类是Object类的子类,属于IO体系。File对象可以作为参数传递给流的构造函数。也可以直接将文件实体作为参数传递给流,但是如果使用File对象,可以操作文件实体。
可以直接操作File的流:FilenameFilter(接口)、FileInputStream、FileOutputStream、FileReader、FileWriter、PrintStream、RandomAccessFile、
步骤如下:
1. 将a.txt封装成对象,可以将已有的未出现的文件或文件夹封装成对象。例如,File f = new File("d:\\java007\\day20\\a.txt");。
也可以通过设置目录来创建对象,File f2 = new File("d:\\java007\\day20","b.txt");将目录和文件分成2独立参数,单个参数可以是变量或表达式,例如,(“c:\\”,str)
也可以将目录封装成对象,再作为参数传递。例如,File d = new File("d:\\java007\\day20"); File f3 = new File(d,"c.txt");
可以直接打印文件,打印的是构造函数中的内容,即路径和文件名。
“\\”目录分割符只适合windows系统,不适合Linux系统,不能跨平台,应该使用属性seperator完成分割,例如,new File("d:"+File.separator+"java007"+File.separator+"day20");
常见方法
1. 创建
boolean createNewFile(),在指定位置创建文件,返回布尔型。如果该文件已经存在,不创建,返回false,不覆盖。new File只是在内存中创建了一个File对象,直到调用此方法才在硬盘创建文件;和输出流不一样,输出流对象一建立就在硬盘创建文件,如果文件已存在,就覆盖已有文件。
createTempFile(String perfix, String suffix),创建临时文件,使用后删除,提供前后缀名。
createTempFile(String perfix, String suffix, File directeray),提供路径,可以放在系统目录下,system32。
创建目录(文件夹):
boolean mkdir(),创建目录。可以创建在指定路径下,也可以创建在当前路径下。返回值表示是否创建成功。可以通过System中的user.dir获取当前目录。只能创建一级目录,就是说,只能在已有目录下再创建目录,但不能在不存在的目录下创建。
boolean mkdirs(),创建多级目录。
2. 删除
boolean delete(),删除文件,返回布尔型,表示是否删除成功,可以用于判断。
void deleteOnExit(),程序退出时自动删除指定文件,即使发生异常,也可以删除。
3. 判断
boolean OnExceute(),是否能够执行,文件只有在硬盘才能执行。与Runtime对象结合使用,打开所有程序。
boolean canRead(),是否可以读取抽象路径的文件。抽象路径,表示在内存但不在硬盘。
boolean canWrite(),是否可以修改抽象路径的文件。
int compareTo(),比较两个抽象路径的文件的字母顺序,返回字母顺序的差值,从首字母开始比较。例如,f-c=3。可以用于按字母顺序排序。
boolean exists(),文件是否存在硬盘的当前目录下。判断是否存在,然后决定是否让流操作。输出流中创建对象时覆盖文件,调用exists()判断,如果文件已经存在,删除后再创建。
isFile(),是否是文件
isDirectory(),是否是文件夹,
注意:判断文件对象是否是文件或文件夹,必须首先要判断该文件对象封装的内容是否存在,即文件是否存在于硬盘,调用exists()判断。new File只是在内存中创建了一个文件对象,硬盘中没有文件存在,不能准确判断文件的类型,.txt也有可能是文件夹。
isHidden(),是否是隐藏文件,java会取出所有文件,但是隐藏文件无法读取,需要先判断一下是否隐藏,隐藏的不要。
isAbsolute(),是否是绝对路径,即明确的路径。文件不在硬盘也可以为true,与文件是否存在不相关。
4. 获取信息
String getName(),返回文件或目录的名字。
String getPath(),获取相对路径,获取构造时封装的路径。
String getParent(),获取绝对路径中的父目录。因为可以在a目录下创建b目录,所以只要没有绝对路径,父目录返回都是null。如果相对路径中有上一级目录,返回上一级目录。
String getAbsolutePath(),获取绝对路径,与构造时封装的路径无关。相当于,父目录加相对路径。这个路径也可以创建。AbsolutePath=Parent+Name。
long lastModified(),返回最后一次修改的时间,如果返回的时间和上一次lastModified()的时间不一样,可以认为该文件已经被修改。
long length(),返回文件长度,FileInputStream类中availabel()方法调用的就是length(),但是length()的容量更大。
获取路径时,文件或文件夹是否在硬盘没有影响。
String和File直接可以互相转换,File通过toString()变成String;String放入File的构造函数变成File对象。
renameTo(),例如,f1.renameTo(f2),将f1的文件名改为f2的文件名,但是f1的文件内容不变;如果f1、f2路径不同,f1会覆盖f2,并从自己的目录下删除。
static File[] listRoots(),列出计算机中有效的盘符。明确盘符,知道储存文件的目的地。
String[] list(),将指定目录下的文件或文件夹的名称全部列出,并存在一个字符串数组中。列出所有文件,包含隐藏文件或文件夹。所以调用list()的File对象内部必须封装的是目录(即使是空目录),该目录还必须存在。如果是封装的是文件,数组返回为空,出现空指针异常。
list(FilenameFilter filter),文件名过滤。传入参数为过滤器FilenameFilter子类的对象,由于过滤器接口只有一个accept()方法,可以使用匿名内部类。按照accept()方法过滤,将符合要求的文件放入数组,最后获得某个目录中指定的文件。
File dir = new File("d:\\java007\\day18");
String[] arr = dir.list(new FilenameFilter()//list依照accept()的返回值来判定是否需要将文件放入数组。
{
public boolean accept(File dir , String name)//name为目录dir下的文件名
{
return name.endsWith(".java");//后缀名为java
}
});
for(String name : arr)//将文件输出。
{
sop(name);
}
File[] listFiles(),功能类似于list(),都是对路径进行操作,不同的是返回的是对象,可以对其进行操作。开发中使用更广。
列出指定目录下的文件和文件夹,包括子目录的内容。只要使用同一个具有目录列出的功能的函数即可。举例:
File[] files = dir.listFiles();
for(int x=0; x<files.length; x++)
{
if(files[x].isDirectory())
showDir(files[x]);//自己调用自己
else
sop("File="+files[x]);
}
目录层级很多,需要逐层空格,即给出层级,返回对应的空格数。可以通过容器StringBuffer容纳空格。例如
public static String getLevel(int level)
{
StringBuffer sb = new StringBuffer();
sb.append("|---");//只在文件名头部有分隔符
for(int x=0; x<level;x++)
{
sb.insert(0," ");//分隔符之前用空格
}
return sb.toString();
}
递归
在列出过程中,出现的还是目录的话,可以再次调用本功能,也就是函数自己调用自己。这种编程模式称为“递归”。
注意:
1. 必须有限定条件,保证函数能够结束。
2. 小心递归的次数,尽量避免内存溢出。
递归原理:按照正常顺序依次执行,只是某些语句由函数代替,需要先将函数内的语句执行完毕才能继续向下执行。
删除一个带内容的目录
原理:在windows中,从里向外删除,先把最里面的目录删除。使用递归方法。例如,
public static void removeDir(File dir)
{
File[] files = dir.listFiles();
for(File f : files)
{
if(f.isDirectory())
removeDir(f);
else
sop(f.toString()+"..."+f.delete());
}
sop(dir.toString()+"=="+dir.delete());//递归后,dir变化成每个文件夹
}
直接从硬盘删除,不进垃圾站。可以对删除动作进行打印,如果为false,说明有二次删除。C盘下的文档无法删除,因为有隐藏目录,java无法访问,也就无法删除。会导致数组为空,出现空指针异常。再就是会有文件很像目录,所以最好对是否隐藏和是否是目录都进行判断,例如,!f.isHidden()&&f.isDirectory()。避开隐藏目录。
将指定目录下的java文件的绝对路径储存到一个文本文件中,构建java文件列表。方便寻找文件。将数据存入硬盘,称为“数据的持久化”。
思路:1. 对目录进行递归。2. 获取递归过程中所有java文件的路径。3. 将路径存到集合中。4.将集合中的数组吸入文件中。