Java-IO流

  • 输入流和输出流相对于内存设备而言.
  • 将外设中的数据读取到内存中:输入
  • 将内存的数写入到外设中:输出
  • 字节流的两个顶层父类:
    • InputStream
    • OutputStream.
  • 字符流的两个顶层父类:
    • Reader
    • Writer
  • 这些体系的子类都以父类名作为后缀。
  • 而且子类名的前缀就是该对象的功能。

字符流

  • 字符流的由来:
  • 其实就是:字节流读取文字字节数据后,不直接操作而是先查指定的编码表。获取对应的文字。
  • 在对这个文字进行操作。简单说:字节流+编码表

FileWriter

使用方法

  • 需求:将一些文字存储到硬盘一个文件中。流程在代码注释中。
  • 创建一个可以往文件中写入字符数据的字符输出流对象,如第14行代码
  • 既然是往一个文件中写入文字数据,那么在创建对象时,就必须明确该文件(用于存储数据的目的地)。
    • 如果文件不存在,则会自动创建;如果文件存在,则会被覆盖。
  • 为什么调用Writer对象中的write(string)方法,写入数据后,文件并没有显示写入的数据?
    • 其实数据只是写入到临时存储缓冲区中,还需要进行刷新 flush();
  • close();关闭流,关闭资源。在关闭前会先调用flush刷新缓冲中的数据到目的地。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class FileWriterDemo {

//换行,unix windows 不一样 ,所以使用这个
private static final String LINE_SEPARATOR = System.getProperty("line.separator");

public static void main(String[] args) throws IOException {

//创建一个可以往文件中写入字符数据的字符输出流对象。
/*
* 如果构造函数中加入true,可以实现对文件进行续写!
*/
FileWriter fw = new FileWriter("demo.txt",true);

//调用Writer对象中的write(string)方法,写入数据
fw.write("abcde"+LINE_SEPARATOR+"hahaha");
//fw.write("xixi");

//进行刷新,将数据直接写到目的地中。
fw.flush();

// 关闭流,关闭资源。在关闭前会先调用flush刷新缓冲中的数据到目的地。
fw.close();

//关闭流后,无法再进行写操作
// fw.write("haha");// java.io.IOException: Stream closed
}

}

处理异常

  • 最好使用try,catch。在外部初始化创建 fileWriter,new在try代码块内部
  • finally中判断是否为空,然后写关闭的语句,给它也进行try,catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class IOExceptionDemo {

public static void main(String[] args) {

FileWriter fw = null;
try {

fw = new FileWriter("k:\\demo.txt");

fw.write("abcde" + LINE_SEPARATOR + "hahaha");

} catch (IOException e) {
System.out.println(e.toString());
} finally {
if (fw != null)
try {
fw.close();
} catch (IOException e) {
// code....
throw new RuntimeException("关闭失败");
}
}

}

}

FileReader

  • 需求:读取一个文本文件。将读取到的字符打印到控制台
1
2
3
4
5
6
7
8
FileReader fr = new FileReader("demo.txt");

int ch = 0;

while((ch=fr.read())!=-1){
System.out.println((char)ch);
}
//如果读完已有值后,再读取文件,会返回-1
  • 第二种读取方式:
    • 以数组为单位的读取
1
2
3
4
5
6
7
8
假设demo.txt中有 abcde 五个字母

char[] buf = new char[3];
//将读取到的字符存储到数组中
int num = fr.read(buf);
System.out.println(num+":"+new String(buf)); //abc
int num1 = fr.read(buf);
System.out.println(num1+":"+new String(buf)); //dec
  • 为什么一共只有五个字母,第二次读取时却是 ”dec“ ?
    • 因为是存在缓冲区,第一次abc没有清除,第二次是de覆盖了ab
1
2
3
4
5
6
7
FileReader fr = new FileReader("demo.txt");

char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1){
System.out.println(new String(buf,0,len));//读取buf,从0读到len
}

练习:复制文件

  • 练习:将c盘的一个文本文件复制到d盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class CopyTextTest_2 {

private static final int BUFFER_SIZE = 1024;

public static void main(String[] args) {
//读取一个已有的文本文件,使用字符读取流和文件相关联。
FileReader fr = null;
//创建一个目的,用于存储读到数据。
FileWriter fw = null;
try {
fr = new FileReader("IO流_2.txt");
fw = new FileWriter("copytest_2.txt");

//创建一个临时容器,用于缓存读取到的字符。
char[] buf = new char[BUFFER_SIZE];//这就是缓冲区。

//定义一个变量记录读取到的字符数,(其实就是往数组里装的字符个数 )
int len = 0;

while((len=fr.read(buf))!=-1){
fw.write(buf, 0, len); //读取buf,从0开始,len结束
}

} catch (Exception e) {
throw new RuntimeException("读写失败");
}finally{
if(fw!=null)
try {
fw.close();
} catch (IOException e) {

e.printStackTrace();
}
if(fr!=null)
try {
fr.close();
} catch (IOException e) {

e.printStackTrace();
}
}
}

}

缓冲区

  • 这就好比去超市,没有缓冲区就是买一样东西一结帐,有缓冲区就是拿着筐或者推着车,买完再结帐,可以大幅度提高效率
  • 不过如果车过大会堵塞出口,那样的话就可以在车子里面放几个筐,结账用筐,也会提高效率

BufferWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws IOException {

FileWriter fw = new FileWriter("buf.txt");

//为了提高写入的效率。使用了字符流的缓冲区。
//创建了一个字符写入流的缓冲区对象,并和指定要被缓冲的流对象相关联
BufferedWriter bufw = new BufferedWriter(fw);

//使用缓冲区的写入方法将数据先写入到缓冲区中。
for(int x=1; x<=4; x++){
bufw.write("abcdef"+x);
bufw.newLine();
//使用缓冲区的刷新方法将数据刷目的地中。
bufw.flush();
}

//关闭缓冲区。其实关闭的就是被缓冲的流对象。
bufw.close();
}

BufferReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FileReader fr = new FileReader("buf.txt");		
BufferedReader bufr = new BufferedReader(fr);

FileWriter fw = new FileWriter("buf_copy.txt");
BufferedWriter bufw = new BufferedWriter(fw);

String line = null;

while((line=bufr.readLine())!=null){
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufw.close();
bufr.close();

自定义MyBufferReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* 自定义的读取缓冲区。其实就是模拟一个BufferedReader.
*
* 分析:
* 缓冲区中无非就是封装了一个数组,
* 并对外提供了更多的方法对数组进行访问。
* 其实这些方法最终操作的都是数组的角标。
*
* 缓冲的原理:
* 其实就是从源中获取一批数据装进缓冲区中。
* 在从缓冲区中不断的取出一个一个数据。
*
* 在此次取完后,在从源中继续取一批数据进缓冲区。
* 当源中的数据取光时,用-1作为结束标记。
*/
public class MyBufferedReader extends Reader {

private Reader r;

//定义一个数组作为缓冲区。
private char[] buf = new char[1024];

//定义一个指针用于操作这个数组中的元素。当操作到最后一个元素后,指针应该归零。
private int pos = 0;


//定义一个计数器用于记录缓冲区中的数据个数。 当该数据减到0,就从源中继续获取数据到缓冲区中。
private int count = 0;


MyBufferedReader(Reader r){
this.r = r;
}

/**
* 该方法从缓冲区中一次取一个字符。
* @return
* @throws IOException
*/
public int myRead() throws IOException{

if(count==0){
count = r.read(buf);
pos = 0;
}
if(count<0)
return -1;

char ch = buf[pos++];

count--;

return ch;
}

public String myReadLine() throws IOException{

StringBuilder sb = new StringBuilder();

int ch = 0;
while((ch = myRead())!=-1){

if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
//将从缓冲区中读到的字符,存储到缓存行数据的缓冲区中。
sb.append((char)ch);

}

if(sb.length()!=0)
return sb.toString();
return null;
}

public void myClose() throws IOException {

r.close();
}

装饰设计模式

  • 对一组对象的功能进行增强时,就可以使用该模式进行问题的解决。
  • 缓冲区的设计就是基于装饰设计模式实现的
  • 然而装饰和继承都能实现一样的特点:进行功能的扩展增强。哪个好那?
装饰和继承对比
  • 假设有一个继承体系
1
2
3
Writer
|--TextWriter:用于操作文本
|--MediaWriter:用于操作媒体。
  • 想要对操作的动作进行效率的提高,需要加入缓冲技术。
  • 按照面向对象,可以通过继承对具体的进行功能的扩展。
1
2
3
4
5
6
7
8
Writer
|--TextWriter:用于操作文本
|--BufferTextWriter:加入了缓冲技术的操作文本的对象。
|--MediaWriter:用于操作媒体。
|--BufferMediaWriter:
但是这样做好像并不理想,如果这个体系进行功能扩展,又多了流对象。
那么这个流要提高效率,是不是也要产生子类呢?
是。这时就会发现只为提高功能,进行的继承,导致继承体系越来越臃肿。不够灵活。
  • 既然加入的都是同一种技术–缓冲。前一种是让缓冲和具体的对象相结合。
  • 可不可以将缓冲进行单独的封装,哪个对象需要缓冲就将哪个对象和缓冲关联。
1
2
3
4
5
6
7
8
9
10
Writer
|--TextWriter:用于操作文本
|--MediaWriter:用于操作媒体。
|--BufferWriter:用于提高效率。

class BufferWriter extends Writer{
BufferWriter(Writer w)
{
}
}
  • 装饰比继承灵活
  • 特点:装饰类和被装饰类都必须所属同一个接口或者父类。

LineNumberReader

  • 可以增添行号
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws IOException {

FileReader fr = new FileReader("IO流_2.txt");
LineNumberReader lnr = new LineNumberReader(fr);

String line = null;
lnr.setLineNumber(100);
while((line=lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+line);
}

lnr.close();
}

字节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void demo_read() throws IOException {

//1,创建一个读取流对象。和指定文件关联。
FileInputStream fis = new FileInputStream("bytedemo.txt");

//建议使用这种读取数据的方式
byte[] buf = new byte[1024];
int len = 0;

while((len=fis.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
fis.close();
}

public static void demo_write() throws IOException {

//1,创建字节输出流对象。用于操作文件.
FileOutputStream fos = new FileOutputStream("bytedemo.txt");

//2,写数据。直接写入到了目的地中。
fos.write("abcdefg".getBytes());

fos.close();//关闭资源动作要完成。
}

练习

  • 复制一个Mp3文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class CopyMp3Test {

/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {

copy_2();

}

//fis.available():文件的字节数
//如果文件字节数特别大,那么速度会很慢
//不建议使用
public static void copy_3() throws IOException {
FileInputStream fis = new FileInputStream("c:\\0.mp3");
FileOutputStream fos = new FileOutputStream("c:\\3.mp3");

byte[] buf = new byte[fis.available()];
fis.read(buf);
fos.write(buf);
fos.close();
fis.close();
}

public static void copy_2() throws IOException {

FileInputStream fis = new FileInputStream("c:\\0.mp3");
BufferedInputStream bufis = new BufferedInputStream(fis);

FileOutputStream fos = new FileOutputStream("c:\\2.mp3");
BufferedOutputStream bufos = new BufferedOutputStream(fos);

int ch = 0;

while((ch=bufis.read())!=-1){
bufos.write(ch);
}

bufos.close();
bufis.close();
}

public static void copy_1() throws IOException {

FileInputStream fis = new FileInputStream("c:\\0.mp3");
FileOutputStream fos = new FileOutputStream("c:\\1.mp3");

byte[] buf = new byte[1024];

int len = 0;

while((len=fis.read(buf))!=-1){
fos.write(buf,0,len);
}

fos.close();
fis.close();
}

}

键盘录入

  • 运行下面代码后,在控制台输入一个字母,按回车运行,是不是发现多出来一个 10 ?
  • 是的,这个是“ \n ”,也就是刚才的回车。
1
2
3
4
5
6
7
8
9
10
public static void readKey() throws IOException {

InputStream in = System.in;

int ch = in.read();//阻塞式方法。
System.out.println(ch);
int ch1 = in.read();//阻塞式方法。
System.out.println(ch1);

}

练习

  • 获取用户键盘录入的数据,并将数据变成大写显示在控制台上
  • 如果用户输入的是over,结束键盘录入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void readKey2() throws IOException {

//1,创建容器。
StringBuilder sb = new StringBuilder();

//2,获取键盘读取流。
InputStream in = System.in;

//3,定义变量记录读取到的字节,并循环获取。
int ch = 0;

while((ch=in.read())!=-1){

//在存储之前需要判断是否是换行标记 ,因为换行标记不存储。
if(ch=='\r')
continue;
if(ch=='\n'){
String temp = sb.toString();
if("over".equals(temp))
break;
System.out.println(temp.toUpperCase());
sb.delete(0, sb.length());
}
else
//将读取到的字节存储到StringBuilder中。
sb.append((char)ch);
}
}

转换流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* 这是方便理解的做法
//字节流
InputStream in = System.in;
OutputStream out = System.out;

//将字节转成字符的桥梁。装换流。
InputStreamReader isr = new InputStreamReader(in);
OutputStreamWriter osw = new OutputStreamWriter(out);

//字符流。
BufferedReader bufr = new BufferedReader(isr);
BufferedWriter bufw = new BufferedWriter(osw);
*/

//更简洁
BufferReader bufr = new BufferReader(new InputStream(System.in));
BufferWriter bufr = new BufferWriter(new OutputStream(System.out));

String line = null;

while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}

  • 1、需求:将键盘录入的数据写入到一个文件中。
1
2
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(newFileOutputStream("a.txt")));
  • 2、需求:将一个文本文件内容显示在控制台上。
1
2
BufferedReader bufr = new BufferedReader(new InputStreamReader(newFileInputStream("a.txt")));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
  • 3、需求:将一个文件文件中的内容复制到的另一个文件中。
1
2
BufferedReader bufr = new BufferedReader(new InputStreamReader(newFileInputStream("a.txt")));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(newFileOutputStream("b.txt")));
  • 根据不同的需求,改变源的不同位置即可

编码解码

  • file 流中的read write都是默认编码,如果需要指定编码,则选择其父类
  • InputStreamReader 、OutputStreamWriter
  • OutputStreamWriter接收一个字节输出流对象,既然是操作文件,那么该对象应该是FileOutputStream
1
2
3
4
5
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("uu.txt"),"UTF-8");
//BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("uu.txt"),"UTF-8"));

