《Java8高级应用与开发》读书笔记(一)

写在前面

本博客涉及内容:文件IO流、JDBC笔记、线程、正则表达式、Swing+网络编程 


第一章,文件及IO流

IO流部分Demo来源于简书 

文件:计算机以硬盘,光盘,移动存储设备等为载体的储存在计算机上的信息集合,通常有扩展名。

文件系统(文件+目录+符合链接):Linux和Unix操作系统只有一个根目录,Windows系统有多个。

文件路径:绝对路径+相对路径;

File类:

java.io包中提供对文件进行处理的接口和类,File类代表与平台无关的文件和目录,File类可以对文件,目录及其属性进行管理和访问,是java.io包中唯一磁盘文件本身的对象,通过调用File类中的方法实现创建,删除,重命名文件等操作。

使用File类创建一个文件对象。 

new File(String pathname);://给定路径名字符串转换为抽象路径名来创建一个新的File实例。
File file=new File("F:\\1.txt")new File(String patrent,String child);
//根据定义的父路径和子路径字符串(包含文件名)创建,
File file=new File("F:\\","1.txt")new File(File f,String child);
//根据定义的父路径对象和子路径字符串创建,
File file = new File(f,"1.txt");
import java.io.File;
public class DirList {
   public static void main(String args[]) {
      String dirname = "/java";
      File f1 = new File(dirname);
      if (f1.isDirectory()) {
         System.out.println( "Directory of " + dirname);
         String s[] = f1.list();
         for (int i=0; i < s.length; i++) {
            File f = new File(dirname + "/" + s[i]);
            if (f.isDirectory()) {
               System.out.println(s[i] + " is a directory");
            } else {
               System.out.println(s[i] + " is a file");
            }
         }
      } else {
         System.out.println(dirname + " is not a directory");
    }
  }
}

File类的常用方法

创建目录: 

import java.io.File;
 
public class CreateDir {
  public static void main(String args[]) {
    String dirname = "/tmp/user/java/bin";
    File d = new File(dirname);
    // 现在创建目录
    d.mkdirs();
  }
}


读取目录:

import java.io.File;
 
public class DirList {
  public static void main(String args[]) {
    String dirname = "/tmp";
    File f1 = new File(dirname);
    if (f1.isDirectory()) {
      System.out.println( "目录 " + dirname);
      String s[] = f1.list();
      for (int i=0; i < s.length; i++) {
        File f = new File(dirname + "/" + s[i]);
        if (f.isDirectory()) {
          System.out.println(s[i] + " 是一个目录");
        } else {
          System.out.println(s[i] + " 是一个文件");
        }
      }
    } else {
      System.out.println(dirname + " 不是一个目录");
    }
  }
}

删除目录或文件:

import java.io.File;
 
public class DeleteFileDemo {
  public static void main(String args[]) {
      // 这里修改为自己的测试目录
    File folder = new File("/tmp/java/");
    deleteFolder(folder);
  }
 
  //删除文件及目录
  public static void deleteFolder(File folder) {
    File[] files = folder.listFiles();
        if(files!=null) { 
            for(File f: files) {
                if(f.isDirectory()) {
                    deleteFolder(f);
                } else {
                    f.delete();
                }
            }
        }
        folder.delete();
    }
}


FilenameFilter接口:是一个文件过滤器接口,该接口只提供一个accept(File dir,String name)方法,该方法的返回值类型为boolean型,可以对文件进行过滤,File的list()方法可以接受FileNameFiler类型的参数"."代表当前目录,获取相对路径的父路径可能会出错,要使用getAbsolutePath().getParent()返回。当使用createNewFile()创建新文件时 ,可能回引发IOException异常,因此需要try……catch语句进行异常处理。路径分割符需要使用“\\”.

String[] list(FilenameFilter filter)
//返回File对象所对应的目录中满足指定过滤条件的文件名和子目录名;
File [] listFiles(FilenameFilter fifter)
//返回File对象所对应的目录中满足指定过滤条件的文件和子目录。


String [] filterFileNames = file.list(new FilenameFilter(){     
 public boolean accept(File dir,String name){   
      return (name.endsWith(".mp3")||name.endsWith(".text")); 
     }           
 });
//创建FileNameFilter类型的匿名内部类直接实现该接口中的accept()方法,并作为参数传入到list()方法中。

IO流

Java的IO流是是实现数据输入(Input)和数据输出(Output)的基础。可以对数据实现读写操作。流(Stream)的优势在于使用统一的方式对数据进行操作或传递,简化了代码操作。流提供了一条通道程序,可以使用这条通道把源中的字节序列送到目的地,

在Java中“数据源”可以是一个磁盘文件,一个网络套接字,甚至一个网络文件。

流的分类:

按照流向来分(从程序运行所作的内存角度来划分):

  • 输入流(只能读取数据(数据源),而不能写入数据)
  • 输出流(只能写入数据(数据源),不能读取数据)。

按照流所操作的基本数据单元来分

  • 字节流(所操作的基本数据单元是8位(byte),无论输入输出都直接对字节进行处理)
  • 字符流(所操作的基本数据单元是16位的字符(Unicode),无论输入输出都直接对字符进行处理)。

按照流的角色来分

  • 节点流(用于从/向一个特定的IO设备(磁盘,网络)读/写数据的流,也称低级流(Low Level Stream),节点通常指文件,内存或管道)
  • 处理流(对一个已经存在的流进行连接或封装,通过封装后的流来实现数据的读/写功能,又称高级流)

Java中使用处理流来包装节点流是一种典型的使用模式,通过使用处理流来包装不同的节点流,消除了不同节点流实现的差异

流的体系结构:Java的IO流都是由4个抽象基类派生的:

流的四个抽象基类
 字节流输入流:InputStream(数据的读操作)
  输出流:OutputStream(数据的写操作)
IO流字符流

输入流:Reader(数据的读操作)

  输出流:Writer(数据的写操作)

Java的IO流体系按照功能分类的常用流表,其中访问文件,数组,管道和字符串的流都是节点流,必须直接与指定的物理节点关联,

IO流按照功能分类的常用流
分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputStreamOutputStreamReaderWriter
访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
访问字符串  StringReaderStringWriter
缓存流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
转换流  InputStreamReaderOutputStreamWriter
对象流ObjectInputStreamObjectOutputStream  
过滤基类FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
打印流 PrintStream PrintWriter
推回输入流PushbackInputStream PushbackReader 
特殊流DataInputStreamDataOutputStream  

计算机中所有的数据都是以二进制的形式组织,而字节流可以处理所以二进制文件。所以字节流比字符流功能强大,在使用IO流时要注意。如果进行输入输出的内容是文本内容,则使用字符流,如果进行输入的是二进制内容,则使用字节流。计算机中的文件被分为二进制文件和文本文件,所有能被记事本正常显示打开的文件都称为文本文件,反之则称为二进制文件。

字节流:

字节流处理的基本单元是字节,其输入/输出操作都是在字节的基础上进行的,字节流的两个抽象基类InputStream和OutputStream,其他的字节流都是由这两个字节流派生的。

InputSream(字节输入流):可以从数据源以字节为单位读取数据。

InputStream常用方法
abstract int read()读取一个字节并返回,如果遇到源的末尾,则返回-1
int read( byte[] b)将数据读入到字节数组中,并返回实际读取的字节数,当已达到流的尾端而没有可以使用的字节时,返回-1
int read(byte[] b,int offset,int len)将数据读入到字节数组中,并返回实际读取的字节数,当已达到流的尾端而没有可以使用的字节时,返回-1,len表示读取的最大字节数,offset表示数组中存放数据的开始位置。
int available()用于返回在不发生阻赛的情况下,从输入流中可以读取的字节数
void close()关闭此输入流,并释放与该流关联的所有系统资源。
mark(int readlimit)在输入流的当前位置放置一个标记,readlimit参数告知此输入流在标记失效之前允许读取的字节数
reset()将输入指针返回到当前所做的标记处
skip(long n)跳过输入流上的n个字节并返回实际跳过的字节数
markSupported()如果当前流支持mark()/reset()操作就返回true

 

InputStream类是抽象类,不能直接实例化,使用其子类完成具体的功能。

InputStream类其子类
InputStreamFileInputStream(文件输入流,从文件中读取二进制数据) 
 PipedInputStream(管道输入流,产生一份数据,能被写入的相应的PipedOutputStream中) 
 FilterInputStream(过滤输入流,用于连接两个流,一个接到另个的末端,)LineNumberInputStream
 ByteAttayInputStream(为读取字节数组设计的流,允许内存的缓存区被当作InputStream使用)DataInputStream
 SequenceInputStreamBufferedInputStream
 StringBufferInputStreamPushbackInputStream
 ObjectInputStream(对象输入流,用于将保存在磁盘或网络中的对象读取出来) 

OutputStream(字节输出流)可以从数据源以字节为单位写入数据。

OutputStream常用方法
void write(int c)将一个字节写入到文件输入流中
void write(byte [] b)将字节数组中的数据写入到文件输出流中
void write(byte [] b,int offset,int len)将字节数组中的offset开始的len个字节写入到文件输出流中
void close()关闭此输入流,并释放与该流关联的所有系统资源
void flush()将缓存区中的字节数立即发送到流中,同时清空缓存
OutputStream类及其子类
OutputStreamFileOutputStream(文件输入流,用于以二进制形式把数据写入到文件中) 
 PipedOutputStream(管道输出流,产生一份数据,能被读取到相应的PipedInputStream中) 
 FilterOutputStream(过滤输出流,用于将一个流连接到另外一个流的末端,将两种流连接起来)DataOutputStream
 ByteArrayOutputStream(按照字节数组的方式向设备中写入字节流的类)BufferedOutputStream
 ObjectOutputStream(对象输出流,将对象保存到磁盘或在网络中传递)PrintStream

字节流中文件输入/输出流

FileInputStram与FileOutputStream类都是用来操作磁盘文件,如果用户的文件读取/写入需求比较简单,则可以使用FileInputStream类和FileOutputStream类,参数构造函数相同。

File f = new File("C:/java/hello");
InputStream f = new FileInputStream("C:/java/hello");
InputStream in = new FileInputStream(f);
OutputStream f = new FileOutputStream("C:/java/hello");
OutputStream f = new FileOutputStream(f);
OutputStream f = new FileOutputStream(f,true);

 FileInputStream类实例:

public static void main(String[] args) throws IOException{
    InputStream f  = new FileInputStream("/home/桌面/test.txt");
    int c = 0;
    while((c =  f.read()) != -1) 
    //这里也可以先用available方法得到可读的字节数
    System.out.println((char)c);
}

