corejava11(3.7 输入和输出)

3.7 输入和输出

为了使示例程序更有趣,我们希望接受输入并正确格式化程序输出。当然,现代程序使用GUI来收集用户输入。然而,对这样一个接口进行编程需要的工具和技术比我们目前所能使用的要多。我们的第一个任务是对Java编程语言更熟悉,因此我们使用谦逊的控制台进行输入和输出。

3.7.1 读取输入

您看到,只需调用System.out.println,就可以轻松地将输出打印到“标准输出流”(即控制台窗口)。从“Standard input stream”系统中读取并不是那么简单。要读取控制台输入,首先构造一个连接到System.in的Scanner:

Scanner in = new Scanner(System.in);

(我们将在第4章中详细讨论构造函数和新的操作符。)现在您可以使用Scanner类的各种方法来读取输入。例如,nextLine方法读取一行输入。

System.out.print("What is your name? ");
String name = in.nextLine();

这里,我们使用nextLine方法,因为输入可能包含空格。要读取单个单词(用空格分隔),请调用

String firstName = in.next();

要读取整数,请使用nextInt方法。

System.out.print("How old are you? ");
int age = in.nextInt();

类似地,nextDouble方法读取下一个浮点数。

清单3.2中的程序询问用户的姓名和年龄,然后打印一条类似

Hello, Cay. Next year, you'll be 57

最后注意行

import java.util.*;

在程序开始的地方。Scanner类在java.util包中定义。每当使用基本java.lang包中未定义的类时,都需要使用import指令。我们将在第4章中更详细地研究包和导入指令。

清单3.2 InputTest/InputTest.java

import java.util.*;
 
/**
* This program demonstrates console input.
*
* @version 1.10 2004-02-10
* @author Cay Horstmann
*/
public class InputTest {
  public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
 
    // get first input
    System.out.print("What is your name? ");
    String name = in.nextLine();
 
    // get second input
    System.out.print("How old are you? ");
    int age = in.nextInt();
 
    // display output on console
    System.out.println("Hello, " + name + ". Next year, you'll be " + age);
  }
}
 

注意

Scanner类不适合从控制台读取密码,因为任何人都可以清楚地看到输入。Java 6为这个目的专门介绍了一个Console类。要读取密码,请使用以下代码:

Console cons = System.console();
String username = cons.readLine("User name: ");
char[] passwd = cons.readPassword("Password: ");

出于安全原因,密码以字符数组而不是字符串的形式返回。处理完密码后,应立即用填充值覆盖数组元素。(第108页第3.10节“阵列”中讨论了阵列处理。)

Console对象的输入处理不如Scanner方便。您必须一次读取一行输入。没有阅读单个单词或数字的方法。

java.util.Scanner 5

  • Scanner(InputStream in)
    从被给的输入流中构造一个Scanner对象
  • String nextLine()
    读取输入流的下一行
  • String next()
    读取下一个输入的单词(用空格分隔)
  • int nextInt()
  • double nextDouble()
    读取并且转化下一个用来表示一个整形或者浮点型数字的字符序列
  • boolean hasNext()
    测试在输入中是否有另一个单词
  • boolean hasNextInt()
  • boolean hasNextDouble
    测试是否下一个字符序列是整形或者浮点型

java.lang.System 1.0

  • static Console console() 6
    返回一个Console对象,用于通过控制台窗口与用户交互(如果可能的话),否则为空。控制台对象可用于在控制台窗口中启动的任何程序。否则,可用性取决于系统。

java.io.Console 6

  • static char[] readPassword(String prompt, Object… args)
  • static String readLine(String prompt, Object… args)
    显示提示并读取用户输入,直到输入行结束。args参数可用于提供格式化参数,如下一节所述。

3.7.2 格式化输出

您可以使用语句System.out.print(x)将数字x打印到控制台。该命令将以该类型的最大非零位数打印X。例如,

double x = 10000.0 / 3.0;
System.out.print(x);

打印出

3333.3333333333335

这是一个问题,如果你想显示,例如,美元和美分。

在早期版本的Java中,格式化数字有点麻烦。幸运的是,Java 5从C库中带回了可尊敬的printf方法。例如,调用

System.out.printf("%8.2f", x);

打印x,字段宽度为8个字符,精度为2个字符。也就是说,打印输出包含一个前导空格和七个字符

