IO流:★★★★★,用于处理设备上数据。
流:可以理解数据的流动,就是一个数据流。IO流最终要以对象来体现,对象都存在IO包中。
流也进行分类:
1:输入流(读)和输出流(写)。
2:因为处理的数据不同,分为字节流和字符流。
字节流:处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。
那么为什么要有字符流呢?因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时+ 指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流体系。
注意:流的操作只有两种:读和写。
流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。
字节流:InputStream OutputStream
字符流:Reader Writer
在这四个系统中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都是这个子类的功能名称。
public static void main(String[] args) throws IOException { //读、写都会发生IO异常 /* 1:创建一个字符输出流对象,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。 2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。 3:如果指定位置,出现了同名文件,文件会被覆盖。 */ FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException /* 调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。怎么把数据弄到文件中? */ fw.write("abcde"); fw.flush(); // 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。 fw.close(); // 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。 } close()和flush()的区别: flush():将缓冲区的数据刷到目的地中后,流可以使用。 close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。
io异常的处理方式:io一定要写finally;
FileWriter写入数据的细节:
1:window中的换行符:\r\n两个符号组成。 linux:\n。
2:续写数据,只要在构造函数中传入新的参数true。
3:目录分割符:window \\ /
public static void main(String[] args) { FileWriter fw = null; try { fw = new FileWriter("demo.txt",true); fw.write("abcde"); } catch (IOException e ){ System.out.println(e.toString()+"...."); } finally{ if(fw!=null) try{ fw.close(); } catch (IOException e){ System.out.println("close:"+e.toString()); } } }
FileReader:使用Reader体系,读取一个文本文件中的数据。返回 -1 ,标志读到结尾。
import java.io.*; class FileReaderDemo { public static void main(String[] args) throws IOException { /* 创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相关联。 */ FileReader fr = new FileReader("demo.txt"); int ch = 0; while((ch = fr.read())!= -1) { //条件是没有读到结尾 System.out.println((char)ch); //调用读取流的read方法,读取一个字符。 } fr.close(); } }
读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。
import java.io.*; class FileReaderDemo2 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("demo.txt"); //创建读取流对象和指定文件关联。 //因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。 char[] buf = new char[1024]; int len = 0; while(( len=fr.read(buf)) != -1) { System.out.println(new String(buf,0,len)); } fr.close(); } }
IO中的使用到了一个设计模式:装饰设计模式。
装饰设计模式解决:对一组类进行功能的增强。
包装:写一个类(包装类)对被包装对象进行包装;
* 1、包装类和被包装对象要实现同样的接口;
* 2、包装类要持有一个被包装对象;
* 3、包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现;
字符流:
Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
|---BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
|---LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
|---InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
|---FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
|---CharArrayReader:
|---StringReader:
-------------------------------------------------
Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
|---BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
|---OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
|---FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。
|---PrintWriter:
|---CharArrayWriter:
|---StringWriter:
---------------------------------
字节流:
InputStream:是表示字节输入流的所有类的超类。
|--- FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
|--- FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
|--- BufferedInputStream:该类实现缓冲的输入流。
|--- Stream:
|--- ObjectInputStream:
|--- PipedInputStream:
-----------------------------------------------
OutputStream:此抽象类是表示输出字节流的所有类的超类。
|--- FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
|--- FilterOutputStream:此类是过滤输出流的所有类的超类。
|--- BufferedOutputStream:该类实现缓冲的输出流。
|--- PrintStream:
|--- DataOutputStream:
|--- ObjectOutputStream:
|--- PipedOutputStream:
--------------------------------
缓冲区是提高效率用的,给谁提高呢?
BufferedWriter:是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体的流对象的效率。
FileWriter fw = new FileWriter("bufdemo.txt"); BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。 for(int x=0; x<4; x++){ bufw.write(x+"abc"); bufw.newLine(); //写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。 bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。 } bufw.close();//关闭缓冲区,其实就是在关闭具体的流
BufferedReader:
FileWriter fw = new FileWriter("bufdemo.txt"); BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。 for(int x=0; x<4; x++){ bufw.write(x+"abc"); bufw.newLine(); //写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。 bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。 } bufw.close();//关闭缓冲区,其实就是在关闭具体的流
流对象
其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。
流的操作规律:
1,明确源和目的。
数据源:就是需要读取,可以使用两个体系:InputStream、Reader;
数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer;
2,操作的数据是否是纯文本数据?
如果是:数据源:Reader
数据汇:Writer
如果不是:数据源:InputStream
数据汇:OutputStream
3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?
明确操作的数据设备。
数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。
4,需要在基本操作上附加其他功能吗?比如缓冲。
如果需要就进行装饰。
转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。
转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。
发现转换流有一个子类就是操作文件的字符流对象:
InputStreamReader
|--FileReader
OutputStreamWriter
|--FileWrier
想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。
但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。
FileReader fr = new FileReader("a.txt");
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
以上两句代码功能一致,
如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。
如果需要制定码表,必须用转换流。
转换流 = 字节流+编码表。
转换流的子类File = 字节流 + 默认编码表。
凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。
File类:将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。
File类常见方法:
1:创建。
boolean createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
boolean mkdir():创建此抽象路径名指定的目录。
boolean mkdirs():创建多级目录。
2:删除。
boolean delete():删除此抽象路径名表示的文件或目录。
void deleteOnExit():在虚拟机退出时删除。
注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。
window的删除动作,是从里往外删。注意:java删除文件不走回收站。要慎用。
3:获取.
long length():获取文件大小。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
String getParent():返回此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回 null。
long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
File.pathSeparator:返回当前系统默认的路径分隔符,windows默认为 “;”。
File.Separator:返回当前系统默认的目录分隔符,windows默认为 “\”。
4:判断:
boolean exists():判断文件或者文件夹是否存在。
boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute():测试此抽象路径名是否为绝对路径名。
5:重命名。
boolean renameTo(File dest):可以实现移动的效果。剪切+重命名。
String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。
如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。
递归:就是函数自身调用自身。
什么时候用递归呢?
当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。
简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。
递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误。
其实递归就是在栈内存中不断的加载同一个函数。
Java.util.Properties:一个可以将键值进行持久化存储的对象。Map--Hashtable的子类。
Map
|--Hashtable
|--Properties:用于属性配置文件,键和值都是字符串类型。
特点:1:可以持久化存储数据。2:键值都是字符串。3:一般用于配置文件。
|-- load():将流中的数据加载进集合。
原理:其实就是将读取流和指定文件相关联,并读取一行数据,因为数据是规则的key=value,所以获取一行后,通过 = 对该行数据进行切割,左边就是键,右边就是值,将键、值存储到properties集合中。
|-- store():写入各个项后,刷新输出流。
|-- list():将集合的键值数据列出到指定的目的地。
以下介绍IO包中扩展功能的流对象:基本都是装饰设计模式。
Java.io.outputstream.PrintStream:打印流
1:提供了更多的功能,比如打印方法。可以直接打印任意类型的数据。
2:它有一个自动刷新机制,创建该对象,指定参数,对于指定方法可以自动刷新。
3:它使用的本机默认的字符编码.
4:该流的print方法不抛出IOException。
该对象的构造函数。
PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
PrintStream(File file, String csn) :创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out) :创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush) :创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding) :创建新的打印流。
PrintStream(String fileName) :创建具有指定文件名称且不带自动行刷新的新打印流。
PrintStream(String fileName, String csn)
PrintStream可以操作目的:1:File对象。2:字符串路径。3:字节输出流。
前两个都JDK1.5版本才出现。而且在操作文本文件时,可指定字符编码了。
当目的是一个字节输出流时,如果使用的println方法,可以在printStream对象上加入一个true参数。这样对于println方法可以进行自动的刷新,而不是等待缓冲区满了再刷新。最终print方法都将具体的数据转成字符串,而且都对IO异常进行了内部处理。
既然操作的数据都转成了字符串,那么使用PrintWriter更好一些。因为PrintWrite是字符流的子类,可以直接操作字符数据,同时也可以指定具体的编码。
PrintWriter:具备了PrintStream的特点同时,还有自身特点:
该对象的目的地有四个:1:File对象。2:字符串路径。3:字节输出流。4:字符输出流。
开发时尽量使用PrintWriter。
方法中直接操作文件的第二参数是编码表。
直接操作输出流的,第二参数是自动刷新。
//读取键盘录入将数据转成大写显示在控制台. BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//源:键盘输入 //目的:把数据写到文件中,还想自动刷新。 PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//设置true后自动刷新 String line = null; while((line=bufr.readLine())!=null){ if("over".equals(line)) break; out.println(line.toUpperCase());//转大写输出 } //注意:System.in,System.out这两个标准的输入输出流,在jvm启动时已经存在了。随时可以使用。当jvm结束了,这两个流就结束了。但是,当使用了显示的close方法关闭时,这两个流在提前结束了。 out.close(); bufr.close();
SequenceInputStream:序列流,作用就是将多个读取流合并成一个读取流。实现数据合并。
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
该对象的构造函数参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。
但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。
合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。
import java.io.*; import java.util.*; class SplitFileDemo{ private static final String CFG = ".properties"; private static final String SP = ".part"; public static void main(String[] args) throws IOException{ File file = new File("c:\\0.bmp"); File dir = new File("c:\\partfiles"); meger(dir); } //数据的合并。 public static void meger(File dir)throws IOException{ if(!(dir.exists() && dir.isDirectory())) throw new RuntimeException("指定的目录不存在,或者不是正确的目录"); File[] files = dir.listFiles(new SuffixFilter(CFG)); if(files.length==0) throw new RuntimeException("扩展名.proerpties的文件不存在"); //获取到配置文件 File config = files[0]; //获取配置文件的信息。 Properties prop = new Properties(); FileInputStream fis = new FileInputStream(config); prop.load(fis); String fileName = prop.getProperty("filename"); int partcount = Integer.parseInt(prop.getProperty("partcount")); //-------------------------- File[] partFiles = dir.listFiles(new SuffixFilter(SP)); if(partFiles.length!=partcount) throw new RuntimeException("缺少碎片文件"); //--------------------- ArrayList<FileInputStream> al = new ArrayList<FileInputStream>(); for(int x=0; x<partcount; x++){ al.add(new FileInputStream(new File(dir,x+SP))); } Enumeration<FileInputStream> en = Collections.enumeration(al); SequenceInputStream sis = new SequenceInputStream(en); File file = new File(dir,fileName); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[1024]; int len = 0; while((len=sis.read(buf))!=-1){ fos.write(buf,0,len); } fos.close(); sis.close(); } //带有配置信息的数据切割。 public static void splitFile(File file)throws IOException{ //用一个读取流和文件关联。 FileInputStream fis = new FileInputStream(file); //创建目的地。因为有多个。所以先创建引用。 FileOutputStream fos = null; //指定碎片的位置。 File dir = new File("c:\\partfiles"); if(!dir.exists()) dir.mkdir(); //碎片文件大小引用。 File f = null; byte[] buf = new byte[1024*1024]; //因为切割完的文件通常都有规律的。为了简单标记规律使用计数器。 int count = 0; int len = 0; while((len=fis.read(buf))!=-1){ f = new File(dir,(count++)+".part"); fos = new FileOutputStream(f); fos.write(buf,0,len); fos.close(); } //碎片文件生成后,还需要定义配置文件记录生成的碎片文件个数。以及被切割文件的名称。 //定义简单的键值信息,可是用Properties。 String filename = file.getName(); Properties prop = new Properties(); prop.setProperty("filename",filename); prop.setProperty("partcount",count+""); File config = new File(dir,count+".properties"); fos = new FileOutputStream(config); prop.store(fos,""); fos.close(); fis.close(); } } class SuffixFilter implements FileFilter{ private String suffix; SuffixFilter(String suffix){ this.suffix = suffix; } public boolean accept(File file){ return file.getName().endsWith(suffix); } }
RandomAccessFile:
特点:
1:该对象即可读取,又可写入。
2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组。
3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。
4:该对象操作的源和目的必须是文件。
5:其实该对象内部封装了字节读取流和字节写入流。
注意:实现随机访问,最好是数据有规律。
class RandomAccessFileDemo{ public static void main(String[] args) throws IOException{ write(); read(); randomWrite(); } //随机写入数据,可以实现已有数据的修改。 public static void randomWrite()throws IOException{ RandomAccessFile raf = new RandomAccessFile("random.txt","rw"); raf.seek(8*4); System.out.println("pos :"+raf.getFilePointer()); raf.write("王武".getBytes()); raf.writeInt(102); raf.close(); } public static void read()throws IOException{ RandomAccessFile raf = new RandomAccessFile("random.txt","r");//只读模式。 //指定指针的位置。 raf.seek(8*1);//实现随机读取文件中的数据。注意:数据最好有规律。 System.out.println("pos1 :"+raf.getFilePointer()); byte[] buf = new byte[4]; raf.read(buf); String name = new String(buf); int age = raf.readInt(); System.out.println(name+"::"+age); System.out.println("pos2 :"+raf.getFilePointer()); raf.close(); } public static void write()throws IOException{ //rw:当这个文件不存在,会创建该文件。当文件已存在,不会创建。所以不会像输出流一样覆盖。 RandomAccessFile raf = new RandomAccessFile("random.txt","rw");//rw读写模式 //往文件中写入人的基本信息,姓名,年龄。 raf.write("张三".getBytes()); raf.writeInt(97); raf.close(); } }
管道流:管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。
注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法是阻塞式的,没有数据的read方法会让线程等待。
public static void main(String[] args) throws IOException{ PipedInputStream pipin = new PipedInputStream(); PipedOutputStream pipout = new PipedOutputStream(); pipin.connect(pipout); new Thread(new Input(pipin)).start(); new Thread(new Output(pipout)).start(); }
对象的序列化:目的:将一个具体的对象进行持久化,写入到硬盘上。
注意:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中。
如何将非静态的数据不进行序列化?用transient 关键字修饰此变量即可。
Serializable:用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。
import java.io.*; class ObjectStreamDemo { public static void main(String[] args) throws Exception{ writeObj(); readObj(); } public static void readObj()throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt")); Object obj = ois.readObject();//读取一个对象。 System.out.println(obj.toString()); } public static void writeObj()throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt")); oos.writeObject(new Person("lisi",25)); //写入一个对象。 oos.close(); } } class Person implements Serializable{ private static final long serialVersionUID = 42L; private transient String name;//用transient修饰后name将不会进行序列化 public int age; Person(String name,int age){ this.name = name; this.age = age; } public String toString(){ return name+"::"+age; } }
DataOutputStream、DataInputStream:专门用于操作基本数据类型数据的对象。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt")); dos.writeInt(256); dos.close(); DataInputStream dis = new DataInputStream(new FileInputStream("data.txt")); int num = dis.readInt(); System.out.println(num); dis.close();
ByteArrayInputStream:源:内存
ByteArrayOutputStream:目的:内存。
这两个流对象不涉及底层资源调用,操作的都是内存中数组,所以不需要关闭。
直接操作字节数组就可以了,为什么还要把数组封装到流对象中呢?因为数组本身没有方法,只有一个length属性。为了便于数组的操作,将数组进行封装,对外提供方法操作数组中的元素。
对于数组元素操作无非两种操作:设置(写)和获取(读),而这两操作正好对应流的读写操作。这两个对象就是使用了流的读写思想来操作数组。
//创建源: ByteArrayInputStream bis = new ByteArrayInputStream("abcdef".getBytes()); //创建目的: ByteArrayOutputStream bos = new ByteArrayOutputStream(); int ch = 0; while((ch=bis.read())!=-1){ bos.write(ch); } System.out.println(bos.toString());
网络编程:
端口:
物理端口:
逻辑端口:用于标识进程的逻辑地址,不同进程的标识;有效端口:0~65535,其中0~1024系统使用或保留端口。
java 中ip对象:InetAddress.
import java.net.*; class IPDemo{ public static void main(String[] args) throws UnknownHostException{ //通过名称(ip字符串or主机名)来获取一个ip对象。 InetAddress ip = InetAddress.getByName("www.baidu.com");//java.net.UnknownHostException System.out.println("addr:"+ip.getHostAddress()); System.out.println("name:"+ip.getHostName()); } }
Socket:★★★★,套接字,通信的端点。
就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。
UDP传输:
1,只要是网络传输,必须有socket 。
2,数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。
直接操作udp不可能,对于java语言应该将udp封装成对象,易于我们的使用,这个对象就是DatagramSocket. 封装了udp传输协议的socket对象。
因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对象。这个数据包对象就是:DatagramPacket.通过这个对象中的方法,就可以获取到数据包中的各种信息。
DatagramSocket具备发送和接受功能,在进行udp传输时,需要明确一个是发送端,一个是接收端。
udp的发送端:
1,建立udp的socket服务,创建对象时如果没有明确端口,系统会自动分配一个未被使用的端口。
2,明确要发送的具体数据。
3,将数据封装成了数据包。
4,用socket服务的send方法将数据包发送出去。
5,关闭资源。
import java.net.*; class UdpSend{ public static void main(String[] args)throws Exception { // 1,建立udp的socket服务。 DatagramSocket ds = new DatagramSocket(8888);//指定发送端口,不指定系统会随机分配。 // 2,明确要发送的具体数据。 String text = "udp传输演示 哥们来了"; byte[] buf = text.getBytes(); // 3,将数据封装成了数据包。 DatagramPacket dp = new DatagramPacket(buf, buf.length,InetAddress.getByName("10.1.31.127"),10000); // 4,用socket服务的send方法将数据包发送出去。 ds.send(dp); // 5,关闭资源。 ds.close(); } }
udp的接收端:
1,创建udp的socket服务,必须要明确一个端口,作用在于,只有发送到这个端口的数据才是这个接收端可以处理的数据。
2,定义数据包,用于存储接收到数据。
3,通过socket服务的接收方法将收到的数据存储到数据包中。
4,通过数据包的方法获取数据包中的具体数据内容,比如ip、端口、数据等等。
5,关闭资源。
class UdpRece { public static void main(String[] args) throws Exception{ // 1,创建udp的socket服务。 DatagramSocket ds = new DatagramSocket(10000); // 2,定义数据包,用于存储接收到数据。先定义字节数组,数据包会把数据存储到字节数组中。 byte[] buf = new byte[1024]; DatagramPacket dp = new DatagramPacket(buf,buf.length); // 3,通过socket服务的接收方法将收到的数据存储到数据包中。 ds.receive(dp);//该方法是阻塞式方法。 // 4,通过数据包的方法获取数据包中的具体数据内容,比如ip,端口,数据等等。 String ip = dp.getAddress().getHostAddress(); int port = dp.getPort(); String text = new String(dp.getData(),0,dp.getLength());//将字节数组中的有效部分转成字符串。 System.out.println(ip+":"+port+"--"+text); // 5,关闭资源。 ds.close(); } }
TCP传输:两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为socket流。该流中既有读取,也有写入。
tcp的两个端点:一个是客户端,一个是服务端。
客户端:对应的对象,Socket
服务端:对应的对象,ServerSocket
TCP客户端:
1,建立tcp的socket服务,最好明确具体的地址和端口。这个对象在创建时,就已经可以对指定ip和端口进行连接(三次握手)。
2,如果连接成功,就意味着通道建立了,socket流就已经产生了。只要获取到socket流中的读取流和写入流即可,只要通过getInputStream和getOutputStream就可以获取两个流对象。
3,关闭资源。
-------------------------------------------------------------- import java.net.*; import java.io.*; //需求:客户端给服务器端发送一个数据。 class TcpClient{ public static void main(String[] args) throws Exception{ Socket s = new Socket("10.1.31.69",10002); OutputStream out = s.getOutputStream();//获取了socket流中的输出流对象。 out.write("tcp演示,哥们又来了!".getBytes()); s.close(); } } --------------------------------------------------------------
TCP服务端:
1,创建服务端socket服务,并监听一个端口。
2,服务端为了给客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端对象。
3,可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。
4,如果通讯结束,关闭资源。注意:要先关客户端,再关服务端。
-------------------------------------------------------------- class TcpServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10002);//建立服务端的socket服务 Socket s = ss.accept();//获取客户端对象 String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+".....connected"); // 可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。 InputStream in = s.getInputStream();//读取客户端的数据,使用客户端对象的socket读取流 byte[] buf = new byte[1024]; int len = in.read(buf); String text = new String(buf,0,len); System.out.println(text); // 如果通讯结束,关闭资源。注意:要先关客户端,在关服务端。 s.close(); ss.close(); } }
反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。
反射的好处:大大的增强了程序的扩展性。
反射的基本步骤:
1、获得Class对象,就是获取到指定的名称的字节码文件对象。
2、实例化对象,获得类的属性、方法或构造函数。
3、访问属性、调用方法、调用构造函数创建对象。
获取这个Class对象,有三种方式:
1:通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。
2:每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。
前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。
3:使用的Class类中的方法,静态的forName方法。
指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。
// 1. 根据给定的类名来获得 用于类加载
String classname = "cn.itcast.reflect.Person";// 来自配置文件
Class clazz = Class.forName(classname);// 此对象代表Person.class
// 2. 如果拿到了对象,不知道是什么类型 用于获得对象的类型
Object obj = new Person();
Class clazz1 = obj.getClass();// 获得对象具体的类型
// 3. 如果是明确地获得某个类的Class对象 主要用于传参
Class clazz2 = Person.class;
反射的用法:
1)、需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:
Class.forName(classname) 用于做类加载
obj.getClass() 用于获得对象的类型
类名.class 用于获得指定的类型,传参用
2)、反射类的成员方法:
Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();
3)、反射类的构造函数:
Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)
4)、反射类的属性:
Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);
获取了字节码文件对象后,最终都需要创建指定类的对象:
创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):
1,调用空参数的构造函数:使用了Class类中的newInstance()方法。
2,调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。
综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。
// 如何生成获取到字节码文件对象的实例对象。 Class clazz = Class.forName("cn.itcast.bean.Person");//类加载 // 直接获得指定的类型 clazz = Person.class; // 根据对象获得类型 Object obj = new Person("zhangsan", 19); clazz = obj.getClass(); Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2(); public static void method_2() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); //既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。 //获取一个带参数的构造器。 Constructor constructor = clazz.getConstructor(String.class,int.class); //想要对对象进行初始化,使用构造器的方法newInstance(); Object obj = constructor.newInstance("zhagnsan",30); //获取所有构造器。 Constructor[] constructors = clazz.getConstructors();//只包含公共的 constructors = clazz.getDeclaredConstructors();//包含私有的 for(Constructor con : constructors) { System.out.println(con); } } ------------------------------------------------------ 反射指定类中的方法: //获取类中所有的方法。 public static void method_1() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。 methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。 for(Method method : methods) { System.out.println(method); } } //获取指定方法; public static void method_2() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); //获取指定名称的方法。 Method method = clazz.getMethod("show", int.class,String.class); //想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。 Object obj = clazz.newInstance(); method.invoke(obj, 39,"hehehe");//执行一个方法 } //想要运行私有方法。 public static void method_3() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); //想要获取私有方法。必须用getDeclearMethod(); Method method = clazz.getDeclaredMethod("method", null); // 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。 method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。 } //反射静态方法。 public static void method_4() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); Method method = clazz.getMethod("function",null); method.invoke(null,null); }
正则表达式:★★★☆,其实是用来操作字符串的一些规则。
好处:正则的出现,对字符串的复杂操作变得更为简单。
特点:将对字符串操作的代码用一些符号来表示。只要使用了指定符号,就可以调用底层的代码对字符串进行操作。符号的出现,简化了代码的书写。
弊端:符号的出现虽然简化了书写,但是却降低了阅读性。
其实更多是用正则解决字符串操作的问题。
组:用小括号标示,每定义一个小括号,就是一个组,而且有自动编号,从1开始。
只要使用组,对应的数字就是使用该组的内容。别忘了,数组要加\\。
(aaa(wwww(ccc))(eee))技巧,从左括号开始数即可。有几个左括号就是几组。
常见操作:
1,匹配:其实用的就是String类中的matches方法。
String reg = "[1-9][0-9]{4,14}";
boolean b = qq.matches(reg);//将正则和字符串关联对字符串进行匹配。
2,切割:其实用的就是String类中的split方法。
3,替换:其实用的就是String类中的replaceAll();
4,获取:
1),先要将正则表达式编译成正则对象。使用的是Pattern中静态方法 compile(regex);
2),通过Pattern对象获取Matcher对象。
Pattern用于描述正则表达式,可以对正则表达式进行解析。
而将规则操作字符串,需要从新封装到匹配器对象Matcher中。
然后使用Matcher对象的方法来操作字符串。
如何获取匹配器对象呢?
通过Pattern对象中的matcher方法。该方法可以正则规则和字符串想关联。并返回匹配器对象。
3),使用Matcher对象中的方法即可对字符串进行各种正则操作。