osw.write("你好");
osw.close();
  • 什么时候使用转换流呢?
    • 1、源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁。提高对文本操作的便捷。
    • 2、一旦操作文本涉及到具体的指定编码表时,必须使用转换流 。

流的操作规律

  • 之所以要弄清楚这个规律,是因为流对象太多,开发时不知道用哪个对象合适。
  • 想要知道开发时用到哪些对象。只要通过四个明确即可。
  • 1、明确源和目的(汇)
1
2
源:InputStream  Reader
目的:OutputStream Writer
  • 2、明确数据是否是纯文本数据。
1
2
3
4
5
源:是纯文本:	Reader
否: InputStream

目的:是纯文本: Writer
否: OutputStream

到这里,就可以明确需求中具体要使用哪个体系。

  • 3、明确具体的设备。
1
2
3
4
5
源设备:	
硬盘: File
键盘: System.in
内存: 数组
网络: Socket流
1
2
3
4
5
目的设备:
硬盘: File
控制台:System.out
内存: 数组
网络: Socket流
  • 4、是否需要其他额外功能。
1
2
3
4
1、是否需要高效(缓冲区);
是,就加上buffer.

2、转换。

File对象

  • 可以将一个已存在的,或者不存在的文件或者目录封装成file对象。
1
2
3
4
5
6
File f1 = new File("c:\\a.txt");
File f2 = new File("c:\\","a.txt");
File f = new File("c:\\");
File f3 = new File(f,"a.txt");
File f4 = new File("c:"+File.separator+"abc"+File.separator+"a.txt");
System.out.println(f4);

