继承关系

1
2
3
java.lang.Object
|----> java.io.Writer
|----> java.io.PrintWriter

public class PrintWriter extends Writer向文本输出流打印对象的格式化表示形式。此类实现在 PrintStream 中的所有 print 方法。它不包含用于写入原始字节的方法,对于这些字节,程序应该使用未编码的字节流进行写入。

PrintStream 类不同,如果启用了自动刷新,则只有在调用 printlnprintfformat 的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成。这些方法使用平台自有的行分隔符概念,而不是换行符。

此类中的方法不会抛出 I/O 异常,尽管其某些构造方法可能抛出异常。客户端可能会查询调用 checkError() 是否出现错误。

构造函数

方法 描述
PrintWriter(File file) 打印到文件中,不自动刷新
PrintWriter(File file, String csn) 打印到文件中,指定字符集,自动刷新
PrintWriter(OutputStream out) 打印到OutputStream中,不自动刷新
PrintWriter(OutputStream out, boolean autoFlush) 打印到输出流中,autoFlush为true就自动刷新
PrintWriter(String fileName) 打印到文件名为fileName的文件中
PrintWriter(String fileName, String csn) 打印到文件名为fileName的文件中,指定字符编码
PrintWriter(Writer out) 打印到字符输出流中
PrintWriter(Writer out, boolean autoFlush) 打印到字符输出流中,autoFlush为true时自动刷新

成员方法

这个方法有点多,分开写比较好。

可以自动刷新的方法

注意了,只有下表的方法可以自动刷新,其他方法是不会自动刷新的。
自动刷新的情况:

  • 调用println()方法
  • 调用printf()方法
  • 调用format()方法
方法 描述
PrintWriter format(Locale l, String format, Object... args) 使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
PrintWriter format(String format, Object... args) 使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
PrintWriter printf(Locale l, String format, Object... args) 使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
PrintWriter printf(String format, Object... args) 使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
void println() 通过写入行分隔符字符串终止当前行。
void println(boolean x) 打印 boolean 值,然后终止该行。
void println(char x) 打印字符,然后终止该行。
void println(char[] x) 打印字符数组,然后终止该行。
void println(double x) 打印双精度浮点数,然后终止该行。
void println(float x) 打印浮点数,然后终止该行。
void println(int x) 打印整数,然后终止该行。
void println(long x) 打印 long 整数,然后终止该行。
void println(Object x) 打印 Object,然后终止该行。
void println(String x) 打印 String,然后终止该行。

追加方法

方法 描述
PrintWriter append(char c) 将指定字符添加到此 writer。
PrintWriter append(CharSequence csq) 将指定的字符序列添加到此 writer。
PrintWriter append(CharSequence csq, int start, int end) 将指定字符序列的子序列添加到此 writer。
方法 描述
void print(boolean b) 打印 boolean 值。
void print(char c) 打印字符。
void print(char[] s) 打印字符数组。
void print(double d) 打印 double 精度浮点数。
void print(float f) 打印一个浮点数。
void print(int i) 打印整数。
void print(long l) 打印 long 整数。
void print(Object obj) 打印对象。
void print(String s) 打印字符串。

write(…)方法

方法 描述
void write(char[] buf) 写入字符数组。
void write(char[] buf, int off, int len) 写入字符数组的某一部分。
void write(int c) 写入单个字符。
void write(String s) 写入字符串。
void write(String s, int off, int len) 写入字符串的某一部分。

错误相关方法

方法 描述
boolean checkError() 如果流没有关闭,则刷新流且检查其错误状态。
protected void clearError() 清除此流的错误状态。
protected void setError() 指示已发生错误。

刷新方法

方法 描述
void close() 关闭该流并释放与之关联的所有系统资源。
void flush() 刷新该流的缓冲。

实例

不自动刷新的例子

来看一个不自动刷新的例子

1
2
3
4
5
public static void main(String[] args) throws IOException
{
BufferedWriter writer=new BufferedWriter(new FileWriter("xiaoming.txt"));
writer.write("小明");
}

这样会不会在xiaoming.txt文件中写入“小明”这个字符串呢?答案是不会,因为,现在写的这个小明保存在BufferedWriter的内部字符数组中,并没有写到本地,想写到本地的话需要调用flush()方法刷新内部字符数组,把放在内部字符数组中的“小明”这个字符串写到本地中去。BufferedWriter内部缓存放满了,或者放不下了,会自动把内部缓存刷到本地中去。或者调用close()方法的时候也会刷新一下内部缓存。
现在小明两个字符没有充满内部缓存,BufferedWriter不会自动刷新内部缓存。而我们没有调用flush()方法,也没有关闭BufferdWriter。所以文件不会写入数据,main()方法结束后数据丢失。

PrintWriter自动刷新实例

使用PrintWriter并使用自动刷新刷新:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws IOException
{
//自动刷新到文件xiaoming.txt中
PrintWriter writer=new PrintWriter(new FileWriter("xiaoming.txt"),true);
//使用下面三个方法会自动刷新
writer.println("小明");
writer.printf("%-10s#\n","小明");
writer.format("%-10d#", 12345);
}

运行结果,xiaoming.txt文件中的内容:

1
2
3
小明
小明
12345

PrintWriter不会自动刷新的方法

PrintWriter的其他不会自动刷新的方法:

  • write()方法
  • print()方法
  • append()方法
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IOException
{
PrintWriter writer=new PrintWriter(new FileWriter("xiaoming.txt"),true);
writer.println("小明");
writer.printf("%-10s#\n","小明");
writer.format("%-10d#\n", 12345);
writer.write("我是不会写到文件中去的");
writer.print("我也不会自动写到文件中去的");
writer.append("我也一样不会写到文件中去的");
}

运行后,xiaoming.txt中的内容如下:

1
2
3
小明
小明
12345

可以看到这三个方法是不会自动刷新的,需要显示调用flush()方法进行写入,或者在写文件最后调用close()写入文件。这里调用close()方法在最后关闭文件,close()方法会把缓存的字符刷新到文件中去。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws IOException
{
PrintWriter writer=new PrintWriter(new FileWriter("xiaoming.txt"),true);
writer.println("小明");
writer.printf("%-10s#\n","小明");
writer.format("%-10d#\n", 12345);
writer.write("我是不会写到文件中去的\n");
writer.print("我也不会自动写到文件中去的\n");
writer.append("我也一样不会写到文件中去的\n");
writer.close();
}

运行上面代码后,xiaoming.txt文件中的内容如下

1
2
3
4
5
6
7
小明
小明
12345
我是不会写到文件中去的
我也不会自动写到文件中去的
我也一样不会写到文件中去的

疑惑的地方write()方法和print(...)方法append()方法的区别?

write()方法和print(...)方法的区别

  • 共同点:两者都不刷新页面,只在原来的页面写数据.最终都是重写了抽象类Writer里面的write方法.
  • 不同点:print方法可以将各种类型的数据转换成字符串的形式输出。重载的write方法只能输出字符、字符数组、字符串等与字符相关的数据。

这些疑惑看下面的源码就知道了。

看源码

字段

1
2
3
4
5
6
7
protected Writer out;//字符输出流
private final boolean autoFlush;//自动刷新
private boolean trouble = false;
private Formatter formatter;
private PrintStream psOut = null;
private final String lineSeparator;//换行符

构造方法

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
public PrintWriter (Writer out) {
//调用另一个:PrintWriter(Writer out, boolean autoFlush)
this(out, false);
}
public PrintWriter(Writer out, boolean autoFlush) {
super(out);
//设置字符输出流
this.out = out;
//设置自动刷新标志
this.autoFlush = autoFlush;
//设置换行符
lineSeparator = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}
public PrintWriter(OutputStream out) {
//调用另一个构造方法:PrintWriter(OutputStream out, boolean autoFlush)
this(out, false);
}
public PrintWriter(OutputStream out, boolean autoFlush) {
//把字节流封装成字符流:BufferedWriter后,
//调用:PrintWriter(OutputStream out, boolean autoFlush)
this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush);

// save print stream for error propagation
if (out instanceof java.io.PrintStream) {
psOut = (PrintStream) out;
}
}
public PrintWriter(String fileName) throws FileNotFoundException {
//包装后调用:PrintWriter(OutputStream out, boolean autoFlush)
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),
false);
}
//搞不懂为什么这里要颠倒一下参数的顺序?
private PrintWriter(Charset charset, File file)
throws FileNotFoundException
{
//包装后调用:PrintWriter(OutputStream out, boolean autoFlush)
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)),
false);
}
public PrintWriter(String fileName, String csn)
throws FileNotFoundException, UnsupportedEncodingException
{
//调用:PrintWriter(Charset charset, File file)
this(toCharset(csn), new File(fileName));
}
public PrintWriter(File file) throws FileNotFoundException {
//调用:PrintWriter(OutputStream out, boolean autoFlush)
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))),
false);
}
public PrintWriter(File file, String csn)
throws FileNotFoundException, UnsupportedEncodingException
{
//调用:PrintWriter(Charset charset, File file)
this(toCharset(csn), file);
}

看源码就知道了:

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
public void print(boolean b) {
write(b ? "true" : "false");
}
public void print(char c) {
write(c);
}
public void print(int i) {
write(String.valueOf(i));
}
public void print(long l) {
write(String.valueOf(l));
}
public void print(float f) {
write(String.valueOf(f));
}
public void print(double d) {
write(String.valueOf(d));
}
public void print(char s[]) {
write(s);
}
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
public void print(Object obj) {
write(String.valueOf(obj));
}

