File类
File类是IO包中唯一代表磁盘文件本身信息的类,而不是文件中的内容。File类了一些与平台无关的方法来操作文件,例如创建、删除文件和重命名文件等。
java中的目录被当做一种特殊的文件使用,调用File方法可以返回目录中的所有子目录和文件名。并且java可以正确处理路径分隔符。
一些主要方法:
构造方法:File f=new File(“abc.txt”):在程序所在的目录下创建文件。
exists():判断文件是否存在,返回值为boolean型。delete():删除文件。
createNewFile():创建新文件,需要异常处理。getName():返回文件的名称。
getPath():返回文件的路径。getAbsolutePath():返回文件的绝对路径。
getParent():获得文件的父目录。canRead():判断文件是否可读。
isDirectory():判断文件是否是目录。lastModified():返回文件最后的修改时间,返回值为long型。
RandomAccessFile类
RandomAccessFile类支持随机访问方式,即可以跳到文件的任意位置进行读写。相对于顺序读写而言,随机读写等长记录格式的文件时很有优势。等长记录格式文件即文件里面存储的信息是一条条的记录,每条记录的内容长度一样,而且每条记录又可以分为若干相同的字段。
RandomAccessFile类仅限于访问操作文件,不能访问其他的IO设备。两种构造方法:
RandomAccessFile(f,”rw”);//以读写的方式打开文件
RandomAccessFile(f,r)// 以只读的方式打开文件
读操作与写操作共享文件指示器。
编程举例:
import java.io.*;
public class RandomFileTest {
public static void main(String[] args)throws Exception {
Employee e1=new Employee("zhangsan",23);
Employee e2=new Employee("lisi",24);
Employee e3=new Employee("wangwu",25);
RandomAccessFile ra=new RandomAccessFile("employee.txt","rw");
ra.write(e1.name.getBytes());
ra.writeInt(e1.age);
//只写入占一个字节的数据,而writeInt方法才写入占四个字节的数据。
ra.write(e2.name.getBytes());
ra.writeInt(e2.age);
ra.write(e3.name.getBytes());
ra.writeInt(e3.age);
ra.close();
int len=0;
byte[] buf=new byte[Employee.LEN];
String strName=null;
RandomAccessFile raf=new RandomAccessFile("employee.txt","r");
raf.skipBytes(Employee.LEN+4);//跳过12个字节
len=raf.read(buf);//可以将数据读取到一个字节数组中
strName=new String(buf,0,len);
System.out.println(strName.trim()+":"+raf.readInt());
//readInt()读取四个字节的整数。
raf.seek(0);//定位到文件的某一字节处。
len=raf.read(buf);
strName=new String(buf,0,len);
System.out.println(strName.trim()+":"+raf.readInt());
raf.skipBytes(Employee.LEN+4);
len=raf.read(buf);
strName=new String(buf,0,len);
System.out.println(strName.trim()+":"+raf.readInt());
raf.close();
}
}
public class Employee {
public String name = "";
public int age = 0;
public static final int LEN = 8;
public Employee(String name, int age) {
if(name.length()>LEN)
{
this.name=name.substring(0,LEN);
}
else
{
while(name.length()<LEN)
{
name+="/u0000";// /u0000在java中表示空格
}
}
this.name=name;
this.age=age;
}
}
为了是上面的程序能接受中文姓名,读取name时,要将write、read方法改为:writeChars与readChar,这时读入的是字符,也就不需要字节数组了。要注意unicode编码中每个字符占两个字节。
字节流
流提供了统一的方式从各种输入输出设备中读取和写入字节数据的方法。文件时数据的静态存储形式,而流是指数据传输时的形态。用于写入数据的程序可以一段接着一段地向数据流管道中写入数据,这些数据段会按照先后顺序形成一段长的数据流,对于读取数据的程序并看不到数据在写入时的分段情况,它每次可以读取任意长度的数据。
java中流分为两大类:节点流类和过滤流类(也叫处理流类)。用于直接操作目标设备所对应的类叫节点流类。节点流类所关联的IO目标为流节点(进行IO操作的主体或发起者)。程序可以通过一个间接的流类去调用节点流类,以方便地达到读写各种类型的数据。
InputStream类
程序可以从中连续读取字节的对象叫输入流,在java中,用InputStream类来描述所有输入流的抽象概念,InputStream是一个抽象类。它并不对应到具体的流设备,而是描述了所有的流设备的共性。
InputStream类的方法:
int read() 读取一个字节的内容,并将该字节的内容以整数的形式返回,若到流的结尾则返回-1.若流没有结束,也没有数据可读,则会堵塞,直到有新的数据可读。
int read(byte[] b):该方法用于从输入流中读取若干个字节存放到数组当中,返回值为实际读取的字节个数。若流没有结束,也没有数据可读,则会堵塞,直到有新的数据可读。
int read(byte[] b,int off,int len):第二个参数表示数组b的下角标。返回值意义与上方法同。
long skip(long n):跳过n个字节,并返回实际跳过的字节数,多用于包装流类中。
int available():用以返回当前输入流中可以读取的字节数。
void mark(int readlimit):在输入流建立一个标记,参数表示从该标记处开始还可以最多读取多少字节。多用于包装流类中。
void reset():回到以前的标记处,与mark方法配合使用。
boolean markSupported(); void close():java中的垃圾回收器,仅能处理类实例对象,而不处理相应的系统资源,所以要用close方法通知系统释放相应的资源。
OutputStream类
程序可以从中连续写入字节的对象叫输出流,在java中,用OutputStream类来描述所有输出流的抽象概念,InputStream是一个抽象类。它并不对应到具体的流设备,而是描述了所有的输出流设备的共性。
OutputStream类的方法:
void write(int b):用于将整数四个字节中最低的那个字节写到输出流中,高部分的字节被舍弃。
void write(byte[] b):将字节数组中的所有数据写入到输出流中。
void write(byte[] b,int off,int len); void fluse():用于将内存缓冲区中的内容全部清空,并输出到IO设备中。并不是所有的类都有该方法。
void close();当调用此方法时,有缓冲区的类会将内存缓冲区中的内容全部清空,并输出到IO设备中。
FileInputStream与FileOutputStream类
FileInputStream与FileOutputStream类分别用来创建磁盘文件的输入流和输出流对象,通过他们的构造函数来指定文件的路径和文件名。(输入和输出是相对于程序而言的)
创建FileInputStream类实例对象时,指定的文件应当是存在和可读的。创建FileOutputStream类实例对象时,指定的文件可以不存在,如果文件已经存在,这个文件中的原来的内容将被覆盖清除。
对同一个磁盘文件创建FileInputStream对象的两种方式:
第一种是将文件的路径名作为构造函数的参数:
FileInputStream in=new FileInputStream(“hello.txt”);
第二种方式是先创建一个File对象,File对象中的构造函数中就已经有了文件的路径信息,然后只需将该File对象作为FileInputStream的构造函数的参数:
File f=new File(“hello.txt”);
FileInputStream in=new FileInputStream(f);
创建FileOutputStream对象的构造函数的参数与创建FileInputStream对象的构造函数的参数形式与意义相同。创建FileOutputStream对象时,可以指定还不存在的文件名,不能指定一个已经被其他的程序打开了的文件。
编程举例:用FileOutputStream类向文件中写入一个字符串,然后用FileInputStream读出写入的文件内容。
import java.io.*;
public class FileStream {
public static void main(String[] args) throws Exception{
FileOutputStream out=new FileOutputStream("hello.txt");
out.write("hello world".getBytes());
out.close();
byte[] buf=new byte[1024];
File f=new File("hello.txt");
FileInputStream in=new FileInputStream(f);
int len=in.read(buf);
System.out.println(new String(buf,0,len));
in.close();
}
}
InputStream与OutputStream主要用于读取二进制文件。因此在读取时用到了字节的转化。
Reader与Writer类
Reader与Writer类是所有字符流类的抽象基类,用于简化对字符串的输入输出编程,即用与读写文本数据。
二进制文件与文本文件的区别
文件都是以二进制数据的形式存放的,每个字节都是二进制的。而各种文本字符是由一个或者多个字节而组成的,其中每个字节的数据不能是任意的,不能像二进制数据一样从零到255都可以,而表示字符的字节只能是从0到255之间的一些特殊数字,一些数字是在任何字符字节中不可能存在的。如果文件中的每个字节或每相邻的几个字节的数据都可以表示成某种字符,就可以称这个文件为文本文件。因此文本文件是二进制文件的一种特例。因此可以简单的认为,如果一个文件中除了文本数据外不包括其他二进制数据,可称该文件为文本文件,其余的为二进制文件。Reader与Writer类主要用于读取文本格式的内容,而InputStream与OutputStream主要用于读取二进制文件。
编程举例:用FileWriter类向文件中写入一个字符串,然后用FileReader读取写入的内容。
import java.io.*;
public class FileStreamTest {
public static void main (String[] args)throws Exception{
FileWriter out=new FileWriter("hello.txt");
out.write("hello java"); //可以直接写入字符串
out.close();
char []buf=new char[1024];//创建字符数组
FileReader in=new FileReader("hello.txt");
int len=in.read(buf);//不能直接读取字符串
System.out.println(new String(buf,0,len));
in.close();
}
}
写入一个字节的write方法不会将写入的字节刷新到目标IO设备中,而对于写入多个字节的write方法,则在调用此方法时,内部就会刷新一次。写入字符串的write方法内部都不会刷新。
PipedInputStream与PipedOutputStream类
PipedInputStream与PipedOutputStream类用于在应用程序中的创建管道通信。一个PipedInputStream的实例对象必须与一个PipedOutputStream类实例对象进行连接而形成一个通信管道。PipedOutputStream可以向管道中写入数据,而PipedInputStream从中读取数据。这两个类主要用于实现线程之间的通信,一个线程的PipedInputStream对像能从另一个线程的PipedOutputStream对象中读取数据。
变成举例:
而PipedInputStream和Pipedreader类用于处理字符数据的管道通信。
使用管道流类,可以实现各个程序模块之间的松耦合通信。这样我们可以在程序中灵活地将多个模块的输入输出流相连,然后拼装成满足各种需要的程序。
ByteArrayInputStream与ByteArrayOutputStream类
ByteArrayInputStream与ByteArrayOutputStream类用于以IO流的方式来完成对字节数组的内容的读写,来支持类似内存虚拟文件或者内存映像文件的功能。这两个类对于要创建临时性文件的程序以及网络数据的传输、数据压缩后的传输等可以提高运行的的效率,可以不用访问磁盘。同样有StringReader与StringWriter类以字符IO流的方式处理字符串。
ByteArrayInputStream的两个构造函数:
ByteArrayInputStream(byte[] buf):使用字节数组当中的所有数据作为数据源,程序就可以像输入流一样读取其中的数据。
ByteArrayInputStream(byte[] buf,int offset,int length)
ByteArrayOutputStream的两个构造函数:
ByteArrayOutputStream() :创建一个有32个字节的缓冲区。
ByteArrayOutputStream(int):根据指定大小创建缓冲区。
以上两个构造函数创建的缓冲区,都会随着输入的数据的增多而动态增长。在创建完缓冲区以后,程序就会像对待文件一样向其中写入内容。
编程举例:编写一个函数,把输入流中所有英文字母变成大写字母,然后将结果写入到一个输出流对象。用这个函数来将一个字符串中的所有字符转换成大写。
import java.io.*;
public class ByteArrayTest {
public static void main(String[] args) {
String tmp="hello java";
byte[] src=tmp.getBytes();
ByteArrayInputStream input=new ByteArrayInputStream(src);
ByteArrayOutputStream output=new ByteArrayOutputStream();
transform(input,output);
byte[] result=output.toByteArray();
System.out.println(new String(result));
try
{
input.close();
output.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void transform(InputStream in,OutputStream out)
{
int ch=0;
try
{
while((ch=in.read())!=-1)
{
int upCh=Character.toUpperCase((char)ch);
out.write(upCh);
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
System.in连接到键盘,是InputStream类型的实例对象。System.out连接到显示器,是PrintStream类的实例对象。System.out.println():方法执行时,会自动刷新。
不管各种底层物理设备用什么方式实现数据的终止点,InputStream的read方法总是返回-1来表示输入流的结束。
在Windows下。按下ctrl+z组合键可以产生键盘输入流的结束标记,在linux下,则是按下ctrl+d组合键来产生键盘输入流的结束标记。
字符编码
ASCII码(美国标准信息交换码)中每个字符占一个字节,最高bit位为0,即这些数字在0到127之间。
而在中国大陆每一个中文字符都用两个字节的数字表示,每个字节的的最高位bit都为1,这种编码规则成为GB2312(国标码)。在GB2312的基础上,对更多的中文字符(包括繁体)进行了编码,新的编码规则称为GBK,即GB2312为GBK的子集。GBK就称为系统的本地字符集。
同一个字符在采用不同的编码集的时候,会对应不同的数字。ISO(国际标准化组织)将全世界所有的符号进行了统一的编码,称之为Unicode编码。Unicode编码的字符都占有两个字节的大小,对于ASCII码所表示的字符,只是简单地在ASCII码原来占用一个字节前面,增加一个所有bit为0的字节。
UTF-8编码是对不同范围的字符用不同的字节个数来表示的编码方式,ASCII码字符保持原样,仍然只占有一个字节,对于其他国家的字符,UTF-8使用两个或三个字节来表示,使用UTF-8编码的文件,通常都要用EF BB BF作为文件开头的三个字节数据。当字符占一个字节时,最高位(最左边,下同)为0;当字符占两个字节时,左边第一个字节的高位为110,第二个字节的高位为10; 当字符占三个字节时,左边第一个字节的高位为1110,后两个字节的高位为10。
UTF-8编码不会出现内容为0x00的字节,即使在传输中丢失字节也会判断出以后字符的开始字节,可以直接处理使用ASCII码的英文文档。
UTF-16编码是在Unicode基础上进行了一些细节上的扩充,增加了对Unicode编码没有包括的那些字符的表示方式。并没有影响Unicode编码所包括的那些字符,也就是一个使用Unicode编码的字符就是UTF-16格式的。UTF-16扩充的字符,占用四个字节,数值范围为0xD800-0xDFFF。
在不同的体系结构的计算机中,UTF-16编码的Unicode字符在内存中的字节存储顺序是不同的。
UTF-16编码的文件通常使用字节顺序标记来说明字符中的字节存储顺序(高位在前还是地位在前)如果文件是以0xFE 0xFF这两个字节开头,则表明文本其余部分是Big-Endian (重要的字节在前)的,如果文件是以0xFE 0xFE这两个字节开头,则表明文本其余部分是little-Endian的。
变成举例:
查看中文字符的Unicode码:
String str =”中国”;
for(int i=0;i<str.length();i++)
System.out.println(Integer.toHexString((int)str.charAt(i)));
其中toHexString输出数据的十六进制形式。
查看中文字符的GB2312码:
bytep[] buf=str.getBytes(“gb2312”)
将Unicode码转变为GB2312码,然后以数组形式返回。无参数时调用系统默认的字符集。
for(int i=0;i<buf.length();i++)
System.out.println(Integer.toHexString(byte[i]));
要想在屏幕上打印出中文字符,写入到屏幕输出流的应是GB2312码。