会员可以在此提问,百战程序员老师有问必答
对大家有帮助的问答会被标记为“推荐”
看完课程过来浏览一下别人提的问题,会帮你学得更全面
截止目前,同学们一共提了 132834个问题

package com.bjsxt.server;

import java.util.List;
import java.util.Map;

import com.bjsxt.servlet.Servlet;

/**
 * Web应用程序
 * @version 1.0
 *
 */
public class WebApp {
	private static ServletContext contxt;
	static{
		contxt=new ServletContext();
		//分别获取对应关系的Map集合
		Map<String,String> servlet=contxt.getServlet();
		Map<String,String> mapping=contxt.getMapping();
		//创建解析XML文件对象
		WebDom4j web=new WebDom4j();
		web.parse(web.getDocument());//解析xml
		//获取解析XML之后的List集合
		List<Entity> entityList=web.getEntityList();
		List<Mapping> mappingList=web.getMappingList();
		
		//将List集合中的数据存储到Map集合
		for (Entity entity : entityList) {
			servlet.put(entity.getName(), entity.getClazz());
		}
		//	System.out.println(servlet);
		for( Mapping map:mappingList){
			//遍历url-pattern的集合
			List<String> urlPattern=map.getUrlPattern();
			for(String s:urlPattern){
				mapping.put(s, map.getName());
			}
		}
		System.out.println(mapping);
	}
	/**
	 * 通过反射创建Servlet对象
	 * @param url
	 * @return Servlet
	 */
	public static Servlet getServlet(String url){
		if (url==null||url.trim().equals("")) {
			return null;
		}
		//调用无参构造方法创建Servlet对象
		
		try {
			//如果url正确
			//根据url的key获取servlet-name的值 /log=login, /reg=register
			String servletName=contxt.getMapping().get(url);
			//根据servletName得到对应的servlet-class
			String servletClass=contxt.getServlet().get(servletName);  //得到的是一个完整个的包名+类的字符串
			System.out.println(servletClass);
			//使用反射创建 Servlet对象
			Class<?> clazz=Class.forName(servletClass);
			Servlet servlet = (Servlet) clazz.newInstance();
			return servlet;
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	public static void main(String[] args) {
		System.out.println(getServlet("/log"));
		System.out.println(getServlet("/login"));		
	}
}

image.png

老师,为什么这里得到的是一个包名+类的字符串,过程具体是怎么实现的?

JAVA 全系列/第二阶段:JAVA 基础深化和提高/手写服务器项目(旧) 2686楼
JAVA 全系列/第二阶段:JAVA 基础深化和提高/网络编程(旧) 2687楼

学了这次的视频,我了解了下一直不太懂的命名空间

命名空间的出现是为了解决dtd不能解决的xml的命名冲突问题,这次课的视频里,xsd文件没有定义自己的命名空间。

然后在book.xml中引入book.xsd的时候属性使用的是

xsi:noNamespaceSchemaLocation="book.xsd"

不指定命名空间,只指定用于模式校验的xsd文档的位置,这种写法,XML模式处理器已经可以校验到xml里的写法问题了。如图:具体报错:

The content of element 'book' is not complete. One of '{price}' is expected.

说明已经检验成功了

image.png

在这里老师用的是

xsi:noNamespaceSchemaLocation="{book.xsd}" 而这种写法在我的idea里面是报红的。 至于视频里老师不报红,我猜测不同工具的不同文件,可能工具没有识别出来语法错误,把验证插件给关闭了。image.png为什么老师要多此一举,使用java的相关类,SchemaFactory进行解析, 是不是因为要让我们了解如果脱离eclipse或者相关IDE,没有XML解析器,在只有xml文件及xsd文件的基础上,java也可以完成使用schema对xml文档的校验。

(扩充:自己测试了一下似乎谷歌浏览器无法完成使用schema对xml文档的校验,不报任何错误。)

这点在视频里没有提到。。这里存在疑问:(******) 


另外给大家一种使用命名空间的方式校验:targetNamespace在book.xsd中给当前命名空间起名字,名称任意:http://www.example.org/books 这个是可以更改的

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/books" 
elementFormDefault="qualified">
    <xs:element name="books">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="book" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="name" type="xs:string"></xs:element>
                            <xs:element name="author" type="xs:string"></xs:element>
                            <xs:element name="price" type="xs:double"></xs:element>
                        </xs:sequence>
                        <xs:attribute name="id" type="xs:positiveInteger"></xs:attribute>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

book.xml 使用命名空间http://www.example.org/books

<?xml version="1.0" encoding="UTF-8" ?>
<books xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.example.org/books"
        xsi:schemaLocation="http://www.example.org/books book.xsd">
    <book id="1001">
        <name>html的使用</name>
        <author>张三</author>
        <price>51.6</price>
    </book>
    <book id="1002">
        <name>css的使用</name>
        <author>李四</author>
        <price>40.6</price>
    </book>
</books>

注释掉第一个book元素的price元素

结果:具体报错:The content of element 'book' is not complete. One of '{"http://www.example.org/books":price}' is expected.

image.png

想让老师解答一下带****号的那里的疑问!谢谢!

JAVA 全系列/第二阶段:JAVA 基础深化和提高/XML 技术(旧) 2690楼

以下是我自己对UDP 一个发送者与一个接受者,多次咨询,双方互相发送的代码的优化,参考TCP的多线程

首先是Clinet项目

为一个发送者建立两个线程类,一个Send类,一个Receive类。用于发送和接收。

Send类

package com.gz.udp2;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Scanner;

public class Send implements Runnable{
    private DatagramSocket ds;
    private Scanner sc;
    private String destIp;//目标ip
    private int destPort;//目标端口号
    private String role;//角色名称
    private boolean flag=true;


    public Send(DatagramSocket ds,String destIp,int destPort,String role) {
        this.ds = ds;
        this.destIp=destIp;
        this.destPort=destPort;
        this.role=role;
        sc=new Scanner(System.in);
    }
    private String getMessage(){
        return sc.next();
    }
    private void send(String str){
        //发送时间
        String date=new Date().toLocaleString();
        str=date+"   "+role+":"+str;
        byte[] buf=str.getBytes();
        try {
            DatagramPacket dp=new DatagramPacket(buf,buf.length, InetAddress.getByName(destIp),destPort);
            ds.send(dp);
        } catch (IOException e) {
            flag=false;
            if(ds!=null) {
                ds.close();
            }
        }
    }

    @Override
    public void run() {
        while(flag){
            this.send(this.getMessage());
        }

    }
}

Receive类

package com.gz.udp2;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Scanner;

public class Receive implements Runnable{

    private DatagramSocket ds;
    private Scanner sc;
    private boolean flag=true;

    public Receive(DatagramSocket ds) {
        this.ds = ds;
        sc=new Scanner(System.in);
    }
    private String receive(){
        String str="";
        try {
            byte [] buf=new byte[1024];//等待接收数据
            DatagramPacket dp=new DatagramPacket(buf,buf.length);
            ds.receive(dp);
            str=new String(dp.getData(),0,dp.getLength());
        } catch (IOException e) {
            flag=false;
            if(ds!=null){
                ds.close();
            }
        }
        return str;
    }

    @Override
    public void run() {
        while(flag){
            System.out.println(this.receive());
        }
    }
}

Test1类,开启线程的类

package com.gz.udp2;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class Test1 {
    public static void main(String[] args) throws IOException {
        //DS
        DatagramSocket ds=new DatagramSocket(9777);//自己的端口号

        Send s=new Send(ds,"localhost",9888,"咨询者");
        //目标ip,目标端口号,自己角色
        Receive r=new Receive(ds);

        new Thread(s).start();
        new Thread(r).start();


    }
}


对于接收者,也是拥有两个类,Send类和Receive类,及同样的开启线程类,只不过是端口号不同,角色不同。

接收者的Test2类

package com.gz.udp2;

import java.io.IOException;
import java.net.DatagramSocket;

public class Test2 {
    public static void main(String[] args) throws IOException {
        //DS
        DatagramSocket ds=new DatagramSocket(9888);

        Send s=new Send(ds,"localhost",9777,"客服人员");
        //目标ip,目标端口号,自己角色
        Receive r=new Receive(ds);

        new Thread(s).start();
        new Thread(r).start();


    }
}


结果:成功实现了,客服多次回复:又优化加上了发送时间:

咨询者端:

image.png

客服端:

image.png


想让老师看一下我的代码还有哪里值得优化,还有一个问题,就是,单对单的时候,接收者在回复的时候可以获得发送者发送来的ip与端口号,但是我现在写成了多线程,并且在我的发送者与接收者的测试类里,ip跟端口号是单独指定的,也就是说,当线程开启之前,ip与端口号就固定下来了,下面的交互都是这两个ip与端口号之间的交互。我想问的是有办法像单对单那样,获取到上一个接收过来的数据来自的ip和端口号吗,这样的话就要找到上一个接收线程的DatagramPacket数据包dp,从dp能够获取到上一个发送端的ip及端口号,可是由于多线程,我编辑的逻辑是,每次接收和发送的方法都会创建新的DatagramPacket,当上一个线程调用发送方法以后,随着方法结束,dp数据包也会随之在内存中释放消失。所以,我觉得是完不成根据dp获取上一个发送端的ip及端口号的。因此这也是我的发送端和接收端都要重新制定ip及端口号的原因。

思考到这,(小总结******)我在想UDP是不是就是适合于两个人在通信的时候进行的交互,比如qq聊天,客服聊天,并且这两个人的聊天是指定两个人的单次会话的多次聊天。

而TCP适合群聊,中间的服务器可以将信息转发给其他人,也可以满足随时有人加入群聊,为随时加入群聊的客户端开启处理线程。


以上是我的思考,不知道有没有误解,想跟老师求证最后说的总结(带******号的)的正确性。

还有上面多线程的代码是否可以进一步优化,以及我对我的程序中发送端和接收端都要重新制定ip及端口号的理解是否正确。

还有就是UDP的单次会话的多线程如果真的实现是不是像我写的那样的,发送端和接收端都要重新制定ip及端口号。

JAVA 全系列/第二阶段:JAVA 基础深化和提高/网络编程(旧) 2692楼

客户端不显示

package com.bjsxt.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/** 
 * <p>Title: TcpServerScoket01</p>  
 * <p>Description: 使用TCP Socket实现单向通信(服务器端)</p>  
 * @author xiaoding
 * @date Jul 29, 2020  
 * @version 1.0 
 */
public class TcpServerScoket01 {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器启动");
        //创建服务器对象
        ServerSocket my = new ServerSocket(9999);
        //使用accept方法监听,服务器端口
        Socket p = my.accept();
        //接受客户端发送的数据
        InputStream fis = p.getInputStream();
        //定义一个中转站
        byte[] buf = new byte[1024];
        int len = 0;    //获取读取的次数
        //将读取的数据打印出来
        while((len = fis.read(buf)) != -1) {
            System.out.println(new String(buf,0,len));
        }
//        System.out.println((char)fis.read());
        
        //发送消息
        OutputStream fos = p.getOutputStream();
        //写入数据
        fos.write("收到了".getBytes());
        //关闭IO流
        if (fos != null) {
            fos.close();
        }
        if (fis != null) {
            fis.close();
        }
    }
}

package com.bjsxt.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;

/** 
 * <p>Title: TestTcpSocket01</p>  
 * <p>Description: 使用Tcp Scoket单向通信(客户端)</p>  
 * @author xiaoding
 * @date Jul 29, 2020  
 * @version 1.0 
 */
public class TestTcpSocket01 {
    public static void main(String[] args) throws UnknownHostException, IOException {
        //创建Socket对象
        Socket my = new Socket("127.0.0.1",9999);
        //发送消息
        OutputStream fis = my.getOutputStream();
        fis.write("你好".getBytes());
        
        //接受消息
        InputStream fos = my.getInputStream();
                
        //定义一个中转站
        byte[] buf = new byte[1024];
        int len = 0;    //获取字符
        while((len = fos.read(buf)) != -1) {
            System.out.println(new String(buf,0,len));
        }
        //关闭IO
        if (fos != null) {
            fos.close();
        }
        if (fis != null) {
            fis.close();
        }
        
        if (my != null) {
            my.close();
        }
    }
}


JAVA 全系列/第二阶段:JAVA 基础深化和提高/网络编程(旧) 2693楼

看完了这节课的视频以后,总结了下面的内容:

这节课的视频与上次课的视频不同的点在

上次课最后实现的多线程是 多个客户端访问一个服务器的情况,为了让服务器处理多个客户端,使用循环开启多个线程,为每一个客户端开启一个线程来处理服务器端接收信息及相应信息的操作。

while(true){

      Socket socket=server.accept();//接受多个客户端的连接

      ServerThread st=new ServerThread(socket);

      new Thread(st).start();

}


这次课的多线程是客户端的多线程,是单个客户端和单个服务器端之间,单个客户端想跟单个服务器端多次聊天,如果是单次聊天,按视频里最开始的写法(没有多线程的),客户端就只能先,通过BufferedReader包装键盘录入发送信息,后获取响应,并且只有一次聊天的机会,socket就结束了。

为了解决这两个问题:

1)客户端想多次聊天

