字符流和字节流的区别:
1、字节流是读到一个字节就返回一个字节。
字符流调用字节流读取一个或多个字节,然后对照指定的编码表,返回对应的字符
2、字节流可以处理所有的数据
字符流只能处理字符数据
IO体系的基本功能读和写
字符: Reader Writer
字节: InputStream OutputStream
Reader 常用方法:
int read(); 返回读取的字符对应的整数,结尾时返回-1
int read(char[]); 返回读取字符的个数,结尾返-1
void close();可能会抛出异常,需要处理
Writer 常用方法:
write(ch); 写一个字符到流
write(char[],offset,length);
write(string);
flush();刷新流
close();关闭流,会自动调用flush(),刷新流
FileWriter :
该类没有自己特有的方法,只有自己的构造器
特点:
1、用于处理文本文件
2、该类有默认的编码表。 GBK
3、该类有临时缓冲
构造器:在写入流对象初始化时,必须关联一个数据存储的目的地(硬盘文件)
FileWriter(String fileName); 会将同名文件删除,然后新建一个
FileWriter(String fileName,boolean append)
当 append 为 true 时,会将数据流添加到文件末尾 而不是覆盖原文件
FileReader:
1、用于读取文本文件的流对象
2、用于关联文本文件
构造函数: FileReader(String fileName)
fileName必须是已存在的文件,否则抛出异常 FileNotFoundException
文本写入代码演示
class Demo
{
public static void main(String[] args)
{
FileWriter fw = null;
try
{
fw = new FileWriter("Demo.txt");
fw.write("This is a demo of writing chars...");
fw.flush();
}
catch (IOException e)
{
System.out.pringtln(e.toString());
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.pringtln(e.toString());
}
}
}
}
class ReaderDemo
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("demo.txt");
int ch = 0 ;
whlie((ch=fr.read())!=-1)
{
System.out.println((char)ch);//返回的是一个字节的整形,需要强转成字符
}
fr.close();
}
}
字符流缓冲区:
缓冲区出现的目的是提高对流的操作效率
原理:用数组对流进行存储,减少对对磁盘的读取次数
对应的类:
BufferedWriter(FileWriter fw)
特有方法:
newLine(); 跨平台的换行符
BufferedReader(FileReader fr)
特有方法:
readLine();以行为单位进行读取,返回行标记之前的内容,不返回行标记
读到末尾时返回 null
注意:缓冲区是为了增强流的功能而存在,所以新建缓冲区对象,必须关联已经存在的流对象。
否则,将出现异常。
练习:通过缓冲区进行文本的拷贝
class BufferedDemo
{
public static void main(String[] a) throws IOException
{
BufferedReader bufr = new BufferedReader(new FileReader("demo.txt"))'
BufferedWriter bufw = new BufferedWriter(new FileWriter("copy.txt"));
//以行为单位读取
String line = null;
while((line=bufr.readLine())!=null)
{
bufw.write(line);
bufw.newLine();//换行
bufw.flush();//刷新
}
bufw.close();
bufr.close();
}
}
readLine() 方法的原理:缓冲区对象,调用与之关联的流对象的read的方法读取字符,
但不是马上进行具体的操作,而是先进行判断,只有读取换行符时
才进行操作,否则现将读到的字符进行临时存储。当读到换行符时,
将临时存储区的数据一次性返回。
class MyReadLine()
{
private Reader r;
MyreadLine(Reader r)
{
this.r = r;
}
public String readline() throws IOException
{
//建立临时存储区
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=r.read())!=-1)
{
if(ch=="\r")
continue;
if(ch=="\n")
return sb.toString();
else
sb.append((char)ch);//返回的是int型,需要强转
}
if(sb.length()!=0)//不是换行符结尾,需要判断是否有字符
return sb.toString();
else
return null;
}
public void close() throws IOException
{
r.close();
}
}
装饰设计模式:基于对象的功能,并将其功能增强。
与继承的区别:
1、比继承有更好的灵活性
2、通常装饰类和被装饰类都具有相同的父类或者实现了相同的接口
练习:编写 LineNumberReader 中的 getLineNumber() 和 setLineNumber() 方法
class MyLineNumberReader extends MyReaderLine
{
private Reader() r;
private int lineNumber = 0;
MyLineNumberReader(Reader r)
{
super(r);//调用父类的构造器初始化
}
public void setLineNumber(int num)
{
this.lineNumber = num;
}
public String readLine() throws IOException
{
lineNumber++;
return super.readLine();//直接调用父类的方法
}
public int getLineNumber()
{
return lineNumber;
}
}
*******************************************************************************
分割线 分割线 分割线 分割线
*******************************************************************************
字节流:
抽象基类: InputStream OutputStream
字节流可以操作任何数据。
注意: 字节流使用的数组是字节数组。 byte[]
字符流使用 字符数组 char[]
InputStream 常用方法:
int available(); 返回此输入流(关联的文件)的字节数
void close();
int read(); 读取一个字节。返回 0 到 255 范围内的 int 字节值。
读到末尾时返回 -1
int read(byte[] b);读取字节数组长度的字节数
int read(byte[] b,int offset,int len);
long skip(long n);
装饰设计模式增强后的类:
BufferedInputStream(InputStream in)
BufferedOutputStream(OutputStream out)
练习:复制一张图片
class StreamDemo
{
public static void main(String[] s) throws IOException
{
//读取流
BufferedInputStream bufin = new BufferedInputStream(new InputStream("1.jpg"));
//输出流
BufferedOutputStream bufout = new BufferedOutputStream(new OutputStream("copy.jpg"));
int by;//注意是 int 型
while((by=bufin.read())!=-1)
{
bufout.write(by);
}
bufout.close();
bufin.close();
}
}
字节流的 read() 方法读取一个字节,但是返回的并不是byte类型,而是int类型的原因:
因为read方法读到文件末尾时,返回 -1 作为结束的标志。
而数据流中很容易出现多个连续1的情况,而连续8个1,就是-1,
此时将导致读取提前结束。
所以将读到的一个字节提升为int类型,但是只保留原字节,并将高位的字节填0
具体操作: by&255 或 by&&0xff
对于write()方法,虽然返回的是一个int型,但是实际它只写入低8位的一个字节,其余的丢弃。
简单的说,read()对读到的数据进行了提升,而write对操作的数据进行转换。
转换流:
特点:
1、是字节流和字符流之间的桥梁
2、该流对象中可以对读取到的字节数据进行指定编码表的编码转换
什么时候使用?
1、字节和字符之间有转换动作时
2、流操作的数据需要进行指定编码类型的编码时
具体的类:
InputStreamReader:字节到字符的桥梁
OutputStreamWriter:字符到字节的桥梁
这两个流的对象是字符流体系的成员。
它们有转换作用,同时本身又时字符流,所以在构造的时候,需要传递字节流对象
InputStreamReader 构造函数
InputStreamReader(InputStream in)
使用默认的字符集
InputStreamReader(InputStream in, Charset cs)
创建使用给定字符集的 InputStreamReader
InputStreamReader(InputStream in, CharsetDecoder dec)
创建使用给定字符集解码器的 InputStreamReader
InputStreamReader(InputStream in, String charsetName)
创建使用指定字符集的 InputStreamReader。
String getEncoding();返回流使用的字符编码的名称
操作文本的字符流对象是转换流的子类
Reader
|--InputStreamReader
|--FileReader //构造函数中,并不能指定编码类型
Writer
|--OutputStreamWriter
|--FileWriter
转换流中的read 方法,已经融入了编码表,
在底层调用字节流的read方法时将获取一个或者多个字节数据进行临时存储,
并去对照指定的编码表,如果编码表没有指定,
则去查询默认的编码表。这样转换流的read方法就返回对应的字符。
转换流已经完成了编码转换的动作,对于直接操作文本文件的FileReader而言,就不
用在重新定义了,
只要继承该转换流,获取其方法,就可以直接操作文本文件中的字符数据了
注意:
在使用FileReader操作文本数据时,该对象使用的是默认的编码表
如果要指定编码表时,必须使用转换流。
FileReader fr = new FileReader("1.txt");//使用默认的编码表 GBK
InputStreamReader ins = new InputStreamReader(new FileInputStream("1.txt"),"GBK");
指定使用utf-8编码表
InputStreamReader ins = new InputStreamReader(new FileInputStream("1.txt"),"utf-8");
*****************************************************************************************************
练习:用指定的编码表将数据存至文件,再用对应的编码表读取内容输出至控制台
import java.io.*;
class CharSetDemo
{
public static void main(String[] args) throws IOException
{
String charSet = "GBK";
String charSet1 = "UTF-8";
//建立读取转换流,指定编码表
InputStreamReader ins = new InputStreamReader(new FileInputStream("utf.txt"),charSet);
InputStreamReader ins1 = new InputStreamReader(new FileInputStream("gbk.txt"),charSet1);
//缓冲
BufferedReader bufin = new BufferedReader(ins);
BufferedReader bufin1 = new BufferedReader(ins1);
//建立输出转换流
BufferedWriter bufo1 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("utf.txt"),charSet));//utf-8输出
BufferedWriter bufo = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("gbk.txt"),charSet1));//gbk输出
bufo.write("我是用GBK编码的");
bufo1.write("我是用utf-8编码的");
bufo.close();
bufo1.close();
int by = 0;
//读取utf.txt文件输出到控制台
while((by=bufin.read())!=-1)
{
System.out.print((char)by);
}
System.out.println();//换行
//读取gbk.txt文件输出到控制台
while((by=bufin1.read())!=-1)
{
System.out.print((char)by);
}
System.out.println();
bufin.close();
System.out.println("Hello World!");
}
}
***********************************************************************************************
IO与集合结合的类: Properties
Map
|-- Hashtable
|--Properties
Properties 该集合已限制键值对是 String 类型,所以不需要泛型
常用方法:
Object setProperty(String key,String value); 设置(修改)键值对
返回值是 Hashtable 调用 put 的结果: 此哈希表中指定键的以前的值,如果不存在该值,则返回 null
String getProperty(key);获取key对应的值
Set<String> stringPropertyNames(); 返回列表的键值
void list(PrintStream out); 将属性列表输出至指定流
void list(PrintWriter out); 将属性列表输出至指定流
例: list(System.out);输出至控制台
list(new PrintStream("1.txt"); 输出至文档1.
void load(InputStream inStream)
从输入流中读取属性列表(键和元素对)。
void load(Reader reader)
按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)
注意:键值对应该有固定的格式, 键 = 值
void store(OutputStream out, String comments)
以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。
void store(Writer writer, String comments)
以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。
*********************************************************************************
练习:记录一个程序运行的次数,当到达次数时,该程序不可以再次运行。
import java.io.*;
import java.util.*;
class MyPropertiesDemo
{
public static void main(String[] args) throws IOException
{
//记录次数
int time = 0;
//创建流关联配置文件
File file = new File("prop.ini");
if(!file.exists())
file.createNewFile();
FileInputStream fin = new FileInputStream(file);
FileOutputStream fos = null;
//创建集合
Properties prop = new Properties();
//加载配置键值对
prop.load(fin);
//查询已用次数
String count = prop.getProperty("time");
if(count==null)
{
count = "0";
}
time = Integer.parseInt(count);
time++;
if(time>=5)
{
System.out.println("Time has reached 5.Use limitied,please register!!");
return;
}
//回传配置信息并存储
count =new Integer(time).toString();
prop.setProperty("time",count);
fos = new FileOutputStream(file);
prop.store(fos,"");
fin.close();
System.out.println("This is the "+time+" time!");
System.out.println("Hello World!");
}
}
*********************************************************************************
File 类
该类的出现是对文件系统中的文件以及文件夹进行对象的封装。
以对象的思想来操作文件以及文件夹。
1、构造函数
File(String fileName);
File(String parent,String child);
File(File parent,String child);
2、特殊字段:可跨平台使用
static String pathSeparator
与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static char pathSeparatorChar
与系统有关的路径分隔符。
static String separator
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
static char separatorChar
与系统有关的默认名称分隔符。
3、常见方法:
1 创建:
boolean creatNewFile() throws IOException
如果文件已存在,则不创建
boolean mkdir(); 建文件夹
boolean mkdirs(); 建立多级目录
2 删除:
boolean delete(); 删除文件或文件夹
注意:对于文件夹只能删除不带内容的空文件夹,
对于非空文件夹,只能从里往外删除(递归删除)
对于不存在或已删除的文件 返回 false
void deleteOnExit(); 删除动火证交给系统完成。无论是否发生异常,系统在退出时
执行删除动作。
3 判断:
boolean canExexute();
boolean canWrite();
boolean canRead();
boolean exists(); 判断文件是否存在
boolean isFile(); 判断 File 对象封装的是否是文件
boolean isDirectiry();判断 File 对象封装的是否目录(文件夹)
boolean isHidden(); 判断文件或者文件夹是否隐藏。
注意:在获取硬盘文件或文件夹时,对于系统目录中的文件,java是无法访问的,
所以在遍历文件夹时,应该避开隐藏文件
4 获取
getName(); 获取文件或文件夹的名字
getPath();
getAbsolutePath(); 获取绝对路径
getParent(); 获取File对象封装文件或文件夹的父目录
注意:如果封装的是相对路径,则返回null
long length(); 获取文件大小
long lastModified(); 获取最后一次修改的时间
static File[] listRoots();获取系统中的有效盘符
String[] list(); 获取指定目录下当前的文件或文件夹名称
String[] list(FilenameFilter filter);
可以根据指定的过滤器,过滤文件及文件夹名称
File listFiles();返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件
File listFiles(FilenameFilter filter);
返回过滤后的文件对象
5 重命名:
renameTo(File);移动并重命名文件
删除文件夹,或者遍历一个目录,经常需要用到递归功能
使用 递归时需注意的问题:
1 一定要限定条件,否则内存溢出,或者只递不归
2 使用递归时,需要注意递归次数不要过多,否则可能导致内存溢出
******************************************************************************************************
练习:列出指定目录下的文件以及文件夹
import java.io.*;
import java.util.*;
class ShowFile
{
public static void main(String[] args) throws IOException
{
File file = new File("C:\\Users\\hyning\\Desktop\\java基础笔记\\day21\\");
show(file,0);
System.out.println("Hello World!");
}
public static void show(File file,int level) throws IOException
{
System.out.println("Dir: ... "+file.getName()+"..."+level);
//获取指定路径下的文件对象
File[] f = file.listFiles();
++level;
//遍历文件夹
for(int i=0; i<f.length; i++)
{
if(f[i].isDirectory())
{
show(f[i],level);
}
else if(f[i].isFile()&&(!f[i].isHidden()))
{
// System.out.println(f[i].getAbsolutePath() +" ... "+f[i].getName()+"..."+level);
System.out.println("File: ... "+f[i].getName()+"..."+level);
}
}
}
}
******************************************************************************************************
IO 包中的其他对象:
1 打印流
PrintStream:
是一个字节打印流,System.out 对应的类型就是PrintStream
它的构造函数可以接受三种数据类型的值
1 字符串路径
2 File 对象
3 OutputStream
PrintStream(File file, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding)
创建新的打印流。
PrintStream(String fileName, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintWriter
是一个字符打印流。构造函数可以接受四种类型的值。
1 字符串路径
2 File 对象
对于1 2类型的数据,还可以指定编码表,即字符集
3 OutputStream
4 Writer
对于3 4类型的数据,可以指定自动刷新
注意:该自动刷新值为true时,只有三个方法可以用:println,printf,format
如果要自动刷新,有可以指定编码,应该如何进行封装?
PrintWriter pw = new PrintWriter((new OutputStreamWriter(new FileOutputStream("1.txt")),"utf-8"),true);
加缓冲
PrintWriter pw = new PrintWriter(
BufferedWriter(new OutputStreamWriter(new FileOutputStream("1.txt")),"utf-8"),true));
2、管道流
PipedInputStream
PipedOutputStream
特点:
读取管道流和写入管道流可以进行连接。
连接方式:
1 通过两个流对象的构造函数
2 通过调用两个对象的connect()方法
注意:通常两个流在使用时,需要加入多线程技术,也就是让读写同时运行。
因为read()方法是阻塞式的,也就是说没有数据的情况下,该方法会等待。
3、 RandomAccessFile
该对象并不是流体系中的一员。该对象中封装了字节流,同时还封装了一个
缓冲区(字节数组),通过内部的指针来操作数组中的数据。
该对象的特点:
1 该对象只能操作文件,所以构造函数接受两种类型的参数。
a 字符串路径
b File 对象
2 该对象既可以对文件进行读取,也可以进行写入
在进行对象实例化时,必须要指定指定该对象的操作模式,r rw
RandomAccessFile(File file, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
RandomAccessFile(String name, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
该对象有直接操作基本数据类型的方法
int read()
从此文件中读取一个数据字节。
int read(byte[] b)
将最多 b.length 个数据字节从此文件读入 byte 数组。
int read(byte[] b, int off, int len)
将最多 len 个数据字节从此文件读入 byte 数组。
byte readByte()
从此文件读取一个有符号的八位值。
char readChar()
从此文件读取一个字符。
double readDouble()
从此文件读取一个 double。
float readFloat()
从此文件读取一个 float。
void readFully(byte[] b)
将 b.length 个字节从此文件读入 byte 数组,并从当前文件指针开始。
void readFully(byte[] b, int off, int len)
将正好 len 个字节从此文件读入 byte 数组,并从当前文件指针开始。
int readInt()
从此文件读取一个有符号的 32 位整数。
String readLine()
从此文件读取文本的下一行。
write(int b); 写入1个字节,高位丢弃
writeInt(int b);写入四个字节
该对象独特的 方法:
skipBytes():跳过指定的字节数
seek();指定指针的位置
getFilePointer();获取指针的位置
通过这些方法,就可以完成对一个文件数据的随机访问。
该对象可以读数据,可以写数据,如果写入位置已有数据,原数据会被覆盖,
即可以修改数据。
注意:该对象在实例化时,如果要操作的文件不存在则自动建立。
如果操作的文件存在,则不会建立,如果存在的文件有数据。
在没有指定指针位置的情况下,写入数据,数据会从头开始写。
对于数据有规则或者分段的数据,可以使用多线程,分段写入数据,提高速率。
4、序列流 SequenceInputStream
特点:可以将多个流合并成一个流,这样操作起来很方便
原理:其实就是将每一个读取流对象存储到一个集合,最后一个流对象结尾作为这个流的结尾
两个构造函数:
1 SequenceInputStream(InputStream in1,InputStream in2)
2 SequenceInputStream(Enumeration<? extends InputStream> e)
可以将枚举中的多个流合并成一个流
注意:因为 Enumeration 式 Vector 中特有的取出方式,而 Vector 被
ArrayList取代,所以要使用ArrayList集合效率更高一些。可通过如下方式获取
重写 Enumeration
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
//添加要合并的流
al.add(new FileInputStream("..."));
.....
Iterator<FileInputStream> it = al.iterator();
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()
{
public boolean hasMoreElements()
{
return it.hasNext();
}
public FileInputStream nextElement()
{
return it.next();
}
};
//创建数据目的
FileOutputStream fos = new FileOutputStream("1.txt");
SequenceInputStream sin = new SequenceInputStream(en);
byte[] by = new byte[1024*1024];
int len;
while((len=sin.read())!=-1)
{
fos.write(by,0,len);
}
fos.close();
sis.close();
文件切割:将文件读入输入流,再传递给输出流,输出流将流内容分段输出到不同的文档即可。
5、 ObjectInputStream
ObjectOutputStream 通常这两个对象成对使用
可以通过这两个流对象直接操作已有的对象并对对象进行本地持久化存储。
存储后的对象可进行网络传输。
两个对象特有的方法:
ObjectInputStream
Object readObject(); 该方法抛出异常: ClassNotFoundException
ObjectOutputStream
void writeObject(Object);被写入的对象必须实现一个接口 Serializable
否则抛出异常: NotSerializableException
Serializable: 该接口其实就是一个没有方法的标记接口。
用于给类指定一个uid,该uid 是通过类中的可序列化成员的数字签名运算出来
的一个long型值。
只要这些成员发生变化,该值就会重新计算。
该值用于判断被序列化的对象和类文件是否兼容。(文件篡改检查)
如果被序列化的对象需要被不同的类版本所兼容,可以在类中自定义UID
定义方式: static final long serialVersionUID = 32L;
注意:对于静态的成员变量,不会被序列化。
对应的非静态成员也不想被序列化时,可以使用 关键字 transient 修饰
6、操作基本数据类型的流对象
DataInputStream(InputStream);
操作基本数据类型的方法:
int readInt(); 一次读取四个字节,并将其转换成int值
boolean readBoolean(); 一次读取一个字节
short readShort();
long readLong();
。。。。。。。
String readUTF();按照utf-8修改班读取字符。注意,它只能读writeUTF()写入的字符数据
DataOutputStream(OutputStream);
操作基本数据类型的方法;
writeInt(int); 一次写入四个字节
write(int); 一次只写入一个字节
writeBoolean(boolean);
。。。。。。。。
writeUTF(String); 按照utf-8修改版将字符数据进行存储。只能通过readUTF读取。
通常只要操作基本数据类型的数据。就需要通过 DataStram 进行包装。
操作数组的流对象。
1,操作字节数组
ByteArrayInputStream
ByteArrayOutputStream
toByteArray();
toString();
writeTo(OutputStream);
2,操作字符数组。
CharArrayReader
CharArrayWriter
对于这些流,源是内存。目的也是内存。
而且这些流并未调用系统资源。使用的就是内存中的数组。
所以这些在使用的时候不需要 close。
操作数组的读取流在构造时,必须要明确一个数据源。所以要传入相对应的数组。
对于操作数组的写入流,在构造函数可以使用空参数。因为它内置了一个可变长度数组
作为缓冲区。
编码转换:
在 io 中涉及到编码转换的流是转换流和打印流。
但是打印流只有输出。
在转换流中是可以指定编码表的。
默认情况下,都是本机默认的码表。GBK. 这个编码表怎么来的?
System.getProperty("file.encoding");
常见码表:
ASCII:美国标准信息交换码。使用的是 1 个字节的 7 位来表示该表中的字符。
ISO8859-1:拉丁码表。使用 1 个字节来表示。
GB2312:简体中文码表。
GBK:简体中文码表,比 GB2312 融入更多的中文文件和符号。
unicode:国际标准码表。都用两个字节表示一个字符。
UTF-8:对 unicode 进行优化,每一个字节都加入了标识头。
编码转换:
字符串 -->字节数组 :编码。通过 getBytes(charset);
字节数组-->字符串 : 解码。通过 String 类的构造函数完成。String(byte[],charset);
如果编错了,没救!
如果编对了,解错了,有可能还有救!
String s = "你好";
//编码。
byte[] b = s.getBytes("GBK");
//解码。
String s1 = new String(b,"iso8859-1");
System.out.println(s1);//????
/*
对 s1 先进行一次解码码表的编码。获取原字节数据。
然后在对原字节数据进行指定编码表的解码。
*/
byte[] b1 = s1.getBytes("iso8859-1");
String s2 = new String(b1,"gbk");
System.out.println(s2);//你好。
这种情况在 tomcat 服务器会出现。
因为 tomcat 服务器默认是 iso8859-1 的编码表。
所以客户端通过浏览器向服务端通过 get 提交方式提交中文数据时,
服务端获取到会使用 ISO8859-1 进行中文数据的解码。会出现乱码。
这时就必须要对获取的数据进行 iso8859-1 编码。然后在按照页面指定的编码表进行解
码即可
而对于 post 提交,这种方法也通用。但是 post 有更好的解决方式。
request.setCharacterEncoding("utf-8");即可。
所以建立客户端提交使用 post 提交方式
------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------