3333.33

可以向printf提供多个参数。例如:

System.out.printf("Hello, %s. Next year, you'll be %d", name, age);

以%字符开头的每个格式说明符都将替换为相应的参数。格式说明符结尾的转换字符指示要格式化的值的类型:f是浮点数字,s是字符串,d是十进制整数。表3.5显示了所有转换字符。

表3.5 printf转换

转换字符类型例子
d十进制整数159
x十六进制整数9f
o八进制整数237
f固定浮点的浮点数15.9
e科学计数法浮点数1.59e+01
g一般的浮点数(比e和f更短)-
a十六进制浮点数0x1.fccdp3
s字符串Hello
c字符H
b布尔true
h哈希代码42628b2
tx或Tx日期和时间(T强制大写)废除,使用java.time类代替,参见卷二第六章
%百分号标记%
n平台相关的行分隔符-

此外,还可以指定控制格式化输出外观的标志。表3.6显示了所有标志。例如,逗号标志添加了组分隔符。也就是说,

表3.6 printf的标志

标志目的例子
+打印正数和负数的符号+3333.33
空格在正数前面添加一个空格| 3333.33|
0添加前导0003333.33
-左调整域|3333.33|
(将负数括在括号中(3333.33)
,添加组分隔符3,333.33
#(和f一起使用,包含一个浮点格式)3,333.
#(和x或o一起使用,添加0x或者0前缀)0xcafe
$指定要格式化的参数的索引;例如,%1$d %1$x以十进制和十六进制打印第一个参数。159 9F
<格式化与上一个规范相同的值;例如,%d %<x以十进制和十六进制打印相同的数字。159 9F
System.out.printf("%,.2f", 10000.0 / 3.0);

打印出

3,333.33

可以使用多个标志,例如“%,(.2f”使用组分隔符并将负数括在括号中。

注意

您可以使用s转换来格式化任意对象。如果任意对象实现了Formattable接口,则调用对象的formatTo方法。否则,将调用ToString方法将对象转换为字符串。我们讨论了第5章中的toString方法和第6章中的接口。

可以使用静态的String.format方法创建格式化字符串而不打印:

String message = String.format("Hello, %s. Next year, you'll be %d", name, age);

出于完整性的考虑,我们简要讨论了printf方法的日期和时间格式选项。对于新代码,应该使用第二卷第6章中描述的java.time包的方法。但在旧代码中可能会遇到日期类和相关的格式选项。格式由两个字母组成,以t开头,以表3.7中的一个字母结尾;例如,

表3.7 日期和时间转化字符

转化字符类型例子
c完整的日期和时间Mon Feb 09 18:05:19 PST
2015
FISO 8601日期2015-02-09
D美国格式日期02/09/2015
T24小时时间18:05:19
r12小时时间06:05:19 pm
R24小时时间,没有秒18:05
Y四位数年份(有前导0)2015
y年份的最后两位(有前导0)15
C年的前两位(有前导0)20
B完整的月份名字February
b或者h月份名字的简写Feb
m两位数月份(有前导0)02
d两位数日期(有前导0)09
e两位数日期(没有前导0)9
A完整星期名Monday
a星期名的简写Mon
j三位数表示这一年的第几天(有前导0),从001到366069
H两位数小时(有前导0),从00到2318
k两位数小时(没有前导0),从0到2318
I(大写i)两位数小时(有前导0),从01到1206
l(小写L)两位数小时(没有前导0),从1到126
M两位数分钟(有前导0)05
S两位数秒(有前导0)19
L三位数毫秒(有前导0)047
N九位数纳秒(有前导0)047000000
p上午或下午标志pm
zRFC 822,距离GMT的数字偏离-0800
Z时区PST
s从1970-01-01 00:00:00开始的秒数1078884319
Q从1970-01-01 00:00:00开始的毫秒数1078884319047
System.out.printf("%tc", new Date());

以以下格式打印出当前日期和时间

Mon Feb 09 18:05:19 PST 2015

如表3.7所示,某些格式只生成给定日期的一部分,例如,仅生成日期或月份。如果你必须多次提供日期来格式化每个部分,那就有点傻了。因此,格式字符串可以指示要格式化的参数的索引。索引必须紧跟在%之后,并且必须以$终止。例如,

System.out.printf("%1$s %2$tB %2$te, %2$tY", "Due date:", new Date());

打印出

Due date: February 9, 2015

或者,可以使用<标志。它指示应再次使用与前面格式规范中相同的参数。也就是说,声明

System.out.printf("%s %tB %<te, %<tY", "Due date:", new Date());

生成与前面语句相同的输出。

小心

参数序号值从1开始,而不是0:%1$…格式化第一个参数。这与0标志不同,容易引起混乱

您现在已经看到了printf方法的所有特性。图3.6显示了格式说明符的语法图。

图3.6 格式化特定语法

注意

数字和日期的格式是特定于区域设置的。例如,在德国,组分隔符是句点,而不是逗号,星期一的格式是蒙太格。第二卷第7章介绍了如何控制您的申请的国际行为。

3.7.3 文件输入和输出

为了从文件读取,像如下构造一个Scanner对象:

Scanner in = new Scanner(Path.of("myfile.txt"), StandardCharsets.UTF_8);

如果文件名包含反斜杠,请记住用附加的反斜杠转义每个文件名:“c:\\mydirectory\\myfile.txt”。

注意

这里,我们指定UTF-8字符编码,这是Internet上文件的通用(但不是通用)编码。阅读文本文件时,您需要知道字符编码(有关详细信息,请参阅第二卷第2章)。如果省略字符编码,则使用运行Java程序的计算机的“默认编码”。这不是一个好主意,程序可能会根据运行位置的不同而采取不同的行动。

现在,您可以使用我们已经描述过的任何Scanner方法来读取文件。

要写入文件,请构造一个PrintWriter对象。在构造函数中,提供文件名和字符编码:

PrintWriter out = new PrintWriter("myfile.txt", StandardCharsets.UTF_8);

如果文件不存在,则创建该文件。可以像打印到System.out时那样使用print、println和printf命令。

小心

您可以用字符串参数构造一个Scanner,但scanner将字符串解释为数据,而不是文件名。例如,如果你调用

Scanner in = new Scanner("myfile.txt"); // ERROR?

然后扫描器将看到10个字符的数据:“m”、“y”、“f”等。这可能不是本例的初衷。

注意

当指定一个相对的文件名时,例如“myfile.txt”、“mydirectory/myfile.txt”或“…/myfile.txt”,文件相对于Java虚拟机启动的目录而定位。如果从命令shell启动程序,通过执行

java MyProg

那么起始目录就是命令shell的当前目录。但是,如果使用集成开发环境,它将控制起始目录。您可以使用此调用找到目录位置:

String dir = System.getProperty("user.dir");

如果您在查找文件时遇到困难,请考虑使用绝对路径名,如“c:\\mydirectory\\myfile.txt”或“/home/me/mydirectory/myfile.txt”。

如您所见,您可以像使用System.in和System.out一样轻松地访问文件。只有一个陷阱:如果使用不存在的文件构建Scanner,或用无法创建的文件名构造PrintWriter,则会发生异常。Java编译器认为这些异常比“除零”异常更严重。在第7章中,您将学习处理异常的各种方法。现在,您应该简单地告诉编译器您知道“输入/输出”异常的可能性。为此,您可以使用throws子句标记主方法,如下所示:

public static void main(String[] args) throws IOException
{
    Scanner in = new Scanner(Path.of("myfile.txt"), StandardCharsets.UTF_8);
    . . .
}

现在您已经了解了如何读写包含文本数据的文件。对于更高级的主题,如处理不同的字符编码、处理二进制数据、读取目录和写入zip文件,请参阅第二卷第2章。

注意

从命令shell启动程序时,可以使用shell的重定向语法并将任何文件附加到System.in和System.out:

java MyProg < myfile.txt > output.txt

那么,您不必担心如何处理IOException。

java.util.Scanner 5

  • Scanner(Path p, String encoding)
    构造一个Scanner,它从给定的路径中读取数据,使用给定的字符编码
  • Scanner(String data)
    构造一个Scanner,它从给定的字符串读取数据

java.io.PrintWriter 1.1

  • PrintWriter(String fileName)
    构造一个PrintWriter,它将数据写到给定的文件名

java.nio.file.Path

  • static Path of(String pathname) 11
    通过给定的路径名构造一个Path

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值