2)客户端能不能不要总是先发送,后接受信息,能主动接受信息吗?

解决:

1) 循环,循环发送信息,循环读取信息,就可以多次聊天了

2)怎么能决定先发送还是先接受,如果写成以前死的代码,就是先发送后接受,想先接受怎么办,多线程,建立一个发送线程,一个接受线程,让2个线程开启,cpu不断的切换2个线程,有内容发送到客户端来,就主动去接受,就可以先接受了。这边键盘录入,用户想发送的时候按下回车,就可以向输入流中写数据,创造输入的字符串,有内容想发送到服务端,就会触发发送线程。


我分析的视频中的代码:

image.png


一个客户端 一个服务端

服务端只接受一次的socket对象,后面的多次聊天都是因为 

客户端一直开启线程,等待用户键盘录入的回车,从而发送数据,

服务器端接收到数据就一直执行while(true),不停的readUTF() 和writeUTF("收到了");


至于我看到有同学问为什么服务器端能一直读取,我想是因为while(true)里面的readUTF()在不停执行,只要在流里读到东西就会读取出来,在流里东西可用,检测到流末尾或者抛出异常前,此方法一直阻塞,等待读取的字符串。这里需要老师验证一下我说的,是不是我分析的这个原因******。


还有一个问题******,就是在客户端线程类:Send类的send方法中写出字符串的时候,为什么要flush一下。