方法

  • 1、获取
    • 获取文件名称
    • 1.2 获取文件路径
    • 1.3 获取文件大小
    • 1.4 获取文件修改时间
1
2
3
4
5
6
7
8
9
10
File file = new File("a.txt");
String name = file.getName();
String absPath = file.getAbsolutePath();//绝对路径(开头有盘符)
String path = file.getPath(); //相对路径(开头无盘符)(找不到父目录)
long len = file.length();
long time = file.lastModified(); //毫秒值
Date date = new Date(time);
//Date对象格式化
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);
String str_time = dateFormat.format(date);
  • 2、创建与删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//和输出流不一样,如果文件不存在,则创建,如果文件存在,则不创建。 
boolean b = file.createNewFile();
System.out.println("b="+b);

boolean b = file.delete();
System.out.println("b="+b);

File dir = new File("abc");
boolean b = dir.mkdir();//make directory
System.out.println("b="+b);
System.out.println(dir.delete()); //慎用,不走回收站

File dir = new File("abc\\q\\e\\c\\z\\r\\w\\y\\f\\e\\g\\s");
dir.mkdirs();//创建多级目录
System.out.println(dir.delete()); //运行后,仅删除 S 文件夹
  • 3、判断
1
2
3
4
5
6
File f = new File("aaa");		
boolean b = f.exists(); //是否存在
System.out.println("b="+b);
// 最好先判断是否存在。
System.out.println(f.isFile()); //是否是文件
System.out.println(f.isDirectory());//是否是目录
  • 4、重命名
1
2
3
4
5
6
//可以从C到D盘 重命名
//即为剪切C盘的文件,复制到D盘
File f1 = new File("c:\\9.mp3");
File f2 = new File("d:\\aa.mp3");
boolean b = f1.renameTo(f2);
System.out.println("b="+b);

根目录方法

1
2
3
4
5
6
7
8
File[] files  = File.listRoots();
for(File file : files){
System.out.println(file);
}
File file = new File("d:\\");
System.out.println("getFreeSpace:"+file.getFreeSpace()); //空闲容量
System.out.println("getTotalSpace:"+file.getTotalSpace()); //总容量
System.out.println("getUsableSpace:"+file.getUsableSpace());//可使用容量

获取目录内容

  • 获取当前目录下的文件以及文件夹的名称,包含隐藏文件
  • 调用list方法的File对象中封装的必须是目录,否则会发生NullPointerException
  • 如果访问的系统级目录也会发生空指针异常。
  • 如果目录存在但是没有内容,会返回一个数组,但是长度为0.
1
2
3
4
5
6
7
8
9
public static void listDemo() {
File file = new File("c:\\");
String[] names = file.list();
System.out.println(names.length);

for(String name : names){
System.out.println(name);
}
}

过滤器

  • 显示后缀.java 的文件,使用 File类的 list方法
1
2
3
4
5
6
7
8
public class FilterByJava implements FilenameFilter {

@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}

}
  • 显示未隐藏的文件,使用 listFiles 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FilterByHidden implements FileFilter {

@Override
public boolean accept(File pathname) {

return !pathname.isHidden();
}

}
public static void listDemo() {

File dir = new File("c:\\");

File[] files = dir.listFiles(new FilterByHidden());

for(File file : files){
System.out.println(file);
}
}
  • 如果想过滤后缀,但是每次改过滤器太复杂,可以重新设计过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SuffixFilter implements FilenameFilter {

private String suffix ;
public SuffixFilter(String suffix) {
super();
this.suffix = suffix;
}
@Override
public boolean accept(File dir, String name) {

return name.endsWith(suffix);
}

}
  • 这样ListDemo 类中就可以更改想要的后缀
1
2
String[] names = dir.list(new SuffixFilter(".txt"));
String[] names = dir.list(new SuffixFilter(".java"));

练习

深度遍历

  • 需求:对指定目录进行所有内容的列出(包含子目录中的内容),也可以理解为 深度遍历。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class FileTest {
public static void main(String[] args) {

File dir = new File("d:\\MyBlog");

listAll(dir,0);
}

public static void listAll(File dir, int level) {
System.out.println(getSpace(level)+dir.getAbsolutePath());
level++;
File[] file = dir.listFiles();
for (int x = 0; x < file.length; x++) {
if (file[x].isDirectory())
listAll(file[x], level);
else
System.out.println(getSpace(level)+file[x].getAbsoluteFile());
}
}

private static String getSpace(int level) {
StringBuilder sb = new StringBuilder();
sb.append("|-- ");
for (int i = 0; i < level; i++) {
sb.insert(0, "| ");
}
return sb.toString();
}
}

删除目录(含内容的)

  • 删除一个带内容的目录。
    • 原理:必须从最里面往外,需要进行深度遍历。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Remove {
public static void main(String[] args) {
File dir = new File("d:\\demodemo");
remove(dir);
}
public static void remove(File dir){
File[] files = dir.listFiles();
for (File file:files){
if (file.isDirectory())
remove(file);
else
System.out.println( file+":"+file.delete());
}
System.out.println( dir+":"+dir.delete());
}
}

Properties

概述

  • Properties 是Map 子类Hashtable 的子类
  • Properties集合特点:
    • 该集合中的键和值都是字符串类型。
    • 集合中的数据可以保存到流中,或者从流获取。
  • 通常该集合用于操作以键值对形式存在的配置文件。