write(…)方法源码

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
protected Writer out;
public void write(int c) {
try {
synchronized (lock) {
ensureOpen();
//调用低层Writer的write(int)方法
out.write(c);
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
public void write(char buf[], int off, int len) {
try {
synchronized (lock) {
ensureOpen();
//调用低层writer的写字符数组方法
out.write(buf, off, len);
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
public void write(char buf[]) {
//调用自己的write(char buf[], int off, int len)
write(buf, 0, buf.length);
}
public void write(String s, int off, int len) {
try {
synchronized (lock) {
ensureOpen();
//调用低层得写字符串方法
out.write(s, off, len);
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
public void write(String s) {
//调用自己的写字符串方法
write(s, 0, s.length());
}

可以看到PrintWriter的writer(…)方法最终调用低层流的writer(..)来实现的。

append(…)方法源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public PrintWriter append(CharSequence csq) {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
public PrintWriter append(CharSequence csq, int start, int end) {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}
public PrintWriter append(char c) {
write(c);
return this;
}

我们可以看到append方法最终也是调用PrintWriterwrite()方法来实现的,write()方法和append方法不同的地方在于返回值吧,append返回值为PrintWriter,而write()返回值为void
print()方法和append()方法相同的地方是它们都通过write()方法实现。

println(…)方法源码

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
private void newLine() {
try {
synchronized (lock) {
ensureOpen();
out.write(lineSeparator);
if (autoFlush)
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
public void println() {
newLine();
}
public void println(boolean x) {
synchronized (lock) {
print(x);
println();
}
}
public void println(char x) {
synchronized (lock) {
print(x);
println();
}
}
public void println(int x) {
synchronized (lock) {
print(x);
println();
}
}
public void println(long x) {
synchronized (lock) {
print(x);
println();
}
}
public void println(float x) {
synchronized (lock) {
print(x);
println();
}
}
public void println(double x) {
synchronized (lock) {
print(x);
println();
}
}
public void println(char x[]) {
synchronized (lock) {
print(x);
println();
}
}
public void println(String x) {
synchronized (lock) {
print(x);
println();
}
}
public void println(Object x) {
String s = String.valueOf(x);
synchronized (lock) {
print(s);
println();
}
}

我们看到println(…)最终调用print(…)和println()方法事项,print(…)方法前面已经说了。
而println()方法,调用newLine()这私有的方法来进行换行和刷新操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void newLine() {
try {
synchronized (lock) {
ensureOpen();
//写换行符
out.write(lineSeparator);
if (autoFlush)
out.flush();//刷新缓存
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}

因为println()方法提供和换行和自动刷新功能,所以其他println(…)调用了该方法也就能实现自动刷新的功能了。

format()方法源码

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
//格式化类:java.util.Formatter
private Formatter formatter;
......
public PrintWriter format(String format, Object ... args) {
try {
synchronized (lock) {
ensureOpen();
if ((formatter == null)
|| (formatter.locale() != Locale.getDefault()))
formatter = new Formatter(this);
formatter.format(Locale.getDefault(), format, args);
if (autoFlush)
out.flush();
}
} catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
} catch (IOException x) {
trouble = true;
}
return this;
}
public PrintWriter format(Locale l, String format, Object ... args) {
try {
synchronized (lock) {
ensureOpen();
if ((formatter == null) || (formatter.locale() != l))
formatter = new Formatter(this, l);
formatter.format(l, format, args);
if (autoFlush)
out.flush();
}
} catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
} catch (IOException x) {
trouble = true;
}
return this;
}

这里的format()方法其实就java.util.Formatter类的format()方法实现的。而且format()方法调用了out.flush()来实现自动刷新。

printf()方法源码

1
2
3
4
5
6
public PrintWriter printf(String format, Object ... args) {
return format(format, args);
}
public PrintWriter printf(Locale l, String format, Object ... args) {
return format(l, format, args);
}

可以看到这里的printf()方法最终是调用了format(``.``.)方法来实现的。因为format()方法中调用了out.flush()来自动刷新,所以printf()方法也就跟着实现了自动刷新功能。
这些自动刷新功能说的很玄乎,其实不过就是下面两句话:

1
2
3
4
......
if (autoFlush)
out.flush();//刷新缓存
......

其他方法如write()方法,print()方法,append()方法没有上面这句话所以就没了自动刷新功能。

就这样吧

Java IO流 接口

Closeable接口

public interface Closeable
Closeable 是可以关闭的数据源或目标。调用 close 方法可释放对象保存的资源(如打开文件)。
Closeable接口

void close() throws IOException关闭此流,并释放与此流关联的所有系统资源。如果已经关闭该流,则调用此方法无效。
抛出:
    IOException - 如果发生 I/O 错误

Flushable接口

public interface Flushable

Flushable 是可刷新数据的目标地。调用 flush 方法将所有已缓冲输出写入底层流。

方法详细信息
void flush() throws IOException通过将所有已缓冲输出写入底层流来刷新此流。

抛出:
     IOException - 如果发生 I/O 错误

Appendable接口

public interface Appendable能够被添加 char 序列和值的对象。

如果某个类的实例打算接收取自 Formatter 的格式化输出,那么该类必须实现 Appendable 接口。

要添加的字符应该是有效的 Unicode 字符,正如 Unicode Character Representation 中描述的那样。注意,增补字符可能由多个 16 位 char 值组成。

Appendable 对于多线程访问而言没必要是安全的。线程安全由扩展和实现此接口的类负责。

由于此接口可能由具有不同的错误处理风格的现有类实现,所以无法保证错误不会传播给调用者。

方法 描述
Appendable append(char c) 向此 Appendable 添加指定字符。
Appendable append(CharSequence csq) 向此 Appendable 添加指定的字符序列。
Appendable append(CharSequence csq, int start, int end) 向此 Appendable 添加指定字符序列的子序列。

formatter方法

方法 描述
void close() 关闭此 formatter。
void flush() 刷新此 formatter。
Formatter format(Locale l, String format, Object... args) 使用指定的语言环境、格式字符串和参数,将一个格式化字符串写入此对象的目标文件中。
Formatter format(String format, Object... args) 使用指定格式字符串和参数将一个格式化字符串写入此对象的目标文件中。
IOException ioException() 返回由此 formatter 的 Appendable 方法上次抛出的 IOException 异常。
Locale locale() 返回构造此 formatter 时设置的语言环境。
Appendable out() 返回输出的目标文件。
String toString() 返回对输出的目标文件调用 toString() 的结果。

Formatter是广泛被用到的格式化方法,它能让一些东西变得更加有规范,例如:超市小票,信息单,用这个方法来格式化就显得很不错。

参考:https://blog.csdn.net/ouyang_peng/article/details/16355237

有时会想把数字,日期,字符串按照给定规则给格式化SUN JDK为我们提供了这个API,它是java.util.Formatter

Formatter提供了对布局对齐和排列的支持,以及对数值、字符串和日期/时间数据的常规格式和特定于语言环境的输出的支持。

格式化规则

要想按照自己的想法格式化必须事先编写一个规则。这个规则的语法如下。

常规类型字符类型数值类型格式说明符语法

1
%[argument_index$][flags][width][.precision]conversion

用来表示日期和时间类型的格式说明符语法

1
%[argument_index$][flags][width]conversion

与参数不对应的格式说明符语法

1
%[flags][width]conversion

API中有这样三种规则,很显然第一个规则的内容是最全面的。其它规则的内容和第一规则的内容有重复,那单说第一规则内容,其它规则依次类推。

注意:规则一中的precision前面要加英文句号“.

语法规则详细说明

可选项[argument_index$]

  • 可选的 argument_index 是一个十进制整数用于表明参数在参数列表中的位置
  • 第一个参数由 “1$”引用,第二个参数由”2$”引用,依此类推。
    argument_index很好理解,就是一参数占位符,用来表示要被格式化的参数。
1
2
3
4
5
String str1="hello";
String str2="world";
Formatter formatter=new Formatter();
formatter.format("%2$s,%1$s", str1,str2);
System.out.println(formatter);

运行结果:

1
world,hello

上面的%2$s,中的s表示这个参数字符串。详细往后看

必选项conversion

注意:conversion和前面的%是格式化规则中必须要有的,这两者是必选项。其他的都是可以选项。

conversion 是一个表明应该如何格式化参数的字符。用这个来确定被格式内容的类型,如果类型不匹配会报错。

conversion分类:

  • 常规-可应用于任何参数类型

  • 字符-可应用于表示Unicode字符的基本类型:char、Character、byte、Byte、short和Short。当Character.isValidCodePoint(int) 返回 true时,可将此转换应用于 int和Integer 类型

  • 数值

    • 整数-可应用于Java的整数类型:byte、Byte、short、Short、int、Integer、long、Long 和 BigInteger
    • 浮点数-可用于Java的浮点数类型:float、Float、double、Double 和 BigDecimal
  • 日期/时间-可应用于Java的、能够对日期或时间进行编码的类型:long、Long、Calendar 和 Date。

  • 百分比-产生字面值’%’ (‘\u0025’)

  • 行分隔符-产生特定于平台的行分隔符

常规格式符

常规格式符 描述
B b 格式化为布尔字符串,如果参数arg 为 null,则结果为 “false”。如果arg 是一个 boolean 值或Boolean,则结果为String.valueOf() 返回的字符串。否则结果为 “true”。
H h 格式化为哈希码,如果参数arg 为 null,则结果为 “null”。否则,结果为调用Integer.toHexString(arg.hashCode()) 得到的结果。
S s 格式化为字符串,如果参数arg 为 null,则结果为 “null”。如果arg 实现 Formattable,则调用arg.formatTo。否则,结果为调用 arg.toString() 得到的结果。

实例:

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
public static void test_conversion_s()
{
Formatter formatter=new Formatter();
String name = "World";
formatter.format("Hello %s !", name);
System.out.println(formatter.toString());
}
public static void test_conversion_S()
{
Formatter formatter=new Formatter();
String name = "World";
formatter.format("Hello %S !", name);
System.out.println(formatter.toString());
}
public static void test_conversion_b()
{
Formatter formatter=new Formatter();
String name = "World";
formatter.format("Hello %b !", name);
System.out.println(formatter.toString());
}
public static void test_conversion_B()
{
Formatter formatter=new Formatter();
String name = "World";
formatter.format("Hello %B !", name);
System.out.println(formatter.toString());
}
public static void test_conversion_h()
{
Formatter formatter=new Formatter();
String name = "World";
formatter.format("Hello %h !", name);
System.out.println(formatter.toString());
}
public static void test_conversion_H()
{
Formatter formatter=new Formatter();
String name = "World";
formatter.format("Hello %H !", name);
System.out.println(formatter.toString());
}

运行结果:

1
2
3
4
5
6
Hello World !
Hello WORLD !
Hello true !
Hello TRUE !
Hello 4fe2b72 !
Hello 4FE2B72 !

从结果中我们可以看出:大写%S就会格式化为大写的字符串,小写的%s就会格式化成小写的字符串。大写的%B就会格式化为大写的布尔字符串,小写的%b就格式化为小写的。%H格式化为大写的哈希码,%h格式化为小写的哈希码

Unicode字符

字符格式符 描述
C c 结果是一个 Unicode字符

实例:中文对应的Unicode字符(Utf-16be)为\u4E2D\u6587

1
2
3
4
5
6
7
8
9
public static void test_conversion_c(Formatter formatter)
{
char[] chs={'\u4E2D','\u6587'};
for (char c : chs)
{
formatter.format("%c", c);
}
System.out.println(formatter);
}

运行结果为:

1
中文

这里提一下,中文转为Unicode字符这个我还没去研究,暂时想到下面这种方式实现:

1
2
3
4
5
6
7
8
9
10
11
String str = "中文";
try
{
String encodeStr = URLEncoder.encode(str, "utf-16be");
encodeStr = encodeStr.replaceAll("%(.{2})%(.{2})", "\\\\u$1$2");
System.out.println(encodeStr);
} catch (UnsupportedEncodingException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}

运行结果:

1
\u4E2D\u6587

整数格式符

整数格式符 描述
d 结果被格式化为十进制整数
o 结果被格式化为八进制整数
X x 结果被格式化为十六进制整数

实例:

1
2
3
4
5
6
7
Formatter formatter=new Formatter();
int value=233;
formatter.format("十进制:%d\n", value);
formatter.format("八进制:%o\n", value);
formatter.format("小写十六进制:%x\n", value);
formatter.format("大写十六进制:%X\n", value);
System.out.println(formatter);

运行结果:

1
2
3
4
5
十进制:233
八进制:351
小写十六进制:e9
大写十六进制:E9

浮点数格式符号

浮点数格式符 描述
f 结果被格式化为十进制数
E e 结果被格式化为用计算机科学记数法表示的十进制数
G g 根据精度和舍入运算后的值,使用计算机科学记数形式或十进制格式对结果进行格式化。
A a 结果被格式化为带有效位数和指数的十六进制浮点数

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
Formatter formatter=new Formatter();
double value=1230000.45678;
formatter.format("十进制浮点数:%f\n", value);
formatter.format("科学计数法十进制浮点数:%e\n", value);
formatter.format("科学计数法十进制浮点数:%E\n", value);
formatter.format("根据精度和舍入运算后的值:%g\n", value);
formatter.format("根据进度和舍入运算后的值:%G\n", value);
value=12345.456789;
formatter.format("根据精度和舍入运算后的值:%g\n", value);
formatter.format("根据进度和舍入运算后的值:%G\n", value);
formatter.format("带有效位数和指数的十六进制浮点数:%a\n", value);
formatter.format("带有效位数和指数的十六进制浮点数:%A\n", value);
System.out.println(formatter);

运行结果:

1
2
3
4
5
6
7
8
9
十进制浮点数:1230000.456780
科学计数法十进制浮点数:1.230000e+06
科学计数法十进制浮点数:1.230000E+06
根据精度和舍入运算后的值:1.23000e+06
根据进度和舍入运算后的值:1.23000E+06
根据精度和舍入运算后的值:12345.5
根据进度和舍入运算后的值:12345.5
带有效位数和指数的十六进制浮点数:0x1.81cba780fdc16p13
带有效位数和指数的十六进制浮点数:0X1.81CBA780FDC16P13

日期/时间转换前缀

日期格式符 描述
T t 日期和时间转换字符的前缀
注意这里的t/T只是一个缀,它必须加上后缀才起作用。

日期/时间转换后缀

后缀如下

以下日期和时间转换的后缀字符是为 ‘t’ 和 ‘T’ 转换定义的。这些类型相似于但不完全等同于那些由 GNU date 和 POSIX strftime(3c) 定义的类型。提供其他转换类型是为了访问特定于 Java 的功能(如将 ‘L’ 用作秒中的毫秒)。

格式化时间后缀

以下转换字符用来格式化时间

时间后缀 描述
H 24 小时制的小时,被格式化为必要时带前导零的两位数,即 00 - 23。
I 12 小时制的小时,被格式化为必要时带前导零的两位数,即 01 - 12。
k 24 小时制的小时,即 0 - 23。
l 12 小时制的小时,即 1 - 12。
M‘` 小时中的分钟,被格式化为必要时带前导零的两位数`,即 00 - 59。
S 分钟中的秒,被格式化为必要时带前导零的两位数,即 00 - 60 (”60” 是支持闰秒所需的一个特殊值)。
L 秒中的毫秒,被格式化为必要时带前导零的三位数,即 000 - 999。
N 秒中的毫微秒,被格式化为必要时带前导零的九位数,即 000000000 - 999999999。
p 特定于语言环境的 上午或下午 标记以小写形式表示,例如 “am” 或 “pm”。使用转换前缀 ‘T’ 可以强行将此输出转换为大写形式。
z 相对于 GMT 的 RFC 822 格式的数字时区偏移量,例如 -0800。
Z 表示时区缩写形式的字符串。Formatter 的语言环境将取代参数的语言环境(如果有)。
s 协调世界时 (UTC) 1970 年 1 月 1 日 00:00:00 至现在所经过的秒数,即 Long.MIN_VALUE/1000 与 Long.MAX_VALUE/1000 之间的差值。
Q 协调世界时 (UTC) 1970 年 1 月 1 日 00:00:00 至现在所经过的毫秒数,即 Long.MIN_VALUE 与 Long.MAX_VALUE 之间的差值。

格式化日期后缀

以下转换字符用来格式化日期

大写表示全称,小写表示简称

日期后缀 描述
B 特定于语言环境的月份全称,例如 “January” 和 “February”。
b 特定于语言环境的月份简称,例如 “Jan” 和 “Feb”。
h 与 ‘b‘ 相同。也就是月份的简称
A 特定于语言环境的星期几全称,例如 “Sunday” 和 “Monday”
a 特定于语言环境的星期几简称,例如 “Sun” 和 “Mon”
C 除以 100 的四位数表示的年份,被格式化为必要时带前导零的两位数,即 00 - 99
Y 年份,被格式化为必要时带前导零的四位数(至少),例如,0092 等于格里高利历的 92 CE。
y 年份的最后两位数,被格式化为必要时带前导零的两位数,即 00 - 99。
j 一年中的天数,被格式化为必要时带前导零的三位数,例如,对于格里高利历是 001 - 366。
m 月份,被格式化为必要时带前导零的两位数,即 01 - 13。
d 一个月中的天数,被格式化为必要时带前导零两位数,即 01 - 31
e 一个月中的天数,被格式化为两位数,即 1 - 31。

常见的日期/时间组合简写

以下转换字符用于格式化常见的日期/时间组合

日期/时间组合 描述
R 24 小时制的时间,被格式化为 “%tH:%tM
T 24 小时制的时间,被格式化为 “%tH:%tM:%tS“。
r 12 小时制的时间,被格式化为 “%tI:%tM:%tS %Tp“。上午或下午标记 (‘%Tp’) 的位置可能与语言环境有关。
D 日期,被格式化为 “%tm/%td/%ty“。
F ISO 8601 格式的完整日期,被格式化为 “%tY-%tm-%td“。
c 日期和时间,被格式化为 “%ta %tb %td %tT %tZ %tY“,例如 “Sun Jul 20 16:17:00 EDT 1969“。

任何未明确定义为转换的字符都是非法字符,并且都被保留,以供将来扩展使用。

实例:

1
2
3
4
5
6
7
8
9
10
Formatter formatter = new Formatter();
Date date = new Date();
formatter.format("年月日:%1$tY-%1$tm-%1$td\n", date);
formatter.format("时分秒:%1$tH:%1$tM:%1$tS\n", date);
formatter.format("月份 :%1$tB 星期:%1$tA\n", date);
formatter.format("年月日时分秒:%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS\n", date);
formatter.format("日期年份 :%1$tc\n", date);
formatter.format("年月日时分 简写:%1$tF %1$tR\n", date);
formatter.format("年月日时分秒简写:%1$tF %1$tT\n", date);
System.out.println(formatter);

运行结果:

1
2
3
4
5
6
7
年月日:2018-08-12
时分秒:19:02:20
月份 :八月 星期:星期日
年月日时分秒:2018-08-12 19:02:20
日期年份 :星期日 八月 12 19:02:20 CST 2018
年月日时分 简写:2018-08-12 19:02
年月日时分秒简写:2018-08-12 19:02:20

格式化百分号

百分号 描述
% 结果为字面值 ‘%
这是因为%在格式语法中是必选项,%有特殊含义所以在格式的时候,使用%%来表示%这个符号本身。

实例:

1
2
3
4
5
6
7
public static void testFormatPercent()
{
Formatter formatter = new Formatter();
String string="Hello World!";
formatter.format("%% %s %%", string);
System.out.println(formatter);
}

运行结果:

1
% Hello World! %

格式化行分隔符

行分隔符 描述
n 结果为特定于平台的行分隔符

实例:

1
2
3
4
Formatter formatter = new Formatter();
String string="Hello%nWorld!";
formatter.format(string);
System.out.println(formatter);

运行结果:

1
2
Hello
World!

可选项[width]

可选项width 是一个非负十进制整数,表明要向输出中写入的最少字符
width 就表示一最少字符数,被格式化参数字符个数如果小于width ,则补上字符让字符宽度等于width 。如果被格式化字符宽度大于width则不用补齐,width不起作用。所以width 的作用就是为少补齐
实例:

1
2
3
4
5
6
7
8
public static void testWidth()
{
int value = 123;
Formatter formatter = new Formatter();
formatter.format("%4d%6d%10d\n", value,value,value);
formatter.format("%1$4d%1$6d%1$10d", value);
System.out.println(formatter);
}

运行结果:

1
2
123   123       123
123 123 123

可选项[.precision]

可选 precision 是一个非负十进制整数,通常用来限制字符数。特定行为取决于转换类型。
precision 是一个截取器,用于截取被格式化参数。被格式化参数用precision 截取器截取后与width 相比,
实例:

1
2
3
4
5
double value=321.123456789;
Formatter formatter=new Formatter();
formatter.format("只精确到小数点后三位:%1$.3f\n", value);
formatter.format("总共占10位,只精确到小数点后三位:%10.3f\n", value);
System.out.println(formatter);

运行结果:

1
2
只精确到小数点后三位:321.123
总共占10位,只精确到小数点后三位: 321.123

要注意的是[width]选项掌管的是整个格式化字符串的宽度,[.precision]设置的是小数点后面能显示几位。小数点和小数点后面的宽度都在都包含在width宽度之内,例如上面的321.123占7个字符,而**%10.3f表示整个占10个字符,这十个字符里面小数点后面占3**位,所以会在321.123前面补上三个空格占满10个字符。如果设置为%6.3f则真正的宽度为7大于6width属性失效。

可选项[flags]

可选项flags 是修改输出格式的字符集。有效标志集取决于转换类型。

标记 含义 示例
- 结果将是左对齐的。 %-4d 宽度最小为四位并且左对齐
0 结果将用零来填充 %010d 所有宽度不满10位的数字填充前导0
# 如果是浮点数则结果加小数点,十六进制和八进制分别加0x0 %#x 最后输出结果变为16进制并且加0x
, 结果将包括特定于语言环境的组分隔符 %,d 每三位数字来个逗号,看起来清晰
+ 正数数字会额外显示一个+ %+d 结果如果是正数则显示+号
一个空格 对于正值,结果中将包括一个前导空格 % d 如果结果是正数则在前面加个空格
( 结果将是用圆括号括起来的负数 %(d 如果结果是负数,不写符号而是用()括起来

注意支持的类型:

下表总结了受支持的标志。y 表示该标志受指示参数类型支持。

|标志|常规|字符|整数|浮点|日期/时间|说明|
|-|-|-|-|-|
|’-‘|y|y|y|y|y|结果将是左对齐的。|
|’#‘|y1|-|y3|y|-|结果应该使用依赖于转换类型的替换形式|
|’+‘|-|-|y4|y|-|结果总是包括一个符号|
|’ ‘|-|-|y4|y|-|对于正值,结果中将包括一个前导空格|
|’0‘|-|-|y|y|-|结果将用零来填充|
|’,‘|-|-|y2|y5|-|结果将包括特定于语言环境的组分隔符|
|’(‘|-|-|y4|y5|-|结果将是用圆括号括起来的负数|

设置左右对齐,0补齐

标记 含义 示例
- 结果将是左对齐的。 %-4d 宽度最小为四位并且左对齐
0 结果将用零来填充 %010d 所有宽度不满10位的数字填充前导0

实例:

1
2
3
4
5
6
7
8
9
public static void testAlign()
{
double value=321.123456789;
Formatter formatter=new Formatter();
formatter.format("总共占10位,只精确到小数点后三位:%10.3f##\n", value);
formatter.format("左对齐,总共占10位,不足0填充,只精确到小数点后三位:%010.3f##\n", value);
formatter.format("左对齐,总共占10位,只精确到小数点后三位:%-10.3f##\n", value);
System.out.println(formatter);
}

运行结果:

1
2
3
总共占10位,只精确到小数点后三位:   321.123##
左对齐,总共占10位,不足0填充,只精确到小数点后三位:000321.123##
左对齐,总共占10位,只精确到小数点后三位:321.123

正负数符号表示

标记 含义 示例
+ 正数数字会额外显示一个+ %+d 结果如果是正数则显示+号
一个空格 对于正值,结果中将包括一个前导空格 % d 如果结果是正数则在前面加个空格
( 结果将是用圆括号括起来的负数 %(d 如果结果是负数,不写符号而是用()括起来

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
double value=321.123456789;

StringBuilder builder=new StringBuilder();
//格式化结果输出到StringBuilder中
Formatter formatter = new Formatter(builder);

formatter.format("显示正负号 ,左对齐,总共占10位,只精确到小数点后三位:%+-10.3f##\n", value);
formatter.format("用空格表示正数,左对齐,总共占10位,只精确到小数点后三位:% -10.3f##\n", value);
formatter.format("用空格表示正数,左对齐,总共占10位,只精确到小数点后三位:%(-10.3f##\n", value);
value=-value;
formatter.format("显示正负号 ,左对齐,总共占10位,只精确到小数点后三位:%+-10.3f##\n", value);
formatter.format("用空格表示正数,左对齐,总共占10位,只精确到小数点后三位:% -10.3f##\n", value);
formatter.format("用空格表示正数,左对齐,总共占10位,只精确到小数点后三位:%(-10.3f##\n", value);
formatter.close();
System.out.println(builder);

运行结果:

加进制标志加小数点

标记 含义 示例
# 如果是浮点数则结果加小数点,十六进制和八进制分别加0x0 %#x 最后输出结果变为16进制并且加0x

先来回顾一下java,二进制,八进制,十六进制整数的静态初始化:

1
2
3
int a=0123;//8进制数
int b=0xAA;//16进制数
int c=0b1010;//二进制数

也可以使用:

1
2
//解析16进制数为十进制数
int x=Integer.parseInt("FF", 16);

这种方式来把其他进制转成10进制。
实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int a = 0123;// 8进制数
int b = 0xAA;// 16进制数
int c = 0b1010;// 二进制数
StringBuilder builder=new StringBuilder();
//格式化结果输出到StringBuilder中
Formatter formatter = new Formatter(builder);
// 解析16进制数为十进制数
int x = Integer.parseInt("FF", 16);
formatter.format("十进制:%1$-4d 等于%2$-3d进制:%1$#o\n", a, 8);
formatter.format("十进制:%1$-4d 等于%2$-3d进制:%1$#x\n", a, 16);
formatter.format("十进制:%1$-4d 等于%2$-3d进制:%1$#o\n", b, 8);
formatter.format("十进制:%1$-4d 等于%2$-3d进制:%1$#x\n", b, 16);
formatter.format("十进制:%1$-4d 等于%2$-3d进制:%1$#o\n", c, 8);
formatter.format("十进制:%1$-4d 等于%2$-3d进制:%1$#x\n", c, 16);
formatter.format("十进制:%1$-4d 等于%2$-3d进制:%1$#o\n", x, 8);
formatter.format("十进制:%1$-4d 等于%2$-3d进制:%1$#x\n", x, 16);
// formatter.format("%#b\n",c);
formatter.close();
System.out.println(builder.toString());

运行结果:

1
2
3
4
5
6
7
8
十进制:83   等于8  进制:0123
十进制:83 等于16 进制:0x53
十进制:170 等于8 进制:0252
十进制:170 等于16 进制:0xaa
十进制:10 等于8 进制:012
十进制:10 等于16 进制:0xa
十进制:255 等于8 进制:0377
十进制:255 等于16 进制:0xff

中英文混合输出的情况下英文应该使用左对齐,这样排列起来才整齐,还有就是使用等宽字体。

组分隔符

标记 含义 示例
, 结果将包括特定于语言环境的组分隔符 %,d 每三位数字来个逗号,看起来清晰
1
2
3
4
5
6
7
8
9
StringBuilder builder=new StringBuilder();
//格式化结果输出到StringBuilder中
Formatter formatter = new Formatter(builder);
//formatter.close();
//System.out.println(builder);
int value=123456789;
formatter.format("%,d\n",value);
formatter.close();
System.out.println(builder);

运行结果:

1
123,456,789

Console类

Java中Console类的用法

参考1:https://blog.csdn.net/ChengXuYuanXingCheng/article/details/71430068、
参考2:https://www.jb51.net/article/118527.htm

javaConsole类的使用方法及实例

JDK 6中提供了java.io.Console类专用来访问基于字符的控制台设备。如果你的Java程序要与Windows下的cmd或者Linux下的Terminal交互,就可以用这个Java Console类代劳。由于Scanner类输入是可见的,故Scanner类不适用于从控制台读取密码。从Java se 6开始加入了Console类来解决此问题。

要注意的是,javaw所执行的应用程序,没有主控台(Console),也就取不到Console对象(例如在Eclipse中)。

读控制台方法

方法 描述
String readLine() 从控制台读取单行文本。
String readLine(String fmt, Object... args) 提供一个格式化提示,然后从控制台读取单行文本。
char[] readPassword() 从控制台读取密码,禁用回显。
char[] readPassword(String fmt, Object... args) 提供一个格式化提示,然后从控制台读取密码,禁用回显。

Console类也提供有reader()writer()方法,可以传回 ReaderWriter 对象,以便进行其它的IO处理,例如结合Scanner一同使用:

Scanner scanner = new Scanner(System.console().reader());

实例:读密码不回显

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package study.news;

import java.io.Console;
public class TestConsole
{
public static void main(String[] args)
{
Console console = System.console();
//读入用户名
String username = console.readLine("User name:");
//读入密码
char[] passwd = console.readPassword("Password:");
console.printf("user:" + username + "\n");
console.printf(String.valueOf(passwd));
}
}

因为Java.io.Console 只能用在标准输入、输出流未被重定向的原始控制台中使用,在 Eclipse 或者其他 IDE 的控制台是用不了的。
所以我们到cmd控制台中来编译运行这个实例:
编译:javac -d . TestConsole.java
运行:study.news.TestConsole
输入密码:123456,可以看到密码123456没有显示在控制台上。

运行结果:

readLine()和readPassword()方法,还可以像c语言中的printf,使用变参和格式符那样打印输出信息。

1
2
3
4
5
Console console = System.console();
String username = console.readLine("%s", "你是谁:");
char[] password = console.readPassword("%s!请输入口令:", username);
System.out.println("username:"+username);
System.out.println("password:"+new String(password));

运行结果:

1
2
3
4
你是谁:xiaoming
xiaoming!请输入口令:
username:xiaoming
password:qunide

获取控制台的输入输出流

方法 描述
Reader reader() 获取与此控制台关联的唯一 Reader 对象。
PrintWriter writer() 获取与此控制台关联的唯一 PrintWriter 对象。
void 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
29
30
31
32

public static void testConsoleReader()
{
Console console = System.console();
if (console != null)
{
System.out.print("输入以#结束");
Pattern endRegex = Pattern.compile("([^#]*)#");
Matcher isEnd;
// 获取控制台的字符输入流,这样可以读取到我们在控制台上打的字
Scanner scan = new Scanner(console.reader());
String str;
StringBuilder builder = new StringBuilder();
while (scan.hasNext())
{
str = scan.next();
isEnd = endRegex.matcher(str);
// matches()会匹配整个字符串匹配后匹配的下表
if (isEnd.matches())
{
// 使用group()进行捕获的前提一定要是在匹配的情况下
// 可以在matches(),find(),lonkingAt()返回true的时候调用
builder.append(isEnd.group(1));
break;
}
builder.append(str);
// System.out.println(str);
}
System.out.println("接收到控制台输入:");
System.out.println(builder.toString());
}
}

运行结果:

使用这种方式,可以一次读入多行字符。不过这样其实和new Scanner(System.in)没啥区别了。
也可以使用BufferedReader类包装console.reader()

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
public static void testConsoleReader2() throws IOException
{
Console console = System.console();
if (console != null)
{
System.out.print("输入以#结束");
Pattern endRegex = Pattern.compile("([^#]*)#");
Matcher isEnd;
// 获取控制台的字符输入流,这样可以读取到我们在控制台上打的字
// Scanner scan = new Scanner(console.reader());

BufferedReader reader=new BufferedReader(console.reader());
String line;
//设置默认大小为100,面输入字符过多使得StringBuilder频繁扩容,每次扩容的长度是当前长度(length*2+2)
StringBuilder builder = new StringBuilder(100);
while ((line=reader.readLine())!=null)
{
isEnd = endRegex.matcher(line);
// matches()会匹配整个字符串匹配后匹配的下表
if (isEnd.matches())
{
// 使用group()进行捕获的前提一定要是在匹配的情况下
// 可以在matches(),find(),lonkingAt()返回true的时候调用
builder.append(isEnd.group(1));
break;
}
builder.append(line);
}
System.out.println("接收到控制台输入:");
System.out.println(builder.toString());
reader.close();
}
}

获取对控制台的输出流

console.writer()可以获取对控制台的输出流(PrintWriter)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void testConsoleWriter()
{
Console console = null;
PrintWriter out = null;
try
{
console = System.console();
if (console != null)
{
// 获取控制台的PrintWriter实例,也就是对控制台的输出
out = console.writer();
out.println("这句话将输出到控制台中");
}
} catch (Exception ex)
{
ex.printStackTrace();
}
}

运行结果:

使用Console类自己的控制台输入和输出方法

上面的控制台输出,我们借助于System.out.prinln()方法实现的。既然讲Console类,那就使用Console类自己的方式来进行输入和输出。

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 testReaderWriter()
{
Console console =null;
console=System.console();
if(console!=null)
{
PrintWriter consoleOut=console.writer();
BufferedReader consoleIn=new BufferedReader(console.reader());
String line=null;
try
{
while((line=consoleIn.readLine())!=null)
{
if(line.equals("exit"))
break;
consoleOut.println("你输入了:"+line);
}
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

控制台格式化输出方法

方法 描述
Console format(String fmt, Object... args) 使用指定格式字符串和参数将格式化字符串写入此控制台的输出流中。
Console printf(String format, Object... args) 使用指定格式字符串和参数将格式化字符串写入此控制台输出流的便捷方法。

printf()源码:

1
2
3
4
public Console printf(String format, Object ... args) 
{
return format(format, args);
}

可以看到printf()方法还是原封不动的调用format方法来实现
format源码:

1
2
3
4
5
6
7
private Formatter formatter;
......
public Console format(String fmt, Object ...args)
{
formatter.format(fmt, args).flush();
return this;
}

可以看到这里的format方法是使用Formatter类的formatter方法来实现的。

File类简介

Filejava.io包下面的一个类,代表与平台无关的文件或者目录。Java中,无论文件还是目录,都可以看作File类的一个对象。File类能对文件或目录新建,删除,获取属性等操作,但是不能直接操作文件内容(文件内容需要用数据流访问)。也就是File类只用于表示文件(目录)的信息(名称、大小等),不能用于文件内容的访问。

JVM默认会将workspace作为相对路径,即user.dir系统变量所指路径, 即如果这样初始化file对象,File file = new File("."); 就是获取了user.dir路径。

File类方法

继承关系

1
2
java.lang.Object
|----> java.io.File

字段

方法 描述
static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static char pathSeparatorChar 与系统有关的路径分隔符。
static String separator 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
static char separatorChar 与系统有关的默认名称分隔符。

其中常用的是第三个File.separator,这个表示与系统相关的路径分隔符,因为在Windows下的路径分隔符为:\而在Linux下的路径分隔符为:/,所以当直接使用”\“或者”/“时作为路径分隔符时,在跨平台使用时会报No Such file or diretory异常。
所以需要跨平台使用时,应该使用File.separator作为路径分隔符。

例如window下平常使用路径为:

1
File file=new File(".\\test\\demo.txt");

应该改为:

1
File file = new File("."+ File.separator +"demo.txt");

这样编译好的代码可以在linux中正常运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package test.before;

import java.io.File;
import java.io.IOException;

public class TestFile
{
public static void main(String[] args) throws IOException
{
File file=new File("."+File.separator+"gbk.txt");
// File file=new File(".\\gbk.txt");
System.out.println(file.getAbsolutePath());
System.out.println(file.isFile());
}
}

首先在工程目录下创建以名为gbk.txt的文件
Window中的输出结果:

1
2
D:\dev\workspace\IO\.\gbk.txt
true

Linux中的输出结果:

1
2
/home/lan/桌面/java/test/./gbk.txt
true

可以看到虽然文件分隔符都不一样的使用了File.separator可以跨平台。如果改成File file=new File(".\\gbk.txt");将在Linux中运行出错。

1
2
/home/lan/桌面/java/test/.\gbk.txt
false

构造函数

方法 描述
File(String pathname); 根据字符串路径来创建File对象
File(String parent, String child) ; 根据父路径字符串,和文件或者目录名来创建File对象
File(File parent, String child); 根据父路径的File对象,和子文件(子目录)的名称来创建File对象

判断方法

方法 描述
public boolean exists() 判断文件或目录是否存在
public boolean isFile() 判断该File对象是不是一个文件
public boolean isDirectory() 判断该File对象是不是目录
public boolean isAbsolute() 判断该File对象是不是绝对路径
public boolean canRead() 判断程序是否可读该文件
public boolean canWrite() 判断程序是否可写该文件
public boolean isHidden() 判断该文件是不是隐藏文件

详细说明:

  • public boolean exists()测试此抽象路径名表示的文件或目录是否存在。
    返回:
        当且仅当此抽象路径名表示的文件或目录存在时,返回 true;否则返回 false
  • public boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。如果该文件不是一个目录,并且满足其他与系统有关的标准,那么该文件是标准 文件。由 Java 应用程序创建的所有非目录文件一定是标准文件。
    返回:
        当且仅当此抽象路径名表示的文件存在且 是一个标准文件时,返回 true;否则返回 false 。
  • public boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录
    返回:
        当且仅当此抽象路径名表示的文件存在且 是一个目录时,返回 true;否则返回 false

所以,这三个方法都会去判断文件是否存在,isFile()和isDirectory()会在文件存在的基础之上判断该文件是标准文件还是目录

  • public boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。绝对路径名的定义与系统有关。
    • 在 UNIX 系统上,如果路径名的前缀是 “/“,那么该路径名是绝对路径名。
    • 在 Microsoft Windows 系统上,如果路径名的前缀是后跟 “\“ 的盘符,或者是 “\\“,那么该路径名是绝对路径名。

返回:
    如果此抽象路径名是绝对路径名,则返回 true;否则返回 false

  • public boolean canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。
    返回:
        当且仅当此抽象路径名指定的文件存在且 可被应用程序读取时,返回 true;否则返回 false
  • public boolean isHidden()测试此抽象路径名指定的文件是否是一个隐藏文件。隐藏 的具体定义与系统有关
    • 在 UNIX 系统上,如果文件名以句点字符 (‘.’) 开头,则认为该文件被隐藏。
    • 在 Microsoft Windows 系统上,如果在文件系统中文件被标记为隐藏,则认为该文件被隐藏。

返回:
    当且仅当此抽象路径名表示的文件根据底层平台约定是隐藏文件时,返回 true

总结:我还是应该多看看API

创建文件或目录方法

方法 描述
boolean createNewFile() 当文件不存在的时候创建一个文件。如果指定的文件不存在则创建,则返回 true;如果指定的文件已经存在,则返回 false
boolean mkdir(); 创建一个该抽象路径对应的目录,如果新建的目录的上级目录不存在则mkdir()回报异常,创建文件夹
boolean mkdirs(); 创建多级目录,创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。注意,此操作失败时也可能已经成功地创建了一部分必需的父目录。

实例:

当文件不存在时创建该文件

1
2
3
4
5
File file=new File("file2.txt");
if(!file.exists())
{
file.createNewFile();
}

当目录不存在时创建该目录:

1
2
3
4
5
File file=new File("file2.txt");
if(!file.exists())
{
file. mkdir();
}

删除文件或目录

方法 描述
boolean delete(); 删除此抽象路径名表示的文件或目录。如果此路径名表示一个目录,则该目录必须为空才能删除。

获取文件信息方法

方法 描述
public long length() 返回文件的大小 以字节为单位;如果文件不存在,则返回 0L。对于表示特定于系统的实体比如设备或管道)的路径名,某些操作系统可能返回0L
public String getName() 返回由此File对象表示的文件或目录的名称。该名称是路径名名称序列中的最后一个名称。如果路径名名称序列为空,则返回空字符串。
public long lastModified() 返回文件最后一次被修改的时间,用与时间点(1970 年 1 月 1 日,00:00:00 GMT)之间的毫秒数表示;
public boolean renameTo(File dest) 重命名该文件为dest.如果文件名为dest的文件不存在

路径相关方法

方法 描述
public String getParent() 返回父路径名称字符串
public File getParentFile() 返回父路径的File对象
public String getPath(); 获取相对路径字符串
public String getAbsolutePath(); 获取绝对路径字符串。
public String getCanonicalPath(); 返回规范化路径字符串
public File getAbsoluteFile(); 返回此File对象的副本?等同于new File(this.getAbsolutePath())

返回目录列表

方法 描述
public String[] list() 获取该目录下的文件或目录名的字符串列表,如果此抽象路径名不表示一个目录,那么此方法将返回 null。否则返回一个字符串数组,每个数组元素对应目录中的每个文件名或目录名
public File[] listFiles() 获取该目录下面的所有文件或者目录的File对象列表,如果此抽象路径名不表示一个目录,那么此方法将返回 null。否则返回一个File对象数组,每个数组元素对应目录中的每个文件或目录的File对象

6.代码实例

(1)构造函数实例:

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

//构造函数File(String pathname)
File f1 = new File("D:\\a\\1.txt");
System.out.println(f1);
//File(String parent,String child)
File f2 = new File("D:\\a", "2.txt");
System.out.println(f2);
//File(File parent,String child)
//File.separactor:表示路径分割符,在Windows系统中是"\",在Linux系统中是"/"
File f3 = new File("D:"+File.separator+"a");
System.out.println(f3);
File f4 = new File(f3,"3.txt");
System.out.println(f4);
}

运行结果:

1
2
3
4
D:\a\1.txt
D:\a\2.txt
D:\a
D:\a\3.txt

(2)创建文件或目录实例:

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
public class FileDemo1
{
public static void main(String[] args) throws IOException
{
File file = new File("D:\\a\\file.txt");
File directory = new File("D:\\a");
File dir = new File("D:\\a\\test1\\test2");
if (!directory.exists()) {
//创建目录
System.out.println("创建目录-"+directory);
System.out.println(directory.mkdir());
}
else {
System.out.println("删除目录-"+dir);
System.out.println(directory.delete());//删除目录
//删除此抽象路径名表示的文件或目录。如果此路径名表示一个目录,则该目录必须为空才能删除。
}
if (!dir.exists()) {
//创建多级目录
System.out.println("创建多级目录-"+dir);
System.out.println(dir.mkdirs());
}
else {
System.out.println("删除多级目录-"+dir);
System.out.println(dir.delete());//
}
if (!file.exists()) {
//创建文件
System.out.println("创建文件-"+file);
System.out.println(file.createNewFile());
}
else {
System.out.println("删除文件-"+file);
System.out.println(file.delete());//删除文件
}
}
}

结果:

  1. 第一次运行的时候,尚未创建文件或目录。

    1
    2
    3
    4
    5
    6
    创建目录-D:\a
    true
    创建多级目录-D:\a\test1\test2
    true
    创建文件-D:\a\file.txt
    true

    分析:全部创建成功

  2. 第2次运行,文件和目录以及多级目录都创建成功了。

1
2
3
4
5
6
删除目录-D:\a\test1\test2
false
删除多级目录-D:\a\test1\test2
true
删除文件-D:\a\file.txt
true

分析:

文件file可以删除掉,而目录directory(D:\a)不是空目录,无法删除。目录dir(D:\a\test1\test2) test2下面没有目录和文件,可以删除掉目录test2,delete()函数只能删除掉最下面的目录test2,不会删除掉test2的父目录test1.这与mkdirs()函数不是互逆操作。

(3)判断文件函数:exists(),isFile(),isAbsolute(),isDirectory(),canRead(),canWrite(),isHidden()

代码验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FileDemo2
{
public static void main(String[] args)
{
File file=new File("D:\\a\\file.txt");
File noAbsolute=new File("我当然不是绝对路径啦");
File dir=new File("D:\\a");
System.out.println("file对象是否是文件:"+file.isFile());
System.out.println("dir对象是否是目录:"+dir.isDirectory());
System.out.println("file对象的路径是否是绝对路径: "+file.isAbsolute());
System.out.println("noAbsolute对象的路径是否是绝对路径: "+noAbsolute.isAbsolute());
System.out.println("file.txt是否存在:"+file.exists());
System.out.println("file.txt是否可读:"+file.canRead());
System.out.println("file.txt是否可写:"+file.canWrite());
System.out.println("file.txt是否是隐藏文件:"+file.isHidden());
}
}

运行结果:

1
2
3
4
5
6
7
8
file对象是否是文件:true
dir对象是否是目录:true
file对象的路径是否是绝对路径: true
noAbsolute对象的路径是否是绝对路径: false
file.txt是否存在:true
file.txt是否可读:true
file.txt是否可写:true
file.txt是否是隐藏文件:false

(4)获取,操作文件属性函数

lastModified(),length(),getName(),getParent(),getPath(),getAbsolutePath():

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 class FileAPIDemo1
{
public static void main(String[] args)
{
File file=new File("D:\\a\\file.txt");
System.out.println("file.txt最后一次修改的时间:"+timeStampToDate(file.lastModified()));
System.out.println("file.txt长度(字节):"+file.length()+"B");
System.out.println("file的文件名:"+file.getName());
System.out.println("file.txt的父路径字符串:"+file.getParent());
File fileParent=file.getParentFile();//获取父路径的File对象。
System.out.println("父路径File对象fileParent的目录名:"+fileParent.getName());
System.out.println("file对象的抽象路径字符串:"+file.getPath());
System.out.println("file对象的绝对路径字符串:"+file.getAbsolutePath());
File AbsolutePathFile=file.getAbsoluteFile();//获取绝对路径的File对象
System.out.println("AbsolutePathFile的名字:"+file.getName());
System.out.println("file和AbsolutePathFile引用的是相同的文件?:"+file.equals(AbsolutePathFile));
}
//把时间戳转换成格式化时间字符串
static String timeStampToDate(long timeStamp)
{
Date date =new Date(timeStamp);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(date);
}
}

运行结果:

1
2
3
4
5
6
7
8
9
file.txt最后一次修改的时间:2018-05-03 16:15:00
file.txt长度(字节):15B
file的文件名:file.txt
file.txt的父路径字符串:D:\a
父路径File对象fileParent的目录名:a
file对象的抽象路径字符串:D:\a\file.txt
file对象的绝对路径字符串:D:\a\file.txt
AbsolutePathFile的名字:file.txt
file和AbsolutePathFile引用的是相同的文件?:true

7.有些困惑的地方

(1)getAbsoluteFile()函数其实就是获取当前File对象的一个副本?
(2)规范化路径getCanonicalPath(),相对路径getPath(),绝对路径getAbsolutePath()三者的区别:
1)当输入为绝对路径时,3个方法返回的都是绝对路径。
2)当输入为相对路径时:
getPath() 返回的是File构造方法里的路径
getAbsolutePath() 返回的其实是user.dir+getPath()的内容
getCanonicalPath() 返回的是去掉多余点号(.)等的规范化路径

测试代码:

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 class FileDemo3
{
public static void main(String[] args) throws IOException
{
// 打印当前工程的父路径
// System.out.println(System.getProperty("user.dir"));
File file = new File("D:\\a\\file.txt");
System.out.println("相对路径 :" + file.getPath());
System.out.println("绝对路径 :" + file.getAbsolutePath());
System.out.println("规范化路径 :" + file.getCanonicalPath());
System.out.println("-----------------------------");
File file2 = new File(".\\file.txt");
System.out.println("user.dir:" + System.getProperty("user.dir"));
System.out.println("相对路径 :" + file2.getPath());
System.out.println("绝对路径 :" + file2.getAbsolutePath());
System.out.println("规范化路径 :" + file2.getCanonicalPath());

// 当输入为绝对路径时,3个方法返回的都是绝对路径。
// 当输入为相对路径时:
// getPath()返回的是File构造方法里的路径
// getAbsolutePath()返回的其实是user.dir+getPath()的内容
// getCanonicalPath()返回的就是标准的将符号完全解析的路径
}
}

(3) list()和listFile()的区别

看返回类型就知道了:

    list()函数获取的是当前目录下的所有文件和目录的名字列表(String数组)

    listFiles()获取的是当前目录下面的所有文件和目录的File对象列表(File数组)。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FileAPIDemo2
{
public static void main(String[] args)
{
//工程路径
File dir=new File(System.getProperty("user.dir"));
String[] list=dir.list();//返回当前目录下的所有文件和目录的名字
for (String string : list)
{
System.out.println(string);
}
System.out.println("--------------------");
File[] fileList=dir.listFiles();//获取当前下所有目录和文件的File对象
for (File file : fileList)
{
System.out.println(file.getName());
}

}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.classpath
.project
.settings
bin
demo
demo1
src
--------------------
.classpath
.project
.settings
bin
demo
demo1
src

(4)使用listFiles()函数,递归遍历所有的目录:

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
public class FileAPIDemo4
{
// 递归遍历所有的子目录和文件
static void forEachAllDir(File dir)
{
if (dir == null)
{
System.out.println(dir + " 不是File对象");
return;
}
if (!dir.isDirectory())
{
System.out.println(dir + " 不是目录");
}

File[] lists = dir.listFiles();// 获取当前目录下子文件和子目录的File对象
for (File file : lists)
{
if (file.isDirectory())// 如果是目录的话
{
System.out.println(file);//输出目录的绝对路径
// 递归调用
forEachAllDir(file);//继续显示子目录
}
else//如果是文件的话,就直接输出文件的绝对路径
{
System.out.println(file);
}
}
}
public static void main(String[] args)
{
File dir = new File(System.getProperty("user.dir"));
forEachAllDir(dir);
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
D:\dev\workspace\TestLearned\.classpath
D:\dev\workspace\TestLearned\.project
D:\dev\workspace\TestLearned\.settings
D:\dev\workspace\TestLearned\.settings\org.eclipse.jdt.core.prefs
D:\dev\workspace\TestLearned\bin
D:\dev\workspace\TestLearned\bin\lan
D:\dev\workspace\TestLearned\bin\lan\csdn
D:\dev\workspace\TestLearned\bin\lan\csdn\c
D:\dev\workspace\TestLearned\bin\lan\csdn\c\java
D:\dev\workspace\TestLearned\bin\lan\csdn\c\java\Myint.class
......
D:\dev\workspace\TestLearned\src\string
D:\dev\workspace\TestLearned\src\string\Test_IndexOf.java
D:\dev\workspace\TestLearned\src\string\Test_LastIndexOf.java
D:\dev\workspace\TestLearned\src\string\Test_Substring.java

分析:可以看到当前工程下的所有目录中的文件和目录都已经遍历了一遍,通过这样递归遍历整个目录可以用来拷贝整个目录

8.文件过滤器FilenameFilter接口

看下面的File类的方法:

[java] view plain copy

<code class="language-java">public String[] list(FilenameFilter filter) ;   //返回一个字符串数组,这些字符串是目录中满足指定过滤器的文件和目录名字符串。  
public File[] listFiles(FilenameFilter filter);  
                                             //返回File数组,数组中的元素是该目录下的所有满足指定过滤器filter的文件和目录的File对象</code>  

上述方法可以实现文件过滤,返回符合条件的文件,过滤掉不符合条件的文件(不返回)

一、FilenameFilter介绍

java.io.FilenameFilter是文件名过滤器,用来过滤不符合规格的文件名,并返回合格的文件;

一般地:

(1)String[] fs = f.list();

(2)File[] fs = f.listFiles();

这两个方法返回f下的所有文件或目录;

FilenameFilter用来返回符合要求的文件或目录,不符合的文件的目录不返回(过滤掉);

因此可以调用:

(1)String []fs = f.list(FilenameFilter filter);

(2)File[]fs = f.listFiles(FilenameFilter filter);

FilenameFilter中有一个方法:

boolean accept(File dir,String name); //dir表示文件的当前目录,name表示文件名;

实现该方法即可对文件进行过滤:

使用方式:

(1)使用匿名内部类

(2)实现FilenameFilter

代码实现:

(1)匿名内部类方式实现文件过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void printAllFile(File dir,final String ends)
{
String[] listStr=dir.list(new FilenameFilter()
{
@Override
// dir表示文件的当前目录,name表示当前文件的文件名;
public boolean accept(File dir, String name)
{
//该目录是文件,而且文件以.java结尾
return new File(dir, name).isFile() && name.endsWith(ends);
}
});
for (String string : listStr)
{
System.out.println(string);
}
}

(2)实现FilenameFilter方式实现文件过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//静态内部类可以直接new不用加上外部类的类名
//实现FilenameFilter
static class MyFilenameFilter implements FilenameFilter
{
String ends;
public MyFilenameFilter(){}
public MyFilenameFilter(String ends)
{
this.ends=ends;
}
@Override
public boolean accept(File dir, String name)
{
return new File(dir, name).isFile() && name.endsWith(ends);
}
}
public static void printAllFile2(File dir, String ends)
{
String[] listStr=dir.list(new MyFilenameFilter(ends));
for (String string : listStr)
{
System.out.println(string);
}
}

完整代码:

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
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import com.lan.filepath.FilePath;

public class FilenameFilterTest
{
public static void printAllFile(File dir, final String ends)
{
String[] listStr = dir.list(new FilenameFilter()
{
@Override
// dir表示文件的当前目录,name表示当前文件的文件名;
public boolean accept(File dir, String name)
{
// 该目录是文件,而且文件以.java结尾
return new File(dir, name).isFile() && name.endsWith(ends);
}
});
for (String string : listStr)
{
System.out.println(string);
}
}
// 静态内部类可以直接new不用加上外部类的类名
// 实现FilenameFilter
static class MyFilenameFilter implements FilenameFilter
{
String ends;
public MyFilenameFilter()
{
}
public MyFilenameFilter(String ends)
{
this.ends = ends;
}
@Override
public boolean accept(File dir, String name)
{
return new File(dir, name).isFile() && name.endsWith(ends);
}
}
public static void printAllFile2(File dir, String ends)
{
String[] listStr = dir.list(new MyFilenameFilter(ends));
for (String string : listStr)
{
System.out.println(string);
}
}

public static void main(String[] args) throws IOException
{
// 获得当前的包路径
String packagePath = FilePath
.getSrcPackagePath(FilenameFilterTest.class);
File dir = new File(packagePath);
System.out.println("当前目录下的所有java文件:");
printAllFile(dir, ".java");
System.out.println("-----------------------------------------------");
System.out.println("当前目录下的所有txt文件:");
printAllFile(dir, ".txt");
System.out.println("-----------------------------------------------");
System.out.println("当前目录下的所有txt文件:");
printAllFile2(dir, ".txt");
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
当前目录下的所有java文件:
FileAPIDemo1.java
FileAPIDemo2.java
FileAPIDemo4.java
FileDemo.java
FileDemo1.java
FileDemo2.java
FileDemo3.java
FilenameFilterTest.java
TestFile.java
TestGBK.java
-----------------------------------------------
当前目录下的所有txt文件:
Test.txt
-----------------------------------------------
当前目录下的所有txt文件:
Test.txt

可以看到上面两种方式都能实现根据后缀名来过滤目录下面的文件,匿名内部类的方式比较适合于临时使用的情况,实现接口的方式比较适合于多次重复使用的情况。

参考:

JAVA基础知识之IO-File类

FilenameFilter总结

Linux 常用环境变量及作用和环境变量文件的详细介绍及其加载执行顺序
原文:

前言:

  1. 环境变量是操作系统环境设置的变量,适用于整个系统的用户进程;
  2. 环境变量可以在命令中设置,但是用户注销的时候将会丢失这些设置值;
  3. 若要重复适用,则最好在.profile中定义;环境变量的使用与本地变量的使用方法相同,
  4. 但是在使用之前,必须用export命令导出。

一、环境变量文件介绍

Linux中环境变量包括系统级用户级

  • 系统级的环境变量是每个登录到系统的用户都要读取的系统变量,
  • 而用户级的环境变量则是该用户使用系统时加载的环境变量,
    所以管理环境变量的文件也分为系统级和用户级的。

二、Linux的变量种类

按变量的生存周期来划分,Linux变量可分为两类:

  • 永久的:需要修改配置文件,变量永久生效
  • 临时的:使用export命令声明即可,变量在关闭shell时失效

三、Linux 环境变量文件

linux变量文件有

  • /etc/environment
  • /etc/profile
  • ~/.profile
  • /etc/bash.bashrc
  • ~/.bashrc
  • ~/.bash_profile(一般是用户在自己目录下新建的
  • ~/.bash_logout.

系统级:

/etc/environment

  • 该文件是系统在登录时读取的第一个文件,该文件设置的是整个系统的环境变量,只要启动系统就会读取该文件,用于为所有进程设置环境变量。
  • 系统使用此文件时并不是执行此文件中的命令,而是根据而是根据KEY=VALUE模式的代码,对KEY赋值以VALUE,因此文件中如果要定义PATH环境变量,只需加入一行形如 PATH=$PATH:/xxx/bin的代码即可
    例如:
    1
    PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"

/etc/profile

  • 此文件是系统登录时执行的第二个文件。 为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行。并从/etc/profile.d目录的配置文件中搜集shell的设置。
  • /etc/profile可以用于设定针对全系统所有用户的环境变量,环境变量周期是永久性,例如如果需要让所有用户可以使用java,把Java环境变量写在该文件中。

/etc/bashrc

  • 是针对所有用户的bash初始化文件,在此中设定的环境变量将应用于所有用户的shell中,此文件会在用户每次打开shell时执行一次。
  • 即每次新开一个终端,都会执行/etc/bashrc

用户级:

这些文件处于(Home)家目录下~,用于定制该用户的专属环境变量

~/.profile

对应当前登录用户的profile文件,用于定制当前用户的个人工作环境(变量是永久性),每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件。这里是推荐放置个人设置的地方

~/.bashrc

该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该文件被读取。(~/.bashrc只针对当前用户,变量的生命周期是永久的),所以不推荐在这里设置常用的变量,因为每开一个shell,这个文件会读取一次,效率肯定有影响。

暂时整理到这里,我看作者写的有点乱。

永久修改,对所有用户有效

1
$vi /etc/profile

/etc/profile文件最后添加下面的内容:

1
2
3
4
#set Java environment
export JAVA_HOME=/usr/lib/java/jdk1.8.0_171
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$JAVA_HOME/bin:$PATH

修改文件后如果想马上生效,执行命令:source /etc/profile
最后重启:shutdown -r -t 0

永久修改,对单一用户有效

修改用户目录下的.bash_profile文件

1
2
cd ~
vi .bash_profile

在文件最后添加下面的内容

1
2
3
4
#set Java environment
export JAVA_HOME=/usr/lib/java/jdk1.8.0_171
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$JAVA_HOME/bin:$PATH

此时如果想要马上生效执命令:source .bash_profile
最后重启:shutdown -r -t 0

只对当前bash /shell 生效

直接在shell中输入:

1
2
3
export JAVA_HOME=/usr/lib/java/jdk1.8.0_171
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$JAVA_HOME/bin:$PATH

这个只对当前shell/bash有效重新打开一个shell就没有用了。

原文:https://blog.csdn.net/jillliang/article/details/8216308

实现把一个目录中的所有内容复制到一个目录中去

实现文件复制

因为一个目录下的文件可能是字符文件,也可能是二进制文件,所以使用字节流来进行复制,这样能复制所有类型的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 复制一个文件
* @param srcFile 源文件
* @param destFile 目的文件
* @throws IOException
*/
public static void copyFile(String srcFile, String destFile)
throws IOException
{
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(destFile);
// 2097152(Byte)=2048(KB)=2M
byte[] buffer = new byte[2097152];
int size = 0;
//每次读取一个字节数组
while ((size = in.read(buffer)) != -1)
{
//读到多少写入多少
out.write(buffer, 0, size);
}
in.close();
out.close();
}

上述方法可以复制一个文件,我们可以在这个方法的基础之上实现目录的复制。

复制目录的算法

  • 遍历该目录列表,如果列表项是文件就调用上面的copyFile()方法复制该文件
  • 如果是该列表项是一个目录的话,就递归调用自己,复制该子目录。

复制目录的实现代码

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
/**
* 使用递归复制目录,
* @param FromDir 源目录的路径名称
* @param ToDir 目的目录的路径名称
* @throws IOException
*/
public static void copyDir(String FromDir, String ToDir) throws IOException
{
// 创建目录的File对象
File srcDir = new File(FromDir);
// 判断源目录是不是一个目录
if (!srcDir.isDirectory())
{
//如果不是目录那就不复制
return;
}
//创建目的目录的File对象
File destDir = new File(ToDir);
// 如果目的目录不存在
if (!destDir.exists())
{
// 创建目的目录
destDir.mkdir();
}

// 获取源目录下的File对象列表,每一个对象代表一个目录或者文件
File[] srcDirList = srcDir.listFiles();
// 遍历源目录File对象列表
for (int i = 0; i < srcDirList.length; i++)
{
// 如果是目录的话
if (srcDirList[i].isDirectory())
{
// 递归调用复制该目录
copyDir(FromDir + File.separator + srcDirList[i].getName(),
ToDir + File.separator + srcDirList[i].getName());
}
// 如果是文件的话
if (srcDirList[i].isFile())
{
// 调用复制文件的方法
copyFile(FromDir + File.separator + srcDirList[i].getName(),
ToDir + File.separator + srcDirList[i].getName());
}

}
}

main方法中调用:

1
2
3
4
public static void main(String[] args) throws IOException
{
copyDir("FromDir", "ToDir");//复制当前工程下的FromDir目录中的内容到ToDir目录中。
}

当前eclipse中的目录结构如下:

运行后的目录结构:

可以看到整个目录复制成功了。

完整代码如下:

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
81
82
83
84
85
86
87
88
89
90
91
package copy.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyDir
{
public static void main(String[] args) throws IOException
{
copyDir("FromDir", "ToDir");
}
/**
* 使用递归复制目录,
*
* @param FromDir
* 源目录的路径名称
* @param ToDir
* 目的目录的路径名称
* @throws IOException
*/
public static void copyDir(String FromDir, String ToDir) throws IOException
{
// 创建目录的File对象
File srcDir = new File(FromDir);
// 判断源目录是不是一个目录
if (!srcDir.isDirectory())
{
// 如果不是目录那就不复制
return;
}
// 创建目的目录的File对象
File destDir = new File(ToDir);
// 如果目的目录不存在
if (!destDir.exists())
{
// 创建目的目录
destDir.mkdir();
}

// 获取源目录下的File对象列表,每一个对象代表一个目录或者文件
File[] srcDirList = srcDir.listFiles();
// 遍历源目录File对象列表
for (int i = 0; i < srcDirList.length; i++)
{
// 如果是目录的话
if (srcDirList[i].isDirectory())
{
// 递归调用复制该目录
copyDir(FromDir + File.separator + srcDirList[i].getName(),
ToDir + File.separator + srcDirList[i].getName());
}
// 如果是文件的话
if (srcDirList[i].isFile())
{
// 调用复制文件的方法
copyFile(FromDir + File.separator + srcDirList[i].getName(),
ToDir + File.separator + srcDirList[i].getName());
}

}
}
/**
* 复制一个文件
*
* @param srcFile
* 源文件
* @param destFile
* 目的文件
* @throws IOException
*/
public static void copyFile(String srcFile, String destFile)
throws IOException
{
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(destFile);
// 2097152(Byte)=2048(KB)=2M
byte[] buffer = new byte[2097152];
int size = 0;
// 每次读取一个字节数组
while ((size = in.read(buffer)) != -1)
{
// 读到多少写入多少
out.write(buffer, 0, size);
}
in.close();
out.close();
}

}

部分内容已经丢失

可以看到包装FileReaderBufferedReader在读取文件时候如果文件的编码和项目的编码不一样的时候,会出现乱。

乱码问题

使用包装InputStreamReaderBufferedReader读取文件

1
2
3
4
5
6
7
8
9
String file = "utf8.txt";
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(file), "utf-8"));
char[] cbuf=new char[20];
int size;
while((size=reader.read(cbuf, 0, cbuf.length))!=-1)
{
System.out.println(new String(cbuf,0,size));
}

运行结果:

1
2
utf-8 file
这里是一句中文

这里要弄清楚的是BufferedReader只负责读到它的内部缓冲区中,而解码的工作是InputStreamReader完成的。

BufferedWriter

BufferedWriter的API:

继承关系

1
2
3
java.lang.Object
|----> java.io.Writer
|----> java.io.BufferedWriter

构造函数:

方法 描述
BufferedWriter(Writer out) 创建一个缓冲字符输出流,使用默认大小的输出缓冲区
BufferedWriter(Writer out, int sz) 创建一个缓冲字符输出流,使用给定大小的输出缓冲区

成员方法

方法 描述
void write(int c) 写入单个字符。
void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
void write(String s, int off, int len) 写入字符串的某一部分。
void newLine() 写入一个行分隔符。
void close() 关闭此流,但要先刷新它。
void flush() 刷新该流的缓冲。

写文件实例

使用上述三个写方法写文件:一个字符一个字符的复制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws IOException
{
BufferedWriter writer=new BufferedWriter(new FileWriter("静夜思.txt"));
char ch='床';
//写入一个字符
writer.write(ch);
String next="前明月光,";
char[] nexts=next.toCharArray();
//写入一个字符数组
writer.write(nexts,0,nexts.length);
//写入换行符
writer.newLine();//写入换行符
String nextLine="疑是地上霜。";
//写入一个字符串。
writer.write(nextLine);
//关闭流
writer.close();
}

运行结果,静夜思.txt:

1
2
床前明月光,
疑是地上霜。

应用:复制文本文件

逐个字符复制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void copyByChar(String srcFile, String destFile) throws IOException
{
BufferedReader reader = new BufferedReader(new FileReader(srcFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(destFile));
int ch=0;
//读取一个字符
while ((ch = reader.read()) != -1)
{
//写入一个字符
writer.write(ch);
}
reader.close();
writer.close();
}

逐个字符数组复制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void copyByCharArray(String srcFile, String destFile) throws IOException
{
BufferedReader reader = new BufferedReader(new FileReader(srcFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(destFile));
int size=0;
char[] cbuf=new char[20];
//读取一个字符数组
while ((size = reader.read(cbuf)) != -1)
{
//读入多少写入多少
writer.write(cbuf,0,size);
}
reader.close();
writer.close();
}

按行复制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void copyByLine(String srcFile,String destFile) throws IOException
{
BufferedReader reader=new BufferedReader(new FileReader(srcFile));
BufferedWriter writer=new BufferedWriter(new FileWriter(destFile));
String line;
//BufferedReader读取一行的时候返回的字符串中不包括换行符
//如果有一行字符就返回该行字符串,没有就返回null
while((line=reader.readLine())!=null)
{
writer.write(line);
writer.newLine();//写换行符
}
reader.close();
writer.close();
}

需要注意的是,BufferedReaderreadLine()读取一行的时候返回的字符串没有换行符,所以,复制的时候写文件是我们好多写入一个换行符,使用writer.newLine()方法即可。

测试:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IOException
{
String from = "gbk.txt";
String to = "gbk_copy.txt";
String to1 = "gbk_copy1.txt";
String to2 = "gbk_copy2.txt";
copyByChar(from, to);
copyByCharArray(from, to1);
copyByLine(from, to2);
}

源文件gbk.txt:
运行结果:
gbk_copy.txt

1
2
gbk file
这里是一句中文

gbk_copy1.txt

1
2
gbk file
这里是一句中文

gbk_copy2.txt

gbk file
这里是一句中文

bug:按行复制的时候多写换行符

细心的朋友可能发现,按行复制的时候,复制的文件会莫名其妙的在文件后面多了一个换行符。这是因为我们每次都在读到的字符串后面写一个换行符。
解决办法:在读到的字符串前面写换行符,这样出现新的问题,就是在文件开头多出了一个空行,所以加入控制语句,在第一行不写入换行符,第二行后再写。

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
static void copyByLine(String srcFile,String destFile) throws IOException
{
BufferedReader reader=new BufferedReader(new FileReader(srcFile));
BufferedWriter writer=new BufferedWriter(new FileWriter(destFile));
String line;
//BufferedReader读取一行的时候返回的字符串中不包括换行符
//如果有一行字符就返回该行字符串,没有就返回null
boolean flag=false;
while((line=reader.readLine())!=null)
{
if(!flag)
{
flag=true;
writer.write(line);
}
else
{
writer.newLine();//写换行符
writer.write(line);
}

}
reader.close();
writer.close();
}

这样复制的文件就不会多谢换行符了,保证复制的文件和源文件是一模一样的。

bug:乱码问题

因为我们使用的是包装FileReaderBufferedReader,包装FileWriterBufferedWriter。所以读字符,写字符的时候使用的是默认的字符编码读写的。所以读写文件的时候会出现乱码,可以使用包装InputStreamReaderBufferedReader,包装OutputStreamWriterBufferedWriter来复制文件,这样就可以支持各种字符编码。

实例:gbk编码的文件复制到utf8编码的文件中:

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
static void copyByLineEncoding(String srcFile, String srcEncoding, String destFile,
String destEncoding)
{
BufferedReader reader = null;
BufferedWriter writer = null;
try
{
reader = new BufferedReader(new InputStreamReader(
new FileInputStream(srcFile), srcEncoding));
writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(destFile), destEncoding));
char[] charArray = new char[512];
int size;
while ((size = reader.read(charArray, 0, charArray.length)) != -1)
{
writer.write(charArray, 0, size);
}

} catch (UnsupportedEncodingException | FileNotFoundException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
} finally
{
if (writer != null)
{
try
{
writer.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
if (reader != null)
{
try
{
reader.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
}

main方法:

1
2
3
4
5
6
public static void main(String[] args) throws IOException
{
String from = "gbk.txt";
String to = "copyto_utf8.txt";
copyByLineEncoding(from,"gbk",to,"utf-8");
}

源文件gbk.txt(gbk编码):

1
2
gbk file
这里是一句中文

目标文件copyto_utf8.txt:

1
2
utf-8 file
杩欓噷鏄竴鍙ヤ腑鏂�

乱码是正常的,因为我们的工程目录用的gbk编码,把copyto_utf8.txt编码显示就好了:

1
2
utf-8 file
这里是一句中文

所以使用包装InputStreamReaderBufferedReader,包装OutputStreamWriterBufferedWriter来复制文件的好处就是可以指定复制文件的时候使用的字符编码,例如上面的复制操作,从gbk编码的文件中读取,然后写入到utf8编码的文件中去。

FileWriter类

FileWriter类可以把字符直接写入流中,FileWriter继承与OutputStreamWriter,它的所有方法也都继承于OutputStreamWriter

继承关系

1
2
3
4
5
java.lang.Object
|----> java.io.Writer
|----> java.io.OutputStreamWriter
|----> java.io.FileWriter

构造方法:

方法 描述
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象。
FileWriter(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象。是否追加写入
FileWriter(FileDescriptor fd) 构造与某个文件描述符相关联的 FileWriter 对象。
FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象。
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否追加加写入数据的 boolean 值来构造 FileWriter 对象。

读操作方法

OutputStreamWriter继承的方法:

方法 描述
void write(int c) 写入单个字符。
void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
void write(String str, int off, int len) 写入字符串的某一部分。
String getEncoding() 返回此流使用的字符编码的名称。
void flush() 刷新该流的缓冲。
void close() 关闭此流,但要先刷新它。

FileWriter类和OutputStreamWriter类的区别

还是和上面的一样,区别主要在构造方法上。
OutputStream类的构造方法:

方法 描述
OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, Charset cs) 创建使用给定字符集的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, CharsetEncoder enc) 创建使用给定字符集编码器的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的 OutputStreamWriter。
从OutputStreamWriter的构造方法中可看出,第一个参数为OutputStream,第二个参数可以是字符编码或者编码器。所以写操作时需要指定字符编码的时候必须使用OutputStreamWriter

FileWriter的构造方法中第一个参数为File,String,FileDescriptor对象,第二个参数表示可以是否追加写入。FileWriter会使用默认的编码来进行写操作。所以使用File类或String文件名来进行写操作是使用FileWriter,要求追加写入文件时也要使用FileWriter

读写实例:复制文件

使用FileReader和FileWriter复制文件

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
/**   
* 从名字为from的源文件复制到名字为to的目的文件中。
* @param from 源文件
* @param to 目的文件
*/
public static void copyByFileReader_Writer(String from, String to)
{
FileReader reader = null;
FileWriter writer = null;
try
{
reader = new FileReader(from);
writer = new FileWriter(to);
char[] cbuf = new char[128];
int size = 0;
while ((size = reader.read(cbuf, 0, cbuf.length)) != -1)
{
writer.write(cbuf, 0, size);
}
} catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
} finally
{
if (reader != null)
try
{
reader.close();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
if (writer != null)
try
{
writer.close();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

main方法中调用:

1
2
3
4
5
6
public static void main(String[] args)
{
String from = "gbk.txt";
String to = "gbk_copy.txt";
copyByFileReader_Writer(from, to);
}

源文件gbk.txt:

1
2
gbk file
这里是一句中文

复制要的目的文件gbk_copy.txt

1
2
gbk file
这里是一句中文

编码问题

使用FileReader和FileWriter使用默认的编码来读文件,写文件。这样容易出现问题。现在项目的编码是gbk的,但是此时用来复制utf-8编码的文件就会出现问题。
源文件utf8.txt(utf-8编码):

1
2
utf-8 file
这里是一句中文

main方法:

1
2
3
4
5
6
public static void main(String[] args)
{
String from = "utf8.txt";
String to = "utf8_copy.txt";
copyByFileReader_Writer(from, to);
}

运行结果,复制好的utf8_copy.txt中的内容:

1
2
utf-8 file
杩欓噷鏄竴鍙ヤ腑鏂�

出现这样的乱码是因为,我们现在是以默认的编码(gbk)打开的,现在改成以utf-8编码打开看看:

1
2
utf-8 file
这里是一句中�?

可以看到虽然已经以utf-8编码打开了,但是还是显示乱码。

所以遇到需要处理字符编码的时候要使用InputStreamReaderOutputStreamWriter来操作。