我猜是因为:这里也需要老师解答一下:

1)每次写出之后flush是为了提高效率,如果一直不flush,缓冲区如果满了就会造成缓冲区堵塞

2)视频里的案例没有关闭流,关闭流的操作里会刷新,但是没有关闭流,一直开着,所以在这里刷新一下。

ps:

什么时候字节流需要刷新:

如果你的流入数据不是特别多,可以都操作结束了关流时写入,那么如果你的数据特别多,你把他们全都放到流中就会占用内存吧,如果占用够多就会死机吧?然后结果你明白了!你读取数据失败了!或者説你担心万一死机了数据没有保存不是很坑!所以你可以选择什么时候刷新下

所以当传入的数据特别大,又担心什么时候会突然停止的时候,字节流可以在某些地方刷新一下。

image.png


以上提到的两个地方都需要老师解答一下(带******号的),还有提到的其他地方如果有不太对的地方,也希望老师指导一下,谢谢!

JAVA 全系列/第二阶段:JAVA 基础深化和提高/网络编程(旧) 2696楼
JAVA 全系列/第二阶段:JAVA 基础深化和提高/IO 流技术(旧) 2697楼

容器中有两个同名hello对象,如果用list.remove("hello"),为何默认删除索引是0的这个,如果要删除后面的hello只能根据索引删除吗?


