参考《疯狂java讲义》
与用户互动
实际上,大部分程序都需要处理用户动作,包括接受用户的键盘输入,鼠标动作等。本章未涉及图形用户接口(GUI)编程,故本节主要介绍程序如何获得用户的键盘输入。
1. 运行Java程序的参数
回忆Java程序入口——main()方法的方法签名
//Java程序入口,main()方法
public static void main(String[] args)
为什么要用上面的方法签名呢
public修饰符:Java类由JVM调用,为了让JVM可自由调用这个main方法,所以使用public方法把这个方法暴露出来
static修饰符:JVM调用这个主方法时,不会先创建该主类对象,然后通过对象调用main方法。JVM通过该类来调用main方法,因此用static修饰该方法
void返回值:因为主方法被JVM调用,该方法的返回值将返回给JVM,这没有任何意义,因此main方法返回值没有任何意义
字符串数组形参:根据方法调用的规则,谁调用方法,谁就负责为形参赋值,也就是说,main方法由JVM调用,即args形参应该由JVM负责赋值。但JVM怎么知道如何为args数据赋值呢?
public class MainTest{
public static void main(String[] args){
//输出数组长度
System.out.println(args.length);//输出0
//输出args数组的每个元素
for(String arg : args){
System.out.println(arg);//没有输出
}
}
}
该为如下命令来运行结果
//如果在运行时在类名后紧跟一个或多个字符串
//(多个字符串用空格隔开,若一个字符串中有空格,应将其用“ ”括起来)
//输出2,分两行输出java ,String
java MainTest java String
1. 使用Scanner获取键盘输入
Scanner是一个基于正则表达式的文本扫描器,它可以从文件,输入流,字符串中解析出基本类型值和字符串值,Scanner类提供了多个构造器,不同构造器可以接收文件,输入流,字符串作为数据源,用于从文件,输入流,字符串中解析数据。
Scanner主要提供了两个方法来扫描输入
- hasNextXxx():是否还有下一个输入项,其中Xxx可以是,Int,Long等代表基本数据类型的字符串,如果只是判断是否包含下一个字符串,则直接使用hasNext()。
- nextXxx():获取下一个输入项
注意: 在默认情况下,Scanner使用空白字符(包括空格,Tab,回车)作为多个输入项之间的分隔符
public class ScannerKeyBoardTest{
public static void main(String[] args){
//System.in代表标准输入,就是键盘输入
Scanner sc = new Scanner(System.in);
//增加下面一行将只把回车作为分隔符
//sc.useDelimiter("\n");
//判断是否还有下一个输入项
while(sc.hasNext()){
//输出输入项
System.out.println(sc.next());
}
}
}
Scanner的读取操作可能被阻塞(当执行顺序流暂停)来等待信息的输入。如果输入源没有结束,Scanner又读不到更多输入项时(尤其在键盘输入时比较常见),Scanner的hasNext和next()方法都有可能被阻塞,hasNext是否阻塞与next方法是否阻塞无关。
为Scanner设置分隔符使用useDelimiter(String pattern)方法即可,该方法参数是一个正则表达式
Scanner提供了两个简单的方法来逐行读取
- boolean hasNextLine():返回输入源是否还有下一行
- String nextLIne():返回输入源中下一行的字符串
public class ScannerLongTest{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while(sc.hasNextLong()){
System.out.println(sc.nextLomg());
}
}
}
2. Scanner读取文件输入
只要在创建Scanner对象时传入一个File对象作为参数,就可以让Scanner读取该文件的内容
public class ScannerFileTest{
public static void main(String[] args){
//将一个File对象作为Scanner构造器的参数,Scanner读取文件内容
Scanner sc = new Scanner(new File("ScannerFileTest.java"));
System.out.println("文件内容如下:");
while(sc.hasNextLine())
{
//输出文件的下一行
Systemout.println("sc.nextLine()");
}
}
}
系统相关
Java程序在不同操作系统上运行时,可能需要取得平台的相关属性,或者调用平台命令来完成特定功能。Java提供了System类和Runtime类来与程序运行的运行平台进行交叉。
1. System类
System代表Java程序的运行平台,程序不能创建System类的对象,System类提供了一些类变量和类方法,允许直接通过System类来调用这些类变量和类方法。
System类提供了代表标准输入,标准输出和错误输出的类变量,并提供了一些静态方法用于访问环境变量,系统属性的方法,还提供了加载文件和动态链接库的方法。
加载文件和动态链接库主要用对native方法有用,对于一些特殊的功能(如访问操作系统底层硬件设备等)Java程序无法实现,必须借助C来完成,此时需要使用C为Java提供方法实现,实现步骤如下:
1 Java程序中声明native修饰的方法,类似于abstract方法,只有方法签名,没有实现,编译该Java程序,生成一个class文件。
2 用javah编译第一步生成的class文件,将产生一个.h文件
3 写一个.cpp文件实现native方法,这一步需要包含第2步产生的.h文件(这个.h文件又包含了JDK带的jni.h文件)。
4 将第3步产生的.cpp文件编译成动态链接库文件
5 在Java中用System类的loadLibrary…()方法或Runtime类的loadLibrary()方法加载第4步产生的动态链接库,Java程序中就可以调用这个native方法。
public class SystemTest{
public static void main(String[] args){
//获取系统所有的环境变量
Map<String,String>env = System.getenv();
for(String name : env.keySet()){
System.out.println(name + "---->" + env.get(name))
}
//获取指定环境变量的值
System.out.println(System.getenv( "JVAV_HOME"));
//获取所有系统属性
Properties props = System.getProperties();
//将系统所有属性保存在props.txt文件中
props.store( "new FileOutputStream( "props.txt" )","System.Properties" );
//输出特定的系统属性
System.out.println(System.getProperty( "os.name" ));//输出 Windows 10
}
}
提示:System提供了通知系统进行垃圾回收的gc()方法,以及通知系统进行资源清理的runFinalization()方法
System还提供了两个获取系统时间的方法:currentTimeMillis()和nanoTime(),它们都返回一个long型整数。实际上它们都返回当前时间与UTC 1970年1月1日午夜的时间差,前者以毫秒作为单位,后者以纳秒为单位。必须指出的是,这两个方法返回的时间粒度取决于底层操作系统,可能所在操作系统根本不支持以毫秒和纳秒作为计时单位。例如好多操作系统都以及时毫秒作为单位测量时间,currentTimeMillis方法根本不可能返回精确的毫秒数:而nanoTime方法很少用,因为大部分操作系统都不支持一纳秒为计时单位
除此之外,System类的in , out , err分别代表系统的标准输入(通常是键盘),标准输出(通常是显示器)和错误输出流,并提供了setIn,setOut和setErr方法来改变系统的标准输入,标准输出和标准错误输出流。
System还提供了一个identityHashCode(Object x)方法,该方法返回指定对象的精确hashCode值,也就是根据对象地址计算得到的HashCode值。当某个类的hashCode方法被重写后,该类实例的hashCode值就不能唯一标识该对象:但通过identifyHashCode方法返回的hashCode值,依然是根据对象地址计算得到的hashCode值。所以如果两个对象的identifyHasCode值相同,则两个对象绝对是同一个对象。
public class IdentifyHashCodeTest{
public static void main(String[] args){
//下面程序中s1和s2是两个不同的对象
String s1 = new String("Hello");
String s2 = new String("Hello");
//String类重写了equals方法和hashCode方法,只要两字符串序列相同equals方法就返回true,
//hashCode方法是根据字符串序列计算的hashCode值。
System.out.println(s1.hashCode()==s2.hashCode());//输出true
//s1和s2是不同的对象,所以返回的IdentifyHashCode的值不同
System.out.println(System.identityHashCode(s1)==System.identityHashCode(s2));//输出false
String s3 = "java";
String s4 = "java";
//s3和s4是同一对象
System.out.println(System.identityHashCode(s3)==System.identityHashCode(s4));//输出true
}
}
1. Runtime类与Java 9 的ProcessHandle
Runtime代表Java运行时环境,可以访问JVM的相关信息,如处理器数量,内存信息等。每个Java程序都有一个与之对应的Runtime实例,应用程序通过该对象与其运行时环境连接,应用程序不能创建自己的Runtime实例,但可以通过getRuntime()方法获取与之关联的Runtime对象。
与System类似的是,Runtime类也提供了gc()方法和runFinalization()方法来通知系统进行垃圾回收,清理系统资源。并提供了load(String filename)和loadLibrary(String libname)加载文件和动态连接库.
public calss RuntimeTest{
public static void main(String[] args){
//获取Java程序关联的运行时对象
Runtime rt = Runtime.getRuntime();
System.out.println("处理器数量:"+ rt.availableProcessors());//输出 8
System.out.println("空间内存数:"+rt.freeMemory() );//输出 125561504
System.out.println("总内存数:"+ rt.totalMemory());//输出 128974848
System.out.println("可用最大内存数:"+rt.maxMemory());//输出 1886912512
}
}
Runtime类还有一个功能——可以直接单独启动一个进程来运行操作系统的命令
public class ExecTest{
public static void main(String[] args) throws Exception{
Runtime rt = Runtime.getRuntime();
//运行记事本程序
rt.exec("notepad.exe");
}
}
通过exec启动平台上的命令之后,它就变成了一个进程,java使用Process来代表进程。Java 9还新增了一个ProcessHandle.Info类,用于获取进程的命令,参数,启动时间,累计运行时间,用户等信息,下面示范了通过ProcessHandle获取进程的相关信息。
public class ProcessHandleTest{
public static void main(String[] args)throws Exception{
Runtime rt = Runtime.getRuntime();
//运行记事本程序
Process p = rt.exec("notepad.exe");
ProcessHandle ph = p.toHandle();
System.out.println("进程是否运行:" + ph.isAlive());
System.out.println("进程ID:" + ph.pid());
System.out.println("父进程:" + ph.parent());
//获取ProcessHandle.info信息
ProcessHandle.info info = ph.info();
//通过ProcessHandle.info;信息获取进程相关信息
System.out.println("进程命令:" + info.command());
System.out.println("进程参数:"+ info.arguments());
System.out.println("进程启动时间:"+info.startInstant()");
System.out,println("进程累计运行时间:" + info.totalCpuDuration());
//通过CompletableFuture在进程结束时运行某个任务
CompletableFuture<ProcessHandle>cf = ph.onExit();
cf.thenRunAsync(()->{
System.out.println("程序退出");
})
Thread.sleep(5000);
}
}