演示方法

  • 倒数第二行注意,大多数时候用于调试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void propertiesDemo(){
//创建一个Properties集合。
Properties prop = new Properties();

//存储元素。
prop.setProperty("zhangsan","30");
prop.setProperty("lisi","31");
prop.setProperty("wangwu","36");
prop.setProperty("zhaoliu","20");

//修改元素。
prop.setProperty("wangwu","26");

//取出所有元素。
Set<String> names = prop.stringPropertyNames();

for(String name : names){
String value = prop.getProperty(name);
System.out.println(name+":"+value);
}

//多用于调试,在控制台显示所有信息
prop.list(System.out);
}
  • 想要将这些集合中的字符串键值信息持久化存储到文件中,则需要关联输出流。(使用store方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void methodDemo_3() throws IOException {
Properties prop = new Properties();

//存储元素。
prop.setProperty("zhangsan","30");
prop.setProperty("lisi","31");

//关联输出流。
FileOutputStream fos = new FileOutputStream("info.txt");

//将集合中数据存储到文件中,使用store方法。
prop.store(fos, "info");

fos.close();

}
  • 既然可以存在文件中,自然可以在文件中读取(使用load方法)
  • 注意:必须要保证该文件中的数据是键值对
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void methodDemo_4() throws IOException {	

Properties prop = new Properties();

//使用到读取流。
FileInputStream fis = new FileInputStream("info.txt");

//使用load方法。
prop.load(fis);

prop.list(System.out);

}

load方法原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void myLoad() throws IOException{

Properties prop = new Properties();

BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));

String line = null;

while((line=bufr.readLine())!=null){

if(line.startsWith("#"))
continue;

String[] arr = line.split("=");

prop.setProperty(arr[0], arr[1]);
}

prop.list(System.out);

bufr.close();

}

修改配置信息

  • 流程
    • 读取文件
    • 创建集合存储配置信息
    • 将流信息存储到集合中
    • 修改集合
    • 将集合信息写回文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void test(File file) throws IOException{
//读取这个文件。
if(!file.exists()){
file.createNewFile();
}
FileReader fr = new FileReader(file);

//创建集合存储配置信息。
Properties prop = new Properties();

//将流中信息存储到集合中。
prop.load(fr);

prop.setProperty("wangwu", "16");

//需要信息修改完后才能将集合与输出流相关联
FileWriter fw = new FileWriter(file);

prop.store(fw,"");

fw.close();
fr.close();

}

练习一

  • 定义功能:获取一个应用程序运行的次数,
  • 如果超过5次,给出使用次数已到请注册的提示,并不要在运行程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
思路:
1、 应该有计数器。
每次程序启动都需要计数一次,并且是在原有的次数上进行计数。
2、 计数器就是一个变量。 那么我们就可以,程序启动时候进行计数,计数器必须存在于内存并进行运算。
可是程序一结束,计数器消失了。那么再次启动该程序,计数器又重新被初始化了。
而我们需要多次启动同一个应用程序,使用的是同一个计数器。
这就需要计数器的生命周期变长,从内存存储到硬盘文件中。

3、 如何使用这个计数器呢?
首先,程序启动时,应该先读取这个用于记录计数器信息的配置文件。
获取上一次计数器次数。 并进行试用次数的判断。
其次,对该次数进行自增,并自增后的次数重新存储到配置文件中。


4、 文件中的信息该如何进行存储并体现。
直接存储次数值可以,但是不明确该数据的含义。 所以起名字就变得很重要。
这就有了名字和值的对应,所以可以使用键值对。
可是映射关系map集合搞定,又需要读取硬盘上的数据,所以map+io = Properties.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class PropertiesTest {

public static void main(String[] args) throws IOException {

getAppCount();

}

public static void getAppCount() throws IOException{

//将配置文件封装成File对象。
File confile = new File("count.properties");

if(!confile.exists()){
confile.createNewFile();
}

FileInputStream fis = new FileInputStream(confile);

Properties prop = new Properties();

prop.load(fis);


//从集合中通过键获取次数。
String value = prop.getProperty("time");
//定义计数器。记录获取到的次数。
int count =0;
if(value!=null){
count = Integer.parseInt(value);
if(count>=5){
throw new RuntimeException("使用次数已到,请充值vip,给钱!");
}
}
count++;

//将改变后的次数重新存储到集合中。
prop.setProperty("time", count+"");

FileOutputStream fos = new FileOutputStream(confile);

prop.store(fos, "");

fos.close();
fis.close();


}

}

练习二

  • 获取指定目录下,指定扩展名的文件(包含子目录中的),将这些文件的绝对路径写入到一个文本文件中。
  • 简单说,就是建立一个指定扩展名的文件的列表。
  • 思路:
    • 进行深度遍历
    • 创建过滤器,将符合要求的文件绝对路径写入集合
    • 对容器中内容遍历并将绝对路径写入文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//过滤器
public class Filter implements FilenameFilter {
private String suffix;

public Filter(String suffix) {
super();
this.suffix = suffix;
}

@Override
public boolean accept(File dir, String name) {
return name.endsWith(suffix);
}
}


