Utilisation de inputStream.available ()
Il est toujours acceptable que System.in.available () renvoie 0.
J'ai trouvé le contraire - il renvoie toujours la meilleure valeur pour le nombre d'octets disponibles. Javadoc pour InputStream.available()
:
Returns an estimate of the number of bytes that can be read (or skipped over)
from this input stream without blocking by the next invocation of a method for
this input stream.
Une estimation est inévitable en raison du timing / de l'obsolescence. Le chiffre peut être une sous-estimation ponctuelle car de nouvelles données arrivent constamment. Cependant, il «rattrape» toujours le prochain appel - il doit tenir compte de toutes les données arrivées, sauf celles qui arrivent juste au moment du nouvel appel. Le renvoi permanent de 0 lorsqu'il y a des données échoue à la condition ci-dessus.
Première mise en garde: les sous-classes concrètes d'InputStream sont responsables de available ()
InputStream
est une classe abstraite. Il n'a aucune source de données. Cela n'a aucun sens pour lui d'avoir des données disponibles. Par conséquent, javadoc pour available()
indique également:
The available method for class InputStream always returns 0.
This method should be overridden by subclasses.
Et en effet, les classes de flux d'entrée concrètes remplacent available (), fournissant des valeurs significatives, et non des 0 constants.
Deuxième mise en garde: assurez-vous d'utiliser le retour chariot lors de la saisie d'une entrée dans Windows.
Si vous utilisez System.in
, votre programme ne reçoit l'entrée que lorsque votre shell de commande le transmet. Si vous utilisez la redirection de fichiers / tubes (par exemple un fichier> java myJavaApp ou une commande | java myJavaApp), les données d'entrée sont généralement transmises immédiatement. Cependant, si vous tapez manuellement l'entrée, le transfert des données peut être retardé. Par exemple, avec le shell Windows cmd.exe, les données sont mises en mémoire tampon dans le shell cmd.exe. Les données ne sont transmises au programme java en cours d'exécution qu'après un retour chariot (control-m ou <enter>
). C'est une limitation de l'environnement d'exécution. Bien sûr, InputStream.available () renverra 0 tant que le shell met les données en mémoire tampon - c'est un comportement correct; il n'y a pas de données disponibles à ce stade. Dès que les données sont disponibles depuis le shell, la méthode renvoie une valeur> 0. NB: Cygwin utilise cmd.
Solution la plus simple (pas de blocage, donc pas de timeout requis)
Utilisez simplement ceci:
byte[] inputData = new byte[1024];
int result = is.read(inputData, 0, is.available());
// result will indicate number of bytes read; -1 for EOF with no data read.
Ou équivalent,
BufferedReader br = new BufferedReader(new InputStreamReader(System.in, Charset.forName("ISO-8859-1")),1024);
// ...
// inside some iteration / processing logic:
if (br.ready()) {
int readCount = br.read(inputData, bufferOffset, inputData.length-bufferOffset);
}
Solution plus riche (remplit au maximum le tampon dans le délai d'expiration)
Déclarez ceci:
public static int readInputStreamWithTimeout(InputStream is, byte[] b, int timeoutMillis)
throws IOException {
int bufferOffset = 0;
long maxTimeMillis = System.currentTimeMillis() + timeoutMillis;
while (System.currentTimeMillis() < maxTimeMillis && bufferOffset < b.length) {
int readLength = java.lang.Math.min(is.available(),b.length-bufferOffset);
// can alternatively use bufferedReader, guarded by isReady():
int readResult = is.read(b, bufferOffset, readLength);
if (readResult == -1) break;
bufferOffset += readResult;
}
return bufferOffset;
}
Ensuite, utilisez ceci:
byte[] inputData = new byte[1024];
int readCount = readInputStreamWithTimeout(System.in, inputData, 6000); // 6 second timeout
// readCount will indicate number of bytes read; -1 for EOF with no data read.
is.available() > 1024
cette suggestion échoue. Il y a certainement des flux qui renvoient zéro. SSLSockets par exemple jusqu'à récemment. Vous ne pouvez pas vous y fier.En supposant que votre flux ne soit pas soutenu par une socket (vous ne pouvez donc pas l'utiliser
Socket.setSoTimeout()
), je pense que la manière standard de résoudre ce type de problème est d'utiliser un Future.Supposons que j'ai l'exécuteur et les flux suivants:
J'ai un écrivain qui écrit des données puis attend 5 secondes avant d'écrire le dernier élément de données et de fermer le flux:
La manière normale de lire ceci est la suivante. La lecture bloquera indéfiniment pour les données et cela se termine en 5s:
qui sort:
S'il y avait un problème plus fondamental, comme l'écrivain ne répondant pas, le lecteur bloquerait pour toujours. Si j'emballe la lecture dans un futur, je peux alors contrôler le délai comme suit:
qui sort:
Je peux attraper le TimeoutException et faire tout le nettoyage que je veux.
la source
executer.shutdownNow
ne tuera pas le fil. Il essaiera de l'interrompre, sans effet. Il n'y a pas de nettoyage possible et c'est un problème grave.Si votre InputStream est sauvegardé par un Socket, vous pouvez définir un délai d'expiration de Socket (en millisecondes) à l'aide de setSoTimeout . Si l'appel read () ne se débloque pas dans le délai spécifié, il lèvera une SocketTimeoutException.
Assurez-vous simplement d'appeler setSoTimeout sur le Socket avant de faire l'appel read ().
la source
Je remettrais en question l'énoncé du problème plutôt que de l'accepter aveuglément. Vous n'avez besoin que de délais d'expiration depuis la console ou via le réseau. Si vous possédez ces derniers
Socket.setSoTimeout()
etHttpURLConnection.setReadTimeout()
que les deux font exactement ce qui est nécessaire, à condition de les configurer correctement lorsque vous les construisez / les acquérez. Le laisser à un point arbitraire plus tard dans l'application lorsque tout ce que vous avez est le InputStream est une mauvaise conception conduisant à une implémentation très délicate.la source
Je n'ai pas utilisé les classes du package Java NIO, mais il semble qu'elles pourraient être utiles ici. Plus précisément, java.nio.channels.Channels et java.nio.channels.InterruptibleChannel .
la source
Voici un moyen d'obtenir un NIO FileChannel à partir de System.in et de vérifier la disponibilité des données à l'aide d'un délai d'expiration, qui est un cas particulier du problème décrit dans la question. Exécutez-le sur la console, ne tapez aucune entrée et attendez les résultats. Il a été testé avec succès sous Java 6 sur Windows et Linux.
Fait intéressant, lorsque vous exécutez le programme dans NetBeans 6.5 plutôt que sur la console, le délai d'attente ne fonctionne pas du tout et l'appel à System.exit () est en fait nécessaire pour tuer les threads zombies. Ce qui se passe, c'est que le thread d'interruption se bloque (!) Lors de l'appel à reader.interrupt (). Un autre programme de test (non montré ici) essaie également de fermer le canal, mais cela ne fonctionne pas non plus.
la source
Comme jt l'a dit, NIO est la meilleure (et la bonne) solution. Si vous êtes vraiment coincé avec un InputStream, vous pouvez soit
Créez un thread dont le travail exclusif consiste à lire à partir de InputStream et à placer le résultat dans un tampon qui peut être lu à partir de votre thread d'origine sans blocage. Cela devrait bien fonctionner si vous n'avez qu'une seule instance du flux. Sinon, vous pourrez peut-être tuer le thread à l'aide des méthodes obsolètes de la classe Thread, bien que cela puisse provoquer des fuites de ressources.
Fiez-vous à isAvailable pour indiquer les données qui peuvent être lues sans blocage. Cependant, dans certains cas (comme avec Sockets), il peut prendre une lecture potentiellement bloquante pour isAvailable pour signaler autre chose que 0.
la source
Socket.setSoTimeout()
est une solution tout aussi correcte et beaucoup plus simple. OuHttpURLConnection.setReadTimeout()
.Inspiré par cette réponse, j'ai proposé une solution un peu plus orientée objet.
Ceci n'est valable que si vous avez l'intention de lire des caractères
Vous pouvez remplacer BufferedReader et implémenter quelque chose comme ceci:
Voici un exemple presque complet.
Je retourne 0 sur certaines méthodes, vous devriez le changer en -2 pour répondre à vos besoins, mais je pense que 0 est plus approprié avec le contrat BufferedReader. Rien de mal ne s'est produit, il a juste lu 0 caractères. La méthode readLine est un horrible tueur de performances. Vous devez créer un tout nouveau BufferedReader si vous souhaitez réellement utiliser readLin e. À l'heure actuelle, ce n'est pas thread-safe. Si quelqu'un appelle une opération pendant que readLines attend une ligne, cela produira des résultats inattendus
Je n'aime pas retourner -2 où je suis. Je jetterais une exception car certaines personnes peuvent simplement vérifier si int <0 pour considérer EOS. Quoi qu'il en soit, ces méthodes prétendent que "ne peut pas bloquer", vous devriez vérifier si cette déclaration est réellement vraie et ne pas la surcharger.
la source