public static void main(String[] args) {
        // 创建一个FileInputStream对象
        try {
            FileInputStream fis = new FileInputStream("/home/xiejunyu/桌面/test.txt");
            byte[] b=new byte[100];
            fis.read(b,0,5); 
            /*把字节从文件读入b数组,从b数组的0位置开始存放,
            读取5个字节*/
            System.out.println(new String(b));
            fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 FileOutputStream类实例:

public static void main(String args[]){
    try{
      byte bWrite[] = "ABC".getBytes();
      OutputStream os = new FileOutputStream("/home/xiejunyu/桌面/test.txt");
      for(int x=0; x < bWrite.length ; x++){
      os.write(bWrite[x] ); // writes the bytes
    }
}

使用FileOutputStream向文件中写入数据时,默认指定的文件不存在时,会先创建一个,在将输入内容写入文件,如果已存在,则先清空原来文件中的内容,在写入新文件,如果要在原文件末尾追加,需要使用FileOutputStream(String  name ,boolean append)构造方法创建一个文件输出流。其中append的参数值为true。不能指定一个已经被其他程序打开的文件。

InputStream 和 OutputStream 用法的例子:

import java.io.*;
 
public class FileStreamTest{
  public static void main(String args[]){
    try{
      byte bWrite[] = "ABC".getBytes();
      OutputStream os = new FileOutputStream("/home/xiejunyu/桌面/test.txt");
      for(int x=0; x < bWrite.length ; x++){
      os.write(bWrite[x] ); // writes the bytes
    }
    os.close();
 
    InputStream is = new FileInputStream("/home/xiejunyu/桌面/test.txt");
    int size = is.available();
 
    for(int i=0; i< size; i++){
      System.out.print((char)is.read() + "  ");
    }
      is.close();
    }catch(IOException e){
      System.out.print("Exception");
    }  
  }
}

用InputStream和OutputStream配合进行文件的复制,即读取原件数据,写入副本文件。
复制有两种实现方式:

  • 实现一:将源文件中的内容全部读取进来,之后一次性的写入到目标文件
  • 实现二:边读边写,实际上边读边写也分为三种方式:
    • 1.批量拷贝(循环读取,每次读入一个byte数组)
    • 2.缓冲拷贝(使用缓冲流)
    • 3.批量+缓冲拷贝(循环批量读取到字节数组中,然后使用缓冲输出流写入到文件) 
public static void main(String[] args) {
        // 文件拷贝
        try {
            FileInputStream fis=new FileInputStream("happy.gif");
            FileOutputStream fos=new FileOutputStream("happycopy.gif");
            int n=0;
            byte[] b=new byte[1024];
            while((n=fis.read(b))!=-1){ 
            /*循环读取,每次1024个字节,最后一次可能不满1024。
            后面的字节覆盖前面的字节,不必担心数组溢出。*/
                fos.write(b,0,n); //n是实际读取到的字节数,如果写fos.write(b),会造成最后一次数组未满的情况也写1024个字节,从而造成副本比原件略大
            }
            fis.close();
            fos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }
    }

 

字节流中带缓存的输入/输出流

BufferedInputStream类可以对所有的InputStream类进行带缓存区的包装以达到性能的优化,BufferedOutputStream类与OutputStream不一样的是存在一个flush()方法用来将缓存区的数据强制输出完,就是在缓存区没有满的情况下,也将该缓存区的内容强制写入外设,习称刷新,只对OutputStream的子类有效,在调用close方法时,也会刷新。构造方法相同。

BufferedOutputStream(OutputStream in)(创建32字节的缓存区)||BufferedInputStream(OutputStream in,int size)(指定缓存区szie)

字节流中数据输入流和输出流

DataInputStream与DataOutputStream类允许应用程序以与机器无关的方式从底层输入流中读取基本的Java数据类型,即读取该数据时不关心字节。构造方法:

DataInputStream(InputStream in)(以基础的InputStream创建一个DataInputStream);只提供readUTF()方法返回字符串

DataOutputStream(OutputStream out)(创建一个新的数据输出流,将数据写入到指定基础输出流)写入方法有三种:writeBytes(String s),writeChars(String s),WriteUTF(String s)

ZIP压缩输入/输出流

ZIP压缩管理文件(ZIP archive)是一种十分典型的文件压缩形式,使用可以节省空间,java.util.zip包中的ZipOutputStream与ZipInputStream类来实现文件的压缩/解压缩。要从ZIP压缩管理文件内读取某个文件,要先找到对应该文件的“目录进入点(确定文件在ZIP文件的的位置点)”,如果要写入文件到zip文件内,必须先写入到对应文件的"目录进入点"。并且把要写入文件内容的位置移到此进入点所指的位置,然后写入文件。Java实现了I/O数据流与网络数据流的单一接口,ZipEntry类产生的对象,用来代表一个ZIP压缩文件的进入点(entry).ZipInputStream类用来读取ZIP压缩格式的文件,所支持的包括已压缩及未压缩的进入点(entry).ZipOutputStream类用来写出ZIP压缩格式的文件,支持包括已压缩和未压缩的进入点(entry)

压缩文件ZipOutputStream类对象,可以将文件压缩为.zip文件,构造方法:

ZipOutputStream(OutputStream out);

ZipOutputStream类的常用方法
putNextEntry(ZipEntry e)void开始写入一个新的ZipEntry,并将流内的位置移至此entry所指数据的开头
write(byte[] b, int off,int len)void将字节数组写入到当前ZIP条目数据
finish()void完成写入ZIP输出流的内容,无需关闭它所配合的OutputStream
setComment(String comment)void可设置此ZIP文件的注释文字

ZipInputSertam类可读取ZIP压缩格式的文件,包括已压缩和未压缩的条目(entry),ZipInputStream构造方法:

ZipInpusertam(InputStream in)

ZipInputStream类的常用方法
read(byte[] b,int off,int len)int读取目录b数组内off偏移量的位置,长度是len字节
available()int判断是否已读完目前entry 所指定的数据,读完返回0,反之1
closeEntry()void关闭当前ZIP条目并定位流已读取下一个条目
skip(long n )long跳过当前ZIP条目中指定的字节数
getNetEntry()ZipEntry读取下一个ZipEntry,并将流内的位置移至该entry所指定数据的开头,
createZipEntry(String name)ZipEntry以指定的name参数新建一个ZipEntry对象

 

字符流

Java语言中字符采用Unicode字符编码,每个字符占两个字节空间,文本有可能采用GBK和UTF-8,字符流的两个基本抽象类Reader和Writer,其他的字节流都是由这两个字节流派生的。

Reader(字符输入流):用于以字符为单位向数据源中读取数据。Reader类中常用方法

Reader类中常用方法
int read()读取一个字符并返回,如果遇到源的末尾,则返回-1
int read( char[] b)将数据读入到字符数组中,并返回实际读取的字符数,当已达到流的尾端而没有可以使用的字符时,返回-1
int read(cahr[] b,int offset,int len)将数据读入到字符数组中,并返回实际读取的字符数,当已达到流的尾端而没有可以使用的字符时,返回-1,len表示读取的最大字符数,offset表示数组中存放数据的开始位置。
void close()关闭此Reader流,并释放与该流关联的所有系统资源。
Reader类及其子类
ReaderBufferedReader(缓存字符输入流,从字符输入流中读取文本,缓存各个字符,一次读取一行文本)LineNumberReader
 CharArrayReader(字符数组读取器,此类实现一个可用作字符输入流的字符缓存区) 
 InputStreamReader(将字节流转换成字符流,读出字节并且将其按照指定的编码方式转换成字符)FileReader
 FilterReader(字符文件输入流,用于读文件中的数据)PushbackReader
 PipedReader 
 StringReader(字符串输入流,用于读文件中的数据) 

Writer(字符输出流):用于以字符为单位向数据源中写入数据。Write类中常用方法

Writer常用方法
void writer(int c)写入单个字符
void writer(char[] b)写入字符数组
void writer(char[] b,int offset,int len)写入字符数组的某一部分
void writer(String str)写入字符串
Writer类及其子类
WriterBufferedWriter(缓存字符输出流,往字符输出流中写文本,缓存各个字符)LineNumberReader
 CharArrayWriter(字符数组输出流,往字符输出流中写文本,缓存各个字符) 
 OutputStreamWriter(将字符流转换为字节流,将要写入流中的字符编码为字节)FileWriter
 FilterWriter(文件字符输出流,往文件中写内容)PushbackReader
 PipedWriter 
 StringWriter(字符串输出流) 
 printWriter 

字符流的文件输入/输出流FileReader和FileWriter

FileReader顺序的的读取文件,只要不关闭流,每次调用read()方法就顺序读取源中其余的内容,直到源的末尾或流被关闭。

BufferedReader的readLine()方法一次读取文件中的一行内容。当读取到末尾是返回null;

字符流的带缓存的输入/输出流BufferedReader和BufferedWriter

都具有内部缓存机制,并可以以行为单位进行输入/输出,

字符数据读取文件过程:字符数据>>BufferedWriter>>OutputStreamWriter>>>>OutpitStream>>文件

BufferedReader类常用的方法
read()读取单个字符
readLine()读取一个文本行,并将其返回为字符串,若无数据可读,则返回null
 

BufferedWriter类常用的方法

write(String s,int off,int len)写入字符串的某一部分
flush()刷新该流的缓存
newLine()写入一个行分隔符

 

在使用BufferedWriter类的Writer方法时,数据并没有立刻写入输入流,而是首先进入缓存区中,如果想立刻将缓存区中的数据写入输入流,一定要调用flush()方法。

 

过滤流和转换流

过滤流:用于对一个已有的流行行连接和封装处理,以便更加便利的方式对数据读写操作,过滤流分为过滤输入流和过滤输出流。

FiIterInpurStream常见子类
FilterInputStreamLineNumberInputStream(跟踪输入信号流,以废弃)
 DataInputStream(与DateOutputStream搭配使用,可以按照与平台无关的方式从流中读取基本类型数据)
 BufferedInputStream(利用缓存区来提高读取效率)
 PushbackInputStream(能够把读取的一个字节压回到缓存区中,通常用做编译器扫描)

     

FliterOurputStream常见子类
FilterOutputStreamDataOutputStream(与DateInputStream搭配使用,可以按照与平台无关的方式从流中读取基本类型数据)
 BufferedOutputStream(利用缓存区来提高读取效率)
 PrintStream(用于产生格式化输出)利用写入数据

转换流:InputStreamReader(将字节输入流转换为字符输入流),OutputStreamWriter(将字符输出流转化为字节输出流)

对象流

在Java中,使用对象流可以实现对象的序列化和反序列化操作。

对象的序列化与反序列化:对象的序列化(Serialize)是指将对象数据写入到一个输出流中的过程,而对象的反序列化是指从一个输入流中读取一个对象,将对象序列化后会转换成与平台无关的二进制字节流,从而允许将二进制字节流持久的保存在磁盘上,或通过网络将二进制传输到另一个网络节点,其他程序从网络或磁盘上获取这种二进制字节流,并将其反序列化后恢复成原来的Java对象。对象序列化功能非常简单,强大,广泛应用于RMI,Socket,Jms,EJB等技术。

对象序列化有两个特点:对象序列化可以在分布式应用中进行使用,分布式应用需要跨平台,跨网络,因此要求所有传递的参数,返回值都必须实现序列化。对象序列化不仅可以保存一个对象的数据,而且通过循环可以保存每个对象的数据。

在Java中,只有对象为序列化的,才可以保存到磁盘或通过网络传输。一个类的对象是可序列化的,则该对象必须实现java.lang包下的Serializable接口(标识性接口,没有任何方法)或Esternalizable接口。在实现Serializable接口时无需实现任何方法,它只是用于表明该类的实例对象是可以序列化的,只有实现才可以利用序列化工具保存和复原。

ObjectOutputStream和ObjectInputStream

ObjectOutputStream 是OutputStream的子类,该类也实现了ObjectOutput接口,其中ObjectOutput接口支持对象序列化:

构造函数:ObjectOutputStream(OutputStream outStream)throws IOException

ObjectOutputStream的方法
final void writeObject(Object obj)写入一个obj对象到调用的流中
void writeInt(int i)写入一个32位int值到调用的流中
void writeBytes(String str)以字节序列形式将字符串srt写入到调用的流中
void writeChar(int c)写入16位的char的值到调用的流中。

ObjectInputStream 是InputStream的子类,该类也实现了ObjectInput接口,其中ObjectInput接口支持对象序列化:

构造函数:ObjectInputStream(InputStream InStream)throws IOException

ObjectInputStream常用方法
final Object readObgect()从流中读取对象
int readInt()从流中读取一个整型值
String readUTF()从流中读取UTF-8格式的字符串
cahr readCahr()读取一个16位的字符

NIO

JDLK1.4开始新增的NIO,在包java.nio包及子包下。NIO也是用于数据的输入输出,但NIO采用内存映射文件的方式处理,将文件或文件的一段区域映射到内存中,提高访问效率。

java中与NIO有关的包:

java.nio包(包含各种与Buffer(缓存)相关的类)

Java.nio,channels包(与Channel(通道)和Selector相关的类)

java.nio.charset包(主要包括与字符集相关的类)

java.nio.channels.spi包(与Channel相关的服务提供者编程接口)

java.nio.charset.spi包(包括字符集相关的服务提供者编程接口)

Buffer和Channel是NIO的两个核心对象:

Buffer可以理解为一个容器,本质为一个数组,往Channel中发送或读取的对象都必须先放到Buffer中;在NIO系统中所有数据都需要经过通道传输,Channel与传统的InputStream,OutputSteam最大的区别是提供map()方法,可以将数据映射到内存中。IO是面向流的,NIO是面向块处理的,除了Buffer和Channel外,NIO还提供用于将Unicode字符串映射成字节序列。

Buffer类

Buffer是一个抽象类,其最常使用的子类是ByteBuffer,用于在底层字节数组上进行get/set()操作,除了布尔型,都有对应的Buffer类;CharBuffer……。没有构造方法,通过 static XxxxBuffer  allocate(int capacity):创建一个指定容量的XxxBuffer对象。

MappedByteBuffer为Buffer的子类,表示Channel将磁盘文件的部分或全部内容映射到内存中所的到的结果,对象由Channel的map()方法返回。

使用Buffer涉及的三个概念;

容量(capacity):最大数据容量,创建后不能改变。界限(limit):之后的数据不能被读写,位置(position):用于指明下一个可以被读出或者写入的缓存区位置索引 ,标记(mark):允许position定位到mark处,关系:0<=mark<=position<=limit<=capacity.

Buffer的常用方法
int capacity()返回此缓存区的容量
Buffer clear()清除此缓存区
Buffer filp()反转此缓存区(limit与position互换)
boolean hasRemaining()告知在当前位置和限制之间是否有元素
int limit()返回此缓存区的限制
Buffer limit(int newLimit)设置此缓存区的限制
Buffer mark()在此缓存区的位置设置标记
int posmaining()返回此缓存区的位置
Buffer position(int newPosition)设置此缓存区的位置
int remaining()返回当前位置与限制之间的元素数
Buffer reset()将此缓存区的位置重置为以前标记的位置
Buffer rewind()重绕此缓存区

Channel类

Channel与传统IO流相似,不同的地方Channel类可以直接将指定文件的部分或全部直接映射成Buffer;程序不能直接访问,Channel中的数据,Channel只能与Buffer进行交互。Channel是接口,其实现类包括DatagramChannel,FileChannel等,所有的Channel对象都不是通过构造器直接创建,而是通过传统结点InputStream或OutputStream的getChannel()f获取对应的Channel对象。不同流获取的对象不同。

FileChannel常用方法
MappedByteBuffer map(FileChannel. MapMode mode,long position,long size)将此通道的文件区域直接映射到内存中
int read(ByteBuffer dst)将字节序列从此通道读入给定的缓存区
int write(ByteBuffer src)将字节序列从给定的缓存区写入此通道

NIO.2

Java1.7对NIO改进称为NIO.2,改进方向:提供了全面的文件IO和文件系统的访问支持,新增java.nio.file包及其子包。新增了基于异步的Channel的IO,在Java.nio.channel包下增加了多个以Asynchronous开头的Channel接口和类。引入Path接口,该接口代表一个与平台无关的文件路径,提供Files和Paths两个工具类,Paths工具类提供了get()静态方法来创建Path实例对象。

Paths静态方法
public static Path get(String first,String...more)将路径字符串,或多个字符串连接形成一个路径的字符串序列,转换成一个Path对象
public static Path get(URL url)将给定的URL转换为一个Path对象

RandomAccessFile随机存取访问接口:

java.io
类 RandomAccessFile

java.lang.Object
  java.io.RandomAccessFile

所有已实现的接口:

Closeable, DataInput, DataOutput

此类的实例支持对随机访问文件的读取和写入随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

通常,如果此类中的所有读取例程在读取所需数量的字节之前已到达文件末尾,则抛出 EOFException是一种 IOException)。如果由于某些原因无法读取任何字节,而不是在读取所需数量的字节之前已到达文件末尾,则抛出 IOException,而不是 EOFException。需要特别指出的是,如果流已被关闭,则可能抛出 IOException

package com.liruilong.IoDome;


import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * @Description : IO 练习题
 * @Author: Liruilong
 * @Date: 2019/8/26 9:11
 */
public class FilesDemo {


    /**
     * @return void
     * @Author Liruilong
     * @Description (一) 在电脑D盘下创建一个文件为HelloWorld.txt文件,判断他是文件还是目录,在创建一个目
     * 录IOTest,之后将HelloWorld.txt移动到IOTest目录下去;之后遍历IOTest这个目录下的文
     * 件
     * @Date 10:18 2019/8/26
     * @Param [fileName, dirName]
     **/

    public static void createFile() {
        // 创建文件
        File fileName = new File("D:\\HelloWorld.txt");
        try {
            fileName.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 对文件进行判断
        if (fileName.isDirectory()) {
            System.out.println("是一个目录");
        }
        if (fileName.isFile()) {
            System.out.println("是一个文件");
        }
        // 创建目录
        File dirName = new File("D:\\IOTest");
        dirName.mkdir();

        // 移动文件
        File dirNames = new File("D:\\IOTest\\HelloWorld.txt");
        //renameTo在API里写的是重新命名此抽象路径名表示的文件。
        // 所以我理解应该是,只能作用与相同File类型,不能作用与文件和目录及同是目录、
        // 并且要先实例化好复制后的文件类型。
        System.out.println("目录是否复制成功:" + (new File("D:\\Dir1").renameTo(new File("D:\\Dir2"))));
        System.out.println("文件是否复制成功:" + fileName.renameTo(dirNames));
        // 遍历目录
        Arrays.stream(dirName.list()).forEach(System.out::println);
    }


    /**
     * @return void
     * @Author Liruilong
     * @Description 递归实现输入任意目录,列出文件以及文件夹,效果看图
     * @Date 13:50 2019/8/26
     * @Param [dir]
     **/

    public static void printFD(String dir) {

        List<File> files = new ArrayList<>();
        File file = new File(dir);
        if (file.exists() && file.isDirectory()) {
            //longErodic(file, files);
            longErodicJava(file, files);
        }
        files.stream().forEach(System.out::println);
    }

    private static void longErodic(File file, List<File> files) {
        File[] fileAll = file.listFiles();
        if (fileAll == null) {
            return;
        }
        for (File file1 : fileAll) {
            files.add(file1);
            longErodic(file1, files);
        }
    }

    private static void longErodicJava(File file, List<File> files) {
        File[] fileAll = file.listFiles((f) -> f.getName().endsWith(".java"));
        if (fileAll == null) {
            return;
        }
        for (File file1 : fileAll) {
            files.add(file1);
            longErodicJava(file1, files);
        }
    }

    /**
     * @return void
     * @Author Liruilong
     * @Description 从磁盘读取一个文件到内存中,再打印到控制台
     * @Date 14:11 2019/8/26
     * @Param []
     **/

    private static void printtext() {
        File file = new File("D:\\HelloWorld.txt");
        InputStream inputStream = null;
        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            int len = 0;
            byte[] bytes = new byte[1024];
            StringBuilder stringBuilder = new StringBuilder();
            while ((len = fileInputStream.read(bytes)) != -1) {
                stringBuilder.append(new String(bytes, 0, len));
            }
            System.out.println(stringBuilder);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @return java.lang.String
     * @Author Liruilong
     * @Description 读取指定文本的第一行。
     * @Date 15:49 2019/8/26
     * @Param [bufferReaderProcessFile]
     **/

    public static String processFiles(BufferReaderProcessFile bufferReaderProcessFile) throws IOException {
        try (BufferedReader bufferedReader =
                     new BufferedReader(new FileReader("D:\\log.txt"))) {
            return bufferReaderProcessFile.peocess(bufferedReader);
        }
    }

    /**
     * @return void
     * @Author Liruilong
     * @Description 在指定文件中写入 字符。
     * @Date 15:52 2019/8/26
     * @Param []
     **/

    public static void wriedDemo() {

        File file = new File("D:\\log.txt");
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file, true);
            fileOutputStream.write("你好世界!".getBytes());
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * @return void
     * @Author Liruilong
     * @Description 拷贝一张文件,移动到指定的位置
     * @Date 16:20 2019/8/26
     * @Param []
     **/

    public static void copyDemo() {
        File file = new File("D:\\sss.png");
        File fileto = new File("D:\\tt.png");
        try {
            if (!fileto.createNewFile()) {
                System.out.println("创建文件失败!");
            }
            FileInputStream fileInputStream = new FileInputStream(file);
            FileOutputStream fileOutputStream = new FileOutputStream(fileto);
            int len = 0;
            byte[] bytes = new byte[1024];
            while ((len = fileInputStream.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, len);
            }
            fileInputStream.close();
            fileOutputStream.close();
            System.out.println("文件复制成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @return void
     * @Author Liruilong
     * @Description 读取文件中的指定字符。
     * @Date 16:23 2019/8/26
     * @Param []
     **/

    public static void analyzetext() throws IOException {
        File file = new File("D:\\HelloWorld.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        int num = 0;
        int len = 0;
        // read()
        while ((len = fileInputStream.read()) != -1) {
            if (new String((char) len + "").equals("a")) {
                num++;
            }
        }
        System.out.println("a的字节数量:" + num);

    }

    /**
     * @return void
     * @Author Liruilong
     * @Description 使用随机文件流类RandomAccessFile将一个文本文件倒置读出。
     * RandomAccessFile类的构造方法如下所示:
     * <p>
     * RandomAccessFile(File file ,  String mode)
     * //创建随机存储文件流,文件属性由参数File对象指定
     * <p>
     * RandomAccessFile(String name ,  String mode)
     * //创建随机存储文件流,文件名由参数name指定
     * <p>
     * 这两个构造方法均涉及到一个String类型的参数mode,它决定随机存储文件流的操作模式,其中mode值及对应的含义如下:
     * <p>
     * “r”:以只读的方式打开,调用该对象的任何write(写)方法都会导致IOException异常
     * “rw”:以读、写方式打开,支持文件的读取或写入。若文件不存在,则创建之。
     * “rws”:以读、写方式打开,与“rw”不同的是,还要对文件内容的每次更新都同步更新到潜在的存储设备中去。
     * 这里的“s”表示synchronous(同步)的意思
     * “rwd”:以读、写方式打开,与“rw”不同的是,还要对文件内容的每次更新都同步更新到潜在的存储设备中去。
     * 使用“rwd”模式仅要求将文件的内容更新到存储设备中,而使用“rws”模式除了更新文件的内容,
     * 还要更新文件的元数据(metadata),因此至少要求1次低级别的I/O操作
     * @Date 16:45 2019/8/26
     * @Param []
     **/

    public static void RandomAccessDemo() {
        File file = new File("D:\\HelloWorld.txt");
        try {
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
            // 返回文件的长度
            long length = randomAccessFile.length();
            StringBuilder stringBuilder = new StringBuilder();
            while (length > 0) {
                length--;
                //seek()设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作
                randomAccessFile.seek(length);
                int c = (char) randomAccessFile.readByte();
                if (c >= 0 && c <= 255) {
                    stringBuilder.append((char) c);
                } else {
                    length--;
                    randomAccessFile.seek(length);
                    byte[] cc = new byte[2];
                    //readFully()将 b.length 个字节从此文件读入 byte 数组,并从当前文件指针开始。
                    randomAccessFile.readFully(cc);
                    stringBuilder.append(new String(cc));
                }
            }
            System.out.println(stringBuilder);
            randomAccessFile.close();
        } catch (IOException e) {
            e.printStackTrace();

        }
    }


    /**
     * @return void
     * @Author Liruilong
     * @Description 在电脑D盘下创建一个文件为HelloWorld.txt文件,
     * * 判断他是文件还是目录,再创建一个目录IOTest,
     * * 之后将HelloWorld.txt移动到IOTest目录下去;
     * * 之后遍历IOTest这个目录下的文件
     * @Date 18:55 2019/8/26
     * @Param []
     **/

    public static void createNameFile() {
        File file = new File("D:\\", "HelloWorld.txt");
        boolean isCreate;
        try {
            // 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
            isCreate = file.createNewFile();
            if (isCreate) {
                System.out.println("创建文件成功!");
            } else {
                System.out.println("创建文件失败!");
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static List<File> printFDs(String dir) {

        List<File> files = new ArrayList<>();
        File file = new File(dir);
        if (file.exists() && file.isDirectory()) {
            longErodic(file, files);
            //longErodicJava(file, files);
        }
        return files;
    }

    /**
     * @return void
     * @Author Liruilong
     * @Description 输入两个文件夹名称,将A文件夹内容全部拷贝到B文件夹,要求使用多线程来操作。
     * @Date 18:56 2019/8/26
     * @Param []
     **/

    public static void asd() {

        File file = new File("");
        File file1 = new File("");
         List<File> fileList = null;
        new Thread() {
            @Override
            public void run() {
                if (file.isFile()) {
                    System.out.println("复制单个文件!");
                } else {
                    //不是文件,
                    //当把复制的文件复制到自己的子文件夹里。会形成死循环。
                    if (file1.getName().replace("/", "\\")
                            .toLowerCase().startsWith(file1.getName().replace("/", "\\")
                                    .toLowerCase())) {
                        return;
                    }
                    //获取所有的File对象
                    List<File> fileList = printFDs("");
                    ExecutorService executorService = Executors.newFixedThreadPool(20);
                    for (File file : fileList){
                        String name = file.getAbsolutePath();
                        String toName = name.replace(file.getParent(), file1.getName() + "/");
                        System.out.println(name + "变成了" + toName);
                        if (file.isDirectory()){
                                new File(toName).mkdir();
                        }else {
                                executorService.execute(() ->{
                                    File copyFile = new File(toName);
                                    // 先要有父文件夹
                                    copyFile.getParentFile().mkdirs();

                                    // 开始复制文件
                                    copy(file, copyFile);
                                });
                        }
                    }

                }
            }


        }.start();

    }
    private  static  void copy(File file, File copyFile) {
        //文件写入
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(copyFile);

        //文件读取
        FileInputStream fileInputStream  =new FileInputStream(file);
        int len = 0;
        byte[] bytes = new byte[1024];
        while ((len = fileInputStream.read(bytes)) != -1) {
            fileOutputStream.write(bytes, 0, len);
        }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

    public static void  selectdemo(){
        List<File> list = printFDs("D:\\");
        // 是否是目录排序,文件名,文件名大小排序。
        list.stream().sorted((o1, o2) ->
            ( o2.isDirectory() ? 1 : -1) - (o1.isDirectory() ? 1 : -1)
        ).forEach(System.out::println);
              /*  sorted((o1, o2) -> o1.getName().length() - o2.getName().length());
        // 按文件名排序
        list.stream().sorted(((o1, o2) ->
                o1.getName().compareTo(o2.getName())) )
                .sorted(((o1, o2) ->
                o1.getName().compareTo(o2.getName()))
        ).
*/

    }


    public static void main(String[] args) throws IOException {
        createFile();
        //  printFD("D:\\");
        //printFD(".\\src");
        //  printtext();
        /*System.out.println(processFiles((bs) -> bs.readLine()));
        wriedDemo();
        copyDemo();
        analyzetext();
        RandomAccessDemo();*/
        selectdemo();
    }

}

JDBC编程

使用JDBC,java程序可以轻松地操作各种主流数据库,Oracle,MSSQL,Mysq等,使用JDBC编写的程序不仅可以实现跨数据库,还具有跨平台性和可移植性。

JDBC(Java Database Connectiovity,Java数据库连接)是一种执行SQL语句的JavaAPI,程序可以通过JDBC API连接数据库,并使用SQL结构化查询语言完成对数据库的操作,程序员使用JDBC编程时只需要掌握标准的JDBC API 即可,当需要在不同的数据库之间切换时,只需要更换不同的数据库驱动类,是面向接口编程的典例应用。

JDBC访问数据库时主要完成三个操作:

  • 建立与数据库的连接
  • 执行SQL语句
  • 获取执行结果

JDBC驱动: 数据库驱动程序是JDBC程序和数据库之间的转换层,数据库驱动程序负责将JDBC调用映射成特定的数据库调用,有4种类型:JDBC-ODBC桥(最早),本地API驱动,网络协议驱动,本地协议驱动(建议使用,纯java编写)

JDBC API:提供一组用于与数据库进行通信的接口和类,都定义在java.sql包下。

Java.sql包常用接口和类
DriverManager用于管理JDBC驱动的服务类,该类的主要功能是加载和卸载各种驱动程序,建立数据库连接并获取连接对象
Connection该接口代表数据库的连接,要访问数据库必须先获得数据库的连接
Statement用于执行SQL语句的工具接口,当执行查询语句时返回一个查询到的结果集
PreparedStatement该接口用于执行预编译的SQL语句,这些SQL语句带有参数,避免数据库每次都需要编译SQL语句,执行时传参即可
CallableStatement该接口用于调用SQl储存过程
ResultSet该接口表示结果集,包含访问查询的各种方法

使用JDBC API中的类或者接口访问数据库时,容易引发SQLException异常,属于检查性异常。需要放在try……catch语句里,对于DriverManager来讲,要用到ClassNotFoundException异常

DriverManager类:

是数据库驱动管理类,用于管理一组JDBC驱动程序的基本服务,应用程序和数据库之间可以通过DriverManager建立连接,

DriverManager常用静态方法
static connection getConnection(String url,String user,String password)获取指定的URL的数据库连接,其中url为提供了一种标识数据库位置的方法,user用户名,password密码
static Driver getDriver(String url)返回能够打开url所指定的数据库的驱动程序
  

Connection接口:

用于连接数据,每个Connection代表一个数据库连接会话,一个应用程序可以与单个或者多个数据库连接,通过DriverManager类的getConnection()方法可以返回同一个Connection对象,该对象提供创建SQL的语法,完成基本SQL操作

Connection的常用方法
void close()断开连接,释放此Connection对象的数据库和JDBC资源
Statement createStatement()创建一个Statement对象来将SQL语句发送到数据库
void commit()用于提交SQL语句,确认从上一次提交/回滚以来进行的所有更改
boolean isClosed()用于判断Connection对象是否已经被关闭
CallableStatement prepareCall(String sql)创建一个CallableStatement对象来调用数据库储存过程
PrepareStatement(String sql)创建一个PreparedStatement对象来将参数化的SQL语句发送到数据库
void rollback()用于取消SQL语句,取消在当前事务中进行的所有更改

Statement接口:

一般用于执行SQL语句,在JDBC中要执行SQL查询语句的方式有一般查询(Statement),参数查询(PrepareStatement)和储存查询(CallableStatement )三种方式Statement和PreparedStatement和CallableStatement三个接口具有依次继承关系。Statement接口的主要功能是将SQL语句传送给数据库,并返回SQL语句的执行结果,Statement提交的SQL语句是静态的,不需要接受任何参数,SQL语句可以包含三种类型语句:SELECT查询语句,DML语句,DDL语句。

Statement接口常用方法
void close()关闭Statement对象
boolean execute(String sql)执行给定的SQL语句,可以返回多个结果
ResultSet executeQuery(String sql)执行给定SQL语句,返回单个ResultSet对象
int executeUpdate(String sql)执行给定的SQl语句,可以为DML语句或者DDL语句,返回影响行数
Connection getConnection()获取生成此Statement对象的Connection对象
int getFetchSize()获取结果集合的行数,该数是根据此Statement对象生成的ResultSet对象的默认获取大小
int  getMaxRows()获取由此Statement对象生成的ResultSet对象可以包含的最大行数
ResultSet getResultSet()获取由此Statement执行查询语句返回的的ResultSet对象
int getUpdateCount()获取此Statement执行DML语句所影响的记录个数
void cioseOnComplection()当所有依赖Statement对象的ResultSet结果集关闭时,该Statement会自动关闭
boolean isCloseOnCompletion()判断是否打开closeOnCompletion()
long executeLargeUpdate(String sql)增强版读完executeUpdate()方法,记录数超过Integer时使用,为long

ResultSet接口:

用于封装结果集对象,该对象包含访问查询结果的方法,使用Statement中的executeQuery()方法可以返回一个ResultSet结果集对象(f封装了所有查询条件的记录),ResultSet具有指向当前数据行的游标,并提供了许多方法来操作结果集中的游标,提供getXXX()方法对结果集进行访问。调用next()方法游标会向下移动,      

ResultSet接口常用方法
boolean absolute(int row)将游标移动到row条记录
boolean relative(int rows)按相对行数(正或负)移动游标
void beforeFirst()将游标移动到结果集的开头(第一行前)
boolean first()将游标移动到结果集的第一行
boolean previous()将游标移动到结果集的上一行
boolean next()将游标移动到结果集中当前位置的下一行
boolean last()将游标移动到结果集的最后一行
void afterlast()将游标移动到结果集的末尾(最后一行后)
boolean isAfterLast()判断游标是否位于结果集的末尾(最后一行后)
boolean isBeforeFirst()判断游标是否位于结果集的开头(第一行前)
boolean isFirst()判断游标是否位于结果集的第一行
boolean isLast()判断游标是否位于结果集的最后一行
int getRow()检索当前行编号
String getString(int x)返回当前行第X列的列值,类型为String
int getInt(int x)返回当前行第X列的列值,类型为int
Statement getStatement()获取生成结果集的Statement对象
void close()释放此ResultSet对象的数据库和JDBC资源
ResultSetMetaData getMetaData()获取结果集的列的编号,类型,和属性

数据库环境搭建:

1,创建数据库表。2设置Oracle驱动类路径(Oracledatabase所提供的JDBC驱动程序(jre文件)导入到工程中)。

数据库访问:使用JDBC访问数据库步骤:

  1. 加载数据库驱动:Class.forName("数据库驱动类名");mysql:com.mysql.jdbc.Driver;oracle:oracle.jdbc.driver.OracleDriver;
  2. 建立数据连接:Connection conn = DriverManager.getConnection(String url,String user,String pass);                                            mysql:DriverManager.getConnection("jdbc:mysql://localhost:3306/shop", "root","mysql");                                                  oracle:DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","sys as sysdba","oracle");
  3. 创建Statement对象:通过Connection对象获取Statement的方法;                                                                                              createStatement()//创建一个基本的Statement对象。prepareStatement(String sql)//根据参数化的SQL语句创建一个预编译的PreparedStatement对象。PrepareCall(String sql)//根据SQL语句创建一个CallableStatement对象,用于调用储存过程。   
  4. 执行SQl语句:获取Statement对象后,可以调用该对象的不同方法来执行SQL语句,有三种执行SQl语句的方法。                    executeQuery():只能执行查询语句,用于产生单个结果集。executeUpdate()和executeLargeUpdate():用于执行DML和DDL语句,返回对应的行数和0;execute():可以执行任何SQL语句返回值为布尔型。Statement对象将结果集分装为ResultSet对象并返回
  5.  访问结果集:SQL的查询结果使用ResultSet封装,ResultSet结果集中包含了满足SQL查询条件的所有的行,使用getXXX()方法对结果集进行数据访问,通过列索引和列名获取游标所指的列数据,通过next()和循环控制游标
  6. 关闭对象:关闭结果集,Statement对象,连接对象。

操作数据库:

Statement接口:

execute()方法可以执行任何SQL语句,返回值为布尔型表示是否返回了ResultSet对象,

  • true时使用Statement的getResultSet()方法来获取execute()方法执行SQL语句返回的ResultSet对象;
  • false时使用getUpdateCount()方法获取受影响得行数。

executeUpdate()和executeLargeUpdate():用于执行DML和DDL语句,返回对应的行数和0;当DML语句影响的记录超过Integer.MAX_VALUE时,使用executeLargeUpdate(),类型为long;

PreparedStatement接口:

  PreparedStatement对象包含的SQL语句进行预编译,需要多次执行相同的SQL语句时,编译好的语句比Statement对象快。可以执行动态的SQL语句,即在SQL 语句中提供参数,动态SQL语句中用?作为动态参数的占位符。用setXXX()的方法通过占位符的索引完成对参数的赋值。可以防止sql注入

         例:
         

String insterSql = "INSERT INTO LIRUILONG.my_tests VALUES(?,?)";   

         PreparedStatement ps = conn.prepareStatement(insterSql);
         ps.setInt(1,4);
         ps.setString(2, "李瑞龙");      

CallableStatement接口:

JDBC提供CallableStatement接口,用于执行数据库的储存过程,该接口可以处理一般的SQL 语句,也可以处理以三种带参数(IN ,OUT,IN OUT)的SQL储存过程,使用Connection类的prepareCall(String sql)方法可以创建一个CallableStatement对象,方法的参数可以是一个调用存储过程的字符串,cast = conn.prepareCall("{call LIRUILONG.addSub(?,?)");对CallableStatement的SQL语句执行一般用execute().

调用存储过程的SQL:“{call 存储过程名[(参数占位符?)]}”、、有返回值的:“{call 存储过程名[(参数占位符?)]}”

CallableStatement接口通过setXXX()方法对IN参数进行赋值,

通过registerOutParameter(1, sql.Types.)方法对OUT参数进行类型注册,第二个参数通常使用java.sql.Types静态常量指定。

检索结果的获取通过getXXX()方法获取OUT和IN OUT参数的值,

对于IN OUT 参数的值来讲需要先使用setXXX()方法对参数进行设置,然后使用registerOutParameter()进行类型注册,最后使用getXXX()方法来获取检索结果

数据库访问优化:

即编写一个数据库访问工具类DBUtil,用于提供访问数据库时所用到的连接,查询等

  1. 编写属性文件:存放连接数据库的参数信息,在项目的更目录下创建一个config子目录。并添加一个属性文件oracle.properties,该文件以键-值对的形式保存连接Oracle的配置信息。在读取配置文件的配置信息时,需要编写一个Config配置类,在给类中通过java.util.Properrties类的get()方法来获取指定的“键”所对应的值。
  2. 编写DBUtil工具类。将SQL语句与参数包装传递
  3. 使用DBUtil工具类。

集元数据

集元数据(Meta Data)是有关数据库和表结构的信息,JDBC提供了获取这些信息的DatabaseMetaData和ResultSetMetaData接口。

DatabaseMetaData接口

DatabaseMetaData接口主要用于获取数据库相关信息,如数据库的所有表的列表,系统函数,关键字,数据库产品名以及驱动类型。DatabaseMetaData对象通过getMetaData()方法进行获取。DatabaseMetaData接口提供大量获取信息的方法,这些方法可分为两大类:

  • 返回值为boolean型,多用于检查数据库或驱动器是否支持某项功能,
  • 获取数据库或驱动的参数特征值。
DatabaseMetaData
boolean supportsOuterJolns()检查数据库是否支持外部连接
boolean supportsStoredProcedures()检查数据库是否支持储存过程
String getURL()返回用于连接数据库的URL地址
String getUserName()获取当前的用户名
String getDatabaseProductName()获取使用的数据库产品名
String getDatabaseProductVersion()获取使用的数据库版本号
String getDriverName()获取用于连接的驱动类型名称
String getProductVerslon()获取用于连接的驱动器版本号
ResultSet getTypeInfo()获取数据库中可能取得的所有数据类型的描述

ResultSetMetaData接口

用于获取结果集的结构信息,通过ResultSet的getMetaData()方法来获取对应的ResultSetMetaData对象

ResultSetMetaData的常用方法
int getColumnCount()返回此ResultSst对象中的列数
String getColumnName(int column)获取指定列的名称
int getColumnType(int cloumn)检索指定列的SQL类型
String getTableName(int column)获取指定列的表名
int getColumnDisplaySize(int column)指示指定列的最大标准宽度,以字符为单位
boolean isAutoIncrement(int column)指示是否为自动为指定列中进行编号,这些列任然为只读
int isNullable(int column)指示指定列中的值是否可以为null
boolean isSearchable(int column)指示是否可以在where子句中使用指定的列
boolean isReadOnly(int column)指示指定法人列是否明确不可写入

事务处理:

事务是保证底层数据完整的重要手段由一步或几步数据库操作序列注组成的逻辑执行单元。事务具有ACID四个特性:

原子性(Atomicity):事务是应用中的最小执行单位,具有不可在分的特性,全部完成或不执行。

一致性(Consistency):事务执行前后数据库都必须是出于一致状态。

隔离性(Isolation):各个事务互不干扰,及并发事务间互不影响。

持久性(Durability):事务一旦提交,对数据库的的物理存储进行改变,不被丢失。

事务处理包括事务提交,终止和回滚,事务提交分显式提交(commit)和自动提交,事务终止指未能成功完成事务,执行中断,事务回滚有显式回滚(rollback)和自动回滚(系统错误或强行退出)

JDBC对事务操作由Connection提供:

  • 开启事务,执行任意多条DML语句,执行成功提交事务,
  • 失败回滚事务,Connection在默认情况下会自动提交,及事务是关闭的,一条SQL语句执行后,系统调用commit()方法提交数据库,无法回滚,
  • 使用Connection的setAutoCommit()方法可以开启关闭自动提交模式(事务),该方法为布尔型,参数false为关闭自动提交,反之打开自动提交。

开启事务:conn.setAutoCommit(false);提交事务:conn.commit();回滚事务:conn.rollback();

当程序遇到未处理的SQLException异常,都会自动回滚,当捕获该异常时,则需要在处理块中显示回滚。

保存点操作:使用Savepoint类声明一个保存点对象(Savepoint s1),在需要的位置设置保存点(s1= conn.setSavepoint();)在回滚操作中可以回滚到保存点位置(conn.rollback(s1));

批量更新:即多条SQL语句被作为一批操作同时收集,并同时提交,必须得到底层数据库的支持,调用DatabaseLargeBatch()方法查看底层数据库是否支持批量更新。创建一个Statement对象,使用Statement对象的addBatch()方法收集多条SQL语句,调用Statement的executeBatch()或者executeLargeBatch()方法同时提交所有的SQL语句。

public class DBUtil {
	Connection conn = null;//定义一个连接类
	PreparedStatement pstmt = null;//定义一个操作带参数sql语句的类
	Statement stme = null;//普通查询
	CallableStatement clst = null;//带参数存储过程
	ResultSet rs = null;//结果集
	public Connection getConnection() throws ClassNotFoundException,SQLException,
	                                         InstantiationException,IllegalAccessException{
		String driver = Config.getValue("driver");
		String url = Config.getValue("url");
		String user = Config.getValue("user");
		String pwd = Config.getValue("pwd");
		try {
			Class.forName(driver);//加载数据库驱动
			conn=DriverManager.getConnection(url, user, pwd);
			System.out.println("连接成功");
			return conn;
		}catch(Exception e) {
			throw new SQLException("驱动错误或连接失败");
		}
	}
	public void closeAll() {
		if(rs!=null) {
			try {
				rs.close();
			}catch(SQLException e) {
				e.printStackTrace();
			}
		}
		if(pstmt != null) {
			try {
				pstmt.close();
			}catch(SQLException e) {
				e.printStackTrace();
			}
		}
		if(clst != null) {
			try {
				clst.close();
			}catch(SQLException e) {
				e.printStackTrace();
			}
		}
		if(rs != null) {
			try {
				rs.close();
			}catch(SQLException e) {
				e.printStackTrace();
			}
		}
		if(conn != null) {
			try {
			conn.close();
		}catch(SQLException e) {
			e.printStackTrace();
			
		}
			
		}
	}
	public ResultSet executeQuery(String preparedSql,String [] param) {
		try {
			pstmt = conn.prepareStatement(preparedSql);
			if(param !=null) {
				for(int i= 0;i<param.length;i++) {
					pstmt.setString(i+1, param[i]);
				}
			}
			rs = pstmt.executeQuery();
		}catch(Exception e) {
			e.printStackTrace();
		}
		return rs;
	}
	public int executeUpdate(String preparedSql,String[] param) {
		int num = 0;
		try {
			pstmt = conn.prepareStatement(preparedSql);
			if(param !=null) {
				for(int i= 0;i<param.length;i++) {
					pstmt.setString(i+1, param[i]);
				}
			}
			num =pstmt.executeUpdate();
		}catch(Exception e) {
			e.printStackTrace();
		}
		return num;
	}
	public void prepareCall(String preparedSql,String[] param) {
		try {
		 clst= conn.prepareCall(preparedSql);
		if(clst !=null) {
			for(int i= 0;i<param.length;i++) {
				clst.setString(i+1, param[i]);
			}
			clst.execute();
			System.out.println("����ɹ���");
		}

		}catch(Exception e) {
			e.printStackTrace();
			
		}
	}
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		DBUtil as = new DBUtil();
		Connection asa=null;
		try {
		asa=as.getConnection();
		}catch(SQLException e) {
			e.printStackTrace();
		}
	}
	
	
}

Swing UI设计

GUI概述:图形用户界面(Graphical User Interface ,GUI)的形式。Java提供AWT(Abstract Windows Tookit ,抽象窗口工具集)和Swing来实现GUI图形用户界面编程。

AWT,Swing。2D,API,辅助功能API以及拖放API共同组成JFC(Java Foundation Class ,Java基础类库)。

使用Swing组件进行GUI编程的优势有以下几点:

  • Swing用户界面组件丰富,使用优雅。
  • Swing组件对底层平台的依赖少,与平台的BUG也少。
  • 能够保证不同平台用户一致的感官效果。

Swing组件层次

大部分Swing组件都是JComponent抽象的直接或间接子类,在JComponent 抽象类中定义了所有子类组件的通用方法,   JComponent类位于javax.swing的java扩展包中。

JComponent类的继承层次(由父类到子类以下都是):java.lang.Object,java.awt.Comtainer,java.awt.Container,java.swing.Component.

将Swing组件按功能划分:

顶层容器:JFrame,JApplet,JDialog,JWindow。

中间容器:JPanel,JScrollPane,JSplitPane和JToolBar等

特殊容器:特殊作用的中间容器,如JInternalFrame,JRootPane和JLayeredPane等。

基本组件:与用户交互的组件,如JButton,JComboBox,JList,JMenu和JSlider等。

特殊对话框:直接产生特殊对话框组件,如JColorChooser和JFileChooser等。

不可编辑信息的显示组件:组件中的信息不可编辑,如JLable,JProgressBar和JToolTip等;

可编辑信息的显示组件:组件中的信息可以编辑,如JTeXField,JTextArea和JTable等。

容器:可以用来存放其他组件,而容器的本身也是一个组件,属于Component的子类。容器具有组件的所有性质,同时还具备容纳其他组件的容器功能。

JFrame顶级容器:

Jframe(窗口框架):是可以独立存在的顶级窗口容器,能够包含其他子容器,但不能被其他容器所包含。JFrame继承了java,awt.Frame.类;java.lang.Object,java.awt.Component,java.awt.Window,java.awt.Frame,javax.swing.JFrame. 构造方法如下want to sy:

JFrame():不带参数的构造方法,该方法用于创建一个初始不可见的新窗体。

JFrame(String title):带一个字符串的参数,创建一个初始不可见的新窗体,窗口标题为字符串参数。

JFrame的方法列表
protected void frameInit()在构造方法中调用该方法用来初始化窗体
public Compoent add(Component comp)Container 的子类,用于向窗口添加组件
public voidsetLocation(int x,int y )Component的子类,用于设置窗口位置坐标
public void setSize(int width ,int height)从Window中继承而来,用于设置窗口大小
public void setVisble(boolean b)从window中继承而来,用于设置是否可视,当参数为true时可视,反之隐藏
public void setContentPane(Container contentPane)设置容器面板
public void setIcomImage(Image)设置窗体左上角的图标
public void setJMenuBar(JMenuBar menubar)设置窗体的菜单栏
public void setDefaultCloseOperation(int operation)

设置用户对此窗体的默认关闭操作,参数为常量:

DO_NOTHING_ON_CLOSE:不执行任何操作

HIDE_ON_CLOSE:自动隐藏该窗体

DISPOSE_ON_CLOSE:自动隐藏并释放该窗体

EXIT_ON_CLOSE:退出应用程序

public void setTitle(String title)从Frame类中继承而来,用于设置窗口标题

JPanel(面板)中间容器

JPanel类与顶级容器不同,不能独立存在,必须放在其他容器中,JPanel中间容器的意义在于为其他组件提供空间,使用时,一般先将其他组件添加到JPanel中间容器中,然后在将JPanel中间容器添加到JFrane顶级容器中。

java.lang.Object,java.awt.Component,java.awt.Container,java.swing.JCompoent,javax.swing.JPanel;构造方法如下:

JPanel():不带参数的构造方法,用于创建一个默认为流布局(FlowLayout)的面板

JPanel(LayoutManager layout):参数为一个布局管理器,创建一个指定布局的面板。

JPanel的方法列表
public Component add(Compoent comp)从Container类中继承而来,用于向面板容器中添加其他组件
public void setLayout(LayoutManager mgr)从Container类中继承而来,设置面板的布局方式

  布局

布局管理器用来管理组件在容器中的布局格式,当容器中容纳多个组件时,使用布局管理器对组件进行排列和分布。AWT提供FlowLayout(流布局),BorderLayout(边界布局),GridLaout(网格布局),GridBagLayout和CardLayout(卡片布局)五个常用布局管理器,Swing提供BoxLayout(盒布局)布局管理器还有NULL布局。

FlowLayout流布局:组件从左到右,从上到下,流动的排布分列,构造方法:

FlowLayout():使用默认对其方式(中间对其)和默认间距的方式(水平垂直5px)创建新的流布局管理器。

FlowLayout(int align):带有对齐方式参数的构造方法,

FlowLayout(int align,int hgap,int vgap):带有对齐方式,水平垂直间距的流布局管理器。

FlowLayout.LEFT:左对齐,FlowLayout.CENTER:居中对齐,默认方式,FlowLayout.RIGHT:右对齐。

FlowLayout是面板的默认布局,创建一个面板时默认流布局。当容器采用FlowLayout流布局时,改变窗体大小,各组件的位置会随窗体变化而变化,大小不变保持原来大小。

BorderLayout边界布局:允许组件有选择的放置在容器的中东南西北五个区域。在向容器中添加组件时,要指定位置

add(Componentcomp,int index),一般只能放置一个组件,多个时,后放置的组件会被覆盖,构造函数:

BorderLayout():创建一个无间距的边界布局管理器。

BorderLayout(int hgap, int vgap):创建一个指定水平垂直边距的边界布局管理器。

BorderLayout.EAST/WEST/SOUTH/NORTH/CENTER=东西南北中(默认)。

BorderLayout边界布局是窗体(JFrame)的默认布局,使用边界布局,改变窗体,组件拉伸扩展

GridLayout网格布局:像表格一样,等大分割窗体,添加组件默认左到右,上到下,组件随窗体变化。构造方法:

GridLayout(int rows,int cols):创建指定行数和列数的布局管理器。

GridLayout(int rows,int cols,int hgap,int vgap):用于创建一个指定行数,列数水平间距和垂直间距的的网格布局管理器。

CardLayout卡片布局:将组件看做是重叠的卡片加入容器中,每次只能看到对上面的,以时间来管理容器的组件。构造方法:

CardLayout()创建默认间距为0的布局管理器对象。

CardLayout(int hgap,int vgap):带参数的构造方法,指定水平垂直间距的卡片布局管理器对象。

CardLayout类中的构造方法
first(Container parent)显示容器中第一张卡片
last(Container parent)显示容器最后一张卡片
previous(Container parent)显示当前卡片的上一张
next(Container parent)显示当前卡片的下一张
show(Container parent)显示指定名称的卡片

一个容器使用CardLayout卡片布局后,向容器中添加组件时,使用两个参数的add()方法。add(String name ,Container comp);给组件指定名字。卡片组件的大小随窗体变换变换。

BoxLayout盒布局:Swing引入新的布局管理器,可以在水平垂直两个方向摆放布局。构造:BoxLayout(Container target,int axis);创建一个基于target容器,沿给定轴放置的组件的盒布局管理器对象。

BoxLayout.X_AXIS/Y_AXIS=X轴/Y轴排列。LINE_AXIS:根据容器的ComponentOrienation属性,按照文字在一行的排列布置组件。

PAGE_AXIS:根据容器的ComponentOrienation属性,按照文本行在一页的排列布置组件。

NULL空布局:可以采用混合布局(多个布局管理器结合使用)或者采用NULL布局,即不采用任何布局,通过组件的绝对定位进行布局。

使用NULL布局的步骤:

  1. 设置面板对象布局为空,p.setLayout(null);
  2. 调用setBounds()设置组件绝对位置的坐标和大小。或使用setLocation(),和setSize()设置组件的最
  3. 将组件添加到容器中。

NULL用于组件位置相对固定的,窗口不允许随便变换大小,否则会偏移。

第五章,线程

线程和进程:在操作系统中每个独立运行的程序就是一个进程(Process),当程序进入到内存运行时变成一个进程,是操作系统进行资源分配和调度的独立单位。

进程的特性:独立性(拥有自己的资源,私有的地址空间,除非允许其他进程不能访问),动态性(程序是静态指令的集合,只有程序进入到内存中,才变为进程,进程是一个在运行的,动态的指令集合,进程具有自己的生命周期和各种不同状态),并发性(多个进程可以在单个处理器上并发操作,互补影响)。

并发性(concurrency):多个事件在同一时间间隔内执行,CPU上运行多个进程进行切换。并不是同时发生。

并行性(parallel):是多额事件在同一时刻发生。实质是在多个进程在同一时刻可以在不同的CPU上同时执行,每个CPU运行一个进程。

线程是进程的组成部分。线程是最小的物理单位,可以拥有自己的堆栈,计数器和局部变量,但不能有系统资源(多个线程共享)。多线程可以在一个程序中同时完成多个任务。

多线程在多CPU的计算机中可以实现真正物理上的同时执行,而对于单CPU事务计算机实现的只是逻辑是上的同步。多线程之间的数据块可以共享,一个进程中的各个线程可以共享程序段,数据段等资源,多线程比多进程更便于资源共享,java的同步机制解决线程之间的数据完整性问题。

多进程之间的数据块是相互独立的,彼此互不影响,进程之间需要通过信号,管道等进行交互。

多线程之间共享内存,节约系统资源成本。充分利用CUP,执行并发任务效率高,Java内置多线程功能支持,简化编程模型。使得GUI界面的交互性好。                                             

Java线程模型:

java线程模型有Thread类,Runnable接口和Future接口等。都是面向对象的。

 

Thread类的常用方法
Thread()不带参数的构造方法,用于构造默认的线程对象
Threrad(Runnable target)使用传递的Runnable构造线程对象。
Thread(Runnable target,String name)使用传递的Runnable构造名为name的线程对象。
ThreadThreadGroup group,Runnable target,String name)使用传递的Runnable在group线程组内构造名为name的线程对象。
final String getName()获取线程的名称
final boolean isAlive()判断线程是否处于激活状态,如果是(当线程处于就绪,运行和阻塞时候),则返回true,处于新建,死亡返回false。
final void setName(String name)设置线程名称
long getId()获取线程Id
setPriority(int newPriority)设置线程的优先级,
getPriority()获取线程的优先级
final void join()等待线程死亡,一般用于线程加入,存在线程A,需要插入线程B,并要求线程b执行完执行线程A,join方法加入到另外一个线程中,另一个线程会等到调用join的线程执行完在继续执行。
static void sleep(long millis)线程休眠,即将线程挂起一段时间,参数以毫秒为基本单位需要抛出异常
void run()线程的执行方法
void start()启动线程并运行的方法,启动线程后会自动执行run()方法,start方法不能多次调用,多次调用会抛出IllegaIThreadStateException异常
void stop()线程停止,方法过期
void interrput()中断线程
static int activeCount()返回激活的线程数
static void yield()临时暂停正在执行的线程,并允许执行其他线程。线程的礼让,非非强制性。

       Thread类的run()方法是线程中最重要的方法,该方法用于执行线程要完成的任务,当创建一个线程时,完成自己的业务逻辑需要重写run()方法。任何继承Thread类的线程都可以通过start()方法来启动。

      Runnable接口用于标识某个Java类是否作为线程类,该接口只有一个抽象方法run(),即线程中最重要的执行体,用于执行线程所要完成的任务。java.lang包下。

      Callable接口时java5新增的接口,该接口提供一个call()方法作为线程的执行体,call方法比run方法功能更加强大,call()可以有返回值,也可以声明抛出异常,java.util.concurrent包中。

      

package java.lang;
public interface Runnable{
    public abstract void run();
}

package java.util.concurrent;
public interfaceCallable<V>{
    V call() throws Exception;
}

       Future接口用来接收Callable接口中call()方法的返回值,future接口提供一些方法用于控制与其关联的Callable任务,

  

Future接口中的方法
boolean cancel(boolean mayInterruptIfRunning)取消与Future关联的Callable任务
boolean isCancelled()判断Callable任务是否被取消。如果在任务正常完成之前被取消,则返回true
boolean isDone()判断callable任务是否完成,从完成返回true
V get() throws InterruptedException,ExecutionException返回Callable任务中call()方法的返回值。
V get(long timeout,TimeUnit unit )throws InterrupttedException,ExceptionExecutionException,TimeoutException在指定时间内返回Callable任务中的call()方法的返回值,如果没有返回,则抛出异常。

Callable接口有泛型限制,该接口中的泛型形参类型与call()方法返回值的类型相同,而且对callable接口是函数式接口,因此从java8开始可以使用Lambda表达式创建callable对象。

主线程:

每个独立运行的程序就是一个进程,每个进程至少包含一个线程,即主线程。Java中每个独立运行的java 程序都至少有一个主线程,且在程序启动时,JVM会自动创建一个主线程来执行该程序中的main()方法。一个进程肯定包含一个主线程,主线程用来执行main()方法。

/*
*在main()方法中调用Thread()类的静态方法currentThrerad()来获得主线程。
*
*/
public class MainThreadDemo {

	public MainThreadDemo() {
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//调用Thread类的currentThread()方法获取当前线程
		Thread t = Thread.currentThread();
		t.setName("李瑞龙");
		System.out.println("主线程为:"+t);
		//获取 线程ID;
		System.out.println("线程ID为:"+t.getId());
		//获取线程名
		System.out.println("线程名:"+t.getName());
	}

}

在多线程编程时,main()方法的方法体就是主线程的执行体,main()方法中的代码就是主线程要完成的任务。通过Thread.curreadThread()静态方法获取主线程;

创建线程的方式:

第一种方式是继承Thread类,重写Thread‘类中的run()方法,直接创建。(1,定义一个子类继承Thread类,并覆写run()方法。2,创建子类的实例,实例化线程对象。3,调用线程对象的start()方法.)

后两种方式是因为Java不支持多继承,可以通过实现接口的方式间接创建线程。多个线程可以共享一个target目标对象。适合多个相同的线程处理同一个资源的情况,从而将CPU,代码和数据分开,形成清晰的数据模型。通过继承Thread类的方式来创建线程,直接使用this即可获得当前线程,但受制于单继承限制,一般推存使用接口的方式。

线程在CPU中的执行时由操作系统所控制的,执行顺序是不确定除非使用同步机制按特定的顺序执行。

package Demo.liruilong.Threrad;

public class ThreadDemo extends Thread {

	public ThreadDemo() {
		// TODO Auto-generated constructor stub
	}

	@Override
	public void run() {
		for(int i = 0;i<100;i++) {
			System.out.println("当前线程的名字:"+this.getName()+":"+i);
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadDemo td  = new ThreadDemo();
		td.start();
		for(int i = 1000;i<1100;i++) {
			System.out.println("当前线程的名字: "+Thread.currentThread().getName()+":"+i);
		}
	}

}
//两个线程,主线程和创建的子线程。JVM会自动创建主线程来执行main方法,即主线程执行体不是run()方法,而是main()方法。

两个线程,主线程和创建的子线程。JVM会自动创建主线程来执行main方法,即主线程执行体不是run()方法,而是main()方法。

第二种方式是实现Runnable接口,在通过Thread类和Runnable的实现类间接创建一个线程。Runnable接口中有一个run()方法。一个类实现Runnable接口后,并不代表该类是一个线程类,不能直接启动线程,必须通过Thread类的实例来创建并启动线程。步骤(

1,定义一个类实现Runnable接口,并实现该类中的run()方法。

2,创建一个Thread类的实例,并将Runnable接口的实现类所创建的对象作为参数传入Thread类的构造方法中。

3,,调用Thread对象的start()方法启动该线程。

package Demo.liruilong.Threrad;

 class ThreadTask implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
	
}
 public class RunnableDemo{
	 public static void main(String[] args) {
		Thread t = new Thread(new ThreadTask());
		t.start();
		for(int i=1000;i<1100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}

	}
 }

直接调用Thread类或Runnable接口所创建的对象的run()方法无法启动线程,必须通过start()方法才能启动线程,从java8开始,Runnable接口中使用@FunctionInterface修饰说明Runnable接口是函数式接口。可以使用Lambda表达式来创建Runnable对象。

第三种方式是使用Callable和Future接口间接创建线程。Callable()接口提供一个call()方法作为线程的执行体,该方法的的返回值使用Future接口来代表,Java5开始,为Future接口提供一个FutureTask实现类,该类同时实现了Future和Runnable两个接口,因此可以作为Thread类的target参数,使用Callable和Future接口的最大优势在于可以在线程执行完任务之后获取执行结果。

步骤(

1,创建Callable接口的实现类,并实现call()方法,该方法将作为线程的执行体,并具有返回值,然后创建call()方法的返回值。

2,使用FutureTask类来包装Callable对象,在FutureTask对象中封装了Callable对象的call()方法的返回值。

3,使用FutureTask对象作为Thread对象的target,创建并启动新线程。

4,调用FutureTask对象的get()方法来获取子线程执行结束或后的返回值)。

package Demo.liruilong.Threrad;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class LambdaCallableFutureDemo {

	public LambdaCallableFutureDemo() {
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		FutureTask<Integer>task = new FutureTask<Integer>(
				(Callable<Integer>)() -> {
					int i = 0;
					for(;i<100;i++) {
						System.out.println(Thread.currentThread().getName()+":"+i);
					}
					return i;
		});
		new Thread(task,"子线程").start();
		try {
			System.out.println("子线程返回的值为:"+task.get());
		}catch(InterruptedException e){
			e.printStackTrace();
		}catch(ExecutionException e){
			e.printStackTrace();
		}
		for(int i=900;i<1000;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}

}
//使用Lambda表达式



package Demo.liruilong.Threrad;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

 class Task implements Callable<Integer> {


	@Override
	public Integer call() throws Exception {
		int i = 0;
		for(;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
		return i;
	}
 }
public class CallableFutureDemo{
	public CallableFutureDemo() {
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		FutureTask<Integer>task = new FutureTask<>(new Task());
		new Thread(task,"子线程").start();
		try {
			System.out.println("子线程返回的值为:"+task.get());
		}catch(InterruptedException e){
			e.printStackTrace();
		}catch(ExecutionException e){
			e.printStackTrace();
		}
		for(int i=900;i<1000;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}
//不使用表达式

RunnableFuture接口继承了Runnable和Future两个接口,因此FutureTask类既实现了Runnable接口,又实现了Future接口。

线程生命周期:

线程具有生命周期:包含5种状态(出生状态(New),就绪状态(Runnable),运行状态(Running),阻塞状态(Blocked)和死亡状态(Dead)).

出生状态

 

就是线程被创建时处于的状态(即使用new后),仅有JVM为其分配内存并初始化。在用户使用该线程实例调用start()方法之前线程都处于出生状态:

就绪状态(可执行状态)

在用户使用该线程实例调用start()方法之后,调度程序就可以把CPU分配给该线程,JVM会为该线程创建方法调用栈和程序计数器。处于就绪状态的线程并没有开始运行,只是表示该线程准备就绪等待执行。(只能对新建状态的线程调用start()方法,即new完一个线程,只能调用一次start()方法。否则引发IllegalThreadStateException异常)。一但线程进入可执行状态,它就会在就绪与运行状态下转换,同时也可能进入等待,休眠,阻塞,或死亡状态。处于就绪的方法(调用sleep(),wait(),IO完成)

如果调用start()方法后需要线程立即开始执行,可以使用Thread.sleep(1)来让当前运行的主线程休眠1毫秒,此时处于空闲的CPU会去执行处于就绪状态的线程,这样就可以让子线程立即执行了。

运行状态:

处于就绪状态的线程获得CPU后,开始执行run()方法的线程执行体,此时该线程处于运行状态(Running),如果计算机的CPU是单核的,则在任何时候只能有一个线程处于运行状态,一个线程开始运行后,不可能一直处于运行状态,除非线程的执行体足够短。瞬间结束。(就绪到运行)

线程在运行过程中被中断使其他的线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略,对于采用抢占式策略会为每个线程分配一小段CPU时间片,UNIX采用时间片算法,Windows采用抢占式策略,小型设备则采用协作式调度策略。

死亡状态:

当线程结束,线程进入死亡状态。线程执行run()和call()方法,正常结束,抛出一个未捕获的Exception或Error;不要对处于死亡状态的线程调用start()方法,程序只能对新建的线程调用。

阻塞状态(进入):

休眠状态,主动放弃被 占用的资源。等待状态。调用一个阻塞式IO方法,在该方法返回之前,线程被阻塞。线程试图获得一个同步的监视器,但该监视器正被其他线程所持有。程序调用线程的suspend()方法将该线程挂起,但该方法容易导致死锁。被阻塞的线程阻塞解除后会进入就绪状态而不是运行状态,必须重新等待线程调度器再次调度。

解除阻塞(进入)就绪状态:调用sleep()方法经过指定的时间。线程调用的阻塞的IO方法已经返回。线程成功地获得了试图取得的同步监视器。线程处于等待状态,其他线程调用notify()或notifyAll()方法发出一个通知时,则线程就回到就绪状态。处于挂起状态的线程被调用resume()恢复方法.

如果一个线程包含了 很长的循环,在循环的每次迭代之后把该线程切换到sleep休眠状态是一种很好的策略,这可以保证其他线程不必等待很长时间就能轮到处理器执行。

等待状态:在运行状态下的线程调用Thread类中的wait()方法,该线程进入等待状态。必须调用Thread类中的notify()方法才能被唤醒。(所有线程被唤醒)

休眠状态:当线程调用Thread的sleep()方法时,进入休眠状态。在使用sleep()方法时,该方法声明了InterrupteException异常并处理,要么在该方法使用throws显示声明抛出的异常。

主线程结束时,其他的子线程并不受影响,并不会随主线程的结束而借宿,一旦子线程启动起来,子线程就拥有和主线程相同的地位,不受影响。

线程的中断:如果线程使用sleep()或wait()方法进入就绪状态,可以使用Thread类 的interrupt()方法使线程离开run()方法,同时结束线程,担程序会抛出InterruptedException异常。可以在处理异常的块中实现线程的中断业务处理。

线程的优先级:

当有多个线程同时处于可执行状态并等待获得CPU处理器时,系统将根据各个线程的优先级来调度各线程优先级高的线程获得CPU时间的机会更多,优先级低的较少。

每个线程都有默认的优先级,其优先级都与创建该线程的父线程优先级相同,在默认情况下,主线程具有普通优先级,由主线程创建的子线程也具有普通优先级。

Thread类提供三个静态常量来标识线程的优先级:

MAX_PRIORITY------最高优先级10;

NORM_PRIORITY------普通优先级,其值为5;

MIN_PRIORITY--------最低优先级,其值为1;

Thread类提供了setProiority()方法来对线程的优先级进行设置(参数为1-10,一般用静态常量),而getPriority()方法可以获得线程的优先级。优先级并不能保证线程的执行次序,避免使用线程的优先级做为构建任务执行顺序的标准。

线程同步:多线程访问同一资源数据时,很容易出现线程安全问题,在Java中,提供了线程同步的概念以保证某个资源在某一时刻只能由一个线程访问,保证共享数据的一致性。

Java使用监控器(也称对象锁)实现同步,每个对象都有一个监控器,使用监控器可以保证一次只允许一个线程执行对象的同步语句,即在对象的同步语句执行完毕前,其他试图执行当前对象的同步语句的线程都将处于阻塞状态。只有线程在当前对象的同步语句执行完毕后,监控器才会释放对象锁,并让优先级最高的阻塞线程处理同步语句。

线程同步采用的方式:同步代码块,同步方法,同步锁;

同步代码块:需要将实例的访问语句放入一个同步块中,语法格式:

synchronizeed(Object){//同步的关键字
//需要同步的代码块
}
//Object就是同步监控器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

任何时候只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

同步方法:使用synchronizeed关键字修饰需要同步的方法。synchronized锁定的是对象,而不是方法或代码块;synchronized也可以修饰类,当用synchronized修饰类时,表示这个类的所有方法都是synchronized方法。

[访问修饰符] synchronized 返回类型 方法名 ([参数列表]){
        //方法体
}
synchronized关键字修饰的实例方法无需显示的指定同步监视器,同步方法的同步监视器时this,即该方法所属的对象。

一旦一个线程进入一个实例的任何同步方法,其他的线程将不能进入该实例的所有同步方法,但该实例的非同步方法任然能够被调用。具有同步方法类被称为线程安全类,该类的对象可以被多个线程安全的访问。

同步锁

同步锁Lock通过显示定义同步锁对象来实现线程同步,Lock是控制多个线程对共享资源进行访问的工具,能够对共享资源进行独立访问,每次只能有一个线程对Lock对象加锁,线程访问共享资源之前需要获得Lock对象,Lock和ReadWriterLock是Java5提供的关于锁的根接口,并为Lock提供了ReentrantLock(可重如锁)实现类。java8提供了StampedeLock类,可以代替ReentrantReadWriterLock类。

ReentrantLock类是常用的可重入的同步锁。该类对象可以显式的加锁,释放锁。通常使用ReentrantLock步骤:

定义一个final类型的ReentrantLock锁对象:private final ReentrantLock lock = new ReentrantLock();

在需要保证线程安全的代码之前增加"加锁"操作。lock.lock().

在执行完线程安全的代码后"释放锁"。lock.unlock();

加锁和释放锁需要放在线程安全的方法中。lock.unlock()放在finally语句中,不管发生异常是否,都会执行。

class{
    private final ReentrantLock lock = new ReentrantLock();
    //定义需要保证线程安全的方法中;
    public void myMethod(){
        lock.lock();//加锁
        try{
           //需要保证线程安全的代码
        }finally{
       lock.unlock();//释放锁。
    }
}
}

线程通信:

线程通信:java中提供一些机制来保证线程之间的协调运行。

线程通信可以使用Object类中定义的wait(),notify()和notifyAll()方法,使线程之间互相进行事件通知,执行这些方法,必须拥有相关对象的锁。

wait()方法:让当前线程等待,并释放对象锁,直到其他线程调用该监视器的notify()或notifyAll()来唤醒该线程,wait()方法也可以带一个参数,用于指明等待的时间,此时不需要notify()或notifyAll()的唤醒,wait()方法只能在一个同步方法中调用。

notify()方法:唤醒在此同步监视器上等待的单个线程,解除该线程的阻塞状态。只能在同步块(方法)中使用。

notifyAll()方法:唤醒在此同步监视器上等待的所有线程,唤醒次数由系统控制。只能在同步块(方法)中使用。

wait方法区别于Sleep方法之外,wait()方法调用时会释放对象锁。而sleep不会。

 

正则表达式

    java.util.regex是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包。它包括两个类:Pattern和Matcher Pattern (一个Pattern是一个正则表达式经编译后的表现模式)。 Matcher( 一个Matcher对象是一个状态机器,它依据Pattern对象做为匹配模式对字符串展开匹配检查)。 首先一个Pattern实例订制了一个所用语法与PERL的类似的正则表达式经编译后的模式,然后一个Matcher实例在这个给定的Pattern实例的模式控制下进行字符串的匹配工作。

反斜线字符 ('\') 用于引用转义构造,如上表所定义的,同时还用于引用其他将被解释为非转义构造的字符。因此,表达式 \\ 与单个反斜线匹配,而 \{ 与左括号匹配。

在不表示转义构造的任何字母字符前使用反斜线都是错误的;它们是为将来扩展正则表达式语言保留的。可以在非字母字符前使用反斜线,不管该字符是否非转义构造的一部分。

 

Pattern类用于创建一个正则表达式,也可以说创建一个匹配模式,它的构造方法是私有的,不可以直接创建,但可以通过Pattern.complie(String regex)简单工厂方法创建一个正则表达式, 
Java代码示例: 

Pattern p=Pattern.compile("\\w+"); 
p.pattern();//返回 \w+ 

pattern() 返回正则表达式的字符串形式,其实就是返回Pattern.complile(String regex)的regex参数

Pattern有一个split(CharSequence input)方法,用于分隔字符串,并返回一个String[]

 

 

public final class Pattern
extends Object

implements Serializable

正则表达式的编译表示形式。

指定为字符串的正则表达式必须首先被编译为此类的实例。然后,可将得到的模式用于创建 Matcher 对象,依照正则表达式,该对象可以与任意字符序列匹配。执行匹配所涉及的所有状态都驻留在匹配器中,所以多个匹配器可以共享同一模式。Pattern.matcher(CharSequence input)返回一个Matcher对象.
Matcher类的构造方法也是私有的,不能随意创建,只能通过Pattern.matcher(CharSequence input)方法得到该类的实例. 
Pattern类只能做一些简单的匹配操作,要想得到更强更便捷的正则匹配操作,那就需要将Pattern与Matcher一起合作.Matcher类提供了对正则表达式的分组支持,以及对正则表达式的多次匹配支持. 

因此,典型的调用顺序是

  Pattern p = Pattern.compile("a*b"); 
  Matcher m = p.matcher("aaaaab"); 
  boolean b = m.matches();

在仅使用一次正则表达式时,可以方便地通过此类定义 matches 方法。此方法编译表达式并在单个调用中将输入序列与其匹配。语句

 boolean b = Pattern.matches("a*b", "aaaaab");
 Pattern.matches(String regex,CharSequence input)
 是一个静态方法,用于快速匹配字符串,该方法适合用于只匹配一次,且匹配全部字符串.

等效于上面的三个语句,尽管对于重复的匹配而言它效率不高,因为它不允许重用已编译的模式。

此类的实例是不可变的,可供多个并发线程安全使用。Matcher 类的实例用于此目的则不安全。

通过调用模式的 matcher 方法从模式创建匹配器。创建匹配器后,可以使用它执行三种不同的匹配操作:

  • matches 方法尝试将整个输入序列与该模式匹配。

    Pattern p=Pattern.compile("\\d+"); 
    Matcher m=p.matcher("22bb23"); 
    m.matches();//返回false,因为bb不能被\d+匹配,导致整个字符串匹配未成功. 
    Matcher m2=p.matcher("2223"); 
    m2.matches();//返回true,因为\d+匹配到了整个字符串

    我们现在回头看一下Pattern.matcher(String regex,CharSequence input),它与下面这段代码等价 
    Pattern.compile(regex).matcher(input).matches() 

  • find 方法扫描输入序列以查找与该模式匹配的下一个子序列。

Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("22bb23"); 
m.find();//返回true 
Matcher m2=p.matcher("aa2223"); 
m2.find();//返回true 
Matcher m3=p.matcher("aa2223bb"); 
m3.find();//返回true 
Matcher m4=p.matcher("aabb"); 
m4.find();//返回false 
  • lookingAt 尝试将输入序列从头开始与该模式匹配。

    Pattern p=Pattern.compile("\\d+"); 
    Matcher m=p.matcher("22bb23"); 
    m.lookingAt();//返回true,因为\d+匹配到了前面的22 
    Matcher m2=p.matcher("aa2223"); 
    m2.lookingAt();//返回false,因为\d+不能匹配前面的aa 

    每个方法都返回一个表示成功或失败的布尔值。通过查询匹配器的状态可以获取关于成功匹配的更多信息。

当使用matches(),lookingAt(),find()执行匹配操作后,就可以利用以下三个方法得到更详细的信息. Mathcer.start()/ Matcher.end()/ Matcher.group():

start()返回匹配到的子字符串在字符串中的索引位置. 
end()返回匹配到的子字符串的最后一个字符在字符串中的索引位置. 
group()返回匹配到的子字符串 

Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("aaa2223bb"); 
m.find();//匹配2223 
m.start();//返回3 
m.end();//返回7,返回的是2223后的索引号 
m.group();//返回2223 

Mathcer m2=m.matcher("2223bb"); 
m.lookingAt();   //匹配2223 
m.start();   //返回0,由于lookingAt()只能匹配前面的字符串,所以当使用lookingAt()匹配时,start()方法总是返回0 
m.end();   //返回4 
m.group();   //返回2223 

Matcher m3=m.matcher("2223bb"); 
m.matches();   //匹配整个字符串 
m.start();   //返回0,原因相信大家也清楚了 
m.end();   //返回6,原因相信大家也清楚了,因为matches()需要匹配所有字符串 
m.group();   //返回2223bb

start(),end(),group()均有一个重载方法它们是start(int i),end(int i),group(int i)专用于分组操作,Mathcer类还有一个groupCount()用于返回有多少组. 

Pattern p=Pattern.compile("([a-z]+)(\\d+)"); 
Matcher m=p.matcher("aaa2223bb"); 
m.find();   //匹配aaa2223 
m.groupCount();   //返回2,因为有2组 
m.start(1);   //返回0 返回第一组匹配到的子字符串在字符串中的索引号 
m.start(2);   //返回3 
m.end(1);   //返回3 返回第一组匹配到的子字符串的最后一个字符在字符串中的索引位置. 
m.end(2);   //返回7 
m.group(1);   //返回aaa,返回第一组匹配到的子字符串 
m.group(2);   //返回2223,返回第二组匹配到的子字符串

一、捕获组的概念

捕获组可以通过从左到右计算其开括号来编号,编号是从1 开始的。例如,在表达式 ((A)(B(C)))中,存在四个这样的组:

1        ((A)(B(C)))
2        (A)
3        (B(C))
4        (C)

组零始终代表整个表达式。 以 (?) 开头的组是纯的非捕获 组,它不捕获文本,也不针对组合计进行计数。

与组关联的捕获输入始终是与组最近匹配的子序列。如果由于量化的缘故再次计算了组,则在第二次计算失败时将保留其以前捕获的值(如果有的话)例如,将字符串"aba" 与表达式(a(b)?)+ 相匹配,会将第二组设置为 "b"。在每个匹配的开头,所有捕获的输入都会被丢弃。

Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("我的QQ是:456456 我的电话是:0532214 我的邮箱是:aaa123@aaa.com"); 
while(m.find()) { 
     System.out.println(m.group()); 
} 
//查找数字
while(m.find()) { 
     System.out.println(m.group()); 
     System.out.print("start:"+m.start()); 
     System.out.println(" end:"+m.end()); 
} 

 

正则表达式的构造摘要

构造匹配
 
字符
x字符 x
\\反斜线字符
\0n带有八进制值 0 的字符 n (0 <= n <= 7)
\0nn带有八进制值 0 的字符 nn (0 <= n <= 7)
\0mnn带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7)
\xhh带有十六进制值 0x 的字符 hh
\uhhhh带有十六进制值 0x 的字符 hhhh
\t制表符 ('\u0009')
\n新行(换行)符 ('\u000A')
\r回车符 ('\u000D')
\f换页符 ('\u000C')
\a报警 (bell) 符 ('\u0007')
\e转义符 ('\u001B')
\cx对应于 x 的控制符
 
字符类
[abc]a、b 或 c(简单类)
[^abc]任何字符,除了 a、b 或 c(否定)
[a-zA-Z]a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]]a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def]]d、e 或 f(交集)
[a-z&&[^bc]]a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]]a 到 z,而非 m 到 p:[a-lq-z](减去)
 
预定义字符类
.任何字符(与行结束符可能匹配也可能不匹配)
\d数字:[0-9]
\D非数字: [^0-9]
\s空白字符:[ \t\n\x0B\f\r]
\S非空白字符:[^\s]
\w单词字符:[a-zA-Z_0-9]
\W非单词字符:[^\w]
 
POSIX 字符类(仅 US-ASCII)
\p{Lower}小写字母字符:[a-z]
\p{Upper}大写字母字符:[A-Z]
\p{ASCII}所有 ASCII:[\x00-\x7F]
\p{Alpha}字母字符:[\p{Lower}\p{Upper}]
\p{Digit}十进制数字:[0-9]
\p{Alnum}字母数字字符:[\p{Alpha}\p{Digit}]
\p{Punct}标点符号:!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
\p{Graph}可见字符:[\p{Alnum}\p{Punct}]
\p{Print}可打印字符:[\p{Graph}\x20]
\p{Blank}空格或制表符:[ \t]
\p{Cntrl}控制字符:[\x00-\x1F\x7F]
\p{XDigit}十六进制数字:[0-9a-fA-F]
\p{Space}空白字符:[ \t\n\x0B\f\r]
 
java.lang.Character 类(简单的 java 字符类型
\p{javaLowerCase}等效于 java.lang.Character.isLowerCase()
\p{javaUpperCase}等效于 java.lang.Character.isUpperCase()
\p{javaWhitespace}等效于 java.lang.Character.isWhitespace()
\p{javaMirrored}等效于 java.lang.Character.isMirrored()
 
Unicode 块和类别的类
\p{InGreek}Greek 块(简单)中的字符
\p{Lu}大写字母(简单类别
\p{Sc}货币符号
\P{InGreek}所有字符,Greek 块中的除外(否定)
[\p{L}&&[^\p{Lu}]] 所有字母,大写字母除外(减去)
 
边界匹配器
^行的开头
$行的结尾
\b单词边界
\B非单词边界
\A输入的开头
\G上一个匹配的结尾
\Z输入的结尾,仅用于最后的结束符(如果有的话)
\z输入的结尾
 
Greedy 数量词
X?X,一次或一次也没有
X*X,零次或多次
X+X,一次或多次
X{n}X,恰好 n
X{n,}X,至少 n
X{n,m}X,至少 n 次,但是不超过 m
 
Reluctant 数量词
X??X,一次或一次也没有
X*?X,零次或多次
X+?X,一次或多次
X{n}?X,恰好 n
X{n,}?X,至少 n
X{n,m}?X,至少 n 次,但是不超过 m
 
Possessive 数量词
X?+X,一次或一次也没有
X*+X,零次或多次
X++X,一次或多次
X{n}+X,恰好 n
X{n,}+X,至少 n
X{n,m}+X,至少 n 次,但是不超过 m
 
Logical 运算符
XYX 后跟 Y
X|YXY
(X)X,作为捕获组
 
Back 引用
\n任何匹配的 nth 捕获组
 
引用
\Nothing,但是引用以下字符
\QNothing,但是引用所有字符,直到 \E
\ENothing,但是结束从 \Q 开始的引用
 
特殊构造(非捕获)
(?:X)X,作为非捕获组
(?idmsux-idmsux) Nothing,但是将匹配标志i d m s u x on - off
(?idmsux-idmsux:X)  X,作为带有给定标志 i d m s u x on - off
非捕获组
(?=X)X,通过零宽度的正 lookahead
(?!X)X,通过零宽度的负 lookahead
(?<=X)X,通过零宽度的正 lookbehind
(?<!X)X,通过零宽度的负 lookbehind
(?>X)X,作为独立的非捕获组

 

网络编程:

网络类型:

根据规模类型:局域网,城域网,广域网。

根据网络的拓扑结构:星型,总线型,环型,树型,分布式,网状,蜂窝型,混合型。

TCP/IP协议:在计算机网络中实现通信必须遵守一些约定,即通信协议,即传输数据的规则,用于规范传输速率,传输代码,传输控制步骤,出错控制。

IP地址和端口:

IP地址用于唯一标识网络中的一个通信实体,可以为一台主机,可以为打印机,或路由器的端口,基于IP协议传输的数据包,必须使用IP地址进行标识。网络设备根据IP地址将数据包从源通行实体送往指定的目标通行实体。IP地址为32位的二进制地址, 通常会将其分为4个8位的二进制数,每八位用圆点隔开,而每8位二进制可以转换为0-255的十进制数,所有日常的IP地址为4个0-255之间的数组成。IP地址被分为5类:abcde。

端口:IP地址用于唯一标识网络上的一个通行实体,但一个通行实体可以同时提供多个网络端口,如果把IP地址比作房子,则端口为门。即应用程序与外界交流的出入口,端口是一种抽象的软件结构,包括数据结构和I/O缓存区,一台计算机上不能有两个程序同时使用一个端口,端口由端口号来标识,为16位的整数。0-65535.

端口分为:公认端口(通常表名某种协议)0-1023,注册端口(通常应用程序使用)1024-49151,动态/或私有端口49152-65535。

常用端口及服务:7Echo服务,21FTP服务,23Telnet服务,25SMTP服务,80HTTP服务。

域名:由一串用点分隔的字符串所组成的Internet上的计算机组的名称。即主机名,用于在数据传输时标识互联网上计算机的地理位置,开头和结尾不能有-。不区分大小写,最长可达67个字节。

域名分类:顶级域名(国际顶级域名:.com,.net,.org,国家顶级域名:.cn,.us,.jp),二级域名,三级域名。

Java网络API

Java中有关网络方面的功能都定的在Java.net包中,该包下的URL和URLConnection等类提供了以程序的方式来访问Web服务,而URLDecoder和URLEncoder而提供了普通字符串和appilcation/x-www-form-urlencoderMIME字符串相互转换的静态方法,

InetAddress类

Java提供InetAddress类来封装IP地址或域名,该类有两个子类,Inet4Address和Inet6Address,分别用于封装4个字节和6个字节的IP地址。InetAddress内部对地址数字进行了隐藏,InetAddress无构造方法,因此不能直接创建其对象,而是通过类的静态方法创建一个InetAddress对象或InterAddress数组,InetAddress常用方法:

public static InetAddress gerLocalHost()获取本机对应的InetAddress对象
public static InetAddress getByName(String host )根据主机获得具有相同名字的一组InetAddress对象
public static InetAddress [] getAllByName( ) 根据主机获得具有相同名字的一组InetAddress对象。
public static InetAddress  getByAddress( byte[] addr)获取addr所封装的Ip地址对应的InetAddress对象
public static getCanonicalHostName()获取此IP地址的全限定域名
public bytes[] getHostAddress()获取该IntetAddress对象对应的IP地址字符串。
public String getHostName()获得该InetAddress对象的主机名称
public boolean isReachable(int timeout)判断是否可到达该地址。

在获得Internet上的域名所对应的地址信息后,需要保证运行运行环境能访问Ineternet,否则抛出UnknownHostException异常。

URL类:

URL(Uniform Resource Locator,统一资源定位器),表示互连网上某一地址。由协议名,主机名(域名),端口(可选),资源四部分构成。protocol://host:post/resourceName;

Java将URL封装为URL类,通过URL对象记录URL的完整的信息。

public URL(String spec)构造方法,根据指定的字符串来创建一个URL对象
public URL(String protocol ,String host,int port,String file)构造方法,根据指定的协议,主机名,端口号和文件资源类创建一个URL对象。
public URL(String protocol, String host  ,String file)根据指定的协议,主机名,和文件资源来创建URL对象。
public String getProtocol()返回协议名
public String getHost()

返回主机名

public int geyPort()返回端口号,没有返回-1
public String getFile()返回文件名
public String geyRef()返回URL的锚
public String geyQuery()返回URL的查询信息
pubic String getPath()返回URL的路径
public URLConnection openConnection()返回一个URLConnection对象
public final InputStream openStream()返回一个读取该URL资源的InputStream流

URL的构造方法声明抛出异常MaolformedURLException,因此在创建URL对象时,需要对该异常进行处理。或者在方法后面使用throws声明显示的抛出异常。

import java.net.MalformedURLException;
import java.net.URL;

import static java.lang.System.out;
import static java.lang.System.setOut;

public class URlDemo {
    public static void main(String[] args) {
        try{
            URL mybook = new URL("http://book.moocollege.cn/java-book1.html");
            out.println("协议protocol:"+mybook.getProtocol());
            out.println("主机host:"+mybook.getHost());
            out.println("端口post:"+mybook.getPort());
            out.println("文件file:"+mybook.getFile());
            out.println("锚tef:"+mybook.getRef());
            out.println("查询路径:"+mybook.getQuery());
        }catch(MalformedURLException e){
            e.printStackTrace();
        }
    }
}

URLConnection类

URLConnection代表与URL指定的数据源的动态连接,提供一些比URL类更大的服务器交互控制方法,允许使用POST和 RUT和其他HTTP请求方法的讲数据送回服务器。是一个抽象类。

public int getContentLength()获得文件的长度
public String getContenType()获得文件的类型
public long getDate()获得文件创建的时间
public InputStream getInputStream()获取输入流,以便读取文件的数据
public OutputStream geyOutputStream()获取输出流,输出数据
public void setRequestProperty(String key,String value)设置请求属性
 
public long getLastModdified()获取文件最后修改时间
 public static void main(String[] args) {
        BufferedReader bufferedReader=null;
        try {
            //构建URL对象
            URL mybook = new URL("http://book.moocollege.cn/java-book1.html");
            //由URL对象获取URLConnection对象。
            URLConnection urlConnection = mybook.openConnection();
             //设置请求属性,字符集为UTF-8
            urlConnection.setRequestProperty("Charset","UTF-8");
            bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            String inputLine;
            while((inputLine = bufferedReader.readLine())!=null){
                out.println(inputLine);
            }
            bufferedReader.close();
        }catch (MalformedURLException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

Java8新增了一个URLPermission工具类,用于管理URLConnection的权限问题。

URLDecodder和URLEncoder类:

当系统中包含非西欧字符集时候,系统会将西欧字符集转换为特殊编码(%XX的格式),此种编码方式为applicable/x-www-form-urlencodedMIME类型,在编成过程中,可能会涉及到的普通的字符串和applicable/x-www-form-urlencodedMIME类型字符串之间的相互转换,需要URLDecoder和URLEncoder两个工具类。

  • URLDecder工具类提供了一个decode(Stirng s,Stirng enc)静态方法,将applicable/x-www-form-urlencodedMIME类型转换为普通的字符串。
  • URLEncoder工具类提供了一个encode(String s,Sting enc)静态方法,该方法与decode()方法正好相反,能够将普通的字符串转换为application/x-www-form-urlEncoderMIME字符串。

基于TCP的网络编程:

TCP/IP通信协议是一种可靠的,双向的,持续的,点对点的网络协议。使用TCP/IP 协议进通信时,会在通信两端各建立一个Socke(套接字),从而在通信的两端之间形成网络虚拟链路。Java对TCP的网络通信实现了良好的封装,使用Scoke对象封装了两端的通信端口,Scoke对象屏蔽了网络的底层细节,例如媒体类型,数据包的大小,网络地址,信息的重发等,Socket允许应用程序将网络连接当成一个IO流,既可以向流中写入数据,也可以重流中读取数据,一个Socket的对象可以用来建立Java的IO体系到Internet上的任何(包括)机器的程序的连接。

Java.net包中提供了基于TCP协议的网络编程主要的使用两种socket:

  • ServerSocket是服务器套接字,用于监听并接收来自客户端的Socket连接。
  • Socket是客户端套接字,用于实现两台计算机之间的通行。

 Socke类

使用Socket套接字可以方便的在网络上传递数据,从而实现两台计算机之间的通信客户端使用Socket来连接指定的服务器,Socket构造方法:

Socket(InetAddress|String host,int port)---------创建连接到指定远程主机和端口号的Socket对象,没有指定本地地址和本地端口号,默认使用本地主机IP地址和系统动态分配的端口。

Socket(Inet Address|String host,int port,Inet Address localAddr ,int localPort)-----创建连接到远程主机和端口号的Socket对象,并指定远程主机的端口号,,适用与本地主机有多个IP地址的情况。

创建Socket,构造方法都声明抛出IOException异常,因此在创建Socket对象必须捕获抛出异常。

public InetAddress getIneAddress()返回连接到主机的地址,连接失败返回以前连接的主机。
public int getPort()返回Socket连接到远程主机的端口号,
public int getLocalPort()返回本地连接终端的端口号
public InputStream getInputStream()返回一个输入流,从Socket读取数据
public OutputStream getOutputStream()返回一个输出流,往Socket中写入数据
public synchronized void close()关闭当前Socket连接

通常使用Socket进行网络通信的具体步骤:

  • 根据指定的IP地址和端口号创建一个Socket对象。
  • 调用getInputStream()/getOutputStream()获取连接到Socket的输入流和输出流。
  • 客户端与服务器进行交互,知道关闭连接,
  • 关闭客户端的Socket。
请问

获取本机的端口号和IP地址:

运行-->CMD-->'ipconfig/all'查看本机IP
       |__ ->'netstat -an'查看本机端口(开放的端口)

 

 try {
            //创建连接到本地的的Socket对象
            Socket socket = new Socket("172.26.19.135", 52338);
            //将Socket对应的输出流包装成PrintStream.
            PrintStream ps = new PrintStream(socket.getOutputStream());
            //往服务器发送数据
            ps.print("李瑞龙");
            ps.flush();
            //将Socket对应的输入流包装成BufferReader
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = null;
            while((line = br.readLine())!=null) {
                System.out.println("来自服务器的数据:" + line);
            }
            br.close();
            socket.close();
        }catch (UnknownHostException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }finally {

        }

SeeverSocket类:

即服务器套接字,运行在服务器端,通过指定的端口主动监听来自客户端的Socket连接,当客户端发送Socket请求并与服务器建立连接时,服务器将验证并接受客户端的Socket,从而建立客户端与服务器之间的网络虚拟链路,一旦两端的实体之间建立了虚拟链路,就可以互相传输数据。

ServerSocket类常用的构造方法,需要捕获异常。

Server Socket(int port):根据指定的端口号创建一个Server Socket对象。

ServerSocket(int port,int backlog):创建一个ServerSocket对象,指定端口和连接队列长度,此时增加一个用来改变连接队列长度的参数backlog;

ServerSocket(int port,int backlog,InetAddress localAddr):创建一个ServerSocket对象,指定端口,连接队列长度和IP地址,当机器拥有多个IP地址时,才允许使用LocalAddr参数指定具体的IP地址。

public Socket accept()接受客户端Socket连接请求,并返回一个与客户端Socket对应的Socket实例,该方法是一个阻塞方法,如果没有接受到客户端发送的Socket,则一直处于等待状态,线程也会被阻塞。
public InetAddress getInetAddress()返回当前ServerSocket实例的地址信息
publi int getLocalPort()返回当前的ServerSocket实例的服务端口信息
public void close()关闭当前ServlerSocket实例

通常使用ServerSocket进行网络通信的具体步骤:

  • 根据指定的端口号来实例化一个ServerSocket对象。
  • 调用ServerSocket对象的accept()方法接收客户端发送的Socket对象。
  • 调用Socket对象的getInputStream()/getOutputStream()方法来建立与客户端进行交互的IO流。
  • 服务器与客户端根据协议交互,直到关闭连接,
  • 关闭服务器的Socket
  • 返回第二部,继续监听下一次客户端的Socket发送请求连接。

一般服务器和客户端使用Socket进行基于C/S架构的网络通信程序设计过程:

  • 服务器端通过某个端口监听是否有客户端发送Socket连接请求,
  • 客户端向服务器发送一个Socket连接请求,
  • 服务器端调用accept()接收客户端Socket连接请求,
  • 服务器调用Socket对象的getInputStream()/getOutputStream()方法进行IO流操作,服务器与客户端之间进行信息交互。
  • 关闭服务器和客户端之间的Socket。

服务器与客户端建立连接后,两端进行信息交互是按照一定顺序进行。客户端先发送数据,然后读取数据,服务器先读取数据,后发送数据,具体步骤:

  • 客户端调用Socket对象的getOutputStream()方法获取输出流,将信息发送给服务器,
  • 服务器端调用Socket对象的getIntputStream()方法获取输入流,读客户端发送的信息。
  • 服务器端在调用Socket对象getOutputStream()方法获取输出流,将信息返回给客户端。
  • 最后客户端调用Socket对象的getInputStream()方法获取输入流,读服务发送的数据。

获取套接字的输入和输出流,都是站在内存的立场上考虑的,而不是套接字的立场,getInputStream()方法获取套接字的输入流,用于读取Socket数据,并将数据存入到内存中。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山河已无恙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值