package string;


import java.util.List;
import java.util.ArrayList;
import java.util.Scanner;

public class TestArrayList {
    public static void main(String[] args) {
        //创建集合对象,接口new实现类
        List list = new ArrayList();
        //1.添加add(Object obj)
        list.add("hello");
        list.add(123);//自动装箱

        //2.集合中元素的个数size()
        System.out.println(list.size());
        System.out.println("集合是否为空"+list.isEmpty());
        //3.addAll(collection c)集合中元素添加到其他集合中
        List list2=new ArrayList();
        list2.add("hello");
        list2.add(123);
        list.addAll(list2);
        System.out.println(list);
        System.out.println("集合中元素个数"+list.size());
        //4.删除
        System.out.println("根据对象去删除");
        list.remove("hello");
        System.out.println(list);
        list.remove(new Integer(123));//索引
        System.out.println(list);
        list.remove(0);//根据索引去删除
        System.out.println(list);
        list.add("world");
        System.out.println(list);
    }
}


JAVA 全系列/第二阶段:JAVA 基础深化和提高/容器(旧) 2698楼
JAVA 全系列/第二阶段:JAVA 基础深化和提高/IO 流技术(旧) 2700楼

课程分类

百战程序员微信公众号

百战程序员微信小程序

©2014-2025百战汇智(北京)科技有限公司 All Rights Reserved 北京亦庄经济开发区科创十四街 赛蒂国际工业园
网站维护:百战汇智(北京)科技有限公司
京公网安备 11011402011233号    京ICP备18060230号-3    营业执照    经营许可证:京B2-20212637