Java的基本学习(六)——多线程与输入输出

前言:

接着前面的内容,我们来继续学习Java,我们来了解一下多线程与输入输出

 

 

 

 

 

 


多线程

Java中的多线程技术:

每个Java程序都有一个缺省的主线程。对于Application,主线程是main()方法执行的线索。对于Applet,主线程是浏览器加载并执行Java小程序。要实现多线程,必须在主线程中创建新的线程对象。

Java语言使用Thread类及其子类的对象来表示线程。新建的线程在它的一个完整的生命周期中通常要经历新生、就绪、运行、阻塞和死亡等五种状态。

  • 新生状态:

当用new和某线程的构造方法创建一个新对象后,这个线程对象处于新生状态,此时它有了自己响应的内存空间,并已经被初始化。处于该状态的线程可以通过调用start()方法进入就绪状态。

  • 就绪状态

处于就绪状态的线程已经具备了运行的条件,但尚未得到CPU资源,因而它将进入线程队列排队,等待系统为它分配CPU。一旦获得了CPU资源,该线程就进入运行状态,并自动调用自己的run()方法。此时,它脱离创建它的主线程,独立开始了自己的线程

  • 运行状态

进入运行状态的线程执行自己的run方法中的代码。遇到下列情况将终止run方法的执行:

  1. 终止操作。调用当前线程的stop方法或destroy方法进入死亡状态
  2. 等待操作。调用当前线程的join(millis)方法或wait(millis)方法进入阻塞状态,当线程进入阻塞状态,在millis毫秒可由其他线程调用notify或notifyAll方法将其唤醒,进入就绪状态。在millis内若不被唤醒,则需等待到当前线程结束。
  3. 睡眠操作。调用sleep(millis)方法来实现。当前线程停止执行后,会处于阻塞状态,睡眠millis(毫秒)之后重新进入就绪状态
  4. 挂起操作。通过调用suspend来实现。将当前线程挂起,进入阻塞状态,之后当其他线程调用当前线程的resume方法后,才使其进入就绪状态。
  5. 退让操作。通过调用yield方法来实现。当前线程放弃执行,进入就绪状态
  6. 当前线程要求I/O时,则进入阻塞状态
  7. 若分配给当前线程的时间片用完,则当前线程进入就绪状态,若当前线程的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对象创建输入输出流,其次是从文件输入/输出流中读/写数据。从文件输入/输出流中读/写数据有以下两种方式:

  1. 用文件输入/输出类自身的读/写功能完成文件的读/写操作: FileInputStream 类和 FileOutputStream 类自身的读/写功能是直接从父类 InputStream 和 OutputStream 那里继承来的,并未做任何功能的补充。
  2. 配合其他功能较强的输入/输出流完成文件的读/写操作:以 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错误!");
		}
	}
} 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

发表评论