public class test {
public static void main(String[] args) {
File dir = new File("D:\\Code\\Java");
//过滤".java"结尾的文件
Filter filter = new Filter(".java");
getFileList(dir,filter);
}

private static void getFileList(File dir, FilenameFilter filter) {
//判断目录是否存在,增强健壮性
if (!dir.exists())
throw new NullPointerException("该指定目录不存在");
//创建容器
List<File> list = new ArrayList();
getFile(dir,filter,list);
//destfile:要存储到的目的文件
File destfile = new File(dir,"javaList.txt");
writeToFile(list,destfile);
}

private static void getFile(File dir, FilenameFilter filter, List<File> list) {
File[] files = dir.listFiles();
//深度遍历
for (File file:files){
//如果是文件夹,递归
if (file.isDirectory())
getFile(file, filter, list);
else//过滤文件,将其添加到容器中
if (filter.accept(file, file.getName())) {
list.add(file);
}
}
}

private static void writeToFile(List<File> list, File destfile) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(destfile));
for (File file:list){
bw.write(file.getAbsolutePath());
bw.newLine();
bw.flush();
}
} catch (IOException e) {
throw new RuntimeException("写入失败");
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
throw new RuntimeException("关闭失败");
}
}
}
}

}

打印流

PrintStream

  • 1、提供了打印方法可以对多种数据类型值进行打印。并保持数据的表示形式。
  • 2、它不抛IOException
  • 构造函数,接收三种类型的值:
    • 1、字符串路径
    • 2、File对象
    • 3、字节输出流
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws IOException {

PrintStream out = new PrintStream("print.txt");
//只写最低8位,对比下面两个结果,文件中存储的值都为 b
out.write(98);
out.write(610);

//将97先变成字符保持原样将数据打印到目的地。
out.print(97);
out.close();
}

PrintWriter

  • 字符打印流
  • 构造函数参数:
    • 1、字符串路径
    • 2、File对象
    • 3、字节输出流
    • 4、字符输出流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws IOException {
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//在创建输出流的时候,最后加上true,则可以自动刷新
PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);
//PrintWriter out = new PrintWriter(System.out,true);//在控制台输出
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
out.println(line.toUpperCase());
//out.flush; 第四行中有了true的设置,就不用手动刷新了
}

out.close();
bufr.close();
}

序列流

  • 可以将文件合并为一个序列流,对其进行操作
  • 需求:将1.txt 2.txt 3.txt文件中的数据合并到一个文件中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class SequenceInputStreamDemo {

public static void main(String[] args) throws IOException {
//Vector含枚举,但是太慢了
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for(int x=1; x<=3; x++){
al.add(new FileInputStream(x+".txt"));
}

Enumeration<FileInputStream> en = Collections.enumeration(al);
/* 这里是Collection工具类的enumeration实现原理
//利用迭代器和新建的枚举
final Iterator<FileInputStream> it = al.iterator();
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>(){

@Override
public boolean hasMoreElements() {

return it.hasNext();
}

@Override
public FileInputStream nextElement() {

return it.next();
}

};*/

SequenceInputStream sis = new SequenceInputStream(en);

FileOutputStream fos = new FileOutputStream("1234.txt");

byte[] buf = new byte[1024];

int len = 0;

while((len=sis.read(buf))!=-1){
fos.write(buf,0,len);
}

fos.close();
sis.close();

}

}

文件切割与合并

切割

  • 需求:将一个.MP3 文件按照 1M 切割
  • 思路:
    • 用读取流关联源文件,定义缓冲区
    • 创建目的
    • 将被切割文件的信息保存到prop集合中
    • 将prop集合中的数据存储到文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class SplitFileDemo {

private static final int SIZE = 1024 * 1024;

public static void main(String[] args) throws Exception {

File file = new File("c:\\aa.mp3");

splitFile(file);
}

private static void splitFile(File file) throws IOException {

// 用读取流关联源文件。
FileInputStream fis = new FileInputStream(file);

// 定义一个1M的缓冲区。
byte[] buf = new byte[SIZE];

// 创建目的。
FileOutputStream fos = null;

int len = 0;
int count = 1;


/*
* 切割文件时,必须记录住被切割文件的名称,以及切割出来碎片文件的个数。 以方便于合并。
* 这个信息为了进行描述,使用键值对的方式。用到了properties对象
*
*/
Properties prop = new Properties();

File dir = new File("c:\\partfiles");
if (!dir.exists())
dir.mkdirs();

while ((len = fis.read(buf)) != -1) {

fos = new FileOutputStream(new File(dir, (count++) + ".part"));
fos.write(buf, 0, len);
fos.close();
}

//将被切割文件的信息保存到prop集合中。
prop.setProperty("partcount", count+"");
prop.setProperty("filename", file.getName());

fos = new FileOutputStream(new File(dir,count+".properties"));

//将prop集合中的数据存储到文件中。
prop.store(fos, "save file info");

fos.close();
fis.close();

}
}

合并

  • 被切割的文件是无法被运行的,需要进行合并(以上面切割代码为示例)
  • 思路:
    • 获取指定目录下的配置文件对象
    • 记录配置文件对象,获取该配置文件中的信息
    • 获取该目录下的所有碎片文件(.part)
    • 将碎片文件和流对象关联 并存储到集合中
    • 将多个流合并成一个序列流,输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class MergeFile {

public static void main(String[] args) throws IOException {

File dir = new File("c:\\partfiles");

mergeFile(dir);
}

public static void mergeFile(File dir) throws IOException {

/*
* 获取指定目录下的配置文件对象。
*/
File[] files = dir.listFiles(new SuffixFilter(".properties"));

if(files.length!=1)
throw new RuntimeException(dir+",该目录下没有properties扩展名的文件或者不唯一");
//记录配置文件对象。
File confile = files[0];

//获取该文件中的信息
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(confile);

prop.load(fis);

String filename = prop.getProperty("filename");
int count = Integer.parseInt(prop.getProperty("partcount"));

//获取该目录下的所有碎片文件。
File[] partFiles = dir.listFiles(new SuffixFilter(".part"));

if(partFiles.length!=(count-1)){
throw new RuntimeException(" 碎片文件不符合要求,个数不对!应该"+count+"个");
}

//将碎片文件和流对象关联 并存储到集合中。
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for(int x=0; x<partFiles.length; x++){

al.add(new FileInputStream(partFiles[x]));
}

//将多个流合并成一个序列流。
Enumeration<FileInputStream> en = Collections.enumeration(al);
SequenceInputStream sis = new SequenceInputStream(en);

FileOutputStream fos = new FileOutputStream(new File(dir,filename));

byte[] buf = new byte[1024];

int len = 0;
while((len=sis.read(buf))!=-1){
fos.write(buf,0,len);
}

fos.close();
sis.close();

}
}

