前言:
接着前面的内容,我们来继续学习Java,我们来了解一下多线程与输入输出
多线程
Java中的多线程技术:
每个Java程序都有一个缺省的主线程。对于Application,主线程是main()方法执行的线索。对于Applet,主线程是浏览器加载并执行Java小程序。要实现多线程,必须在主线程中创建新的线程对象。
Java语言使用Thread类及其子类的对象来表示线程。新建的线程在它的一个完整的生命周期中通常要经历新生、就绪、运行、阻塞和死亡等五种状态。
- 新生状态:
当用new和某线程的构造方法创建一个新对象后,这个线程对象处于新生状态,此时它有了自己响应的内存空间,并已经被初始化。处于该状态的线程可以通过调用start()方法进入就绪状态。
- 就绪状态
处于就绪状态的线程已经具备了运行的条件,但尚未得到CPU资源,因而它将进入线程队列排队,等待系统为它分配CPU。一旦获得了CPU资源,该线程就进入运行状态,并自动调用自己的run()方法。此时,它脱离创建它的主线程,独立开始了自己的线程
- 运行状态
进入运行状态的线程执行自己的run方法中的代码。遇到下列情况将终止run方法的执行:
- 终止操作。调用当前线程的stop方法或destroy方法进入死亡状态
- 等待操作。调用当前线程的join(millis)方法或wait(millis)方法进入阻塞状态,当线程进入阻塞状态,在millis毫秒可由其他线程调用notify或notifyAll方法将其唤醒,进入就绪状态。在millis内若不被唤醒,则需等待到当前线程结束。
- 睡眠操作。调用sleep(millis)方法来实现。当前线程停止执行后,会处于阻塞状态,睡眠millis(毫秒)之后重新进入就绪状态
- 挂起操作。通过调用suspend来实现。将当前线程挂起,进入阻塞状态,之后当其他线程调用当前线程的resume方法后,才使其进入就绪状态。
- 退让操作。通过调用yield方法来实现。当前线程放弃执行,进入就绪状态
- 当前线程要求I/O时,则进入阻塞状态
- 若分配给当前线程的时间片用完,则当前线程进入就绪状态,若当前线程的run方法执行完,则线程进入死亡状态
- 阻塞状态
一个正在执行的线程在某些特殊情况下,如执行了suspend、join、sleep方法,或等待I/O设备的使用权,那么它将让出CPU并终止自己的执行,进入阻塞状态。阻塞时他就不能进入就绪队列,只有当引起阻塞状态的原因被消除时,线程才可以转入就绪状态,重新进入到线程队列中排队等待CPU资源,以便从原终止处开始继续进行
- 死亡状态
处于死亡状态的线程将永远不再执行。线程死亡有两个原因:一是正常运行的线程完成了它的全部工作。二是线程被提前强制性地终止了。例如,通过执行stop或destroy方法来终止线程。
通过集成Thread类方式创建线程:
示例:
package 测试;
import java.util.Calendar;
class test extends Thread{
int pauseTime;
String name;
public test(int hTime,String hStr) {
pauseTime=hTime;
name=hStr;
}
public void run() {
//Calendar是Java系统提供的日期时间类的类型标识符
Calendar now;
int hour,minute,second;
for(int i=1;i<10;i++) {
try {
//得到系统时间
now=Calendar.getInstance();
//取小时值
hour=now.get(Calendar.HOUR);
//取分值
minute=now.get(Calendar.MINUTE);
//取秒值
second=now.get(Calendar.SECOND);
System.out.println(" "+name+"时间:"+hour+":"+minute+":"+second);
Thread.sleep(pauseTime);
}
catch(Exception e){
System.out.println(name+":"+"线程错误"+e);
}
}
}
static public void main(String[] args) {
//线程A执行一次后睡眠2000毫秒
test myThread1=new test(2000,"线程A");
myThread1.start();
//线程B执行一次后睡眠1000毫秒
test myThread2=new test(1000,"线程B");
myThread2.start();
}
}
通过实现Runnable接口方式创建线程:
创建线程对象的另一个途径就是实现Runnable接口,而Runnable接口只有一个方法run(),用户新建线程的操作就由这个方法来决定。run()方法必须由实现此接口的类来实现。定义好run()方法之后,当用户程序需要建立新线程时,只要以这个实现了run()方法的类为参数创建系统类Thread的对象,就可以把用户实现的run方法继承过来。
示例:通过创建两个线程实现“Java Now!”与矩形框在屏幕上呈相反方向不停走动。
这个示例需要三个文件:CString.java、CSquare.java、test.java
#test.java
package 测试;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.JApplet;
public class test extends JApplet{
@Override
public void init() {
//得到窗口容器对象
Container cp=getContentPane();
//创建JPanel对象
CString pa=new CString();
//创建JPanel对象
CSquare pa1=new CSquare();
//设置pa对象的尺寸
pa.setPreferredSize(new Dimension(300,150));
//设置pa的对象背景颜色
pa.setBackground(Color.cyan);
pa1.setPreferredSize(new Dimension(300,150));
//设置pa1的对象背景颜色
pa1.setBackground(Color.cyan);
//cp容器的布局为BorderLayout,添加pa及pa1的对象到cp容器中
cp.add(pa,BorderLayout.NORTH);
cp.add(pa1,BorderLayout.SOUTH);
}
}
#CString.java
package 测试;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class CString extends JPanel implements Runnable{
int x=10,y=30;
//创建字符串对象
String Message="Java Now!";
//创建字体对象
Font f=new Font("TimesRoman",Font.BOLD,24);
//以这个实现了run()方法的类为参数创建系统类Thread的对象
//就可以把Runnable的方法继承过来
Thread th1=new Thread(this);
public CString() {
start();
}
private void start() {
th1.start();
}
@Override
public void run() {
while(true) {
x=x-40;
if(x<=0) {
x=300;
}
//repaint()方法调用paint()方法重画字符串
repaint();
try {
Thread.sleep(500);
}
catch(InterruptedException e){
}
}
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2=(Graphics2D)g;
g2.setFont(f);
g2.drawString(Message, x, y);
}
}
#CSquare.java
package 测试;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JPanel;
public class CSquare extends JPanel implements Runnable{
int x1,y1,w1,h1;
Thread th2=new Thread(this);
public CSquare() {
x1=5;
y1=100;
w1=40;
h1=40;
start();
}
void start() {
th2.start();
}
@Override
public void run() {
while(true) {
x1=x1+45;
if(x1>=250)
x1=0;
//repaint方法调用paint()方法重新画矩阵框
repaint();
try{
//线程二睡眠500毫秒
Thread.sleep(500);
}
catch(InterruptedException e) {
}
}
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2=(Graphics2D)g;
Rectangle2D.Double rec1=new Rectangle2D.Double(x1,y1,w1,h1);
g2.draw(rec1);
}
}
多线程的管理:
在单CPU计算机上运行多线程程序,或者当线程数多于处理的数目时,势必存在多个线程争用CPU的情况,这时需要提供一种机制来合理的分配CPU,使多个线程有条不紊、无不干扰的工作,这种机制称为调度。在Java运行系统中,由线程调度器对线程按优先级进行调度。线程调度器中写好了相应的调度算法,当有多个线程在同一时刻处于就绪状态时,线程调度器就会选择优先级最高的线程运行。
Java的线程调度算法可分为两种:优先抢占式调度,轮转调度。
线程优先级:
在Java系统中,运行的每个线程都有优先级(一个1~10的正整数,数值越大越优先)。未设定优先级的线程取缺省值5。Java线程的优先级设置遵从下述原则:
- 线程创建时,子线程继承父线程的优先级
- 线程创建时,可在程序中通过setPriority()方法改变线程的优先级
- 线程的优先级是1~10之间的正整数,并用标识符常量MIN_PRIORITY表示优先级为1,用NORM_PRIORITY表示优先级为5,用MAX_PRIORITY表示优先级为10。其他级别的优先级既可以直接用1~10之间的正整数来设置,也可以在标识符常量的基础上加一个常数,例如
setPriority(Thread.NORM_PRIORITY+3)
这个值即代表8。
示例:
package 测试;
class test{
public static void main(String args[]) {
//创建A线程
Thread First=new MyThread("A");
//A线程优先级为1
First.setPriority(Thread.MIN_PRIORITY);
Thread Second=new MyThread("B");
Second.setPriority(Thread.NORM_PRIORITY+1);
Thread Third=new MyThread("C");
Third.setPriority(Thread.MAX_PRIORITY);
First.start();
Second.start();
Third.start();
}
}
class MyThread extends Thread{
String message;
MyThread(String message){
this.message=message;
}
public void run() {
for(int i=0;i<2;i++) {
System.out.println(message+" "+getPriority());
}
}
}
输出:
C 10
C 10
A 1
B 6
B 6
A 1
线程同步:
由于Java支持多线程,具有并发功能,从而大大提高了计算机的处理能力,在各线程之间不存在共享资源的情况下,几个线程的执行顺序可以是随机的。但是,当两个或两个以上的线程需要共享同一资源时,线程之间的执行顺序就需要协调,并且在某一线程占用资源时,其他线程就要等待。
可以这么理解,Java第一个线程是一个生产者,第二个线程是一个消费者,中间资源是一个货架。当货架被生产者占用(放货),消费者不能去用。当货架被消费者占用(消费),生产者不能去用,在这个问题中,两个线程要共享货架这一临界资源,需要在某些时刻(货空/货满)协调他们的工作,即货空时消费者应等待,货满时生产者应等待,这种机制在操作系统中称为线程间的同步。在同步机制中,将那些访问临界资源的程序段称为临界区。
在Java中,临界区是用关键字“synchronized”来标注,并通过一个称为监控器的系统软件来管理的。当执行被冠以“synchronized”的程序段即临界区程序时,监控器将这段程序(访问的临界资源)加锁,此时,称该线程占有临界资源,知道这段程序执行完,才释放锁。只有锁被释放后,其他线程才可以访问这些临界资源。用关键字 synchronized 定义临界区的语句形式是:
synchronize (expression) statement
其中,expression 代表类的名字,是可选项。
statement 可以是一个方法;也可以是一个语句或一个语句块。
示例:
package 测试;
public class test{
public static void main(String args[]) {
//h为键控器
HoldInt h=new HoldInt();
//生产者
ProduceInt p=new ProduceInt(h);
//消费者
ConsumeInt c=new ConsumeInt(h);
p.start();
c.start();
}
}
class HoldInt{
private int sharedInt;
//writeAble=true表示生产者线程能产生新数据
private boolean writeAble=true;
//临界区程序段,也称为同步方法
public synchronized void set(int val) {
while(!writeAble) {
//生产者线程不能生产新数据时进入等待
try {
wait();
}catch(InterruptedException e) {}
}
//生产者被唤醒后继续执行下面的语句
writeAble=false;
sharedInt=val;
notify();
}
//同步方法
public synchronized int get() {
while(writeAble) {
//消费者线程不能消费数据时进入等待状态
try {
wait();
}catch(InterruptedException e) {}
}
//消费者被唤醒后继续执行下面的语句
writeAble=true;
notify();
return sharedInt;
}
}
//生产者线程
class ProduceInt extends Thread{
private HoldInt hi;
public ProduceInt(HoldInt hiForm) {
hi=hiForm;
}
public void run() {
for(int i=1;i<=4;i++) {
hi.set(i);
System.out.println("产生的新数据是:"+i);
}
}
}
//消费者线程
class ConsumeInt extends Thread{
private HoldInt hi;
public ConsumeInt(HoldInt hiForm) {
hi=hiForm;
}
public void run() {
for(int i=1;i<=4;i++) {
int val=hi.get();
System.out.println("读到的数据是:"+val);
}
}
}
输出:
产生的新数据是:1
读到的数据是:1
读到的数据是:2
产生的新数据是:2
产生的新数据是:3
产生的新数据是:4
读到的数据是:3
读到的数据是:4
注解:shareInt就是我们的共享资源,一开始writeAble是true的,在这个程序中,共享数据的shareInt方法set()和get()头部的修饰符 synchronized 使HoldInt的每个对象都有一把锁。当ProduceInt对象调用set方法时,HoldInt对象就被锁定(所以即使是不同线程,ConsumeInt也不能调用HoldInt对象的get方法),若set()方法中的writeAble的值为false,则调用set()方法中的wait()方法,把调用set()方法的ProduceInt对象放到HoldInt对象的等待队列中,并将HoldInt对象的锁打开,使该对象的其他synchronized方法可被调用。这个ProduceInt对象就一直在等待队列中等待,直到被唤醒使他进入就绪状态,等待分配CPU,当Producement对象再次进入运行状态时,就从刚刚的wait往后继续执行,如此往复。
线程组:
Java系统的每一个线程都属于某一个线程组。采用线程组结构以后,可以对多个线程进行集中管理。比如,可以同时启动、挂起或者终止一个线程组中的全部线程。Java系统专门在java.lang包中提供了ThreadGroup类来实现对线程组的管理功能。
大多数情况下,一个线程属于哪一个线程组是由编译人员在程序中指定的,若编译人员没有指定,则Java系统会自动将这些线程归于“main”线程组。main线程组是java系统启动时创建的。一个线程组不仅可以包含多个线程,而且线程组中还可以包含其他的线程组。
输入与输出:
基本输入/输出流类
流是数据的有序序列,它既可以是未加工的原始二进制数据,也可以是经过一定编码处理后的符合某种规定格式的特定数据。在Java.io包中,基本输入/输出流类可按读/写数据的不同类型分为两种:字节流和字符流
- 字节流用于读/写字节类型的数据(包括ASCII表中的字符)。字节流类可分为表示输入流的InputStream类及其子类,表示输出流的额OutputStream类及其子类
- 字符流用于读/写Unicode字符。它包括表示输入流的Reader类及其子类,表示输出流的Writer类及其子类
用于ASCII的字节流:
我们来看个例子:键盘输入数据的存储
package 测试;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
public class test{
public static void main(String args[]) {
int count;
byte b[]=new byte[256];
String str;
//输入缓冲区流的对象
BufferedInputStream bis=new BufferedInputStream(System.in);
//根据输入缓冲区构造字节流输入对象
DataInputStream in=new DataInputStream(bis);
try {
//判断当前输入流是否支持mark和reset方法
if(in.markSupported()) {
System.out.println("支持mark");
System.out.println("输出字符串,按Enter结束");
//在输入流的当前位置上设置标记,并保留256位
in.mark(256);
//读键盘输入的数据到b数组,count得到输入长度
count=in.read(b);
System.out.println("读入字符数"+count);
//将b数组转换为字符串
str=new String(b,0,count);
System.out.println("输入的字符串为:"+str);
//重新回到标记处读取数据
in.reset();
//读前两个字符
in.read(b,0,2);
str=new String(b,0,2);
System.out.println("字符串的前两个为:"+str);
in.reset();
in.skip(count/2);
in.read(b,0,count/2);
str=new String(b,0,count/2);
System.out.println("字符串的后半段"+str);
}else {
System.out.println("不支持mark");
}
bis.close();
in.close();
}catch(IOException E) {
System.out.println("发生I/O错误!");
}
}
}
输出:
支持mark
输出字符串,按Enter结束
sajdflsjadf
读入字符数13
输入的字符串为:sajdflsjadf
字符串的前两个为:sa
字符串的后半段sjadf
原理:
从键盘读入字符串并由屏幕输出:
package 测试;
import java.io.*;
public class test{
public static void main(String args[]) {
int count;
byte b[]=new byte[256];
//输入缓冲区流对象
BufferedInputStream in=new BufferedInputStream(System.in);
//输出缓冲区流对象
BufferedOutputStream bout=new BufferedOutputStream(System.out);
//输出流对象
DataOutputStream out=new DataOutputStream(bout);
//输出流对象
PrintStream p=new PrintStream(System.out);
try {
p.println("请输入字符串");
//从键盘读入数据给b数组,count得到b的长度
count=in.read(b);
in.close();
p.println("读入字节数:"+count);
p.println("输入的字符串为:");
//将b数组从0位置开始的count长度的字节写到out对象中
out.write(b,0,count);
//将缓冲流缓冲区中的数据输出到屏幕上
bout.flush();
p.close();
out.close();
}catch(IOException e) {
System.out.println("发生I/O错误!");
}
}
}
输出就不演示了,看执行过程:
创建BufferedInputStream输入缓冲区类in的对象的构造方法中的参数“System.in”是InputStream类的标准输入流,表示从键盘上读入数据到in的对象中,程序通过in.read方法将in的对象数据写入到b数组中。
创建BufferedOutputStream输出流类bout的对象的构造方法中的参数“System.out”是OutputStream类的标准输出流,表示向屏幕输出。
创建DataOutputStream输出流类out的对象的构造方法中的参数“bout”,表示将out的对象与bout的对象组合成输出流链,这样可以实现动态地增加输出流的功能。
程序的运行过程是通过out.write(b,0,count)方法,将b数组的数据输出到out的对象中,再通过组合输出流链将out的对象的数据输出到bout的对象中,当引用bout.flush,系统自动将缓冲输出流的数据写到屏幕上。
PrintStream类是打印输出流。Java的标准输出System.out是PrintStream的子类,通过引用print()方法或println()方法可向屏幕输出不同的数据
用于Unicode的字符流:
示例:利用InputStreamReader类、BufferedReader类、OutputStreamWriter类实现从键盘输入字符串,再输出到屏幕上。利用CharArrayReader类、CharArrayWriter类实现存储器读/写操作
package 测试;
import java.io.*;
public class test{
public static void main(String args[]) {
char c1[],c2[];
String str;
CharArrayReader cin;
CharArrayWriter cout;
//将键盘上输入的数据放入到BufferedReader类in的对象中
InputStreamReader sin=new InputStreamReader(System.in);
BufferedReader in=new BufferedReader(sin);
//屏幕输出
OutputStreamWriter out=new OutputStreamWriter(System.out);
try {
System.out.println("请输入一个字符串,请按Enter结束");
//读入字符串
str=in.readLine();
//将字符串转换成字符数组
c1=str.toCharArray();
//创建CharArrayReader类cin的对象,并与输入流c1数组绑定
cin=new CharArrayReader(c1);
cout=new CharArrayWriter();
//读cin的对象数据内容到cout的对象中
//(cin.ready)返回输入流是否是可读信息
while(cin.ready()) {
//读cin中的一个字符并写到cout中,读/写指针后移一个字符的位置
cout.write(cin.read());
}
System.out.print("c2=");
//将cout的对象数据写到字符数组c2中
c2=cout.toCharArray();
//用c2字符数组创建字符串对象并打印
System.out.println(new String(c2));
System.out.print("将cout的对象数据写入out的对象中,并输出:");
cout.writeTo(out);
//强制输出out中的数据到屏幕
out.flush();
}catch(IOException E) {
System.out.println("I/O错误!");
}
}
}
输出:
请输入一个字符串,请按Enter结束
asdfsdfsa
c2=asdfsdfsa
将cout的对象数据写入out的对象中,并输出asdfsdfsa
BufferedReader 类和 InputStreamReader 类都是 Reader 的子类。但由于 InputStreamReader 类的对象每读一次都要用read()方法进行字节和字符的转化,效率很低;而BufferedReader类是具有缓冲功能的字符输入流类,可实现字符、数组和行的高效读取。若要用readLine()方法一次一行地进行读取,需要将 System.in 包装成 BufferedReader 来使用,此时必须用 InputStreamReader 把 System.in 转换成 Reader 。
创建 InputStreamReader 类sin的对象的构造方法的参数是 “System.in” ,创建BufferedReader 类in的对象的构造方法的参数是“sin”,这表示建立sin的对象与in的对象的组合式链接通道。
程序运行过程是将键盘上输入的数据放入到sin的对象中进行字节到字符的转化,并输入到in的对象中,再通过“str=in.readLine()”语句,实现将 in 的对象的数据按行读取并放入 str 中。
CharArrayReader 和 CharArrayWriter 类可以将字符数组当作字符数据输入或输出的来源。根据这个特性,可以将文本文件的内容读入字符数组,对字符数组作随机存储,然后写回文本。
writeTo()方法不能直接将结果输出到屏幕上,本书借助 OutputStreamWriter 类(这个类是字符流到字节流的转换桥梁,它的方法可以将字符转换为字节再写入字节流,从而实现外部输出)来完成。
文件的输入/输出
在计算机系统中,需要长期保留的数据是以文件的形式存放在磁盘、磁带等外部存储设备中的。程序运行时常常要从文件中读取数据,同时也要把需要长期保留的数据写入文件中。我们来介绍Java的文件与目录管理。
File类:
Java语言的java.io包中的File类是专门用来管理磁盘文件和目录的。每个File类的对象表示一个磁盘文件或目录,其对象属性中包含了文件或目录的相关信息,如文件或目录的名称、文件的长度、目录中所含文件的个数等。调用File类的方法可以完成对文件或目录的常用管理操作,如创建文件或目录,删除文件或目录,查看文件的有关信息等。
示例:获取文件的文件名、长度、大小等特性:
package 测试;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
public class test{
public static void main(String[] args) {
String Path;
//键盘输入
InputStreamReader din=new InputStreamReader(System.in);
BufferedReader in=new BufferedReader(din);
try {
System.out.print("请输入相对或绝对路径:");
//读取输入
Path=in.readLine();
File f=new File(Path);
System.out.println("路径:"+f.getParent());
System.out.println("档案:"+f.getName());
System.out.println("绝对路径:"+f.getAbsolutePath());
System.out.println("文件大小:"+f.length());
System.out.println("是否为文件:"+(f.isFile()?"是":"否"));
System.out.println("是否为目录:"+(f.isDirectory()?"是":"否"));
System.out.println("是否为隐藏:"+(f.isHidden()?"是":"否"));
System.out.println("是否为读取:"+(f.canRead()?"是":"否"));
System.out.println("是否为写入:"+(f.canWrite()?"是":"否"));
System.out.println("最后修改时间:"+new Date(f.lastModified()));
}catch(IOException e) {
System.out.println("I/O错误!");
}
}
}
这个是读取已经存在的文件,注意,输入路径的时候不要有空格
示例:显示“E:/Java”文件夹的内容
package 测试;
import java.io.*;
import java.util.*;
public class test{
public static void main(String[] args) {
File ListFile[];
long totalSize=0;
int FileCount=0, DirectoryCount=0;
//生成File对象
File f=new File("E:/Java");
System.out.println("目录:"+f.getParent()+"\n");
if(f.exists()!=true) {
System.out.println(f.getPath()+"不存在!");
return;
}
//若路径为目录
if(f.isDirectory()) {
//得到文件列表
ListFile=f.listFiles();
for(int i=0;i<ListFile.length;i++) {
System.out.print((ListFile[i].isDirectory()?"D":"X")+" ");
System.out.print(new Date(ListFile[i].lastModified())+" ");
System.out.print(ListFile[i].length()+" ");
System.out.print(ListFile[i].getName()+"\n");
if(ListFile[i].isFile())
FileCount++;
else
DirectoryCount++;
totalSize+=ListFile[i].length();
}
}else { //路径为文件时
System.out.print((f.isDirectory()?"D":"X")+" ");
System.out.print(new Date(f.lastModified())+" ");
System.out.print(f.length()+" ");
System.out.print(f.getName()+"\n");
FileCount++;
totalSize+=f.length();
}
System.out.println("\n\t\t 目录数:"+DirectoryCount);
System.out.println("\t\t 文件数:"+FileCount);
System.out.println("\t\t 总字节:"+totalSize);
}
}
我的输出:
FileInputStream 类和 FileOutputStream 类:
程序中会经常用到文件的读/写操作。例如,从已经存在的数据文件中读入数据,或者将程序中产生的大量数据写入磁盘文件中。这时我们就需要使用文件输入/输出流类。Java 系统提供的 FileInputStream 类是用于读取文件中的字节数据的字节文件输入流类;FileOutputStream类是用于向文件写入字节数据的字节文件输出流类。
字节文件输入/输出流的读写:
利用字节文件输入/输出流完成磁盘文件的读/写,首先要利用文件名字符串或File对象创建输入输出流,其次是从文件输入/输出流中读/写数据。从文件输入/输出流中读/写数据有以下两种方式:
- 用文件输入/输出类自身的读/写功能完成文件的读/写操作: FileInputStream 类和 FileOutputStream 类自身的读/写功能是直接从父类 InputStream 和 OutputStream 那里继承来的,并未做任何功能的补充。
- 配合其他功能较强的输入/输出流完成文件的读/写操作:以 FileInputStream 和 FileOutputStream 为数据源,完成与磁盘文件的映射连接后,再创建其他流类的对象,如 DataInputStream 类和 DataOutputStream 类,这样就可以从 FileInputStream 和 FileOutputStream 对象中读/写数据了。
示例:直接利用 FileInputStream 类和 FileOutputStream 类完成从键盘读入数据写入文件中,再从写入的文件中读出数据打印到屏幕上的操作。
package 测试;
import java.io.*;
public class test{
public static void main(String[] args) {
char c;
int c1;
//在当前目录下建目录,也可用绝对路径
File filePath=new File("d:/");
//若目录不存在,则建之
if(!filePath.exists())
filePath.mkdir();
//在指定目录下建文件类对象
File f1=new File(filePath,"d1.txt");
try {
FileOutputStream fout=new FileOutputStream(f1);
System.out.println("请输入字符,输入结束按#:");
//将从键盘输入的字符写入磁盘文件
while((c=(char)System.in.read())!='#') {
fout.write(c);
}
fout.close();
System.out.println("\n打印从磁盘读入的数据");
FileInputStream fin=new FileInputStream(f1);
//磁盘文件读入程序
while((c1=fin.read())!=-1) {
//将从磁盘读入的数据打印到屏幕上(将int变量c1强制转换为char)
System.out.print((char)c1);
}
fin.close();
}catch(FileNotFoundException e){
System.out.println(e);
}catch(IOException e) {
System.out.println(e);
}
}
}
然后就可以通过控制台的输入给文件增加内容。
示例:利用FileInputStream 和 FileOutputStream 输入/输出流,再套接上 DataInputStream 类和 DataOutputStream 类输入/输出流完成文件的读/写操作。本程序是将程序中的数据写到“t1.txt”文件,再从该文件中读出,输出到屏幕上。
package 测试;
import java.io.*;
public class test{
public static void main(String[] args) {
boolean lo=true;
short si=-32768;
int i=65534;
long l=134567;
float f=(float)1.4567;
double d=3.14159265359;
String str1="ABCD";
String str2="Java 语言数学";
try {
FileOutputStream fout=new FileOutputStream("t1.txt");
//文件输出流对象为参数
DataOutputStream out=new DataOutputStream(fout);
FileInputStream fin=new FileInputStream("t1.txt");
DataInputStream in=new DataInputStream(fin);
//将数据写入t1.txt文件
out.writeBoolean(lo);
out.writeShort(si);
out.writeByte(i);
out.writeInt(i);
out.writeLong(l);
out.writeFloat(f);
out.writeDouble(d);
out.writeBytes(str1);
out.writeUTF(str2);
out.close();
//将t1.txt文件的数据读出,并输出到屏幕
System.out.println("Boolean lo="+in.readBoolean());
System.out.println("Short si="+in.readShort());
System.out.println("Byte i="+in.readByte());
System.out.println("Int i="+in.readInt());
System.out.println("Long l="+in.readLong());
System.out.println("Float f="+in.readFloat());
System.out.println("Double d="+in.readDouble());
byte b[]=new byte[4];
in.readFully(b);
System.out.print("str1=");
for(int j=0;j<4;j++) {
System.out.print((char)b[j]);
}
System.out.println();
System.out.println("str2="+in.readUTF());
in.close();
}catch(IOException e) {
System.out.println(e.toString());
}
}
}
输出:
Boolean lo=true
Short si=-32768
Byte i=-2
Int i=65534
Long l=134567
Float f=1.4567
Double d=3.14159265359
str1=ABCD
str2=Java 语言数学
FileReader类和FileWriter类
FileReader类和FileWriter类用于读取文件和向文件写入字符数据。
示例:复制文件
package 测试;
import java.io.*;
public class test{
public static void main(String[] args) {
String temp;
//创建File对象
File sourceFile,targetFile;
BufferedReader source;
BufferedWriter target;
try {
InputStreamReader din=new InputStreamReader(System.in);
BufferedReader in=new BufferedReader(din);
System.out.println("请输入来源文件路径");
sourceFile=new File(in.readLine());
source =new BufferedReader(new FileReader(sourceFile));
System.out.println("请输入目标文件路径");
targetFile=new File(in.readLine());
target =new BufferedWriter(new FileWriter(targetFile));
System.out.print("确定要复制?(y/n)");
if((in.readLine()).equals("y")) {
//源文件的内容不为空
while((temp=source.readLine())!=null) {
//向目标文件写入
target.write(temp);
target.newLine();
target.flush();
}
System.out.println("复制文件完成!!");
}else {
System.out.println("复制文件失败!!!");
return;
}
din.close();
in.close();
}catch(IOException e) {
System.out.println("I/O错误!");
}
}
}
测试输出:
请输入来源文件路径
t1.txt
请输入目标文件路径
d:/t2.txt
确定要复制?(y/n)y
复制文件完成!!
程序中使用了FileReader类输入流链接BufferedReader类缓冲区输入流、FileWriter类输出流链接BufferedWriter类缓冲区输出流的策略,加快了复制文件的速度。
注意flush:
BufferedWriter是缓冲输入流,意思是调用BufferedWriter的write方法时候。数据是先写入到缓冲区里,并没有直接写入到目的文件里。必须调用BufferedWriter的flush()方法。这个方法会刷新一下该缓冲流,也就是会把数据写入到目的文件里。或者你可以调用BufferedWriter的close()方法,该方法会在关闭该输入流之前先刷新一下该缓冲流。也会把数据写入到目的文件里。如果没有在里面的for()循环中添加 bw.flush();这句话,在if 的时候重新 new BufferedWriter(); 就把原来bw(缓冲区)中的覆盖掉了。于是就不能写进文件字符。
RandomAccessFile类
前面介绍的文件存取方式属于顺序存储,即只能从文件的起始位置向后顺序读/写。java.io包提供的RandomAccessFile类是随机文件访问类,该类的对象可以引用与文件位置指针有关的成员方法,读/写任意位置的数据,实现对文件的随机读/写操作。文件的随机存取要比顺序存取更灵活。
从键盘输入五个整数并写入文件t3.txt,再从这个文件中随机读出其中的某个数(由键盘输入确定),将它显示在屏幕上,同时允许用户对这个数进行修改。
package 测试;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
public class test{
public static void main(String[] args) {
int num,a;
long fp;
try {
//键盘输入
InputStreamReader din=new InputStreamReader(System.in);
BufferedReader in=new BufferedReader(din);
//建立随机存取文件(以读写方式打开)
RandomAccessFile rf=new RandomAccessFile("t3.txt", "rw");
System.out.println("请输入五个整数");
int b[]=new int[5];
for(int i=0;i<5;i++) {
System.out.print("第"+(i+1)+"个数 ");
//Integer.parseInt将字符串转换为int
b[i]=Integer.parseInt(in.readLine());
rf.writeInt(b[i]);
}
while(true) {
//移动文件指针到文件头
rf.seek(0);
System.out.println("请输入要显示第几个数(1-5):");
//读入序号
num=Integer.parseInt(in.readLine());
num=num-1;
//每个整数四个字节,计算移动位置
fp=(num)*4;
rf.seek(fp);
a=rf.readInt();
System.out.println("第"+(num+1)+"个数是"+a);
System.out.print("改写此数:");
b[num]=Integer.parseInt(in.readLine());
fp=num*4;
rf.seek(fp);
//写入文件
rf.writeInt(b[num]);
System.out.print("继续吗?(y/n)");
if((in.readLine()).equals("n"))
break;
}
}catch(Exception e) {
System.out.println("I/O错误!");
}
}
}
欢迎访问我的博客 is-hash.com
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