操作对象流

  • 平常创建的对象都是存储在硬盘上,通过对象输出流可以将其存储在硬盘中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
* Serializable:用于给被序列化的类加入ID号。
* 用于判断类和对象是否是同一个版本。
*/
public class Person implements Serializable/*标记接口*/ {

/**
* transient:非静态数据不想被序列化可以使用这个关键字修饰。
* 如果不自己添加序列号,序列化后更改name为public 就会报错
*/
private static final long serialVersionUID = 9527l;
private String name;
private int age;
//构造函数
//get set函数
}

public class ObjectStreamDemo {

public static void main(String[] args) throws IOException, ClassNotFoundException {

writeObj();
readObj();
}

public static void readObj() throws IOException, ClassNotFoundException {

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.object"));
//对象的反序列化。
Person p = (Person)ois.readObject();

System.out.println(p.getName()+":"+p.getAge());

ois.close();

}

public static void writeObj() throws IOException, IOException {

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));
//对象序列化。 被序列化的对象必须实现Serializable接口。
oos.writeObject(new Person("小强",30));

oos.close();
}
}

RandomAccessFile

  • 特点:
    • 1、即可读取,又可以写入。
    • 2、内部维护了一个大型的byte数组,通过对数组的操作完成读取和写入。
    • 3、通过getFilePointer方法获取指针的位置,还可以通过seek方法设置指针的位置。
    • 4、该对象的内容应该封装了字节输入流和字节输出流。
    • 5、该对象只能操作文件。
  • 注意:
    • 通过seek方法操作指针,可以从这个数组中的任意位置上进行读和写,可以完成对数据的修改。
    • 但是要注意:数据必须有规律。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class RandomAccessFileDemo {

public static void main(String[] args) throws IOException {
// writeFile();
// readFile();
randomWrite();
}

public static void randomWrite() throws IOException{
RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "rw");

//往指定位置写入数据。
raf.seek(3*8);

raf.write("哈哈".getBytes());
raf.writeInt(108);

raf.close();
}


public static void readFile() throws IOException {

RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "r");

//通过seek设置指针的位置。
raf.seek(1*8);//随机的读取。只要指定指针的位置即可。

byte[] buf = new byte[4];
raf.read(buf);

String name = new String(buf);

int age = raf.readInt();

System.out.println("name="+name);
System.out.println("age="+age);

System.out.println("pos:"+raf.getFilePointer());

raf.close();


}

//使用RandomAccessFile对象写入一些人员信息,比如姓名和年龄。
public static void writeFile() throws IOException{
//如果文件不存在,则创建,如果文件存在,不创建
RandomAccessFile raf = new RandomAccessFile("ranacc.txt","rw");

raf.write("张三".getBytes());
raf.writeInt(97);
raf.write("小强".getBytes());
raf.writeInt(99);
raf.close();
}

}

管道流

  • 需要和多线程技术相结合的流对象 PipedOutputStream、PipedInputStream
  • 如果想指定哪里来的数据读取哪个数据的话,需要将两个流接上,则使用管道流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class PipedStream {

public static void main(String[] args) throws IOException {

PipedInputStream input = new PipedInputStream();
PipedOutputStream output = new PipedOutputStream();

input.connect(output);

new Thread(new Input(input)).start();
new Thread(new Output(output)).start();
}
}

class Input implements Runnable{

private PipedInputStream in;
Input(PipedInputStream in){
this.in = in;
}
public void run(){
try {
byte[] buf = new byte[1024];
int len = in.read(buf);

String s = new String(buf,0,len);

System.out.println("s="+s);
in.close();
} catch (Exception e) {
// TODO: handle exception
}
}
}

class Output implements Runnable{
private PipedOutputStream out;
Output(PipedOutputStream out){
this.out = out;
}
public void run(){

try {
Thread.sleep(5000);
out.write("hi,管道来了!".getBytes());
} catch (Exception e) {
// TODO: handle exception
}
}
}

操作基本数据类型的流

  • 用操作基本数据类型值的对象 DataInputStream 、DataOutputStream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DataSteamDemo {

public static void main(String[] args) throws IOException {
// writeData();
readData();
}
public static void readData() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
String str = dis.readUTF();
System.out.println(str);
}
public static void writeData() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("你好");
dos.close();
}
}

操作数组的流

  • 设备是内存的流对象。
  • ByteArrayInputStream ByteArrayOutputStream
  • CharArrayReader CharArrayWriter
  • StringReader StringWriter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ByteArrayStreamDemo {

public static void main(String[] args) {

ByteArrayInputStream bis = new ByteArrayInputStream("abcedf".getBytes());
ByteArrayOutputStream bos = new ByteArrayOutputStream();

int ch = 0;
while((ch=bis.read())!=-1){
bos.write(ch);
}
System.out.println(bos.toString());
}
}

编码表

常见编码表

  • ASCII:
    • 美国标准信息交换码,用一个字节7位表示
  • ISO8859-1:
    • 拉丁码表,用一个字节8位表示
  • GB2312:
    • 中文编码表
  • GBK:
    • 升级的中文编码表
  • Unicode:
    • 国际标准码,融合多种文字(均用两字节表示)
  • UTF-8:
    • 国标码(最多三个字节表示一个字符)

简单编码解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void encodeDemo(String str)
throws UnsupportedEncodingException {
//编码
byte[] buf = str.getBytes("UTF-8");

printBytes(buf);

//解码
String s1 = new String(buf,"UTF-8");

System.out.println("s1="+s1);
}

private static void printBytes(byte[] buf) {
for(byte b : buf){
System.out.print(b +" ");
}
}

问题1

  • 如果编码的时候是 ” GBK“, 解码的时候是 ”iso8859-1“ 怎么解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "谢谢";
encodeDemo(str);
}

public static void encodeDemo(String str) throws UnsupportedEncodingException {

byte[] buf = str.getBytes("gbk");
String s1 = new String(buf,"iso8859-1");
System.out.println("s1="+s1);
//用iso8859-1编码
byte[] buf2 = s1.getBytes("iso8859-1");
//用GBK解码
String s2 = new String(buf2,"GBK");
System.out.println("s2="+s2);
}
  • 如果编码用GBK,解码用UTF-8,会出现什么问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws UnsupportedEncodingException {
String str1 = "你好";
String str2 = "哈哈";
String str3 = "谢谢";
encodeDemo(str1);
encodeDemo(str2);
encodeDemo(str3);
}

public static void encodeDemo(String str) throws UnsupportedEncodingException {

byte[] buf = str.getBytes("gbk");
String s1 = new String(buf,"utf-8");
System.out.println("s1="+s1);
//用iso8859-1编码
byte[] buf2 = s1.getBytes("utf-8");
//用GBK解码
String s2 = new String(buf2,"GBK");
System.out.println("s2="+s2);
}
  • 运行一下上面的代码 看看结果是什么样的?
    • 只有谢谢成功转回原样,其他的都是类似这样的–>”锟斤拷锟�“
  • 我们把printBytes的注释 解掉,发现 ”-17 -65 -67“ ,重复出现,这是什么原因呢?
    • 因为”你好“的字节在经过 UTF-8 转义时出现了错误 ,没有能够成功识别,
    • 返回的不是“你好”的字节,而是UTF-8特有的 未知字符 的字节

联通问题

  • 切换到桌面,新建一个文本文档,在里面输入”联通“ 这两个字,保存后关闭,重新打开,发现什么问题?
  • 是否发现不是联通两个字,变成了未知字符那?

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "联通";
byte[] buf = str.getBytes("gbk");
for (byte b : buf){
System.out.print(b+" ");
//System.out.println(Integer.toBinaryString(b));
System.out.println(Integer.toBinaryString(b&255));
}
}
  • 我们试试用GBK解码”联通“,先看一下他的字节,没有发现什么,再转为二进制取后八位
1
2
3
4
11000001
10101010
11001101
10101000
  • 开头分别是110,10 这就导致它被记事本解码时,误以为是UTF-8,于是解码后出现未知字符

练习

  • 在java中,字符串“abcd”与字符串“ab你好”的长度是一样,都是四个字符。
  • 但对应的字节数不同,一个汉字占两个字节。
  • 定义一个方法,按照最大的字节数来取子串
    • 如:对于“ab你好”,如果取三个字节,那么子串就是ab与“你”字的半个,
    • 那么半个就要舍弃。如果去四个字节就是“ab你”,取五个字节还是“ab你”.
  • 思路:
    • 我们先从GBK编码的入手,字母是一个字节,汉字是两个字节
    • 汉字两个字节开头都是1,所以为负数,如“你”字,-60 -29
    • 以”ab你好“为例,97 98 -60 -29 -70 -61 一次取5字节,进行判断
    • 指针指向-70,为负数,指针前移,若指向负数,计数器每次加一,直到指针指向正数
    • 如果计数器值%2为0,可以全部输出
    • 如果%2为1,输出(截取字节数-1)个字节(取5字节就是这种情况)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) throws IOException {

String str = "ab你好cd谢谢";
int len = str.getBytes("GBK").length;
for(int x=0; x<len; x++){
System.out.println("截取"+(x+1)+"个字节结果是:"+cutStringByByte(str, x+1));
}
}
public static String cutStringByByte(String str,int len) throws IOException{

byte[] buf = str.getBytes("gbk");

int count = 0;
for(int x=len-1; x>=0; x--){
if(buf[x]<0)
count++;
else
break;
}

if(count%2==0)
return new String(buf,0,len,"gbk");
else
return new String(buf,0,len-1,"gbk");
}
  • UTF-8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static String cutStringByU8Byte(String str, int len) throws IOException {

byte[] buf = str.getBytes("utf-8");

int count = 0;
for(int x=len-1; x>=0; x--){
if(buf[x]<0)
count++;
else
break;
}

if(count%3==0)
return new String(buf,0,len,"utf-8");
else if(count%3==1)
return new String(buf,0,len-1,"utf-8");
else
return new String(buf,0,len-2,"utf-8");

}